книги / Практикум по программированию на языке Си
..pdf1.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