freeCodeCamp/guide/russian/c/pointers/index.md

320 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
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*/
```