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

Инициализация указателей

После объявления нестатического локального указателя до первого присвоения он содержит неопределенное значение. (Глобальные и статические локальные указатели при объявлении неявно инициализируются нулем.) Если попытаться использовать указатель перед присвоением ему нужного значения, то скорее всего он мгновенно разрушит программу или всю операционную систему. Это очень досадная ошибка.

При работе с указателями большинство программистов придерживаются следующего важного соглашения: указатель, не ссылающийся в текущий момент времени должным образом на конкретный объект, должен содержать нулевое значение. Нуль используется потому, что С гарантирует отсутствие чего-либо по нулевому адресу. Следовательно, если указатель равен нулю, то это значит, во-первых, что он ни на что не ссылается, а во-вторых — что его сейчас нельзя использовать.

Указателю можно задать нулевое значение, присвоив ему 0. Например, следующий оператор инициализирует р нулем:

char *p = 0;

Дополнительно к этому во многих заголовочных файлах языка С, например, в <stdio.h> определен макрос NULL, являющийся нулевой указательной константой. Поэтому в программах на С часто можно увидеть следующее присваивание:

p = NULL;

Однако равенство указателя нулю не делает его абсолютно "безопасным". Использование нуля в качестве признака неподготовленности указателя — это только соглашение программистов, но не правило языка С. В следующем примере компиляция пройдет без ошибки, а результат, тем не менее, будет неправильным:

int *p = 0;

*p = 10; /* ошибка! */

В этом случае присваивание посредством p будет присваиванием по нулевому адресу, что обычно вызывает разрушение программы.

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

#include <stdio.h>

#include <string.h>

int search(char *p[], char *name);

char *names[] = {

"Сергей",

"Юрий",

"Ольга",

"Игорь",

NULL}; /* Нулевая константа кончает список */

int main(void)

{

if(search(names, "Ольга") != -1)

printf("Ольга есть в списке.\n");

if(search(names, "Павел") == -1)

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

return 0;

}

/* Просмотр имен. */

int search(char *p[], char *name)

{

register int t;

for(t=0; p[t]; ++t)

if(!strcmp(p[t], name)) return t;

return -1; /* имя не найдено */

}

В функцию search() передаются два параметра. Первый из них, p — массив указателей на строки, представляющие собой имена из списка. Второй параметр name является указателем на строку с заданным именем. Функция search() просматривает массив указателей, пока не найдет строку, совпадающую со строкой, на которую указывает name. Итерации цикла forповторяются до тех пор, пока не произойдет совпадение имен, или не встретится нулевой указатель. Конец массива отмечен нулевым указателем, поэтому при достижении конца массива управляющее условие цикла примет значение ЛОЖЬ. Иными словами, p[t] имеет значение ЛОЖЬ, когда p[t] является нулевым указателем. В рассмотренном примере именно это и происходит, когда идет поиск имени "Павел", которого в списке нет.

В программах на С указатель типа char * часто инициализируют строковой константой (как в предыдущем примере). Рассмотрим следующий пример:

char *p = "тестовая строка";

Переменная р является указателем, а не массивом. Поэтому возникает логичный вопрос: где хранится строковая константа "тестовая строка"? Так как p не является массивом, она не может храниться в p, тем не менее, она где-то записана. Чтобы ответить на этот вопрос, нужно знать, что происходит, когда компилятор встречает строковую константу. Компилятор создает так называемую таблицу строк, в ней он сохраняет строковые константы, которые встречаются ему по ходу чтения текста программы. Следовательно, когда встречается объявление с инициализацией, компилятор сохраняет строку "тестовая строка" в таблице строк, а в указатель p записывает ее адрес. Дальше в программе указатель p может быть использован как любая другая строка. Это иллюстрируется следующим примером:

#include <stdio.h>

#include <string.h>

char *p = "тестовая строка";

int main(void)

{

register int t;

/* печать строки слева направо и справа налево */

printf(p);

for(t=strlen(p)-1; t>-1; t--) printf("%c", p[t]);

return 0;

}

Параметры-указатели

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

void swap1(int *x,int *y) //явные параметры-указатели

{ int tmp=*x;

*x=*y; *y=tmp;

}

Кроме явных параметров-указателей в объявлении функции можно использовать и косвенные параметры-указатели, описанные с помощью механизма подстановок:

#define pint int*

...............

void swap1(pint x,pint y) //косвенные параметры указатели

{ int tmp=*x;

*x=*y; *y=tmp;

}

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

typedef int* pint;

Представим себе, что функция swap1 была бы оформлена с параметрами-значениями:

void swap1(int x,int y)

{ int tmp=x;

x=y; y=tmp;

}

Тогда переставляемые значения поступили бы через стек и попали бы в локальные переменные (формальные параметры) x и y. Перестановка значений в локальных переменных была бы произведена, но вызывающая программа об этом ничего бы не узнала.

Таким образом, если мы хотим, чтобы результат работы вызываемой функции нашел отражение в передаваемых параметрах, мы должны сообщать не значения параметров, а их адреса. Зная адрес, вызванная функция сама может извлечь нужное значение и, при необходимости, отправить по этому адресу полученный результат.

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

int x=2,y=3;

swap1(&x,&y);

Рассмотрим еще один пример передачи указателя по указателю:

void swap3(int **v1, int **v2)

{ int *tmp=*v1; *v1=*v2; *v2=tmp; }

К этой функции можно обратиться следующим образом:

int i=10, j=20;

int *pi=&i, *pj=&j;

swap(&pi,&pj);

после такого обращения указатели pi и pj "смотрят" на новые значения, т.к. они поменялись адресами ( *pi=20, *pj=10 ), но сами переменные i и j свои значения не поменяли.

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

int sum_v(int *a,int n)

{ int j,s=0;

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

s += a[j];

return s;

}

Обращение к такой функции может выглядеть следующим образом:

int q[20];

..........

k1=sum_v(q,20); //суммирование всех компонент вектора

k2=sum_v(q,10); //суммирование первых 10 компонент вектора

k3=sum_v(&q[5],3); //суммирование q[5]+q[6]+q[7]

k4=sum_v(q+5,3); //суммирование q[5]+q[6]+q[7]

Не забывайте, что имя массива одновременно является и указателем на его первый элемент (т.е. q и &q[0] – это одно и то же).

Параметры-ссылки

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

void swap2(int &x,int &y) //явные параметры-ссылки

{ int tmp=x;

x=y; y=tmp;

}

Параметры-ссылки могут быть объявлены в заголовках функций и с помощью косвенных типов, предварительно описанных в конструкциях #define или typedef:

#define rint int&

//или

typedef int& rint;

...................

void swap2(rint x,rint y)

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

int x=2,y=3;

swap2(x,y);

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

void swap3(int *&v1,int *&v2)

{ int *t=v2; v2=v1; v1=t; }

К этой функции можно обратиться следующим образом:

int i=10, j=20;

int *pi=&i, *pj=&j;

swap(pi,pj);

После этого обращения указатели pi и pj "смотрят" на новые значения ( *pi=20, *pj=10 ), но сами переменные i и jсохранили свои прежние значения.

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