
- •Происхождение языка с
- •Язык среднего уровня
- •Структурированный язык
- •Язык программирования
- •Компиляторы против интерпретаторов
- •Вид программ на с
- •Библиотеки и компоновка
- •Раздельная компиляция
- •Карта памяти с-программы
- •Переменные, константы, операторы и выражения
- •Идентификаторы
- •Типы данных
- •Модификаторы типов
- •Модификаторы доступа
- •Объявление переменных
- •Локальные переменные
- •Формальные параметры
- •Глобальные переменные
- •Спецификаторы хранения
- •Статические переменные
- •Статические локальные переменные
- •Статические глобальные переменные
- •Регистровые переменные
- •Оператор присваивания
- •Многочисленное присваивание
- •Преобразование типов при присваивании
- •Инициализация переменных
- •Константы
- •Символьные константы с обратным слэшем
- •Операторы
- •Арифметические операторы
- •Увеличение и уменьшение
- •Операторы отношения и логические операторы
- •Битовые операторы
- •Оператор ?
- •Операторы указания & и *
- •Оператор 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
- •Операторы препроцессора # и ##
- •Предопределенные макросы
- •Комментарии
Указатели
Правильное понимание и использование указателей имеет большое значение при создании большинства С и С++-программ по четырем причинам:
Указатели предоставляют способ, позволяющий функциям модифицировать передаваемые аргументы.
Указатели используются для поддержки системы динамического выделения памяти.
Использование указателей может повысить эффективность работы некоторых подпрограмм.
Указатели, как правило, используются для поддержки некоторых структур данных типа связанные списки и двоичные деревья.
Помимо того, что указатели - одна из самых сильных сторон С, они, в то же время, могут нанести большой ущерб. Например, неинициализированный или дикий указатель может привести к краху системы, может быть даже хуже, когда некорректное использование указателей приводит к трудноуловимым ошибкам.
Указатели - это адреса
Указатель содержит адрес памяти. Как правило, данный адрес содержит местоположение какой-либо переменной в памяти. Если одна переменная содержит адрес другой, то говорят, что первая переменная указывает на вторую. Например, если переменная по адресу 1000 указывает на переменную по адресу 1004, то по адресу 1000 будет находиться значение 1004. Данная ситуация продемонстрирована на рис.
Рисунок: Одна переменная указывает на другую |
|
Переменные-указатели
Если переменная должна содержать указатель, она объявляется следующим образом. Объявление указателя включает базовый тип, * и имя переменной. Стандартный вид объявления указателя следующий: тип *имя; где тип - это любой допустимый тип (базовый тип указателя), а имя - это имя переменной указателя.
Базовый тип указателя определяет тип переменной, на которую указывает указатель. С технической точки зрения любой тип указателя может указывать в любой участок памяти, но С предполагает, что указатель указывает на объект своего базового типа. Далее будет видно, что все арифметические действия с указателями выполняются применительно к базовому типу. Поэтому базовый тип указателя играет важную роль.
Операторы для работы с указателями
Имеется два специальных оператора для работы с указателями - * и &.
Оператор & - это унарный оператор, возвращающий адрес операнда. Например: р = # помещает адрес переменной num в р. Данный адрес соответствует внутреннему положению переменной в компьютере. Он ничего не делает со значением num. Операцию & можно рассматривать как «взятие адреса». Следовательно, предыдущий оператор присваивания можно прочитать как «p получает адрес num».
Предположим, что переменная num использует адрес 2000 для хранения своего значения. Также предположим, что значение num - 100. В результате этих предположений р получит значение 2000.
Следующий оператор * дополняет оператор &. Оператор * - это унарный оператор, возвращающий значение переменной, находящейся по указанному адресу. Например, если р содержит адрес памяти переменной num, то q=*p; поместит значение num в q. Следуя вышеприведенным предположениям, q получит значение 100, поскольку 100 хранится по адресу 2000, являющемуся адресом, хранящимся в р. Оператор * можно рассматривать как «по адресу». В данном случае вышеприведенный оператор может быть прочитан как *q получает значение по адресу р».
Следующая программа демонстрирует все вышеперечисленное: #include <stdio.h> int main(void) { int num, q; int *p; num = 100; /* num присваивается 100 */ p = # /* p получает адрес num */ q = *p; /* q посредством p присваивается значение num */ printf("%d", q); /* выводит 100 */ return 0; } Данная программа выводит значение 100 на экран.
К несчастью, значок для умножения и значок «по адресу» совпадают, впрочем как и значок битового И и значок «взятие адреса». Данные операторы не влияют друг на друга. Как &, так и * имеют более высокий приоритет по сравнению с остальными арифметическими операторами, кроме унарного минуса, приоритет которого совпадает с приоритетом данных операторов.
Следует убедиться, что переменная-указатель указывает на корректный тип данных. Например, при объявлении указателя на тип int, компилятор предполагает, что любой адрес, содержащийся в переменной, указывает на целочисленное значение. Поскольку С позволяет присваивать любой адрес переменной-указателю, следующий фрагмент кода компилируется (хотя компилятор и выдает предупреждение), но выполнение не приводит к желаемому результату: #include <stdio.h> int main(void) { double x, y; int *p; x = 100.123; p = &x; у = *p; printf ("%f", у); /* неверно */ return 0; } Данная программа не присваивает значение х переменной у. Поскольку р объявляется как целочисленный указатель (предположим, что в системе 16-битное целое), только 2 байта будут перенесены к у, а не 8, как обычно делается для double.