
- •Часть 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.4. Указатели в качестве параметров функций
Следует учитывать, что в языке Си имеется только один способ передачи параметров – передача по значению. С помощью такого способа можно только передавать данные в функцию. Это защищает фактический параметр вызова от его “порчи” функцией. Иными словами, фактические параметры, передаваемые по значению, могут быть только входными параметрами. Иногда возникает необходимость с помощью параметров вернуть из функции результаты ее работы. Для этой цели в качестве параметров функций следует использовать указатели. Одной из типичных задач, для которых возникает необходимость использовать указатели, является организация функции, предназначенной для ввода данных.
В качестве примера задачи, в которой возникает целесообразность в организации функции пользователя для ввода исходных данных, рассмотрим задачу приближенного вычисления максимума функции с помощью неоднократного табулирования функции.
Постановка задачи. Определить приближенно максимум функции y = x * sin(x), заданной на отрезке [xn, xk]. Метод решения – табулирование функции в n равноотстоящих точках. Для повышения точности табулирование выполняется дважды. После первого табулирования сужается отрезок, на котором выполняется табулирование.
Решение. Для решения задачи напишем две функции. Первая из них input_data() должна использоваться для ввода исходных данных: xn, xk и n. Вторая функция display_table() должна выводить таблицу на экран. В программе организованы два модуля: головной модуль, в котором находится функция main() и модуль table, содержащий функции пользователя. Реализацию функции display_table() оставляем читателю для самостоятельной работы.
// Файл main.c #include <stdio.h> #include <stdlib.h> #include "table.h" int main() { double xn, xk; int n; input_data(&xn, &xk, &n); //#1 display_table(xn, xk, n); input_data(&xn, &xk, &n); display_table(xn, xk, n); getchar(); return 0; } // Файл table.h #ifndef _TABLE_H #define _TABLE_H void input_data(double* pxn, double* pxk, int* pn); void display_table(double xn, double xk, int n); #endif // Файл table.c #include <stdio.h> #include “table.h” void input_data(double* pxn, double* pxk, int* pn) { printf("%s", "xn = "); scanf("%lf", pxn); printf("%s", "xk = "); scanf("%lf", pxk); printf("%s", "n = "); scanf("%d", pn); } <определение функции display_table()>
Все три параметра функции input_data() являются указателями (pxn, pxk и pn). Во время вызова этой функции input_data() в её параметры записываются адреса переменных xn, xk и n, находящихся в вызывающей функции (см. код вызова функции в строке #1). Таким образом, через параметры–указатели вызываемая функция получает доступ к памяти, выделенной в точке вызова. Поэтому ввод из функции input_data направляется в переменные xn, xk и n вызывающей функции main().
2.7. Обработка одномерных массивов
Напомним, что в языке Си следует различать массивы трех категорий: массивы с фиксированными размерами, массивы с переменными размерами и динамические массивы.
Прежде всего, следует учитывать, что каждая операция, связанная с обработкой массивов, обычно требует организации функции.
Определение функции, служащей для обработки одномерных массивов, в языке Си носит универсальный характер. Оно может использоваться с фактическим массивом любой категории и любого размера. Рассмотрим особенности интерфейса функций, работающих с массивами. Формальный параметр, работающий с массивом, должен быть указателем. Однако в языке Си указатель на одиночный объект не отличим от указателя на массив. Поэтому на практике обычная форма записи формального параметра вида double* p используется в случае, когда фактическим параметром является одиночный объект. Для случая, когда фактическим параметром является массив, применяется альтернативная форма записи этого указателя: double ar[]. Наличие пустых скобок [] является подсказкой для программиста: фактический параметр в вызове функции должен быть массивом. Заметим, что в том случае, когда массив используется только для передачи в функцию входных данных, и функция не должна изменять элементы массива целесообразно в объявлении формального параметра использовать квалификатор const. Например, const double ar[].
Перейдем к вопросам, связанным с организацией клиентского кода. Следует учитывать, что в клиентском коде необходимо выделить память для массивов, с которыми будет работать функция. Остановимся на особенностях работы с каждым из трех видов массивов.
Массивы с фиксированными размерами. В определениях таких массивов их размер следует задавать с помощью константного выражения. Предпочтительнее для этой цели использовать символические константы, а не так называемые «магические» числа (например, число 100). В языке Си имеются два способа объявления символических констант. Старый способ основан на использовании директивы препроцессора define. Недостаток этого способа состоит в том, что такая константа оказывается нетипизированной. От этого недостатка свободен новый способ, в котором используется квалификатор const. Однако использовать символические константы, заданные с помощью const при определении массивов с фиксированными размерами в языке Си нельзя. В языке С++ можно, а в языке Си нет. В связи с этим при определении размера таких массивов приходиться ориентироваться на использование директивы define.
Массивы с переменными размерами. Эти массивы появились в стандарте С99. Однако имеются компиляторы еще не поддерживающие массивы с переменными размерами. Преимущество таких массивов по сравнению с массивами с фиксированными размерами состоит возможности оперативно во время выполнения программы задавать размер массива. Отметим, что память для массивов c переменными размерами выделяется только один раз, её нельзя перераспределять.
Динамические массивы. При работе с динамическими массивами выделение и освобождение памяти реализуется программистом с помощью библиотечных функций. В отличие от массивов с переменными размерами память для динамических массивов можно многократно перераспределять.