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

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

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

пользователем с клавиатуры, но не воспринимает символ новой строки \n. Исправляет ситуацию функция cin. get (cnewline) .

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

psz = new char[ISTRING_MAX]; pi = new int; pf = new float;

Следующее выражение предназначено для считывания введенной строки текста, длина которой не может превышать значение, заданное константой istring_max, — 50 символов:

cin.get(psz, ISTRING_MAX) ;

Поскольку функция cin . get() ожидает в качестве первого аргумента указатель на строку, при вызове функции voutput( ) нет необходимости выполнять операцию раскрытия указателя:

voutput (psz, cresponse) ;

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

voutput(psz,cresponse);

voutput(pi,cresponse);

voutput(pf,cresponse);

Функция voutput() воспринимает все эти аргументы независимо от их типа только благодаря тому, что в объявлении функции указан параметр типа void*. Напомним, что для извлечения содержимого указателей, заданных как void*, их сначала нужно привести к конкретному типу данных, например char*.

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

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

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

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

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

//

//chrarray.c

//Эта программа на C++ выводит на экран массив символов в обратной

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

//

 

 

#include <iostream.h>

 

#include <string.h>

 

void

main () {

 

char

pszpalindrome[] = "Дом мод";

 

char

*pc;

- 1);

pc =

pszpalindrome + (strlen(pszpalindrome)

do

{

 

cout

<< *pc;

 

pc--;

 

 

} while (pc >= pszpalindrome); }

171

После объявления и инициализации массива pszpalindrome создается указатель рс типа char*. Вспомните, что имя массива само по себе является адресом. Сначала указателю рс присваивается адрес последнего символа массива. Это осуществляется с помощью функции strlen(), которая возвращает длину массива символов.

Функция strlen() не учитывает символ окончания Строки \0.

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

После того как указатель переместится на последний значащий символ массива, запускается цикл do/while. В этом цикле на экран выводится символ, адресуемый текущим значением указателя, а в конце каждой итерации адрес указателя уменьшается на единицу, после чего полученный адрес сравнивается с адресом первого элемента массива. Цикл продолжается до тех пор, пока все элементы массива не будут выведены на экран.

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

В языках C/C++ имеется еще одна интересная возможность — создавать массивы указателей. Такой массив представляет собой совокупность элементов, каждый из которых является указателем на другие объекты. Эти объекты, в свою очередь, также могут быть указателями.

Концепция построения массивов указателей на указатели широко используется при работе с параметрами argc и argv функции main() , с которыми вы познакомились в главе "Функции". В следующей программе определяется максимальное или минимальное из значений, введенных в командной строке (ожидается ввод неотрицательных чисел). Аргументами могут быть либо только числа, либо, в дополнение к ним, специальная опция, задающая режим работы: поиск минимального (-s, -S) или максимального (-l, -L) значений.

//

//argcargv.cpp

//Эта программа на языке C++ демонстрирует работу с массивами указателей

//на указатели на примере параметров argcи argvфункции main().

#include <iostream.h>

#include <process.h>

// exit()

#include <stdlib.h>

// atoi()

#define IFIND_LARGEST 1

 

#define IFIND_SMALLEST 0 #define IMAX32767

int main(int argc, char *argv[]) f char *psz;

int ihow_many;

int iwhich_extreme = IFIND_SMALLEST; int irange_boundary = IMAX; if(argc--< 2) {

cput<< "\nВведите одну из опций -S, -s,-L, -l"

<< " и, как минимум, одно целое число."; exit(l); } if ((*++argv) [0] == '-') {

psz = argv[0] + 1; switch (*psz) { case ' s ' : case 'S': iwhich_extreme = IFIND_SMALLEST; irange_boundary = IMAX;

break; case 'l': case 'L':

iwhich_extreme = IFIND_LARGEST; irange_boundary =0;

172

break;

default:

cout << "Нераспознанныйаргумент " << *psz <<, "\n"; exit(1) ;

}

if(*++psz != '\0'){

cout<< "Нераспознанный аргумент " << *psz << "\n"; exitfl.); ' ) if (--argc == 0) {

cout<< "Введите как минимум одно число\n"; exit(l); }

argv++; } ihow_many = argc; while (argc--) { int present_value;

present_value = atoi(*argv++);

if(iwhich_extreme .== IFIND_LARGEST && present_value > irange_boundary) irange_boundary = present_value;

if(iwhich_extreme == IFIND_SMALLEST S& present_value < irange_boundary) irange_boundary = present_value; }

cout << ( (iwhich_extreme) ? "Максимальное " : "Минимальное "); cout<< "значение в ряду из " << ihow_many

<<" введенных чисел = "

<<irange_boundary << "\n"; return(0); }

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

argcargv argcargv98 argcargv98 21 argcargv -s 98 argcargv -S 98 21 argcargv -l 14 argcargv -L 14 67

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

Примечание

Параметр argv не является константой. Напротив, это переменная, чье значение может быть изменено. Об этом важно помнить при изучении работы программы. Первый элемент массива

— argv[0] — является указателем на строку символов, содержащую имя программы.

В первую очередь в программе проверяется, не является ли значение параметра argc меньшим 2. Если это так, значит, пользователь ввел в командной строке только имя программы без аргументов. По-видимому, пользователь не знает назначения программы или того, какие аргументы должны быть заданы в командной строке. Поэтому программа отобразит на экране информацию о своих аргументах и завершится выполнением функции exit().Обратите внимание, что после выполнения проверки счетчик аргументов уменьшается на единицу (argc--). К моменту начала цикла while счетчик должен стать равным количеству числовых аргументов, заданных в командной строке, здесь же мы уменьшили его на единицу, чтобы учесть аргумент, содержащий имя программы.

Затем происходит обращение к первому символу второго аргумента, и если полученный символ является дефисом, то программа определяет, какая именно опция, -s или -l, задана в командной строке. Заметьте, что к указателю массива argv сначала добавляется единица (++argv), в результате чего аргумент с именем программы (первый элемент массива)

173

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

Поскольку значение указателя argvбыло изменено и он теперь ссылается на второй аргумент командной строки, выражение argv[0] возвращает адрес первого символа этого аргумента. Путем прибавления единицы мы перемещаем указатель ко второму символу, записывая его адрес в переменную psz:

psz = argv[0] + 1;

Анализируя значение этого символа, программа определяет, какую задачу ставит перед ней пользователь: поиск минимального или максимального чисел из списка.

При этом в переменной iwhich_extreme устанавливается флаг режима

(IFIND_SMALLEST — ПОИСК МИНИМAЛЬНОГО ЧИСЛA,IFIND_LARGEST —

МAКСИМAЛЬНОГО),

а в переменную irange_boundary записывается начальное значение, с которым будут сравниваться числовые аргументы. Из соображений совместимости мы предполагаем, что неотрицательные числа типа int попадут в диапазон 0—32767, что соответствует 16разрядным системам. В случае, если пользователь задаст неверную опцию, скажем, -d или -su, в ветви default будет выведено соответствующее сообщение. Проверка

if(--argc ==0)

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

argv++;

необходима для того, чтобы переместить указатель argv на аргумент, идущий после опции.

Функция atoi() в цикле while преобразовывает каждый из аргументов в целое число (не забывайте, что на данный момент все они представлены в виде массивов символов) и сохраняет результат в переменной present_value. Опять-таки после выполнения этой функции указатель argv будет перемещен на следующий аргумент командной строки. В оставшихся двух инструкциях if впеременную irange_boundагу записывается текущее минимальное или максимальное значение из списка в зависимости от содержимого флага iwhichextreme.

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

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

/*

*dblptr.c

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

*/

#include <stdio.h> #include <stdlib.h> #define IMAXELEMENTS 3

void voutputdnt (**ppiresult_a, int **ppiresult_b, int **ррiresult_с)

;

void vassignfint (*pivirtual_array[], int *pinewblock); void main ()

{

int **ppiresult_a, **ppiresult_b, **ppiresult_c; int *pivirtual_array[IMAXELEMENTS];

int *pinewblock, *pioldblock;

ppiresult_a = &pivirtual_array[0]; ppiresult_b = &pivirtual_array[l]; ,ppiresult_c = &pivirtual_array[2];

pinewblock = (int *) malloc(IMAXELEMENTS * sizeof(int)); pioldblock = pinewblock;

vassign(pivirtual_array, pinewblock);

174

**ppiresult_a = 7; **ppiresult_b = 10; **ppiresult_c =15;

voutput(ppiresult_a, ppiresult_b, ppiresult_c); pinewblock = (int *) malloc(IMAXELEMENTS * sizeof(int)); *pinewblock = **ppiresult_a;

*(pinewblock +1)= **ppiresult_b; *(pinewblock +2)= **ppiresult_c; free (pioldblock) ; vassign(pivirtual_array, pinewblock);

voutput(ppiresult_a, ppiresult_b, ppiresult_c);

void yassign(int *pivirtual_array [] , int *pinewblock)

pivirtual_array

[0]

= pinewblock;

pivirtual_af ray [1]

= pinewblock + 1;

pivirtual_array

[2]

= pinewblock + 2;

void voutput (int **ppiresult_a, int **ppiresult_b, int **ppiresult_c){

printf ("%d\n",**ppiresult_a) ; printf ("%d\n",**ppiresult_b) ; printf ("%d\n",**ppiresult_c) ;

}

В этой программе указатели ppiresult_a, ppiresult_b иppiresult_c ссылаются на указатели с постоянными адресами (&pivirtual_array[0], spivirtual_array[l] , &pivirtual_array [2]), но содержимое последних может меняться динамически.

Рассмотрим блок объявлений в функции main().Переменные ppiresult_a, ppiresult_b и ppiresult_c объявлены как указатели на указатели, содержащие адреса значений типа int. Переменная pivirtual_array является массивом указателей типа int с числом элементов, заданным константой imaxelements. Последние две переменные, pinewblock и pioldblock, являются одиночными указателями такого же типа, что и массив pivirtual_array. Взаимоотношения между всеми переменными после того, как указателям ppiresult_a, pplresult_b и ppiresult_c были присвоены адреса соответствующих элементов массива pivirtual_array, показаны на рис. 9.15.

175

Рис. 9.15. Взаимоотношения между переменными программы после инициализации указателей ppiresult_a, pplresult_b и ppiresult_c

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

На рис. 9.16 показано, что произойдет после того, как в динамическую память будет помещен массив pinewblock, переменной pioldblock будет присвоен его адрес и выполнена функция vassign(). Обратите внимание на соответствие адресов, хранящихся в "физическом" массиве pinewblockи "виртуальном" массиве pivirtual_array.

В функцию vassign() в качестве аргументов передаются массив pivirtual_arгау и адрес только что созданного динамического блока, представленного переменной pinewblock. В теле функции каждому элементу массива присваивается соответствующий ему адрес динамической ячейки памяти. Поскольку обращение к массиву из функции осуществляется по адресу, все изменения вступают в силу и в теле функции main().

На рис. 9.17 показано состояние переменных после присвоения ячейкам динамической памяти трех целочисленных значений.

Далее ситуация становится интереснее. С помощью функции malloc() в динамической памяти создается еще один блок ячеек, адрес которого записывается в указатель pinewblock. А указатель pioldblock по-прежнему связан с созданным ранее блоком. Все это сделано для того, чтобы примерно смоделировать процесс перемещения блока ячеек в новую позицию.

Рис.9.16. Динамическое создание блока ячеек и инициализация массива pivirtual_array

176

Рис. 9.17. Заполнение ячек данными

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

*pinewblock = **ppiresult_a;

*(pinewblock

+

1)

= **ppiresult_b;

*(pinewblock

+

2)

= **ppiresult_c;

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

177

Рис. 9.18. Создание и заполнение нового динамического блока ячек

На рис. 9.19 показано, что произойдет после вызова функций free()и vassign() для переадресации указателей массива pivirtual_arrayна ячейки нового блока.

178

Рис. 9.19. Переадресация указателей массива pivirtual_array

Самое важное, что наглядно демонстрирует рис. 9.19, заключается в том, что указатели ppiresult_a, ppiresult_b и ppiresult_c не изменились в ходе выполнения программы. Поэтому когда программа выводит на экран их значения, мы видим все ту же последовательность чисел 7, 10 и 15, хотя размещение соответствующих ячеек в памяти изменилось.

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

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

/*

*arraystr.c

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

*с массивом указателей на строки.

*/

#include <ctype.h> #include <stdio.h>

#define INUMBER_OF_ERRORS 3

char *pszarray[INUMBER_OF_ERRORS] =

{

179

"\nфайл недоступен.\n",

"\nВведенный символ не является алфавитно-цифровым.\n", "\nЧисло не попадает в диапазон от 1 до 10.\n"

};

FILE *fopen_a_file (char *psz) char cget_a_char (void) ; int ,iget_an_integer (void) ; FILE *pfa_file;

void main () { char cvalue; int ivalue;

fopen_a_file("input.dat"); cvalue = cget_a_char() ; ivalue = iget_an_ihteger() ,

}

FILE *fopen_a_file(char *psz)

(

const ifopen_a_file_error = 0; pfa_file = fopen(psz, "r"); if (!pfa_file)

printf("%s",pszarray [ifopen_a_file_error] ); return (pfa_f ile );

}

char cget_a_char(void) { char cvalue;

const .icget_a_char_error = 1; printf("\nВведитебукву: "); scanf("%c",&cvalue); if(!isalpha (cvalue))

printf("%s",pszarray[icget_a_char_error]); return(cvalue); }

int iget_an_integer (void) { int ivalue;

const iiget_an_integer =2;

printf("\nВведите целое число в диапазоне от 1 до 10: ");

scanf("%d",

sivalue) ;

 

if ((ivalue<

1)

I |

(ivalue > 10)

)

printf("%s",

pszarray

[iiget_an_integer]

);

return (ivalue) ; }

Массив pszarray объявлен вне функций, поэтому является глобальным. Обратите внимание,

что в каждой из трех функций — fopen_a_file() , cget_a_char () и iget_an_integer() —

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

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

Язык C++ предоставляет возможность прямого обращения к переменным по адресам, что даже проще, чем использовать указатели. Аналогично языку С, в C++ допускается создание как обычных переменных, так и указателей. В первом случае резервируется область памяти для непосредственного сохранения данных, тогда как во втором случае в памяти компьютера выделяется место для хранения адреса некоторого объекта, который может быть создан в любое время. В дополнение к этому язык C++ предлагает третий вид объявления переменных

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

int iresult_a = 5;

int& riresult_a = iresult_a; // допустимое создание ссылки

180

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