Борис Пахомов Санкт Петербург бхв петербург 2013 удк 004. 4 Ббк 32. 973. 26018. 2 П
![]()
|
и структурами данных Указатель Указатель — это переменная, которая содержит адрес другой переменной (говорят, что указатель указывает на переменную того типа, адрес которой он содержит. Существует одноместная (унарная, те. для одного операнда) операция взятия адреса переменной Если имеем объявление int то можно определить адрес этой переменной Если Pa — указатель, который будет указывать на переменную типа int , то можем записать Существует унарная операция * она называется операцией разыменования, которая воздействует на переменную, содержащую адрес объекта, те. на указатель. При этом извлекается содержимое переменной, адрес которой находится в указателе. Если, как мы видели, Pa=&a , то, воздействуя на обе части операцией * , получим по определению этой операции Исходя из этого, указатель объявляется так тип переменной * имя указателя> Здесь: <тип переменной — это тип той переменной, на которую указывает указатель, те. тип переменной, чей адрес может находиться в переменной, которую мы задаем как указатель имя указателя — это имя переменной. Это и есть правило объявления указателя указатель на переменную какого-то типа это такая переменная, при воздействии на которую операцией разыменования получаем значение переменной этого же типа. Прежде чем использовать указатель, его необходимо инициализировать, те. настроить на какой-то конкретный объект. Указатель может иметь нулевое значение, гарантирующее, что он не совпадает ни с одним значением указателя, используемого в данный момент в программе. Если мы присвоим указателю константу нуль, то 106 Часть I. Изучение языка С/С++ получим указатель с нулевым значением. Такой указатель можно сравнивать с мнемоническим NULL , определенным в стандартной библиотеке stdio.h. Указатель может иметь тип void , те. указывать на "ничто, но указатель этого типа нельзя путать с нулевым. Объявление void сообщает о том, что ptr не указывает на конкретный тип данных, а является универсальным указателем, способным настраиваться на любой тип значений, включая и нулевой. Примером указателя типа void может служить функция malloc() , возвращающая указатель на динамическую область памяти, выделяемую ею под объект. Она возвращает указатель типа void , и пользователь должен сделать приведение (casting) этого типа к типу объекта методом принудительного назначения типа (в скобках указать тип. Если, например, мы выделяли память под объект типа char , то надо объявлять char object[]; char *P=(char Пусть некоторый указатель Pc указывает на переменную типа ст. е. содержит адрес места памяти, начиная с которого располагается объект типа с (например, строка символов. Объявление такого указателя по определению будет выглядеть так char Здесь имя указателя — Pc , а не Несмотря на такое объявление, сам указатель — это переменная Pc . Теперь воздействуем на него операцией разыменования. Получим *Pc . Это будет значение первого символа строки, на начало которой указывал указатель. Чтобы получить значение следующего символа строки, надо указатель увеличить на единицу Pc++ и применить Вообще, какого бы типа не был объект, на начало которого в памяти указывает некоторый указатель Р (а он указывает именно на начало объекта в памяти, когда говорят, что он указывает на объект, Р всегда указывает наследующий элемент объекта, P+i — на й элемент. Приращение адреса, который содержит указатель Р, всегда сопровождается масштабированием размера памяти, занимаемого элементом объекта. То есть указатель при увеличении его значения на единицу передвигается на величину, равную длине элемента объекта, устанавливаясь на начало следующего элемента объекта. Мы только что разобрали понятие указателя в общем смысле. Однако в VC++ это понятие несколько расширено, что вполне естественно для развивающихся систем. Глава 7. Работа с указателями и структурами данных Здесь рассматриваются указатели трех типов регулируемые указатели нерегулируемые указатели нерегулируемые указатели функций. Чтобы понять суть сказанного, вспомним, что ранее упоминалась среда CLR — это менеджер, который управляет исполнением кода, памятью, потоками и работой с удаленными компьютерами, при этом строго обеспечивая безопасность и создавая надежность исполнения кода. CLR является добавкой-расширением С, введенной фирмой Microsoft, начиная с версии VC++ 2005. Подключение к вашему проекту этого менеджера осуществляется на этапе компиляции. Если вы посмотрите свойства вашего проекта с помощью опции Project | Properties ив открывшемся диалоговом окне выберете папку Configuration Properties | General, тов правой части окна увидите настраиваемое свойство Common Language Runtime support (поддержка режима CLR). Среда версии 2011 в режиме CLR понимает программы, использующие "родные" указатели, обозначаемые символом "*" (это видно из примера листинга 7.2, расположенного ниже по тексту, хотя этаже среда предусматривает совсем иное обозначение указателя, что мы увидим далее. Без режима CLR, когда вы в своей программе работаете с некоторыми объектами здесь нам приходится забегать вперед, ибо объекты — это предмет более позднего изучения, когда мы станем знакомиться с классами, вы должны сами заботиться об их размещении в памяти, выделяемой средой. Память для вашего приложения выделяется в так называемой "куче" (heap): в ней вы размещаете свои объекты, там же сами должны освобождать память, когда перестаете работать с объектом, иначе куча может переполниться, и процесс выполнения приложения прервется. Это так называемая неуправляемая куча. Указатели на участки памяти в такой куче обозначаются символом "*" (те. выше мы как раз рассматривали указатели для неуправляемой памяти. Вот два последних типа указателя из приведенного выше перечня типов указателей и являются традиционными указателями С/С++ на объекты вне- регулируемом объеме памяти, выделяемой для исполнения приложения. Другое дело, когда включается режим CLR. Такое приложение отличается от обычного тем, что его заготовка обеспечивает подключение к приложению специального системного пространства System , содержащего объекты, размещение в памяти которых надо автоматически регулировать. Если вы внимательно смотрели тексты ранее изученных нами программ, тов каждой из них имеется строка using namespace System , что свидетельствует о том, что программа работает в режиме CLR (а мы и выбирали такой шаблон-заготовку при создании проекта программы. Так вот режим CLR работает уже с управляемой кучей памяти, в которой размещение объектов и ее освобождение от них происходит под управлением среды. Такой сервис входит, например, в язык Java, где не надо делить кучу на управляемую и неуправляемую. Регулируемый указатель — это тип указателя, который ссылается на объекты (адреса памяти, по которым можно обращаться к объектам, расположенные в общей 108 Часть I. Изучение языка С/С++ регулируемой куче памяти, предоставленной приложению в момент его исполнения. Для таких указателей принято специальное обозначение вместо символа "*" применяется символ "^". Создание CLR привело к необходимости разработки аппарата преобразования переменных, относящихся к одной куче, в адреса в другой и т. п. Этот процесс назвали маршаллизацией. Существует специальная библиотека, обеспечивающая этот процесс. Таблица некоторых преобразований приведена в главе 14. При рассмотрении работы с компонентами так называемых форм см. главы 10—12) вы увидите, что регулируемые указатели создаются в обработчиках событий компонентов и что свойства проектов, работающих с объектами, содержат автоматическое подключение при компиляции поддержки режима CLR. В среде С+ существует специальная утилита gcnew, которая формирует экземпляр какого-то объекта, выделяя ему (экземпляру) некоторую память, и возвращает ссылку на этот экземпляр (аналог функции malloc() ). В листинге 7.1 демонстрируется работа с объектом "Структура, суть которого мы опишем позже. Результат работы программы представлен на рис. 7.1. Листинг 7.1 // 7.1_2011.cpp #include "stdafx.h" ref struct Message { System::String ^sender, ^receiver, ^data; }; using namespace System; int main() { Message ^M= gcnew Message ; M->sender="The message to all"; M->data="11.03.2012"; Console::WriteLine(M->sender); Console::WriteLine(M->data); Console::WriteLine(L"Hello World"); Console::ReadLine(); для задержки экрана } Здесь объявлена некая структура Message ссылочного ( ref ) типа. Элементами ее являются указатели-ссылки ^sender , ^receiver , ^data на объект типа String (строковые данные. В головной программе main() для структуры Message утилитой gcnew выделяется память и возвращается указатель M типа Message на эту структуру. Это аналог известной функции malloc() . Алгоритм программы интуитивно поня- Глава 7. Работа с указателями и структурами данных тен: по адресу, на который указывает указатель sender , пересылается сообщение "The message to all", по адресу, на который указывает указатель data , пересылается строка с датой, затем эти два текста выводятся на консольное устройство вывода у нас в консольных приложениях консольное устройство вывода — экран. После этого туда же выводится сообщение "Hello World" и выполняется оператор чтения строки с клавиатуры для обеспечения задержки убегания экрана. Рис 7.1. Результат работы программы листинга Шаблон CLR, когда мы хотим использовать регулируемые указатели, предполагает подключение к тексту программы специального пространства System , содержащего классы, которые задают ссылочные типы данных и функции работы сними. В частности, функция WriteLine() вывод на печать) принадлежит этому же пространству. А нам как рази надо работать с такими данными. Указатели и массивы Продолжим рассмотрение нерегулируемых указателей. Интересно соотносятся между собой указатели и массивы. Пусть имеем массив int и указатель, указывающий на какой-то объект типа int : int После объявления значение указателя никак не определено, как не определено и значение любой переменной (под них компилятор только выделяет соответствующую память. Настроим указатель на массив А. Адрес первого элемента массива занесем в указатель Как мы видели ранее, Pa+i будет указывать на й элемент массива, те. можно достать такой элемент из массива путем выполнения оператора int Но по определению массива мы можем записать int Мы говорили ранее, что массив элементов строится в языке С так, что его имя — это адрес первого элемента массива, в нашем случае и Pa=&A[0] 110 Часть I. Изучение языка С/С++ Следовательно: Pa=A Pa+i = Более того, хотя Ра — это просто переменная, содержащая адрес, но когда она содержит адрес массива, то можно писать Ра[i]=A[i] компилятор обрабатывает эту конструкцию, те. обращаться к элементам массива можно через индексированный указатель. Пример программы, демонстрирующей вышесказанное, приводится в листинге 7.2 все пояснения даны по тексту программы, результат работы программы — на рис. 7.2. Заметим, что программа строится в среде CLR, хотя содержит нерегулируемые указатели (мы раньше говорили о том, что среда при работе в режиме CLR узнает "родные указатели. Листинг 7.2 // 7.2_2011.cpp #include "stdafx.h" #include #include #include #define maxline 1000 #define eof -1 Ввод строки с клавиатуры- int getline(char s[],int lim) Здесь getline() изменена, чтобы отлавливала Ctrl+z { int c,i; for(i=0; i { int A[maxline]={0,1,2,3,4,5,6,7,8,9}; инициализация массива int *Pa=&A[0]; настройка указателя на массив Глава 7. Работа с указателями и структурами данных char s[maxline]; для ввода номера элемента массива int c; do для обеспечения цикличности ввода номеров элементов { printf("Enter the element's number <0-9> >"); запрос на ввод номера элемента getline(s,maxline); ввод номера элемента как строки символов int i=atoi(s); преобразование номера элемента в число printf("i=%d A[i]=%d *(Pa+i)=%d *(A+i)=%d %d\n", i, A[i], *(Pa+i), *(A+i), Pa[i]); } while((getline(s,maxline) != eof)); для обеспечения цикличности ввода номеров элементов признак конца цикла ввода — Ctrl+z*/ _getch(); задержка изображения на экране } Рис 7.2. Результат работы программы листинга Операции над указателями Над указателями, содержащими адрес одного итого же объекта, можно выполнять определенные операции операции отношения ( > , < и т. д. Например, P и указывают на массив А[] Тогда имеет смысл операция P < Q . Это говорит о том, что P указывает на элемент с меньшим индексом, чем Q . Тогда имеет смысли разность Q — Р, которая определяет количество элементов между Р и Q ; операции равенства и неравенства ( == , != ); указатель можно сравнивать с Все остальные арифметические операции к указателям неприменимы. Указатели и аргументы функций Мы видели, что аргументы в функцию можно помещать либо передавая их значения, либо — ссылки на эти значения (те. адреса. В последнем случае значения переданных по ссылке переменных могут быть изменены в теле функции. Приме 112 Часть I. Изучение языка С/С++ ром этого может служить программа, текст которой приводится в листинге 7.3 (результат работы программы — на рис. 7.3). Листинг 7.3 // 7.3_2011.cpp #include "stdafx.h" #include #include { int i=*a; *a=*b; *b=i; return(0); } int main() { int c=12; int d=120; printf("The (c,d)'s value before function application: c=%d,d=%d\n",c,d); f(&c,&d); передача адресов переменных printf("The (c,d)'s value after function application: c=%d d=%d\n",c,d); _getch(); } Рис 7.3. Результат работы программы из листинга В этой функции аргументы объявлены как указатели, следовательно, при обращении к такой функции ей надо передать адреса переменных, а не их значения. А поскольку мы передали адреса переменных, тов теле функции по этим адресам можно изменять содержимое самих переменных. Например, когда мы пишем *a=*b; , это и есть работа с адресами. Глава 7. Работа с указателями и структурами данных Указатели символов и функций Символьная константа или массив символов в языке С — это строка символов с признаком конца (символом '\0' ). Если, например, имеем char a[10]; , то а — это указатель на первый элемент массива Если, с другой стороны, имеем char *p=&a[0] , то наряду с инициализацией a[]="abc"; можем записать Компилятор в обоих случаях, начиная с адреса, помещенного в указатель р, разместит символы a , b , Следовательно, оперирование именем массива и указателем на этот массив равносильно. Но за исключением некоторого небольшого обстоятельства если мы хотим записать строку символов в некоторое место памяти, то при объявлении char a[100]; компилятор выделит 100 байтов для помещения строки символов, и мы сможем записать в массив a[] свои символы. Если же объявить указатель char *p , то, чтобы записать символы, начиная с адреса, указанного в р, указатель должен быть предварительно инициализирован, те. ему должен быть присвоен некий адрес, указывающий на участок, где будут располагаться объекты типа char . Этот участок должен быть получен либо с помощью функции malloc() , которая возвратит указатель на выделенный участок, после чего значение этого указателя надо будет присвоить указателю р, либо вы должны объявить массив символов размерности, соответствующей вводимой строке, и настроить указатель на этот массив. После этого можно работать с указателем. Кстати, функции тоже могут быть "указателями, те. возвращать указатели на объекты заданного типа. В этом случае функция при ее объявлении имеет вид тип объекта, на который указывает указатель имя функции>(аргументы функции) Например, рассмотрим функцию char Приведем в качестве примера программу, работающую с указателями символьного типа (листинг 7.4). Программа содержит функции работы с символьными строками, в которых отражена работа с указателями. Листинг 7.4 // 7.4_2011.cpp #include "stdafx.h" #include #include #include #include #include 114 Часть I. Изучение языка С/С++ #define maxline 1000 #define eof -1 //Ctrl+z Ввод строки с клавиатуры- int getline(char s[],int lim) { int c,i; for(i=0; i { int i=0; while((s[i]=t[i])!='\0') i++; } Копирует строку, на которую указывает указатель t, в строку, на которую указывает указатель s */ void strcpy2(char *s,char *t) { while((*s=*t) != '\0') { s++; t++; } } //--------------------------------- выделяет по malloc() память по длине строки, на которую указывает s, ив эту память помещает саму строку, а затем выдает указатель на начало помещенной в буфер строки char *strsave(char *s) { char *p; int i=strlen(s)+1; p=(char *)malloc(i); if((p != NULL)) strcpy2(p,s); копирует строку в кучу Глава 7. Работа с указателями и структурами данных return(p); возвращается указатель на строку, помещенную в кучу /* т. к. malloc() выдает указатель типа void, то принудительно приводим его к типу char, чтобы согласовать с р */ } //------------------------------------------------------------- int main() { Проверка strcpy1() printf("Enter string for strcpy1 >"); char s[maxline],t[maxline]; getline(t,maxline); ввод строки с клавиатуры в t strcpy1(s,t); printf("inp.string=%s\nout.string=%s\n",t,s); Проверка strcpy2() printf("Enter string for strcpy2 >"); getline(t,maxline); ввод строки с клавиатуры в t strcpy1(&s[0],&t[0]); printf("input string=%s\noutput string=%s\n",t,s); Проверка strsave() printf("Enter string for strsave>"); getline(s,maxline); ввод строки в s char *p=strsave(&s[0]); в р указатель на память, куда записана строка из s printf("Saved string=%s\n",p); _getch(); } Функция void strcpy1(char s[],char t[]) копирует массив в массив. Это происходит поэлементно с помощью оператора цикла while , в его заголовочной части находится выражение, которое надо вычислить, чтобы принять решение о продолжении завершении цикла. При вычислении выражения происходит поэлементная пересылка пересылается один элемент, затем начинает работать тело while , в котором всего один оператор наращивания индекса элемента массива. Когда тело завершается, управление передается в заголовочную часть while , где снова вычисляется выражение, обеспечивающее пересылку следующего элемента массива в массив s , и т. д, пока не будет переслан последний символ ' \0' . Когда это произойдет, результат выражения станет равным нулю, и функция завершится. Функция не возвращает никакого значения (ее тип void ), но через параметр, адрес которого передается в функцию массив, возвращает копию массива Параметрами функции void strcpy2(char *s,char *t) являются указатели типа char . Эта функция копирует символы, начиная с адреса, на который указывает указатель, в область, на которую указывает указатель s 116 Часть I. Изучение языка С/С++ Перед применением этой функции (как отмечалось ранее) надо определить область памяти, на которую указывает указатель s : либо через массив, либо через функцию malloc() . В теле strcpy2() организован цикл посимвольного перемещения символов из одной области в другую с помощью указателей этих областей указатель *t передает элемент входной области, а указатель *s — выходной. После того как первый элемент перешлется, в теле while произойдет приращение значений указателей на единицу, которое позволит обратиться к следующему элементу объекта. Программа снова возвратится в заголовочную часть while , обеспечивающую пересылку второго элемента в область, на которую указывает s (и т. д, пока не будет переслан последний элемент — признак конца массива. При этом значение выражения в заголовочной части while станет равным нулю, и оператор while завершит работу. Функция char *strsave(char *s) копирует строку символов, на которую указывает указатель s , в буфер памяти, выделяемый стандартной функцией malloc() , и возвращает указатель на начало этого буфера, чтобы в дальнейшем можно было извлекать сохраненную в нем строку. Копирование производится с помощью ранее рассмотренной функции Работа происходит следующим образом длина исходной строки с учетом признака конца строки определяется с помощью стандартной функции strlen() . Затем по функции malloc() выделяется участок памяти такого же размера, как длина входной строки. Перед тем как начать копирование с использованием указателя, возвращенного функцией malloc() , проверяется, успешно ли сработала эта функция те. действительно ли выделено место в памяти. Это существенный вопрос, т. к. свободной памяти в данный момент могло не оказаться или мог произойти какой- либо сбой. Если память выделена, то malloc() возвращает ненулевой указатель типа void , который требуется привести к типу char , т. кв выделенном буфере будет размещен объект типа char . После выделения памяти происходит собственно копирование в буфер и возврат указателя на область копирования. При пояснении основной программы стоит обратить внимание только на проверку strcpy2() . Так как эта функция работает с указателями, то ей мы и передаем указатели адреса первых элементов массивов. Результат расчета приведен на рис. 7.4. Рис 7.4. Результат выполнения программы листинга 7.4 Глава 7. Работа с указателями и структурами данных Передача в качестве аргумента функции массивов размерности больше единицы До сих пор мы передавали в качестве аргумента функции только одномерный массив. Можно передавать и массивы большей размерности. Если, например, двумерный массив передается в качестве аргумента функции, то его описание, как аргумента функции, может быть следующим int m[2][13]; int Здесь указывает на начало массива, поэтому компилятору достаточно знать только количество столбцов массива и начало его первого элемента. Другой вариант описания int (Здесь m указывает на начало массива. Массивы указателей Мы видели, что с помощью массива, объявленного, например, как char M[n][m] , можно задавать множество символьных строк постоянной длины. Иначе и не задать, потому что компилятор не сможет найти заданный элемент массива. Однако в жизни чаще всего приходится работать со строками переменной длины. Тогда жесткая конструкция двумерного массива для их хранения не подойдет. Для решения этой проблемы существует конструкция, называемая массивом указателей. Создается одномерный массив, элементами которого служат указатели на заданный тип данных. Например, массив char *s[10]; — это десять указателей ( s[0],s[1],...,s[9] ), каждый из них указывает на строку, которая может быть переменной длины. Такой массив формируется так в некоторой памяти размещается первая строка, ее адрес заносится в s[0] . Затем размещается вторая строка, ее адрес заносится в и т. д. Чтобы обратиться к элементам такого массива, нужно воспользоваться определением указателя. Обратиться к нулевому элементу нулевой строки следует как *s[0] , к первому элементу той же строки как *s[0]++ и т. д. К нулевому элементу первой строки нужно обратиться как *s[1] , к ее первому элементу как и т. д. Инициализация массива указателей на строки символов, например, char будет выглядеть так char Первая строка символов, "Вторая строка символов, Третья строка символов"}; В чем же различие между записями, например, int n[10][20] и int *b[10] ? Под первый вариант компилятор выделяет 200 единиц памяти. И поиск элемента этого массива производится путем вычисления обычных прямоугольных индексов. 118 Часть I. Изучение языка С/С++ При втором варианте (если предположить, что и там строки содержат по 20 элементов) под них также будет выделено 200 единиц памяти, но еще понадобится память для хранения десяти указателей. То есть памяти при втором варианте размещения данных требуется больше. Но это неудобство перекрывается тем, что в таких конструкциях можно хранить строки переменной длины, и что доступ к таким строкам происходит напрямую — по их адресам, без вычисления индексов массивов. Указатели на функции В языке С возможно определять указатели на функции и, следовательно, обрабатывать указатели, передавать их в качестве аргумента другим функциями т. д. При объявлении указатель на функцию записывается в виде Тип возвращаемого функцией значения (имя функции) (список параметров) Например: int (*comp)(char s1,char Это указатель на функцию comp(s1,s2) , которая возвращает результат типа Если мы подействуем операцией разыменования ( * ) на этот указатель (по определению указателя записав воздействие в виде (*comp)(s1,s2) ), то функция выполнится и возвратит некое целое число. Ранее мы видели, что имена массивов можно передавать в качестве аргументов функции. Теперь, поскольку есть указатели на функции, функции можно передавать в качестве аргументов другим функциям. В таких функциях их аргументы- функции описываются как указатели на функции, а передача функций в качестве аргументов происходит указанием имени самой функции Все остальное улаживает компилятор. То есть с функциями, при передаче их в качестве аргументов другим функциям, происходит тоже, что и с массивами их имена считаются внешними переменными. Приведем пример функции gener() , которая в качестве своих параметров имеет две функции ввода строки символов и подсчета количества ненулевых битов в целом числе (листинг 7.5). Листинг 7.5 // 7.5_2011.cpp #include "stdafx.h" #include #include #include #define eof '?' #define maxline 1000 Глава 7. Работа с указателями и структурами данных //--- Функция подсчета количества битов в целом числе int bitcount(unsigned int n) { int b; for(b=0; n != 0; n>>=1) if(n & 01) //01 — восьмеричная единица b++; return(b); } Ввод строки с клавиатуры int getline(char s[],int lim) { int c,i; for(i=0; i { char s[maxline]; int lim=100; printf("Enter any number >\n"); (*getline)(s,lim); unsigned int n=atoi(s); n=(*bitcount)(n); return(n); } //------------------------------------------------------------- int main() { int n=gener(getline,bitcount); printf("The amount of ones in the input n=%o\n",n); _getch(); return 0; } Мы уже знакомы с функциями getline() и bitcount() (последнюю составляли, когда изучали операции сдвига — она подсчитывает в целом числе без знака количество единиц (ненулевых битов. 120 Часть I. Изучение языка С/С++ Рассмотрим вызывающую функцию gener() . Мы видим, что оба ее аргумента описаны как указатели на функции первый — на функцию getline() , второй — на bitcount() . Затем идет выполнение первой переданной в качестве аргумента функции. Чтобы заставить выполниться функцию, находящуюся по адресу, который содержится в указателе getline , надо подействовать на него операцией разыменования (по определению указателя. Получим Функция getline() выполнится и результатом ее работы станет введенное в строку число, которое переводится в беззнаковое n c помощью функции atoi() . После этого выполнится функция bitcount() , тоже описанная как указатель. Результат ее работы и возвращается в качестве результата функции gener() . Результат расчета показан на рис. 7.5. Рис 7.5. Результат расчета программы листинга Структуры. Объявление структур Структуры — это такие конструкции языка С, которые объединяют в себе данные разных типов, в том числе и подструктуры (такие же структуры, но являющиеся членами главной структуры. Эти конструкции полезны тем, что во многих ситуациях позволяют группировать связанные данные таким образом, что сними можно работать как с единым целым. Как объявляется структура, покажем на примере объявления данных некоторого человека struct man { char name[80]; имя char phone_number[80]; телефон int age; возраст int height; рост Так задается шаблон будущего экземпляра структуры. Здесь man — имя шаблона. То, что находится в теле, ограниченном фигурными скобками, — это члены струк- туры-шаблона (под такое объявление компилятор память не выделяет. На основе такого шаблона создается экземпляр структуры, под который память уже выделяется и с которым можно работать в программе. Чтобы начинающему изучать Сбыло более понятно, скажем, что объявленный шаблон — это тип "структура. А имея Глава 7. Работа с указателями и структурами данных такой тип, станем объявлять данные этого типа. Точно также, как имея, например, тип int , начинаем объявлять данные типа Экземпляры структуры создаются несколькими путями по шаблону man : struct man Здесь созданы два экземпляра структуры один — это массив структур (каждый элемент такого массива представляет собой структуру шаблона man . Можно сказать так friends — это переменная типа man ), другой — обычный экземпляр по шаблону man . В языке С+ ключевое слово struct можно опускать, те. в С+ уже пришли к аналогии с простыми типами данных там экземпляр структуры уже можно объявить так man friends[100],others; Видите сходство с объявлением простых переменных И friends и others объявлены как переменные типа man ; при объявлении шаблона struct man { char name[80]; char phone_number[80]; int age; int height; Здесь создан один экземпляр структуры — others ; с помощью квалификатора типа typedef , который изменяет имя шаблона и позволяет воспользоваться новым именем в качестве типа данных. Этот квалифи- катор позволяет вводить в объявления имена-синонимы, более удобные вис- пользовании в программе. Например, можно записать объявление некой переменной в виде typedef int step; и далее в программе при объявлении какой-то переменной писать вместо int ее синоним step: step aa; . Компилятор потом все вернет на свои места. Поэтому же принципу можно сократить работу со структурой, объявив, например, typedef struct { char phone_number[80]; int age; int То есть заменили всю структуру на один тип Теперь можно писать aa d1,d2[20],*p; . Здесь объявлено три переменных типа aa : экземпляр d1 структуры шаблона man , массив структур d2[20] и р — указатель на структуру. 122 Часть I. Изучение языка С/С++ При объявлении шаблона структуры, члены- данные структуры объявляются такого же формата, как если бы они были вне структуры тип, имя, точка с запятой. Приведем пример вложенной структуры, те. такой структуры, которая является членом другой структуры struct date { int day; день недели int month; номер месяца int year; год char monthname[4]; название месяца }; struct person { char name[30]; имя char adress[70]; домашний адрес long mailcod; почтовый код float salary; заработная плата struct date birthdate; дата рождения struct date hiredate; дата поступления на работу Это типичный пример объявления личной карточки работника (реальная карточка содержит намного больше данных. Здесь объявлен указатель на структуру и массив структур шаблона person . Если такой массив заполнить, то получим данные на 1000 работников. Указатель на структуру — это не экземпляр структуры (экземпляр структуры объявляется как emp[]), а указатель, которому в дальнейшем будет присвоен адрес некоторой структуры, с ее элементами можно будет работать через указатель. |