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

Паппас К., Мюррей У. - Visual C++ 6. Руководство разработчика - 2000

.pdf
Скачиваний:
288
Добавлен:
13.08.2013
Размер:
4.96 Mб
Скачать

* Эта программа на языке С демонстрирует использование функции strcpy().

*/

#include <stdio.h> #include <string.h> #define iSIZE 20 main() {

char szsource_string[iSIZE]= "Исходная строка", szdestination_string[iSIZE];

strcpy(szdestination_string, "Постоянная строка"); printf("%s\n",szdestination_string); strcpy(szdestination_string, szsource_string); printf("%s\n",szdestination_string);

return (0);

}

При первом вызове функций strcpy() происходит копирование константной строки "Постоянная строка" в массив szdestination_string, а вторая функция strcpy() копирует туда же массив szsource_string, содержащий строку "Исходная строка". В результате получаем следующие сообщения:

Постоянная строка Исходная строка

Ниже показана аналогичная программа на языке C++:

//

//strcpy.срр

//Это версия предыдущей программы, написанная на языке C++. #include <iostream.h>

#include <string.h> #define iSIZE 20 main () {

char szsource_string [iSIZE] = "Исходная строка", szdestination_string [iSIZE] ;

strcpy (szdestination_string, "Постоянная строка"); cout << "\n"<< szdestination_string; strcpy(szdestination_string, szsource_string) ; cout << "\n"<< szdestination_string;

return(0); }

Функция strcatf() конкатенирует (объединяет) две самостоятельные строки в один массив. Обе строки должны заканчиваться нулевыми символами. Результирующий массив также завершается символом \0. Применение функции strcat() иллюстрируется следующей программой:

/*

*strcat.c

*Эта программа на языке С демонстрирует использование функции strcat() .

*/

#include <stdio.h> #include <string.h> #define 1STRING_SIZE 35

main()

{

char szgreeting[]= "Доброе утро,", szname[] = " Каролина! ", szmessage[iSTRING_SIZE] ;

strcpy (szmessage, szgreeting) ; strcat (szmessage, szname) ;

151

strcat (szmessage, "Какдела?") ; printf ("%s\n",szmessage); return(0); }

В этом примере два массива, szgreetingи szname, инициализируются в момент объявления, тогда как массив szmessage создается пустым. Первое, что делает программа, это копирует с помощью функции strcpyt() содержимое массива szgreetingв массив szmessage. Затем функция strcat() добавляет в конец массива szmessage("Доброе утро,") содержимое массива szname("

Каролина! "). И наконец, последняя функция strcat() добавляет к полученной строке "Доброе утро, Каролина! " текст "Как дела?". В результате программа выведет на экран

Доброе утро, Каролина! Как дела?

Следующая программа демонстрирует возможность сравнения двух строк с помощью функции strncmp():

/*

*stncmp.c

*Эта программа на языке С ср'авнивает две строки с помощью функции strncmp() .

*/

#include <stdio.h> #include <string.h> main () {

char szstringA[] = "Вита", szstringB[]= "Вика"; int istringA_length, iresult = 0; istringA_length = strlen(szstringA); if(strlen(szstringB) >= strlen(szstringA))

iresult = strncmp(szstringA, szstringB, istringA_length);

printf ("Строка %s обнаружена",, iresult == 0 ? "была" : "не была"); return(0); }

Используемая в программе функция strlen() возвращает число символов в строке, указанной в качестве аргумента, не считая последнего, нулевого символа. В программе эта функция вызывается дважды с разными целями, что дает возможность получить о ней более полное представление. В первом случае функция записывает в переменную istringA_length длину массива szstringA. Во втором случае значения, возвращаемые двумя функциями strlen(), сравниваются с помощью оператора >=. Если длина массива szstringB больше или равна длине массива szstringA, то происходит вызов функции strncmp().

Функция strncmp() ищет первую строку во второй, начиная с первого символа. Длина первой строки задается в третьем аргументе. Если строки одинаковы, функция возвращает нулевое значение. Когда строки не идентичны, функция возвращает отрицательное значение, если строка szstringA меньше строки szstringB, и положительное значение, если строка szstringA больше строки szstringB.

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

Строка не была обнаружена

В завершение главы подчеркнем, что двумя наиболее частыми причинами сбоев в работе программ являются выход за пределы массива и отсутствие нулевого символа (\0) в конце символьного массива, используемого в качестве строки. Обе эти ошибки могут никак не проявляться в течение многих месяцев, до тех пор пока пользователь не введет, к примеру, слишком длинную строку текста.

152

Глава 9. Указатели

Указатели как особый тип переменных o Объявление указателей

o Использование указателей o Инициализация указателей

o Ограничения на использование оператора & o Указатели на массивы

o

o Указатели на указатели o

o Указатели на строки

o Арифметические операции над указателями

o Применение арифметики указателей при работе с массивами o Распространенная ошибка при использовании операторов ++ и -- o Применение квалификатора const совместно с указателями

o

o Другие операции над указателями o Физическая реализация указателей

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

Динамическая память

oУказатели типа void

Подробнее об указателях и массивах

o Строки (массивы типа char )

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

o Дополнительные сведения об указателях на указатели

oМассивы строковых указателей

Ссылки в языке C++

oФункции, возвращающие адреса

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

153

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

Указатели как особый тип переменных

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

imemorycell_contents+= 10;

Другой способ получения доступа к значению переменной заключается в использовании другой переменной, которая содержит ее адрес. Предположим, имеется переменная типа intс

именем imemorycell_contents и переменная pimemory_cell_address, являющаяся указателем на нее. Как вы уже знаете, в C/C++ есть оператор взятия адреса &, который возвращает адрес своего операнда. Поэтому вам будет нетрудно разобраться в синтаксисе присвоения одной переменной адреса другой переменной:

pimemorycell_address = &imemorycell_contents;

Переменные, которые хранят адреса других переменных, называются указателями. На рис. 9.1 схематично показана взаимосвязь между переменной и указателем на нее. Переменная imemorycell_contents представлена в памяти компьютера ячейкой с адресом 7751. После выполнения показанной выше строки программы адрес этой переменной будет присвоен указателю pimemorycell_address.

Рис. 9.1. Взаимосвязь между переменной и её указателями

Обращение к переменной, чей адрес хранится в другой переменной, осуществляется путем помещения перед указателем оператора *: *pimemorycell_address. Такая запись означает, что будет произведен косвенный доступ к ячейке памяти через имя указателя, содержащего адрес ячейки. Например, если выполнить две показанные ниже строки, то переменная imemorycell_contentsпримет значение 20:

pimemorycell_address = &imemorycell_contents;

*pimemorycell_address = 20;

:

С учетом того, что указатель pimemorycell_address хранит адрес переменной imemorycell_contents, обе следующие строки приведут к одному и тому же результату: присвоению переменной imemorycell_contents значения 20.

imemorycell_contents = 20; *pimemorycell_address = 20;

Объявление указателей

В языках C/C++ все переменные должны быть предварительно объявлены. Объявление указателя pimemorycell_address выглядит следующим образом:

int *pimemorycell_address;

Символ * говорит о том, что создается указатель. Этот указатель будет адресовать переменную типа int. Следует подчеркнуть, что в C/C++ указатели могут хранить адреса только переменных конкретного типа. Если делается попытка присвоить указателю одного типа адрес переменной другого типа, возникнет ошибка либо во время компиляции, либо во время выполнения программы.

154

Рассмотрим пример:

int *pi

float real_value = 98.26; pi = &real_value;

Вданном случае переменная pi объявлена как указатель типа int. Но в третьей строке делается попытка присвоить этому указателю адрес переменной real_value, имеющей тип float.

Врезультате компилятор выдаст предупреждение вида "несовместимые операнды в операции присваивания", а программа, использующая указатель pi, будет работать неправильно.

Использование указателей

В следующем фрагменте программы посредством указателей производится обмен значений между переменными iresult_a и iresult_b:

int

iresialt_a

= 15,

iresult_b = 37, itemporary;

int

*piresult;

 

 

piresult = &iresult_a; itemporary = *piresult; *piresult = iresult_b; iresult_b = itemporary;

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

связана с ячейкой 7916, a itemporary— с ячейкой 2385 (рис. 9.2).

Рис. 9.2. Резервирование и инициализация ячеек памяти

Во второй строке программы создается указатель piresult. При этом также происходит резервирование именованной ячейки памяти (скажем, с адресом 1920). Поскольку инициализация не производится, то в данный момент указатель содержит пустое значение. Если попытаться применить к нему оператор *, то компилятор не сообщит об ошибке, но и не возвратит никакого адреса.

Втретьей строке происходит присваивание указателю piresult адреса переменной iresult_a(рис. 9.3).

Вследующей строке в переменную itemporary записывается содержимое переменной iresult_a, извлекаемое с помощью выражения *piresult:

itemporary = *piresult;

Рис. 9.3. Присваивание указателю piresult адреса переменной iresult_a

155

Таким образом, переменной itemporary присваивается значение 15 (рис. 9.4). Если перед именем указателя piresult не поставить символ *, то в результате в переменную itemporary будет ошибочно записано содержимое самого указателя, т.е. 5328. Это очень коварная ошибка, поскольку многие компиляторы не выдают в подобных ситуациях предупреждений или сообщений об ошибке. Компилятор VisualC++ отобразит предупреждение вида "разные уровни косвенной адресации в операции присваивания".

Рис. 9.4. Запись в переменную ietemporary значения переменной iresult_a

В пятой строке содержимое переменной iresult_b копируется в ячейку памяти, адресуемую указателем piresult(рис. 9.5):

*piresult = iresult_b;

Рис. 9.5. В переменную iresult_a записывается значение переменной iresult_b

В последней строке число, хранящееся в переменной itemporary, просто копируется в переменную iresult_b(рис. 9.6).

Рис. 9.6. Конечный результат

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

char cswitchl = 'S', cswitch2 = "I" ; char *pcswitchl, *pcswitch2, *pctemporary; pcswitchl = scswitchl;

pcswitch2 = &cswitch2; pctemporary = pcswitchl;

156

pcswitchl = pcswitch2; pcswitch2 = pctemporary;

printf("%c%c",*pcswitchl, *pcswitch2);

На рис. 9.7 показана схема отношений между зарезервированными ячейками памяти после выполнения первых четырех строк программы. В пятой строке содержимое указателя pcswitchl копируется в переменную pctemporary, в результате чего оба указателя адресуют одну переменную: cswitchl(рис. 9.8).

Рис. 9.7. Исходные отношения между переменными

Рис. 9.8. Указателю pctemporary присвоен адрес, хранящийся в указателе pcswitch1

В следующей строке содержимое указателя pcswitch2 копируется в указатель pcswitchl, после чего оба будут содержать адрес переменной cswitch2 (рис. 9.9):

pcswitchl = pcswitch2;

Рис. 9.9. Присвоение указателю pcswitch1 адреса, хранящегося в указателе pcswitch2

157

Обратите внимание, что если бы содержимое указателя pcswitchl не было продублировано во временной переменной pctemporary, то в результате выполнения предыдущего выражения ссылка на адрес переменной cswitchl была бы утеряна.

В предпоследней строке происходит копирование адреса из указателя pctemporary в указатель pcswitch2 (рис. 9.10). В результате работы функции printf() получаем:

TS

Рис. 9.10. Передача адреса от указателя pctemporary к указателю pcswitch2

Заметьте: в ходе выполнения программы исходные значения переменных cswitchlи cswitch2 не изменялись. Описанный метод может пригодиться вам в дальнейшем, так как, в зависимости от размеров объектов, часто бывает проще копировать их адреса, чем перемещать содержимое.

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

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

int iresult;

int *piresult = &iresult;

Идентификатор iresult представляет собой обычную целочисленную переменную, apiresult— указатель на переменную типа int. Одновременно с объявлением указателя piresult ему присваивается адрес переменной iresult. Будьте внимательны: здесь инициализируется содержимое самого указателя, т.е. хранящийся в нем адрес, но не содержимое ячейки памяти, на которую он указывает. Переменная iresult остается неинициализированной.

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

/*

*psz.c

*Эта программа на языке С содержит пример инициализации указателя.

*/

#include <stdio.h> #include <string.h>

void main 0 {

char *pszpalindrome = "Доммод"; int i;

for (i = strlen(pszpalindrome) - 1; i >= 0; i--) printf("%c",pszpalindrome[i]); printf("%s",pszpalindrome); }

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

158

Функция strlen(), объявленная в файле STRING.H, в качестве аргумента принимает указатель на строку, заканчивающуюся нулевым символом, и возвращает число символов в строке, не считая последнего. В нашем примере строка состоит из семи символов, но счетчик цикла for инициализируется значением 6, поскольку строка в нем интерпретируется как массив, содержащий элементы от нулевого до шестого. На первый взгляд, может показаться странной взаимосвязь между строковыми указателями и массивами символов, но если мы вспомним, что имя массива по сути своей является указателем на первый элемент массива, то станет понятным, почему переход от имени указателя к имени массива в программе не вызвал возражений компилятора.

Ограничения на использование оператора &

Оператор взятия адреса (&) можно применять далеко не с каждым выражением. Ниже иллюстрируются ситуации, когда оператор & используется неправильно:

/*с константами*/ pivariable = &48;

/*в выражениях с арифметическими операторами*/ int iresult = 5;

pivariable = &(iresult + 15);

/* с переменными класса памяти register*/ register int registerl; pivariable = &registerl;

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

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

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

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

Как уже говорилось, указатели и массивы логически связаны друг с другом. Вспомните из предыдущей главы, что имя массива является константой, содержащей адрес первого элемента массива. В связи с этим значение имени массива не может быть изменено оператором присваивания или каким-нибудь другим оператором. Например, ниже создается массив типа floatс именем ftemperatures:

#define IMAXREADINGS 20

float ftemperatures[IMAXREADINGS]; float *pftemp;

В следующей строке объявленному выше указателю pftemp присваивается адрес первого элемента массива:

pftemp = ftemperatures;

Это же выражение можно записать следующим образом: pftemp = &ftemperatures[0];

Тем не менее, даже если указатель описан для хранения адреса переменных типа float, все равно следующие выражения недопустимы:

ftemperatures = pftemp; &ftemperatures[0]= pftemp;

Эти выражения невыполнимы, поскольку в них делается попытка изменить константу ftemperatures и эквивалентное ей выражение &ftemperatures[0] , что так же бессмысленно, как и строка

10 = pftemp;

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

159

В C/C++ можно создавать указатели на другие указатели, которые, в свою очередь, содержат адреса реальных переменных. Смысл этого процесса проиллюстрирован на рис. 9.11, где ppiявляется указателем на указатель.

Рис. 9.11. Пример указателя на указатель

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

int **ppi;

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

В следующем фрагменте создается ряд указателей с различными уровнями косвенной адресации (рис. 9.12).

int ivalue = 10; int *pi;

int **ppi; int ***pppi;

pi = &ivalue; ppi = π pppi= &ppi;

Рис. 9.12. Несколько уровней косвенной адресации

Впервых четырех строках объявляются четыре переменные: ivalue типа int, указатель piна переменную типа int (первый уровень косвенной адресации), указатель ppi (второй уровень косвенной адресации) и указатель pppi (третий уровень косвенной адресации). Этот пример показывает, что при желании можно создать указатель любого уровня.

Впятой строке указателю первого уровня piприсваивается адрес переменной ivalue. Теперь значение переменной ivalue(10) может быть получено с помощью выражения *pi. В шестой строке в указатель второго уровня ppiзаписывается адрес (но не содержимое) указателя pi, который, в свою очередь, указывает на переменную ivalue. Обратиться к значению переменной можно будет посредством выражения **ppi. В последней строке аналогичным образом заполняется указатель третьего уровня.

ВC/C++ указатели можно инициализировать сразу при объявлении, как и любые другие переменные. Например, указатель pppi можно было бы инициализировать следующей строкой:

int ***pppi = &ppi;

Указатели на строки

Строковые константы в действительности представляют собой символьные массивы с конечным нулевым символом (рис. 9.13). Указатель на строку можно объявить и инициализировать следующим образом:

160

Соседние файлы в предмете Программирование на C++