Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Практикум по программированию на языке Си

..pdf
Скачиваний:
24
Добавлен:
12.11.2023
Размер:
3.53 Mб
Скачать

/* 07_09.c - доступ разыменованием к элементам двумерного массива */

#include <stdio.h>

#include <stdlib.h> /* для RAND_MAX и rand() */ #define N 6

#define M 5

int matr[N][M]; int main ()

{

int i, j; int n, m;

printf("n="); scanf("%d",&n); printf("m="); scanf("%d",&m); if (m>M || n>N || m<0 || n<0)

{ printf("Error! (m>M || n>N || m<0 || n<0)" "\nm=%d, M=%d, n=%d, N=%d,",m,M,n,N);

return 0;

}

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

{ *(*(matr+i)+j)= (int)(198*(double)rand()/RAND_MAX)-99; printf("\n%d",*(*(matr+i)+j));

}

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

{ printf("%c [%d][%d]=%3d",

j == 0 ?'\n':' ',i,j,*(*(matr+i)+j));

}

return 0;

}

Результаты выполнения программы:

n=3<ENTER>

m=4<ENTER>

-75 -80 -26 -60 -95 66

231

43

16

15 -58 70 -3

[0][0]=-75 [0][1]=-80 [0][2]=-26 [0][3]=-60 [0][4]=0 [1][0]=-95 [1][1]= 66 [1][2]= 43 [1][3]= 16 [1][4]=0 [2][0]= 15 [2][1]=-58 [2][2]= 70 [2][3]= -3 [2][4]=0 [3][0]= 0 [3][1]= 0 [3][2]= 0 [3][3]= 0 [3][4]=0 [4][0]= 0 [4][1]= 0 [4][2]= 0 [4][3]= 0 [4][4]=0 [5][0]= 0 [5][1]= 0 [5][2]= 0 [5][3]= 0 [5][4]=0

Для компактности результатов элементы массива обозначены при печати только их индексами. Матрица с размерами 3 на 4 занимает в массиве строки с номерами 0, 1, 2 и столбцы с 0-го по 3-й.

Для обращения к элементу массива из i-й строки и j-го столбца используется выражение

*(*(matr+i)+j),

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

ЭКСПЕРИМЕНТ. В цикле формирования значений элементов матрицы (программа 07_09.с) используйте такое (синтаксически ошибочное) обращение к элементам массива:

*(matr+i+j).

Сообщения компилятора:

07_09_1.c: In function `main':

07_09_1.c:20: incompatible types in assignment of 'int' to 'int[5]'

ЭКСПЕРИМЕНТ. Исправьте в выражении *(matr+i+j) синтаксическую ошибку, используя такое (неверное семантически) выражение:

**(matr+i+j).

Результаты выполнения программы 07_09_2.с:

232

n=3<ENTER>

m=4<ENTER>

-75

 

 

 

 

 

 

-80

 

 

 

 

 

 

-26

 

 

 

 

 

 

-60

 

 

 

 

 

 

-95

 

 

 

 

 

 

66

 

 

 

 

 

 

43

 

 

 

 

 

 

16

 

 

 

 

 

 

15

 

 

 

 

 

 

-58

 

 

 

 

 

 

70

 

 

 

 

 

 

-3

0

[0][2]=

0

[0][3]=

0

[0][4]=0

[0][0]=-75 [0][1]=

[1][0]=-95 [1][1]=

0

[1][2]=

0

[1][3]=

0

[1][4]=0

[2][0]= 15 [2][1]=

0

[2][2]=

0

[2][3]=

0

[2][4]=0

[3][0]=-58 [3][1]=

0

[3][2]=

0

[3][3]=

0

[3][4]=0

[4][0]= 70 [4][1]=

0

[4][2]=

0

[4][3]=

0

[4][4]=0

[5][0]= -3 [5][1]=

0

[5][2]=

0

[5][3]=

0

[5][4]=0

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

Планируемый вариант (реализованный в 07_09.с):

[0][0] [1][0] [2][0] [2][1] [2][2] [2][3]

Результат из 07_09_2.с (при неверном разыменовании):

[0][0] [1][0] [2][0] [3][0] [4][0] [5][0]

Объяснение простое – двумерный массив хранится в памяти “по строкам”. Прибавление 1 к имени двумерного массива соответствует перемещению на длину строки. Сумма индексов i+j воспринимается как одно значение. Поэтому для (i=0, j=0) значение –75 присваивается элементу с индексом [0][0]. Для (i=0, j=1) и (i=1, j=0) элементу [0][1] последовательно присваиваются значения

–80 и −95 (остается −95). Для (i=0, j=2), (i=1, j=1), (i=2, j=0) эле-

менту последовательно присваиваются значения –26, 66, 15 и т.д.

233

ЭКСПЕРИМЕНТ. Перенесите в программе 07_09.с определение массива int matr[N][M]; внутрь тела функции main(). Откомпилируйте и выполните программу для тех же исходных данных (n=3, m=4). Объясните результаты.

Массив перемещен из статической памяти в автоматически распределяемую память блока. Изменяются правила инициализации. Вместо нулевых значения неиспользованных элементов (например, с индексами [0][4]) станут непредсказуемыми (см. программу 07_09_3.с). Следует отметить, что непредсказуемость может означать и равенство нулю, но это не обязательно для объектов автоматической памяти.

ЭКСПЕРИМЕНТ. При заполнении матрицы в программе 07_09.с используйте такое “комбинированное” выражение для обращения к элементам массива:

*(matr[i]+j).

Правильный результат приведен в программе 07_09_4.с, он совпадает с результатом программы 07_09.с.

ЗАДАЧА 07-10. Сформируйте матрицу с целочисленными элементами в двумерном массиве, как это сделано в предыдущей задаче. Определите адреса и индексы ее минимального и максимального элементов. Отделите определение значений элементов матрицы от решения основной задачи поиска в матрице минимального и максимального элементов.

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

// 07_10A.c - генерация значений элементов матрицы

#include <stdio.h>

#include <stdlib.h>/*для srand(),RAND_MAX и rand()*/ #define START 9234

int main ()

{

int i, j, memb;

234

int n, m;

printf("n="); scanf("%d",&n); printf("n=%d",n); printf("\nm="); scanf("%d",&m); printf("m=%d",m);

if (m<0 || n<0)

{ printf("Error! \nm=%d, n=%d",m,n); return 0;

}

srand(START); // настройка для rand() for (i=0; i<n; i++)

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

{ memb=(int)(198*(double)rand()/RAND_MAX) - 99; printf("%c [%d][%d]=%3d",

j == 0 ?'\n':' ',i,j,memb);

}

return 0;

}

Результаты выполнения программы:

n=3<ENTER>

n=3

m=4<ENTER>

m=4

[0][0]= 19 [0][1]=-81 [0][2]=-29 [0][3]= 50 [1][0]=-64 [1][1]= 40 [1][2]= 41 [1][3]= 27 [2][0]=-44 [2][1]= 87 [2][2]= 81 [2][3]= 58

В программе не потребовалось использовать массивы и индексацию. Пользователь вводит количество строк (n), число столбцов (m). Во вложенных циклах формируются и печатаются случайные значения элементов матрицы (переменная memb). Диапазон значений от –99 до 99. Используя препроцессорную константу START, библиотечная функция srand() выполняет “настройку” функции rand(), применяемой для вычисления псевдослучайных значений элементов матрицы. Для наглядности матрица печатается по строкам, и кроме значений элементов указаны их индексы (номер строки, номер столбца в двумерном массиве).

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

c:\…\test.exe>07_10.txt

235

После исполнения будет создан текстовый файл 07_10.txt, например, с такими результатами:

n=n=3

m=m=4

[0][0]= 19 [0][1]=-81 [0][2]=-29 [0][3]= 50 [1][0]=-64 [1][1]= 40 [1][2]= 41 [1][3]= 27 [2][0]=-44 [2][1]= 87 [2][2]= 81 [2][3]= 58

Для решения основной задачи поиска необходимо по данным из файла создать матрицу. Для этого придется аккуратно выбирать из полученного файла только те данные, которые нужны для формирования целочисленной матрицы с размерами n на m. Все сведется к указанию в форматной строке функции scanf() количества символов, которые нужно игнорировать при вводе из файла 07_10.txt. Для чтения значений размеров матрицы нужно пропустить первые 4 символа, например, так:

scanf(“%*4s%d”,&n);

При чтении значения очередного элемента матрицы следует игнорировать 7 знаков (отличных от пробелов), т.е. использовать форматную строку вида “%*7s%d”. В качестве второго аргумента в scanf() используем (*(matr+i)+j).

Для определения адреса минимального элемента введем указатель int *addressMin и инициализируем его значением адреса начального элемента массива &matr[0][0]. Для определения индексов по найденному адресу минимального элемента нужно вспомнить, что двумерный массив с размерами N (число строк) на M (число столбцов) размещается в памяти ЭВМ по строкам (рис. 7.1). Разность адресов addressMin-&matr[0][0] равна порядковому номеру минимального элемента матрицы в ее представлении в виде одномерного массива. Учитывая округление при делении целочисленных операндов, легко вычислить номера строки и столбца:

i=(addressMin-&matr[0][0])/M; j=addressMin-&matr[0][0]-i*M.

236

Указанные решения реализует следующая программа, в которой: размеры массива заданы препроцессорными константами; размеры матрицы и значения ее элементов считываются из текстового файла, подготовленного программой генерации 07_10A.с.

Рис. 7.1. Двумерный массив со строками из М элементов

/* 07_10B.c - найти минимальный элемент матрицы */ #include <stdio.h>

#define N 6 #define M 5

int matr[N][M]; int main ()

{

int i, j; int n, m;

int * addressMin=&matr[0][0];

scanf("%*4s%d",&n);

printf("n=%d\n",n);

scanf("%*4s%d",&m);

printf("m=%d",m);

if (m>M || n>N || m<0

|| n<0)

{ printf("Error! (m>M || n>N || m<0 || n<0)" "\nm=%d, M=%d, n=%d, N=%d,",m,M,n,N);

return 0;

}

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

scanf("%*7s%d",(*(matr+i)+j)); for (i=0; i<N; i++)

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

{ printf("%c [%d][%d]=%3d",

j == 0 ?'\n':' ',i,j,*(*(matr+i)+j)); if ((i<n && j<m) && matr[i][j]<*addressMin)

addressMin=&matr[i][j];

}

printf("\nmin=%d",*(addressMin));

237

printf("\nrowMin=%d",(addressMin-&matr[0][0])/M); printf("\ncolumnMin=%d",(addressMin-&matr[0][0])- ((addressMin-&matr[0][0])/M)*M);

return 0;

}

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

C:\…>test.exe < 07_10.txt

Результаты выполнения программы:

n=3

m=4

[0][0]= 19 [0][1]=-81 [0][2]=-29 [0][3]= 50 [0][4]=0 [1][0]=-64 [1][1]= 40 [1][2]= 41 [1][3]= 27 [1][4]=0 [2][0]=-44 [2][1]= 87 [2][2]= 81 [2][3]= 58 [2][4]=0 [3][0]= 0 [3][1]= 0 [3][2]= 0 [3][3]= 0 [3][4]=0 [4][0]= 0 [4][1]= 0 [4][2]= 0 [4][3]= 0 [4][4]=0 [5][0]= 0 [5][1]= 0 [5][2]= 0 [5][3]= 0 [5][4]=0 min=-81

rowMin=0

columnMin=1

Программа 07_10B.с будет неверно считывать данные из файла, если при его формировании размеры матрицы хотя бы по одному измерению превысят значение 9. (Соответствующее увеличение значений N и M необходимо, но не поможет!)

ЗАДАНИЕ. Устраните это ограничение.

Например, введите проверку индексов при вводе элементов матрицы. Если i>9 или j>9 – используйте вызов функции scanf() с другой форматной строкой.

7.4.Массивы указателей

Вряде случаев применение массивов указателей существенно упрощает решение задач. В качестве примера рассмотрим внешне очень несложную задачу упорядочения объектов (например, пере-

238

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

ЗАДАЧА 07-11. Определите и инициализируйте произвольными значениями несколько разноименных вещественных переменных. Напечатайте их значения в порядке убывания.

/* 07_11.c - массив указателей. Упорядочение объектов */

#include <stdio.h> int main ()

{

double a=12.0, b=34.1, c=11.9, d=92.4, e=55.5, f=16.5, g=55.5, h=77.2;

double * pArray[] = {&a,&b,&c,&d,&e,&f,&g,&h}; double * pTemp;

int size, i, j; size=sizeof(pArray)/sizeof(*pArray); for (i=0; i<size-1; i++)

for (j=i+1; j<size; j++)

if (*pArray[i] < *pArray[j])

{pTemp=pArray[i]; pArray[i] = pArray[j]; pArray[j] = pTemp;

}

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

printf("%c[%d]=%6.2f",i%4?'\t':'\n',i,*pArray[i]); return 0;

}

Результаты выполнения программы:

[0]=

92.40

[1]=

77.20

[2]=

55.50

[3]=

55.50

[4]=

34.10

[5]=

16.50

[6]=

12.00

[7]=

11.90

 

 

 

 

 

 

 

239

В программе массив указателей double * pArray[] инициализирован адресами переменных. Далее стандартная простейшая схема упорядочения. Во вложенных циклах выполняется сравнение тех объектов (переменных), которые адресованы указателями из массива. При необходимости меняются адреса (значения элементов массива), но сами переменные остаются без изменений. Тем самым формируется массив указателей, элементы которого адресуют переменные в нужном порядке. Для обмена значений элементов массива указателей определен вспомогательный указатель pTemp того же типа. В следующем цикле последовательно выбираются элементы массива указателей и с помощью их разыменования выводятся значения переменных. Рис. 7.2 иллюстрирует общую схему решения задачи:

Рис. 7.2. Схема косвенного упорядочения переменных

В рассмотренной задаче массив указателей позволил выполнить сортировку разноименных объектов (переменных). Более точно – массив указателей позволил обращаться к разноименным объектам в соответствии с конкретным правилом упорядочения их значений. Сами объекты (их значения) остались без изменений.

240