
- •Программирование на языке высокого уровня
- •Составитель: л.А. Прокушев
- •Подписано к печати Формат 60х84 1/16. Бумага тип. №3
- •Редакционно-издательский отдел
- •190000, Санкт-Петербург, ул. Б. Морская, 67
- •Алгоритмизация вычислительных процессов
- •Средства программирования вычислительных процессов
- •Данные и их типы
- •Константы
- •Переменные
- •Ввод-вывод данных
- •Ввод-вывод данных в стиле с
- •Форматированный ввод
- •Форматированный вывод
- •Консольный ввод-вывод
- •Функция ввода символа (без отображения):
- •Функция очистки экрана результатов:
- •Выражения и операции
- •Логические выражения и операции.
- •Работа с ветвящимися процессами Операторы
- •Оператор присваивания
- •Условный оператор (if)
- •Операторы передачи управления
- •Оператор break (прервать) используется для прерывания работы текущего сложного оператора, в теле которого находится оператор break, и передачи управления на следующий по порядку оператор.
- •Оператор выбора (switch)
- •Действие оператора выбора состоит в следующем:
- •Введите 2 числа х, y : 3 8
- •Работа с циклическими вычислительными процессами
- •Циклы с фиксированным числом повторений Оператор цикла с предусловием (while)
- •Прерывание цикла
- •Циклы с неизвестным числом повторений Вычисление рекуррентных последовательностей
- •Оператор цикла с постусловием (do)
- •Вложенные циклы и организация диалога в программе
- •Оператор цикла с параметром (for)
- •Программа:
- •Работа с массивами
- •Описание массива
- •Доступ к элементам массива
- •Указатель.
- •Занесение данных в массив
- •Многомерные массивы
- •Работа с функциями
- •Определение функции
- •Вызов функции
- •Передача параметров функции
- •Передача данных по значению
- •Передача данных по адресу
- •Прототип (шаблон) функции
- •Блочная структура программы
- •Внешние описания переменных
- •Многомодульные программы Проект программы
- •Внешние ссылки
- •Создание проекта программы
- •Работа с указателями Объявления объектов со сложными описателями
- •Массивы указателей
- •Указатель на указатель
- •Указатель на функцию
- •Использование указателя на функцию как аргумента
- •Массивы указателей на функции
Многомерные массивы
Рассмотрим более подробно работу с многомерными массивами. Размерность массива – это число индексов, используемых для ссылки на конкретный элемент в массиве. Многомерный массив – это массив, состоящий из массивов, то есть его элементами являются массивы. Например, двухмерный массив, описанный как int m[2][3], состоит из двух элементов-строк (одномерных массивов): m = { m[0], m[1] } по три элемента в каждом:
массив с индексом 0: m[0] с элементами m[0][0], m[0][1], m[0][2],
массив с индексом 1: m[1] с элементами m[1][0], m[1][1], m[1][2].
Имя массива m является указателем-константой на начало двухмерного массива: m=&m[0][0]; в свою очередь элементы m[0], m[1] являются указателями-константами на начала своих одномерных массивов: m[0] = &m[0][0], m[1] = &m[1][0]. Поскольку m = m[0] = &m[0][0], постольку имя массива m является указателем на массив указателей на начала своих одномерных массивов (m = { m[0], m[1] }), что определяет вид адресных выражений для двухмерного массива. Для ссылки на элементы двухмерного массива “точкой отсчета” может быть как самый первый элемент, так и указатели на строки массива.
Адрес начала i-й строки массива (m[i]) можно получить выражением *(m+i), где имя массива m используется как адрес начала массива указателей. Адрес j-го элемента i-й строки определяется эквивалентными выражениями:
m[i] + j = *(m+i) + j = &m[i][j].
Поэлементный ввод-вывод двухмерного массива можно осуществить несколькими способами, используя индексные и адресные выражения.
Пример 30.
Ввести и вывести по строкам двухмерный массив.
Программа:
void main ()
{ int m[2][3], i, j; // описание массива и параметров циклов
puts (“Ввод массива m по строкам”);
for (i=0; i < 2; i++) // цикл по строкам
for (j=0; j < 4; j++) // цикл по столбцам
scanf (“%d”, &m[i][j] ); // ввод элемента массива
// варианты адресных выражений: m[i]+j; *(m+i)+j)
printf (“Вывод массива m по строкам:\n”, );
for (i=0; i < 2; i++) // цикл по строкам
{ for (j=0; j < 4; j++) // цикл по столбцам
printf (“%d”, m[i][j]); // индексное выражение
printf (“\n”); // переход на новую строку
// варианты адресных выражений: *(m[i]+j); *(*(m+i)+j)
}
}
Отметим, что использование указателей для доступа к элементам массива дает более короткие подпрограммы. Из примера 30 видно, что адресное обращение к элементам двухмерного массива выглядит более сложным и громоздким по сравнению с индексным обращением. Недостаток этого примера в том, что он неуниверсален, поскольку размерности массива заданы конкретными числами. Изменив размерности массива, мы должны изменить их в программе и перекомпилировать программу. Использование директивы #define k 10 (примеры 28, 29) позволяет улучшить текст программы, но изменение k опять требует ее перекомпилирования. Таким образом, эти программы не отвечают требованию массовости (универсальности) алгоритма. Универсальной можно считать программу, которая получает значения размерностей массива в процессе выполнения программы, то есть динамически. Использование ограниченной по объему статической памяти, выделяемой для текста и данных программы, не позволяет этого добиться.
Динамическая память.
Но есть динамическая память, имеющая гораздо больший объем (так называемая "куча" на жаргоне программистов), которой можно управлять. В начале программы необходимо добавить директиву препроцессора подключения библиотеки функций для работы с динамической памятью:
#include <alloc.h>.
Функции управления динамической памятью:
(type*) malloc (nbytes); – выделяет блок памяти размера nbytes байтов, и возвращает указатель на первый байт блока для данных, определяемых типом type;
(type*) calloc (nelem, elsize); – выделяет блок памяти для nelem элементов по elsize байтов каждый (всего nelem*elsize байтов), обнуляет выделенную память и возвращает указатель на нее;
(type*) realloc (*bloc, size) – делает попытку установить новый размер size блока, на который ссылается указатель bloc.
free (*bloc) – освобождает блок памяти, на начало которого ссылается указатель bloc. Использование этой функции обязательно, после работы с динамической памятью.
Использование указателей является средством разработки универсальных программ. Выше было показано, что для массива int m[2][3] имя массива m является указателем на массив указателей. Так как первый элемент массива – указатель, то имя массива m есть указатель на указатель (константа). В С/С++ можно описать переменную "указатель на указатель". Если простой указатель объявляется *р, то указатель на указатель обозначается: **рр, то есть число звездочек * определяет уровень указателя. Фактически указатель на указатель – это ячейка памяти, хранящая адрес другого указателя. Рассмотрим, как можно воспользоваться этим средством для обработки матриц.
Пример 31.
Программа демонстрирует использование указателя на указатель для работы с двухмерными массивами и выделение динамической памяти. Выполняется ввод прямоугольной матрицы (S[n][k]) и вывод ее на экран по строкам:
#include<stdio.h>
#include<conio.h>
#include<alloc.h> // функции для динамической памяти
void main()
{ int n, k, i, j; // размеры массива и индексы
int **S; // указатель на указатель для массива
clrscr();
printf("Введите количество строк матрицы n = ");
scanf("%d", &n); // ввод числа строк
printf("Введите количество столбцов матрицы k = ");
scanf("%d", &k); // ввод числа столбцов
S=(int**) malloc(n*sizeof(int)); // память для массива указателей строк
for (i=0; I < n; i++) // цикл выделения памяти
S[i]=(int*) malloc(k*sizeof(int)); // для элементов строк матрицы
printf("Введите матрицу S[%i][%i]:\n", n, k);
for (i=0; i <n; i++) // цикл по строкам
for (j=0; j < k; j++) // цикл по столбцам
scanf("%d", &S[ i ][ j ]); // ввод элементов матрицы
printf("Введена матрица S[%i][%i]:\n", n, k);
for (i=0; i <n; i++) // цикл по строкам
{ for (j=0; j < k; j++) // цикл по столбцам
printf("%d ", S[i][j]); // вывод элементов матрицы
printf('\n'); // переход на новую строку
}
// Освобождение динамической памяти:
for (i=0; i<n; i++) // цикл по строкам
free(S[i]); // удаление строк
free(S); // удаление массива указателей
}
Из программы видно, что после динамического выделения памяти для матрицы можно обращаться к ее элементам в привычной для пользователя индексной форме. Освобождение использованной динамической памяти обязательно, так как неиспользуемые блоки не только уменьшают объем памяти, но и фрагментируют ее.
Символьные массивы
Описание массива типа
char string[80]; позволяет описывать и обрабатывать символьные данные, однако тип char[ ] относится к целому типу, поскольку имеет дело с ASCII-кодами символов.
Особым случаем массива является строковый литерал (строка-константа) – последовательность любых символов, заключенных в кавычки: “это строковый литерал”. Строковый литерал представляется в памяти как массив элементов типа char[ ], в конце которого помещен символ ‘\0’ (нуль-байт), поэтому его называют ACSIIZ-строкой (от zero – нуль). Как и с любым массивом, со строковым литералом связан указатель-константа на первый элемент строки.
Можно также описать указатель символьного типа
char *str; который пока не связан ни с какой символьной информацией, но ему выделено два байта для хранения адреса.
Символьные массивы можно инициировать, вводить и выводить поэлементно, как и числовые массивы, однако более рационально выполнять инициирование с помощью строковых литералов, а ввод-вывод - по ссылке на имя символьного массива, которое является указателем-константой на первый элемент массива (с индексом 0).
Совершенно идентичны следующие описания с инициализацией:
char string [10] = {‘с’,’л’,’о’,’в’,’о’,’\0’};
char string [10] = ”слово”;
Последнее описание выглядит проще, а нулевой байт записывается в конце строки автоматически. Адрес первого элемента (‘с’) становится адресом массива string. Строка в массиве ограничена нуль-байтом, а остальные элементы массива не используются. Можно не задавать размер массива, компилятор выделит нужное число элементов плюс нуль-байт при инициализации:
char string[ ]=”слово”;
Описание вида char *str; выделит место в памяти только для указателя str, а место для отсутствующей строки не выделяется. Можно инициировать указатель строковым литератором, например,
char *str = “Иванов А.С.”; или присваиванием определить указатель:
str = “Иванов А.С.”; тогда указатель получит адрес первого символа литерала, который располагается в сегменте данных программы.
Стандартные функции ввода-вывода можно использовать для обработки символьных данных.
Пример 30.
Ввести и вывести строку символов.
Программа:
void main ( )
{ char name [25];
puts (“Введите Фамилию И.О.:”);
gets ( name );
puts (“Вывести Фамилию И.О.:”);
puts ( name ); // или printf(“%s”, name);
}
Функция scanf (“%s”, name) менее удобна, поскольку вводит символы до первого пробела.
При использовании указателя для выделения памяти под любое содержимое строки необходимо использовать функции динамического выделения памяти.
Пусть дано описание указателя
char *str; тогда оператор вида
str = (char*) malloc (30); выделит 30 байтов в динамической памяти и запишет адрес начала этого блока в указатель str.
Пример 31.
Ввод и вывод строк в динамической памяти.
Программа:
#include <alloc.h>
void main ( )
{ char *name;
pname = (char*) malloc (30); // выделение динамической памяти
printf (“Введите Фамилию И.О.:”);
gets ( name );
printf (“Вывод Фамилии И.О.:”);
free (name ); // освобождение динамической памяти
}
Особый случай – инициализация двухмерного массива строковыми литералами. Многомерные массивы могут инициироваться и без указания одной (самой левой) из размерностей массива в [ ]. Компилятор сам определяет число элементов по числу членов в списке инициализации.
Например,
char str [ ] [80] = { “Первая строка”,
“Вторая строка”,
“Третья строка” };
Как было показано выше, элементы массива str[0], str[1], str[2] являются указателями на строки двухмерного массива, что можно использовать при обработке таких массивов.
Пример 32.
Ввод и вывод строковых данных.
Программа:
void main ( )
{ int i ;
char buffer [3][80];
puts (“Введите строки в буфер:”);
for (i=0; i<3; i++) gets (buffer[i]);
puts (“Вывод строк из буфера:”);
for (i=0; i<3; i++) puts (buffer[i]);
}
Пример 33.
Скопировать символы из массива s1, на который ссылается указатель ps1, в массив s2, на который ссылается указатель ps2. Исходная строка заканчивается символом ‘\0’.
Программа:
void main ( )
{ char s1[80], s2[80], *ps1=s1, *ps2=s2; // инициализация указателей
clrscr ();
puts (“Введите строку и нажмите Enter:”);
gets (s1); // чтение строки и запись нуль-байта
while (*ps1!= ’\0’) // цикл пока не конец строки
{ *ps2 = *ps1; // копирование символа
ps1++; // переход к следующему символу в s1
ps2++; // переход к следующему символу в s2
}
*ps2 = ’\0’; // запись нуль-байта в строку s2
printf (“Скопирована строка :\n%s”,s2); // вывод строки s2
}
С учетом старшинства и действий операций присваивания (=), разадресации (*) и увеличения (++) копирование символов в цикле while можно выполнить в заголовке оператора while() с пустым телом цикла с одновременной проверкой условия:
while ( *ps2++ = *ps1++ ) ;
Язык С (и С++) одновременно и любят и не любят за возможность использования столь лаконичных конструкций, которые требуют высокой квалификации программистов, но затрудняют понимание алгоритма.
Проще выглядит копирование символов с помощью оператора цикла:
for ( i=0; s2[i]=s1[i]; i++ ) ; // тело цикла – пустой оператор