Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Информатика.doc
Скачиваний:
89
Добавлен:
15.03.2015
Размер:
2.58 Mб
Скачать

Тема 4 Массивы и указатели

Теоретический материал

Одномерные и двумерные массивы

Между массивами и указателями существует тесная связь, поэтому имеет смысл разбирать их вместе.

Массив – это расположенные вплотную друг за другом в памяти элементы одного и того же типа. Всем элементам массива дается одно и то же имя, а различают их по порядковому номеру (индексу). Это позволяет компактно записывать однотипные действия с элементами массива, используя циклы.

Можно сказать, что массив - это структура данных, которую можно рассматривать как набор переменных одинакового типа, имеющих общее имя. Массив используется для хранения однородной по своей природе информации (массив отсчетов напряжения входного сигнала, массив отсчетов времени и т. п.)

Описание одномерных массивов

тип имя [размерность]

int x[5] //описание массива с именем x из 5 целых чисел

Массивы, как и другие объекты, можно размещать в памяти либо с помощью операторов описания в сегменте данных или в стеке, либо в динамической области памяти с помощью операции выделения памяти. Нельзя задать массив переменного размера. Для этого существует отдельный механизм динамического выделения памяти

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

При описании массива его можно инициализировать, то есть присвоить его элементам начальные значения:

int x[5]={1,5.4,8,4}

Индексация начинается с нуля.

int x[10] –массив целого типа, содержащий 10 элементов от x[0] до x[9].

Количество байтов = <размерность базового типа> * <количество элементов>

Массив занимает непрерывную область памяти.

Доступ к элементам массива осуществляется по имени массива и индексу элемента.

Основные свойства одномерных массивов

  • все элементы имеют один и тот же тип;

  • все элементы располагаются в памяти друг за другом;

  • индекс первого элемента равен 0;

  • имя массива является указателем - константой, равной адресу начала массива (первого байта первого элемента массива);

  • признак массива при описании – наличие парных квадратных скобок []. Константа или константное выражение в [ ] задает число элементов массива.

int x[10]; char y[80];

Инициализация одномерных массивов

При описании массивов может быть выполнена явная инициализация его элементов.

Для этого после описания массива помещается список начальных значений элементов массива, заключенных в фигурные скобки {}.

Известны две формы явной инициализации массива:

  • явное указание числа элементов массива и список начальных значений, может быть, с меньшим числом элементов:

int array[10]={5,2,1,3};

Описывается массив из 10 элементов. Первые 4 элемента массива инициализированы числами 5, 2, 1, 3. Значения остальных 6 элементов либо равны 0, либо не определены.

Если список начальных значений содержит больше элементов, чем число в [], то компилятор генерирует сообщение об ошибке;

  • только со списком начальных значений. Компилятор определяет число элементов массива по списку инициализации:

char arr[ ]={‘A’,’B’,’C’};

В результате создается символьный массив ровно из 3 элементов, и эти элементы получат начальные значения из списка инициализации.

В Си для повышения производительности программы не выполняются многие проверки корректности вычислений, в том числе и контроль допустимости значения индекса массива. (Автоматического контроля выхода за границы массива нет!) Для упрощения контроля границ индекса массива удобно использовать операцию sizeof, которая применительно к массивам возвращает не размер элемента, а число байтов памяти, зарезервированное компилятором для массива. Так можно определить число элементов массива:

int array[10]={1,2,3,4,5,6,7};

int members;

members=sizeof(array)/sizeof(int);

При выводе на экран значения переменной members получим ответ:

members=10;

Описание двумерных массивов

тип имя_массива [размерность1][размерность2]

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

int x[2][3] – целочисленная матрица из 2 строк и 3 столбцов.

Массив хранится по строкам в непрерывной области памяти. При просмотре массива от начала первым будет изменяться правый индекс (номер столбца!)

Доступ к отдельному элементу - x[i][j],где i, j - номера строки и столбца соответственно.

Имя массива представляет собой константный указатель на начало массива.

Инициализация двумерных массивов

int x[2][3]={1,2,3,4,5,6}

int x[2][3]={{1,2,3},{4,5,6}}

int x[][3]={{1,2,3}{4,5,6}} - память будет выделена под столько строк массива, сколько серий значений в фигурных скобках будет указано в списке.

Пример программы, работающей с одномерным массивом(вектором)

Постановка задачи

Ввести с клавиатуры значения элементов исходного массива а, состоящего из 5 элементов целого типа.

Сформировать новый массив b в соответствии со следующим правилом:

b[i]=a[i]+2(max-min), где max и min – максимальное и минимальное значения исходного массива a. Ввод-вывод элементов массива – потоковый.

Анализ задачи

В данной задаче речь идет о двух массивах: исходном массиве а и результирующем массиве в. Для работы с этими массивами под каждый из них необходимо выделить память, для чего их необходимо задекларировать. Размерность массивов может быть только константой (const int n=5;)

Декларация самих массивов соответствует строке (int a[n],b[n];)

Для хранения значений максимума, минимума и индекса выделяются переменные с именами max,min и i (int i,max,min;)

Цикл(1) осуществляет поэлементный ввод конкретных значений массива а в ячейки памяти, выделенные под массив на этапе компиляции.

Цикл (2) ищет максимальное и минимальное значения массива а.

Цикл (3) формирует значения нового массива в в соответствии с заданием и тут же поэлементно выводит сформированные значения на экран монитора.

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

Текст программы

#include<iostream.h>

int main() {

const int n=5;

int a[n], b[n];

int i, max, min;

cout<<"enter array a:"<<endl;

for(i=0;i<n;i++) {

cout<<"a["<<i<<"]="; //цикл (1)

cin>>a[i];

}

max=a[0]; min=a[0];

for(i=0;i<n;i++) {

if(a[i]>max) max=a[i]; // цикл (2)

if(a[i]<min) min=a[i];

}

cout<<endl<<"results:"<<endl;

cout<<"N a[i] b[i]"<<endl;

for(i=0;i<n;i++) {

b[i]=a[i]+2*(max-min); //цикл(3)

cout<<i<<" "<<a[i]<<" "<<b[i]<<endl;

}

int kol=sizeof(a)/sizeof(int);

cout<<endl<<"kol="<<kol;

return 0;

}

Варианты заданий к работе 4_1

1. Дан вектор, состоящий из 7 вещественных элементов. Найти сумму отрицательных элементов вектора.

Ввод/вывод – потоковый.

2. Дан вектор, состоящий из 17 вещественных элементов. Найти произведение элементов вектора, стоящих после максимального элемента. Предполагается, что максимальный элемент не последний элемент в массиве.

Ввод/вывод – потоковый.

3. Дан вектор, состоящий из 5 вещественных элементов. Найти сумму положительных элементов вектора.

Ввод/вывод – потоковый.

4. Дан вектор, состоящий из 9 вещественных элементов. Найти сумму элементов вектора с нечетными номерами (индексами).

Ввод/вывод – потоковый.

5. Дан вектор, состоящий из 10 вещественных элементов. Найти сумму положительных элементов, расположенных после первого отрицательного элемента. Предполагается, что первый отрицательный элемент не есть последний элемент массива и что среди элементов массива есть и положительные, и отрицательные элементы.

Ввод/вывод – потоковый.

6. Дан вектор, состоящий из 7 элементов целого типа. Найти минимальный по модулю элемент вектора.

Ввод/вывод – потоковый.

7.Дан вектор, состоящий из 25 элементов целого типа. Найти номер минимального элемента.

Ввод/вывод – потоковый.

8. Дан вектор, состоящий из 7 вещественных элементов. Поменять местами первый и максимальный элементы вектора. Предполагается, что они не совпадают по индексу.

Ввод/вывод – потоковый.

9.Дан вектор, состоящий из 9 положительных и отрицательных вещественных элементов. Создать из него новый вектор, состоящий только из положительных элементов.

Ввод/вывод – потоковый.

10. Дан вектор, состоящий из 8 вещественных элементов. Найти произведение элементов, имеющих четные индексы.

Ввод/вывод – потоковый.

11. Дан вектор, состоящий из 10 элементов целого типа. Найти сумму элементов, стоящих после максимального элемента. Предполагается, что максимальный элемент –не последний.

Ввод/вывод – потоковый.

12. Дан вектор, состоящий из 12 элементов целого типа. Найти сумму элементов, стоящих после минимального элемента. Предполагается, что минимальный элемент не последний.

Ввод/вывод – потоковый.

13. Дан вектор, состоящий из 10 элементов целого типа. Найти сумму элементов, меньших 5. Предполагается, что такие элементы в векторе присутствуют.

Ввод/вывод – потоковый.

14. Дан вектор, состоящий из 10 элементов целого типа. Найти сумму элементов, стоящих перед максимальным элементом. Предполагается, что максимальный элемент –не первый.

Ввод/вывод – потоковый.

15. Дан вектор, состоящий из 30 элементов целого типа. Найти количество положительных элементов, стоящих между максимальным и минимальным элементами.

Ввод/вывод – потоковый.

16. Дан вектор, состоящий из 8 вещественных элементов. Найти количество элементов, стоящих после максимального элемента. Предполагается, что максимальный элемент – не последний . Использовать потоковый ввод/вывод.

17. Дан вектор, состоящий из 10 элементов целого типа. Сжать массив, удалив из него все элементы, модуль которых не превышает 3. Освободившиеся в конце массива элементы заполнить нулями.

Использовать потоковый ввод/вывод

18. Дан вектор, состоящий из 7 вещественных элементов. Упорядочить элементы по неубыванию, используя сортировку методом пузырька.

Ввод/вывод - потоковый.

19. Дан вектор, состоящий из 22 вещественных элементов. Упорядочить элементы по убыванию, используя линейную сортировку.

Ввод/вывод – потоковый

20. Дан вектор, состоящий из 9 элементов целого типа. Упорядочить элементы массива по неубыванию модулей элементов, используя сортировку одним из известных вам методов.

Ввод/вывод – потоковый.

21. Дан вектор, состоящий из 6 вещественных элементов. Заменить все отрицательные элементы массива их квадратами, после чего упорядочить элементы массива по неубыванию, используя один из известных вам методов сортировки .

Ввод/вывод – потоковый.

Пример программы, работающей с двумерным массивом (матрицей)

Дана целочисленная матрица, состоящая из 3 строк и 4 столбцов. Найти количество положительных элементов в каждой строке матрицы и среднее арифметическое значение элементов всей матрицы. Элементы матрицы вводятся с клавиатуры. Ввод/вывод элементов – потоковый.

Текст программы

#include<iostream.h>

#include<iomanip.h>

int main() {

const int nrow=3,ncol=4;

int a[nrow][ncol];

int i,j;

cout<<"enter array:"<<endl;

for(i=0;i<nrow;i++) //цикл ввода матрицы

for(j=0;j<ncol;j++) // (1)

cin>>a[i][j];

cout<<endl;

for(i=0;i<nrow;i++) { //контрольный вывод

for(j=0;j<ncol;j++)

cout<<setw(5)<<a[i][j]<<" ";

cout<<endl; //(2)

}

cout<<endl;

int n_pos_el;

float s=0;

for(i=0;i<nrow;i++) { //цикл обработки матрицы

n_pos_el=0;

for(j=0;j<ncol;j++) {

s+=a[i][j]; //(3)

if(a[i][j]>0) n_pos_el++;

}

cout<<"in row "<<i<<" n_pol_el=:"<<n_pos_el<<endl;

}

cout<<endl;

s/=nrow*ncol;

cout<<"sr="<<s<<endl;

return 0;

}

Пояснения к программе:

Заголовочный файл <iostream.h> позволяет воспользоваться объектами cin и cout для связи с клавиатурой и экраном соответственно и операциями ввода/вывода >> и << .

Заголовочный файл <iomanip.h> позволяет воспользоваться манипулятором setw(int), который устанавливает максимальную ширину поля вывода.

nrow-количество строк матрицы

ncol – количество столбцов матрицы

int a[nrow][ncol] – декларация матрицы, в соответствии с которой на этапе компиляции будет выделена память для хранения значений матрицы, состоящей из nrow строк и ncol столбцов.

Цикл(1) позволяет ввести значения элементов матрицы в оперативную память, которая была выделена под матрицу на этапе компиляции в соответствии с вашей декларацией. Ввод значений осуществляется по строкам.

Цикл(2) проводит контрольный вывод только что введенной матрицы, выделяя под каждый элемент 5 позиций и выводя матрицу по строкам.

Представляет интерес последний цикл(3). До входа в этот цикл задекларирована переменная n_pos_el целого типа. Это счетчик, который будет считать количество положительных элементов в каждой отдельной строке. Здесь же задекларирована вещественная переменная s, которая будет накапливать сумму всех элементов матрицы. Эта сумма необходима для расчета среднего арифметического значения, которое будет вещественного типа, ибо является результатом деления суммы всех элементов на их количество. До входа в цикл значение s обнулили.

При первом входе в цикл фиксируем нулевую строку(i=0) и работаем с ней. Прежде чем пробежаться по всем ее столбцам(j от 0 до ncol)обнулим переменную n_pos_el=0.Счет положительных элементов еще не начался. Мы в начале нулевой строки.

Теперь войдем во внутренний цикл по j и прогуляемся по столбцам нулевой строки. При этом в теле внутреннего цикла выполним два оператора. Первый будет добавлять к сумме значение очередного элемента строки, а второй – в случае, если этот очередной элемент положителен, увеличит счетчик положительных элементов строки на единицу.(n_pos_el++).

По окончании работы с нулевой строкой вернемся в начало внешнего цикла по i , которое теперь станет равным единице. Это означает, что мы переходим к работе с первой строкой(i=1).Все операторы тела цикла повторят ту же самую работу, но только теперь для элементов первой строки и т.д..

По выходе из цикла останется найти среднее арифметическое значение.

Варианты заданий к работе 4_2

Задание к выполнению лабораторной работы 4_2 (общее для всех):

Написать программу, реализующую алгоритм решения задачи. Размерность массива nrow (количество строк) и ncol (количество столбцов) задать именованными константами. Значения элементов массива вводить в оперативную память (ОП) с клавиатуры. Ввод/ вывод – потоковый.

Варианты:

1. Дана целочисленная матрица, состоящая из 3 строк и 4 столбцов.

Определить количество строк, не содержащих ни одного нулевого элемента.

2. Дана целочисленная матрица ,состоящая из 4 строк и 5 столбцов.

Определить количество столбцов, не содержащих ни одного нулевого элемента.

3. Дана целочисленная матрица ,состоящая из 3 строк и 4 столбцов.

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

4. Дана целочисленная матрица ,состоящая из 2 строк и 5 столбцов.

Определить номер столбца, не содержащего ни одного нулевого элемента.

5. Дана целочисленная матрица ,состоящая из 3 строк и 4 столбцов.

Определить количество столбцов, в которых не содержится ни одного отрицательного элемента

6.Дана целочисленная матрица ,состоящая из 5 строк и 4 столбцов.

Определить сумму положительных четных элементов матрицы.

7. Дана целочисленная матрица ,состоящая из 3 строк и 8 столбцов.

Определить номер строки, в которой находится самая длинная серия одинаковых элементов.

8. Дана целочисленная матрица ,состоящая из 3 строк и 4 столбцов.

Определить произведение элементов, в тех строках, которые не содержат отрицательных элементов.

9. Дана целочисленная матрица ,состоящая из 3 строк и 4 столбцов.

Определить cумму элементов в тех строках, которые содержат хотя бы один отрицательный элемент.

10. Дана целочисленная матрица ,состоящая из 3 строк и 4 столбцов.

Определить среднее арифметическое элементов 2 строки матрицы.

11. Дана целочисленная матрица ,состоящая из 3 строк и 4 столбцов.

Определить количество строк, среднее арифметическое элементов которых меньше заданной величины.

12. Дана целочисленная квадратная матрица. Найти сумму элементов главной диагонали матрицы.

13. Дана целочисленная квадратная матрица. Найти сумму элементов

в тех строках, которые не содержат отрицательных элементов .

14. Дана целочисленная квадратная матрица. Определить количество строк, которые не содержат отрицательных элементов.

15. Дана целочисленная квадратная матрица, размерностью 4*4.

Сформировать вектор, содержащий только положительные элементы второй строки матрицы.

16. Дана целочисленная квадратная матрица размерностью 5*5.Cформировать вектор, состоящий из элементов главной диагонали.

17. Дана целочисленная матрица, имеющая 4 строки и 5 столбцов.

Сформировать вектор, являющийся первой строкой матрицы и

найти сумму его элементов.

18. Дана целочисленная матрица, имеющая 4 строки и 5 столбцов.

Сформировать вектор, являющийся 3 столбцом матрицы и найти в нем минимальный элемент.

19. Дана целочисленная матрица, имеющая 4 строки и 5 столбцов.

Сформировать вектор, i-тый элемент которого равен единице,

если все числа i-той строки матрицы –положительны и равен

нулю в противном случае.

20. Дана целочисленная матрица, имеющая 4 строки и 5 столбцов.

Сформировать вектор, состоящий из отрицательных элементов третьего столбца матрицы.

21. Дана целочисленная матрица, имеющая 4 строки и 5 столбцов.

Найти среднее арифметическое элементов третьего столбца матрицы

22. Дана целочисленная матрица, имеющая 4 строки и 4 столбца.

Заменить третью строку матрицы на третий столбец.

23. Дана целочисленная матрица, имеющая 4 строки и 4 столбца.

Поменять местами 2 строку и второй столбец.

24. Дана целочисленная матрица, имеющая 4 строки и 5 столбцов.

Во второй строке матрицы найти максимальный элемент и поме-

нять его местами с первым элементом этой строки.

25. Дана целочисленная матрица, имеющая 4 строки и 5 столбцов

Сформировать вектор, состоящий из элементов 2 столбца и найти в нем минимальный элемент.

26. Дана целочисленная матрица, имеющая 4 строки и 5 столбцов.

Сформировать вектор, состоящий только из положительных элементов матрицы. Порядок расположения элементов не имеет значения.

27. Дана целочисленная матрица, имеющая 4 строки и 5 столбцов.

Определить номер строки, в которой находится максимальный элемент матрицы.

Теоретический материал

Указатели

Указатели - это переменные, предназначенные для хранения адресов областей памяти. Значения указателей показывают, где в памяти хранится объект, а не что хранится по адресу.

В Си существует два способа доступа к переменным:

  • ссылка на переменную по имени:

int a;

a=5;

  • использование механизма указателей:

int a,*pa;

pa=&a;

*pa=15;

pa a

&a ---15 , где

pa – указатель, ссылающийся на а (говорят, что pa указывает на а, или pa ссылается на а).

Так как унарный оператор & выдает адрес объекта, то можно написать pa=&a. Еще раз напоминаем, что оператор & применяется только к объектам, расположенным в памяти: к переменным и элементам массивов.

Указатели могут быть:

указателями – константами, и тогда они ссылаются на один и только один адрес памяти;

указателями – переменными, и тогда они служат для хранения различных адресов ОП( их называют просто указатели).

Признаком указателя – переменной для компилятора является наличие в описании переменной двух компонентов:

тип объекта данных, для доступа к которому используется указатель;

символ * перед именем переменной.

В совокупности тип и * воспринимаются компилятором как особый тип данных – “указатель на что-либо”.

Итак, указатель может быть константой или переменной, а также он может указывать на константу или переменную:

int x; //целая переменная

const int n=3; // целая константа

int *px; //указатель на целую переменную

const int * pn; //указатель на целую константу

(изменить константное значение по адресу pn нельзя, но значение самого указателя может быть изменено, то есть его можно заставить указывать на другую константу).

int *const np=&x; //указатель-константа на целую переменную

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

const int *const npn=&n; //указатель-константа на целую константу

(если надо заморозить как адрес, так и значение по этому адресу, то следует использовать константный указатель на константу).

Модификатор const,находящийся между именем указателя и звездочкой, относится к самому указателю и запрещает его изменение.Const слева от звездочки задает постоянство значения, на которое он указывает.

В пару к унарному оператору &-взятия адреса имеется унарный оператор * - раскрытие ссылки. (обращение к содержимому по адресу, равному значению указателя):

*p1=*p2+4; // взять содержимое памяти по адресу, равному значению указателя p2, прибавить к этому содержимому 4, а результат поместить по адресу, равному значению указателя p1.

Пример использования оператора * (косвенной адресации):

#include<stdio.h>

//использование операции косвенной адресации

int main() {

// без косвенной адресации

int x,y;

clrscr();

x=2;

y=x;

printf(“y=%d\n”,y); //будет напечатано y=2

//с косвенной адресацией

int *px;

px=&x;

y=*px;

printf(“y=%d\n,y); // будет напечатано y=2

// использование косвенной адресации в левой части оператора присваивания

int z,*pz;

pz=&z; // инициализировать pz адресом z

*pz=10; // изменить значение, находящееся по указанному

/ / адресу (то есть значение z) Присвоить 10

// по адресу, содержащемуся в переменной pz.

printf(“z=%d\n”,z); //z=10

return 0;

}

Замечание.

Посмотрите таблицу приоритетов операций (табл.1.1)

Убедитесь в том, что приоритет двух унарных операций * и & достаточно высок

Выводы

Значение указателя сообщает о том, где размещен данный объект, но при этом не говорит ничего о самом объекте.

Звездочка * относится непосредственно к имени, поэтому для объявления нескольких указателей надо ставить звездочку перед именем каждого из них.

int *x,y,*z; // описаны два указателя на целое c

//именами x и z, а также целая переменная y.

Инициализация указателей

Осуществляется с помощью присваивание указателю адреса существующего объекта:

int x;

int * px=&x; // использование операции &)

char arr[10];

char * parr=arr; //с помощью имени массива, так как имя массива в этом случае само является адресом начала массива).

arr==&arr[0], где arr[0] –первый элемент массива (элемент массива с индексом =0).

Операции с указателями

Мы уже выделили две особые операции & и *.

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

Кроме этого важны еще арифметические операции

(сложение с константой, вычитание, инкремент, декремент)

Эти операции автоматически учитывают размер типа величин, адресуемых указателями.

Эти операции применимы только к указателям одного типа и имеют смысл в основном при работе со структурами данных, последовательно размещенных в памяти, например, с массивами:

int a[5];

int *pa=a; // инициализировать pa адресом первого элемента массива.

Можно ли с помощью переменной pa осуществить адресацию к определенному элементу массива?

Альтернативные способы задания элементов массива

pa=&a[0]--------|a[0] | a[1]| a[2] |a[3] |a[4]|

a a+1 a+2 a+3 a+4

&a[0] &a[1] &a[2] &a[3] &a[4]

pa pa+1 pa+2 pa+3 pa+4

Арифметические действия над указателями обеспечивают удобный способ манипулирования массивами или другими непрерывными участками памяти.

Добавление целого числа к указателю увеличивает содержащийся в нем адрес на произведение этого целого числа на размер (в байтах) того объекта, на который этот указатель показывает. Именно поэтому надо описать тип данных, на который показывает указатель. За счет этого выполняется масштабирование в соответствии с размером указанного типа.

Таким образом, в языке предусмотрены операции, которые можно использовать для доступа к указателям и для манипулирования ими.

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

Замечание. Мы узнали, что имя массива и адрес его первого элемента

эквивалентны.

Имя массива в действительности является указателем. Поэтому мы не можем присвоить ему новое значение. Имена массивов не являются адресуемыми значениями, и оператор вида имя массива = выражение недопустим, так как нельзя изменить адрес массива.

Выводы

  • Массив- это структура данных, представляющая набор переменных одинакового типа, имеющих общее имя.

  • Размерность массива может быть только константой или константным выражением. Рекомендуется задавать размерность с помощью именованной константы.

  • Нумерация элементов массива начинается с нуля, поэтому максимальный индекс для элементов массива на единицу меньше размерности массива.

  • Автоматический контроль выхода индекса за границы массива отсутствует, поэтому программисту необходимо следить за этим самостоятельно.

  • Массив, как и любой другой объект программы, должен быть задекларирован, чтобы дать возможность компилятору выделить под него память. После выделения памяти надо эту память заполнить конкретными значениями элементов массива, например, с помощью ввода этих элементов с клавиатуры. Только после этого можно начать обработку элементов массива в соответствии с заданием.

  • Ввод / вывод элементов массива и его обработка выполняются только в цикле.

  • Работая с массивом в цикле обратите внимание на то, что параметр цикла и индекс массива всегда имеют одно и то же имя. Это принципиально важно.

  • Указатель – это переменная, в которой хранится адрес области памяти.

  • При работе с массивами автоматически формируется указатель с именем массива. Имя массива является указателем на его нулевой элемент(значение указателя равно адресу нулевого элемента массива).

  • Доступ к элементам массива можно осуществить через указатель с именем массива. Так как указатель является константой, то его можно использовать в выражениях, но нельзя изменять.

Вопросы:

1. Понятие массива

2. Данные каких типов могут выступать в качестве индексов и элементов массива?

3.Какая связь между индексом массива и параметром цикла?

4.Каковы особенности работы с двумерными массивами?

9

ТЕМА: Ф У Н К Ц И И

Теоретический материал

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

ФУНКЦИЯ – это логически самостоятельная именованная часть программы, которой можно передать параметры, и которая может возвратить значение.

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

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

Для работы с функциями необходимо уметь:

  • объявить (задекларировать) функцию;

  • определить (описать) функцию;

  • вызвать функцию.

Объявление (декларация) функции

Задает имя функции, тип возвращаемого значения и список передаваемых параметров:

тип имя_функции ([список формальных параметров]);

Если тип не указан, предполагается по умолчанию, что функция возвращает целое типа int.

Список формальных параметров состоит из перечня типов и имен параметров, разделенных запятыми.

Если параметров нет, то ( ) все равно обязательны.

В списке параметров для каждого параметра должен быть указан тип:

(int x, int y, float z)

Объявление функции – это ее заголовок, который называют прототипом или сигнатурой. До первого вызова функции компилятор должен знать тип возвращаемого результата, а также количество и типы аргументов. Только тогда может быть создан правильный машинный код функции ( то есть объявления функций должны находиться в тексте программ раньше ее вызова). Обычно прототипы функций помещают в заголовочный файл, подключаемый директивой #include к тексту программы.

Чаще всего прототип функции полностью совпадает с заголовком в описании функции, хотя и не всегда так. Дело в том, что имена формальных параметров не играют никакой роли, и могут игнорироваться компилятором:

int func(int a, float b, char*c);

int func(int, float, char*); //оба прототипа эквивалентны

Определение (описание) функции (стандарт ANSI)

тип имя_функции ([список формальных параметров])

{

тело функции

}

  • Имя функциии -это особый тип указателя, называемый указателем на функцию. Его значением является адрес точки входа в функцию. Выполнение функции возвращает управление в точку вызова;

  • Тип возвращаемого значения может быть любым, кроме массива и функции (но может быть указателем на массив и функцию);

  • Список формальных параметров - определяет величины, передаваемые в функцию (типы, имена). Если в функцию ничего не передается, то поле списка аргументов либо пусто (), либо (void). Элементы списка разделяются запятыми;

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

Вызов функции

В простейшем случае надо указать имя функции и имена передаваемых аргументов:

имя_ функции([список фактических параметров]);

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

Обмен информацией между функциями

Любая программа на С++ - это совокупность определений переменных и функций. Связи между функциями осуществляются

  • с помощью глобальных переменных;

  • через возвращаемое значение;

  • с помощью передачи параметров.

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

Возвращаемые значения

реализуется оператором return.

Оператор return имеет два варианта использования:

  • вызывает немедленный выход из текущей функции и возврат в вызывающую программу;

  • может использоваться для возврата значения функции

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

Нахождение наибольшего из двух чисел:

1)

#include <iostream.h>

int func(int a,int b); //прототип функции

int main() {

int x,y,max;

cout<<"input x,y";

cin>>x>>y;

max=func(x,y); //вызов функции

cout<<"max="<<max;

return 0;

}

int func(int a,int b) { //определение функции

int m;

if(a>b) m=a;

else m=b;

return m; //возвращение значения максимума в точку в

// вызова функции

}

Функция нахождения максимума может выглядеть по-другому( 2),3).4)):

2) int func(int a, int b) {

if(a>b) return a;

else return b;

}

3) int func(int a, int b) {

if(a>b) return a;

return b;

}

4) int func(int a, int b) {

return(a>b)? a:b;

}

Все величины, описанные внутри функции, являются локальными. Областью их действия является функция. При вызове функции в специальной области памяти, называемой стеком, выделяется память под локальные автоматические переменные. Большинство переменных являются именно автоматическими, то есть они относятся к классу хранения auto. Автоматические переменные – это всегда локальные переменные, но локальные переменные не обязательно должны быть автоматическими. Локальные переменные могут быть, например, статическими. Класс хранения определяет место, где будет расположен объект (сегмент стека или сегмент данных) и одновременно время жизни этого объекта. Сегмент стека и сегмент данных – это разные области памяти. Сегмент данных хранит значения переменных в течение жизни всей программы. Стек – только время жизни функции. При выходе из функции стек освобождается и готов к приему значений переменных следующей функции.

Кроме того, в стеке сохраняется содержимое регистров процессора на момент, предшествующий вызову функции, и адрес возврата из функции для того, чтобы при выходе из нее можно было продолжить выполнение вызывающей функции. При выходе из функции соответствующий участок стека освобождается, поэтому значения локальных переменных между вызовами одной и той же функции не сохраняются. Если вы все-таки решили сохранить значения переменных, то при объявлении локальных переменных используют модификатор static.(видимость в пределах модуля, в котором определена функция). Все глобальные переменные по умолчанию являются статическими объектами. Локальная же переменная должна быть явно отнесена к классу static. Статические переменные существуют все время, пока выполняется программа.

Пример 1

#include<iostream.h>

int func( int x) {

int m=0;

cout<<”n m p\n”;

while(x--) {

static int n=0;

int p=0;

cout<<n++<<” ”<<m++<<” “<<p++<<endl;

return 0;

}

cout<<endl;

}

int main() {

func(3);

func(2);

return 0;

}

Протокол работы:

n m p

0 0 0

1 1 0

2 2 0

n m p

3 0 0

4 1 0

Пояснения

1) static int n=0 статическая переменная n размещается в сегменте данных и инициализируется один раз при первом выполнении оператора, содержащего ее определение

2) автоматическая переменная int m=0 инициализируется при каждом входе в функцию

3) автоматическая переменная int p=0 инициализирется при каждом входе в блок цикла.

Функция func() вызывается дважды. При этом статическая переменная n после выхода из функции сохраняет свое значение Автоматическая переменная при выходе из функции теряет свое значение.

Пример 2

#include<iostream.h>

int func(int);

int main() {

int count;

for(count=9;count>=5;count-=2)

func(count);

return 0;

}

int func(int x) {

int f=1;

static int stat=1;

cout<<”x=”<<x<<”f=”<<f<<”stat=”<<stat<<endl;

stat++;

f++;

return 0;

}

Протокол работы

x=9 f=1 stat=1

x=7 f=1 stat=2

x=5 f=1 stat=3

Пояснения

1) ststic int stat=1 –это статическая переменная stat. Она размещается в сегменте данных и инициализируется один раз при первом выполнении оператора, содержащего ее определение, то есть при загрузке программы.( в пошаговом режиме выполнения программы поэтому мы этого не увидим).

2) int f=1 – это автоматическая переменная. Она инициализируется при каждом входе в функцию.( то есть при каждом входе в функцию ей присваивается значение, равное 1).

3) Функция func() вызывается трижды (в цикле при count=9,count=7,count=5).При этом статическая переменная stat после выхода из функции сохраняет свое значение, полученное внутри функции. Автоматическая переменная f при выходе из функции теряет свое значение.

Замечания. Статические объекты с локальной областью определения имеют статическое время жизни (время жизни всей программы), но они невидимы из других функций и не могут этими функциями модифицироваться. Это позволяет создавать специальные функции-менеджеры ресурса: менеджеры очередей, памяти и т.п. Для доступа к ресурсу вы вынуждены вызвать функцию - менеджер, так как управляемый ресурс невидим из других функций.

Объекты с динамическим временем жизни создаются и разрушаются специальными функциями динамического управления памятью при выполнении программ (будем изучать в курсе “Современные методы программирования”).

Каждая программа имеет только одну функцию с именем main. C этой функции начинается исполнение программы. Другие функции могут быть вызваны из функции main или из любой другой функции в процессе исполнения программы. Каждая функция может иметь 0 или более параметров. Параметры являются переменными, которые используются для передачи данных между функциями. Имена параметров в вызываемой и вызывающей функциях – независимы.

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

Передача параметров

Механизм передачи параметров – это основной способ обмена информацией между функциями. Параметры функции при описании называются формальными параметрами. При вызове используются фактические параметры (аргументы), которые потом становятся на место формальных параметров.

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

Существует два способа передачи параметров в функции:

  • по значению: в стек заносятся копии значений аргументов, и операторы функции работают с этими копиями; доступа к исходным значениям параметров у функции нет, а значит, нет и возможности их изменения;

  • по адресу (с помощью указателя и по ссылке) В стек заносятся копии адресов аргументов, а функция обращается к сегменту данных по этим адресам и может изменить исходные значения аргументов, так как имеет к ним доступ.

Пример

#include<iostream.h>

int func(int i, int *j, int& k);

int main() {

int i=1, j=2, k=3;

cout<<” i j k\n”;

cout<<i<<” “<<j<<” “<<k<<endl;

func(i, &j, k);

cout<<i<<” “<<j<<” “<<k;

return 0;

}

int func(int i, int* j, int& k){

i++;

(*j)++;

k++;

}

протокол работы.

i j k

1 2 3

1 3 4

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

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

Замечания.

1) если мы хотим запретить изменение параметра внутри функции, то используют модификатор const:

int function(const char*); (константная ссылка).

2) Любая переменная обладает двумя основными характеристиками - временем жизни и областью действия. Эти характеристики зависят от места и способа описания переменной.

. . . . . . . . .

int x; // глобальная переменная

int main() {

int f=1; //локальная переменная

static int stat=1; //локальная статическая переменная

. . . . . . . . .

}

Глобальная

Локальная статическая

локальная

Имя

x

stat

f

Место описания

Вне любого блока

Внутри блока с ключевым словом ststic

Внутри блока

Размещение

Сегмент данных

Сегмент данных

Сегмент стека (память выделяется в момент выполнения операции описания)

Время жизни

Вся программа

Вся программа

Блок, в котором она описана, начиная с точки описания

Область видимости

Весь файл, начиная с точки описания

Блок

блок

Инициализация

Изначально обнуляется

Инициализируется однократно

Автоматического обнуления не происходит. Сколько раз выполняется блок, столько раз рождается и умирает локальная переменная

Память под эти переменные выделяет компилятор

Область видимости – это та область исходного кода программы, из которой возможен корректный доступ к памяти с использованием данного идентификатора. По сути, область определения задает его видимость по умолчанию. Если хотим, что бы несмотря на область определения, область его видимости была бы отлична от того, что задается по умолчанию, то используют, например, объявление с атрибутом extern(внешняя ссылка). Область видимости в большей степени касается не компилятора, а компоновщика. Почему? Чаще всего текст программы размещается в нескольких текстовых файлах, каждый из которых содержит целиком одну или несколько функций. Для объединения в одну программу эти текстовые файлы компилируются совместно. Информация обо всех объединяемых в одну программу файлах помещается в так называемый файл проекта. Компилятор порождает для каждого исходного текстового файла отдельный объектный файл. Затем эти файлы объединяются компоновщиком в EXE-файл.

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

Имена функций – это всегда глобальные имена, видимые по умолчанию из всех файлов проекта. Однако, прототипы функций действуют только в пределах одного файла. Поэтому приходится иметь директивы препроцессора, связанные с подключаемыми .h- файлами, содержащими прототипы библиотечных функций.

Передача массивов в качестве параметров

При использовании массива в качестве параметра в функцию передается указатель на его первый элемент, то есть массив всегда передается по адресу. Но адрес указывает только на начало массива и ничего не говорит о его размере, поэтому размерность массива надо передавать через отдельный параметр:

int function(int arr[], const int n);

int function(int *arr, const int n);

Оба варианта приведут к одному и тому же результату. И в том, и в другом случае вы передаете адрес начала массива и количество его элементов.

Пример программы, работающей с функциями

Ввести с клавиатуры два целочисленных массива ‘а’ и ‘в’ и их размерности. Кол-во элементов в массивах не должно превышать 10. В каждом массиве вычислить произведение элементов, кратных 3. Первые элементы в каждом массиве заменить на полученное произведение.

#include<stdio.h> //(1)

#define N 10

int input(int a[],const int n);

int change(int a[],const int n); //(2)

int output(int a[],const int n);

int main() {

int a[N],b[N],n1,n2;

printf("input razmer a=");

scanf("%d",&n1);

printf("input razmer b=");

scanf("%d",&n2); //(3)

input(a,n1);

change(a,n1);

output(a,n1);

input(b,n2);

change(b,n2);

output(b,n2);

return 0;

}

int input(int a[],const int n) {

int i;

printf("input array:\n");

for(i=0;i<n;i++) {

printf("a[%d]=",i);

scanf("%d",&a[i]); //(4)

}

printf("\n");

return 0;

}

int change(int a[],const int n){

int i,pr=1;

for(i=0;i<n;i++)

if(a[i]%3==0) //(5)

pr*=a[i];

a[0]=pr;

}

int output(int a[],const int n) {

int i;

for(i=0;i<n;i++) //(6)

printf("a[%d]=%d\n",i,a[i]);

}

Пояснения.

(1)-директивы препроцессора для подключения заголовочного файла <stdio.h> и определение подстановки в текст программы;

(2) – прототипы функций input(),change(),output();

(3) – определение главной функции main(). Внутри функции main() расположены вызовы функций input(),change() и output() и настройка параметров этих функций сначала на работу с массивом а, затем с массивом в;

(4) – определение функции input(), которое показывает, как будет работать эта функция, когда мы ее вызовем сначала для ввода элементов массива а, затем для ввода элементов массива в;

(5) –определение функции change(), которая ищет произведение только тех элементов массивов а и в соответственно, которые нацело делятся на 3, и помещает результат этого произведения на место первого элемента массива;

(6) – определение функции output(), которая печатает новые массивы.

Выводы

1.Функция – это логически самостоятельная именованная часть программы. Использование функций упрощает структуру программы.

2. Интерфейс функции определяется ее заголовком.

3. Для вызова функции надо указать ее имя и набор фактических параметров.

4. В определении, объявлении и вызове функции типы и порядок следования параметров должны совпадать.

5. Передача параметров в функцию может выполняться по значению и по адресу.

6. Входные данные функции надо передать по значению или по константной ссылке, а результаты работычерез возвращаемое значение или при необходимости вернуть более одной величины – через параметры по ссылке или указателю.

7. Массивы всегда передаются в функцию по адресу. Количество элементов в массиве должно передаваться отдельным параметром.

8. В многофайловом проекте надо уметь разбить задачу на подзадачи и распределить функции по файлам. По сути файлы соответствуют отдельным модулям. Но в С++ нет конструкций для обозначения модулей, но есть понятие – единица трансляции. Единица трансляции представляет отдельный файл с исходным текстом на С++, который получается после обработки препроцессором. Такое разбиение на отдельные файлы требует последующей сборки. Так как в С++ отсутствуют конструкции для обозначения модулей, то нет и конструкций для их сборки в исполняемую программу. Эта задача решается средствами интегрированной среды, в которой создается проект. В составе проекта перечисляются все объектные модули. Процесс сборки полной программы из объектных модулей называется компоновкой. Программа линкования(компоновки) входит в состав системы программирования.(linker- сборщик).При компоновке в программу собираются и разрабатываемые пользователем модули(файлы) , и стандартные модули из стандартных объектных библиотек. Разделение программы на модули требует согласования определений и объявлений в разных единицах трансляции.

О том, как оформить вашу задачу в виде проекта, можно прочитать в приложении.

Задания к работе 5

1. Возьмите ваше задание по лабораторной работе 4_1 (Одномерные массивы). Оформите каждый пункт задания в виде отдельной функции. Все необходимые данные для функции передать в качестве параметров. Использование глобальных параметров в функциях не допускается.

Дополнительное задание

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

Контрольные вопросы.

1. Как связан принцип: “Разделяй и властвуй” с использованием функций?

2. Что требуется знать, чтобы использовать функцию? Как это можно назвать одним словом?

3.Что необходимо знать для вызова функции?

4.Как передаются одномерные массивы в функцию?

5. Какие параметры функции называются формальными, а какие – фактическими (аргументами)?

К лабораторным работам №8 , №9

Ф У Н К Ц И И

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

ФУНКЦИЯ – это логически самостоятельная именованная часть программы, которой можно передать параметры, и которая может возвратить значение.

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

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

Для работы с функциями необходимо уметь:

  • объявить (задекларировать) функцию;

  • определить (описать) функцию;

  • вызвать функцию.

Объявление (декларация) функции

Задает имя функции, тип возвращаемого значения и список передаваемых параметров:

тип имя_функции ([список формальных параметров]);

Если тип не указан, предполагается по умолчанию, что функция возвращает целое типа int.

Список формальных параметров состоит из перечня типов и имен параметров, разделенных запятыми.

Если параметров нет, то ( ) все равно обязательны.

В списке параметров для каждого параметра должен быть указан тип:

(int x, int y, float z)

Объявление функции – это ее заголовок, который называют прототипом или сигнатурой. До первого вызова функции компилятор должен знать тип возвращаемого результата, а также количество и типы аргументов. Только тогда может быть создан правильный машинный код функции ( то есть объявления функций должны находиться в тексте программ раньше ее вызова). Обычно прототипы функций помещают в заголовочный файл, подключаемый директивой #include к тексту программы.

Чаще всего прототип функции полностью совпадает с заголовком в описании функции, хотя и не всегда так. Дело в том, что имена формальных параметров не играют никакой роли, и могут игнорироваться компилятором:

int func(int a, float b, char*c);

int func(int, float, char*); //оба прототипа эквивалентны

Определение (описание) функции (стандарт ANSI)

тип имя_функции ([список формальных параметров])

{

тело функции

}

  • Имя функциии -это особый тип указателя, называемый указателем на функцию. Его значением является адрес точки входа в функцию. Выполнение функции возвращает управление в точку вызова;

  • Тип возвращаемого значения может быть любым, кроме массива и функции (но может быть указателем на массив и функцию);

  • Список формальных параметров - определяет величины, передаваемые в функцию (типы, имена). Если в функцию ничего не передается, то поле списка аргументов либо пусто (), либо (void). Элементы списка разделяются запятыми;

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

Вызов функции

В простейшем случае надо указать имя функции и имена передаваемых аргументов:

имя_ функции([список фактических параметров]);

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

Обмен информацией между функциями

Любая программа на С++ - это совокупность определений переменных и функций. Связи между функциями осуществляются

  • с помощью глобальных переменных;

  • через возвращаемое значение;

  • с помощью передачи параметров.

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

Возвращаемые значения реализуется оператором return.

Оператор return имеет два варианта использования:

  • вызывает немедленный выход из текущей функции и возврат в вызывающую программу;

  • может использоваться для возврата значения функции

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

Каждая программа имеет только одну функцию с именем main. C этой функции начинается исполнение программы. Другие функции могут быть вызваны из функции main или из любой другой функции в процессе исполнения программы. Каждая функция может иметь 0 или более параметров. Параметры являются переменными, которые используются для передачи данных между функциями. Имена параметров в вызываемой и вызывающей функциях – независимы.

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

Передача параметров

Механизм передачи параметров – это основной способ обмена информацией между функциями. Параметры функции при описании называются формальными параметрами. При вызове используются фактические параметры (аргументы), которые потом становятся на место формальных параметров.

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

Существует два способа передачи параметров в функции:

  • по значению: в стек заносятся копии значений аргументов, и операторы функции работают с этими копиями; доступа к исходным значениям параметров у функции нет, а значит, нет и возможности их изменения;

  • по адресу (с помощью указателя и по ссылке) В стек заносятся копии адресов аргументов, а функция обращается к сегменту данных по этим адресам и может изменить исходные значения аргументов, так как имеет к ним доступ.

Пример

#include<iostream.h>

int func(int i, int *j, int& k);

int main() {

int i=1, j=2, k=3;

cout<<” i j k\n”;

cout<<i<<” “<<j<<” “<<k<<endl;

func(i, &j, k);

cout<<i<<” “<<j<<” “<<k;

return 0;

}

int func(int i, int* j, int& k){

i++;

(*j)++;

k++;

}

протокол работы.

i j k

1 2 3

1 3 4

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

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

.

Имена функций – это всегда глобальные имена, видимые по умолчанию из всех файлов проекта. Однако, прототипы функций действуют только в пределах одного файла. Поэтому приходится иметь директивы препроцессора, связанные с подключаемыми .h- файлами, содержащими прототипы библиотечных функций.

Передача массивов в качестве параметров

При использовании массива в качестве параметра в функцию передается указатель на его первый элемент, то есть массив всегда передается по адресу. Но адрес указывает только на начало массива и ничего не говорит о его размере, поэтому размерность массива надо передавать через отдельный параметр:

int function(int arr[], const int n);

int function(int *arr, const int n);

Оба варианта приведут к одному и тому же результату. И в том, и в другом случае вы передаете адрес начала массива и количество его элементов.

Пример программы, работающей с функциями

Ввести с клавиатуры два целочисленных массива ‘а’ и ‘в’ и их размерности. Кол-во элементов в массивах не должно превышать 10. В каждом массиве вычислить произведение элементов, кратных 3. Первые элементы в каждом массиве заменить на полученное произведение.

#include<stdio.h> //(1)

#define N 10

int input(int a[],const int n);

int change(int a[],const int n); //(2)

int output(int a[],const int n);

int main() {

int a[N],b[N],n1,n2;

printf("input razmer a=");

scanf("%d",&n1);

printf("input razmer b=");

scanf("%d",&n2); //(3)

input(a,n1);

change(a,n1);

output(a,n1);

input(b,n2);

change(b,n2);

output(b,n2);

return 0;

}

int input(int a[],const int n) {

int i;

printf("input array:\n");

for(i=0;i<n;i++) {

printf("a[%d]=",i);

scanf("%d",&a[i]); //(4)

}

printf("\n");

return 0;

}

int change(int a[],const int n){

int i,pr=1;

for(i=0;i<n;i++)

if(a[i]%3==0) //(5)

pr*=a[i];

a[0]=pr;

}

int output(int a[],const int n) {

int i;

for(i=0;i<n;i++) //(6)

printf("a[%d]=%d\n",i,a[i]);

}

Пояснения.

(1)-директивы препроцессора для подключения заголовочного файла <stdio.h> и определение подстановки в текст программы;

(2) – прототипы функций input(),change(),output();

(3) – определение главной функции main(). Внутри функции main() расположены вызовы функций input(),change() и output() и настройка параметров этих функций сначала на работу с массивом а, затем с массивом в;

(4) – определение функции input(), которое показывает, как будет работать эта функция, когда мы ее вызовем сначала для ввода элементов массива а, затем для ввода элементов массива в;

(5) –определение функции change(), которая ищет произведение только тех элементов массивов а и в соответственно, которые нацело делятся на 3, и помещает результат этого произведения на место первого элемента массива;

(6) – определение функции output(), которая печатает новые массивы.

Выводы

1.Функция – это логически самостоятельная именованная часть программы. Использование функций упрощает структуру программы.

2. Интерфейс функции определяется ее заголовком.

3. Для вызова функции надо указать ее имя и набор фактических параметров.

4. В определении, объявлении и вызове функции типы и порядок следования параметров должны совпадать.

5. Передача параметров в функцию может выполняться по значению и по адресу.

6. Входные данные функции надо передать по значению или по константной ссылке, а результаты работычерез возвращаемое значение или при необходимости вернуть более одной величины – через параметры по ссылке или указателю.

7. Массивы всегда передаются в функцию по адресу. Количество элементов в массиве должно передаваться отдельным параметром.

8. В многофайловом проекте надо уметь разбить задачу на подзадачи и распределить функции по файлам. По сути файлы соответствуют отдельным модулям. Но в С++ нет конструкций для обозначения модулей, но есть понятие – единица трансляции. Единица трансляции представляет отдельный файл с исходным текстом на С++, который получается после обработки препроцессором. Такое разбиение на отдельные файлы требует последующей сборки. Так как в С++ отсутствуют конструкции для обозначения модулей, то нет и конструкций для их сборки в исполняемую программу. Эта задача решается средствами интегрированной среды, в которой создается проект. В составе проекта перечисляются все объектные модули. Процесс сборки полной программы из объектных модулей называется компоновкой. Программа линкования(компоновки) входит в состав системы программирования.(linker- сборщик).При компоновке в программу собираются и разрабатываемые пользователем модули(файлы) , и стандартные модули из стандартных объектных библиотек. Разделение программы на модули требует согласования определений и объявлений в разных единицах трансляции.

Контрольные вопросы.

1. Как связан принцип: “Разделяй и властвуй” с использованием функций?

2. Что требуется знать, чтобы использовать функцию? Как это можно назвать одним словом?

3.Что необходимо знать для вызова функции?

4.Как передаются одномерные массивы в функцию?

5. Какие параметры функции называются формальными, а какие – фактическими (аргументами)?

Приложение

Создание нового проекта в интегрированной среде NetBeans 6.8

Integrated development Environment( интегрированная среда разработки) – это программный продукт, который объединяет текстовый редактор, компилятор, отладчик, компоновщик. Вам предстоит работать со средой NetBeans IDE 6.8. Любую программу, даже самую простую, мы будем оформлять как отдельный проект.

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

После запуска NetBeans вы увидите перед собой экран с верхней полоской меню (Рис.П.1).

Рис. П. 1

Вызываем пункт меню File/New Project. В появившемся окне New Project выбираем Categories C/C++(подсветка слева).

Справа щелчком подсвечиваем пункт C/C++Application, переходим к следующей операции щелчком Next (Рис. П. 2).

Рис. П. 2

Далее предлагается выбрать имя проекта и его местоположение(Project Name and Location). Имя, которое вы дадите проекту, получит и папка, в которой этот проект будет храниться.

В окошечке Set as Main project не забудьте поставить птичку. И нажмите Finish(Рис. П. 3).

Рис. П. 3

Слева в окне с названием Projects появится значок с именем вашего проекта. Щелкнув по этому значку , вы увидите из каких файлов состоит ваш проект:

Header files

Resource Files

Source Files

Important Files.

Выделите Sourse Files цветом , войдите в меню File/New File и выберите С++Files и Main C++Files, нажмите кнопку Next

(Рис. П. 4).

Рис. П .4

Теперь определитесь с именем файла, расширением(cpp) и папкой, в которой будете хранить файл, и нажмите кнопку Finish (Рис. П. 5).

Рис. П. 5

Пример выполнения проекта

Возьмем пример, приведенный в разделе функций и выполним его в виде проекта.

Постановка задачи.

Ввести с клавиатуры два целочисленных массива ‘а’ и ‘в’ и их размерности. Количество элементов в массивах не должно превышать 10. В каждом массиве вычислить произведение элементов, кратных 3. Первые элементы в каждом массиве заменить на полученное произведение. Ввод элементов массивов a и b ,обработку массивов и вывод результатов оформить в виде функций. Окончательно задача должна быть выполнена в виде проекта.

В разделе исходных файлов (sourse files) будут находиться файлы с расширением .cpp. Это главный файл (main.cpp) и предположим, файл с именем func.cpp. В файле func.cpp разместим все определения функций input,change и output.

В разделе Header Files разместим файл с именем func.h. В этом файле разместим прототипы наших функций:

func.h

#ifndef_FUNC_H // “страж определен?” if !defined

// _FUNC_H , если не определен, то

// его надо определить

#define_FUNC_H //определение стража

#endif/*_FUNC_H*/ // конец #ifndef

int input(int a[],const int n);

int change(int a[],const int n);

int output(int a[],const int n);

Такая последовательность команд препроцессора называется стражем включения и предназначена для предотвращения повторного включения содержимого файла в программу (эти команды препроцессора поместит в файл сама система).

В разделе Sourse Files разместим файл с именем func.cpp. В этом файле разместим определения наших функций input(),change() и output() (все функции, кроме main()).

func.cpp

#include<stdio.h>

#include”func.h”

int input(int a[],const int n) {

int i;

printf("input array:\n");

for(i=0;i<n;i++) {

printf("a[%d]=",i);

scanf("%d",&a[i]);

}

printf("\n");

return 0;

}

int change(int a[],const int n){

int i,pr=1;

for(i=0;i<n;i++)

if(a[i]%3==0)

pr*=a[i];

a[0]=pr;

}

int output(int a[],const int n) {

int i;

for(i=0;i<n;i++) printf("a[%d]=%d\n",i,a[i]);

}

Это можно показать так:

func.cpp

#include <stdio.h>

int input(int a[],const int n) {

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

}

int change(int a[],const int n) {

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . .

}

int output(int a[],const int n) {

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

}

Функция main() тоже помещена в разделе Sourse Files, но отдельно от других наших функций.

main.cpp

#include<stdio.h>

#include”func.h”

int main() {

int a[N],b[N],n1,n2;

printf("input razmer a=");

scanf("%d",&n1);

printf("input razmer b=");

scanf("%d",&n2);

input(a,n1);

change(a,n1);

output(a,n1);

input(b,n2);

change(b,n2);

output(b,n2);

return 0;

}

Это можно показать так:

main.cpp

#include<stdio.h>

#include”func.h”

int main() {

……. . . . . . .

. . . . . . . .

return 0

}

Обратите внимание, мы подключая с помощью директивы #include свой пользовательский заголовочный файл (не стандартный) используем кавычки, а не привычные уголки(“func.h”).

В окне Projects, щелкнув по значку с именем вашего проекта и раскрыв значки Header Files и Sourse Files вы увидите как разместились ваши функции в этих разделах:

Header Files

func.h

Sourse files

main.cpp

func.cpp

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

Попробуйте самостоятельно нарисовать этапы создания исполняемой программы конкретно для этой задачи (для этого повторите материал введения и внимательно рассмотрите в качестве образца рис.1.1.)

Выполняя лабораторные и самостоятельные работы каждую свою программу вы будете представлять в виде одного отдельного проекта. Для каждого проекта будет создана отдельная папка.

Надеемся, что если в данный момент изученный вами материал все еще не кажется совсем легким, то, по крайней мере, многое стало понятным.

Желаем успехов!

112

Соседние файлы в предмете Информатика