
- •Происхождение языка с
- •Язык среднего уровня
- •Структурированный язык
- •Язык программирования
- •Компиляторы против интерпретаторов
- •Вид программ на с
- •Библиотеки и компоновка
- •Раздельная компиляция
- •Карта памяти с-программы
- •Переменные, константы, операторы и выражения
- •Идентификаторы
- •Типы данных
- •Модификаторы типов
- •Модификаторы доступа
- •Объявление переменных
- •Локальные переменные
- •Формальные параметры
- •Глобальные переменные
- •Спецификаторы хранения
- •Статические переменные
- •Статические локальные переменные
- •Статические глобальные переменные
- •Регистровые переменные
- •Оператор присваивания
- •Многочисленное присваивание
- •Преобразование типов при присваивании
- •Инициализация переменных
- •Константы
- •Символьные константы с обратным слэшем
- •Операторы
- •Арифметические операторы
- •Увеличение и уменьшение
- •Операторы отношения и логические операторы
- •Битовые операторы
- •Оператор ?
- •Операторы указания & и *
- •Оператор sizeof
- •Оператор «запятая»
- •Операторы [ ] u ()
- •Приоритеты в с
- •Выражения
- •Преобразование типов в выражениях
- •Принудительные преобразования
- •Пробелы и круглые скобки
- •Сокращенные операторы в с
- •Операторы управления программой
- •Истина и ложь в с
- •Операторы выбора
- •Вложенные if
- •Лесенка if-else-if
- •Оператор ?
- •Вложенные операторы switch
- •Вариации цикла for
- •Бесконечный цикл
- •Циклы for без тела
- •Метки и goto
- •Функции
- •Оператор return
- •Выход из функции
- •Возвращаемые значения
- •Значения, возвращаемые функцией main()
- •Правила видимости для функций
- •Аргументы функции
- •Передача по значению и передача по ссылке
- •Создание передачи по ссылке
- •Передача массивов в функции
- •Аргументы функции main()
- •Функции, возвращающие нецелые значения
- •Использование прототипов функции
- •Прототипы стандартных библиотечных функций
- •Создание прототипов функций, не имеющих параметров
- •Возврат указателей
- •Рекурсия
- •Сопоставление классического и современного объявления параметров
- •Указатели на функции
- •Особенности реализации
- •Параметризированные функции и функции общего назначения
- •Эффективность
- •Массивы
- •Одномерный массив
- •Создание указателя на массив
- •Передача одномерных массивов в функции
- •Двумерные массивы
- •Массивы строк
- •Многомерные массивы
- •Индексация с помощью указателей
- •Размещение массивов
- •Инициализация массива
- •Инициализация безразмерных массивов
- •Пример программы игры в крестики-нолики
- •Указатели
- •Указатели - это адреса
- •Переменные-указатели
- •Операторы для работы с указателями
- •Выражения с указателями
- •Присваивание указателей
- •Арифметические действия с указателями
- •Сравнение указателей
- •Динамическое выделение и указатели
- •Указатели на константы
- •Указатели на константы
- •Указатели на константы
- •Указатели и массивы
- •Указатели на символьные массивы
- •Массивы указателей
- •Указатели на указатели - многочисленное перенаправление
- •Инициализация указателей
- •Указатели на функции
- •Проблемы, связанные с указателями
- •Структуры, объединения и определяемые пользователем типы
- •Структуры
- •Доступ к членам структуры
- •Присваивание структур
- •Массивы структур
- •Программа инвентаризации
- •Передача структур в функции
- •Передача членов структур в функции
- •Передача всей структуры в функцию
- •Указатели на структуры
- •Объявление указателя на структуру
- •Использование указателей на структуру
- •Массивы и структуры в структурах
- •Битовые поля
- •Объединения
- •Перечисления
- •Использование sizeof для обеспечения переносимости
- •Ввод, вывод, потоки и файлы
- •Потоки и файлы
- •Текстовые потоки
- •Двоичные потоки
- •Консольный ввод/вывод
- •Чтение и запись символов
- •Чтение и запись строк: gets() и puts()
- •Форматированный консольный ввод/вывод
- •Печать символов
- •Вывод чисел
- •Вывод адресов
- •Спецификатор %n
- •Модификаторы формата
- •Спецификатор минимума ширины поля
- •Спецификатор точности
- •Выровненный вывод
- •Работа с другими типами данных
- •Модификаторы * u #
- •Спецификаторы формата
- •Ввод чисел
- •Ввод беззнаковых целых
- •Чтение отдельных символов с помощью scanf()
- •Чтение строк
- •Ввод адреса
- •Спецификатор %n
- •Использование множества сканирования
- •Пропуск нежелательных специальных символов
- •Обычные символы в управляющей строке
- •В scanf() следует передавать адреса
- •Модификаторы формата
- •Подавление ввода
- •Файловая система ansi с
- •Указатель на файл
- •Открытие файла
- •Запись символа
- •Чтение символа
- •Использование fopen(), getc(), putc() и fclose()
- •Использование feof()
- •Две расширенные функции: getw() и putw()
- •Работа со строками: fgets() и fputs()
- •Fseek() и произвольный доступ
- •Удаление файлов
- •Работа с консолью
- •Препроцессор и комментарии
- •Директивы условной компиляции
- •Использование defined
- •Операторы препроцессора # и ##
- •Предопределенные макросы
- •Комментарии
Указатели на указатели - многочисленное перенаправление
Концепция массивов указателей открыта и проста, поскольку индексы имеют вполне определенное значение. Тем не менее, в случае, когда один указатель указывает на другой, могут возникнуть проблемы. Указатель на указатель является формой многочисленного перенаправления или цепочки указателей. Рассмотрим рис.
Рисунок: Одиночное и многочисленное перенаправление |
|
В случае обычных указателей, указатель содержит адрес некоторого участка памяти, содержащего некоторое значение. В случае указателя на указатель, первый указатель содержит адрес второго, который в свою очередь содержит адрес участка памяти, содержащего некоторое значение.
Многочисленное перенаправление может и дальше расширяться. Но существует немного случаев, когда необходимо что-то более мощное, чем указатель на указатель. Излишнее перенаправленние приводит к концептуальным ошибкам, которые очень трудно исправлять. (Не надо путать многочисленное перенаправление со связанными списками, которые используются в базах данных.)
Переменная, являющаяся указателем на указателе должна быть описана следующим образом. Это выполняется путем помещения двух звездочек перед именем. Например, следующее объявление сообщается компилятору, что newbalance - это указатель на указатель типа float: float **newbalance; Важно понимать, что newbalance - это не указатель на число с плавающей точкой, а указатель на указатель на вещественное число.
Для получения доступа к целевому значению, косвенно указываемому указателем на указатель, следует применить оператор * два раза, как показано в следующем примере: #include <stdio.h> int main(void) { int x, *p, **q; x = 10; p = &x; q = &p; printf ("%d", **q) ; /* вывод значения x */ return 0; } Здесь p объявляется как указатель на целое, a q - это указатель на указатель на целое. Вызов printf() выводит число 10 на экран.
Инициализация указателей
После объявления указателя и до первого присвоения ему значения, указатель может содержать неизвестное значение. Если попытаться использовать указатель до сообщения ему значения, можно нарушить работу не только программы, но и всей операционной системы.
По существующим соглашениям неиспользуемый указатель должен содержать нулевое значение. То, что указатель имеет нулевое значение, не обеспечивает безопасности. Если использовать нулевой указатель слева от оператора присваивания, это может привести к «зависанию» программы.
Поскольку предполагается, что нулевой указатель не используется, можно его использовать для упрощения большинства подпрограмм, работающих с указателями. Например, можно использовать нулевой указатель для пометки конца массива указателей. Если это сделано, то процедура, осуществляющая доступ к массиву, будет знать, что она достигла конца массива при обнаружении нулевого значения. Данный способ реализации функции search() показан ниже: /* Ищем имя */ int search(char *р[], char *name) { register int t; for(t=0; p[t]; ++t) if(!strcmp(p[t], name)) return t; return -1; /* не найдено */ } Цикл for в search() запускается каждый раз при обнаружении нулевого указателя. Поскольку конец массива помечен нулем, условие, управляющее циклом, останавливает цикл (выдает ложное состояние) при достижении конца массива.
При профессиональном написании программ типично инициализировать строки. Пример этого был показан в функции serror(). Ниже приводится другой вариант объявления строк: char *р = "hello world\n"; Как можно видеть, указатель р - это не массив. Все компиляторы С создают так называемую таблицу строк, используемую компилятором для хранения строковых констант, используемых программой. Следовательно, данный оператор объявления помещает адрес "hello world" в указатель р. р может быть использован в программе как обычная строка. Например, следующая программа абсолютно корректна: #include <stdio.h> #include <string.h> char *p = "hello world"; int main(void) { register int t; /* вывод строки в прямом и обратном порядке */ printf(р); for(t=strlen(р)-1; t>-1; t--) printf ("%c", p[t]); return 0; }