
- •Структура программы на языке Си. Этапы выполнения программы
- •1.1. Алфавит языка Си
- •1.2. Лексемы
- •1.3. Идентификаторы и ключевые слова
- •1.4. Знаки операций
- •1.5. Литералы (константы)
- •1.6. Комментарии
- •1.7. Общая структура программы на языке Си.
- •1.8. Функциональная и модульная декомпозиции
- •1.9. Этапы обработки программы.
- •1.10. Роль препроцессора.
- •1.11. Ошибки
- •2. Переменные и константы. Типы данных
- •2.1. Основные типы данных
- •2.2. Декларация (объявление) объектов
- •2.3. Константы в программах
- •2.4. Целочисленные константы
- •2.5. Константы вещественного типа
- •2.6. Символьные константы
- •2.7. Строковые константы
- •3. Обзор операций
- •3.1. Операции, выражения
- •3.2. Арифметические операции
- •3.3. Операции сравнения
- •3.4. Логические операции
- •4. Операции (продолжение).
- •4.1. Операция присваивания
- •Примеры недопустимых выражений:
- •4.2. Сокращенная запись операции присваивания
- •4.3. Преобразование типов операндов бинарных операций
- •4.4. Преобразование типов при присваивании.
- •4.5. Операция явного приведения типа
- •4.6. Операция «,» (запятая)
- •5. Стандартная библиотека языка Си
- •5.1. Стандартные математические функции
- •5.2. Потоковый ввод-вывод
- •5.3. Консольные функции вывода данных на экран
- •5.4. Консольные функции ввода информации
- •5.5. Ввод-вывод в оконных приложениях.
- •Советы по программированию
- •6. Операторы языка с.
- •7. Составление разветвляющихся алгоритмов
- •7.1. Условные операторы
- •If (выражение) оператор;
- •If (выражение) оператор 1 ;
- •If (выражение 1) оператор 1;
- •If (выражение 2) оператор 2;
- •If (выражение 3) оператор 3;
- •7.2. Оператор выбора альтернатив (переключатель)
- •7.3. Условная операция «? :»
- •8. Составление циклических алгоритмов
- •8.1. Оператор с предусловием while
- •8.2. Оператор цикла с постусловием do – while
- •8.3. Оператор цикла с предусловием и коррекцией for
- •8.4. Вложенные циклы.
- •9. Операторы передачи управления.
- •9.1. Оператор безусловного перехода goto
- •9.2. Операторы continue, break и return
- •10. Массивы
- •10.1. Одномерные массивы
- •10.2. Примеры алгоритмов, использующих одномерные массивы.
- •10.3. Многомерные массивы
- •10.4. Примеры алгоритмов, использующих двумерные массивы.
- •10.5. Компонента StringGrid
- •11. Размещение данных и программ в памяти пэвм
- •11.1. Общие понятия.
- •11.2. Кодирование целых чисел.
- •11.3. Кодирование вещественных чисел.
- •11.4. Кодирование символов.
- •Примеры кодов символов:
- •Стандартная часть таблицы символов (ascii)
- •Дополнительная часть таблицы символов
- •11.5. Операция sizeof
- •11.6. Побитовые логические операции. Операции над битами
- •11.7. Кодирование программы.
- •11.8. Регистры
- •12.1. Строки как нуль-терминированные массивы char.
- •12.2. Русификация консольных приложений.
- •12.3. Строки как переменные типа AnsiString.
- •12.4. Преобразования строковых типов.
- •12.5. Тип String в консольных приложениях.
- •13. Функции пользователя и классы памяти.
- •13.1. Сущность и предназначение функций.
- •13.2. Определение и вызов функции.
- •13.3. Прототип функции.
- •13.4. Область видимости.
- •13.5. Классы памяти объектов в языке Cи.
- •13.6. Разбиение программы на модули.
- •14. Структуры и объединения
- •14.1. Понятие структуры
- •14.2. Декларация структурного типа данных
- •14.3. Объявление структурных переменных
- •14.4. Обращение к полям структуры
- •14.5. Операции со структурой как единым целым
- •14.6. Вложенные структуры
- •14.7. Массивы структур
- •14.8. Размещение структурных переменных в памяти
- •14.9. Битовые поля
- •14.10. Объединения
- •15. Генерация псевдослучайных чисел.
- •16. Файлы в языке с
- •16.1. Типы файлов.
- •16.2. Открытие файла
- •16.3. Закрытие файла
- •16.4. Запись - чтение информации
- •А) Посимвольный ввод-вывод
- •Б) Построчный и форматированный ввод-вывод
- •В) Блоковый ввод-вывод
- •Int fflush(file *stream);
- •16.5. Текстовые файлы
- •16.6. Перенаправление стандартного ввода-вывода
- •16.7. Бинарные файлы
- •16.8. Дополнительные полезные функции
- •16.9. Простейший пример создания собственной базы данных
- •17. Указатели
- •17.1. Определение указателей
- •17.2. Связь указателей и массивов.
- •17.3. Операции над указателями (косвенная адресация)
- •17.4. Операции над указателями (косвенная адресация)
- •17.5. Массивы указателей.
- •17.6. Указатели на указатели.
- •17.7 . Указатели как параметры функций.
- •Void f1(int, const double *);
- •17.8 . Указатели на структуры
- •17.9. Ссылка
- •17.10. Указатели на функции
- •Id_функции(список аргументов);
- •18. Работа с динамической памятью
- •18.1. Динамическое выделение и освобождение памяти.
- •18.2. Создание одномерного динамического массива.
- •18.3. Создание двуxмерного динамического массива.
- •19. Операция typedef
- •20. Отладка и пошаговое выполнение программы
17.2. Связь указателей и массивов.
Идентификатор массива указывает адрес памяти, начиная с которого он расположен, т.е. адрес его первого элемента. Работа с массивами тесно связана с применением указателей.
Пусть объявлен массив a из 5 целочисленных элементов:
int a[5];
a
-
a[0]
a[1]
a[2]
a[3]
a[4]
4000 4004 4008 4012 4016
Здесь приведено символическое изображение оперативной памяти, выделенной компилятором для объявленного целочисленного массива а[5]. Адрес массива выбирается компилятором в зависимости от размера доступной памяти, наличия других переменных и массивов и др. Для конкретности, здесь положено значение адреса, равное 4000. В реальной программе вместо 4000 может быть другое значение, но относительное положение элементов массива всегда остается постоянным.
В языке С идентификаторы массивов считаются константными указателями (т.е. в данном примере а "имеет значение" 4000). Такую константу можно присвоить переменной типа указатель, но нельзя подвергать преобразованиям, например:
int a[5], *q;
q = a; // Правильно - присваивание константы переменной
a = q; // Ошибка: в левой части - указатель-константа
Именно потому, что имена массивов считаются константными указателями, в языке Си нельзя непосредственно присваивать массивы друг другу (хотя структуры, включающие массивы как поля, целиком присваивать друг другу можно!)
Однако операция sizeof для массивов все же дает размер массива, а не указателя:
int n = sizeof(a) / sizeof(*a);
// n=5, т.к. sizeof(a)=20, sizeof(int)=4
int m = sizeof(q) / sizeof(*q);
// m=1, т.к. sizeof(int*)=4, sizeof(int)=4
17.3. Операции над указателями (косвенная адресация)
Элементы одного массива хранятся в памяти подряд, поэтому адрес каждого последующего элемента больше адреса предыдущего на размер одного элемента, т.е на sizeof(тип) байт, где тип - тип элемента массива. Поэтому, зная адрес одного элемента, легко вычислить адрес другого.
В языке С программист имеет возможность воспользоваться этим с помощью арифметических операций с указателями, т.е. прибавлением к ним (или вычитанием из них) целой величины:
p+i
p-i
p+=i
p-=i
p++
p--
где: p - указатель, i - целочисленное выражение.
Допускается также вычитание указателей:
p1-p2
где p1, p2 - указатели. Результатом вычитания является целое число.
Чтобы программист не был вынужден при этом каждый раз учитывать размер элемента, в языке Си принято правило: все арифметические операции с указателями выполняются в единицах памяти того типа объекта, на который ссылается этот указатель. Иными словами, операция p++ означает реальное увеличение p не на единицу, а на sizeof(*p) ; при этом p как раз будет указывать на следующий элемент массива. Аналогично, выражение p+i означает в действительности p+ i*sizeof(*p) , т.е. смещение на i элементов.
Заметим, что из-за этого указатели на объекты разных типов, первоначально равные, могут стать неравными при прибавлении к ним одной и той же величины:
int a[5], *q=a; // i указывает на a[0]
double *d=(double*)q;
// Теперь d=q (не считая разницы в типах)
q++; d++;
// теперь d>q, т.к. хранимый в d адрес
// увеличился на 8, а хранимый в q - на 4
q++; // а теперь снова d=q, и равно &a[2]
Однако на практике подобная адресация одного и того же участка памяти указателями разных типов редко имеет смысл.
В силу сказанного выше, адрес i-го элемента массива A всегда можно записывать и как &A[i], и как A+i . Итак, для массивов запись A[i] и *(A+i) эквивалентна. Для удобства операций с указателями, в языке С введено такое же правило записи и для них:
p[i] равносильно *(p+i)
где p - указатель, i - целочисленное выражение.
Иными словами, для обращения к i-му (считая от места, куда указывает p) элементу массива вместо записи *(p+i) можно писать короче: p[i]. Соответственно, и для указателей, и для массивов запись *p эквивалентна p[0]
Пример:
int a[5], *q=a; // Инициализация q: q указывает на a[0]
//(Здесь * перед q означает объявление его типа(указатель), а не разадресацию)
q++;
*q=40; // Означает a[1]=40;
q[3]=70; // Означает a[4]=70;
q[-1]=22; // Означает a[0]=22;
Операции с указателями бывают особенно полезны для массивов char. (Напомним, что в любом месте, где допустима строка как массив char, допустим также указатель на char). С их помощью можно, например, обратиться к середине строки:
char s[]="Hello, world!";
cout<<s+7; // Будет выведен текст: world!