- •Функции.
- •Вызов функции с переменным числом параметров
- •Функция main и её параметры.
- •Директивы препроцессора (прекомпилера).
- •Объявление указателей.
- •Модификатор const.
- •Операции.
- •Указатели на различные типы.
- •Указатель на void.
- •Применение указателей для передачи данных между функциями.
- •Массивы.
- •Индексация массивов.
- •Хранение массива в памяти. Адреса элементов. Хранение массива в памяти.
- •Массивы и константные указатели.
- •Статическое и динамическое выделение памяти.
- •Функции calloc, malloc, free
- •Функция realloc
- •Передача массивов в качестве аргументов функции.
- •Указатели на функции.
- •Библиотеки функций.
- •Функции форматированного ввода-вывода.
- •Функция printf().
- •%[Флаги] [Ширина] [.Точность] [{h | l | I | i32 | i64}]тип
- •Для чего нужен форматированный вывод.
- •Функция scanf().
- •Функции sprintf() и sscanf().
- •Функции fprintf() и fscanf().
- •Функции неформатированного ввода-вывода.
- •Работа со строковыми данными (стрингами). Представление строковых данных в языке c.
- •Функции работы со строками.
- •Потоковый ввод-вывод
- •Функции форматированного ввода-вывода.
- •Функция printf().
- •%[Флаги] [Ширина] [.Точность] [{h | l | I | i32 | i64}]тип
- •Для чего нужен форматированный вывод.
- •Функция scanf().
- •Функции sprintf() и sscanf().
- •Функции fprintf() и fscanf().
- •Функции неформатированного ввода-вывода.
- •Функции работы с файлами.
- •Потоковый ввод-вывод
- •Работа с потоками
- •Курсор.
- •Ввод-вывод отдельных символов и строк.
- •Форматированный ввод-вывод информации в файл.
- •Блочный потоковый ввод-вывод
- •Смена текущей позиции в файле. Проверка конца файла.
- •Функции доступа к файлам нижнего уровня.
- •Методы сортировки данных.
- •Введение
- •Сравнение методов сортировки
- •Программная реализация алгоритмов сортировки
- •Метод пузырька.
- •Метод обмена.
- •Метод вставки.
- •Метод Шелла.
- •Метод кучи (бинарной кучи).
- •Очередь
- •Линейный список
- •Физическое (машинное) представление линейных списков
- •Программные реализации структур данных. Стек. Реализация в виде массива.
- •Стек. Связанное представление.
- •Очереди. Реализация в виде массива.
- •Дерево. Связанное представление.
- •Рекурсивный вызов функций.
- •Структуры. Объединения. Перечисления.
- •Перечисление (enum).
- •Производные типы данных.
- •Структура (struct).
- •Побитовое описание полей структуры.
- •Объявление переменных, реализующих структуру.
- •Доступ к элементам структуры.
- •Объединение (union).
- •Вложенное описание структур и объединений.
- •Описание структур и объединений в виде пользовательского типа.
- •Передача структур и объединений в виде параметров функции.
- •Инициализация структур и объединений.
- •Выгода от использования структур
Указатели на различные типы.
В предыдущих примерах мы рассматривали только указатели на тип char. Однако указатели различаются между собой в зависимости от типа данных (основного или производного) на который он указывает. Этот тип определяет, на какие элементарные участки разбивается область памяти при работе с указателями, а также определяет как будут применяться к ним различные операции.
Например, рассмотрим 2 указателя, один на тип char (однобайтовый), другой – на тип long (четырёхбайтовый):
char * pChar;
long * pLong;
Размер элементарного участка памяти, адресуемого указателем зависит от типа на который он указывает. Таким образом, pChar будет указывать на однобайтовые участки, а pLong – на 4 байтовые.
Как это проявляется на практике ? Пусть мы имеем некоторую область памяти, начинающуюся с адреса 1000, в которой хранятся следующие числа:
Addr=1000 Addr=1004 Addr=1007
1 |
2 |
3 |
4 |
10 |
20 |
30 |
40 |
pChar
pLong
|–*pChar –|
|–––––––––––––– *pLong ––––––––––––––––|
Пусть первоначально оба указателя указывают на один и тот же адрес. Для присвоения необходимо явное преобразование типов, т.к. компилятор не может преобразовать константу 1000 в указатель автоматически.
pChar=(char*)1000;
pLong=(long*)1000;
Теперь воспользуемся операцией чтения памяти. Поскольку тип переменных и их значений, записываемых или читаемых из памяти с помощью оператора *, тождественнен типу, на который указывает указатель, то выражение *pChar вернёт нам значение типа char. Следовательно, *pChar вернёт значение 1.
Операция *pLong вернёт значение типа Long, т.е. четырёхбайтовое значение, записанное в обпамяти, начиная с адреса, записанного в указателе, т.е. с адреса 1000.
Напомню, что адрес ячейки памяти для подавляющего большинства платформ исчисляется в байтах, т.е. размер минимальной ячейки памяти, имеющей собственный адрес, равен 1 байту. Числа, занимающие в памяти более одного байта, хранятся в обратном порядке, т.е. сначала идёт самый младший байт, а последним идёт самый старший байт.
Таким образом, операция *pLong вернёт число, которое хранится по частям в ячейках с адресами 1000, 1001, 1002, 1003. Число это будет равно 04030201h (h – означает, что число записано в шестнадцатиричной форме), в десятичной форме это равно 67305985.
Как видим, даже если значения указателей равны, то, в зависимости от типа, на который они указывают, значения, получаемые с помощью операции * будут совершенно разными.
Значение типа, на который указывает указатель также влиет на смысл арифметических операций, применяемых к нему. В отличие от арифметических типов, операция прибавления или вычитания из указателя целого числа означает не просто изменение адреса, хранящегося в указателе на это целое число, а означает перемещение указателя на заданное количество адресуемых участков.
Продолжим пример, начатый выше. В результате выполнения операций:
pChar=pChar+1;
pLong=pLong+1;
мы получим следующую картину:
Addr=1000 Addr=1004 Addr=1007
1 |
2 |
3 |
4 |
10 |
20 |
30 |
40 |
pChar
pLong
|–*pChar –|
|–––––––––––––– *pLong ––––––––––––––––|
Как видим, значения адресов, хранящихся в указателях, перестали быть равными, хотя операции, на первый взгляд, были произведены полностью идентичные. Поскольку, размер элементарного участка, адресуемого указателем pChar равен 1 байту, то в результате операции +1 он перешёл к следующему участку, т.е. к следующему байту. Указатель pLong также перешёл к следующему участку, но поскольку длина типа long равна 4 байтам, то адрес, хранимый в pLong увеличился на 4 байта.
Таким образом, при совершении операции
Pointer=Pointer+i, где Pointer – указатель, а i – целое число,
физический адрес, хранимый в указателе изменится на i*TypeSize, где TypeSize – размер типа, на который указывает указатель Pointer.
Аналогично работают с указателями операции инкремента (++) и декремента (--).
Операции умножения и деления неприменимы к указателям.