- •Происхождение языка с
- •Язык среднего уровня
- •Структурированный язык
- •Язык программирования
- •Компиляторы против интерпретаторов
- •Вид программ на с
- •Библиотеки и компоновка
- •Раздельная компиляция
- •Карта памяти с-программы
- •Переменные, константы, операторы и выражения
- •Идентификаторы
- •Типы данных
- •Модификаторы типов
- •Модификаторы доступа
- •Объявление переменных
- •Локальные переменные
- •Формальные параметры
- •Глобальные переменные
- •Спецификаторы хранения
- •Статические переменные
- •Статические локальные переменные
- •Статические глобальные переменные
- •Регистровые переменные
- •Оператор присваивания
- •Многочисленное присваивание
- •Преобразование типов при присваивании
- •Инициализация переменных
- •Константы
- •Символьные константы с обратным слэшем
- •Операторы
- •Арифметические операторы
- •Увеличение и уменьшение
- •Операторы отношения и логические операторы
- •Битовые операторы
- •Оператор ?
- •Операторы указания & и *
- •Оператор 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
- •Операторы препроцессора # и ##
- •Предопределенные макросы
- •Комментарии
Выражения с указателями
В целом выражения, использующие указатели, подчиняются тем же правилам, что и обычные выражения С. Данный раздел рассматривает некоторые особенности выражений с указателями.
Присваивание указателей
Как и обычные переменные, указатели могут использоваться с правой стороны оператора присваивания для присвоения значения другому указателю. Например: #include <stdio.h> int main (void) { int x; int *p1, *p2; p1 = &x; p2 = p1; /* выводит адреса, хранящиеся в p1 и р2. Они будут одинаковы */ printf ("%р %р", p1, р2); return 0; } Здесь p1 и p2 содержат адрес х.
Арифметические действия с указателями
К указателям могут применяться только две арифметические операции: сложение и вычитание. Для понимания арифметических действий с указателями предположим, что p1 - это указатель на целое, содержащий значение 2000, и будем считать, что целые имеют длину 2 байта. После выражения p1 ++; содержимое p1 станет 2002, а не 2001! Каждый раз при увеличении p1 указатель будет указывать на следующее целое. Это справедливо и для уменьшения. Например: р1 --; приведет к тому, что p1 получит значение 1998, если считать, что раньше было 2000.
Каждый раз, когда указатель увеличивается, он указывает на следующий элемент базового типа. Каждый раз, когда уменьшается - на предыдущий элемент. В случае указателей на символы это приводит к «обычной» арифметике. Все остальные указатели увеличиваются или уменьшаются на длину базового типа. Рис. демонстрирует данную концепцию.
Рисунок: Арифметическое действия с указателями учитывают тип переменной, на которую они указывают (например, 16-битные целые числа) |
|
Естественно, все не ограничивается только уменьшением или увеличением. Можно добавлять или вычитать из указателей целые числа. Выражение p1 = p1 + 9; приводит к тому, что указатель p1 указывает на девятый элемент по сравнению с элементом, на который он указывал до присваивания.
Помимо добавления или вычитания указателей и целых чисел, единственную операцию, которую можно выполнять с указателями, - это вычитание одного указателя из другого.
В большинстве случаев вычитание одного указателя из другого имеет смысл только тогда, когда оба указателя указывают на один объект, - как правило, массив. В результате вычитания получается число элементов базового типа, находящихся между указателями. Помимо этих операций не существует других арифметических операций, применимых к указателям. Нельзя умножать или делить указатели, нельзя складывать указатели, нельзя применять битовый сдвиг или маски к указателям, нельзя добавлять или вычитать типы float или double.
Сравнение указателей
Возможно сравнивать два указателя. Например, имеются указатели р и q. Тогда справедлив следующий оператор: if(p<q) printf("р points to lower memory than q\n");
Обычно сравнение указателей используется, когда два или более указателя указывают на один объект. В качестве примера представим, что вы создается процедура, обслуживающая стек для хранения целочисленных значений. Стек - это список, построенный по принципу «первый вошел - последний вышел». Стек часто сравнивают со стопкой тарелок на столе - самая первая тарелка на столе забирается самой последней. Стек часто используется в компиляторах, интерпретаторах, электронных таблицах и другом системном программном обеспечении. Для создания стека необходимы две подпрограммы: push() и рор(). Функция push() помещает значение в стек, а роp() извлекает его. Стек содержится в массиве stack, в котором хранится STCKSIZE элементов. Переменная tos содержит адрес памяти вершины стека и используется для предотвращения переполнения или исчерпания стека. После инициализации стека данные функции могут использоваться для работы с целыми числами. Ниже показаны данные функции вместе с простой функцией main(), демонстрирующей их использование: #include <stdio.h> #include <stdlib.h> #define STCKSIZE 50 void push (int i); int pop(void); int *p1, *tos, stack[STCKSIZE]; int main(void) { int value; p1 = stack; /* присваивает p1 начало стека */ tos = p1; /* tos содержит вершину стека */ do { printf ("Enter a number (-1 to quit, 0 to pop): "); scanf("%d", &value); if(value!=0) push(value); else printf ("this is it %d\n", pop()); } while(value!=-1); return 0; } void push (int i) { p1++; if (p1==(tos + STCKSIZE)) { printf("stack overflow"); exit (1); } *p1 = i; } int pop (void) { if(p1==tos) { printf("stack underflow"); exit(1); } p1--; return * (p1+1); }
Как push(), так и рор() проверяют указатель p1 для обнаружения ошибок переполнения или исчерпания. В push() p1 сравнивается с концом стека, получаемого путем сложения STCKSIZE и tos. В рор() p1 сравнивается с tos, чтобы убедиться, что стек не пуст.
В рор() в операторе return необходимы круглые скобки. Если их убрать, то оператор будет выглядеть return *p1 + 1; и он будет выдавать значение, хранящееся по адресу p1, увеличенное на 1, а не значение, хранящееся по адресу p1 + 1. Следует быть очень осторожным при использовании круглых скобок для достижения правильного порядка вычисления выражений с указателями.
