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

Указатель на константу и константный указатель

int main()

{

    int a=100, b=222;//два обычных объекта типа int    

   int *const P2=&a; //Константный указатель

*P2=987; //Менять значение разрешено

     //P2=&b; //Но изменять адрес не разрешается

    const int *P1=&a; //Указатель на константу

        //*P1=110; //Менять значение нельзя

    P1=&b; //Но менять адрес разрешено    

        const int *const P3=&a;//Константный указатель на константу

    //*P3=155; //Изменять нельзя ни значение

    //P3=&b; //Ни адрес к которому такой указатель привязан

    return 0;

}

Чтобы не путаться где что можно и где тут чего нельзя достаточно вспомнить определение указателя. Так как указателем называется адрес памяти, то константный указатель = неизменяемый адрес памяти. Другое дело если мы не хотим изменять то значение, на которое указывает указатель, но предполагаем, что указатель будет указывать на разные объекты, каждый из которых изменению не подлежит. Таким образом у нас получается изменяемый указатель на неизменяемый объект или просто указатель на константу. В описанных случаях есть что-то такое, что разрешено и чего не разрешено изменять, но можно запретить любые изменения, запретив менять и адрес и значение по этому адресу. Для такой жесткой привязки используется константный указатель на константу. Это как бы объединение ограничений двух первых вариантов друг с другом.

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

Указатели и массивы

Понятия указателей и массивов тесно связаны. Рассмотрим следующий фрагмент программы:

char str[80], *p1;

p1 = str;

Здесь p1 указывает на первый элемент массива str. Обратиться к пятому элементу массива str можно с помощью любого из двух выражений:

str[4]

* (p1+4)

Массив начинается с нуля. Поэтому для пятого элемента массива str нужно использовать индекс 4. Можно также увеличить p1 на 4, тогда он будет указывать на пятый элемент. (Напомним, что имя массива без индекса возвращает адрес первого элемента массива.)

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

В следующем фрагменте программы приведены две версии функции putstr(), выводящей строку на экран. В первой версии используется индексация массива, а во второй — адресная арифметика:

/* Индексация указателя s как массива. */

void putstr(char *s)

{

register int t;

for(t=0; s[t]; ++t) putchar(s[t]);

}

/* Использование адресной арифметики. */

void putstr(char *s)

{

while(*s) putchar(*s++);

}

Большинство профессиональных программистов сочтут вторую версию более наглядной и удобной. Для большинства компиляторов она также более быстродействующая. Поэтому в процедурах такого типа приемы адресной арифметики используются довольно часто.

Создание указателя на массив

Указатель на 1-й элемент массива можно создать путем присваивания ему имени массива без индекса. Например, если есть объявление

int sample[10];

то в качестве указателя на 1-й элемент массива можно использовать имя sample. В следующем фрагменте программы адрес 1-го элемента массива sample присваивается указателю р:

int *p;

int sample[10];

p = sample;

В обеих переменных (р и sample) хранится адрес 1-го элемента, отличаются эти переменные только тем, что значение sample в программе изменять нельзя. Адрес первого элемента можно также получить, используя оператор получения адреса &. Например, выражения sample и &sample[0] имеют одно и то же значение. Тем не менее, в профессионально написанных программах вы не встретите выражения &sample[0].

Передача одномерного массива в функцию

В языке С нельзя передать весь массив как аргумент функции. Однако можно передать указатель на массив, т.е. имя массива без индекса. Например, в представленной программе в func1() передается указатель на массив i:

int main(void)

{

int i[10];

func1(i);

...

}

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

void func1(int *x) /* указатель */

{

/* ... */

}

или как

void func1(int x[10]) /* массив определенного размера */

{

/* ... */

}

и наконец как

void func1(int x[]) /* массив без определенного размера */

{

/* ... */

}

Эти три объявления тождественны, потому что каждое из них сообщает компилятору одно и то же: в функцию будет передан указатель на переменную целого типа. В первом объявлении используется указатель, во втором — стандартное объявление массива. В последнем примере измененная форма объявления массива сообщает компилятору, что в функцию будет передан массив неопределенной длины. Как видно, длина массива не имеет для функции никакого значения, потому что в С проверка границ массива не выполняется. Эту функцию можно объявить даже так:

void func1(int x[32])

{

/* ... */

}

И при этом программа будет выполнена правильно, потому что компилятор не создает массив из 32 элементов, а только подготавливает функцию к приему указателя.

Строки

Одномерный массив наиболее часто применяется в виде строки символов. Строка — это одномерный массив символов, заканчивающийся нулевым символом. В языке С признаком окончания строки (нулевым символом) служит символ '\0'. Таким образом, строка содержит символы, составляющие строку, а также нулевой символ. Это единственный вид строки, определенный в С.

На заметку

В C++ дополнительно определен специальный класс строк, называющийся String, который позволяет обрабатывать строки объектно-ориентированными методами. Стандарт С не поддерживает String.

Объявляя массив символов, предназначенный для хранения строки, необходимо предусмотреть место для нуля, т.е. указать его размер в объявлении на один символ больше, чем наибольшее предполагаемое количество символов. Например, объявление массива str, предназначенного для хранения строки из 10 символов, должно выглядеть так:

char str[11];

Последний, 11-й байт предназначен для нулевого символа.

Записанная в тексте программы строка символов, заключенных в двойные кавычки, является строковой константой, например,

"некоторая строка"

В конец строковой константы компилятор автоматически добавляет нулевой символ.

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

Имя функции

Выполняемое действие

strcpy(s1,s2)

Копирование s2 в s1

strcat(s1,s2)

Конкатенация (присоединение) s2 в конец s1

strlen(s1)

Возвращает длину строки s1

strcmp(s1,s2)

Возвращает 0, если s1 и s2 совпадают, отрицательное значение, если s1<s2 и положительное значение, еслиs1>s2

strchr(s1,ch)

Возвращает указатель на первое вхождение символа ch в строку s1

strstr(s1,s2)

Возвращает указатель на первое вхождение строки s2 в строку s1

Эти функции объявлены в заголовочном файле <string.h>. Применение библиотечных функций обработки строк иллюстрируется следующим примером:

#include <stdio.h>

#include <string.h>

int main(void)

{

char s1[80], s2[80];

gets(s1);

gets(s2);

printf("Длина: %d %d\n", strlen(s1), strlen(s2));

if(!strcmp(s1, s2)) printf("Строки равны\n");

strcat(s1, s2);

printf("%s\n", s1);

strcpy(s1, "Проверка.\n");

printf(s1);

if(strchr("Алло", 'л')) printf(" л есть в Алло\n");

if(strstr("Привет", "ив")) printf(" найдено ив ");

return 0;

}

Если эту программу выполнить и ввести в s1 и в s2 одну и ту же строку "Алло!", то на экран будет выведено следующее:

Длина: 5 5

Строки равны

Алло!Алло!

Проверка.

л есть в Алло

найдено ив

Следует помнить, что strcmp() принимает значение ЛОЖЬ, если строки совпадают (хоть это и несколько нелогично). Поэтому в тесте на совпадение нужно использовать логический оператор отрицания ! как в предыдущем примере.

Двухмерные массивы

Если двухмерный массив используется в качестве аргумента функции, то в нее передается только указатель на начальный элемент массива. В соответствующем параметре функции, получающем двухмерный массив, обязательно должен быть указан размер правого измерения, который равен длине строки массива. Размер левого измерения указывать не обязательно. Размер правого измерения необходим компилятору для того, чтобы внутри функции правильно вычислить адрес элемента массива, так как для этого компилятор должен знать длину строки массива. Например, функция, получающая двухмерный массив целых размерностью 10×10, должна быть объявлена так:

void func1(int x[][10])

{

/* ... */

}

Компилятор должен знать длину строки массива, чтобы внутри функции правильно вычислить адрес элемента массива. Если при компиляции функции это неизвестно, то невозможно определить, где начинается следующая строка, и вычислить, например, адрес элемента

x[2][4]

Массивы строк

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

char str_array[30][80];

Чтобы обратиться к отдельной строке массива, нужно указать только левый индекс. Например, вызов функции gets() с третьей строкой массива str_array в качестве аргумента можно записать так:

gets(str_array[2]);

Этот оператор эквивалентен следующему:

gets(&str_array[2][0]);

Из этих двух форм записи предпочтительной является первая.

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

#include <stdio.h>

#define MAX 100

#define LEN 80

char text[MAX][LEN];

int main(void)

{

register int t, i, j;

printf("Для выхода введите пустую строку.\n");

for(t=0; t<MAX; t++) {

printf("%d: ", t);

gets(text[t]);

if(!*text[t]) break; /* выход по пустой строке */

}

for(i=0; i<t; i++) {

for(j=0; text[i][j]; j++)

putchar(text[i][j]);

putchar('\n');

}

return 0;

}

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

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

Передавая многомерный массив в функцию, в объявлении параметров функции необходимо указать все размеры измерений, кроме самого левого. Например, если массив m объявлен как

int m[4] [3] [6] [5];

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

void func1(int d[][3][6][5])

{

/* ... */

}

Конечно, можно включить в объявление и размер 1-го измерения, но это излишне.

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

Указатели и массивы тесно связаны друг с другом. Имя массива без индекса — это указатель на первый (начальный) элемент массива. Рассмотрим, например, следующий массив:

char p[10];

Следующие два выражения идентичны:

p

&p[0]

Выражение

p == &p[0]

принимает значение ИСТИНА, потому что адрес 1-го элемента массива — это то же самое, что и адрес массива.

Как уже указывалось, имя массива без индекса представляет собой указатель. И наоборот, указатель можно индексировать как массив. Рассмотрим следующий фрагмент программы:

int *p, i[10];

p = i;

p[5] = 100; /* в присваении используется индекс */

*(p+5) = 100; /* в присвоении используется адресная арифметика */

Оба оператора присваивания заносят число 100 в 6-й элемент массива i. Первый из них индексирует указатель p, во втором применяются правила адресной арифметики. В обоих случаях получается один и тот же результат.

Можно также индексировать указатели на многомерные массивы. Например, если а — это указатель на двухмерный массив целых размерностью 10×10, то следующие два выражения эквивалентны:

a

&a[0][0]

Более того, к элементу (0,4) можно обратиться двумя способами: либо указав индексы массива: а[0][4], либо с помощью указателя: *((int*)а+4). Аналогично для элемента (1,2): а[1][2] или *((int*)а+12). В общем виде для двухмерного массива справедлива следующая формула:

a[j][k] эквивалентно *((базовый_тип*)а+(j*длина_строки)+k)

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

Двухмерный массив может быть представлен как указатель на массив одномерных массивов. Добавив еще один указатель, можно с его помощью обращаться к элементам отдельной строки массива. Этот прием демонстрируется в функции pr_row(), которая печатает содержимое конкретной строки двухмерного глобального массива num:

int num[10][10];

void pr_row(int j)

{

int *p, t;

p = (int *) &num[j][0]; /* вычисление адреса 1-го

элемента строки номер j */

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

printf("%d ", *(p+t));

}

Эту функцию можно обобщить, включив в список аргументов номер строки, длину строки и указатель на 1-й элемент:

void pr_row(int j, int row_dimension, int *p)

{

int t;

p = p + (j * row_dimension);

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

printf("%d ", *(p+t));

}

/* ... */

void f(void)

{

int num[10][10];

pr_row(0, 10, (int *) num); /* печать 1-й строки */

}

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

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