Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Практикум по программированию на языке Си

..pdf
Скачиваний:
24
Добавлен:
12.11.2023
Размер:
3.53 Mб
Скачать

1.00

2.002.00

3.003.00 3.00

4.00

4.00

4.00

4.00

5.00

5.00

5.00

5.00

6.00

6.00

6.00

6.00

В программе вводятся значения целочисленных переменных nRow, nColumn, которые определяют, соответственно, число строк и число столбцов матрицы. Выделяется участок памяти для nRow элементов массива указателей типа double *, и его адрес присваивается указателю triMatr. (Обратите внимание на приведение типа, возвращаемого функцией calloc() значения, к типу указателя triMatr.) В цикле "по строкам" определяется длина каждой строки jRow, и соответствующему указателю triMatr[i] присваивается адрес участка памяти из jRow элементов, каждый из которых имеет тип double. Во вложенном цикле с параметром j всем элементам массива (элементам строки матрицы) присваивается номер строки формируемой треугольной матрицы (i+1).

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

ЗАДАНИЕ. Сформируйте и напечатайте верхнюю (наддиагональную) матрицу, вводя ее размеры, – число строк и число столбцов.

Коротко о важном

По материалам темы сформулированы следующие утверждения. В конце большинства из них приведены номера программ, подтверждающих справедливость утверждений.

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

(07_01.с).

!Нулевые начальные значения по умолчанию обязательно приписываются только указателям статической памяти (07_01.с).

261

!Значение указателя, адресующего объект, равно адресу объекта

(07_02.с, 07_04.с).

!Указателю типа void* можно присвоить адрес любого объекта

(07_03.с, 07_04.с).

!Чтобы с помощью разыменования получить доступ к объекту, адресованному указателем типа void*, необходимо приведение типов (07_02.с, 07_03.с, 07_04.с).

!Непосредственное разыменование указателя типа void* некор-

ректно (07_02_1.с).

!В конкретной реализации размеры участков памяти, занимаемых указателями разных типов, одинаковы (07_04.с).

!Размер участка памяти, доступного с помощью разыменования адреса (указателя), зависит от его типа (07_04.с).

!Адрес объекта можно присвоить указателям разных типов, но получаемое при разыменовании указателя значение зависит от типа указателя (04_04_1.с).

!Адрес объекта и указатель, адресующий этот объект, могут заменять друг друга в выражениях (07_05.с).

!Разыменование (адреса или указателя) эквивалентно индексации с нулевым значением индекса (07_05.с).

!Бинарная операция [ ] коммутативна – указатель (адрес) и индекс могут меняться местами без изменения значения выражения

(07_05.с, 07_05_1.с).

!При использовании операции [] с указателями типа void* и при их разыменовании используйте приведение типов и учитывайте приоритеты операций (07_05_1.с).

!Разыменование имени массива обеспечивает доступ к его первому элементу, т.е. к элементу с нулевым индексом (07_06.с).

!Результат применения операции sizeof к имени массива – длина участка памяти (в байтах), занимаемого массивом (07_06.с,

07_07.с).

!Результат применения операции sizeof к указателю, адресующему начала массива, – длина участка памяти в байтах, занимаемого указателем (07_06.с).

!Изменяя значение указателя, адресующего элементы массива, можно получить доступ к его элементам в желаемой последовательности (07_06.с).

262

!Адресовав с помощью указателя произвольный элемент массива, можно, применив к указателю индексирование, по любому закону изменять значение индекса и тем самым получать доступ к элементам массива в желаемом порядке (07_06.с).

!Для доступа к элементам массива можно обойтись без операции индексирования (без скобок [ ]), используя разыменование

(07_07.с).

!Разность указателей, адресующих элементы массива, равна разности индексов соответствующих элементов (07_07.с).

!Вычитая из адреса любого элемента массива имя массива, получаем индекс элемента (07_07.с).

!Невозможно обойтись без квадратных скобок при определении массива (07_07_1.с, 07_07_2.с, 07_07_3.с).

!При вводе значений элементов массива функцией scanf() удобно использовать указатели на элементы (в выражениях-аргументах не требуется операции получения адреса &) (07_08.c).

!Элементы глобального (статического массива) по умолчанию инициализируются нулевыми значениями (07_09.с).

!Выражение (имя_двумерного_массива +N) адресует первый эле-

мент N-й строки (07_09.с, 07_09_2.с).

!При замене двойной индексации разыменованиями array[i][j] эквивалентно *(*(array+i)+j), т.е. не обойтись без вложения круглых скобок (07_09.с, 07_09_1.с, 07_09_2.с).

!Адрес конкретного элемента матрицы (двумерного массива) определяет выражение (*(array+i)+j), которое можно использовать, например, в функции scanf() вместо &array[i][j]

(07_10B.c).

!Для регулярного доступа к разноименным объектам (переменным) применяйте массивы адресующих их указателей

(07_11.с).

!Один массив указателей с адресами объектов (переменных) позволяет сортировать объекты по разным законам, не меняя значе-

ний объектов (07_11.с, 07_13.с).

!Массив указателей, адресующих объекты, позволяет, изменяя объекты, упорядочивать их значения по выбранному признаку

(07_12.с).

263

!Массив указателей, адресующих элементы другого массива, позволяет сортировать элементы адресуемого массива по выбранному закону (07_13.с).

!Массив нетипизированных указателей (void *) позволяет сортировать элементы массивов разных типов (07_14.с).

!Указатель на указатель позволяет получить доступ к указателю и объекту, адресованному им (07_15.с).

!При выделении участка динамической памяти проверяйте успешность выполнения соответствующей функции (07_16.с).

!Недопустимо разыменовывать указатели, не адресующие конкретные участки памяти (07_16_1.с).

!В динамически выделенный участок памяти достаточно большого размера можно помещать значения разных типов, т.е. участок памяти не типизирован (07_17.с).

!Адрес динамически выделенного участка памяти не пригоден для оценки размера этого участка, так как адрес не типизирован

(07_18.с).

!Одномерные массивы с динамически изменяемыми размерами можно конструировать, используя средства динамического рас-

пределения памяти (07_18.с, 07_19.с, 07_19_1.с).

!Массивы указателей – основа для моделирования многомерных динамически изменяемых массивов (07_20.с, 07_21.с).

!Элементы массива указателей могут адресовать массивы с разным количеством элементов. Тем самым можно создавать непрямоугольные многомерные массивы, например, "матрицы" с разной длиной строк (07_21.с).

Тема 8

Функции, определяемые программистом

Важная операция над подпрограммами – их вызов, а именно требование осуществить их исполнение для получения вырабатываемого результата.

Ф.Л. Бауэр, Г. Гооз. Информатика. Вводный курс

Основные вопросы темы

!Роль прототипа функции и правила его размещения.

!"Цепочки" вложенных вызовов функций.

!Программная реализация параметрических функциональных зависимостей.

!Функции с возвращаемым значением типа void.

!Соотношение между участками памяти, выделяемыми для параметров и аргументов функции.

!Указатели в качестве параметров функций.

!Действие операторов тела функции на внешние объекты (моделирование процедур).

!Возможности нетипизированных параметров-указателей.

!Особенности спецификации параметров-массивов.

!Различия между массивом в месте его определения и массивомпараметром в теле функции.

!Действие операторов тела функции на элементы массивааргумента.

!Адрес элемента массива как результат выполнения функции с па- раметром-массивом.

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

!Преобразование рекурсивного алгоритма в итерационный и обратно.

!Сортировка массива делением пополам как рекурсивная процедура.

265

8.1. Определение, прототип и вызов функции

ЗАДАЧА 08-01. Определите функцию для вычисления по формуле Ньютона приближенного значения арифметического квадратного корня из значения аргумента. В основной программе введите значение подкоренного выражения и напечатайте значение корня.

Напомним, что для x>0 значение x вычисляется по формуле rn+ 1 = (rn + x / rn ) / 2, где в качестве r0 можно взять ненулевое значе-

ние x. Конечная точность представления вещественных чисел и равномерная сходимость итераций позволяют проводить вычисления до достижения равенства rn+1==rn , где n – номер итерации.

Решение задачи:

/* 08_01.c - Функция для вычисления квадратного корня */

#include <stdio.h> double root(double x)

{

double r1, r2=x;

if (x == 0.0) return 0.0; do {

r1=r2;

r2=(r1+x/r1)/2;

}

while (r1 != r2); return r2;

}

int main()

{

double x, result; printf("x="); scanf("%le",&x); result=root(x); printf("result=%e\n",result); return 0;

}

Результаты выполнения программы.

266

Первое исполнение:

x=49<ENTER>

result=7.000000e+00

Второе исполнение:

x=33<ENTER>

result=5.744563e+00

Обратите внимание на проверку нулевого значения параметра в функции. Если ее опустить, то при нулевом аргументе возникнет деление на нуль, т.е. ошибка во время исполнения программы.

ЗАДАНИЕ. Используйте обращение к функции root() в качестве аргумента функции printf(). (Станет не нужна переменная result.)

ЭКСПЕРИМЕНТ. Перенесите определение функции root() в предыдущей программе в конец текста. Cм. программу 08_01_1.с,

где строка 13: double root(double x); строка 8: result=root(x);

При компиляции будут получены следующие предупреждения:

08_01_1.c:13: warning: type mismatch with previous implicit declaration – несоответствие типов по сравнению с предшествующим неявным объявлением

08_01_1.c:8: warning: previous implicit declaration of `root' - предполагается неявное объявление функции `root'

08_01_1.c:13: warning: `root' was previously implicitly declared to return `int' –

функция `root' до этого объявлена как возвращающая значение типа `int'

Несмотря на предупреждения, компилятор создаст исполнимый модуль программы, но результаты исполнения программы совершенно неверные:

Первое исполнение:

x=49<ENTER>

result=1.075577e+09

267

Второе исполнение:

x=33<ENTER>

result=1.075184e+09

ЗАДАНИЕ. Исправьте программу 08_01_1.с, включив в тело функции main() или до нее прототип double root(double);

Обратите внимание на необязательность имен параметров в прототипах функций. Заданию соответствует правильно работающая программа 08_01_2.с.

ЭКСПЕРИМЕНТ. Разместите в тексте функции main() прототип функции root() непосредственно перед обращением к ней.

См. программу 08_01_3.с, где строка 8: double root(double);

Компиляция программы завершится аварийно, будут выданы следующие сообщения:

08_01_3.c: In function `main':

08_01_3.c:8: parse error before `double'

08_01_3.c: At top level:

08_01_3.c:14: warning: type mismatch with previous implicit declaration

08_01_3.c:9: warning: previous implicit declaration of `root'

08_01_3.c:14: warning: `root' was previously implicitly declared to return `int'

Компилятор обнаружил прототип в той части программы (в строке 8), где размещены исполняемые операторы, и воспринимает это как синтаксическую ошибку (parse error). Последующие сообщения совпадают с предупреждениями, выдаваемыми программой 08_01_1.с. Исполнимая программа не может быть создана.

ЭКСПЕРИМЕНТ. Разместите прототип функции root() в начале тела функции main(), например, перед определением double x, result;.

268

Компиляция пройдет успешно и будет создан правильный исполнимый модуль программы.

ЗАДАЧА 08-02. Напишите функцию, возвращающую минимальное из значений своих четырех вещественных параметров. В основной программе введите значения четырех переменных и, обратившись к функции, напечатайте минимальное из них.

Условию соответствует следующая программа:

/* 08_02.c - Функция для выбора значения минимального параметра */

#include <stdio.h>

#define PRINTF(EXPRESSION) \ printf(#EXPRESSION"=%f\n",EXPRESSION)

#define READD(VARIABLE) \

{ printf(#VARIABLE"="); scanf("%le",&VARIABLE);

}

double min4(double x1,double x2,double x3,double x4)

{

double min;

min = x1 < x2 ? x1 : x2; min = x3 < min ? x3 : min; return x4 < min ? x4 : min;

}

int main()

{

double w,x,y,z; READD(w); READD(x); READD(y); READD(z);

PRINTF(min4(w,x,y,z)); return 0;

}

Результаты выполнения программы:

w=95<ENTER>

x=43<ENTER> y=-7<ENTER> z=39<ENTER>

min4(w,x,y,z)=-7.000000

269

ЭКСПЕРИМЕНТ. Перенесите определение функции min4() в конец текста программы. (См. программу 08_02_1.с.)

При компиляции будут выданы такие же предупреждения, как и при трансляции программы 08_01_1.с. Их смысл в том, что по умолчанию компилятор считает, что функция min4() возвращает значение типа int. Результаты выполнения программы 08_02_1.с приводить не станем – они не верны.

ЗАДАНИЕ. Решите предыдущую задачу, введя дополнительную функцию для определения минимального из значений двух параметров.

/* 08_02_2.c - функция для выбора минимального параметра */

#include <stdio.h>

#define PRINTF(EXPRESSION) \ printf(#EXPRESSION"=%f\n",EXPRESSION)

#define READD(VARIABLE) \

{ printf(#VARIABLE"="); scanf("%le",&VARIABLE);

}

double min2(double x, double y)

{

return x > y ? y : x;

}

double min4(double x1,double x2,double x3,double x4)

{

return min2(min2(min2(x1,x2),x3),x4);

}

int main()

{

double w,x,y,z; READD(w); READD(x); READD(y); READD(z);

PRINTF(min4(w,x,y,z)); return 0;

}

270