
- •Введение в системное программирование Основные понятия и определения Программы и программное обеспечение
- •Системное программирование
- •Этапы подготовки программы
- •Системное программирование
- •Лекция 1
- •1. Язык Си: Общая характеристика, историческая справкаи основные достоинства
- •2. Подготовка к выполнению и выполнение программ
- •3. Элементы языка с
- •Лекция 2
- •1. Понятие типа данных. Переменные и константы. Операция присваивания
- •2.Типы данных в языке си. Описание данных в программе
- •3. Константы в языке Си
- •4. Арифметические операции и арифметические выражения
- •5. Операции отношения, логические операции и логические выражения
- •6. Автоматическое преобразрвание типов и операция приведения
- •7. Простейшие операторы языка си. Составной оператор
- •Лекция 3
- •3. Инициализация переменных и массивов
- •4. Управляющие конструкции языка си
- •Лекция 4
- •1. Адреса и указатели
- •2. Отождествление массивов и указателей.Адресная арифметика
- •3. Указатели на массивы. Массивы указателей и многомерные массивы
- •4. Динамическое выделение памяти под массивы
- •5. Инициализация указателей
- •Лекция 5
- •1. Функции в языке си. Формальные и фактические параметры. Механизм передачи параметров. Возвращаемые значения
- •2. Использование указателей в качестве аргументов функций
- •3. Предварительное описание функций
- •4. Аргументы командной строки
- •Лекция 6
- •1. Ввод и вывод в языке си: Общие концепции
- •2. Файлы данных и каталоги. Внутренняя организация и типы файлов
- •3. Стандартные функции для работы с файлами и каталогами
- •4. Внешние устройства как специальные файлы. Организация обмена со стандартными внешними устройствами
- •5. Операции ввода/вывода через порты микропроцессоров intel 8086/80286
- •Лекция 7
- •1. Общая структура программы на языке си. Время существования и видимость переменных. Блоки
- •2. Классы памяти
- •3. Рекурсивные вызовы функций. Реализация рекурсивных алгоритмов
- •4. Препроцессор языка Си
- •5. Модели памяти, поддерживаемые компилятором ibm c/2
- •Лекция 8
- •1. Структуры в языке си: основные понятия
- •2. Массивы структур
- •3. Указатели на структуры
- •4. Вложение структур
- •5. Структуры и функции
- •6. Объединения
- •7. Перечисления
- •8. Определение и использование новых типов данных
- •9. Классы имен
Лекция 4
Адреса и указатели. Операции получения адреса и косвенной адресации. Отождествление массивов и указателей. Адресная арифметика. Указатели на массивы. Массивы указателей и многомерные массивы. Динамическое выделение памяти под массивы. Инициализация указателей.
1. Адреса и указатели
Во время выполнения всякой программы, используемые ею данные размещаются в оперативной памяти ЭВМ, причем каждому элементу данных ставится в соответствие его индивидуальный адрес. При реализации многих алгоритмов и представлении сложных логических структур данных часто оказывается полезной возможность непосредственной работы с адресами памяти. Подобная ситуация возникает, например, при обработке массивов переменных. Действительно, поскольку соседние элементы массива располагаются в смежных ячейках памяти, то для перехода от одного его элемента к другому можно вместо изменения значения индексного выражения манипулировать адресами этих элементов. Предположим для определенности, что нулевой элемент целочисленного массива расположен в ячейке памяти с номером Ao. Тогда, зная, что длина элемента данных типа int составляет два байта, нетрудно вычислить номер ячейки, в которой будет находиться i-ый элемент этого массива:
Ai = Ao + 2*i
На первый взгляд работа с адресами может показаться утомительной и бесполезной. На самом же деле она является даже более естественной, нежели работа с индексами, поскольку в процессе компиляции программы всякое индексное выражение трансформируется в операции над адресами. Объекты языка Си, значениями которых являются адреса оперативной памяти, получили название указателей. В общем случае указатели являются переменными величинами и над ними можно выполнять определенный набор операций подобно тому, как мы оперировали обычными числовыми переменными. В языке Си всякий указатель имеет базовый тип, который совпадает с типом элемента данных, на который может ссылаться этот указатель. Такое соглашение, возможно несколько ограничительное, существенно упрощает и делает значительно более эффективной работу с указателями. Переменные-указатели, как и переменные любых других типов, перед их использованием в программе должны быть предварительно объявлены в одной из инструкций описания данных. В случае указателей на простые переменные это делается следующим образом:
<sc-specifier> type-specifier *identifier <, ... >;
Здесь type-specifier задает тип переменной, на которую ссылается указатель с именем identifier, а символ звездочка (*) определяет саму переменную как указатель. Описатель класса памяти sc-specifier будет подробно рассмотрен в Лекции 7. Приведем несколько примеров правильного описания указателей в программе:
int *ptr;
long *sum;
float *result, *value;
Каждая из этих инструкций говорит о том, что соответствующая переменная есть указатель на элемент данных определенного типа, а комбинация, например, вида *ptr представляет собой величину типа int. По существу это означает, что подобные комбинации могут использоваться как операнды произвольных выражений. В частности, сохраняя обозначения предыдущего примера, мы могли бы написать
*sum = 0;
for (*ptr = 1; *ptr <= 100; (*ptr)++)
*sum = *sum + (*ptr)*(*ptr);
что соответствует фрагменту программы вычисления суммы квадратов
первых ста натуральных чисел. Круглые скобки в корректирующем выражении оператора цикла являются существенными. Замечание. Несмотря на то, что всякий указатель в языке Си имеет привязку к конкретному типу данных, существует возможность в отдельных случаях создавать указатели неопределенного типа. Это делается при помощи ключевого слова void, используемого вместо имени типа в инструкции описания этого указателя. Так, например, инструкция
void *poiter;
определяет переменную poiter как указатель без конкретной ссылки на его базовый тип. Однако подобные описания не являются типичными и могут быть использованы лишь при объявлении формальных параметров функций и определении типа возвращаемого той или иной функцией значения (см. Лекцию 5, $ 1). Строго говоря, компилятор языка Си рассматривает комбинации вида
*identifier
в составе выражений как некоторую операцию над указателями. Эта операция, символом которой как раз и является звездочка перед именем указателя, носит название операции косвенной адресации и служит для доступа к значению, расположенному по заданному адресу. Существует и другая операция, в определенном смысле противоположная операции косвенной адресации и именуемая операцией получения адреса. Она обозначается символом амперсенда (&) перед именем простой переменной или элемента массива:
&identifier или &identifier[expression]
и сопоставляет своему аргументу адрес его размещения в памяти, т. е. указатель. Естественно, что этим аргументом может быть и указатель, поскольку указатели, как и другие переменные, хранятся в ячейках оперативной памяти. Всевозможные выражения, построенные с использованием указателей или операторов * и &, принято называть адресными выражениями, а сами арифметические операции над указателями - адресной арифметикой. Одноместные операции * и & имеют такой же высокий приоритет, как и другие унарные операции, и в составе выражений обрабатываются справа налево. Именно по этой причине мы обратили внимание на необходимость круглых скобок в выражении (*ptr)++ предыдущего примера, ибо без них оператор ++ относился бы к указателю ptr, а не к значению, на которое ссылается этот указатель. Замечание. Если, например, mas есть массив переменных, то выражениe &mas[0] равносильно простому употреблению имени массива без следующего за ним индексного выражения, поскольку последнее отождествляется с адресом размещения в памяти самого первого элемента этого массива. Вот несколько примеров использования указателей и адресных выражений.
1. Аргументами функции форматированного ввода scanf являются адреса переменных, которым должны быть присвоены прочитанные значения:
scanf("%d %d", &m, &n);
2. Следующая пара операторов
px = &x;
y = *px;
где переменная px об'явлена предварительно как указатель, равносильна непосредственному присваиванию
y = x;
3. При выполнении следующего фрагмента программы сравнение в операторе if всегда будет истинно, поскольку значение указателя numptr совпадает с адресом переменной number:
int number;
int *numptr = &number;
scanf("%d %d", &number, numptr);
if (number == *numptr)
printf("Сравнение истинно");
else
printf("Сравнение ложно");