Перейти к содержанию

Лекция 11: Указатели в языке Си

"What the fuck is this piece of shit?" - Джо Пеши


Предисловие

Warning

'Это, пожалуй, самая сложная и самая важная тема во всём курсе. Без понимания указателей дальнейшее изучении си будет бессмысленным. Указатели – простая концепция, логичная, но требующая внимания к деталям.' - L-10.YkazatelivyazikeSi.pptx


Введение

Указатель — это переменная адресного типа, которая содержит адрес некоторого элемента данных (переменной, константы, функции, структуры). Указатель, как и другие переменные, имеет тип данных и идентификатор. Однако указатели используются таким образом, который отличается от обычных переменных.

Ключевые принципы указателей:

  1. Указатель — это переменная, в которой хранится адрес.
  2. Утверждение вида «A указывает на B» означает, что «A содержит адрес B».

Каждая переменная в программе — это объект, имеющий имя и значение. Имя переменной соответствует адресу выделенного для неё участка памяти, а значение — содержимому этого участка. Адрес переменной можно получить с помощью унарной операции &, а значение по адресу — с помощью *.

Пример:

int a = 5;
int *p = &a;  // p хранит адрес переменной a
printf("%d", *p);  // разыменование указателя возвращает значение 5

Операции над указателями

В языке Си с указателями допустимы следующие операции:

  1. Присваивание: указателю можно присвоить адрес.
  2. Разыменование: операция * получает значение по адресу.
  3. Получение адреса указателя: операция & возвращает адрес переменной.
  4. Арифметика указателей: добавление или вычитание целых чисел изменяет адрес, хранящийся в указателе.
  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

Указатели могут быть:

  1. const int *p — нельзя изменить значение, на которое указывает p.
  2. int *const p — нельзя изменить сам указатель p.
  3. 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

Выводы

  • Указатель — это переменная, хранящая адрес другой переменной.
  • Операции * (разыменование) и & (взятие адреса) являются основными для работы с указателями.
  • Указатели играют важную роль в работе с массивами, функциями и динамическим выделением памяти.
  • Работа с указателями требует аккуратности, так как ошибки могут привести к сбоям программы.

I have found, you can find happiness in slavery