- •Предисловие
- •Знакомство с языком C
- •Структура программы
- •Пример простой программы
- •Более сложный пример
- •Контрольные вопросы
- •Основные понятия, объявление переменных, типы данных
- •Объявление переменных
- •Примеры описаний переменных:
- •Основные типы данных
- •Производные типы.
- •Контрольные вопросы
- •Ввод/вывод с помощью функций printf() и scanf().
- •Модификаторы спецификаций преобразования используемые в функции printf()
- •Применение функции scanf()
- •Контрольные вопросы
- •Операции
- •Простейшие арифметические операции
- •Операция присваивания
- •Использование в выражениях операндов разных типов
- •Операции преобразования типов
- •Дополнительные арифметические операции
- •Дополнительные операции присваивания
- •Операции отношения
- •Логические операции
- •Приоритеты операций
- •Таблица 3. Таблица приоритетов рассмотренных выше операций
- •Контрольные вопросы
- •Операторы
- •Оператор-выражение
- •Ветвление
- •Условный оператор
- •Оператор switch
- •Оператор break
- •Циклы
- •Цикл while
- •Цикл do…while
- •Цикл for
- •Другие операторы
- •Оператор continue
- •Оператор goto
- •Оператор вызова функции
- •Контрольные вопросы
- •Обработка числовых последовательностей
- •Последовательная обработка
- •Обработка числовых последовательностей
- •Примеры
- •Контрольные вопросы
- •Последовательная обработка символьных данных
- •Символьные данные
- •Последовательная обработка символов
- •Функции getchar( ) и putchar( )
- •Контрольные вопросы
- •Обработка массивов
- •Массивы
- •Объявление массива
- •Использование и обработка массивов
- •Примеры
- •Контрольные вопросы
- •Указатели и динамические массивы
- •Указатели
- •Динамическое выделение памяти
- •Статические и динамические массивы
- •Контрольные вопросы
- •Подпрограммы
- •Описание подпрограмм
- •Параметры подпрограмм
- •Область действия переменных
- •Примеры
- •Контрольные вопросы
- •Рекурсивные функции
- •Понятие рекурсивной функции
- •Контрольные вопросы
- •Символьные строки и функции обработки строк
- •Посимвольная обработка строк
- •Пример программы
- •Контрольные вопросы
- •Структуры
- •Тип данных структура
- •Контрольные вопросы
- •Работа с файлами
- •Примеры
- •Контрольные вопросы
- •Массивы и функции как параметры
- •Указатели на функции
- •Массивы и указатели
- •Указатели и двумерные массивы
- •Пример
- •Технологии программирования
- •Структурное программирование
- •Модульное программирование
- •Объектно-ориентированное программирование
- •Компонентное программирование
- •Структурная декомпозиция задачи и разработка алгоритмов и программ методами сверху вниз и снизу вверх
- •Контрольные вопросы
- •Модульное программирование
- •Работа с графикой на языке C и модуль graphics.h
- •Типы видео мониторов и их режимы
- •Инициализация графики
- •Система координат
- •Основные графические функции
- •Функции рисования
- •Функции изменения параметров рисования
- •Шаблоны линий
- •Шаблоны закраски
- •Задание на расчетно-графическую работу
- •Порядок выполнения работы
- •Задание
- •Задание для вариантов 1-11
- •Задание для вариантов 12-20
- •Задание для вариантов 21-40
- •Пример исходных данных для вариантов 12-16
- •Задание для вариантов 41-60
- •Варианты индивидуальных заданий
- •Вариант 1
- •Вариант 2
- •Вариант 3
- •Вариант 4
- •Вариант 5
- •Вариант 6
- •Вариант 7
- •Вариант 8
- •Вариант 9
- •Вариант 10
- •Вариант 11
- •Вариант 12
- •Вариант 13
- •Вариант 14
- •Вариант 15
- •Вариант 16
- •Вариант 17
- •Вариант 18
- •Вариант 19
- •Вариант 20
- •Вариант 21
- •Вариант 22
- •Вариант 23
- •Вариант 24
- •Вариант 25
- •Вариант 26
- •Вариант 27
- •Вариант 28
- •Вариант 29
- •Вариант 30
- •Вариант 31
- •Вариант 32
- •Вариант33
- •Вариант 34
- •Вариант 35
- •Вариант 36
- •Вариант 37
- •Вариант 38
- •Вариант 39
- •Вариант 40
- •Вариант 41
- •Вариант 42
- •Вариант 43
- •Вариант 44
- •Вариант 45
- •Вариант 46
- •Вариант 47
- •Вариант 48
- •Вариант 49
- •Вариант 50
- •Вариант 51
- •Вариант 52
- •Вариант 53
- •Вариант 54
- •Вариант 55
- •Вариант 56
- •Вариант 57
- •Вариант 58
- •Вариант 59
- •Вариант 60
- •Задания на курсовую работу
- •Требования к курсовой работе
- •Этапы разработки программ
- •Содержание отчета по курсовой работе
- •Задания к курсовой работе
- •Литература
- •Функции консольного ввода/вывода
- •Функции обработки строк
- •Функции преобразования данных
Массивы и функции как параметры
Указатели на функции
Функции, как и другие объекты программы, располагаются в памяти ЭВМ. Любая область памяти имеет адрес, в том числе и та, в которой находится функция. Имя функции без круглых скобок за ним представляет собой константный адрес этой области памяти. Таким образом, имея функции со следующими прототипами:
double sin(double x); double cos(double x); double tan(double x);
мы можем в программе использовать имена sin, cos и tan, которые будут обозначать адреса этих функций.
Можно описать и указатель на функцию. Например, для функции с аргументом типа double, возвращающей значение типа double, описание такого указателя будет выглядеть следующим образом:
double (*fn)(double x);
Здесь, как и в случае указателя на массив, круглые скобки увеличивают приоритет операции *. Если бы они отсутствовали, то была бы описан не указатель на функцию, а функция, возвращающая значение указателя на double.
После того, как описан указатель на функцию, становятся возможными следующие операции:
fn = sin; /* Настройка указателя на функцию sin */ a = fn(x); /* Вызов функции sin через указатель */ fn = cos; /* Настройка указателя на функцию cos */ b = fn(x); /* Вызов функции cos через указатель */
Можно описать массив указателей на функцию и проинициализировать
его:
double (*fnArray[3])(double x) = { sin, cos, tan };
Теперь становится возможным следующий цикл:
for(i=0; i<3; i++)
printf( "F(x) = %lf\n", fnArray[i](x) );
118
Можно описать функцию возвращающую значение указателя на функцию:
double (*fnFunc(int i)) (double x)
{
switch(i)
{
case 0 : return sin; case 1 : return cos; case 2 : return tan;
}
}
Описанная функция имеет параметр типа int и возвращает значение указателя на функцию с аргументом типа double, возвращающую значение типа double.
После описания функции fnFunc становится возможным следующий цикл:
for(i=0; i<3; i++)
printf( "F(x) = %lf\n", fnFunc(i)(x) );
Массивы и указатели
В языке C понятие массива тесно связано с понятием указателя. Действительно, как было описано выше, имя массива представляет собой адрес области памяти, распределенной под этот массив, или иными словами адрес первого элемента массива. Пусть описаны следующие данные:
int a[100], *pa;
и осуществлено присваивание:
pa = a;
Оно является корректным, поскольку имя a обозначает адрес первого элемента массива a и поэтому имеет тип указателя на int. После этого присваивания
pa[0] или *pa будет обозначать a[0]; pa[1] или *(pa+1) будет обозначать a[1];
pa[2] или *(pa+2) будет обозначать a[2] и т. д. И вообще обозначения вида *(pa+n) и pa[n] являются полностью эквивалентными. Точно также эквивалентны выражения *(a+i) и a[i].
119
На первый взгляд, кажется, что массив и указатель полностью эквивалентны. Однако имеется два существенных отличия массива от указателя:
•массиву при описании выделяется память для хранения всех его элементов, а указателю только для хранения адреса;
•адрес массива навсегда закреплен за именем, то есть имя массива является адресной константой и выражение вида a = pa недопустимо.
Результат прибавления к указателю или вычитания из него целочисленной величины является указателем того же типа, значение которого отличается от значения исходного указателя на число байт, определяемое произведением целочисленной величины на размер данного, которое адресует указатель. Например, если имеются описания
int A[20], *pA = A; double B[20], *pB = B;
то указатель (pA+3) будет иметь значение на 6 байт больше, чем pA, и будет адресовать элемент A[3] массива A. Указатель (pB+3) будет иметь значение на 24 байта больше, чем pB, и будет адресовать элемент B[3] массива B. С указателями типа void подобные операции выполнены быть не могут, поскольку компилятор не знает размера адресуемого данного.
Для указателей определены операции увеличения и уменьшения на целочисленную величину, как альтернативная форма записи выражений
pA = pA + i; эквивалентно pA += i; pA = pA - i; эквивалентно pA -= i;
pA = pA + 1; эквивалентно pA++; или ++pA;
pA = pA - 1; эквивалентно pA--; или --pA; При этом, работа префиксных и постфиксных операций ++ и -- совпадает с их работой для арифметических данных.
Указатели допускается использовать в операциях сравнения. При этом всегда возможно сравнение указателя с нулем и сравнение двух однотипных указателей. Однако правильность результата последнего сравнения для 16-ти разрядного режима работы IBM PC гарантируется только в том случае, если
120
сравниваемые указатели являются указателями на элементы одного и того же массива данных или если они предварительно подвергаются нормализации (см. ниже).
Разность двух однотипных указателей представляет собой целочисленную величину равную количеству элементов данных (не байт) между соответствующими адресами памяти. Правильность результата этой операции для 16-ти разрядного режима работы IBM PC тоже гарантируется только в том случае, если указатели имеют значение адресов элементов одного и того же массива данных или если они предварительно подвергаются нормализации (см. ниже).
В следующем фрагменте программы иллюстрируется использование вышеописанных операций
double A[100], *pA, *pA100; int i;
/* Заполняем массив A. Работаем с массивом */ for (i=0; i<100; i++) A[i]=0;
/* Заполняем массив A. Работаем с указателями */ for (pA=A, pA100=pA+100; pA<pA100; pA++) *pA=11.9;
Последний вариант заполнения массива может оказаться более эффективным.
Указатели и двумерные массивы
Пусть имеются следующие определения массивов и указателей:
int A[4][2], B[2];
int *p, (*pA)[4][2], (*pAstr)[2];
Здесь A представляет собой двумерный массив из четырех строк и двух столбцов, B - одномерный массив из двух элементов. Для каждого из этих массивов будет выделено соответствующее количество памяти, достаточное для хранения всех их элементов.
Указатель p представляет собой указатель на величину int, указатель pA - указатель на двумерный массив из четырех строк и двух столбцов, pAstr - указатель на одномерный массив из двух элементов. Все указатели имеют размер, равный размеру адреса для данных в используемой модели памяти. Память для хранения данных, естественно, не выделяется. Количество
121
элементов данных из описания массивов будет использовано лишь для корректного изменения значения указателя при выполнении над ним допустимых арифметических операций.
Смысл трактовки этих указателей определяется направлением слеванаправо для подряд следующих операций [], а также изменением приоритета операции * с помощью круглых скобок. Если не поставить круглых скобок, то следующее определение
int *pa[4][2];
рассматривается как определение двумерного массива из указателей на тип int.
Для вышеописанных указателей допустимы следующие операции присваивания, поскольку слева и справа от операции присваивания находятся указатели на один и тот же тип данных:
p = B;
p = &B[1];
p = &A[0][0]; p = A[2];
Следующее присваивание:
p = A; /* неверно */
является неверным, так как слева от операции присваивания находится указатель на тип int, а справа - указатель на первый элемент массива A, который (элемент) представляет собой массив из двух элементов типа int. В таких случаях компиляторы выдают предупреждающее сообщение о подозрительном преобразовании указателя.
Если программист уверен в своих действия, то он может использовать операцию явного приведения типа для устранения этого сообщения, но при этом компилятор снимает с себя всякую ответственность за корректность использования такого указателя. Так, после присваивания
p = (int *) A;
элементы, на которые ссылается указатель, и элементы массива A находятся в следующем соответствии:
p[0] эквивалентно A[0][0] p[1] эквивалентно A[0][1]
122
p[2] эквивалентно A[1][0] p[3] эквивалентно A[1][1] p[4] эквивалентно A[2][0] p[5] эквивалентно A[2][1] p[6] эквивалентно A[3][0] p[7] эквивалентно A[3][1]
Совершенно корректными являются следующие присваивания
pAstr = A;
после которого использование массива A и указателя pAstr совершенно эквивалентны:
pAstr[i][j] эквивалентно A[i][j]
Присваивание
pAstr = &A[2];
устанавливает следующее соответствие между элементами, на которые ссылается указатель pAstr и элементами массива A:
pAstr[0][0] эквивалентно A[2][0] pAstr[0][1] эквивалентно A[2][1] pAstr[1][0] эквивалентно A[3][0] pAstr[1][1] эквивалентно A[3][1]
Следующие присваивания корректны
pA = &A; /* Указатель на двумерный массив */ pAstr = &B; /* Указатель на одномерный массив */
и устанавливают следующее соответствие элементов:
(*pA)[i][j] эквивалентно A[i][j] (*pAstr)[i] эквивалентно B[i]
Массивы указателей удобны для хранения символьных строк:
char *str[] = { "Строка 1", "Строка 2", "Длинная строка 3" };
В этом случае каждый элемент массива представляет собой адрес соответствующей строки символов, а сами строки располагаются компилятором в статическом сегменте данных. Никакой лишней памяти, связанной с различной длиной строк, при этом не расходуется.
123