Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
лекция 21-22.docx
Скачиваний:
13
Добавлен:
23.03.2015
Размер:
63.35 Кб
Скачать

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

Как и объекты любых других типов, указатели могут быть собраны в массив. В следующем операторе объявлен массив из 10 указателей на объекты типа int:

int *x[10];

Для присвоения, например, адреса переменной var третьему элементу массива указателей, необходимо написать:

x[2] = &var;

В результате этой операции, следующее выражение принимает то же значение, что и var:

*x[2]

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

void display_array(int *q[])

{

int t;

for(t=0; t<10; t++)

printf("%d ", *q[t]);

}

Необходимо помнить, что q — это не указатель на целые, а указатель на массив указателей на целые. Поэтому параметрq нужно объявить как массив указателей на целые. Нельзя объявить q просто как указатель на целые, потому что он представляет собой указатель на указатель.

Массивы указателей часто используются при работе со строками. Например, можно написать функцию, выводящую нужную строку с сообщением об ошибке по индексу num:

void syntax_error(int num)

{

static char *err[] = {

"Нельзя открыть файл\n",

"Ошибка при чтении\n",

"Ошибка при записи\n",

"Некачественный носитель\n"

};

printf("%s", err[num]);

}

Массив err содержит указатели на строки с сообщениями об ошибках. Здесь строковые константы в выражении инициализации создают указатели на строки. Аргументом функции printf() служит один из указателей массива err, который в соответствии с индексом num указывает на нужную строку с сообщением об ошибке. Например, если в функциюsyntax_error() передается num со значением 2, то выводится сообщение Ошибка при записи.

Отметим, что аргумент командной строки argv (см. главу 6) также является массивом указателей на строковые константы.

Многоуровневая адресация

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

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

Переменная, являющаяся указателем на указатель, должна быть соответствующим образом объявлена. Это делается с помощью двух звездочек перед именем переменной. Например, в следующем операторе newbalance объявлена как указатель на указатель на переменную типа float:

float **newbalance;

Следует хорошо понимать, что newbalance — это не указатель на число типа float, а указатель на указатель на число типа float.

Указатель Переменная

+--------+ +--------+

| Адрес |------->|Значение|

+--------+ +--------+

Одноуровневая адресация

Указатель Указатель Переменная

+--------+ +--------+ +--------+

| Адрес |----->| Адрес |----->|Значение|

+--------+ +--------+ +--------+

Многоуровневая адресация

Рис. Одноуровневая и многоуровневая адресация

При двухуровневой адресации для доступа к значению объекта нужно поставить перед идентификатором две звездочки:

#include <stdio.h>

int main(void)

{

int x, *p, **q;

x = 10;

p = &x;

q = &p;

printf("%d", **q); /* печать значения x */

return 0;

}

Здесь p объявлена как указатель на целое, a q — как указатель на указатель на целое. Функция printf() выводит на экран число 10.

Указатели на функции

Указатели на функции — очень мощное средство языка С. Хотя нельзя не отметить, что это весьма трудный для понимания термин. Функция располагается в памяти по определенному адресу, который можно присвоить указателю в качестве его значения. Адресом функции является ее точка входа. Именно этот адрес используется при вызове функции. Так как указатель хранит адрес функции, то она может быть вызвана с помощью этого указателя. Он позволяет также передавать ее другим функциям в качестве аргумента.

В программе на С адресом функции служит ее имя без скобок и аргументов (это похоже на адрес массива, который равен имени массива без индексов). Рассмотрим следующую программу, в которой сравниваются две строки, введенные пользователем. Обратите внимание на объявление функции check() и указатель p внутри main(). Указатель p, как вы увидите, является указателем на функцию.

#include <stdio.h>

#include <string.h>

void check(char *a, char *b,

int (*cmp)(const char *, const char *));

int main(void)

{

char s1[80], s2[80];

int (*p)(const char *, const char *);

/* указатель на функцию */

p = strcmp;

/* присваивает адрес функции strcmp указателю p */

printf("Введите две строки.\n");

gets(s1);

gets(s2);

check(s1, s2, p); /* Передает адрес функции strcmp

посредством указателя p */

return 0;

}

void check(char *a, char *b,

int (*cmp)(const char *, const char *))

{

printf("Проверка на совпадение.\n");

if(!(*cmp)(a, b)) printf("Равны");

else printf("Не равны");

}

Проанализируем эту программу подробно. В первую очередь рассмотрим объявление указателя p в main():

int (*p)(const char *, const char *);

Это объявление сообщает компилятору, что p — это указатель на функцию, имеющую два параметра типа const char * и возвращающую значение типа int. Скобки вокруг p необходимы для правильной интерпретации объявления компилятором. Подобная форма объявления используется также для указателей на любые другие функции, нужно лишь внести изменения в зависимости от возвращаемого типа и параметров функции.

Теперь рассмотрим функцию check(). В ней объявлены три параметра: два указателя на символьный тип (a и b) и указатель на функцию cmp. Обратите внимание на то, что указатель функции cmp объявлен в том же формате, что и p. Поэтому в cmp можно хранить значение указателя на функцию, имеющую два параметра типа const char * и возвращающую значение int. Как и в объявлении p, круглые скобки вокруг *cmp необходимы для правильной интерпретации этого объявления компилятором.

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

(*cmp)(a, b)

вызывает функцию strcmp(), на которую указывает cmp, с аргументами a и b. Скобки вокруг *cmp обязательны. Существует и другой, более простой, способ вызова функции с помощью указателя:

cmp(a, b);

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

Вызов функции check() можно записать, используя непосредственно имя strcmp():

check(s1, s2, strcmp);

В этом случае вводить в программу дополнительный указатель p нет необходимости.

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

Альтернативный подход — использование оператора switch с длинным списком меток case — делает программу более громоздкой и подверженной ошибкам.

В следующем примере рассматривается расширенная версия предыдущей программы. В этой версии функция check()устроена так, что может выполнять разные операции над строками s1 и s2 (например, сравнивать каждый символ с соответствующим символом другой строки или сравнивать числа, записанные в строках) в зависимости от того, какая функция указана в списке аргументов. Например, строки "0123" и "123" отличаются, однако представляют одно и то же числовое значение.

#include <stdio.h>

#include <ctype.h>

#include <stdlib.h>

#include <string.h>

void check(char *a, char *b,

int (*cmp)(const char *, const char *));

int compvalues(const char *a, const char *b);

int main(void)

{

char s1[80], s2[80];

printf("Введите два значения или две строки.\n");

gets(s1);

gets(s2);

if(isdigit(*s1)) {

printf("Проверка значений на равенство.\n");

check(s1, s2, compvalues);

}

else {

printf("Проверка строк на равенство.\n");

check(s1, s2, strcmp);

}

return 0;

}

void check(char *a, char *b,

int (*cmp)(const char *, const char *))

{

if(!(*cmp)(a, b)) printf("Равны");

else printf("Не равны");

}

int compvalues(const char *a, const char *b)

{

if(atoi(a)==atoi(b)) return 0;

else return 1;

}

Если в этом примере ввести первый символ первой строки как цифру, то check() использует compvalues(), в противном случае — strcmp(). Функция check() вызывает ту функцию, имя которой указано в списке аргументов при вызове check(), поэтому она в разных ситуациях может вызывать разные функции. Ниже приведены результаты работы этой программы в двух случаях:

Введите два значения или две строки.

тест

тест

Проверка строк на равенство.

Равны

Введите два значения или две строки.

0123

123

Проверка значений на равенство.

Равны

Сравнение строк 0123 и 123 показывает равенство их значений.

Обратите внимание, что в языке С нулем начинаются восьмеричные константы. Если бы эта запись была в выражении, то 0123 не было бы равно 123. Однако здесь функция atoi() обрабатывает это число как десятичное.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]