Files
freeCodeCamp/guide/russian/c/pointers/index.md
2019-04-11 22:11:06 +04:00

17 KiB
Raw Blame History

title, localeTitle
title localeTitle
Pointers указатели

Указатели в C

К настоящему моменту вы должны знать, что C - это низкоуровневый язык, и ничто не показывает, что лучше, чем указатели. Указатели - это переменные, которые дают вам значение переменной, «указывая» на ячейку памяти, а не сохраняя значение самой переменной. Это позволяет использовать некоторые полезные трюки, а также дает доступ к массивам и обработке файлов, среди прочего.

type *var-name; 

Создание и использование указателя

#include <stdio.h> 
 
 int main(void){ 
    double my_double_variable = 10.1; 
    double *my_pointer; 
 
    my_pointer = &my_double_variable; 
 
    printf("value of my_double_variable: %f\n", my_double_variable); 
 
    ++my_double_variable; 
 
    printf("value of my_pointer: %f\n", *my_pointer); 
 
    return 0; 
 } 

Вывод:

value of my_double_variable: 10.100000 
 value of my_pointer: 11.100000 

В этом коде есть два объявления. Первая - это типичная инициализация переменных, которая создает double и устанавливает ее равной 10.1. Новое в наших объявлениях - использование * . Звездочка ( * ) обычно используется для умножения, но когда мы ее используем, помещая ее перед переменной, она сообщает C, что это переменная указателя.

Следующая строка сообщает компилятору, где это где-то еще на самом деле. Используя & таким образом, он становится «оператором разыменования» и возвращает ячейку памяти переменной, на которую она смотрит.

Имея это в виду, давайте еще раз взглянем на этот кусок кода:

double *my_pointer; 
 // my_pointer now stored the address of my_double_variable 
 my_pointer = &my_double_variable; 

my_pointer был объявлен, и он был объявлен как указатель. Компилятор C теперь знает, что my_pointer будет указывать на ячейку памяти. Следующая строка присваивает my_pointer значение ячейки памяти, используя команду & .

Теперь давайте посмотрим, что означает ссылка на ячейку памяти для вашего кода:

    printf("value of my_double_variable: %f\n", my_double_variable); 
 
    // Same as my_double_variable = my_double_variable + 1 
    // In human language, adding one to my_double_variable 
    ++my_double_variable; 
 
    printf("value of my_pointer: %f\n", *my_pointer); 

Обратите внимание, что для получения значения данных на *my_pointer вам нужно сообщить C, что вы хотите получить значение, на которое указывает переменная. Попробуйте запустить этот код без этой звездочки, и вы сможете распечатать местоположение памяти, потому что это то, что на my_variable деле my_variable переменная my_variable .

Вы можете объявить несколько указателей в одном выражении как со стандартными переменными, например:

int *x, *y; 

Обратите внимание, что * требуется перед каждой переменной. Это связано с тем, что указатель считается частью переменной, а не частью типа данных.

Практическое использование указателей

Массивы

Наиболее распространенное приложение указателя находится в массиве. Массивы, о которых вы прочтете позже, допускают группу переменных. Вам не нужно иметь дело с * и & чтобы использовать массивы, но это то, что они делают за кулисами. В большинстве случаев массивы используются для строк. Так как строки не определены в С, они попросту создаются как массив из символов, в большинстве случаев обозначенными как

char * stringName;

функции

Иногда вы хотите настроить значение переменной внутри функции, но если вы просто передадите значение переменной по переменной, функция будет работать с копией вашей переменной вместо самой переменной. Если вместо этого вы передаете указатель, указывающий на ячейку памяти переменной, вы можете получить доступ и изменить ее из-за пределов ее обычной области. Это связано с тем, что вы касаетесь самого исходного места памяти, позволяя вам что-то корректировать в функции и вносить изменения в другое место. В отличие от «вызова по значению», это называется «вызов по ссылке».

Следующая программа меняет значения двух переменных внутри выделенной функции swap . Для этого переменные передаются по ссылке.

 /* C Program to swap two numbers using pointers and function. */ 
 #include <stdio.h> 
 void swap(int *n1, int *n2); 
 
 int main() 
 { 
    int num1 = 5, num2 = 10; 
 
    // address of num1 and num2 is passed to the swap function 
    swap( &num1, &num2); 
    printf("Number1 = %d\n", num1); 
    printf("Number2 = %d", num2); 
    return 0; 
 } 
 
 void swap(int * n1, int * n2) 
 { 
    // pointer n1 and n2 points to the address of num1 and num2 respectively 
    int temp; 
    temp = *n1; 
    *n1 = *n2; 
    *n2 = temp; 
 } 

Вывод

Number1 = 10 
 Number2 = 5 

Адреса, или ячейки памяти, из num1 и num2 передаются функции swap и представлены указателями *n1 и *n2 внутри функции. Таким образом, теперь указатели n1 и n2 указывают на адреса num1 и num2 соответственно.

Итак, теперь указатель n1 и n2 указывает на адрес num1 и num2 соответственно.

Когда значение указателей изменяется, значение в указанной области памяти также изменяется соответственно.

Следовательно, изменения, внесенные в * n1 и * n2, отражаются в num1 и num2 в основной функции.

УКАЗАНИЯ КАК ПАРАМЕТРЫ К ФУНКЦИИ

когда мы передаем какой-либо параметр в функцию, мы делаем копию параметра. посмотрим с примером

#include <stdio.h> 
 
 void func(int); 
 
 int main(void) { 
    int a = 11; 
    func(a); 
    printf("%d",a);// print 11 
 
 
    return 0; 
 } 
 void func(int a){ 
 a=5 
 printf("%d",a);//print 5 
 } 

В приведенном выше примере мы меняем значение целого числа a в функции func, но мы по-прежнему получаем 11 в основной функции. Это происходит потому, что в функции копия целого числа a передается как параметр, поэтому в этой функции у нас нет доступа к «a», который находится в основной функции.

Итак, как бы вы могли изменить значение целого числа, определенного в main, используя другую функцию? Здесь POINTERS входит в роль. когда мы поставляем указатель в качестве параметра, у нас есть доступ к адресу этого параметра, и мы могли бы с любым тигом с этим параметром, и результат будет показан везде. Ниже приведен пример, который делает то же самое, что мы хотим ...

При разыменовании n1 и n2 теперь мы можем изменить память, на которую указывают n1 и n2 . Это позволяет изменить значение двух переменных num1 и num2 объявленным в main функции за пределами их нормального объема. После выполнения функции две переменные теперь меняют местами свои значения, как видно на выходе.

Трюки с местами памяти

Всякий раз, когда этого можно избежать, это хорошая идея, чтобы ваш код легко читал и понимал. В лучшем случае ваш код расскажет историю - он будет легко читать имена переменных и имеет смысл, если вы прочитаете его вслух, и вы будете использовать случайный комментарий, чтобы выяснить, что делает строка кода.

Из-за этого вы должны быть осторожны при использовании указателей. Легко сделать что-то запутанное для вас, чтобы отлаживать или кого-то еще читать. Тем не менее, с ними можно сделать довольно аккуратные вещи.

Взгляните на этот код, который превращает что-то от верхнего к нижнему регистру:

#include <stdio.h> 
 #include <ctype.h> 
 
 char *lowerCase (char *string) { 
    char *p = string; 
    while (*p) { 
        if (isupper(*p)) *p = tolower(*p); 
        p++; 
    } 
    return string; 
 } 

Это начинается с того, что вы берете строку (что-то, о чем вы узнаете, когда попадете в массивы) и пройдите через каждое место. Обратите внимание на p ++. Это увеличивает указатель, что означает, что он смотрит на следующую ячейку памяти. Каждая буква - это ячейка памяти, поэтому указатель смотрит на каждую букву и решает, что делать для каждого.

Const Qualifer

Определитель const может быть применен к объявлению любой переменной, чтобы указать, что его значение не будет изменено (что зависит от того, где хранятся константные переменные, мы можем изменить значение константной переменной с помощью указателя).

Указатель на переменную

Мы можем изменить значение ptr, и мы также можем изменить значение объекта ptr, указывающего на. Следующий фрагмент кода объясняет указатель на переменную

#include <stdio.h> 
 int main(void) 
 { 
    int i = 10; 
    int j = 20; 
    int *ptr = &i;        /* pointer to integer */ 
    printf("*ptr: %d\n", *ptr); 
 
    /* pointer is pointing to another variable */ 
    ptr = &j; 
    printf("*ptr: %d\n", *ptr); 
 
    /* we can change value stored by pointer */ 
    *ptr = 100; 
    printf("*ptr: %d\n", *ptr); 
 
    return 0; 
 } 

Указатель на константу

Мы можем изменить указатель на любую другую целочисленную переменную, но не можем изменить значение объекта (объекта), указанное с помощью указателя ptr.

#include <stdio.h> 
 int main(void) 
 { 
    int i = 10; 
    int j = 20; 
    const int *ptr = &i;    /* ptr is pointer to constant */ 
 
    printf("ptr: %d\n", *ptr); 
    *ptr = 100;        /* error: object pointed cannot be modified 
                     using the pointer ptr */ 
 
    ptr = &j;          /* valid */ 
    printf("ptr: %d\n", *ptr); 
 
    return 0; 
 } 

Постоянный указатель на переменную

В этом случае мы можем изменить значение переменной, на которую указывает указатель. Но мы не можем изменить указатель, чтобы указать на другая переменная.

#include <stdio.h> 
 int main(void) 
 { 
   int i = 10; 
   int j = 20; 
   int *const ptr = &i;    /* constant pointer to integer */ 
 
   printf("ptr: %d\n", *ptr); 
 
   *ptr = 100;    /* valid */ 
   printf("ptr: %d\n", *ptr); 
 
   ptr = &j;        /* error */ 
   return 0; 
 } 

постоянный указатель на константу

Выше декларация является постоянным указателем на постоянную переменную, что означает, что мы не можем изменить значение, указанное указателем, а также не указывать указатель на другую переменную.

#include <stdio.h> 
 
 int main(void) 
 { 
    int i = 10; 
    int j = 20; 
    const int *const ptr = &i; /* constant pointer to constant integer */ 
 
    printf("ptr: %d\n", *ptr); 
 
    ptr = &j;            /* error */ 
    *ptr = 100;        /* error */ 
 
    return 0; 
 } 

Прежде чем продолжить ...

Обзор

  • Указатели являются переменными, но вместо сохранения значения они сохраняют местоположение памяти.
  • * и & используются для доступа к значениям в ячейках памяти и для доступа к ячейкам памяти, соответственно.
  • Указатели полезны для некоторых из основных особенностей C.

Указатель против массива в C

В большинстве случаев обращения с указателями и массивами могут рассматриваться как действующие одинаково, основными исключениями являются:

  1. оператор sizeof
  • sizeof(array) возвращает объем памяти, используемый всеми элементами массива
  • sizeof(pointer) возвращает только объем памяти, используемый самой переменной указателя
  1. оператор &
  • & array - это псевдоним для & array [0] и возвращает адрес первого элемента в массиве
  • & pointer возвращает адрес указателя
  1. строковая литерала инициализации массива символов
  • char array[] = “abc” устанавливает первые четыре элемента в массиве в 'a', 'b', 'c' и '\ 0'
  • char *pointer = “abc” устанавливает указатель на адрес строки "abc" (который может храниться в постоянной памяти и, следовательно, неизменен)
  1. Переменной переменной может быть присвоено значение, тогда как переменной массива быть не может.
    int a[10]; 
    int *p; 
    p = a; /*legal*/ 
    a = p; /*illegal*/ 
  1. Разрешена арифметика по переменной указателя.
    p++; /*Legal*/ 
    a++; /*illegal*/