Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЛР15-С++24-мая-2012.doc
Скачиваний:
23
Добавлен:
23.09.2019
Размер:
1.07 Mб
Скачать

1.8. Массивы и функции

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

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

Пример программы

Задан одномерный массив из N элементов. Найти значение максимального элемента массива.

Поиск максимума оформить в виде функции max:

# include <stdio.h>

# define N 3

int max(int k,int* b) //b – указательна целое,

//k – количество элементов в массиве

{

int i,m1;

m1=*b; //*b – значение 1-го эл-та массива (с индексом 0)

for(i=1;i<k;i++)

{

b++;//Переход к следующему элементу массива

if (m1<*b)m1=*b; //*b – значение текущего эл-та массива

}

return(m1);

}

int main()

{

static int A[N]={1,7,3};

printf("\n max=%d",max(N,&A[0]));

return 0;

}

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

int main()

{

static int B[3][4]={3,5,1,6,8,3,7,3,2,6,9,3};

printf("\n 1 row: max=%d",max(4,B[0]));

printf("\n 2 row: max=%d",max(4,B[1]));

printf("\n 3 row: max=%d",max(4,B[2]));

return 0;

}

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

Результат работы программы:

1 row: max=6

2 row: max=8

3 row: max=9.

Рассмотрим передачу двумерного массива в функцию. В качестве параметров функции передается адрес начала массива и количество элементов в строке массива, соответствующее объявлению массива в вызывающей функции (int A2[][N]), а также два целых числа m и n, имеющие смысл фактического количества строк и столбцов, используемых в массиве. Они не должны превышать значения количества строк и столбцов соответственно, заданные при объявлении массива.

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

#include <stdio.h>

#define N 4

#define M 3

void input_mas(int A2[][N],int m,int n);

void output_mas(int A2[][N],int m,int n);

int diagonal(int A2[][N],int m,int n);

void main()

{

int mas[M][N];

input_mas(mas,M,N);

output_mas(mas,M,N);

printf("Summa elementov diagonali = %d\n", diagonal(mas,M,N));

return 0;

}

void input_mas(int A2[][N],int m,int n)

{

int i,j;

printf("Wwedite chisla->");

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

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

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

}

void output_mas(int A2[][N],int m,int n)

{

int i,j;

printf("Wwedeno:\n");

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

{

for(j=0;j<N;j++) printf("%7d",A2[i][j]);

printf("\n");

}

}

int diagonal(int A2[][N],int m,int n)

{

int i,j,summa=0;

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

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

if (i==j) summa+=A2[i][j];

return summa;

}

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

// объявление указателя на функцию

/*тип данных*/ (* /*имя указателя*/)(/*список аргументов функции*/);

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

// pointer_onfunc.cpp: определяет точку входа для консольного приложения.

  #include "stdafx.h"

#include <iostream>

using namespace std;

int nod(int, int ); // прототип указываемой функции

int main(int argc, char* argv[])

{

    int (*ptrnod)(int, int); // объявление указателя на функцию

    ptrnod=nod; // присваиваем адрес функции указателю ptrnod 

    int a, b;

    cout << "Enter first number: ";

    cin >> a;

    cout << "Enter second number: ";

    cin >> b;

    cout << "NOD = " << ptrnod(a, b) << endl; // обращаемся к функции через указатель

    system("pause");

    return 0;

}

int nod(int number1, int number2) // рекурсивная функция нахождения наибольшего общего делителя НОД

{

    if ( number2 == 0 ) //базовое решение

        return number1;

    return nod(number2, number1 % number2); // рекурсивное решение НОД

}

Данная задача решена рекурсивно, чтоб уменьшить объём кода программы, по сравнению с итеративным решением этой же задачи. В строке 9 объявляется указатель,  которому в строке 10 присвоили адрес функции. Как мы уже говорили до этого, адресом функции является просто её имя. То есть данный указатель теперь указывает на функцию nod(). При объявлении указателя на функцию ни в коем случае не забываем о скобочках, в которые заключаются символ указателя и его имя. При объявлении указателя в аргументах указываем то же самое, что и в прототипе указываемой функции. Результат работы программы (рис. 15. 5).

Рис. 15.5. Указатели в С++

 Вводим первое число, затем второе и программа выдает НОД. На рисунке 5 видно, что НОД для чисел 16 и 20 равен четырём.

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

Пример 15.4

/*обмен a и b */

obmen(int *x,int *y)

{

int t;

t=*x;

*x=*y;

*y=t;

}

#include <stdio.h>

main()

{

int a,b;

a=3;b=7;

obmen(a,b);

printf("a=%d b=%d",a,b);

}

В определении функции формальные параметры char s[] и char *s совершенно идеитичны. Операция s++ (пример 5.5) увеличение на единицу текущее значение указателя, первоначально указывающее на первый символ строки, а операция *s!='\0' сравнивает очередной символс признаком конца строки.

 Пример 15.5

/*длина строки*/

length(s)

char *s;

{

int i;

for(i=0; *s!='\0';s++)

i+++;

return(i);

}

  Кроме ранее расмотренных операций адресной арифметики, к указателям можно применить операции сравнения == и !=. Даже операции отношения = <,>= и т.п. работают правильно, если указатели ссылаются на элементы одного и того же  массива. В последнем случае возможно даже вычитание ссылок: если u и s ссылаются на элементы одного массива, то u-s есть число элементов между u и s. Используем этот факт для составления еще одной версии функции length (пример 15.6). Cначала u указывает на первый символ строки (char *u =s). Затем в цикле по очереди проверяется каждый символ, пока в конце концов не будет обнаружен "\0". Разность u-s дает как раз длину строки.

Пример 15.6

/*длина строки*/

length(s)

char *s;

{

char *u=s;

while(*u!='\0')

u++;

return(u-s);

}

Для илюстрации основных аспектов применения указателей в СИ рассмотрим функцию копирования строки s1 в строку s2. Сначала приведем версию, основанную на работе с массивами (пример 15.7). Для сравнения рядом помещена версия с использованием указателей (пример 15.8).

Пример 15.7

/*копия строки*/

copy(s1,s2)

char s1[],s2[];

{

int i=0;

while((s2[i]=s1[i])!='\0')

i++;

}

Пример 15.8

/*копия строки*/

copy(s1,s2)

char *s1,*s2;

{

while((*s2=*s1)!='\0')

{

s2++;s1++;

}

}

Здесь операция копирования помещена непосредственно в условие, определяющее момент цикла: while((*s2=*s1)!='\0'). Продвижение вдоль массивов вплоть до тех пор, пока не встретится "\0", обеспечивают операторы s2++ и s1++. Их, однако, тоже можно поместить в проверку (пример 15.9).

Пример 15.9

 /*копия строки*/

copy(s1,s2)

char *s1,*s2;

{

while((*s2++=*s1++)!='\0');

}

Еще раз напомним, что унарные операции типа * и ++ выполняются справа налево. Значение *s++ cесть символ, на который указывает s до его увеличения. Так как значение "\0" есть нуль, а цикл while проверет, не нуль ли выражение в скобках, то это позволяет опустить явное сравнение с нулем(пример 15.10) . Так постепенно функция копирования становится все более компактной и ... все менее понятной. Но  в системном программировании предпостение чаще отдают именно компактным и, следовательно, более эффективным по быстродействиб программам.

Пример 15.10

/*копия строки*/

copy(s1,s2)

char *s1,*s2;

{

while(*s2++=*s1++);

}

   В языке Си, что некоторая литерная строка, выраженная как "строка" , фактически рассматривается как указатель на нулевой элемент массива " строка". Допускается, например, такая интересная запись:

            char *uk; uk="ИНФОРМАТИКА";

   Последний оператор присваивает указателю адрес нулевого элемента строки, т.е. символа "И". Возникает вопрос, где находится массив, содержащий символы "ИНФОРМАТИКА"? Мы его нигде не описывали. Ответ такой: эта строка - константа; она является частью функции, в которой встречается, точно также, как целая константа 4 или символьная константа "А" в операторах i=4; c="A";. Более детально пояснит сказанное программа на пример 15.11, которая печатает строку символов в обратном порядке.

       Пример 15.11

#include <stdio.h>

main()

{

char *uk1,*uk2;

uk1=uk2="ИНФОРМАТИКА";

while(*uk2!='\0')

  putchar(*uk2++);

putchar('\n');

while(--uk2 >= uk1)

putchar(*uk2);

putchar('\n');

}

В самом начале указателям uk1 и uk2 присваивается начальный адресс строки "ИНФОРМАТИКА". Затем строка посимвольно печатается и одновременно указатель uk2 смещается вдоль строки. В конце вывода uk2 указывает на последний символ исходной строки. Во втором цикле while все тот же указатель uk2 начинает изменяться в обратном направлении, уменьнаясь до тех пор, пока он не будет указывать на нулевой элемент массива, обеспечивая выдачу строки в обратном порядке.