320 lines
17 KiB
Markdown
320 lines
17 KiB
Markdown
|
---
|
|||
|
title: Pointers
|
|||
|
localeTitle: указатели
|
|||
|
---
|
|||
|
# Указатели в C
|
|||
|
|
|||
|
К настоящему моменту вы должны знать, что C - это низкоуровневый язык, и ничто не показывает, что лучше, чем указатели. Указатели - это переменные, которые дают вам значение переменной, «указывая» на ячейку памяти, а не сохраняя значение самой переменной. Это позволяет использовать некоторые полезные трюки, а также дает доступ к массивам и обработке файлов, среди прочего.
|
|||
|
|
|||
|
#
|
|||
|
```
|
|||
|
type *var-name;
|
|||
|
```
|
|||
|
|
|||
|
## Создание и использование указателя
|
|||
|
|
|||
|
```c
|
|||
|
#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, что это переменная указателя.
|
|||
|
|
|||
|
Следующая строка сообщает компилятору, где это где-то еще на самом деле. Используя `&` таким образом, он становится «оператором разыменования» и возвращает ячейку памяти переменной, на которую она смотрит.
|
|||
|
|
|||
|
Имея это в виду, давайте еще раз взглянем на этот кусок кода:
|
|||
|
|
|||
|
```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` значение ячейки памяти, используя команду `&` .
|
|||
|
|
|||
|
Теперь давайте посмотрим, что означает ссылка на ячейку памяти для вашего кода:
|
|||
|
|
|||
|
```c
|
|||
|
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` .
|
|||
|
|
|||
|
Вы можете объявить несколько указателей в одном выражении как со стандартными переменными, например:
|
|||
|
|
|||
|
```c
|
|||
|
int *x, *y;
|
|||
|
```
|
|||
|
|
|||
|
Обратите внимание, что `*` требуется перед каждой переменной. Это связано с тем, что указатель считается частью переменной, а не частью типа данных.
|
|||
|
|
|||
|
## Практическое использование указателей
|
|||
|
|
|||
|
### Массивы
|
|||
|
|
|||
|
Наиболее распространенное приложение указателя находится в массиве. Массивы, о которых вы прочтете позже, допускают группу переменных. Вам не нужно иметь дело с `*` и `&` чтобы использовать массивы, но это то, что они делают за кулисами.
|
|||
|
|
|||
|
### функции
|
|||
|
|
|||
|
Иногда вы хотите настроить значение переменной внутри функции, но если вы просто передадите значение переменной по переменной, функция будет работать с копией вашей переменной вместо самой переменной. Если вместо этого вы передаете указатель, указывающий на ячейку памяти переменной, вы можете получить доступ и изменить ее из-за пределов ее обычной области. Это связано с тем, что вы касаетесь самого исходного места памяти, позволяя вам что-то корректировать в функции и вносить изменения в другое место. В отличие от «вызова по значению», это называется «вызов по ссылке».
|
|||
|
|
|||
|
Следующая программа меняет значения двух переменных внутри выделенной функции `swap` . Для этого переменные передаются по ссылке.
|
|||
|
|
|||
|
```c
|
|||
|
/* 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 в основной функции.
|
|||
|
|
|||
|
### УКАЗАНИЯ КАК ПАРАМЕТРЫ К ФУНКЦИИ
|
|||
|
|
|||
|
когда мы передаем какой-либо параметр в функцию, мы делаем копию параметра. посмотрим с примером
|
|||
|
|
|||
|
```C
|
|||
|
#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` функции за пределами их нормального объема. После выполнения функции две переменные теперь меняют местами свои значения, как видно на выходе.
|
|||
|
|
|||
|
### Трюки с местами памяти
|
|||
|
|
|||
|
Всякий раз, когда этого можно избежать, это хорошая идея, чтобы ваш код легко читал и понимал. В лучшем случае ваш код расскажет историю - он будет легко читать имена переменных и имеет смысл, если вы прочитаете его вслух, и вы будете использовать случайный комментарий, чтобы выяснить, что делает строка кода.
|
|||
|
|
|||
|
Из-за этого вы должны быть осторожны при использовании указателей. Легко сделать что-то запутанное для вас, чтобы отлаживать или кого-то еще читать. Тем не менее, с ними можно сделать довольно аккуратные вещи.
|
|||
|
|
|||
|
Взгляните на этот код, который превращает что-то от верхнего к нижнему регистру:
|
|||
|
|
|||
|
```c
|
|||
|
#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, указывающего на. Следующий фрагмент кода объясняет указатель на переменную
|
|||
|
|
|||
|
```c
|
|||
|
#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.
|
|||
|
|
|||
|
```c
|
|||
|
#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;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
# Постоянный указатель на переменную
|
|||
|
|
|||
|
В этом случае мы можем изменить значение переменной, на которую указывает указатель. Но мы не можем изменить указатель, чтобы указать на другая переменная.
|
|||
|
|
|||
|
```c
|
|||
|
#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;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
# постоянный указатель на константу
|
|||
|
|
|||
|
Выше декларация является постоянным указателем на постоянную переменную, что означает, что мы не можем изменить значение, указанное указателем, а также не указывать указатель на другую переменную.
|
|||
|
|
|||
|
```c
|
|||
|
#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)` возвращает только объем памяти, используемый самой переменной указателя
|
|||
|
|
|||
|
2) оператор &
|
|||
|
|
|||
|
* & array - это псевдоним для & array \[0\] и возвращает адрес первого элемента в массиве
|
|||
|
* & pointer возвращает адрес указателя
|
|||
|
|
|||
|
3) строковая литерала инициализации массива символов
|
|||
|
|
|||
|
* `char array[] = “abc”` устанавливает первые четыре элемента в массиве в 'a', 'b', 'c' и '\\ 0'
|
|||
|
* `char *pointer = “abc”` устанавливает указатель на адрес строки "abc" (который может храниться в постоянной памяти и, следовательно, неизменен)
|
|||
|
|
|||
|
4) Переменной переменной может быть присвоено значение, тогда как переменной массива быть не может.
|
|||
|
|
|||
|
```c
|
|||
|
int a[10];
|
|||
|
int *p;
|
|||
|
p = a; /*legal*/
|
|||
|
a = p; /*illegal*/
|
|||
|
```
|
|||
|
|
|||
|
5) Разрешена арифметика по переменной указателя.
|
|||
|
|
|||
|
```c
|
|||
|
p++; /*Legal*/
|
|||
|
a++; /*illegal*/
|
|||
|
|
|||
|
```
|