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

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

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

int main ()

{

double d=0.1234;

char * pc = (char *)&d; int * pi = (int *)&d; double * pd = &d;

void * pv = &d; PRINTC(*pc); PRINTI(*pi); PRINTD(*pd); PRINTC(*(char *)pv); PRINTI(*(int *)pv); PRINTD(*(double *)pv); return 0;

}

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

*pc=є *pi=1951633139 *pd=0.123400 *(char *)pv=є

*(int *)pv=1951633139 *(double *)pv=0.123400

Как показывают результаты выполнения программы 07_04.с, указатели pc, pi, pd, pv имеют одинаковое значение. Все они адресуют один объект (переменную d). Но при их разыменовании в программе 07_04_1.с становятся доступными участки памяти разной длины. Только разыменование указателя pd и выражение *(double *)pv позволили получить правильный доступ к значению переменной d.

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

ЗАДАЧА 07-05. Определите переменную типа int и указатель типа int *, инициализированный адресом этой переменной. Продемонстрируйте разные способы доступа к участку памяти, вы-

221

деленному для переменной. (Используйте ее имя и выражения с операциями [ ], *, &.)

Следующая программа предлагает решение задачи.

/* 07_05.c - указатели, адреса, индексация, разыменование */

#include <stdio.h>

#define PRINTI(EXPRESSION) \ printf(#EXPRESSION"=%d\n",EXPRESSION)

int main ()

{

int number=123;

int * pi = &number; PRINTI(number); PRINTI(*&number); PRINTI((&number)[0]); PRINTI(0[&number]); pi[0] = 456; PRINTI(number); PRINTI(*pi); PRINTI(pi[0]); PRINTI(0[pi]);

return 0;

}

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

number=123

*&number=123

(&number)[0]=123

0[&number]=123

number=456

*pi=456

pi[0]=456

0[pi]=456

Из приведенных результатов наглядно видно соответствие между адресом переменной &number и указателем pi, "настроенным" на нее. Разыменование (операция *) и индексация (операция []) с нулевым значением индекса одинаково действуют как на фиксированный адрес &number, так и на указатель pi. Внешне экзотическими, но

222

верными являются выражения 0[&number], соответствующее ему

(&number)[0], и 0[pi], эквивалентное pi[0].

ЭКСПЕРИМЕНТ. В программе 07_05.с удалите круглые скобки из выражения (&number)[0]. Выполните трансляцию и объясните результат.

Компилятор выдаст сообщение об ошибке, так как в соответствии с приоритетами операций будет сделана попытка применить операцию [] к имени number, которое не является адресом и не является указателем, например number – не имя массива.

ЗАДАНИЕ. Повторите решение предыдущей задачи, настроив на целочисленную переменную number указатель типа void *.

Программа, решающая задачу:

/* 07_05_1.c - указатели void * и индексация */ #include <stdio.h>

#define PRINTI(EXPRESSION) \ printf(#EXPRESSION"=%d\n",EXPRESSION)

int main ()

{

int number=123;

void * pv = &number; PRINTI(number); PRINTI(*(int *)pv); ((int *)pv)[0]=789; PRINTI(number); 0[(int *)pv]=555; PRINTI(number);

PRINTI(((int *)pv)[0]); PRINTI(number); PRINTI(0[(int *)pv]); return 0;

}

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

number=123 *(int *)pv=123

223

number=789

number=555

((int *)pv)[0]=555 number=555

0[(int *)pv]=555

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

ЗАДАЧА 07-06. Определите и инициализируйте значениями от 'a' до 'h' символьный массив. Определите два указателя, "настроив" их на первый и последний элементы массива. Используя указатели, "инвертируйте" массив, поменяв местами значения первого элемента и последнего, второго и предпоследнего и т.д. Выведите значения элементов полученного массива, используя указатель и применяя к нему операцию индексации [ ].

Решение:

/* 07_06.c - массивы и указатели */ #include <stdio.h>

int main ()

{

int i; int size;

char memb;

char line[] = {'a','b','c','d','e','f','g','h'}; char * p1 = line, * p2; size=sizeof(line)/sizeof(*line);

p2 = &line[size-1];

for (i=0; i<size/2; i++) { memb = *p1;

*p1++ = *p2; *p2-- = memb;

}

for (i=size-1, p2 = &line[size-1]; i >= 0; i--) printf("%c ",p2[-i]);

return 0;

}

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

h g f e d c b a

224

При определении указателя char *p1=line; он инициализируется значением адреса &line[0], так как имя массива – всегда константный указатель на его первый элемент (с нулевым индексом).

Чтобы присвоить значение указателю char *p2, приходится определить размер массива (переменная size) и явно записывать выражение &line[size-1]. (Обратите внимание на выражение, использованное для вычисления количества элементов массива.) "Перемещение" указателей p1 и p2 по элементам массива осуществляют в первом цикле выражения p1++ и p2--.

Второй цикл выполняет печать элементов массива: line[0], line[1], …, line[size-1]. Доступ к этим элементам реализован достаточно замысловато через указатель p2, адресующий последний элемент массива (используется отрицательный индекс). Выражение

(p2-size+1) адресует элемент line[0], (p2-size+2) – элемент line[1] и т.д. Именно такие значения принимает выражение p2[-i] в теле цикла с параметром i, уменьшающимся от size-1 до 0.

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

for(i=0, p1=line; i<size; i++) printf("%c ", p1[i]);

или наиболее естественный:

for(i=0; i<size; i++) printf("%c ", line[i]);

В каждом из вариантов можно было бы так заменить операцию индексации [] разыменованием:

*(p2-i), *(p1+i), *(line+i)

ЭКСПЕРИМЕНТ. Для иллюстрации особой роли имени массива в операции sizeof замените в программе 07_06.с line именем указателя p1 (настроенного на начало массива), т.е. замените

size=sizeof(line)/sizeof(*line);

выражением size=sizeof(p1)/sizeof(*p1);

225

Так как p1 имеет тип char *, то sizeof(*p1) равно 1, а sizeof(p1) равно не размеру массива line, а размеру участка памяти, выделенного для указателя p1 (для компилятора DJGPP равно 4). Таким образом, size будет равно 4, и после компиляции при исполнении программы 07_06_1.с получим такой результат:

d c b a

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

void * memchr(const void * s, int c, size_t n);

функция поиска среди первых n элементов массива, адресованного указателем s, первого вхождения значения c.

int read(int handle, void * buf, unsigned len);

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

Чтобы привыкнуть к такому использованию указателей вместо имен массивов, целесообразно получить опыт замены операции ин-

дексации [] операцией разыменования *, как это было сделано в предыдущей задаче.

ЗАДАЧА 07-07. Определив и инициализировав одномерный вещественный массив, выведите на печать индекс (не адрес!) и значение его максимального по модулю элемента. Вместо индексации при обращении к элементам массива используйте разыменование их адресов. При печати "изобразите" элемент массива в естественном виде (с индексом).

Решение:

/* 07_07.c - разыменование вместо индексации */ #include <stdio.h>

#include <math.h> int main ()

{

int i; int size;

226

double array[] = {0.0,10,-20,30,40,-50,40,-30}; double * pMax = array; size=sizeof(array)/sizeof(*array);

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

if(fabs(*pMax) < fabs(*(array+i))) pMax=array+i; printf

("\nThe index of max array element: pMax-array=%d", pMax-array);

printf("\nThe max array element *pMax: %f", *pMax);

printf("\nThe element with index: array[%d]=%f", pMax-array, *pMax);

return 0;

}

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

The index of max array element: pMax-array=5 The max array element *pMax: -50.000000

The element with index: array[5]=-50.000000

В тексте программы обратите внимание на следующие особенности, уже упомянутые в комментарии к предыдущей программе. Указатель pMax, которому будет присвоен адрес искомого элемента, инициализируется именем массива, т.е. адресом начала массива. Значение выражения sizeof(array) – размер памяти, занимаемой массивом. sizeof(*array) – размер первого элемента массива (с нулевым индексом). (array+i) – адрес элемента с индексом i. (pMax-array) – значение индекса (целочисленное) элемента, адресуемого указателем pMax.

ЭКСПЕРИМЕНТ. Раз имя массива ведет себя как указатель, то возникает подозрение, что именно так (как указатель) можно определить массив. Замените в программе 07_07.с определение массива double array[ ]={….} на определение double * array={….}.

Оцените результаты (ошибки и предупреждения) трансляции.

Программа 07_07_1.с соответствует эксперименту.

ЭКСПЕРИМЕНТ. Предыдущий эксперимент заканчивается ошибками компиляции. Вспоминаем, что имя массива является

227

константным указателем. Может быть допустимо такое определение: const double * array={…}. Проверьте это с помощью компилятора.

(Программа 07_07_2.с. Опять ошибки!)

ЭКСПЕРИМЕНТ. Инициализируйте в программе 07_07.с указатель pMax значением адреса элемента массива с нулевым индексом &array[0]. Оцените результат.

(Компиляция и исполнение пройдут успешно, программа

07_07_3.с).

ЗАДАНИЕ. Перепишите программы с одномерными массивами из предыдущей темы, используя разыменование вместо индексации.

ЗАДАЧА 07-08. Напишите программу, меняющую местами значения минимального и максимального элементов вещественного массива. Исходные данные (количество элементов и их значения) вводите с клавиатуры. Печатайте индексы и значения найденных элементов, затем – измененный массив. При определении индексов и обращении к элементам массива не используйте квадратные скобки (применяйте адресную арифметику и операцию разыменования).

Решение:

/* 07_08.c - обработка массива без индексации */ #include <stdio.h>

int main ()

{

int sizeArray; printf("sizeArray="); scanf("%d",&sizeArray);

{int i, j;

double array[sizeArray];

double * pMin = array, * pMax = array; double temp;

printf("The array members:\n");

228

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

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

scanf("%lf",array+i);

pMin = *pMin > *(array+i) ? array+i : pMin; pMax = *pMax < *(array+i) ? array+i : pMax;

}

printf("\nThe index of Min: %d, array[%d]=%f", pMin-array,pMin-array,*pMin);

printf("\nThe index of Max: %d, array[%d]=%f", pMax-array,pMax-array,*pMax);

temp = *pMax; *pMax = *pMin; *pMin = temp;

printf("\nThe solution:"); for (j=0; j < sizeArray; j++)

printf("%carray[%d]=%6.3f",

j%3?'\t':'\n',j,*(array+j));

return 0;

}

}

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

sizeArray=5<ENTER> The array members: array[0]=21<ENTER> array[1]=7<ENTER> array[2]=44<ENTER> array[3]=39<ENTER> array[4]=11<ENTER>

The index of Min: 1, array[1]=7.000000 The index of Max: 2, array[2]=44.000000 The solution:

array[0]=21.000 array[1]=44.000 array[2]= 7.000 array[3]=39.000 array[4]=11.000

Обратите внимание, что при вводе значений элементов массива в обращении к функции scanf("%lf", array+i) не требуется операции получения адреса, так как выражение array+i определяет именно адрес i-го элемента.

229

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

Почему будет неверным использование вместо переменной temp указателя? Например, так:

double * pTemp:

pTemp=pMax; pMax=pMin; pMin=pTemp;

ЭКСПЕРИМЕНТ. Выполните предложенную замену переменной temp указателем pTemp и оцените полученные результаты.

Рекомендация: в конце программы выведите значения pMaxarray и pMin-array. Решение приведено в программе 07_08_1.c. Ошибка очевидна – изменяются значения указателей, а элементы массива остаются без изменений (на своих местах).

7.3. Указатели и многомерные массивы

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

ЗАДАЧА 07-09. Определить внешний по отношению к функции main() двумерный массив с целочисленными элементами и фиксированными размерами N на M. Использовать массив для представления матрицы с размерами n на m (n<=N, m<=M), элементы которой принимают случайные значения в диапазоне от –99 до 99. Напечатать значения всех элементов двумерного массива по строкам, демонстрируя размещение матрицы в массиве. При обращении к элементам массива не использовать квадратные скобки, а применять операцию разыменования.

В следующей программе задача решена для случая, когда N и M определены на препроцессорном уровне (N=6, M=5).

230