Вы находитесь на странице: 1из 59

Министерство транспорта и связи Украины

Государственный департамент по вопросам связи и информатизации


Одесская Национальная академия связи им. А. С. Попова

Е. Г. Трофименко

Информатика
Модуль 2
Программирование задач с циклами и массивами

Часть 1

Конспект лекций
для студентов всех специальностей академии

УТВЕРЖДЕНО
методическим советом
факультета
информационных систем

Протокол № 10 от 5.04.2007 г.

Одесса 2007
Введение
С++ Builder – одна из самых мощных систем, позволяющая на самом современном
уровне создавать как отдельные прикладные программы Windows, так и разветвленные
комплексы, предназназченные для работы в корпоративных сетях и в Интернет. Среда С++
Builder объединяет средства языка программирования С++ и компонентно-ориентированный
подход к разработке программ. Сочетание простоты освоения визуальной среды разработки
и поддержка широчайшего спектра технологий делают С++ Builder универсальным
инструментом разработки приложений любого уровня сложности. Даже начинающие
программисты могут быстро создавать приложения с профессионально выглядящим
оконным интерфейсом самой разной направленности, от чисто вычислительных и
логических, до графических и мультимедиа.
В практике программирования важную роль играют циклы, которые обеспечивают
возможность многократного выполнения группы одних и тех же простых или составных
операторов. Циклические вычислительные процессы являются наиболее распространенными
и незаменимыми при обработке массивов.
В данном методическом пособии представлен конспект лекций модуля 2
“Программирование задач с циклами и массивами” по информатике. Кроме теоретических
сведений каждая из предложенных лекций содержит примеры составления программных
проектов средствами С++ Builder для решения задач по рассматриваемой теме лекции.
Эта (первая) часть пособия будет полезной для студентов для закрепления лекционного
материала и при подготовке к лабораторным и практическим занятиям по дисциплине
“Информатика” в модуле 2.

3
Структура модуля 2
Дисциплина “Информатика” изучается в I – II семестрах и предназначена для обучения
студентов работе на персональном компьютере с целью его использования в будущей
профессиональной деятельности.
Целью курса информатики является формирование у студентов знаний и умений в
таких областях, как:
архитектура компьютера;
работа в среде операционной системы Windows;
алгоритмизация вычислительных процессов;
составление программ на алгоритмическом языке С++;
знакомство с объектно-ориентированным программированием на примере решения
простейших задач в среде программирования С++ Builder.
Программа курса состоит из четырех модулей:
модуль 1 – Основные сведения о персональном компьютере и организации
вычислительных процессов;
модуль 2 – Программирование задач с циклами и массивами;
модуль 3 – Программирование задач со структурированными типами данных;
модуль 4 – Основы объектно-ориентированного программирования.
В соответствии с учебным планом структура модуля 2 имеет вид:
Вид занятий Количество часов
Лекции 8
Практические занятия 16
Лабораторные работы 16
Всего аудиторного времени: 40
Индивидуальная и самостоятельная работа студентов 27
Всего часов: 67

Перечень знаний и умений, с которыми студент должен приступить к изучению


содержания данного модуля
Курс информатики, изучаемый в академии, базируется на школьном курсе
информатики (студенты должны владеть знаниями в объеме школьного курса информатики
согласно программе Министерства образования Украины) и опирается на школьный курс
математики и дисциплину «Высшая математика» (функции, формулы преобразования,
факториал, ряды, интеграл, матрица, операции с матрицами и т. д.).
Предполагается, что, приступая к изучению содержания модуля 2 курса информатики,
студент уже изучил материал модуля 1, и овладел следующими знаниями и умениями:
знания по темам:
 архитектура компьютера;
 операционная система Windows;
 среда программирования С++ Builder;
 алгоритм, его свойства и средства описания;
 элементы алгоритмического языка С++ и программы с линейной структурой;
 логические выражения и приоритет выполнения логических выражений;
 операторы передачи управления в С++;
умения:
 работы с файлами в операционной системе Windows;
 разработки алгоритмов, составления программ с линейной и разветвленной
структурой в C++ Builder и выполнения их на компьютере.

4
Тематический план лекций
Лекция 1 Организация циклических вычислительных процессов. Вычисление сумм
конечного и бесконечного количества слагаемых. Табулирование функций.
Лекция 2 Функции.
Лекция 3 Обработка массивов в С++. Одномерные массивы. Организация ввода/вывода
элементов одномерного массива. Алгоритмы вычисления сумм, произведений, количества
элементов одномерных массивов.
Лекция 4 Двумерные массивы. Организация ввода/вывода элементов двумерного
массива. Алгоритмы вычисления сумм, произведений элементов двумерных массивов.
Лекция 5 Динамическая память. Указатели.

Перечни лабораторных работ и практических занятий


Лабораторные работы:
1 Выполнение проекта с использованием оператора цикла с параметром.
2 Создание и выполнение проекта с использованием операторов цикла с условием.
Табулирование функций. Построение графиков.
3 Выполнение программных проектов с созданием и вызовом функций в С++ Builder.
4 Выполнение программ обработки одномерных массивов (суммы и произведения).
5 Выполнение программ с одномерными массивами: сортировка одномерных массивов, поиск
максимума/минимума.
6 Выполнение программ с двумерными массивами. Организация ввода/вывода элементов
двумерного массива. Вычисление сумм, произведений элементов двумерных массивов
7 Выполнение программ обработки одномерных массивов с использованием указателей.
8 Выполнение программ обработки двумерных массивов с использованием указателей.
9 Выполнение проектов обработки одномерных и двумерных массивов с использованием
динамического распределения памяти.
Практические занятия:
1 Составление программ с использованием оператора цикла с параметром. Алгоритм создания
сумм и произведений. Составление программ вычисления сумм с бесконечными рядами.
Выдача комплексного задания.
2 Табулирование функции. Графика в С++ Builder. Компонент Chart. Составление программ с
вложенными циклами. Функции в С++ Builder. Контрольная работа № 1 по теме “Циклы”.
3 Составление проектов программ с одномерными массивами: сумма, произведение,
количество по условию.
4 Программы поиска максимума и сортировка одномерных массивов.
5 Составление проектов с двумерными массивами: суммы и произведения. Компонент
StringGrid. Контрольная работа № 2 по теме “Одномерные массивы”.
6 Примеры разработки программ с многомерными массивами: вычисление элементов массивов
по условию, скалярное произведение.
7 Составление программ с многомерными массивами. Указатели на одномерные и
многомерные массивы. Примеры программ с указателями.
8 Работа с динамической памятью. Динамическое размещение массивов. Примеры программ с
указателями и динамическим распределением памяти. Сдача и защита комплексного задания.
9 Контрольная работа № 3 по теме “Многомерные массивы”.

5
Рекомендации к самостоятельной работе студента
Для закрепления изучаемого материала данного модуля студенту выделяется 27 часов
для самостоятельной работы и выполнения индивидуальных заданий. Рекомендуется
распределить это время следующим образом:
Количество
Вид работы
часов
Проработка лекций 4
Изучение дополнительного материала к лекциям 4
Подготовка к упражнениям, практическим занятиям 4
Подготовка к лабораторным работам 9
Выполнение комплексного индивидуального задания по теме: «Составление
6
алгоритмов и программ с циклической структурой и массивами».
Всего: 27
Индивидуальные варианты комплексного задания для самостоятельного выполнения
выдает преподаватель. Как правило, все задания выбираются из зборника задач для
программирования (см. № 2 из списка дополнительной литературы). Все выполненные
задания должны быть оформлены в отдельной тетради. Каждое выполненное задание должно
содержать:
 схемы алгоритмов для решения индивидуального задания;
 форму проекта;
 тексты программ в С++ Builder.

Перечень знаний и умений, которые должен приобрести студент в процессе изучения


материала модуля 2
Знания по темам:
 циклические вычислительные процессы;
 операторы цикла с параметром, предусловием и постусловием;
 массивы. Векторы и матрицы.
Умения:
Разрабатывать и выполнять на компьютере алгоритмы и программы для решения задач:
 вычисление конечных и бесконечных сумм и произведений;
 вычисление таблиц значений функции по заданной формуле;
 вычисление с вложенными циклами;
 вычисление элементов вектора или матрицы по формуле;
 вычисление сумм и произведений элементов вектора или матрицы;
 вычисление количества элементов вектора или матрицы по условию;
 вычисление минимальных или максимальных значений элементов вектора или
матрицы;
 сортировка элементов массивов.

Дополнительная литература
1 Леонов Ю.Г., Силкина Н.В., Шпинова О.Д. Программирование на алгоритмическом
языке С++. Учеб. пособие с лабораторным практикумом. – Одесса: ОНАС, 2002. – 68 с.
2 Леонов Ю.Г., Угрік Л.М., Швайко І.Г. Збірник задач з програмування. – Одеса:
УДАЗ, 1997. – 80 с.
3 Архангельский А.Я., Тагин М.А. Программирование в С++ Builder 6 и 2006. – М.:
Бином, 2007. – 1184 с.
4 Архангельский А.Я. Программирование в С++ Builder 5. – М.: Бином, 2000. – 1152 с.
5 Березин Б.Н., Березин С.Б. Начальный курс С и С++. – М: Диалог-МИФИ, 2000. –
288 с.
6 Страуструп Б. Язык программирования С++. – СПб.-М.: Бином, 1999. – 991 с.

6
Конспект лекций

7
Лекция 1
Организация циклических вычислительных процессов
Вычислительный процесс называется циклическим, если он неоднократно повторяется
до тех пор, пока не будет выполнено заданное условие. Группу повторяющихся операторов
называют телом цикла.
В С++ существует три разновидности оператора цикла:
оператор цикла for;
оператор цикла с предусловием while;
оператор цикла с постусловием do-while.
1.1 Оператор цикла с параметром for
Как правило, этот оператор используется, если заранее известно количество
повторений. Примерами использования данного оператора могут быть нахождение суммы
заданного количества элементов массива, поиск минимального (максимального) элемента
массива, сортировка элементов массива по возрастанию (по убыванию) и др.
Синтаксис оператора следующий:
for (выражение1; выражение2; выражение3) оператор;
Сначала выражение1 задает начальное значение переменных, управляющих циклом,
затем проверяется условие в выражении2, которое является условием продолжения цикла.
Если условие истинно (имеет ненулевое значение), то выполняется оператор (или группа
операторов в операторных скобках {}), после чего выражение3 изменяет переменные,
управляющие циклом, и в случае истинности условия выполнения цикла продолжается. Если
выражение2 равно нулю (ложь), то управление передается на оператор, следующий за
оператором for.
Существенно то, что проверка условия всегда выполняется в начале цикла. Это значит,
что тело цикла может ни разу не выполниться, если условие выполнения сразу будет
ложным.
10
Простейший пример для вычисления конечной суммы s  i
i 1
s=0
проиллюстрирует использование оператора for:
int s = 0; i  1, 10
for (int i = 1; i <= 10; i++) s += i;
Возможно наличие пустого оператора (отсутствие оператора) в s=s+i
теле цикла. Так, сумму из предыдущего примера можно вычислить
иначе:
for (int s = 0 , i = 1; i <= 10; s += i++);
В этом примере выражение1 включает в себя два оператора, разделенных операцией
запятая и задающих начальные значения переменным s и i.
Во втором варианте решения отсутствует оператор, а вначале задается значение двух
переменных. В операторе возможны конструкции, когда отсутствует то или иное выражение:
выражение1 может отсутствовать, если начальное значение задать до этого; выражение2 –
если предполагается, что условие всегда истинно, т. е. следует обязательно выполнять тело
цикла, пока не встретится break; а выражение3 – если приращение параметра осуществлять в
теле цикла или таковое не требуется. Тогда само выражение пропускается, но точка с
запятой обязательно должна наличествовать.
Примеры оператора цикла для вычисления факториала F=n! (напомним, что факториал
вычисляется по формуле (n)!=1*2*3*...*(n–2)*(n–1)*n. Например, 4!=1*2*3*4=24):
1) int F = 1, n = 5; for (int i = 1; i <= n; i++) F *= i;
2) int F, i, n = 5; for (F = 1, i = 1; i <= n; F *= i++);

8
Рассмотрим несколько примеров решения задач, в которых целесообразно
использование оператора цикла for. Для решаемых задач разработаны алгоритмические
схемы (блок-схемы), задающие порядок выполнения действий, и приведены формы с
результатами вычислений.
Общим правилом вычисления сумм конечного или бесконечного количества слагаемых
является необходимость начального обнуления переменной, в которой будут суммироваться
все слагаемые (см. задачу 1.1). Для вычисления произведения (см. задачу 1.2) перед циклом
необходимо переменной, в которой будут перемножаться все множители, присвоить
начальное значение 1. Эти действия необходимы, поскольку при первом выполнении цикла
суммирование с нулем (или умножение на единицу) не исказит значение результата.
3
(i  2)(i  4)
Задача 1.1 Вычислить сумму конечного числа слагаемых S   ,
i  3 (i  2)(i  5)

где і = –3, –2, …, 2, 3. используя в программе оператор цикла с параметром.


Начало При выполнении вычислений не следует учитывать те
слагаемые, у которых числитель или знаменатель будут равными
S=0 нулю. В данном случае при значении i = –2 образуется нуль в числи-
теле, а при i = 2 – в знаменателе, поэтому условный оператор if по-
i   3,3 может исключить эти слагаемые. Схема алгоритма программы и
пример формы с результатами работы
Не приведены на рис. 1.1 и 1.2.
тт i  2
Да Текст программы:
void __fastcall TForm1::Button1Click(TObject
(i  2)(i  4)
S S *Sender)
(i  2)(i  5) { int i; float S = 0; Рисунок 1.2 – Форма
for (i = -3; i <= 3; i++) с результатами работы
if ( i != 2 && i != -2)
S += (float) (i + 2) * (i + 4) / ((i - 2) * (i + 5));
Вывод S Edit1->Text = FormatFloat("0.000", S);
}
Конец
Рисунок 1.1 –
(k  1)(k  5)
4
Блок-схема Задача 1.2 Вычислить произведение z ,
к задаче 1.1 k  2 k (k  6)
где k = –2, –1, …, 3, 4. Ограничения те же, что и в предыдущей задаче.
Схема алгоритма программы
Начало
и пример формы с результатами
z=1 работы приведены на рис. 1.3 и
1.4.
k   2, 4
Рисунок 1.4 – Форма
Текст программы: с результатами работы
Нет void __fastcall TForm1::Button1Click(TObject *Sender)
k  0, k  -1
{ int k; float p, z = 1;
Да for (k = -2; k <= 4; k++)
(k  1)(k  5) if (k != -1 && k != 0)
z  z z *= (float) (k + 1) * (k - 5) / (k * (k + 6));
k (k  6)
Edit1->Text = "z=" + FormatFloat("0.0000", z); }
В данной программе для вывода значений на экран
Вывод используется функция FormatFloat, которая преобразует число в
z строку. Первый параметр этой функции задает формат, второй –
задает число, которое преобразуется. Выходным значением этой
Конец функции является строка, полученная в результате преобразования
Рисунок 1.3 – и форматирования числа.
Блок-схема к задаче 1.2
9
1.2 Вложенные циклы
Циклы могут быть вложены один в другой. При использовании вложенных циклов
необходимо составлять программу таким образом, чтобы внутренний цикл полностью
вкладывался в тело внешнего цикла, т. е. циклы не должны пересекаться. В свою очередь,
внутренний цикл может содержать свои вложенные циклы. Имена параметров внешнего и
внутреннего циклов должны быть разными. Допускаются следующие конструкции:
for (k=1; k<=10; k++)
{ …
for (i=1; i<=10; i++)
{…
for (m=1; m<=10; m++)
{ ...
}
}
}
Рассмотрим несколько примеров решения задач с вложенными циклами.
n  1 n 3 k
m
Задача 1.3 Вычислить значение S    ; значение m ввести с экрана.
n  2 n k 1 k  1
В данной задаче циклы вложены один в другой, поскольку параметр внутреннего
цикла k зависит от параметра внешнего цикла n (k изменяется от 1 до n + 3). Произведение
n 3
k
k 1 k  1
является сомножителем слагаемого и вычисляется во внутреннем цикле в

переменной P. Поскольку внутренний цикл состоит только из одного оператора, то


Начало операторные скобки { } здесь необязательны.
Перед внешним циклом для вычисления суммы обнуляется
Ввод m переменная S, в которой будут накапливаться слагаемые, а перед
внутренним циклом для вычисления произведения переменной p
S=0 присваивается значение 1.
Поскольку при вычислении произведения участвуют только
n   2, m целые числа, то необходимо использовать операцию приведения
типов (float) k / (k + 1).
Нет Схема алгоритма программы и пример формы с результатами
n≠-1, n≠0 работы приведены на рис. 1.5 и 1.6.
Да
P=1

k  1, n  3

P=P*k/(k+1) Рисунок 1.6 – Форма с результатами работы


Текст программы:
void __fastcall TForm1::Button1Click(TObject *Sender)
S=S+P*(n+1)/n { int n, k, m = StrToInt(Edit1->Text);
float S = 0, p;
for (n = -2; n <= m; n++)
Вывод if (n != -1 && n != 0)
{ p = 1;
S
for (k = 1; k <= n + 3; k++)
Конец if (k != -1 && k != 0) p *= (float) k / (k + 1);
S += (n + 1) * p / n;
Рисунок 1.5 – }
Блок схема Edit2->Text = FloatToStrF(S, ffGeneral, 6, 5);
к задаче 1.3 }

10
7
2  x 2i 1
Задача 1.4 Вычислить сумму ряда S   , где i = 1, 2, …, 7.
i 1 3  ( 2i  1)!

Для вычисления суммы S необходимо просуммировать семь слагаемых, для расчета


каждого из которых необходимо организовать вложенный цикл для подсчета факториалов
(2i  1)! . В данной программе каждое слагаемое вычисляется в
Начало отдельной переменной а.
Поскольку в вычислениях используется математическая
Ввод х
функция возведения в степень pow, то вначале в соответствующем
разделе проекта необходимо подключить математическую
S=0 библиотеку math.h. Схема алгоритма программы и пример формы с
результатами работы приведены на рис. 1.7 и 1.8.
i  1,7
fact=1

k  1,2i  1

fact = fact * i Рисунок 1.8 – Форма с результатами работы


Текст программы:
.....
Вычисл. а #include <math.h>
.....
S=S+а void __fastcall TForm1::Button1Click(TObject *Sender)
{ int i, k, fact;
float S = 0, a, x = StrToFloat (Edit1->Text);
for (i = 1; i <= 7; i++)
Вывод { fact = 1;
S for (k = 1; k <= 2 * i - 1; k++) fact = fact * k;
a = 2 * pow(x, 2 * i - 1) / (3 * fact);
Конец s += a;
Рисунок 1.7 – } Edit2->Text = FloatToStrF(s, ffGeneral, 4, 3);
Блок схема }
к задаче 1.3
1.3 Табулирование функций. Построение графиков
Очень часто при исследовании функциональных зависимостей возникает необходи-
мость построения графика Y = f(X), для чего надо построить таблицу значений Y = f(X) на
заданном промежутке Х  [А, B] с шагом h. Попробуем составить программу табулирования
функции Y = f(X) при изменении Х от А до B с шагом h, когда f(X) = cos 2 3 x .
Для построения графика функции установим на форме компонент Chart,
расположенный на закладке Additional. Для настройки компонента Chart дважды щелкнем на
изображении этого компонента. В появившемся диалоговом окне надо щелкнуть по кнопке
Add. Из дополнительного диалогового окна выбрать тип графика Line. Для увеличения
отображаемого размера графика на форме можно удалить отображение “легенды”, для чего,
перейдя на закладку Legend, в позиции Visible удалить “галочку”. С аналогичной целью
можно перейти на закладку Titles и удалить надпись TChart, а можно просто изменить
надпись графика, например на «График функции». Пример формы для табулирования может
иметь вид, показанный на рис. 1.9.
В приведенном ниже тексте программы табулирования заданной функции исходными
данными, которые надо ввести из компонентов Edit1, Edit2 и Edit3, являются начальное А и
конечное В значения х, а также шаг изменения х – h.

11
Рисунок 1.9 – Форма проекта “Табулирование функции”
В цикле, начиная с x = А и пока будет истинно условие x <= B + 0.1 * h, вычисляется
значение функции у и выводится ее числовое значение в компонент Memo1 и на график в
компонент Series1, после чего х увеличивается на значение шага x += h. Небольшая
величина 0.1 * h, не превышающая величины шага, в условии используется по той причине,
что при вычислении существует так называемое накопление погрешности вычислений,
например, когда 4  3,9999(9). В таком случае последние значения х и y не выводились бы
на форму. Вывод точек на график осуществляется с помощью метода AddXY(x, y, “”, clRed).
Четыре параметра в скобках для этого метода задают параметры выводимой точки: первое
значение x – координату по горизонтальной оси, второе значение y – координату по
вертикальной оси, третий параметр задает параметры отображения числовых значений на
осях (две пустые кавычки “” означают, что подписи будут формироваться автоматически),
четвертый параметр задает цвет выводимой точки. Цвет (color) указывается после символов
cl с большой буквы; так, clRed задает красный цвет, clBlue – синий, clGreen – зеленый и т. п.
Текст программы:
#include <math.h>
.....
void __fastcall TForm1::Button1Click(TObject *Sender)
{ float A, B, h, x, y;
A = StrToFloat (Edit1->Text); B = StrToFloat (Edit2->Text); h = StrToFloat (Edit3->Text);
for (x = A; x <= B + 0.1 * h; x += h)
{ y = pow(fabs(pow(cos(x), 2)), (float)1./ 3);
Memo1 -> Lines -> Add(FormatFloat("0.00", x) + " " + FormatFloat("0.0000", y));
Series1 -> AddXY(x, y, “”, clRed);
} }

1.4 Операторы цикла c предусловием while и постусловием do-while


while (условие) { последовательность операторов };
Нет
Последовательность операторов цикла выполняется нуль Условие?
или несколько раз до тех пор, пока условие истинно (имеет Да
ненулевое значение), а выход из цикла осуществляется тогда, Операторы
когда оно станет ложным (равно нулю).
Вначале вычисляется и проверяется условие. Если оно
изначально ложно, то последовательность операторов не

12
выполняется – и управление передается на следующий оператор программы. Если условие
истинно (не нуль), то выполняется последовательность операторов. Повторение
выполнения операторов происходит до тех пор, пока условие остается истинным.
do { последовательность операторов } while (условие);
Последовательность операторов выполняется один или Операторы
несколько раз до тех пор, пока условие станет ложным (равным
нулю). Оператор цикла do-while используется в тех случаях, когда Да
Условие?
необходимо выполнить тело цикла хотя бы один раз, так как
проверка условия осуществляется после выполнения операторов. Нет
Вначале выполняется последовательность операторов, затем
вычисляется и проверяется условие. Если условие ложно, то оператор завершается – и
управление передается следующему оператору в программе. Если условие истинно (не равно
нулю), то тело оператора выполняется снова и опять проверяется условие. Выполнение тела
оператора продолжается до тех пор, пока условие не станет ложным.
Если тело цикла состоит из одного оператора, то операторные скобки {} необязательны.
Операторы while и do-while могут также завершиться при выполнении операторов break,
goto, return, расположенных внутри тела цикла.
Оператор цикла вида for (выражение1; выражение2; выражение3) оператор; может
быть заменен оператором while следующим образом:
выражение1;
while (выражение2)
{ операторы;
выражение3; }
Так же как и при выполнении оператора for, в операторе while вначале происходит
проверка условия. Поэтому оператор while удобно использовать в ситуациях, когда
операторы тело цикла не всегда нужно выполнять.
Так, цикл for из предыдущей задачи табулирования с использованием оператора цикла
while может выглядеть следующим образом:
.....
x = A;
while(x <= B + 0.1 * h)
{ y = pow(fabs(pow(cos(x), 2)), (float)1./3);
Memo1->Lines->Add(FormatFloat("0.00", x) + " " + FormatFloat("0.0000", y));
Series1->AddXY(x, y, "", clRed);
x += h; }
.....
Тот же цикл с использованием оператора do-while будет выглядеть как
x = A;
do { y = pow(fabs(pow(cos(x), 2)), (float)1./3);
Memo1->Lines->Add(FormatFloat("0.00", x) + " " + FormatFloat("0.0000", y));
Series1->AddXY(x, y, "", clRed);
x += h ; }
while(x <= B + 0.1 * h) ;
В обоих случаях начальное значение x=A присваивается до цикла, а в цикле необходимо
предусмотреть оператор x+=h для изменения значения х, иначе условие выхода из цикла
никогда не будет выполнено и произойдет зацикливание.
Внутри операторов цикла можно использовать локальные переменные, которые
должны быть объявлены с определением соответствующих типов.
Рассмотрим работу разных операторов цикла на примере нахождения суммы всех
нечетных чисел в диапазоне от 10 до 100:
1) с использованием оператора for
int s = 0; for (int i = 11; i < 100; i += 2) s += i;
или int i, s; for (i = 11, s = 0; i<100; s += i++, i++);
или int i, s; for (i = 11, s = 0; i<100; s += i, i += 2);

13
2) с использованием оператора while
int s = 0, i = 11; while (i < 100) {s += i; i += 2;}
3) с использованием оператора do-while
int s = 0, i = 11; do {s += i; i += 2;} while (i < 100);
5
(1)i xi 1
Задача 1.5 Вычислить сумму знакопеременного ряда S  тремя
i 1 i!
вариантами, используя разные операторы цикла.
Здесь (–1)i при нечетных значениях i (1, 3, 5, …) равняется –1, а при четных значениях
i (2, 4, 6, …) – 1, т. е. рассматривается знакочередующийся ряд. Поэтому в программе надо
предусмотреть проверку на четность (i % 2 == 0). Схемы алгоритмов и пример формы с
результатами работы программы приведены на рис. 1.10 и 1.11.

Начало Начало Начало

S=0 S = 0, i=1 S = 0, i=1

Ввод х Ввод х Ввод х


Нет
i = 1, 5 i <= 5 f=1, k=1
Да
f = 1, k=1
f=1 f=f*k, k=k+1
Нет
k = 1, i
k <= i Да
Да k <= i
f=f*k, k=k+1 Нет
f=f*k Нет Да
i–четн?
S=S–xi+1/f S=S+xi+1/f
Нет Да
Нет Да i–четн?
i–четн? i=i+1
S=S–xi+1/f S=S+xi+1/f
i+1 i+1
S=S–x /f S=S+x /f Да
i=i+1 i <= 5
Нет
Вывод Вывод
Вывод
S S
S
Конец Конец Конец
a б в
Рисунок 1.10 – Блок-схемы для разных операторов цикла:
a – For; б – while; в – do-while

Примеры программ с использованием разных операторов цикла:


void __fastcall TForm1::Button1Click(TObject *Sender) // For
{ int i, f, k;
float S = 0, x = StrToFloat(Edit1->Text);
for (i = 1; i <= 5; i++)
{ for (k = 1,f = 1; k <= i; k++) f *= k;
If (i % 2 == 0) S += pow(x, i+1) / f; else S -= pow(x, i+1) / f ;
}
Edit2->Text = FloatToStr(S);
}

14
void __fastcall TForm1::Button2Click(TObject *Sender) // while
{ int i=1, f, k;
float S = 0, x = StrToFloat(Edit1->Text);
while(i <= 5)
{ f = 1; k = 1;
while(k <= i) { f *= k; k++; }
if(i % 2 == 0) S += pow(x, i + 1) / f; else S -= pow(x, i + 1) / f ;
i++;
} Edit3->Text = FloatToStr(S); Рисунок 1.11 – Форма
} с результатами работы
//---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender) // do-while Начало
{ int i = 1, f, k;
float S = 0, x = StrToFloat(Edit1 -> Text); Ввод х
do { f = 1; k = 1;
do { f *= k; k++; } while(k <= i); S = 0, k=0
if (i % 2 == 0) S += pow(x, i+1) / f; else S -= pow(x, i+1) / f ;
i++;
} while( i <= 5); k=k+1, f=1
Edit4 -> Text = FloatToStr(S); }

x 2 k 1 i  1,2k  1
Задача 1.6 Вычислить сумму ряда S  ,
k 1 (2k  1)!
f=f*i
суммируя члены ряда, значения которых по модулю больше
заданной точности   104 . Определить количество слагаемых.
Значение х (– 2 < x < 2) вводить с клавиатуры. Вычисл. а
При разработке программы для решения данной задачи
неуместно использовать оператор цикла с параметром for, посколь- S=S+а
ку заранее неизвестно количество повторений такого цикла.
Целесообразным будет использование оператора цикла с Да
постусловием do-while, поскольку на момент первой проверки a  10 4
условия уже необходимо знать значение первого слагаемого. Нет
Схема алгоритма и пример формы с результатами работы Вывод
программы приведены на рис. 1.12 и 1.13. S, k
Текст программы:
Конец
void __fastcall TForm1::Button1Click(TObject *Sender)
{ float x, a, s = 0; // Объявление переменных Рисунок 1.12 –
int f, i, k = 0; // и установка их начальных значений Блок-схема к задаче 1.6
x = StrToFloat(Edit1->Text); // Ввод значения х
do // Цикл с постусловием
{ k++; // Увеличение переменной k на 1
for(i=1,f=1; i<=2*k-1; i++) f *= i; // Вычисление факториала
a=pow(x, 2*k+1) / f; // Вычисление k-го слагаемого
s += a; } // Суммирование слагаемых
while(fabs(a) >= 1e-4);
Edit2->Text = FloatToStr(s) ; // Вывод полученной суммы
Edit3->Text = IntToStr(k); Рисунок 1.13 – Форма
// и количества слагаемых
} с результатами работы
Следует отметить, что организовывать бесконечные циклы можно и с использованием
оператора for с пустым условным выражением. Тогда для выхода из цикла обычно
используют дополнительное условие и оператор break. Пример:
for ( ; ; )
{ ...
... break;
... }

15
Задача 1.7 Вычислить сумму ряда y

 1k x 2 k , суммируя члены ряда,
k 1 2k (2k  1)!
значения которых по модулю больше заданной точности  . Определить количество
слагаемых. Значения х (– 2 < x < 2) и   10 4 вводить с клавиатуры.
Приведем два способа решения этой задачи. Для наглядности и контроля правильности
отдельно приведем все слагаемые. Множитель (–1)k при нечетных k = 1, 3, 5, … равняется
(–1), а при четных k = 2, 4, … – равняется 1. Таким образом, представленный ряд является
знакочередующимся, где все нечетные слагаемые будут отрицательными, а все четные – со
знаком «+». В программе оператор if(k%2==1)u=-u; для нечетных k изменяет знак слагаемого.
Следует заметить, что множитель (–1)k-1 или (–1)k+1 есть только в знакочередующихся
рядах с положительными нечетными и отрицательными четными слагаемыми.
Пример формы может иметь вид, показанный на рис. 1.14.
Первый способ решения:
void __fastcall TForm1::Button1Click(TObject *Sender)
{ float x, y = 0, u, eps; Memo1->Clear();
x = StrToFloat(Edit1->Text);
eps = StrToFloat(Edit2->Text);
int i, f, k = 1;
do
{ for(i = 1, f = 1; i <= 2 * k - 1; i++) f *= i;
u = pow(x, 2 * k) / (2 * k * f);
if (k % 2 == 1) u = -u;
Memo1->Lines->Add(IntToStr(k) + ") " +
FormatFloat("0.00000", u));
y += u;
k++;
} while (fabs(u) >= eps) ;
Edit3->Text = FloatToStr(y); Рисунок 1.14 – Форма с результатами
Edit4->Text = IntToStr(k - 1); }
работы
Для того чтобы сделать алгоритм программы более оптимальным, можно его усовер-
шенствовать, но для этого надо вычислить рекуррентный множитель. Это позволит в данной
программе избавиться от вложенного цикла для вычисления факториала и оператора про-
верки на нечетность k. Остановимся более подробно на выведении рекуррентной формулы.

Представим сумму ряда y  



 1k x 2 k 
в виде y   u k , где uk 
 1k x 2 k
.
k 1 2k (2k  1)! k 1 2k (2k  1)!
Рекуррентный множитель – это соотношение двух рядом расположенных членов ряда:
u2  R  u1 ;
u3  R  u2 ;

uk  R  uk 1 ,
u
откуда R k . (1.1)
uk 1

Для определения R в формулу (1.1) следует подставить uk 


 1k x2k и uk 1 . Для
2k (2k  1)!
вычисления uk 1 подставим в выражение для u k вместо k – (k – 1):

uk 1 
 1k 1 x2(k 1) =
 1k 1 x2( k 1) ;
2(k  1)( 2(k  1)  1)! 2(k  1)( 2k  3)!

16
R
uk
=
 1k x2k ∙ 2(k  1)(2k  3)! = (1)k  x2k  (2k  2)  (2k  3)! =
uk 1 2k (2k  1)!  1k 1 x 2( k 1) (1)k 1  x 2k  2  2k  (2k  1)!
(1)k  k 1  x 2k ( 2k  2) (2k  2) 1  2  ...  (2k  3) x2
=  =– .
2k 1  2  ...  (2k  3)  (2k  2)  (2k  1) 2k (2k  1)
Кроме рекуррентного множителя R, надо вычислить первый член ряда при k = 1:

u1 
 11 x 2 =  x 2 .
2(2  1)! 2
Второй способ решения с использованием рекуррентных множителей:
void __fastcall TForm1::Button1Click(TObject *Sender)
{ float x, y, u, r, eps; Memo1->Clear();
x = StrToFloat(Edit1->Text);
eps = StrToFloat(Edit2->Text);
int i, f, k = 1;
u = -x * x / 2; y = u;
Memo1->Lines->Add(IntToStr(k) + ") " + FormatFloat("0.00000", u));
do { k++;
r = -x*x/(2*k*(2*k-1));
u *= r ;
Memo1->Lines->Add(IntToStr(k) + ") " + FormatFloat("0.00000", u));
y += u;
} while(fabs(u) >= eps) ;
Edit3->Text = FloatToStr(y);
Edit4->Text = IntToStr(k); }

1.5 Операторы прерывания выполнения циклов


Для прерывания повторений операторов цикла любого типа в теле цикла может быть
использован оператор break. Оператор break передает управление оператору, следующему
за прерванным. Внутри вложенных операторов оператор break завершает только операторы
do, for, switch или while. Чтобы передать управление вне вложенной структуры, могут быть
использованы операторы return и goto.
Для перехода к следующему повторению из любого места тела цикла может быть
применен оператор continue. Оператор continue, как и оператор break, используется только
внутри операторов цикла, но, в отличие от последнего, выполнение программы после
оператора continue продолжается не с оператора, следующего за прерванным оператором, а с
начала прерванного оператора, т. е. оператор continue равносилен переходу к самому концу
цикла. Оператор continue, как и оператор break, прерывает самый внутренний из
объемлющих его циклов.
Пример 1.1 for (int a = 1,b = 0; a < 100; b += a, a++)
{ if (b % 2) continue;
... /* обработка четных сумм */
}
Когда сумма чисел от 1 до а становится нечетной, оператор continue передает управление
на очередную итерацию цикла for, не выполняя операторов обработки четных сумм.
Пример 1.2 while (i-- > 0)
{ x = f(i);
if (x == 1) continue;
y = x * x; }
Тело оператора выполняется, если i > 0. Сначала f(i) присваивается x, затем, если x
равно 1, то выполняется оператор continue. Остальные операторы тела игнорируются, – и
выполнение возобновляется с заголовка цикла, т. е. вычисляется выражение i-- > 0.

17
Лекция 2
Функции в С++
2.1 Понятие функции в С++
Процесс разработки программного обеспечения предполагает разделение сложной
задачи на набор более простых задач и заданий. В С++ поддерживаются ф у н к ц и и как
логические единицы (блоки текста программы), служащие для выполнения конкретного
задания. Функции иногда еще называют п о д п р о г р а м м а м и. Подпрограммы решают
небольшую и специфическую часть общей задачи. В отличие от других языков
программирования высокого уровня, в языке С++ нет деления на подпрограммы-процедуры
и подпрограммы-функции.
Функция – это совокупность объявлений и операторов, обычно предназначенных для
решения определенной задачи. Каждая функция должна иметь и м я, которое используется
для ее объявления, определения и вызова.
При вызове функции ей при помощи аргументов (формальных параметров) могут быть
переданы некоторые значения (фактические параметры), используемые во время выполнения
функции. Функция может возвращать некоторое (одно!) значение. Это возвращаемое
значение и есть результат выполнения функции, который при выполнении программы
подставляется в точку вызова функции, где бы этот вызов ни встретился. Допускается также
использовать функции, не имеющие аргументов, и функции, не возвращающие никаких
значений. Действие таких функций может состоять, например, в изменении значений
некоторых переменных.
2.2 Правила организации функций
Определение функции задает тип возвращаемого значения, имя функции, типы и число
формальных параметров, а также объявления переменных и операторы, называемые телом
функции, и определяющие действие функции:
Тип_данных имя_функции ( формальные параметры )
{
/* Описание локальных переменных */
тип_данных локальная_переменная_1;
...........
тип_данных локальная_переменная_n;
/* Тело функции */
...........
}
Тип_данных функции задает тип возвращаемого значения и может задавать любой тип.
Если Тип_данных не задан, то предполагается, что функция возвращает значение типа int.
Если функция не возвращает значения (т. е. возвращает void), то она служит для того, чтобы
изменять свои параметры (вызывать побочный эффект) или глобальные для функции
переменные.
Функции имеют нуль или более формальных параметров, разделенных запятыми, и
возвращают значение скалярного типа, типа void (пусто) или указатель. Формальные
параметры – это переменные, используемые внутри тела функции и получающие значение
при вызове функции путем копирования в них значений соответствующих фактических
параметров. При вызове функции фактические значения, задаваемые на входе, должны
соответствовать количеству, типу и порядку расположения формальных параметров в
описании функции. При выполнении вызова функции происходит замена формальных
аргументов на фактические. Первый фактический аргумент соответствует первому
формальному аргументу, второй – второму и т. д. Формальные параметры могут быть
основного, структурного, совмещающего, адресного типов или типа массив. Если

18
формальный параметр представлен в списке параметров, но не объявлен, то предполагается,
что параметр имеет тип int. Несоответствие типов формальных и фактических параметров
может привести к серии ошибок, особенно когда несоответствие влечет за собой отличия в
размерах.
Список формальных параметров может заканчиваться запятой (,) или запятой с
многоточием (,...) – это означает, что число аргументов функции переменно. Однако
предполагается, что функция имеет, по крайней мере, столько обязательных аргументов,
сколько формальных параметров задано перед последней запятой в списке параметров.
Такой функции может быть передано большее число аргументов, но над дополнительными
аргументами не производится контроль типов. Если функция имеет переменное число
аргументов, то программист отвечает и за определение их числа и за получение их из стека
внутри тела функции.
Если функция не использует формальных параметров, то наличие круглых скобок
обязательно, а вместо списка параметров рекомендуется указать слово void.
Тело функции – это операторы, определяющие действие функции. Все переменные,
объявленные в теле функции, являются л о к а л ь н ы м и. При вызове функции локальным
переменным отводится память в стеке и производится их инициализация. Управление
передается первому оператору тела функции – и начинается выполнение функции, которое
продолжается до тех пор, пока не встретится оператор return или последний оператор тела
функции. Управление при этом возвращается в точку, следующую за точкой вызова, а
локальные переменные становятся недоступными. При новом вызове функции для
локальных переменных память распределяется вновь – и поэтому старые значения
локальных переменных теряются.
Например, функция, возвращающая куб ее вещественного аргумента:
double cube( double x )
{ return x * x * x ; }
Аргумент х типа double специфицируется вслед за первой открывающей скобкой.
Функция возвращает значение, если ее выполнение заканчивается оператором return,
содержащим некоторое выражение. Указанное выражение вычисляется, преобразуется, если
необходимо, к типу возвращаемого значения и возвращается в точку вызова функции в
качестве результата. Если оператор return не содержит выражения или выполнение функции
завершается после выполнения последнего ее оператора (без выполнения оператора return),
то возвращаемое значение не определено. Для функций, не использующих возвращаемое
значение, должен быть использован тип void, указывающий на отсутствие возвращаемого
значения. Для функций типа void оператор return в конце функции можно не указывать, а
возврат в головную программу будет осуществляться после выполнения последнего
оператора тела функции. Если функция определена как функция, возвращающая некоторое
значение, а в операторе return при выходе из нее отсутствует выражение, то поведение
вызывающей функции после передачи ей управления может быть непредсказуемым. Таким
образом, использование оператора return необходимо либо для немедленного выхода из
функции, либо для передачи возвращаемого значения.
Функция не может возвращать массив или функцию, но может возвращать указатель на
любой тип, в том числе и на массив и на функцию. Подробно указатели будут рассмотрены в
лекции 5 (см. с. 46).
В программах на языке С++ широко используются так называемые библиотечные
функции, т. е. функции, предварительно разработанные и записанные в библиотеки.
Прототипы библиотечных функций находятся в специальных заголовочных файлах,
поставляемых вместе с библиотеками в составе систем программирования, и включаются в
программу с помощью директивы #include.

19
2.3 Примеры организации функций в С++
Задача 2.1 Продемонстрируем правила организации функции на простом примере
вычисления суммы двух целых чисел, которые передаются в функцию в качестве входных
параметров:
int sum (int a, int b)
{ return (a+b); }
Функция sum имеет два формальных параметра – a и b целого типа int – и возвращает
значение типа int, о чем говорит описатель, стоящий перед именем функции. Возвращаемое
оператором return значение равно сумме фактических параметров. Пример формы может
иметь вид, показанный на рис. 2.1.
Вызов этой функции в основной программе может иметь вид:
void __fastcall TForm1::Button1Click(TObject *Sender)
{ int a, x, y;
a = StrToFloat(Edit1->Text);
x = StrToFloat(Edit2->Text);
y = sum(a, x); Рисунок 2.1 – Форма
Edit3->Text = IntToStr(y); } с результатами работы
При вызове функции фактические значения переменных a и х подставляются вместо
формальных параметров a и b (a на место a, х на место b), поскольку имена передаваемых
фактических переменных могут совпадать или не совпадать с именами формальных
параметров.
Задача 2.2 Проверить, является ли вводимый символ буквой русского алфавита или нет.
int rus (unsigned char r)
{ if (r>='А' && r <='я') return 1;
else return 0; }
Здесь определена функция с именем rus, имеющая один символьный параметр с
именем r. Функция возвращает целое значение, равное 1, если параметр функции является
буквой русского алфавита, или 0 – в противном случае.
5
(1) i 1 x i 1
Задача 2.3 Вычислить сумму знакопеременного ряда S   тремя вариан-
i 1 i!
тами, используя разные операторы цикла.
Такое задание уже было рассмотрено в лекции 1 (задача 1.5). Преобразуем решение
таким образом, чтобы в головной программе остались операторы ввода/вывода данных, а все
вычисления по выполнению задания организуем в функциях. Помимо этого преобразования,
можно отказаться от использования условия для проверки четности переменной i,
используя библиотечную функцию возведения в степень pow(-1, i +1).
Схемы алгоритмов с вызовом функций для кнопок формы проекта представлены на рис.
2.2, а на рис. 2.3 – схемы алгоритмов соответствующих функций.
Начало For Начало while Начало do_while

Ввод x Ввод x Ввод x

s=zikl_for(x) s=zikl_while(x) s=zikl_while(x)

Вывод s Вывод s Вывод s

Конец Конец Конец


а б в
Рисунок 2.2 – Блок-схемы с вызовом функций для кнопок:
а – For; б – while; в – do_while

20
Вход в zikl_while Вход в zikl_do
Вход в zikl_for
S = 0, i=1 S = 0, i=1
S=0 Не
i <= 5 f=1, k=1
i = 1, 5 Да т
f = 1, k=1 f=f*k, k=k+1
f=1 Нет Да
k <= i k <= i
Да
k = 1, i f=f*k, k=k+1 Нет
S=S+(–1)i+1xi+1/f
f = f *k
S=S+(–1)i+1xi+1/f i=i+1
Да
S=S+(–1)i+1xi+1/f i=i+1 i <= 5
Нет
Выход из zikl_for Выход из zikl_while Выход из zikl_do
a б в
Рисунок 2.3 – Блок-схемы функций:
а – zikl_ for; б – zikl_ while; в– zikl_do
Пример формы с результатами работы показан на рис. 2.4.
Тексты функций и их вызова в основной программе:
float zikl_for (float x)
{ int i, f, k; float S = 0;
for( i = 1;i <= 5;i++)
{ for(k = 1, f = 1; k <= i; k++) f *= k;
S += pow(-1, i +1) * pow(x, i + 1) / f ;
} return S; }
float zikl_while (float x)
{ int i = 1, f, k; float S = 0;
while( i <= 5)
{ f = 1; k = 1; while(k <= i) { f *= k; k++; }
S += pow(-1, i +1) * pow(x, i + 1) / f ;
i++;
} return S; }
float zikl_do(float x)
{ int i = 1, f, k; float S = 0;
do { f = 1; k = 1;
do { f *= k; k++; } while(k <= i) ; Рисунок 2.4 – Форма
S += pow(-1, i +1) * pow(x, i + 1) / f ; с результатами работы
i++; } while( i <= 5);
return S; }
void __fastcall TForm1::Button1Click(TObject *Sender)
{ float s, x=StrToFloat(Edit1->Text);
s=zikl_for(x);
Edit2->Text=FloatToStr(s); }
void __fastcall TForm1::Button2Click(TObject *Sender)
{ float s, x=StrToFloat(Edit1->Text);
s=zikl_while(x);
Edit3->Text=FloatToStr(s); }
void __fastcall TForm1::Button3Click(TObject *Sender)
{ float s, x=StrToFloat(Edit1->Text);
s=zikl_do(x);
Edit4->Text=FloatToStr(s); }

21

x 2 k 1
Задача 2.4 Вычислить сумму ряда S  , Вход в sum
k 1 (2k  1)!
суммируя члены ряда, значения которых по модулю больше s = 0, k=0
заданной точности   10 4 . Значение х (– 2 < x < 2) вводить с кла-
виатуры.
k=k+1, f=1
Такое задание уже было рассмотрено в лекции 1 (задача 1.6).
Преобразуем решение таким образом, чтобы в головной программе i  1,2k  1
остались операторы ввода/вывода данных, а Начало
все вычисления по выполнению задания f=f*i
организуем в функции. Блок-схема для Ввод х,
кнопки с вызовом функции представлена на eps
рис. 2.5, а на рис. 2.6 – схема алгоритма Вычисл. а
функции sum. Пример формы с резуль- Вызов
татами работы показан на рис. 2.7. s = sum(х, eps) s=s+а
Текст функции и ее вызова в основной
Вывод s
программе: Да
a  10 4
float sum( float x, float eps) Конец Нет
{ float a, s = 0;
Рисунок 2.5 – Блок- Выход из sum
int f, i, k = 0;
do схема для кнопки Рисунок 2.6 – Блок-
{ k++; “Решение” схема функции sum
for (i = 1, f = 1; i <= 2 * k - 1; i++) f *= i;
a = pow (x, 2 * k + 1) / f;
s += a;
} while( fabs(a) >= 1e-4);
return s;
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{ float x, eps, s;
x = StrToFloat(Edit1->Text); // Ввод значения х
eps = StrToFloat(Edit2->Text); // Ввод значения точности
s = sum(x,eps); // Вызов функции
Рисунок 2.7 – Форма
Edit3->Text = FloatToStr(s); // Вывод полученной суммы
} с результатами работы

2.4 Рекурсивные функции


Любая функция в программе на языке С++ может быть вызвана рекурсивно, т. е. она
может вызывать саму себя. Классический пример рекурсии – это математическое
определение факториала n! Так как n!=1 при n=0 и n*(n–1)! при n>1, функция,
вычисляющая факториал, будет иметь следующий вид:
long fact(int n)
{ if( n == 1) return 1 ;
else return n*fact(n-1); }
или, более кратко:
long faсt(int n)
{ return ( (n == 1) ? 1 : n*faсt(n-1) ); }

22
Примечание В вышеприведенном примере использована условная операция, которая
имеет следующий формат:
операнд1 ? операнд2 : операнд3
Операнд1 должен быть целого или плавающего типа или быть указателем. Он
оценивается с точки зрения его эквивалентности 0. Если операнд1 не равен 0, то
вычисляется операнд2 и его значение является результатом операции. Если операнд1 равен
0, то вычисляется операнд3 и его значение является результатом операции. Следует
отметить, что вычисляется либо операнд2, либо операнд3, но не оба вместе.
Тип результата зависит от типов операнда2 и операнда3 следующим образом:
1 Если операнд2 или операнд3 имеет целый или плавающий тип (отметим, что их типы
могут отличаться), то выполняются обычные арифметические преобразования.
Типом результата является тип операнда после преобразования.
2 Если операнд2 и операнд3 имеют один и тот же тип структуры, объединения или
указателя, то тип результата будет тем же самым типом структуры, объединения или
указателя.
3 Если оба операнда имеют тип void, то результат имеет тип void.
4 Если один операнд является указателем на объект любого типа, а другой операнд
является указателем на void, то указатель на объект преобразуется к указателю на
void, который и будет типом результата.
5 Если один из операндов является указателем, а другой – константным выражением со
значением 0, то типом результата будет тип указателя.
Пример: max = (d<=b) ? b : d;
Переменной max присваивается максимальное значение переменных d и b.
Хотя компилятор языка С++ не ограничивает количества рекурсивных вызовов
функций, это количество ограничивается ресурсом памяти компьютера и при слишком
большом количестве рекурсивных вызовов может произойти переполнение стека.
Представленный ниже пример программы демонстрирует рекурсивное вычисление
наибольшего общего делителя двух целых чисел (алгоритм Евклида):
int nod( int a, int b)
{ int c;
if(b > a) c = nod(b, a); // если b>a, выбирается путь 1 – параметры меняются местами
else
if(b <= 0) c = a; // если b<=0, выбирается путь 2
else
c = nod(b, a%b); // путь 3, здесь a%b – остаток от деления a на b
return c;
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{ int a, b;
a = StrToInt(Edit1->Text);
b = StrToInt(Edit2->Text);
Edit3->Text = IntToStr(nod(a, b));
}

23
Лекция 3
Обработка массивов в С++. Одномерные массивы
3.1 Понятие массива
М а с с и в (array) в программировании – это упорядоченная совокупность однотипных
элементов. Массивы широко используют для хранения и обработки однородной
информации, например таблиц, векторов, матриц и др.
Каждый элемент массива однозначно определяется именем и индексами. Имя массива
(идентификатор) подбирают по тем же правилам, что и для переменных. Индексы
определяют местоположение элемента в массиве. Например, элементы вектора имеют один
индекс – номер по порядку; элементы матриц и таблиц имеют по два индекса: первый
определяет номер строки, второй  номер столбца. Количество индексов определяет
размерность массива. Например, векторы в программах – это одномерные массивы, матрицы
– двумерные. В этой лекции рассмотрим только одномерные массивы.
3.2 Объявление одномерных массивов
Одномерный массив объявляется в программе следующим образом:
тип_данных имя_массива [размер_массива];
Имя_массива – это идентификатор массива. Тип_данных задает тип элементов
объявляемого массива. Элементами массива не могут быть функции и элементы типа void.
Размер_массива в квадратных скобках задает количество элементов массива. В отличие от
языка Pascal, в С не проверяется выход за пределы массива, поэтому, чтобы избежать
ошибок в программе, нужно следить за размерами описанных массивов. Значение
размер_массива при объявлении массива может быть опущено в следующих случаях:
 массив инициализируется при объявлении;
 массив объявлен как формальный параметр функции;
 массив объявлен как ссылка на массив, явно определенный в другом файле.
Используя имя массива и индекс, можно адресоваться к элементам массива:
имя_массива [ значение_индекса ]
Значения индексов должны лежать в диапазоне от нуля до величины, на единицу
меньшей чем размер массива, указанный при его описании, поскольку в языке С++
нумерация индексов всегда начинается с нуля.
Например:
int A[10];
объявляет массив с именем А, содержащий 10 целых чисел. А[0] – значение первого
элемента, А[1] – второго, А[9] – последнего.
Типу массива соответствует память, которая требуется для размещения всех его
элементов. Элементы массива с первого до последнего запоминаются в последовательных
возрастающих адресах памяти. Между элементами массива в памяти разрывы отсутствуют.
Элементы массива запоминаются друг за другом поэлементно. Так, под размещение
элементов одномерного массива int В[5] выделяется по 4 байта под каждый из 5-ти
элементов массива – всего 20 байтов.
Элементы В[0] В[1] В[2] В[3] В[4]
Байты 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Таким образом, чтобы получить доступ к i-тому элементу массива В, можно написать
В[i]. При этом величина i умножается на размер типа int и представляет собой адрес i-го
элемента массива В от его начала, после чего осуществляется выборка элемента массива В по
сформированному адресу.

24
Идентификатор, объявляемый как массив, представляет указатель, значение которого
является адресом первого элемента массива. Отметим, что адрес массива не может быть
изменен во время выполнения программы, хотя значение отдельных элементов может
изменяться.
Вот несколько примеров описания массивов:
char N [ 20 ];
int grades [ 125 ];
float mass [ 30 ];
double vector[ 1500 ];
Первый из массивов N содержит 20 символов. Обращением к элементам массива может
быть N[0], N[1], ..., N[19].
Второй массив grades содержит 125 целых чисел. Обращением к элементам массива
может быть grades [0], grades [1], ..., grades[124].
Третий массив mass содержит 30 вещественных чисел. Обращением к элементам
массива может быть mass[0], mass[1], ..., mass[29].
Четвертый массив vector содержит 1500 вещественных чисел с двойной точностью.
Обращением к элементам массива может быть vector[0], vector[1], ..., vector[1499].
При объявлении массивов можно элементам массива (необязательно всем) присваивать
первоначальные значения, которые в дальнейшем в программе могут быть изменены. Если
количество инициализируемых значений меньше, чем размерность массива, то всем
остальным элементам массива присваивается значение 0.
Например:
// а[0]=9, а[1]=33, а[2]=-23, а[3]=8, а[4]=1
int a[5] = {9, 33, -23, 8, 1};
// Q[0]=1.5, Q[1]= -3.8, Q[2]=10, Q[3]=Q[4]=…=Q[9]=0
float Q[10] = {1.5, -3.8, 10};
char S[3]="Фото"; // S[0]="Ф", S[1]="о", S[2]="т", S[3]="о"
char code[ ] = "abc"; // code[0]= "a", code[1]= "b", code[2]= "c", code[3]= "\0"
В последнем примере инициализируется code как массив символов из четырех элементов.
Четвертым элементом является символ "\0", который завершает все строковые литералы.
Если строка короче специфицированного размера массива, то оставшиеся элементы массива
инициализируются нулем (символом "\0").
Массивы в программах можно описывать также с созданием пользовательского типа:
typedef тип_данных имя_типа [размер_массива];
имя_типа имя_массива ;
Например, создадим тип с именем mass как массив из 10-ти положительных целых
чисел и объявим два массива – a и b – созданного типа:
typedef unsigned short mass[10];
mass a,b;

Для объявления массивов удобно использовать типизированные констант-массивы,


позволяющие одновременно объявить массив и задать его значения в разделе объявлений
констант:
const тип_данных имя_массива [размер_массива] = {значения_элементов_массива};
Но изменять значения элементов констант-массивов в программе недопустимо.
Примеры:
const int arr[5] = {9, 3, -7, 0, 123}; // массив из 5-ти целых чисел
const float f [5] = {1.5, 6, 8, 7.4, -1.125}; // массив из 5-ти вещественных чисел
const int С[5] = {15, -6, 546}; // массив из 5-ти целых чисел типа int, где
// С[0]=15, С[1]=-6, С[2]=546, С[3]=0 и С[4]=0

25
3.3 Вывод элементов одномерного массива
Выводить значения массивов можно в файл или на форму, используя разнообразные
компоненты С++ Builder. При этом выводить значения элементов массивов можно только
поэлементно, для чего следует организовывать циклы с изменением значений индексов.
Организация вывода массивов в файл будет рассмотрена в лекциях четвертого модуля. Здесь
рассмотрим организацию вывода одномерных массивов на форму с помощью компонентов
Edit, Label, Memo, ListBox и функции ShowMessage.
В последующих примерах будут использованы следующие переменные:
float A[10];
int i; AnsiString st;
3.3.1 Вывод в Edit
В компонент Edit можно выводить одномерные массивы, отделяя элементы пробелами
(" ") или другим символом. Количество элементов массива, которые можно увидеть,
ограничено длиной компонента Edit на форме.
Пример фрагмента программы вывода массива А:
st = "" ; // очистка строки st
for(i=0; i<=9; i++) // начало цикла по индексам массива
{ st += FormatFloat("0.00", A[i])+" "; } // накапливание в строке значений массива
Edit1->Text=st; // вывод в компонент Edit1 сформированной строки
3.3.2 Вывод в Label
В компонент Label можно выводить массивы, отделяя элементы пробелами (" ") или
символами перехода к новой строке ("\13"). Вывод одномерного массива в строку
организовывают по тем же правилам, что и в компонент Edit, только в программе вместо
Edit1.Text следует указать Label1->Caption (например Label1->Caption=st;). Для вывода
одномерного массива в столбец следует свойство WordWrap компонента Label установить
True вместо False.
3.3.3 Вывод в окно сообщений
Вывод в окно сообщений с помощью функции ShowMessage организовывают так же,
как и для Edit или Label, только вместо оператора присвоения следует записать оператор
вызова процедуры. Например, вместо оператора
Edit1->Text = st;
надо записать
ShowMessage(st);
3.3.4 Вывод в Memo
В многострочный компонент Memo можно выводить массивы с любым количеством
элементов, поскольку возможно использование полос прокрутки (указав свойству ScrollBars
значение ssBoth или ssVertical). Основное свойство Lines содержит все строки компонента.
Пример фрагмента программы вывода массива А (в столбец):
Memo1->Clear(); // очистка компонента
for(i = 0; i <= 9; i++) // начало цикла по индексам массива
Memo1->Lines->Add(FormatFloat("0.00", A[i])) ; // вывод одного элемента массива
3.3.5 Вывод в ListBox
Вывод массивов с помощью компонента ListBox организовывают так же, как и с
компонентом Memo, только вместо Memo следует указать компонент ListBox. Основное
свойство Items содержит все строки списка. Например, вместо оператора
Memo1->Lines->Add(FormatFloat("0.00",A[i])) ;
следует записать
ListBox1->Items->Add(FormatFloat("0.00",A[i]));

26
3.4 Ввод элементов одномерного массива
Вводить значения массивов можно используя такие компоненты С++ Builder, как
Memo, ListBox, Edit. Как и при выводе массивов, при вводе следует организовывать циклы
изменения значений индекса.
3.4.1 Ввод из окна Memo
С помощью компонента Memo можно вводить массивы, как в процессе выполнения
программы, так и при конструировании формы проекта через окно свойств Lines (для
перехода к новой строке при вводе значений следует нажать клавишу <Enter>).
Пример фрагмента программы ввода значений элементов одномерного массива А
(в каждой строке по одному числу):
for(i=0; i<=9; i++) A[i]=StrToFloat(Memo1->Lines->Strings[i]);
3.4.2 Ввод из ListBox
С помощью компонента ListBox можно вводить массивы так же, как и в компонент
Memo, только вместо свойства Lines надо использовать свойство Items.
3.4.3 Ввод из Edit
Ввод элементов одномерного массива с помощью компонента Edit (в одну строку через
пробел) требует создания специальной функции для ввода строки и преобразования ее в
массив величин другого типа. Такая функция будет рассмотрена при изучении работы со
строками.
3.5 Обработка массивов в функциях
Функции могут возвращать величины любого типа, за исключением массивов и
функций. Если массив используется в качестве параметра функции, следует указать адрес
начала массива. Сделать это можно тремя способами:
float r (int a[10])
float r (int a[])
float r (int *a)
При первом способе явно указывается количество элементов массива. Во второй
синтаксической форме опущено константное выражение в квадратных скобках. Эта форма
может быть использована только тогда, когда массив инициализируется или объявлен как
формальный параметр. При третьем способе передается указатель как ссылка на массив,
явно определенный в вызывающей программе. Более подробно работа с указателями будет
рассмотрена в лекции 5.
Если функция организована для обработки элементов массива, т. е. и на входе и на
выходе следует указывать одно и то же имя массива, в таком случае объявляется функция с
типом возврата void (нет возвращаемой величины). Поскольку массив передается по ссылке,
то любые изменения в функции сразу отражаются в головной вызывающей программе
(см. ниже задачи 3.3 и 3.4).
3.6 Примеры разработки программ с одномерными массивами
Задача 3.1 Разработать схему алгоритма и проект программы для ввода элементов
одномерного массива из 8-ми вещественных элементов и вычисления суммы всех элементов
массива.
Схема алгоритма программы и пример формы с результатами работы приведены на
рисунках 3.1 и 3.2. На форме расположены компоненты Memo1, Edit1, Label1, Button1,
Button2, Button3, а их новые свойства приведены в табл. 3.1.

27
Начало Т а б л и ц а 3.1 – Новые свойства компонентов

sum=0 Компо-
Свойства Новые значения
ненты
Label1 Caption Введите 8 вещественных чисел
i  0,7
Button1 Caption Сумма
Ввод Button2 Caption Очистка
A[i] Button3 Caption Выход
Form1 Caption Задача 3.1
sum=sum+A[i]

Вывод
sum
Рисунок 3.2 – Форма
с результатами работы
Конец
Рисунок 3.1 –
Блок-схема Текст программы:
к задаче 3.1 void __fastcall TForm1::Button1Click(TObject *Sender) // Сумма
{ float A[8], sum = 0;
for(int i = 0; i < 8; i++)
{ A[i] = StrToFloat(Memo1->Lines->Strings[i]);
sum += A[i]; }
Edit1->Text = FormatFloat("0.000", sum) ;}
void __fastcall TForm1::Button3Click(TObject *Sender) // Очистка
{ Memo1->Clear();
Edit1->Clear(); }
void __fastcall TForm1::Button2Click(TObject *Sender) // Выход
{ Close(); }

Задача 3.2 Составить схему алгоритма и проект программы для вычисления элементов
одномерного массива из 15 элементов по формуле Ai  lg( i)  tg( 2i ) , где і = 1, 2, …, 15 и их
вывода на форму, а также определения минимального элемента и его порядкового номера.
Схемы алгоритмов программы и пример вида формы с
Начало результатами работы приведены на рисунках 3.3…3.5. На форме
min=A[0], ind=0 расположены компоненты Memo1, Edit1, Button1, Button2, а их новые
свойства указаны в табл. 3.2.
i  1,14
Начало
Нет
min>A[i]
i  0,14
Да
min=A[i], ind=i
Ai  lg( i  1)  tg( 2i 1 )
Вывод
min, ind+1 A[i]

Конец
Рисунок 3.3 – Конец
Блок-схема
для кнопки Рисунок 3.4 – Блок-схема
«Минимальный для кнопки «Вычисление Рисунок 3.5 – Форма
элемент» вектора» с результатами работы

28
Текст программы: Т а б л и ц а 3.2 – Новые свойства
#include <math.h> компонентов формы
..........
Новые
float A[15] ; int i; Компоненты Свойства
значения
void__fastcall TForm1::Button1Click (TObject Memo1 ScrollBars ssVertical
*Sender)
Button1 Caption Вычисление
{ Memo1->Clear();
вектора
for(i = 0; i < 15; i++)
Button2 Caption Минимальный
{ A[i] = log10(i + 1) + tan(pow(2., i+1));
элемент
Memo1->Lines->Add(FormatFloat("0.000", A[i]));
} Form1 Caption Задача 3.2
}
void __fastcall TForm1::Button2Click(TObject *Sender)
{ float min = A[0];
int ind = 0;
for(i = 1; i < 15; i++)
if(min > A[i])
{ min=A[i]; ind=i; }
Edit1->Text = FormatFloat("0.000", min) + " индекс - " + IntToStr(ind + 1);
}
В этой программе переменные A[15] и i объявлены глобально перед процедурами-
обработчиками событий Click. Это сделано для того, чтобы значения элементов массива,
полученные в одной процедуре, были доступны для обработки и в другой процедуре.

Задача 3.3 Разработать схему алгоритма и проект программы для вычисления:


 N элементов вектора по формуле vi  3 cos(4i)  e2i и вывода их на форму проекта в
компонент Memo;
 произведения ненулевых элементов вектора;
 количества отрицательных элементов вектора;
 элементов вектора, являющихся результатом перестановки местами значений
первого и минимального элементов в векторе v, и вывода их в компонент ListBox.
Для решения поставленных задач разработать отдельные функции. Форма проекта с
результатами и схемы алгоритмов функций и основных процедур-обработчиков событий
Click показаны на рисунках 3.6 … 3.8.

Вход в mult Вход в neg Вход в fun


min=v[0], ind=0
Р=1 k=0
i  1, n  1
i  0, kol 1 i  0, kol  1
Нет
Нет Нет min>v[i]
v[i] ≠ 0 v[i] < 0
Да
Да Да
Р = P * v[i] k=k+1 min=v[i], ind=i

v[ind]=v[0]
v[0]=min
Выход из mult Выход из neg
а б Выход из fun
в
Рисунок 3.6 – Блок-схемы функций:
а – mult; б – neg; в – fun

29
Начало
Начало
Ввод N
fun(v, N)
i  0, N  1
Начало Начало
Вычисление v[i] i  0, N  1
M=mult(N,v) neg(N,v)
Вывод Вывод
v[i] Вывод
v[i] Вывод M
результата

Конец Конец Конец Конец


а б в г
Рисунок 3.7 – Блок-схемы для кнопок:
a – вектор; б – перестановка; в – произведение;
г – количество элементов меньше 0

Рисунок 3.8 – Форма проекта с результатами


Тексты функций и их вызова в основной программе:
float v[30]; int i, N; // глобальные переменные
float mult(int kol, float v[ ]) // Функция «Произведение ненулевых элементов»
{ float P = 1; // перед произведением переменная Р=1
for(i = 0; i < kol; i++)
if(v[i] != 0) P *= v[i]; // в Р накапливаем произведение ненулевых v[i]
return P;
}
void fun (float v[ ], int n) //Функция обмена местами значений первого и минимального элементов
// Функция не возвращает никакого значения, а в качестве результата получаем
// преобразованный массив, и, поскольку массив передаем по ссылке, то любые изменения
// в функции сразу отражаются в головной программе
{ float min= v[0]; int ind=0; // Вначале считаем, что первый элемент является минимальным.
for(i = 1; i < n; i++) // В цикле, начиная со второго элемента, проверяем,
if (v[i] < min) // если найден элемент меньше,
{ ind = i; min = v[i]; } // запоминаем индекс (расположение) элемента и его значение.
v[ind] = v[0]; // На место минимального элемента записать значение первого элемента,
v[0] = min; } // а на место первого – минимальный

30
int neg(int kol, float v[ ]) // Функция «Количество отрицательных элементов»
{ int k = 0; // вначале переменная k=0
for(i = 0; i < kol; i++)
if(v[i] < 0) k++; // если элемент отрицательный, k увеличивается на 1
return k;
}
void __fastcall TForm1::Button1Click(TObject *Sender) // Кнопка «Вектор»
{ Memo1->Clear();
N = StrToInt(Edit1->Text); // Ввод количества элементов массива
for(i = 0; i < N; i++)
{ v[i] = 3 * cos(4 * (i + 1)) + exp(-2 * (i + 1)); //вычисление элементов по формуле
Memo1->Lines->Add(FormatFloat("0.000", v[i])); //вывод на форму
}
}
// Кнопка ««Произведение ненулевых элементов»»
void __fastcall TForm1::Button2Click(TObject *Sender)
{ float M = mult(N, v); // Вызов функции
Edit2->Text = FloatToStr(M); // Вывод результата
}
void __fastcall TForm1::Button3Click(TObject *Sender) // Кнопка «Количество элементов < 0»
{ Edit3->Text=IntToStr(neg(N,v)); // Вызов функции и вывод результата
}
void __fastcall TForm1::Button4Click(TObject *Sender) // Кнопка «Перестановка»
{ ListBox1->Clear();
fun (v, N); // Вызов функции
for(i = 0; i < N; i++)
ListBox1->Items->Add(FormatFloat("0.000", v[i])); // Вывод массива
}

Задача 3.4 Разработать схему алгоритма и проект программы для ввода 15-ти (или
меньше) целых чисел из компонента Меmо и отсортировать их по возрастанию.
Сортировку элементов массива организуем в отдельной функции. Схемы алгоритмов
функций и форма проекта с результатами работы приведены на рис. 3.9…3.11.

Начало Начало

i=0,n-1 k=количество
j=i+1,n i=0,k-1

Нет аi>аj Ввод ai

Да
tmp = аi sort(a,n)
аi=аj i=0,k-1

аj= tmp
Вывод ai

Выход Конец
Рисунок 3.9 – Схема Рисунок 3.10 – Схема Рисунок 3.11 – Форма проекта
функции сортировки вектора для основной с результатами работы
по возрастанию программы
31
Методов сортировки существует много. В приведенной ниже функции sort предложен
наиболее простой и распространенный метод пузырьковой сортировки. Алгоритм
сортировки состоит в перестановке местами попарно сравниваемых элементов массива.
Переменная tmp нужна для временного хранения одного из переставляемых элементов. Если
в условии при сравнении элементов записать знак «>», то элементы будут сортироваться по
возрастанию, а при знаке «<» – по убыванию. Необходимость в организации двух циклов
обусловлена тем, что внешний цикл позволяет перебрать все элементы массива для
сравнения с другими элементами, доступ к которым организован во внутреннем цикле.
Текст функции и ее вызова в основной программе:
void sort(int a[ ], int n)
{ int i, j, tmp;
for (i = 0; i < n - 1; i++)
for (j = i + 1; j < n; j++)
if (a[i] > a[j]) // сравнение элементов
{ tmp = a[i]; // перестановка элементов a[i] и a[j]
a[i] = a[j]; // переменная tmp необходима для временного хранения одного из значений
a[j] = tmp;
}
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{ int n, i, a[15];
n = Memo1->Lines->Count; // Определение количества реально заполненных строк Memo1
for (i = 0; i < n; i++)
a[i] = StrToInt(Memo1->Lines->Strings[i]);
ShowMessage("Количество элементов массива " + IntToStr(n)) ;
sort(a, n); // вызов функции
Memo2->Clear();
for (i = 0; i < n; i++)
Memo2->Lines->Add(IntToStr(a[i]));
}

Вышеуказанный алгоритм сортировки можно оптимизировать, уменьшив количество


выполняемых действий. В приведенной ниже функции sort2 переменная ok играет роль
своеобразной лампочки, “включающейся” (ok=1) только в случае перестановки элементов, т.
е. является сигналом, что еще не все элементы упорядочены и надо повторять проверки.
void sort2(int a[], int n)
{ int i, tmp, ok;
M1: ok = 0;
for (i = 0; i < n - 1; i++)
if (a[i] > a[i + 1])
{ tmp = a[i];
a[i] = a[i + 1];
a[i + 1] = tmp;
ok = 1;
}
if(ok) goto M1; // если ok ≠ 0, перейти на оператор с меткой М1
}

32
Задача 3.5 В последовательности целых чисел нечетные Вход
элементы (по значению, а не по индексу) заменить единицами, а
четные элементы – нулями. i=0,n-1
Схема алгоритма функции представлена на рис. 3.12, а
блок-схема и текст программы с вызовом функции для кнопки Vi = 0 V[i] неч?
формы подобны приведенным в предыдущей задаче 3.4. Нет Да
Текст функции для реализации задачи: Vi = 1
void Zamena (int V[ ], int n)
{ int i;
for (i = 0; i < n; i++) // n – количество элементов массива Выход
if(V[i] % 2) V[i] = 1; // (остаток от целочисленного деления на 2) ≠0
else V[i] = 0; Рисунок 3.12 – Блок-
} схема функции Zamena

Задача 3.6 В одномерном массиве действительных чисел, Вход


состоящем из четного количества элементов, поменять местами элементы,
стоящие рядом (1 и 2, 3 и 4 и т. д.). k=0
Схема алгоритма функции представлена на рис. 3.13, а блок-схема и
текст программы с вызовом функции для кнопки формы также подобны i = 1, n

приведенным в предыдущей задаче 3.4. Отличием является только тип


Zi = ak
элементов массива.
Текст функции для реализации задачи: ak = ak+1
void Perehod (float A[ ], int n)
{ int i, k = 0; float z; ak+1 = Z
for (i = 0; i < n / 2; i++)
{ z = A[k]; k= k + 2
A[k] = A[k+1];
A[k+1] = z;
k += 2;
} Выход
}
Рисунок 3.13 – Блок-
схема функции
Perehod
Задача 3.7 Вычислить среднее арифметическое всех элементов одномерного массива
из 10-ти действительных чисел.
Схема алгоритма функции представлена на рис. 3.14. Вход
Текст функции и ее вызова в основной программе:
s=0
float middle (float A[ ], int n)
{ float s = 0; i = 1, n
for (int i = 0; i < n; i++) s += A[i];
s = s / n; s = s + A[i]
return s;
}
void __fastcall TForm1::Button1Click(TObject *Sender) s=s/n
{ float A[10], sr;
for(int i = 0; i < 10; i++) Выход
A[i] = StrToFloat(Memo1->Lines->Strings[i]);
sr = middle(A, 10); Рисунок 3.14 –
Edit1->Text = FormatFloat("0.000", sr); Блок-схема
} функции middle

33
Лекция 4
Двумерные массивы
4.1 Объявление двумерных массивов
Как было отмечено в лекции 3, размерность массива определяется количеством
индексов у его элементов. Элементы одномерного массива (вектора) имеют один индекс,
двумерного массива (таблицы и матрицы) – два индекса: первый из них – номер строки,
второй – номер столбца. Количество индексов у элементов массива не ограничено. При
размещении элементов массива в памяти компьютера в первую очередь изменяется крайний
правый индекс, затем остальные – справа налево.
Многомерный массив объявляется в программе следующим образом:
тип имя [ размер 1] [ размер 2] … [ размер n];
Количество элементов массива равно произведению количества элементов по каждому
индексу. Например, int B[3][4];
Объявлен двумерный массив из 3-х строк и 4-х столбцов (12-ти элементов) массива целого
типа:
В[0,0], В[0,1], В[0,2], В[0,3],
В[1,0], В[1,1], В[1,2], В[1,3],
В[2,0], В[2,1], В[2,2], В[2,3];
Типу массива соответствует память, которая требуется для размещения всех его
элементов. Элементы массива с первого до последнего запоминаются в последовательных
возрастающих адресах памяти. Между элементами массива в памяти разрывы отсутствуют.
Элементы массива запоминаются друг за другом.
Примеры объявления массивов:
float Mas1 [5][5]; // квадратная матрица из 5х5=25 элементов вещественного типа
char Mas2 [10][3]; // двумерный массив из 10х3=30 элементов символьного типа
double Mas3 [4][5][4]; // трехмерный массив из 4х5х4=100 вещественных элементов
При объявлении массива удобно инициализировать начальные значения его элементов,
причем необязательно всех. Примеры инициализации двумерных массивов:
1) int w[3][3] = { { 2, 3, 4 }, { 3, 4, 8 }, { 1, 0, 9 } };
2) float C[4][3]={1.1, 2, 3, 3.4, 0.5, 6.8, 9.7, 0.9};
3) float C[4][3]={{1.1, 2, 3}, {3.4, 0.5, 6.8},{ 9.7, 0.9}};
4) float C[4][3]={{ 1.1, 2}, {3, 3.4, 0.5},{ 6.8},{ 9.7, 0.9}};
В первом примере объявлен и инициализируется массив целых чисел w[3][3]. Элементам
массива присваиваются значения из списка: w[0][0]=2, w[0][1]=3, w[0][2]=4, w[1][0]=3 и т. д.
Списки, выделенные в фигурные скобки, соответствуют строкам массива.
Записи второго и третьего примера эквивалентны, и в них элементы последней строки
не инициализированы, т. е. не определены. В таких случаях в числовых массивах оставшиеся
элементы инициализируются 0.
Размещение элементов Размещение элементов
в примерах 2 и 3 в примере 4
1.1 2 3 1.1 2 0
3.4 0.5 6.8 3 3.4 0.5
9.7 0.9 0 6.8 0 0
0 0 0 9.7 0.9 0

Массивы в программах можно описывать также с созданием пользовательского типа:

typedef тип_данных имя_типа [ размер 1] [ размер 2] … [ размер n];


имя_типа имя_массива ;

34
Например, создадим тип с именем matr как массив положительных целых чисел из
10-ти строк и 7-ми столбцов и объявим два массива – M1 и M2 – созданного типа:
typedef unsigned short matr[10] [7];
mass M1,M2;
Для объявления массивов можно также использовать типизированные констант-
массивы, позволяющие одновременно объявить массив и задать его значения в разделе
объявлений констант, например:
const int arr[2][5] = {{9, 3},{ -7, 123};
но изменять значения элементов констант-массивов в программе недопустимо.
В языке С++ можно использовать сечения массива, как и в других языках высокого
уровня, однако на использование сечений накладывается ряд ограничений. Сечения
формируются вследствие опускания одной или нескольких пар квадратных скобок. Пары
квадратных скобок можно отбрасывать только справа налево и строго последовательно.
Сечения массивов используются при организации вычислительного процесса в функциях
языка С++, разрабатываемых пользователем.
Примеры:
1) int s[2][3];
Если при обращении к некоторой функции написать s[0], то будет передаваться нулевая
строка массива s.
2) int b[2][3][4];
При обращении к массиву b можно записать, например, b[1][2] – и будет передаваться
вектор из четырех элементов, а обращение b[1] даст двумерный массив размером 3х4. Нельзя
написать b[2][4], подразумевая, что передаваться будет вектор, потому что это не
соответствует ограничению, наложенному на использование сечений массива.

4.2 Ввод/вывод элементов двумерных массивов


Осуществлять вывод значений элементов массива можно только поэлементно, для чего
следует организовывать циклы, в которых будут последовательно изменяться значения
индексов элементов.
В последующих примерах вывода элементов массива будут использоваться
переменные, объявленные как:
int В [3][4]; int i, j; AnsiString st;

4.2.1 Вывод в Memo


В компонент Memo можно выводить массивы по строкам, разделяя пробелами
элементы в строке. В компоненте Memo можно использовать полосы прокрутки, для этого
свойству ScrollBar задается значение ssBoth или ssVertical.
Memo1->Clear();
for (i=0;i<3;i++)
{ st="" ;
for (j=0;j<4;j++) st+=FormatFloat("0.00",B[i][j])+" ";
Memo1->Lines->Add(st);
}

4.2.2 Вывод в ListBox


Вывод массива с помощью компонента ListBox организуют так же, как и с помощью
компонента Memo, только вместо свойства Lines надо использовать свойство Items.
Например, вместо
Memo1->Lines->Add(st);
необходимо записать
ListBox->Items->Add(st);

35
4.2.3 Вывод в StringGrid
Вывод массива с помощью компонента StringGrid (таблица строк). Этот компонент
расположен на закладке Additional палитры компонентов и имеет вид таблицы с ячейками. В
каждую ячейку можно записать строку. Таблица может иметь полосы прокрутки, причем
заданное число первых строк и столбцов может быть фиксированным и не прокручиваться.
Таким образом, можно задать заголовки столбцов и строк, постоянно присутствующие в
окне компонента. Во время создания формы нельзя устанавливать значения ячеек таблицы,
так как соответствующее свойство Cells доступно только программно.
Например, для вывода матрицы В размером 3х4 компоненту StringGrid1 следует задать
свойства, представленные в табл. 4.1.
Т а б л и ц а 4.1 – Основные свойства компонента StringGrid
Свойство Описание Значение
ColCount Количество столбцов 5
RowCount Количество строк 4
Cells Массив ячеек таблицы. Например, Cells[i,j] – это ячейка в j-той строке -
i-того столбца (нумерация начинается с нуля)
FixedCols Количество фиксированных столбцов (для заголовка) 1
FixedRows Количество фиксированных строк (для заголовка) 1
Options.goEditing Признак разрешения на редактирование содержимого ячеек True
Options.goTabs Признак разрешения на перемещение по таблице с помощью <Tab> True
Options.goColSizing Признак разрешения на изменение ширины столбцов True
Options.goRowSizing Признак разрешения на изменение высоты строк True
Для вывода матрицы в ячейки StringGrid необходимо организовывать циклы по
номерам строк и столбцов. Пример вывода матрицы int В [3][4] в компонент StringGrid1:
for (i = 0; i < 3; i++)
for (j = 0; j < 4; j++) StringGrid1->Cells[j + 1][i + 1] = FormatFloat("0.00", B[i][j]);
Пример создания заголовков таблицы в ячейках зафиксированных строки и столбца
(с индексом 0) во время активации формы.
void __fastcall TForm1::FormCreate(TObject *Sender)
{ int i, j;
for (i = 1; i <= 3; i++) StringGrid1->Cells[0][i] = IntToStr(i) + ”-я строка”;
for (j = 1; j <= 4; j++) StringGrid1->Cells[j][0] = IntToStr(j) + ”-й столбец”;
}
4.2.4 Ввод из StringGrid
В пример ввода матрицы int В [3][4] из ячеек StringGrid1
for (i = 0; i < 3; i++)
for (j = 0; j < 4; j++) StringGrid1->Cells[j + 1][i + 1] = FormatFloat("0.00", В[i][j]);
можно добавить проверку на незаполненные ячейки:
for (i = 0; i < 3; i++)
for (j = 0; j < 4; j++)
if(StringGrid1->Cells[j+1][i+1] != "") B[i][j] = StrToFloat(StringGrid1->Cells[j + 1][i + 1]);
else
{ ShowMessage("Заполните [" + IntToStr(i + 1) + "," + IntToStr(j + 1) + "] элемент"); break; }

4.3 Примеры разработки программ обработки двумерных массивов


Задача 4.1 Составить схему алгоритма и разработать проект для ввода матрицы
вещественных чисел размерностью 3х5 и нахождения максимального элемента матрицы и
его индексов.
Схема алгоритма программы и пример формы с результатами работы приведены на
рис. 4.1 и 4.2. На форме расположены компоненты StringGrid1, Edit1, Button1, Button2,
Button3, Label1 и Label2, а их новые свойства указаны в табл. 4.2.

36
Начало
Т а б л и ц а 4.2 – Новые свойства компонентов формы
i  0,2
Компоненты Свойства Новые значения
j  0,4 StringGrid1 ColCount 6
StringGrid1 RowCount 4
Ввод Ai,j StringGrid1 Options.goEditing True
StringGrid1 Options.goTabs True
Button1 Caption Решение
max=A00, Button2 Caption Очистка
ind_i=i, ind_j=j Button3 Caption Выход
Form1 Caption Задача 4.1
i  0,2

j  0,4
Нет
max<Aij
Да
max=Aij,
ind_i=i, ind_j=j

max,
ind_i+1,
ind_j+1

Конец
Рисунок 4.1 – Рисунок 4.2 – Форма с результатами работы
Блок-схема задачи 4.1
Тексты программ для всех командных кнопок:
void __fastcall TForm1::FormCreate(TObject *Sender) // Создание формы
{ for (int i = 1; i <= 3; i++) StringGrid1->Cells[0][i] = IntToStr(i) + "-я строка";
for (int j = 1; j <= 5; j++) StringGrid1->Cells[j][0] = IntToStr(j) + "-й столбец";
}
void __fastcall TForm1::Button1Click(TObject *Sender) // Решение
{ float A[3][5], max; int i,j,ind_i,ind_j;
for (i = 0; i < 3; i++)
for (j = 0; j < 5; j++)
if(StringGrid1->Cells[j + 1][i + 1] != "") A[i][j] = StrToFloat(StringGrid1->Cells[j + 1][i + 1]);
else { ShowMessage("Заполните [" + IntToStr(i + 1) + "," + IntToStr(j + 1) + "] элемент");
break; }
max = A[0][0]; ind_i = 0; ind_j = 0;
for (i = 0; i < 3; i++)
for (j = 0; j < 5; j++)
if(max < A[i][j]) { max = A[i][j]; ind_i = i; ind_j = j;}
Edit1->Text = FormatFloat("0.00",max) + " " + IntToStr(ind_i + 1) + "-я строка и " + IntToStr(ind_j + 1)
+ "-й столбец";
}
void __fastcall TForm1::Button2Click(TObject *Sender) // Очистка
{ for (int i = 0; i < 3; i++)
for (int j = 0; j < 5; j++) StringGrid1->Cells[j + 1][i + 1] = "";
Edit1->Clear(); }
void __fastcall TForm1::Button3Click(TObject *Sender) // Выход
{ Close(); }

37
Задача 4.2 Составить схему алгоритма и разработать проект для ввода матрицы целых
чисел размерностью 5х7 и получения вектора сумм отрицательных элементов столбцов
матрицы.
Для заполнения исходной матрицы предусмотрим возможность заполнения ячеек
случайными числами. Пользователь может воспользоваться этой возможностью или же
заполнять ячейки самостоятельно. Случайные числа формируем с помощью генератора
случайных чисел random(N); для его использования следует подключить библиотеку
<stdlib.h>. Функция random(N) генерирует случайное целое положительное число в диапазоне
от 0 до N-1. Чтобы формировались и отрицательные числа, используем конструкцию (50-
random(100)). Функция randomize() инициализирует генератор случайных чисел.
Схема алгоритма программы и пример формы с результатами работы приведены на
рис. 4.3 и 4.4. На форме расположены компоненты StringGrid1, Memo1, Button1, Button2,
Button3, Button4, Label1 и Label2, а их новые свойства указаны в табл. 4.3.

Т а б л и ц а 4.3 – Новые свойства компонентов формы


Начало Компо-
Свойства Новые значения
ненты
i  0,4
StringGrid1 ColCount 7
StringGrid1 RowCount 5
j  0,6
StringGrid1 Options.goEditing True
Ввод Ai,j
StringGrid1 Options.goTabs True
StringGrid1 FixedCols 0
StringGrid1 FixedRows 0
StringGrid1 Name SG1
j  0,6 Button1 Caption Заполнить случайными числами
Button2 Caption Решение
Хj = 0
Button3 Caption Очистка
Button4 Caption Выход
i  0,4
Form1 Caption Задача 4.2
Нет
Aij<0
Да
Xj= Xj +Aij

Вывод Xj

Конец
Рисунок 4.3 –
Блок-схема для кнопки
«Решение»

Рисунок 4.4 – Вид формы с результатами

Массив A[5][7] и переменные i, j объявлены перед всеми процедурами глобально,


поскольку обращение к этим объектам есть в разных процедурах.

38
Тексты программ для всех командных кнопок:
#include <stdlib.h>
...........
int A[5][7], i, j;
void __fastcall TForm1::Button1Click(TObject *Sender) // Заполнить случайными числами
{ randomize();
for(i = 0; i < 5; i++)
for(j = 0; j < 7; j++) SG1->Cells[j][i] = IntToStr(50 - random(100));
}
void __fastcall TForm1::Button2Click(TObject *Sender) // Решение
{ int X[7];
for(i = 0; i < 5; i++)
for(j = 0; j < 7; j++) A[i][j] = StrToInt(SG1->Cells[j][i]); // Ввод матрицы
Memo1->Clear();
for(j = 0; j < 7; j++) // Внешний цикл по столбцам
{ X[j] = 0; // Перед суммированием ячейку X[j] обнуляем.
for(i = 0; i < 5; i++) // Двигаясь по строкам,
if(A[i][j] < 0) X[j] += A[i][j]; // суммируем отрицательные элементы столбцов в X[j]
Memo1->Lines->Add(IntToStr(X[j])); // и сразу выводим найденную сумму X[j].
} }
void __fastcall TForm1::Button3Click(TObject *Sender) // Очистка
{ Memo1->Clear();
Начало for(i = 0; i < 5; i++)
for(j = 0; j < 7; j++) SG1->Cells[j][i] = "";
i  0,3 }

j  0,6
Задача 4.3 Из элементов матрицы размерностью 4х7
Ввод Сi,j
вещественных чисел вычислить вектор произведений ненулевых
элементов четных (2, 4 и 6) столбцов матрицы.
Поскольку при формировании вектора его индексы не
k= 0 совпадают с индексами четных столбцов матрицы, по которым
формируется вектор, то возникает необходимость в использовании
j  0,6 дополнительной переменной, например k для индексов вектора.
Нет четный
Схема алгоритма и пример формы с результатами работы
столбец? приведены на рис. 4.5 и 4.6. На форме расположены компоненты
Да StringGrid1, StringGrid2, BitBtn1, а их новые свойства указаны в
pk = 1 табл. 4.4. Компонент BitBtn представляет собой разновидность
i  0,3 стандартной кнопки Button, но с возможностью вывода на кнопку,
кроме надписи, пиктографических изображений с помощью свойств
Нет Kind или Glyph.
Сij ≠ 0
Да
pk= pk * Cij

Вывод pk

k=k+1

Конец
Рисунок 4.5 –
Блок-схема Рисунок 4.6 – Форма с результатами работы
к задаче 4.3

39
Текст программы: Т а б л и ц а 4.4 – Новые свойства компонентов

void __fastcall TForm1::BitBtn1Click(TObject * Компоненты Свойства Новые значения


Sender) StringGrid1 ColCount 7
{ int i, j; float C[4][7], p[3]; StringGrid1 RowCount 4
for (i = 0; i < 4; i++) StringGrid1 FixedCols 0
for (j = 0; j < 7; j++) StringGrid1 FixedRows 0
C[i][j] = StrToFloat(SG1->Cells[j][i]); StringGrid1 Options.goEditing True
int k = 0; StringGrid1 Options.goTabs True
for(j = 0; j < 7; j++)
StringGrid1 Name SG1
if((j + 1) % 2 == 0) // столбец четный?
StringGrid2 ColCount 3
{ p[k] = 1;
StringGrid2 RowCount 1
for (i = 0; i < 4; i++)
if(C[i][j] != 0) // если элемент ≠ 0 StringGrid2 FixedCols 0
p[k] *= C[i][j]; StringGrid2 FixedRows 0
SG2->Cells[k][0] = FormatFloat("0.00", p[k]); StringGrid2 Name SG2
k++; BitBtn1 Kind bkOK
} BitBtn1 Caption Вектор
} Form1 Caption Задача 4.3
Задача 4.4 Ввести матрицу размерностью 4х4 вещественных чисел и поменять местами
элементы главной диагонали и минимальные элементы соответствующих строк.
Схема алгоритма и пример формы с результатами работы приведены
Начало на рис. 4.7 и 4.8.
i  0,3

j  0,3

Ввод Вi,j

i  0,3

min= Вi,j, k=0


Рисунок 4.8 – Форма с результатами работы
i  0,3
Текст программы:
Нет
min>Сij void __fastcall TForm1::BitBtn1Click(TObject *Sender)
Да { int i, j; float B[4][4], X[3];
min= Вi,j, k=0 for (i = 0; i < 4; i++)
for (j = 0; j < 4; j++) B[i][j] = StrToFloat(SG1->Cells[j][i]);
int k; float min;
Bik= Вi,j, Вi,j=min for (i = 0; i < 4; i++)
{ min = B[i][0] ; // Запоминаем первый элемент строки и
k = 0; // его индекс как минимальный.
i  0,3 for(j = 0; j < 4; j++) // Перемещаясь по столбцам, проверяем,
if(B[i][j] < min) // если найден элемент меньше,
j  0,3 {min = B[i][j]; k = j;} // то запомнить его значение и индекс
// Заменить минимальный элемент строки на элемент диагонали,
Вывод Вi,j B[i][k] = B[i][i];
B[i][i] = min; // а элемент диагонали – на минимальный элемент.
}
for (i = 0; i < 4; i++)
Конец for (j = 0; j < 4; j++) SG2->Cells[j][i] = FormatFloat("0.00", B[i][j]);
Рисунок 4.7 – }
Блок-схема
к задаче 4.4
40
Задача 4.5 Из элементов матрицы размерностью 7х7 целых чисел вычислить вектор
скалярных произведений элементов первой строки на столбцы матрицы.
Напомним, что скалярное произведение – это сумма произведений элементов. В данном
случае
W[0] = p[0][0]*p[0][0] + p[0][1]*p[1][0] + p[0][2]*p[2][0] + p[0][3]*p[3][0] + . . .
W[1] = p[0][0]*p[0][1] + p[0][1]*p[1][1] + p[0][2]*p[2][1] + p[0][3]*p[3][1] + . . .
W[2] = p[0][0]*p[0][2] + p[0][1]*p[1][2] + p[0][2]*p[2][2] + p[0][3]*p[3][2] + . . .
....................................
Начало Схема алгоритма и пример формы с результатами работы
приведены на рис. 4.9 и 4.10.
i  0,6

j  0,6

Ввод p[i][j]

j  0,6

W[j]=0

i  0,6

W[j]+=p[0][i]* p[i][j]

Вывод
W[j]
Рисунок 4.10 – Форма с результатами работы
Текст программы:
Конец void __fastcall TForm1::BitBtn1Click(TObject *Sender)
Рисунок 4.9 – { int i,j; int p[7][7], W[7];
Блок-схема for (i=0; i<7; i++)
к задаче 4.5 for (j=0; j<7; j++)
p[i][j] = StrToInt(SG1->Cells[j][i]);
for (j=0; j<7; j++)
{ W[j]=0;
for (i=0; i<7; i++) W[j] += p[0][i]* p[i][j];
SG2->Cells[j][0]=IntToStr(W[j]);
}
}

4.4 Примеры разработки обработки двумерных массивов в функциях


Задача 4.6 Разработать программу для ввода матрицы размерностью NxN целых чисел
(значение N<=5 задает пользователь) для определения количества элементов со значением
больше 5 и суммы таких элементов, а также заменить в исходной матрице эти элементы
значением 10 и подсчитать новую суму элементов матрицы со значением больше 5.

Схемы алгоритмов функций и головной вызывающей программы, а также пример


формы с результатами работы приведены на рисунках 4.11…4.13. На форме расположены
компоненты StringGrid1, StringGrid2, Edit1, Edit2, Edit3, Edit4, Button1, Label1, Label2, Label3,
Label4, Label5, Label6, а их новые свойства указаны в табл. 4.5.

41
Вход в zamena Начало
Вход в kol Вход в sum
i  0, n  1 Ввод n
k=0 s=0
i  0, n  1
i  0, n  1 i  0, n  1 j  0, n  1
Нет j  0, n  1
j  0, n  1 j  0, n  1
matrij >5
Нет Нет Да Ввод pij
matrij >5 matrij >5
Да Да
matrij = 10
k=kol(p,n)
k=k+1 s = s + matrij
Вывод k
Выход
Выход из kol Выход из sum s=sum(p,n)
в
а Рисунок 4.11 –б Блок-схема функций: Вывод s
а – kol; б – sum; в – zamena
zamena(p,n)

i  0, n  1

j  0, n  1

Вывод pij

s=sum(p,n)
)
Вывод s

Конец
Рисунок 4.12 – Схема
головной программы
(кнопка “Пуск”)
Таблица 4.5 – Новые свойства компонентов
Компо- Новые
Свойства
ненты значения
StringGrid1 ColCount 5
StringGrid1 RowCount 5
StringGrid1 Options.goEditing True
StringGrid1 Options.goTabs True
Рисунок 4.13 – Форма с результатами работы StringGrid1 FixedCols 0
StringGrid1 FixedRows 0
Тексты функций и программ для всех StringGrid1 Name SG1
командных кнопок: StringGrid2 ColCount 5
int kol(int matr[5][5], int n) StringGrid2 RowCount 5
{ int i, j, k = 0; StringGrid2 FixedCols 0
for (i = 0; i < n; i++) StringGrid2 FixedRows 0
for (j = 0; j < n; j++) StringGrid2 Name SG2
if (matr[i][j] > 5) k++; Button1 Caption Решение
return k; } Form1 Caption Задача 4.6

42
int sum(int matr[5][5], int n)
{ int i, j, s=0;
for (i=0; i<n; i++)
for (j=0; j<n; j++)
if (matr[i][j] > 5) s += matr[i][j];
return s; }
void zamena(int matr[5][5], int n)
{ int i, j;
for (i=0; i<n; i++)
for (j=0; j<n; j++)
if (matr[i][j] > 5) matr[i][j] = 10; }
void __fastcall TForm1::Button1Click(TObject *Sender)
{ int i,j,n,k,s; int p[5][5];
n=StrToInt(Edit1->Text);
SG1->RowCount = n; SG1->ColCount = n;
SG2->RowCount = n; SG2->ColCount = n;
for (i=0; i<n; i++)
for (j=0; j<n; j++) p[i][j] = StrToInt(SG1->Cells[j][i]);
k=kol(p,n); Edit2->Text = IntToStr(k);
s=sum(p,n); Edit3->Text = IntToStr(s);
zamena(p,n);
for (i=0; i<n; i++)
for (j=0; j<n; j++) SG2->Cells[j][i] = IntToStr(p[i][j]);
s=sum(p,n); Edit4->Text = IntToStr(s); }
Задача 4.7 Составить схему алгоритма и разработать проект для:
 вычисления элементов матрицы размерностью 4х4 по формуле Аij = sin(i) + ln(j2),
где і = 1, 2, 3, 4 и j = 1, 2, 3, 4;
 получения вектора произведений элементов главной диагонали матрицы на элемен-
ты второй строки;
 замены элементов неглавной диагонали и последнего столбца матрицы.
Схемы алгоритмов функций и основной вызывающей программы, а также пример вида
формы с результатами работы приведены на рис. 4.14…4.16. На форме расположены
компоненты StringGrid1, StringGrid2, StringGrid3, Button1, Button2, Button3, а их новые
свойства указаны в табл. 4.6.
При решении данной задачи удобно будет переменные, ссылка на которые
осуществляется из разных функций, объявить глобально. В этом случае не надо будет
передавать их в качестве параметров функций, поскольку значения глобально объявленных
переменных “видны” во всех функциях программного проекта.
Вход в New_Matr
Вход в Matr i  0,3
i  0,3 Вход в Vector tmp=A[i][3-i];
j  0,3 i  0,3 A[i][3-i]=A[i][3];
Аij=sin(i+1)+ln(j+1) 2
V[i] = A[i][i] * A[1][i]
A[i][3]=tmp;

Выход из Matr Выход из Vector


Выход
а б в
Рисунок 4.14 – Блок-схема функций:
а – Matr; б – Vector; в – New_Matr

43
Начало Начало
Вызов Vector()
функции
i  0,3 i  0,3

i  0,3 Вывод
v[i]
Вывод
А[i][j]
Конец
б
Конец
а
Рисунок 4.15 –
Блок-схема для кнопок:
а – Button1 и Button3; б – Button2

Тексты функций и программ для всех Рисунок 4.16 – Форма с результатами работы
командных кнопок:
// глобально объявленные переменные Т а б л и ц а 4.6 – Новые свойства компонентов
int i, j; float A[4][4], V[4];
void Matr() Компоненты Свойства Новые значения
{ for (i=0; i<4; i++) StringGrid1 ColCount 4
for (j=0; j<4; j++) A[i][j] = sin(i+1)+log((j+1)*(j+1));
StringGrid1 RowCount 4
}
StringGrid1 FixedCols 0
void Vector()
StringGrid1 FixedRows 0
{ for (i=0; i<4; i++) V[i] = A[i][i] * A[1][i];
} StringGrid2 ColCount 4
StringGrid2 RowCount 1
void New_Matr()
StringGrid2 FixedCols 0
{ float tmp;
for (i=0; i<4; i++) StringGrid2 FixedRows 0
{ tmp = A[i][3-i]; StringGrid3 ColCount 4
A[i][3 - i] = A[i][3]; StringGrid3 RowCount 4
A[i][3] = tmp; StringGrid3 FixedCols 0
} } StringGrid3 FixedRows 0
void __fastcall TForm1::Button1Click(TObject Button1 Caption Вычислить матрицу
*Sender) Button2 Caption Вычислить вектор
// Вычислить матрицу Button3 Caption Поменять неглав-
{ Matr(); // Вызов функции ную диагональ и
последний столбец
for (i=0; i<4; i++)
Form1 Caption Задача 7
for (j=0; j<4; j++)
StringGrid1->Cells[j][i] = FormatFloat("0.000", A[i][j]); }
void __fastcall TForm1::Button2Click(TObject *Sender) // Вычислить вектор
{ Vector(); // Вызов функции
for (i=0; i<4; i++) StringGrid2->Cells[i][0] = FormatFloat("0.000", V[i]);
}
void __fastcall TForm1::Button3Click(TObject *Sender)
// Поменять неглавную диагональ и последний столбец
{ New_Matr(); // Вызов функции
for (i=0; i<4; i++)
for (j=0; j<4; j++) StringGrid3->Cells[j][i] = FormatFloat("0.000", A[i][j]); }

44
Задача 4.8 Разработать программу для умножение матриц целых чисел размер-
ностью 3х3.
Пример формы с результатами работы приведен на рис. 4.17.
Текст функции и программы для командной кнопки:
void multiply(int u[3][3], int v[3][3],int w[3][3])
// Подпрограмма, выполняющая умножение матриц
{ const int n = 3; int i, j, k;
for(i = 0; i < n; i++)
for(j = 0; j < n; j++)
{ w[i][j] = 0;
for(k = 0; k < n; k++) w[i][j] += u[i][k] * v[k][j];
} }
void __fastcall TForm1::Button1Click(TObject *Sender)
{ int i, j, a[3][3], b[3][3], c[3][3];
for(i = 0; i < 3; i++)
for(j = 0; j < 3; j++)
{ a[i][j] = StrToInt(StringGrid1->Cells[j][i]);
b[i][j] = StrToInt(StringGrid2->Cells[j][i]);
}
multiply(a, b, c); Рисунок 4.17 – Форма
for(i = 0; i < 3; i++) с результатами работы
for(j = 0; j < 3; j++)
StringGrid3->Cells[j][i] = IntToStr(c[i][j]);
}
Задача 4.9 Разработать программный проект для ввода матрицы целых чисел
размерностью 6х3 и вычисления элементов вектора как средних арифметических нечетных
элементов четных строк.
Решение этой задачи организуем в отдельной функции Vektor. Для поиска элементов
искомого вектора вначале открываем цикл для перемещения по четным строкам (2, 4, 6). В
каждой из четных строк, перемещаясь по столбцам, суммируем только нечетные элементы и
их количество. Перед расчетом среднего арифметического, т. е. перед делением, проверяем
возможность отсутствия в данной четной строке нечетных элементов, т. е. исключаем
деление на ноль. Переменная k необходима для последовательного формирования индексов
вектора, поскольку их нумерация не совпадает с нумерацией строк в матрице.
Пример формы с результатами работы приведен на рис. 4.18.
Текст функции и программы для командной кнопки:
void Vektor(int M[6][3], float X[3])
{ int i, j, kol, k = 0;
for (i = 1; i < 6; i += 2)
{ X[k] = kol = 0;
for (j = 0; j < 3; j++)
if(M[i][j] % 2 == 1) { X[k] += M[i][j]; kol++; }
if(kol) X[k] /= kol ;
else X[k] = 0;
k++; } }
void __fastcall TForm1::BitBtn2Click (TObject
*Sender)
{ int i, j, A[6][3]; float V[3];
for (i = 0; i < 6; i++)
for (j = 0; j < 3; j++) Рисунок 4.18 – Форма с результатами работы
A[i][j] = StrToInt(SG1->Cells[j + 1][i + 1]);
Vektor(A, V); // Вызов функции
for (i = 0; i < 3; i++) SG2->Cells[0][i + 1] = FloatToStr(V[i]); }

45
Лекция 5
Указатели. Динамическая память
5.1 Понятие статической и динамической памяти
Оперативная память ПК представляет собой совокупность ячеек для хранения
информации, каждая из которых имеет собственный номер. Эти номера называются
адресами, они позволяют обращаться к любому байту памяти.
Существует два способа закрепления оперативной памяти за величинами, которые
будут использоваться в программах. Обычно применяют так называемый статический
способ. При использовании этого способа память для каждой величины остается занятой ею
в течение всего времени работы программы. При таком способе иногда бывает неудобно,
когда в одной программе надо обрабатывать различное количество данных. Например, с
массивами разных размеров. В этом случае для универсальности программы надо при
объявлении массива учитывать максимально возможное количество его элементов. А это
приводит к неэффективному использованию памяти.
При динамическом способе закрепления оперативной памяти сама программа может
выделять и освобождать необходимую память для данных, т. е. управлять размещением
используемых в ней данных в оперативной памяти. Например, если вещественная
переменная Х используется только на одном из этапов работы программы, то можно с
началом этого этапа закрепить за ней память размером в 8 байт, а после завершения этапа –
освободить для использования другими данными. Таким образом, можно организовывать
динамические, т. е. изменяющие размеры, структуры данных. При этом оперативная память
используется более эффективно. Такая возможность связана с наличием в C++ особенного
типа данных – указателей.
Область оперативной памяти, в которой можно выделять отдельные участки для
размещения данных, называется динамической областью памяти, или динамической
памятью.
Такие действия предполагают возможность работы с адресами величин (переменных
массивов и других данных), размещаемых в оперативной памяти.
Адресом любой величины можно считать номер первого байта поля памяти,
занимаемого этой величиной. Однако следует помнить, что разные типы компьютеров могут
использовать разные формы записи таких адресов величин.
5.2 Указатели
Указатель – переменная, которая указывает на другую переменную (содержит
местоположение другой переменной в форме адреса). В этом смысле имя переменной
“отсылает” к ее значению непосредственно, а указатель – косвенно. Ссылка на значение
указателя называется косвенной адресацией.
Указатели, подобно любым другим переменным, перед своим использованием должны
быть объявлены. При объявлении переменной типа “указатель” необходимо определить тип
объекта данных, адрес которых будет содержать переменная, и имя указателя с
предшествующей звездочкой (или группой звездочек). Формат объявления указателя:
тип_данных *имя_указателя
Тип_данных может быть любого базового типа или структурой. Задавая вместо типа
ключевое слово void, можно таким образом отсрочить спецификацию типа, на который
ссылается указатель. Переменная, объявляемая как указатель на тип void, может быть
использована для ссылки на объект любого типа. Однако, для того чтобы можно было
выполнять арифметические и логические операции над указателями или над объектами, на
которые они указывают, необходимо при выполнении каждой операции явно определить тип
объектов. Такое определение типов может быть выполнено с помощью операции приведения
типов.

46
* – означает, что следующая за ней переменная является указателем. Переменная,
объявленная как указатель, хранит адрес памяти. Указатели на различные типы
необязательно имеют одну и ту же длину.
Примеры:
char *mes; // Пример 1
int *p1[10]; // Пример 2
int (*p2)[10]; // Пример 3
В примере 1 объявляется переменная-указатель с именем mes. Она указывает на
величину типа char.
В примере 2 объявлен массив указателей с именем p1. Массив состоит из 10 элементов.
Каждый элемент – это указатель на переменную типа int.
В примере 3 объявлена переменная-указатель с именем p2. Она указывает на массив из
10 элементов. Каждый элемент в этом массиве имеет тип int.
В С++ Builder указатели используются очень широко. В частности, все компоненты,
формы и т. п. объявляются именно как указатели на соответствующий объект.
Указатели должны инициироваться либо при своем объявлении, либо с помощью
оператора присваивания. Указатель может получить в качестве начального значения 0,
NULL или адрес. Указатель с начальным значением 0 или NULL ни на что не указывает.
5.3 Операция адресации и разадресации
Эти операции используются для работы с переменными типа указатель.
Адресация "&"
Операция адресации "&" (амперсанд) присваивает указателю адрес некоторой
переменной. Операцию "&" также часто используют для передачи в функции параметров по
ссылке (см. 5.7).
Разадресация "*"
Операция разадресации (или разыменования, или косвенной адресации) (*)
применяется для обращения к переменной по указателю, т. е. для того чтобы получить
значение, на которое указывает указатель, например:
double y, *py=&y; // объявленному указателю py присваивается адрес y
*py = 7.5; // эквивалентно y = 7.5
Если значение указателя содержит недопустимый адрес, то результат непредсказуем.
Рассмотрим типичные ситуации, когда указатель содержит недопустимый адрес:
 указатель является нулевым;
 указатель определяет адрес такого объекта, который не является активным в момент
ссылки;
 указатель определяет адрес, который не приведен к типу объекта, на который он
указывает;
 указатель определяет адрес, не используемый выполняющейся программой.
Примеры:
int *pa, x; int a[20]; double d;
pa = &a[5]; // Пример 1
x = *pa; // Пример 2
if (x == *&x) ShowMessage(“Условие всегда истинно”); // Пример 3
d = *(double*)(&x); // Пример 4
int t, f=0, *adress;
adress = &t; // Пример 5
*adress =f; // Пример 6
В примере 1 операция адресации вырабатывает адрес шестого элемента массива a.
Результат записывается в адресную переменную (указатель) pa.

47
В примере 2 используется операция разадресации для доступа к величине типа int, адрес
которой запомнен в pa. Значение результата присваивается целочисленной переменной x.
В примере 3 будет выводиться сообщение с текстом «Условие всегда истинно». Здесь
показано, что результат применения операции разадресации к адресу x – это то же самое,
что x, т. е. условие (x == x).
В примере 4 показано полезное приложение правил, рассмотренных в примере 3.
Адрес x преобразуется к указателю на double. Затем применяется операция разадресации и
результатом выражения является величина типа double.
В примере 5 переменной adress, объявляемой как указатель, присваивается адрес
переменной t.
В примере 6 переменной, находящейся по адресу, содержащемуся в переменной adress,
присваивается значение переменной f, т. е. 0, что эквивалентно t=f; т. е. t=0;
Операцию разадресации нельзя применять к указателю на void, поскольку для него
неизвестно, какой размер памяти надо разыменовать.
5.4 Операции над указателями
Арифметические операции, применяемые к указателю, имеют осмысленный результат
только когда указатель адресует массив памяти, а целая величина представляет смещение
адреса в пределах этого массива. Преобразование целой величины к адресному смещению
предполагает, что в пределах смещения плотно расположены элементы одинакового размера.
Это предположение справедливо для элементов массива. Массив определяется как набор
величин одного и того же типа; его элементы расположены в смежных ячейках памяти.
Сложение и вычитание адресов, ссылающихся на любые величины, кроме элементов
массива, дает непредсказуемый результат, поскольку в таком случае не гарантируется
плотное заполнение памяти.
С указателями может выполняться ограниченное количество арифметических
операций. Указатель можно увеличивать (++), уменьшать (--), складывать с указателем
целые числа (+ или +=), вычитать из него целые числа (- или -=) или вычитать один
указатель из другого.
Примеры: int i=4, j; float x[15]; float * px;
px = &x[4]+i; // Пример 1 – пропустить 4 элемента
j = &x[i] - &x[i-2]; // Пример 2
В примере 1 целочисленный операнд i складывается с адресом пятого элемента х.
Значения i умножаются на длину типа float и складываются с &x[4]. Значение
результирующего указателя представляет адрес x[8] элемента массива.
В примере 2 адрес третьего элемента x (задается как x[i-2]) вычитается из адреса пятого
элемента x (задается как x[i]). Полученная разность делится на длину типа float. В результате
получается целая величина 2.
При выполнении операций инкремент (++) и декремент (--) значение указателя
увеличивается или уменьшается на длину типа, на который ссылается используемый
указатель.
Пример: int *ptr, a[10];
ptr=&a[5];
ptr++; /* равно адресу элемента a[6] */
ptr--; /* равно адресу элемента a[5] */
В операциях сложения и вычитания могут участвовать указатель и величина типа int.
При этом результатом операции будет указатель на исходный тип, а его значение будет на
указанное число элементов больше или меньше исходного.
Пример:
int *ptr1, *ptr2, a[10]; int i=2;
ptr1=a+(i+4); /* равно адресу элемента a[6] */
ptr2=ptr1-i; /* равно адресу элемента a[4] */

48
В операции вычитания могут участвовать два указателя на один и тот же тип. Результат
такой операции имеет тип int и равен числу элементов исходного типа между уменьшаемым
и вычитаемым, причем, если первый адрес младше (меньше), то результат имеет
отрицательное значение.
Пример:
int *ptr1, *ptr2, a[10];
int i;
ptr1=a+4;
ptr2=a+9;
i=ptr1-ptr2; /* равно -5 */
i=ptr2-ptr1; /* равно 5 */
Значения двух указателей на одинаковые типы можно сравнивать в операциях ==, !=, <,
<=, >, >=. При этом значения указателей рассматриваются просто как целые числа, а
результат сравнения равен 0 (ложь) или 1 (истина). Сравнение указателей на равенство
истинно, если они ссылаются на один и тот же адрес в памяти.
Пример:
int *ptr1, *ptr2, a[10];
ptr1=a+5;
ptr2=a+7;
if (prt1>ptr2) a[3]=4;
В данном примере значение ptr1 меньше значения ptr2 и поэтому оператор a[3]=4 не
будет выполнен.

5.5 Передача параметров в функции при помощи указателей


В п. 5.3 говорилось о том, что передача параметров в функции может происходить при
помощи операции адресации (&). Альтернативной формой передачи параметров является
использование указателей. Тогда адрес переменной передается в функцию при помощи
операции косвенной адресации (*), т. е. передается не сама переменная, а указатель на нее.
В теле функции перед именем параметра тоже необходимо ставить символ *, чтобы
получить доступ к значению переменной. При вызове функции в нее в качестве аргумента
должна передаваться не сама переменная, а адрес, получаемый при помощи операции
адресации &. Например:
void sum(int *a, int *b); // Заголовок функции
{ *a +=*b; } // Изменение значения параметра
............
int x = 5, y = 10;
sum(&x, &y); // Вызов функции

5.6 Указатели на одномерные массивы


Массивы и указатели в С++ тесно связаны и могут использоваться почти эквивалентно.
Например, когда объявляется массив в виде int mass[25], то этим определяется не только
выделение памяти для двадцати пяти элементов массива, но и для указателя с именем mass,
значение которого равно адресу первого по счету (нулевого) элемента массива, т. е. сам
массив остается безымянным, а доступ к элементам массива осуществляется через указатель
с именем mass. С точки зрения синтаксиса языка, указатель mass является константой,
значение которой можно использовать в выражениях, но изменить это значение нельзя.
Поскольку имя массива является указателем, допустимо, например, такое
присваивание:
int mass[25], *ptr;
ptr=mass;
Здесь указатель ptr устанавливается на адрес первого элемента массива, причем
присваивание ptr=mass можно записать в эквивалентной форме ptr=&mass[0].

49
Для доступа к элементам массива существует два различных способа.
Первый способ связан с использованием обычных индексных выражений в квадратных
скобках, например mass[15]=3 или mass[i+2]=7. При таком способе доступа записываются
два выражения, причем второе выражение заключается в квадратные скобки. Одно из этих
выражений должно быть указателем, а второе – выражением целого типа. Указатель,
используемый в индексном выражении, необязательно должен быть константой,
указывающей на какой-либо массив, это может быть и переменная. В частности после
выполнения присваивания ptr=mass доступ к 16-му элементу массива можно получить с
помощью указателя ptr в форме ptr[15].
Второй способ доступа к элементам массива связан с использованием адресных
выражений и операции разадресации в форме *(mass+15)=3 или *(mass+i+2)=7. При таком
способе доступа адресное выражение, равное адресу 16-го элемента массива, записывается
как *(mass+15), т. е. для того, чтобы получить значение 16-го элемента массива mass,
можно записать mass[15] или *(mass+15). Результат один и тот же. При реализации на
компьютере первый способ приводится ко второму, т. е. индексное выражение преобразуется
в адресное. Операции над указателями обрабатываются быстрее, поэтому, если элементы
массива обрабатываются по порядку, то выгоднее использовать второй способ. Если же
выбор элементов случаен, то, во избежание ошибок, предпочтительнее первый способ.
Кроме того, первый способ более нагляден, что способствует лучшей читаемости программ.
Для доступа к начальному элементу массива (т. е. к элементу с нулевым индексом)
можно использовать просто значение указателя mass или ptr. Любое из шести присваиваний
* mass = 2; *ptr = 2;
mass [0] = 2; ptr[0] = 2;
*( mass +0) = 2; *(ptr+0) = 2;
присваивает начальному элементу массива значение 2, но быстрее всего выполняются
присваивания *mass=2 и *ptr=2, так как в них не требуется выполнять операции сложения.
Задача. В одномерном массиве из 8 целых чисел определить количество
положительных элементов.
Пример формы с результатами работы приведен на
рис. 5.1.
Текст функции и программы для командной кнопки:
int poz(int a[8])
{ int c = 0,*pa;
pa = a;
for(int i = 0; i < 8; i++)
if (*(pa + i) > 0) c++;
return c; }
void __fastcall TForm1::Button1Click(TObject *Sender) Рисунок 5.1 – Форма
{ int i, a[8]; с результатами работы
for(i = 0; i < 8; i++)
*(a + i) = StrToInt(Memo1->Lines->Strings[i]); // обращение *(a+i) аналогично a[i]
int k = poz(a);
Edit1->Text = IntToStr(k);
}

5.7 Указатели на многомерные массивы


Указатели на многомерные массивы в языке С++ – это массивы массивов, т. е. такие
массивы, элементами которых являются массивы. При объявлении таких массивов в памяти
компьютера создается несколько различных объектов. Например, при выполнении
объявления двумерного массива int arr[4][3] в памяти выделяется участок для хранения
значения переменной arr, которая является указателем на массив из четырех указателей.
Для этого массива из четырех указателей тоже выделяется память. Каждый из этих четырех

50
указателей содержит адрес массива из трех элементов типа int, и, следовательно, в памяти
компьютера выделяется четыре участка для хранения четырех массивов чисел типа int,
каждый из которых состоит из трех элементов.
arr

arr[0]  arr[0][0] arr[0][1] arr[0][2]
arr[1]  arr[1][0] arr[1][1] arr[1][2]
arr[2]  arr[2][0] arr[2][1] arr[2][2]
arr[3]  arr[3][0] arr[3][1] arr[3][2]
Таким образом, объявление arr[4][3] порождает в программе три разных объекта:
указатель с идентификатором arr, безымянный массив из четырех указателей и безымянный
массив из двенадцати чисел типа int. Для доступа к безымянным массивам используются
адресные выражения с указателем arr. Доступ к элементам массива указателей
осуществляется с указанием одного индексного выражения в форме arr[2] или *(arr+2). Для
доступа к элементам двумерного массива чисел типа int должны быть использованы два
индексных выражения в форме arr[1][2] или эквивалентных ей *(*(arr+1)+2) и (*(arr+1))[2].
Следует учитывать, что, с точки зрения синтаксиса языка С++, указатель arr и указатели
arr[0], arr[1], arr[2], arr[3] являются константами и их значения нельзя изменять во время
выполнения программы.
Размещение трехмерного массива происходит аналогично, и объявление float W[3][4][5]
порождает в программе, кроме самого трехмерного массива из 60-ти чисел типа float, массив
из четырех указателей на тип float, массив из трех указателей на массив указателей на float и
указатель на массив массивов указателей на float.
При размещении элементы многомерных массивов располагаются в памяти подряд по
строкам, т. е. быстрее всего изменяется последний индекс, а медленнее – первый. Такой
порядок дает возможность обращаться к любому элементу многомерного массива, используя
адрес его начального элемента и только одно индексное выражение.
Например, обращение к элементу arr[1][2] можно осуществить с помощью указателя ptr,
объявленного в форме int *ptr=arr2[0] как обращение ptr[1*4+2] (здесь 1 и 2 – индексы
используемого элемента, а 4 – количество элементов в строке) или как ptr[6]. Заметим, что
внешне похожее обращение arr[6] выполнить невозможно, так как указателя с индексом 6 не
существует.
Для обращения к элементу W[2][3][4] из трехмерного массива тоже можно использовать
указатель, описанный как float *p=W[0][0] с одним индексным выражением в форме
p[3*2+4*3+4] или p[22].
Задача. Ввести матрицу из 3-х строк и 3-х столбцов и
транспонировать.
Алгоритм транспонирования реализуем в виде
функции с использованием указателей. Пример формы с
результатами работы приведен на рис. 5.2.
Текст функции и программы для командной кнопки:
const int n=3;
void transp(int *u) // функция транспонирования матрицы
{ int i, j;
for(i = 0; i < n; i++)
for(j = i + 1; j < n; j++)
{ int p = *(u + n * i + j);
*(u + n * i + j) = *(u + n * j + i); Рисунок 5.2 – Форма
*(u + n * j + i) = p; с результатами работы
} }

51
void __fastcall TForm1::Button1Click(TObject *Sender)
{ int i, j, a[n][n];
for(i = 0; i < n; i++)
for(j = 0; j < n; j++) a[i][j] = StrToInt(StringGrid1->Cells[j][i]);
transp(&a[0][0]);
for(i = 0; i < n; i++)
for(j = 0; j < n; j++) StringGrid2->Cells[j][i] = IntToStr(a[i][j]);
}

5.8 Работа с динамической памятью. Динамическое размещение массивов


Некоторые функции для выделения и освобождения памяти:
malloc(s);
выделяет блок оперативной памяти длиной s байт и возвращает указатель на этот блок
памяти. При неудачном завершении возвращается значение NULL;
calloc(n, m);
выделяет блок оперативной памяти для размещения n элементов по m байт каждый и
возвращает указатель на начало этой области памяти. При неудачном завершении
возвращается значение NULL;
free(p);
освобождает ранее выделенный блок оперативной памяти, на который ссылается указатель p.
Эта область становится свободной для дальнейшего использования в других целях, а
указатель p – неопределенным (NULL), т. е. ни на что не ссылается;
realloc(bl, ns);
изменяет размер ранее выделенной памяти с адресом начала bl на ns байт.
При динамическом распределении памяти для массивов следует описать
соответствующий указатель и присваивать ему значение при помощи функций malloc или
calloc. Два аналогичных примера выделения памяти под одномерный массив a[10] из
элементов типа
float *a; float *a;
a=(float*) malloc(10); a=(float*)(calloc(10,sizeof(float));
Примеры создания двумерного массива a[n][m]:
int *p, n=3, m=5; int *p, n=3, m=5;
p=(int*)malloc(n*m*sizeof(int)); p=(int*)calloc(n*m, sizeof(int));
Аналогично можно распределить память и для трехмерных массивов.
Для иллюстрации работы функции realloc приведем пример изменения размера ранее
выделенной памяти под одномерный массив из 10 вещественных элементов до 20 элементов:
a = (float*) realloc(a, 20);
Следует только помнить, что ненужную для дальнейшего выполнения программы память
следует освобождать при помощи функции free, например:
free(а);
Использование указателей совместно с вышеназванными функциями дает возможность
управлять распределением динамической памяти.

5.9 Примеры разработки программ работы


с указателями и динамическим распределением памяти
Задача 5.1 Разработать программу для ввода элементов вектора (до 15 вещественных
чисел) и определения:
 произведения ненулевых элементов вектора;
 количества отрицательных элементов вектора;
 элементов вектора, являющихся результатом перестановки местами значений первого
и минимального элементов в исходном векторе.

52
Подобная задача была рассмотрена в лекции 3 (см. задача 3.3 на с. 29). Помимо
перестановки минимального и первого элементов массива, выведем значение минимального
элемента и его порядковый номер в исходном массиве. Пример формы с результатами
работы приведен на рис. 5.3.
Р е ш е н и е 1 Функция может явно возвращать только один результат, в данном
случае результат – количество отрицательных элементов. Остальные результаты (их 3)
передаются по ссылке. Значения элементов преобразованного массива в качестве
передаваемого результата указывать не нужно, поскольку массив передается по ссылке, то
любые изменения в функции сразу отражаются в основной вызывающей программе.
Текст функции и программы для командной кнопки:
int fun (double mass[ ], int n, double &p, double &min, int &ind)
{ int i, k = 0; ind = 0; p = 1; min = mass[0];
for (i = 0; i < n; i++)
{ if (mass[i] < 0) k++;
if (mass[i] != 0) p *= mass[i];
if (mass[i] < min) { min = mass[i]; ind = i; }
} mass[ind] = mass[0];
mass[0] = min;
return k; }
void __fastcall TForm1::Button1Click(TObject *Sender)
{ int i, n, otr, ind; double A[15], p, min;
n = StrToInt(Memo1->Lines->Count);
for(i = 0; i < n; i++) A[i] = StrToFloat(Memo1->Lines->Strings[i]);
otr = fun(A, n, p, min,I nd);
Edit1->Text = FloatToStr(p);
Edit2->Text = IntToStr(otr);
Edit3->Text = FormatFloat("0.000", min) + " индекс " + IntToStr(ind);
Memo2->Clear();
for(i = 0; i < n; i++) Memo2->Lines->Add(FloatToStr(A[i])); }
Р е ш е н и е 2 Функция не возвращает никаких значений – тип void опущен. Все
результаты (их 4) передаются по ссылке.
Текст функции и программы для командной кнопки:
fun (double mass[ ], int n, double &p, int &k, double &min, int &ind)
{ k = 0; ind = 0; p = 1; min = mass[0];
for (int i = 0; i < n; i++)
{ if (mass[i] < 0) k++;
if (mass[i] != 0) p *= mass[i];
if (mass[i] < min)
{ min = mass[i]; ind = i; }
} mass[ind] = mass[0];
mass[0] = min; }
void __fastcall TForm1::Button1Click(TObject
*Sender)
{ int i, n, otr, ind; double A[15], p, min;
n = StrToInt(Memo1->Lines->Count);
for(i = 0; i < n; i++)
A[i] =StrToFloat(Memo1->Lines->Strings[i]);
fun(A, n, p, otr, min, ind);
Edit1->Text = FloatToStr(p);
Edit2->Text = IntToStr(otr); Рисунок 5.3 – Форма с результатами работы
Edit3->Text = FormatFloat("0.000", min) + " индекс " + IntToStr(ind);
Memo2->Clear();
for(i = 0; i < n; i++) Memo2->Lines->Add(FloatToStr(A[i])); }

53
Р е ш е н и е 3 с применением указателей. В качестве массива – параметра функции
указывается адрес начального (нулевого) элемента массива. Доступ к i-му элементу массива
можно организовывать с помощью выражения mass[i] или как смещение от начала массива
на i элементов *(mass+i).
Текст функции и программы для командной кнопки:
fun (double *mass, int n, double &p, int &k, double &min, int &ind)
{ int i; k = 0; ind = 0; p = 1; min = *mass; // или min=mass[0]
for (i = 0; i < n; i++)
{ if (*(mass+i) < 0) k++; // *(mass+i) эквивалентно mass[i], но не
if (*(mass+i) != 0) p *= *(mass+i); // требует последующего преобразования
if (*(mass+i) < min) // компилятором, а значит, работает быстрее
{ min = *(mass+i); ind = i; }
} *(mass+ind) = *mass; // mass[0] эквивалентно *mass
*mass = min;
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{ int i, n, otr, ind;
double A[15], p, min;
n = StrToInt(Memo1->Lines->Count);
for(i = 0; i < n; i++) A[i] = StrToFloat(Memo1->Lines->Strings[i]);
fun(&A[0], n, p, otr, min, ind); // вызов функции
Edit1->Text = FloatToStr(p); Edit2->Text = IntToStr(otr);
Edit3->Text = FormatFloat("0.000", min) + " индекс " + IntToStr(ind);
Memo2->Clear();
for(i = 0; i < n; i++) Memo2->Lines->Add(FloatToStr(A[i]));
}

Р е ш е н и е 4 Динамическое распределение памяти.


Текст функции и программы для командной кнопки:
fun (double *mass, int n, double &p, int &k, double &min, int &ind)
{ k=0; ind=0; p=1; min = *mass;
for (int i = 0; i < n; i++)
{ if (*(mass + i) < 0) k++;
if (*(mass + i) != 0) p *= *(mass+i);
if (*(mass + i) < min)
{ min = *(mass+i); ind = i; }
} *(mass + ind) = *mass;
*mass = min; }
void __fastcall TForm1::Button1Click(TObject *Sender)
{ int i, n, otr, ind;
double *A, p, min;
n = StrToInt(Memo1->Lines->Count);
A = (double*)malloc(n*sizeof(double)); // выделить память для вектора
for(i = 0; i < n; i++) A[i] = StrToFloat(Memo1->Lines->Strings[i]);
fun(A, n, p, otr, min, ind);
Edit1->Text = FloatToStr(p);
Edit2->Text = IntToStr(otr);
Edit3->Text = FormatFloat("0.000", min) + " индекс " + IntToStr(ind);
Memo2->Clear();
for(i = 0; i < n; i++) Memo2->Lines->Add(FloatToStr (*(A + i)));
free(A); // освободить память
}

54
Задача 5.2 Разработать программу для ввода матрицы размерностью NxN целых чисел
(значение N<=5 задает пользователь), определения количества элементов со значением
больше 5 и суммы таких элементов, а также заменить в исходной матрице эти элементы
значением 10 и подсчитать новую сумму элементов матрицы со значением больше 5.
Пример формы с результатами работы приведен на рис. 5.4.
Р е ш е н и е 1 Функция может явно возвращать только один результат; в данном
случае результат – количество элементов больше 5. Остальные результаты (их 2) передаются
по ссылке. Значение элементов преобразованного массива в качестве передаваемого
результата указывать не нужно, поскольку массив передается по ссылке, любые изменения в
функции сразу отражаются и основной вызывающей программе.
Текст функции и программы для командной кнопки:
int kolm(int matr[5][5], int n, int &m, int &k)
{ int i, j;
m=0; int s=0; k=0;
for (i=0; i<n; i++)
for (j=0; j<n; j++)
if (matr[i][j] > 5)
{ m += 1;
k += matr[i][j];
matr[i][j] = 10;
s += matr[i][j]; }
return s;
}
void __fastcall TForm1:: Button1Click (TObject
*Sender)
{ int i, j, n, kol, ss, kr;
int p[5][5];
n=StrToInt(Edit1->Text);
StringGrid1->RowCount = n;
StringGrid1->ColCount = n;
StringGrid2->RowCount = n;
StringGrid2->ColCount = n;
for (i=0; i<n; i++)
for (j=0; j<n; j++)
p[i][j] = StrToInt(StringGrid1->Cells[j][i]);
ss = kolm(p, n, kol, kr); Рисунок 5.4 – Форма с результатами работы
for (i=0; i<n; i++)
for (j=0; j<n; j++) StringGrid2->Cells[j][i] = IntToStr(p[i][j]);
Edit2->Text = IntToStr(kol);
Edit3->Text = IntToStr(kr);
Edit4->Text = IntToStr(ss);
}
Р е ш е н и е 2 Функция не возвращает никаких значений – тип void опущен. Все
результаты (их 3) передаются по ссылке.
Текст функции и программы для командной кнопки:
kolm (int matr[ ][ ], int n, int &m, int &k, int &s)
{ int i, j; m=0; s=0; k=0;
for (i=0; i<n; i++)
for (j=0; j<n; j++)
if (matr[i][j] > 5)
{ m += 1; k += matr[i][j];
matr[i][j] = 10;
s += matr[i][j]; }
return s; }

55
void __fastcall TForm1::Button1Click(TObject *Sender)
{ int i, j, n, kol, ss, kr; int p[5][5];
n = StrToInt(Edit1->Text);
StringGrid1->RowCount = n; StringGrid1->ColCount = n;
StringGrid2->RowCount = n; StringGrid2->ColCount = n;
for (i=0; i<n; i++)
for (j=0; j<n; j++) p[i][j] = StrToInt(StringGrid1->Cells[j][i]);
kolm( p,n,kol,kr,ss);
for (i=0; i<n; i++)
for (j=0 ;j<n; j++) StringGrid2->Cells[j][i]=IntToStr(p[i][j]);
Edit2->Text = IntToStr(kol);
Edit3->Text = IntToStr(kr);
Edit4->Text = IntToStr(ss);
}
Р е ш е н и е 3 Указатели (без константы)
Текст функции и программы для командной кнопки:
kolm(int *matr,int n,int &m,int &s,int&k)
{ int i,j;
m=0; s=0; k=0;
for (i=0; i<n; i++)
for (j=0; j<n; j++)
if (*(matr+i*5+j) > 5)
{ m += 1; k += *(matr+i*5+j);
*(matr + i*5 + j) = 10;
s += *(matr + i*5 + j);
}
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{ int i,j,kol,n,ss,kr; int mt[5][5];
n = StrToInt(Edit1->Text);
StringGrid1->RowCount = n;StringGrid1->ColCount = n;
StringGrid2->RowCount = n;StringGrid2->ColCount = n;
for (i=0; i<n; i++)
for (j=0; j<n; j++) mt[i][j] = StrToInt(StringGrid1->Cells[j][i]);
kolm( &mt[0][0], n, kol, kr, ss);
for (i=0; i<n; i++)
for (j=0; j<n; j++) StringGrid2->Cells[j][i] = IntToStr(mt[i][j]);
Edit2->Text = IntToStr(kol);
Edit3->Text = IntToStr(ss);
Edit4->Text = IntToStr(kr);
}
Р е ш е н и е 4: Указатели (с константой)
Текст функции и программы для командной кнопки:
const int n=2;
kolm(int *matr, int &m,int &s,int&k)
{ int i, j;
m=0; s=0; k=0;
for (i=0; i<n; i++)
for (j=0; j<n; j++)
if (*(matr+i*n+j) > 5)
{ m += 1; k += *(matr+i*n+j);
*(matr + i*n + j) = 10;
s += *(matr + i*n + j);
} }

56
void __fastcall TForm1::Button1Click(TObject *Sender)
{ int i, j, kol, ss, kr;
int mt[n][n];
for (i=0; i<n; i++)
for (j=0; j<n; j++) mt[i][j] = StrToInt(StringGrid1->Cells[j][i]);
kolm(&mt[0][0], kol, kr, ss);
for (i=0; i<n; i++)
for (j=0; j<n; j++) StringGrid2->Cells[j][i] = IntToStr(mt[i][j]);
Edit2->Text = IntToStr(kol);
Edit3->Text = IntToStr(ss);
Edit4->Text = IntToStr(kr);
}
Р е ш е н и е 5 Динамическое распределение памяти
Текст функции и программ для командных кнопок:
kolm(int *matr,int n,int &m,int &s,int&k)
{ int i,j;
m=0; s=0; k=0;
for (i=0; i<n; i++)
for (j=0; j<n; j++)
if (*(matr+i*n+j) > 5)
{ m += 1; k += *(matr+i*n+j);
*(matr+i*n+j) = 10;
s += *(matr+i*n+j);
} }
void __fastcall TForm1::Button1Click(TObject *Sender)
{ int i, j, n, kol, ss, kr; int *p;
n = StrToInt(Edit1->Text);
p = (int*)malloc(n * n * sizeof(int)); // выделить память под матрицу
for (i=0; i<n; i++)
for (j=0; j<n; j++)
*(p+i*n+j) = StrToInt(StringGrid1->Cells[j][i]);
kolm(p, n, kol, kr, ss);
for (i=0; i<n; i++)
for (j=0; j<n; j++) StringGrid2->Cells[j][i] = IntToStr(*(p+i*n+j));
Edit2->Text = IntToStr(kol);
Edit3->Text = IntToStr(ss);
Edit4->Text = IntToStr(kr);
free(p); // освободить память
}
void __fastcall TForm1::Button2Click(TObject *Sender)
// Заполнить исходный массив порядковыми номерами расположения в памяти элементов
{ int i, j, n, kol, ss, kr; int *p;
n = StrToInt(Edit1->Text);
StringGrid1->RowCount = n;
StringGrid1->ColCount = n;
p = (int*)malloc(n * n * sizeof(int));
for (i=0; i<n; i++)
for (j=0; j<n; j++)
{ *(p+i*n+j) = (j+i*n+1);
StringGrid1->Cells[j][i] = IntToStr(*(p+i*n+j));
}
free(p); }

57
Задания-тесты для проверки знаний и умений
1 Запишите имя переменной, значением которой является сумма элементов в
приведенных трех фрагментах программ:
1) S1=0; 2) S2=1; 3) S3=0;
for (i=0; i<10; i++) for (i=0; i<10; i++) for (i=0; i<10; i++)
S1 += a[i]; S2 *= a[i]; if (a[i] > 0) S3++;

2 Запишите имя переменной, значением которой является количество


положительных элементов в приведенных трех фрагментах программ:
1) S1=0; 2) S2=1; 3) S3=0;
for (i=0; i<10; i++) for (i=0; i<10; i++) for (i=0; i<10; i++)
S1 += a[i]; S2 *= a[i]; if (a[i] > 0) S3++;

3 Запишите имя переменной, значением которой является максимальный из


элементов массива в приведенных трех фрагментах программ:
1) S1=0; 2) S2=a[1]; 3) S3=0;
for (i=0; i<10; i++) for (i=0; i<10; i++) for (i=0; i<10; i++)
S1 += a[i ]; if (a[i] > S2) S2=a[i]; if (a[i] > 0) S3=S3+1;

4 Наберите номер правильного, по Вашему мнению, ответа:


Фрагмент программы с вложенным циклом имеет номер
1) for (k=0; k<10; k++) 2) for (k=0; k<10; k++) 3) for (k=0; k<10; k++)
p=k; { p=k; { p=k; }
for (j=0; j<5; j++) for (j=0; j<5; j++) for (j=0; j<5; j++)
s = s + p*j; s = s + p * j; } { s = s + p*j; }

5 Дополните утверждение, вписав опущенное значение:


После выполнения операторов
{ f = 1; n = 3; i = 2;
1 : if (i > n) goto 9;
f = f * i; i = i + 1; goto 1;
9:}
переменная f будет иметь значение …

6 Наберите правильную, по Вашему мнению, последовательность номеров, для


записи элементов оператора цикла с предусловием:
1) логическое выражение (условие)
2) do
3) операторы тела цикла
4) while

7 Дополните утверждение, вписав опущенное значение:


После выполнения операторов
s = 0.5; i = 0;
while (i < 5) {i ++; s += 1./ i; }
переменная s будет иметь значение …

8 Дополните утверждение, вписав опущенное значение:


После выполнения операторов
s = 0.2; i = 0;
do { i++; s += 1./i } while (i <= 1);
переменная s будет иметь значение …

58
9 Дополните утверждение, вписав опущенное значение:
После выполнения операторов
s = 0.6;
for ( n=5; n <=7; n++) s++;
переменная s будет иметь значение …

10 Дополните утверждение, вписав опущенное значение:


После выполнения операторов
s = 0.5;
for (n=5; n>=2; n--) s = s + 1;
переменная s будет иметь значение …

11 Дополните утверждение, вписав опущенное значение:


В приведенном фрагменте программы
m = 1;
while (m< 5)
{ m += 2; n = 2 * m; }
шаг, с которым изменяется переменная m, имеет значение …

12 Дополните утверждение, вписав опущенное значение:


В приведенном фрагменте программы
m = 1;
while ( m< 5 )
{ m = m + 2; n = 2 * m; }
шаг, с которым изменяется переменная n, имеет значение …

13 Дополните утверждение, вписав опущенное значение:


После выполнения операторов
s = 0;
for ( k= 1; k<=3; k++)
{ for ( j = 1; j<=k; j++)
s= s + j; }
переменная s будет иметь значение …
14 Дополните утверждение вписав опущенное значение:
После выполнения операторов
s = 0;
for (k=1; k<=2; k++)
{ m = k;
do { m = m + 1; s = s + m;}
while ( m > 2); }
переменная s будет иметь значение …
15 Выберите номер правильного, по Вашему мнению, ответа:
После служебного слова while оператора цикла с постусловием следует записать
1) арифметическое выражение
2) оператор присвоения
3) логическое выражение (условие)
4) арифметическую константу

59
Содержание

Введение ....................................................................................................................................3

Структура модуля 2 ......................................................................................................................4

Конспект лекций...........................................................................................................................7

Лекция 1 Организация циклических вычислительных процессов .................................8


1.1 Оператор цикла с параметром for ............................................................................8
1.2 Вложенные циклы ...................................................................................................10
1.3 Табулирование функций. Построение графиков .................................................11
1.4 Операторы цикла c предусловием while и постусловием do-while ................12
1.5 Операторы прерывания выполнения циклов ........................................................17

Лекция 2 Функции в С++ ......................................................................................................18


2.1 Понятие функции в С++ ......................................................................................18
2.2 Правила организации функций ..............................................................................18
2.3 Примеры организации функций в С++ .................................................................20
2.4 Рекурсивные функции ............................................................................................22

Лекция 3 Обработка массивов в С++. Одномерные массивы .........................................24


3.1 Понятие массива ......................................................................................................24
3.2 Объявление одномерных массивов .......................................................................24
3.3 Вывод элементов одномерного массива ...............................................................26
3.4 Ввод элементов одномерного массива ..................................................................27
3.5 Обработка массивов в функциях ...........................................................................27
3.6 Примеры разработки программ с одномерными массивами ..............................27

Лекция 4 Двумерные массивы ..............................................................................................34


4.1 Объявление двумерных массивов..........................................................................34
4.2 Ввод/вывод элементов двумерных массивов .......................................................35
4.3 Примеры разработки программ обработки двумерных массивов ......................36
4.4 Примеры разработки обработки двумерных массивов в функциях ...................41

Лекция 5 Указатели. Динамическая память ......................................................................46


5.1 Понятие статической и динамической памяти .....................................................46
5.2 Указатели .................................................................................................................46
5.3 Операция адресации и разадресации.....................................................................47
5.4 Операции над указателями .....................................................................................48
5.5 Передача параметров в функции при помощи указателей ..................................49
5.6 Указатели на одномерные массивы .......................................................................49
5.7 Указатели на многомерные массивы .....................................................................50
5.8 Работа с динамической памятью. Динамическое размещение массивов ..........52
5.9 Примеры разработки программ работы с указателями и динамическим
распределением памяти .........................................................................................52

Задания-тесты для проверки знаний и умений........................................................................58

60

Вам также может понравиться