Лекция 11: Указатели в языке Си
Предисловие
Warning
'Это, пожалуй, самая сложная и самая важная тема во всём курсе. Без понимания указателей дальнейшее изучении си будет бессмысленным. Указатели – простая концепция, логичная, но требующая внимания к деталям.' - L-10.YkazatelivyazikeSi.pptx
Введение
Указатель — это переменная адресного типа, которая содержит адрес некоторого элемента данных (переменной, константы, функции, структуры). Указатель, как и другие переменные, имеет тип данных и идентификатор. Однако указатели используются таким образом, который отличается от обычных переменных.
Ключевые принципы указателей:
- Указатель — это переменная, в которой хранится адрес.
- Утверждение вида «A указывает на B» означает, что «A содержит адрес B».
Каждая переменная в программе — это объект, имеющий имя и значение. Имя переменной соответствует адресу выделенного для неё участка памяти, а значение — содержимому этого участка. Адрес переменной можно получить с помощью унарной операции &
, а значение по адресу — с помощью *
.
Пример:
int a = 5;
int *p = &a; // p хранит адрес переменной a
printf("%d", *p); // разыменование указателя возвращает значение 5
Операции над указателями
В языке Си с указателями допустимы следующие операции:
- Присваивание: указателю можно присвоить адрес.
- Разыменование: операция
*
получает значение по адресу. - Получение адреса указателя: операция
&
возвращает адрес переменной. - Арифметика указателей: добавление или вычитание целых чисел изменяет адрес, хранящийся в указателе.
- Сравнение указателей: например,
==
,!=
,<
,>
и другие.
Пример:
int arr[] = {10, 20, 30};
int *p = arr;
p++; // теперь p указывает на второй элемент массива
printf("%d", *p); // вывод: 20
Нетипизированный указатель (void *)
void *
— это указатель, который может хранить адрес любого типа данных, но разыменование такого указателя напрямую невозможно.
Пример:
void *ptr;
int a = 10;
ptr = &a;
int *p = (int *)ptr;
printf("%d", *p); // вывод: 10
Указатели и const
Указатели могут быть:
const int *p
— нельзя изменить значение, на которое указываетp
.int *const p
— нельзя изменить сам указательp
.const int *const p
— ни значение, ни указатель менять нельзя.
Пример:
const int a = 10;
const int *p = &a; // нельзя изменить значение через *p
Указатель на указатель
Указатель может указывать на другой указатель. Это называется указатель на указатель и используется для работы с многомерными массивами и сложными структурами данных.
Пример:
int a = 10;
int *p = &a;
int **pp = &p;
printf("%d", **pp); // вывод: 10
Указатель файла
Указатель файла связывает систему ввода-вывода языка Си. Для работы с файлами подключается библиотека <stdio.h>
.
Пример:
FILE *file_ptr = fopen("data.txt", "r");
if (file_ptr != NULL) {
// работа с файлом
fclose(file_ptr);
}
Указатели и массивы
Имя массива — это константный указатель на его первый элемент. Элементы массива можно обрабатывать через арифметику указателей:
Пример:
int arr[] = {10, 20, 30};
int *p = arr;
printf("%d", *(p + 2)); // вывод: 30
Передача массива в функцию может осуществляться через указатель:
void printArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", *(arr + i));
}
}
Указатели на функции
Указатели могут хранить адреса функций. Это позволяет передавать функции как параметры.
Пример:
int add(int a, int b) { return a + b; }
int (*func_ptr)(int, int) = add;
printf("%d", func_ptr(3, 4)); // вывод: 7
Выводы
- Указатель — это переменная, хранящая адрес другой переменной.
- Операции
*
(разыменование) и&
(взятие адреса) являются основными для работы с указателями. - Указатели играют важную роль в работе с массивами, функциями и динамическим выделением памяти.
- Работа с указателями требует аккуратности, так как ошибки могут привести к сбоям программы.