Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.

Предоплата всего

Подписываем
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Предоплата всего
Подписываем
|
Лабораторная работа "Передача массивов в функции" |
6/6 |
Р
Л
ЛАБОРАТОРНАЯ РАБОТА
Тема: Передача массивов в функции
Цель работы: Изучить особенности использования массивов как аргументов функций
Имена массивов и указатели
В Си разница между именем одномерного массива и указателем на тип элементов этого массива проявляется в двух случаях:
1. Использование имени массива в роли указателя.
Имена массивов трактуются компилятором как адреса, а именованные адреса в Си называются указателями.
Обычно под указателем понимают переменную, т.е. объект, который при выполнении программы может изменить свое значения. Для изменения значения переменная должна быть помещена в левую часть оператора присваивания (использоваться как l-value).
Но имена массивов это особый тип указателей. Они обозначают адрес, который меняться не может (указатель-константа). Поэтому имена массивов (без индексов) могут появляться только в правых частях выражений (использоваться только, как r-value)
Пример 1.
В программе определены одномерный массив и указатель, который инициализируется адресом массива.
int arr[12], *pa=arr;
arr++ ; /* Ошибка! Объяснить, почему? */
pa++; /* Правильно */
2. Использование имени массива для вычисления размер объекта.
Результатом применения операции sizeof к имени массива будет размер отведенной массиву памяти (в байтах).
Если применить операцию к однотипному с массивом указателю (pa), то будет вычислен размер указателя, т.е. объем памяти, используемый в программе для хранения адреса.
Передача одномерного массива в функцию
Под передачей массива в функцию будем понимать использование имени массива в качестве фактического параметра функции, например, f(arr); .
В Си аргументы функций передаются значением, т.е. в формальный параметр копируется значение фактического. Когда речь идет о простых переменных, то это проблем не вызывает.
Но, когда нужно передать в функцию массив, то копирование всего массива будет означать громоздкую операцию дублирования большого объема данных.
Поэтому авторами языка Си было решено, что при передаче массива в функцию вместо самого массива копируется только указатель на его начало. Это имеет два следствия.
а) Во-первых, формальный параметр функции, соответствующий аргументу-массиву, объявляется как указатель на тип элемента массива.
а) Во-вторых, так как в функцию передается указатель на реальный массив, то изменение элементов массива внутри функции будет означать изменение элементов фактического массива. Это принципиально отличает передачу массива от передачи простой переменной.
При передаче массивов в функции требуется учитывать следующие особенности.
а) Тип формального параметра.
Компилятор следит за тем, чтобы типы фактического и формального параметров совпадали. В примере 1 идентификатор массива arr имеет тип указателя на элемент массива (т.е. на int). Поэтому в описании функции или ее прототипа соответствующий этому массиву аргумент также должен иметь тип того же указателя:
void f(int *a){…} (1)
Здесь a формальный параметр, который внутри функции может использоваться, как имя одномерного массива.
Вызов функции с передачей массива будет выглядеть так: f(arr);
Однако фактический параметр функции f реально не обязательно должен быть именем массива. В приведенном примере требуется только, чтобы фактический параметр имел тип int* (указателя на int).
В примере 1, наряду с массивом, был определен указатель pa (синоним имени массива arr), имеющий необходимый тип. Поэтому вызов f(pa); будет равноценным f(arr);.
б) Потеря информации о размере.
Когда массив передается в функцию, в этом участвует только адрес первого элемента массива. Это означает, что размер массива никак не учитывается, и в функции он неизвестен. Поэтому обычно функции добавляется еще один дополнительный аргумент, через который передается размер массива.
Когда вместо массива передается обычный указатель (pa), разговор о размере массива в обычном смысле вообще не теряет смысл. Например, может быть передан указатель не на первый, а на любой другой элемент массива.
Эти рассуждения можно обобщить на случай массивов любой размерности: при передаче массива в функцию информация о его первом (левом) размере теряется.
ЗАДАНИЕ 1 (анализ размеров объектов)
1. Массив arr типа int и указатель pa определены как в примере 1.
Написать программу, в которой с помощью операции sizeof вычисляются и выводятся на экран размеры объектов arr и pa с поясняющим текстом Massiv и Ukazatel.
Объяснить результаты работы программы.
2. В программу добавить функцию с прототипом void size_of_arg(int* a), в которой вычисляется и выводится на экран размер переменной a.
В main предусмотреть вызов функции size_of_arg два раза. Первый раз передать ей имя массива, а второй раз указатель pa.
На экран перед результатами должен быть выведен поясняющий текст, соответственно, Peredan massiv и Peredan ukazatel.
Объяснить результаты работы программы.
Другой способ описания типа для аргумента-массива
Чтобы повысить самодокументируемость программ (сделать очевидным, что по смыслу программы аргумент функции принимает массив) в Си предлагается второй способ описания аргументов-массивов:
void f(int a[]); (1)
Однако нужно понимать, что на самом деле это только более удобный для восприятия синтаксис и различия с void f(int *a); чисто внешние. Компилятор, встретив определение аргумента в виде int a[], заменит его на int *a.
По-прежнему при передаче в функцию будет утерян размер фактического массива.
Замечание
Не будет синтаксической ошибкой, если в квадратных скобках указать размер массива void f(int a[12]). Однако с практической точки зрения для одномерного массива это бесполезно, так как размер учитываться никак не будет.
ЗАДАНИЕ 2 (передача одномерного массива)
1. В функции main определяется и инициализируется массив из нескольких чисел типа double.
Написать функцию total, которой передается массив чисел типа double, Функция суммирует элементы этого массива и возвращает полученную сумму.
Внутри функции размер массива вычислить нельзя. Поэтому при вызове функции total передается не только массив, но и количество элементов в нем.
Чтобы сделать программу более универсальной и не привязывать ее к конкретному размеру, количество элементов массива вычисляется в функции main: с помощью операции sizeof следующим образом: получаем размер памяти, занятой массивом, и делим результат на размер типа double. Затем найденное значение подставляется в вызов функции total.
Вывести на экран:
а) вычисленное количество элементов в массиве,
б) значение полученной суммы чисел.
2. Чтобы убедиться, что компилятор две синтаксические формы определения типа формального параметра воспринимает как одну, использовать в прототипе и определении функции разные способы задания аргумента.
Передача в функцию двумерного массива
Почти все рассуждения, которые относятся к передаче одномерного массива в функцию, остаются справедливыми и для двумерных массивов. Исключение составляет тип данных формального параметра, соответствующего массиву.
Замечание
Для двумерных массивов (т.е. матриц) важнейшей характеристикой является количество элементов в строке (или столбцов матрицы). Действительно, 20 чисел можно представить в виде двумерного массива многими способами: 20*1, 2*10, 4*5, 10*2 и т.д. Так как элементы массивов перебираются по строкам (например, при инициализации), то задание количества элементов в строке однозначно определит структуру массива.
В Си имя массива это указатель на тип элемента массива. Двумерный массив это одномерный массив строк матрицы (тоже одномерных массивов). Поэтому имя двумерного массива это указатель на строку матрицы, т.е. на массив из конкретного числа элементов.
Пример
В функцию f нужно передать единственный аргумент двумерный массив arr2 чисел типа int. Размеры массива: 6 строк и 8 столбцов.
Тогда прототип функции должен будет выглядеть следующим образом (с точностью до имени формального параметра):
void f(int (*a)[8]); (2)
Замечания
1. Если при передаче одномерного массива конкретные размеры массива указывать было не нужно, то для двумерного массива обязательно должно быть указано количество столбцов (второй размер, в примере это 8).
Конструкция (*a)[8] читается, как "указатель на массив из 8 элементов" (запись *a[8] означает "массив из 8 указателей", что не соответствует условию примера).
2. Двумерный массив это одномерный массив строк матрицы. Поэтому в функции информация о количестве его элементов (строк переданного ей двумерного массива) теряется.
Для описания аргументов, принимающих двумерные массивы, также имеется вторая синтаксическая форма:
void f(int a[][8]);
Такая форма описания аргумента более проста и понятна. Но вновь это опять только внешнее представление, потому что компилятор перед трансляцией программы преобразует его к виду (2).
Если указать первый размер, то ошибки компиляции не будет, но этот размер никак использоваться не будет.
ЗАДАНИЕ 3 (передача в функцию двумерного массива)
Написать функцию total2, которая вычисляет сумму элементов любого двумерного массива целых чисел с тремя элементами в строке. Функция возвращает подсчитанную сумму.
1. Функция main.
1.1. В функции main определен двумерный числовой массив arr2[][3] со значениями типа int. Массив инициализирован так, чтобы в нем было 4 полных строки (т.е. размеры 4*3).
1.2. С помощью sizeof определяется размер объекта arr2. Используя этот размер и известное количество столбцов (их 3), подсчитать количество строк в массиве. Вывести на экран оба результата.
1.3. Выполнить вызов функции total2, передав ей
а) массив arr2,
б) количество строк в массиве.
2. Функция total2.
В функции total2 выполняются следующие действия.
2.1. С помощью sizeof определяется размер объекта, соответствующего формальному параметру-массиву, и этот размер выводится на экран.
Сравнить эту величину с той, что была получена в первом пункте задания, и объяснить результаты.
2.2. С помощью вложенных циклов подсчитывается сумма элементов двумерного массива, которая возвращается затем в main. Необходимое для одного из операторов цикла количество элементов в строке массива указать числом (т.е. 3). В следующем задании будет разработан более универсальная программа.
Замечание
Хотя количество элементов в строке через аргумент не передается, его можно вычислить следующим образом. Пусть a2 обозначает формальный параметр, через который в функцию передается массив. По своему смыслу это указатель на строку массива (т.е. на одномерный массив). Если к a2 применить операцию разадресации, то мы получим сам массив.
Теперь количество элементов в строке можно найти, если объем памяти, занимаемый строкой, поделить на размер одного элемента (sizeof(*a2)/sizeof(int)).
2.3 Проверить работу программы.
ЗАДАНИЕ 4 (универсальный вариант программы)
Разрабатывается другой вариант программы для задачи предыдущего задания. Для преобразований программы, сохранить файл задания 3 под другим именем.
1. Чтобы сделать функцию total2 более универсальной, критический для массива размер (число колонок) везде, где он встречается (в определении массива arr2, в определениях функции и прототипа, граничное значение в операторе цикла), задать с помощью именованной константы COLUMNS.
Определение константы COLUMNS выполнить в начале файла с помощью директивы
#define COLUMNS 3
Проверить работу программы.
Замечание
Использование именованной константы COLUMNS не только делает функцию total2 применимой для массивов с любой длиной строки, но и избавляет от необходимости вычислять в функции количество элементов в строке массива (см. предыдущий пункт).
Именованная константа, задающая этот размер, доступна в любой части программы, в том числе и в функции total2.
4. Чтобы убедиться, что внутри функции total2 обрабатываются непосредственно элементы массива arr2, добавить в total2 (перед оператором возврата) обнуления первой строки массива.
Соответственно, в функции main добавить после оператора вывода на экран подсчитанной суммы печать элементов первой строки массива arr2.
Изменить значение COLUMNS на 4, затем на 6. Убедиться, что сумма подсчитывается правильно, а количество выведенных обнуленных элементов меняется соответственно размеру.
Контрольные вопросы
1. Почему при передаче массива в функцию копируется не "значение" объекта, т.е. все элементы, а только указатель на начало массива?
2. Когда в функцию передается простая переменная, то в вызванной функции изменения значения соответствующего аргумента на фактической переменной никак не сказываются. Но изменение в функции элемента аргумента-массива является изменением элемента фактического массива. Объяснить обе ситуации.
3. Есть ли принципиальное различие между двумя способами описания аргументов, через которые передаются массивы? Если нет, то объяснить, почему?
4. Если в функцию передается двумерный массив, какие данные необходимо передать в эту функцию, чтобы можно было вести обработку массива?
5. Что подразумевается, когда говорят, что при передаче массива в функцию "теряется" один его размер?