Передача матрицы в функцию
Один из способов передачи матрицы в функцию был рассмотрен в первом семестре. Его недостаток в том, что в прототипе функции для матрицы необходимо было обязательно указывать вторую размерность матрицы, то есть количество элементов в строках. Здесь предлагается более профессиональный способ, основанный на понятиях указатель на указатель и динамические “матрицы”.
Вспомним, что обычный одномерный, например, числовой массив в качестве параметра функции можно передать двумя способами:
void FunArr1 (int *z, int , … );
или
void FunArr1 (int z[], int,… );
но в любом из вариантов мы передаём адрес начала массива.
Аналогично матрица как параметр функции объявляется в прототипе, например, одним из следующих способов:
void FunMatr (int **X, int , int );
или
void FunMatr (int *X[], int , int );
То есть если сравнивать с одномерным массивом, то просто вместо типа int, что означает числовой целочисленный массив, записали тип int *, что означает, что в функцию передаём адрес начала массива указателей. Как правило, в качестве параметров включаются и две размерности матрицы.
Для лучшего понимания необходимо сделать следующее замечание. Как и в случае одномерного массива (FunArr), из такого прототипа ещё не следует, что мы обязательно в качестве фактического параметра должны передавать матрицу. Можно, например, передать указатель на указатель на целое число.
Для передачи матрицы в вызывающей функции создаём полностью динамическую или частично динамическую “матрицу”, а при вызове функции записываем её имя (см. дальше пример). Если продолжить аналогию с обычным одномерным числовым массивом, то в качестве первого параметра функции FunArr1 можно передать как статический массив:
int A[10]; FunArr1(A, 10,…);
так и динамический:
int *d; int n; cin>>n; d= new int[n]; FunArr1(d, n, …);
В случае с матрицей это не совсем так. Пусть объявлена статическая матрица
const Nc=5, Mc=10; int C[Nc][Mc];
Передать её в функцию нельзя, то есть вызов
FunMatr(C, Nc, Mc);
приведёт к ошибке компиляции.
Пример. (+).
Передача матрицы в функцию с помощью указателей. Кроме этого здесь дополнительно показано, как передать формат стандартной функции вывода printf в качестве параметра функции.
void MyFun1 (int **X, int , int ); // или void MyFun1 (int *X[],int , int );
void MyFun2 (int *X[],int , int ); //или void MyFun2 (int **X,int , int );
void MyPrint (int **X, int , int, int, char* );
int main()
{
/* Создание “полностью” динамической матрицы, которая объявляется как динамический массив из N указателей. Обе размерности – переменные.*/
int ** A; int N=3, M=4;
A=new int *[N];
for (int i=0; i<N; i++) A[i]=new int [M];
/* В функцию передаём полностью динамическую матрицу. */
MyFun1(A,N,M);
/* Сначала выводим указанный текст, а затем матрицу A размерности N*M по формату “%5d” */
MyPrint(A,N,M,5,"\n The first test\n");
//Удаляем матрицу
for (int i=0;i<N;i++) delete[]A[i];
delete[]A;
/* Создание “частично” динамической матрицы, которая объявляется как статический массив указателей фиксированной размерности Nconst. Другими словами, количество “строк” фиксировано (Nconst), а количество элементов в строке произвольное: M2 – не константа, а переменная (см. 5.3) */
const Nconst=8; int M2=6;
int *B[Nconst];
/* Для такой полудинамической матрицы B=new int *[Nconst]; не надо. Будет ошибка!!!!!!! */
for (int i=0; i<Nconst; i++)
B[i]=new int [M2];
/* Передаём полудинамическую матрицу */
MyFun2(B,Nconst,M2);
MyPrint(B,Nconst,M2,3,"\n \nThe second test\n");
//Удаляем матрицу B
for (int i=0;i<Nconst;i++)
delete[]B[i];
getch(); return 0;
}
void MyFun1 (int **X, int n, int m)
// или void MyFun1 (int *X[], int n, int m)
{
for (int i=0; i<n; i++)
for (int j=0;j<m;j++)
X[i][j]=(i+1)*(j+1);
// Перестановка 0-й и (n-1)-й строк
int* t;
t=X[0]; X[0]=X[n-1]; X[n-1]=t;
}
void MyFun2 (int *X[], int n, int m)
// или void MyFun2 (int **X, int n, int m)
/* Определяем элементы матрицы. */
{
for (int i=0; i<n; i++)
for (int j=0;j<m;j++)
if (i%2) X[i][j]=(i+1)-(j+1);
else X[i][j]=(i+1)+(j+1);
}
void MyPrint (int **X, int n , int m, int L, char* t )
{
// Формируем формат “%Ld”, где L – переданное в функцию целое число…
char AnyFormat[10],
StrL[3];
/* … для хранения переведенного в строку c помощью встроенной функции itoa целого числа L */
strcpy(AnyFormat, "%");
strcat(AnyFormat,itoa(L,StrL,10));
strcat(AnyFormat,"d");
puts(t);
for (int i=0; i<n; i++)
{
for (int j=0;j<m;j++)
printf(AnyFormat,X[i][j]);
cout<<endl;
}
}
§5. Динамический массив строк (+).
По аналогии с динамической матрицей можно создать динамический массив строк. Для этого объявляем динамический массив указателей как указатель на указатель символа:
char **s;
Объявляем и определяем количество строк:
int n; cin>>n;
Аналогично резервируем память для массива указателей на строки
s=new char*[n];
В цикле резервируем память для каждой строки. Размер её определяем с помощью вспомогательной строки t, длина которой, а, значит и длина каждой строки, не более 40 символов:
char *t; t=new char[40];
for(int i=0; i<n; i++)
{ gets(t);
s[i]=new char[strlen(t)];
strcpy(s[i], t);
}
Работу с таким массивом рассмотрим на примере сортировки массива строк.
for(int j=0; j<n; j++)
for(int i=0; i<n-1; i++)
if (s[i][0]>s[i+1][0]) //Сравнение первых символов строк
//if (strcmp(s[i],s[i+1])>0) // или сравнение двух строк
{ // Перестановка двух строк
strcpy(t,s[i]); // копирование s[i] в t
strcpy(s[i],s[i+1]);
strcpy(s[i+1],t);
}
for(int i=0;i<n;i++) puts(s[i]); // Вывод строк
//Освобождение памяти особенностей не имеет.
for (int i=0; i<n;i++)
delete []s[i]; // Удаление каждой строки из памяти
delete []s; // Удаление массива указателей из памяти
delete []t; // Удаление вспомогательной строки из памяти
В качестве повторения заметим, что два варианта сравнения
if (s[i][0]>s[i+1][0]) …
и
if(strcmp(s[i],s[i+1])>0) …
выполняются по-разному. В первом случае сравниваются только первые символы соседних в массиве строк и независимо от результата следующие символы в сравнении не участвуют. С помощью функции strcmp выполняется сравнение так, как было описано в теме “строки” (см. §5 главы 2).
Упражнения, тесты.
1. Дан код:
const n=5; int * ap[n];
int v; ap[1]=&v; //1
ap[2]= v; //2
int * p; p= new int; ap[3]=*p; //3
int *q; q=new int[10]; ap[9]=q; //4
ap[4]=new int; //5
ap[4]=new int[6]; //6
unsigned m=10; ap[7]=new int[m]; //7
const k=10; int a[k]; ap[8]=a; //8
В каких строках (//1—//8) нет ошибок? Что означают правильные операторы? Объяснить ошибки.
2. Дан код:
const п=5, m=10; int M [n][m], *p, *ap[n];
int k1=0, k2=n-1;
p=M[k1]; //1
M[k1]=M[k2]; //2
M[k2]=p; //3
for (int i=0; i<n; i++) ap[i]=M[i]; //4
int i1=0, i2=m-1;
p=ap[i1]; //5
ap[i1]=ap[i2]; //6
ap[i2]=p; //7
В каких строках (//1—//7) есть ошибки? Объяснить их.
3. Как будет работать следующий фрагмент программы
void MyOut(int *u, int size)
{ cout<<endl;
for (int i=0; i<size;i++) cout<<u[i]<<" ";
}
int main()
{ const n=5;
int ar1[n]={10, -2, 33, 4},
ar2[n]={111, -222, 333, 0, 555};
int *t, *t1, *t2; t1=ar1; t2=ar2;
t=t1; t1=t2; t2=t;
MyOut(ar1,n); MyOut(ar2,n);
MyOut(t1,n); MyOut(t2,n);
return 0; getch();
}
Зачем объявили и использовали указатели t1 и t2? Почему не использовали операции new и delete для t, t1 и t2?
4. Что означает цикл for (int I=0; w[I]; I++); если 1) w – числовой одномерный массив, 2) w -- строка; 3) w – массив указателей.
5. Дана программа.
void MyFun1 (float **X, int , int );
void MyOut (float *X[], int, int);
int main()
{ int ** A; int N=6, M=4; A=new float [M];
for (int i=0; i<N; i++) A[i]=new float* [N];
MyFun1(A,N,M); MyOut(A,N,M);
delete[]A; for (int i=0;i<N;i++) delete[]A[i];
getch(); return 0; }
void MyFun1 (float *X[], int n, int m)
// В динамической “матрице” размерности n* m надо поменять местами первую по порядку и последнюю строки, не перемещая их физически в памяти.
{ for (int i=0; i<n; i++)
for (int j=0;j<m;j++)
X[i][j]=(i+1)*(j+1);
int t; t=X[0]; X[0]=X[n-1]; X[n-1]=t;
}
void MyOut (float **X, int n, int m)
// Вывод динамической “матрицы” размерности n* m
{ for (int i=0; i<n; i++)
{ cout<<endl;
for (int j=0;j<m;j++) printf("%7.2f", X[i][j]);
} }
Найти ошибки, объяснить их и исправить.
7. Почему не работает следующий фрагмент программы?
const n=5; int ar1[n]={10, -2, 33, 4},
ar2[n]={111, -222, 333, 0, 555};
int *t; t=ar1; ar1=ar2; ar2=t;
Как её изменить, чтобы “переставить” местами два массива, не перемещая их элементы “физически” в памяти ? Как это сделать для динамических массивов?
8. Дан код
const n=3; char *S[n],
*t=new char[10]; //1
for(int i=0;i<n;i++)
{ gets(t); S[i]=t; //2
}
for(int i=0;i<n;i++) printf(“%s ”, S[i]);
Что будет выведено, если последовательно введём:
Иванов
Петров
Сидоров?
Варианты ответов:
1) Иванов Петров Сидоров
2) Сидоров Петров Иванов
3) Иванов Иванов Иванов
4) Сидоров Сидоров Сидоров
5) Ошибка в //1
6) Ошибка в //2: нет new для S[i].
9. Дан заголовок функции:
void FunMatr (int **X, int , int );
Какой из следующих вызовов правильный?
/*1*/ const Nc=5, Mc=10; int C[Nc][Mc]; FunMatr(C, Nc, Mc);
/*2*/ int ** A; int N=6, M=4; A=new float [N];
for (int i=0; i<N; i++) A[i]=new float* [M];
FunMatr(C, Nc, Mc);
Варианты ответов:
1) только /*1*/ ; 2) только /*2*/ ; 3) /*1*/ и /*2*/ ; 4) нет правильного.
10. Дан заголовок функции:
void FunArr1 (int *z, int );
Какой из следующих вызовов правильный?
int A[10]; FunArr1(A, 10); //1
int *d; int n; cin>>n; d= new int[n]; FunArr1(d, n); //2
Варианты ответов:
1) только //1; 2) только //2; 3) //1 и //2; 4) нет правильного.
11. Пусть объявлены следующие переменные: int x=10, *p, **q;
Какие из следующих операторов синтаксически правильные?
1) p=x; 2) p=&x; 3) q=p; 4) q=&p; 5) q=&(&p); 6) q=&x; 7) q=&(&x);
12. Пусть объявлена переменная int **u2;
Какие из следующих последовательностей операторов синтаксически правильные?
1) u2=new *int; *u2=new int; cin>>(**u2);
2) u2=new *int; *u2=new int; cout>>(**u2);
3) u2=new *int; cin>>*u2;
4) u2=new *int; cout>>*u2;
5) u2=new *int; *u2=new int; cout>>(**u2);
13. Как можно объявить D, чтобы D =new float *[n]; компилировалось и выполнялось? Выбери правильные варианты из предложенных:
1) const n=5; float *D;
2) const n=5; float **D;
3) int n; cin>>n; float *D;
4) int n; cin>>n; float **D;
5) int n; cin>>n; float f; float &D=f;
6) int n; cin>>n; float f; float &&D=f;
14. Что надо записать, чтобы было достаточно для того, чтобы
for (int i=0; i<n1; i++) D[i]=new int [n2];
компилировался и выполнялся? Выбери правильные варианты из предложенных:
1) const n1=5, n2=4; int *D[n1];
2) int n1=5; const n2=4; int *D[n1];
3) const n1=5; int n2=4; int *D[n1];
4) int n1=5, n2=4; int *D[n1];
5) const n1=5; int n2=4; int **D;
6) const n1=5; int n2=4; int **D; D=new int [n1];
7) const n1=5; const n2=4; int **D; D=new int [n2];
15. Пусть const n=5, m=6; int A[n][m]; В каких строках есть ошибки?
1) for (int i=0; i<n; i++) delete [] A[i]; delete [] A;
2) delete [] A; for (int i=0; i<n; i++) delete [] A[i];
3) for (int i=0; i<n; i++) delete [m] A[i]; delete [n] A;
4) for (int i=0; i<n; i++) for (int j=0; j<m; j++) delete A[i][j];
5) for (int i=0; i<n; i++) delete [] A[i]; delete A;
16. Дан код:
const n=5; char t[20]; char **S; S=new *char [n];
for (int i=0; i<n; i++)
{gets(t); S[i]=new char [strlen(t)];
//оператор1
… }
Какие из следующих операторов на месте оператора “оператор1” формально синтаксически правильные, хотя выполняют не обязательно одно и тоже и могут не иметь практического значения? Выбери правильные варианты из предложенных:
1) strcpy(S[i],t); 2) strcpy(S[i],t[i]); 3) strcpy(S, t); 4) S[i]=t;
5) t=S[i]; 6) S[i]=t[i]; 7) S[i][0]=t[0];
8) for (int j=0; j<strlen(t); j++) S[i][j]=t[j];
9) for (int j=0; j<=20; j++) S[i][j]=t[j];
17. Пусть const n=5, m=6; int A[n][m]; В каких строках есть ошибки?
1) int S[n]; for(int i=0; i<n; i++) S[i]=A[i];
2) int *S[n]; for(int i=0; i<n; i++) { S[i]=new int [m]; A[i]=S[i]; }
3) int **S; S=new *int [n]; for(int i=0; i<n; i++) S[i]=A[i];
4) int *S; S= A[0]; A[0]=A[n-1]; A[n-1]=S;
5) int S; S= A[0]; A[0]=A[n-1]; A[n-1]=S;
6) int S;for(int j=0; j<m; j++) {S=A[0][j]; A[0][j]=A[n-1][j];A[n-1][j]=S; }
18. Дан код.
const n=5; int * M[n]; //1
int arr_of_size[n];
for (int i=0; i<n; i++) cin>>arr_of_size[i];
for (int i=0; i<n; i++)
M[i]= new int[arr_of_size[i]]; //2
for (int i=0; i<n; i++)
for (int j=0; j<n; j++)
M [i][j]=i*j; //3
При каких значениях введённых элементов массива arr_of_size будет правильно работать приведённый фрагмент программы?
Варианты ответов:
1) При любых целых положительных значениях.
2) При любых значениях.
3) Если введём пять значений, каждое из которых равно 5.
4) Ошибка компиляции строки //1, так как n не должна быть константой.
5) Ошибка компиляции строки //2, так как в new нельзя использовать элемент массива.
6) Ошибка компиляции строки //3, так как для M нельзя использовать два индекса, это одномерный массив.