- •Часть 2
- •Содержание
- •1.37.18.5. Несовместимость фактического и формального параметров указателей функции……………………………33
- •1. Основы программирования на языке Си
- •1.30. Цикл, управляемый инструкцией while
- •1.31. Цикл, управляемый инструкцией do while
- •1.32. Цикл с выходом
- •1.33. Вложенные циклы
- •1.34. Перечисление
- •1.35. Функция
- •1.35.1. Назначение функции
- •1.35.2. Определение функции
- •1.35.3. Вызов функции.
- •1.35.4. Прототип функции
- •1.35.5. Способы передача параметров
- •1.35.6. Понятие о встраиваемых (inline) функциях
- •1.36. Организация модулей в языке Си
- •1.36.1. Пример организации модуля
- •1.36.2. Защита от повторного включения заголовочного файла
- •1.37. Массивы и указатели
- •1.37.1. Массивы. Общие сведения
- •1.37.1.1. Классификация массивов
- •1.37.1.2. Определение и инициализация массива
- •1.37.1.3. Операции с массивами
- •1.37.2. Указатели. Общие сведения
- •1.37.2.1. Области применения указателей
- •1.37.2.2. Классификация указателей
- •1.37.2.3. Понятие о нулевом указателе
- •1.37.2.4. Понятие о недействительном указателе
- •1.37.2.5. Определение переменной – указателя
- •1.37.2.6. Операции с указателями
- •1.37.2.6.1. Разыменование указателя. Модель переменной-указателя
- •1.37.2.6.3. Оператор присваивания
- •1.37.2.6.4. Инкремент и декремент указателя
- •1.37.2.6.5. Сложение и вычитание целого числа
- •1.37.2.6.6. Вычитание указателей
- •1.37.2.6.7. Индексирование указателей
- •1.37.2.6.8. Сравнение указателей
- •1.37.2.7. Указатели и динамическая память
- •Функция malloc
- •Функция calloc
- •Функция realloc
- •Функция free
- •1.37.3. Указатели и квалификатор const
- •1.37.4. Указатели и квалификатор restrict
- •1.37.5. Связь между указателями и массивами
- •1.37.6. Указатели – параметры функций
- •1.37.7. Массивы – параметры функций
- •1.37.8. Ошибки при работе с указателями
- •1.37.8.1. Ошибки при объявлении указателей
- •1.37.8.2. Использование неинициализированного указателя
- •1.37.8.3. Присваивание несовместимых указателей
- •1.37.8.4. Разыменование нулевого указателя
- •1.37.18.5. Несовместимость фактического и формального параметров указателей функции
- •1.37.8.6. Возврат из функции указателя на локальную переменную
- •1.37.8.7. Висячий указатель
- •1.37.8.8. Утечка памяти
- •1.37.9. Указатели на функцию
- •1.37.9.1. Выражение указатель на функцию
- •1.37.9.2. Определение указателя переменной на функцию
- •1.37.9.3. Инициализация переменной указатель на функцию
- •1.37.9.4. Операции с указателями на функцию
- •1.38.5.Строковые функции стандарта с11
- •1.38.6. Ввод и вывод строк
- •1.39. Работа со структурами
- •1.39.1. Объявление структур
- •1.39.2. Определение структуры в многомодульной программе
- •1.39.3. Инициализация структур
- •1.39.4. Обращение к полям структуры
- •1.39.5. Операции над структурами
- •1.39.6. Массивы структур
- •1.39.6.1. Объявление массивов структур
- •1.39.6.2. Идентификация элементов массива структур
- •1.40. Работа с внешними устройствами
- •1.40.1. Понятие потока
- •1.40.2. Файлы
- •1.40.2.1. Указатель файла
- •1.40.2.2 Функция fopen
- •1.40.2.9. Построковый ввод-вывод
- •1.40.3.Блоковый ввод-вывод
- •Примеры решенных задач
- •2.4. Итерационные циклы
- •2.5. Вложенные циклы
- •2.6. Работа с функциями
- •2.6.1. Преобразование программы в функцию пользователя
- •2.6.2. Функция как обобщенное решение ряда частных задач
- •2.6.3. Параметры функции или глобальные переменные
- •2.6.4. Указатели в качестве параметров функций
- •2.7. Обработка одномерных массивов
- •2.7.1. Массивы ─ входные и выходные параметры функции
- •2.7.2. Ввод массивов
- •2.7.3. Вывод нескольких массивов в виде таблицы
- •2.7.4. Создание нового массива копированием положительных элементов из исходного массива
- •2.7.5. Значения наибольшего и наименьшего элементов массивов
- •2.8. Обработка двумерных массивов
- •2.8.1. Интерфейс функций, работающих с двумерными массивами.
- •2.8.2. Создание и уничтожение двумерного динамического массива
- •2.8.3. Примеры обработки двумерных массивов
- •2.9. Работа со строками
- •2.9.1. Пользовательские аналоги библиотечных функций
- •2.9.2. Копирование чисел, находящихся в строке, в числовой массив
- •2.9.3. Подсчет количества лексем в строке
- •2.9.4. Массивы строк
- •2.10. Указатели на функцию
- •2.10.1. Использование функции qsort
- •2.10.2. Табулирование произвольной функции одного переменного. Новое решение задачи табулирования
- •2.11. Работа со структурами
- •2.11.1. Структуры, поля которых содержат указатели
- •2.11.2. Работа с массивом структур.
- •2.12. Работа с файлами
- •2.12.1. Работа с текстовыми файлами
- •2.12.2. Работа с двоичными файлами
- •2.12.2.1. Вычисление суммы чисел, содержащихся в двоичном файле.
- •2.12.2.2. Чтение числа из заданной позиции двоичного файла
- •2.12.2.3.Замена всех отрицательных чисел двоичного файла нулями
- •Литература
2.6.2. Функция как обобщенное решение ряда частных задач
Анализ ряда похожих задач может привести к целесообразности разработки функции пользователя. Для этого следует разработать обобщенный алгоритм и систему параметров, позволяющих модифицировать этот обобщенный алгоритм. Такая система параметров должна позволять находить решение каждой из исходных задач. Рассмотрим пример.
Пример. Формульный счет.
Постановка задачи. Вычислить значение величины y:
![]()
Решение. В данную формулу входят три похожих подвыражения, в которых предусматривается вычисление корня квадратного из суммы квадратов. Этот факт позволяет написать обобщенную формулу, с помощью которой можно вычислить значение любого из корней квадратных, входящих в исходную формулу. Обобщенная расчетная формула имеет следующий вид:
![]()
В
обобщенной формуле имеются два параметра:
x1 и
x2. Подставим
в эту формулу значения параметров
x1
== a
– 1 и x2
== x.
Это позволит
вычислить значение первого из корней
.
Подстановка: x1
== x
и x2
== 1 в обобщенную формулу позволит
вычислить значение второго корня
.
Наконец, подставляя значения x1 == a и x2 == 2, получим последний из корней.
Программный код решения задачи.
#include<stdio.h> #include<math.h> double sqrt_sum_sqr(double x1, double x2); int main(void) { double a; printf(“%s”, “a=”); scanf(“%lf”, &a); double x; printf(“%s”, “x=”); scanf(“%lf”, &x); double y = (sqrt_sum_sqr(a – 1, x) + sqrt_sum_sqr(x, 1) + sqrt_sum_sqr(a, 2)) / 3; printf(“y = %0.4g\n”, y); getchar(); return 0; } /* Функция для вычисления корня из суммы квадратов*/ double sqrt_sum_sqr(double x1, double x2) { return sqrt(x1 * x1 + x2 * x2); }
2.6.3. Параметры функции или глобальные переменные
Параметры совместно с возвращаемым значением стандартно используются для организации интерфейса функции с клиентским кодом. Для этой же цели могут применяться внешние переменные. Однако это делать не рекомендуется. Дело в том, что при наличии внешних переменных утрачивается автономность функции. Применение внешних переменных приводит к ухудшению повторного использования кода, ухудшению читабельности кода и усложнению работы с ошибками. Кроме этого, в клиентском коде появляются специфические сложности, связанные с работой с внешними переменными. Рассмотрим конкретный пример.
Постановка задачи. Вычислить значение величины y, заданной следующим образом.

Решение. Вариант 1. Рекомендуемый вариант. Внешние переменные не используются.
Анализ расчетной формулы показывает, что она содержит три подвыражения, имеющие одинаковую структуру.
На первом этапе разработки напишем выражение более общего вида по сравнению с каждым из трех подвыражений. Обобщение достигается путем введения системы параметров, которые бы учитывали различия конкретных подвыражений. Анализ отдельных подвыражений показывает, что для построения обобщенного выражения достаточно ввести три параметра. Два из них должны учитывать различия в начальном и конечном значении переменной суммирования, а третий параметр – различие в виде очередного слагаемого этих сумм. В результате получим:

Нетрудно видеть, что функция, выполняющая вычисления по приведенной выше обобщенной формуле, должна иметь четыре параметра. Три из них (i_min, i_max и x) введены в процессе получения обобщенной формулы. Они учитывают индивидуальные особенности подвыражений. Четвертый параметр (n) одинаков для всех трех индивидуальных подвыражений. Это обстоятельство будет учтено при рассмотрении второго варианта решения этой задачи. Используя обобщенное выражение, можно написать следующий прототип разрабатываемой функции.
Double root_summa(int i_min, int i_max, double x, int n);
Наличие функции с таким прототипом позволяет написать для вычисления искомой величины y следующую инструкцию языка Си.
Y = (root(3, m1, -a, n) + root(2, m2, 0, n)) / (4 + root(2, m1, 2, n));
Ниже приводится текст программы, в которой при решении рассматриваемой задачи интерфейс реализуется стандартным образом (без использования внешних переменных).
#include<stdio.h> #include<math.h> double root_summa(int i_min, int i_max, double x, int n); int main(void) { int n; printf(“%s”, “n=”); scanf(“%d”, &n); double a; printf(“%s”, “a=”); scanf(“%lf”, &a); int m1; printf(“m1=”); scanf(“%d”, &m1); int m2; printf(“m2=”); scanf(“%d”, &m2); double y = (root_summa(3, m1, -a, n) + root_summa(2, m2, 0, n)) /
(4 + root_summa(2, m1, 2, n)); printf(“y = %0.4g\n”, y); getchar(); return 0; } /* Функция для вычисления корня n степени из суммы */ double root_summma(int i_min, int i_max, double x, int n) { double s = 0; for(int i = i_min; i <= i_max; i++) s += (i + x) * (i + x); return pow(s, 1.0 / n); }
Вариант 2. Замена параметров функции внешними переменными.
Анализ решения рассматриваемой задачи, приведенный в варианте 1, показывает, что параметр (n) во всех трех вызовах функции root_summa() имеет одно и то же значение. Есть основания заменить такой параметр внешней переменной. После выполнения замены программа принимает следующий вид.
#include<stdio.h> #include<math.h> double root_summa_2(int i_min, int i_max, double x); int n; //Внешняя переменная int main(void) { <ввод n> <объявление и ввод a, m1 и m2> double y = (root_summa_2(3, m1, -a)+ root_summa_2(2, m2, 0))/ (4 + root_summa_2(2, m1, 2)); printf(“y = %0.4g\n”, y); getchar(); return 0; } /* Функция для вычисления корня n степени из суммы n – внешняя переменная целого типа, равная степени корня */ double root_summma_2(int i_min, int i_max, double x) { <тело идентично телу функции root_summa> }
Сравнивая два полученных решения рассматриваемой задачи, начинающий программист может отдать предпочтение второму варианту. Действительно, функция root_summa_2() имеет меньшее количество параметров по сравнению с функцией root_summa(). Это уменьшает накладные расходы, которые имеют место при вызове функции. Наличие внешней единственной переменной не сильно будет ухудшать ее читабельность. Дело в том, что сама программа невелика. В программе имеется комментарий, который предупреждает, что функция root_summa_2() использует внешнюю переменную. В программе имеется только одна функция. Поэтому невелика опасность случайного (при наличии в них ошибок) изменения внешней переменной. Неустранимым недостатком использования внешних переменных является ухудшение повторного использования кода. Предположим, что разработанную при решении рассматриваемой задачи функцию, необходимо перенести в другую программу. При переносе функции root_summa() программисту достаточно скопировать текст этой функции и перенести его в новую программу; при переносе функции root_summa_2()ему придется считаться с наличием внешней переменной n. Её придётся переносить отдельно. Могут возникнуть дополнительные трудности, когда новая программа уже содержит внешнюю переменную с именем n. Трудности будут нарастать, когда программа будет содержать несколько функций, в которых используются внешние переменные.
Вариант 3. Неудачный вариант. Полная замена параметров функции внешними переменными.
Этому варианту, наряду с недостатками второго варианта решения добавляется новый недостаток, существо которого состоит в следующем. Оказывается, невозможным написать несколько обращений к функции в одном выражении. Это приводит к необходимости вводить дополнительные переменные, назначение которых состоит в сохранении результатов отдельных обращений к функции root_summa_3()..
#include<stdio.h> #include<math.h> double root_summa_3(void); int n, i_min, i_max;//Внешние переменные double x; //Внешняя переменная int main(void) { <ввод исходных данных n, a, m1 и m2> // Подготовка к первому вызову функции root_summa_3 i_min = 3; i_max = m1; x = -a; double s1 = root_summa3(); // вычисление 1–ой суммы //Подготовка ко второму вызову функции root_summa_3 i_min = 2; i_max = m1; x = 0; double s2 = root_summa_3();// вычисление 2–ой суммы // Подготовка к третьему вызову функции root_summa_3 i_min = 2; i_max = m1; x = 2; double y = (s1 + s2) / (4 + root_summa_3()); printf(“y = %0.4g\n”, y); getchar(); return 0; } //Функция для вычисления корня n степени из суммы //Функция использует четыре внешних переменных: //int n – степень корня; int i_min и i_max – минимальное и // максимальное значение переменной суммирования; // double x – параметр, входящий в слагаемые суммы double root_summa_3(void) { <тело данной функции идентично телу функции root_summa> }
Приведенное решение свидетельствует о существенном усложнении клиентского кода функции при замене параметров внешними переменными.
