Присвоение начального значения

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

struct CD { char name[20]; char description[40]; char category[12]; float cost; int number; } disc = {"Лучшие песни", "Тини Тим", "поп-музыка", 12.50, 12};

В этих инструкциях мы создали структуру CD, определили переменную disc и присвоили начальные значения всем пяти членам структуры (рис.11.6).

Присвоение начального значения - student2.ru
Рис. 11.6. Инициализация структурной переменной

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

struct CD disc = {"Моя жизнь", "Биография Б. Гейтса", "книга на диске", 24.99, 213};

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

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

static struct CD;

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

Член структуры всегда является частью структуры. Нельзя обратиться к структурному элементу как к таковому. Например, если вы попытаетесь присвоить значение члену cost структуры CD, используя следующий синтаксис

cost = 23.13;

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

structure_variable.member_name

Такая запись означает, что необходимо указать имя структурной переменной, поставить точку, а затем указать имя члена структуры, как это сделано в инструкциях:

gets(disc.name);gets(disc.description);gets(disc.category);disc.cost = 16.95;disc.number = 5;

Эти инструкции говорят компилятору, в какую именно область памяти следует поместить введенное значение, например, в член name структурной переменной disc.

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

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

Листинг 11.1. Использование структуры при вводе и выводе.

/*CD1.c*/struct CD { char name[20]; char description[40];char category[12]; float cost; int number; } disc;main() { puts("Введите сведения о диске\n\n"); printf("Введите название: "); gets(disc.name); printf("Введите описание: "); gets(disc.description); printf("Введите категорию: "); gets(disc.category); printf("Введите цену: "); scanf("%f", &dics.cost); printf("Введите номер ячейки: "); scanf("%d", &disc.number); puts("Введена следующая информация о диске:\n\n"); printf("Название: %s\n", disc.name); printf("Описание: %s\n", disc.description); printf("Категория: %s\n", disc.category); printf("Цена: %6.2f\n", disc.cost); printf("Номер п/п: %d\n", disc.number); }

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

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

struct CD disc[10];

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

gets(disc[0].name);gets(disc[1].name);

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

Листинг 11.2. Использование массива структур.

/*CD2.c*/struct CD { char name[20]; har description[40]; har category[12]; float cost; int number; } disc[10];main() { int index, repeat; har flag; flag = 'Y'; index =0; do { puts("Введите сведения о диске #%d\n", index); printf("Введите название: "); gets(disc[index].name); printf("Введите описание: "); gets(disc[index].description); printf("Введите категорию: "); gets(disc[index].category); printf("Введите цену: "); scanf("%f", &dics[index].cost); printf("Введите номер ячейки: "); scanf("%d", &disc[index].number); index++; if(index < 10) { printf("Желаете ввести данные о следующем диске? Да -Y, нет -N"); scanf("%C", &flag); } } while (index < 10 && (flag == 'Y' || flag == 'y')); puts("Название Номер п/п"); for (repeat = 0; repeat < index; repeat++) printf("%s %d\n", disc[repeat].name, disc[repeat].number); }

Присвоение начального значения - student2.ru
Рис. 11.7. Массив структур

Определение массива, включающего 10 элементов disc, является частью определения структуры:

} disc[10];

Переменная flag ведена для того, чтобы пользователь мог указать, хочет ли он продолжить ввод информации, если заполнено меньше 10«карточек». Цикл do обеспечивает ввод элементов массива: пять пунктов (членов структуры) для каждого элемента массива (регистрационной карточки в картотеке коллекции). Переменная index используется как индекс в процессе создания картотеки.

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

В цикле do используется следующее условие:

while (index < 10 && (flag == 'Y' || flag == 'y'));

Логический оператор И указывает на то, что оба условия должны выполняться одновременно: значение переменной index должно быть меньше десяти, и переменная flag должна иметь значение 'Y' или 'y'. Если бы в условии не были поставлены внутренние скобки, компилятор Си скомбинировал бы первые два условия и трактовал всю инструкцию как условие ИЛИ (рис.11.8). И если в этом случае переменная flag примет значение 'y', цикл будет повторяться и после ввода значений всех элементов.

Присвоение начального значения - student2.ru
Рис. 11.8. Интерпретация условия при отсутствии внутренних круглых скобок

Когда будут введены данные последнего элемента массива (карточки), значение переменной index окажется на единицу больше количества элементов массива. Эта переменная в дальнейшем используется в цикле for, в котором на дисплей выводится информация о содержании компакт-дисков и их местонахождении.

Обратите внимание на то, что все обращения к членам структуры включают имя переменной c указанием индекса массива.

Структуры и функции

В исходном K&R-стандарте языка Си использование структур ограничено. Структуры могут передаваться в качестве аргументов только с использованием указателей, о которых мы будем говорить дальше в этой главе. Кроме того, отсутствует возможность прямого присваивания одной структуры другой, например, следующим образом:

cdrom = disc;

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

Большинство компиляторов Си++ и компиляторы Си, поддерживающие стандарт ANSI, позволяют передавать и возвращать структуру целиком. В Листинге11.3 продемонстрировано, как можно передать структуру функции. Значения членов структуры вводятся в main(), затем вся структура передается функции putdisc() для вывода. При вызове функции используется структурная переменная disc:

putdisc(disc);

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

putdisc(disk)struct CD disk;

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

Листинг 11.3. Передача структуры.

/*CD3.c*/struct CD { char name[20]; char description[40]; char category[12]; float cost; int number; } disc;main() { puts("Введите сведения о диске\n\n"); printf("Введите название: "); gets(disc.name); printf("Введите описание: "); gets(disc.description); printf("Введите категорию: "); gets(disc.category); printf("Введите цену: "); scanf("%f", &dics.cost); printf("Введите номер ячейки: "); scanf("%d", &disc.number); putdisc(disc); }putdisc(disk)struct CD disk; { puts("Введена следующая информация о диске:\n\n"); printf("Название: %s\n", disc.name); printf("Описание: %s\n", disc.description); printf("Категория: %s\n", disc.category); printf("Цена: %6.2f\n", disc.cost); printf("Номер п/п: %d\n", disc.number); }

В Листинге 11.4 демонстрируется, как функция возвращает структуру. Члены структуры вводятся в функции getdisc(), а затем передаются назад в main() с помощью инструкции

return(inputdisc);

Обратите внимание, что тип записи структуры (CD) используется и в определении функции, и в определении переменных функции getdisc(). Это вызвано тем, что при возврате переменных, отличных от типов int или char, тип переменной следует указать в определении функции. Типом возвращаемой переменной в данном случае является структура CD, так что функция определяется следующим образом:

struct CD getdisc();

Структура, используемая в функции, также относится к типу CD, поэтому она определяется как:

struct CD inputdisc;

Обратите также внимание на то, что функция getdisc() определяется как глобальная, вместе со структурной переменной disc. Такой тип определения требуется в тех случаях, когда функция возвращает структуру.

Листинг 11.4. Возвращение структуры.

/*CD4.c*/struct CD { char name[20]; char description[40]; char category[12]; float cost; int number; } disc, getdisc();main() { disc = getdisc(); puts("Введена следующая информация о диске:\n\n"); printf("Название: %s\n", disc.name); printf("Описание: %s\n", disc.description); printf("Категория: %s\n", disc.category); printf("Цена: %6.2f\n", disc.cost); printf("Номер п/п: %d\n", disc.number); }struct CD getdisc() { struct CD inputdisc; puts("Введите сведения о диске\n\n"); printf("Введите название: "); gets(inputdisc.name); printf("Введите описание: "); gets(inputdisc.description); printf("Введите категорию: "); gets(inputdisc.category); printf("Введите цену: "); scanf("%f", &inputdiscdics.cost); printf("Введите номер ячейки: "); scanf("%d", &inputdisc.number); return(inputdisc); }

Указатели

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

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

Когда вы используете оператор присваивания

tax = 35;

вы, тем самым, вносите в элементы памяти некое значение. По адресу, соответствующему области памяти, отведенной для переменной tax, будет содержаться значение 35. Можно вывести на дисплей адрес элемента памяти переменной, если использовать оператор получения адреса. Инструкция

printf("%d", &tax);

отобразит адрес, по которому хранится значение переменной tax, но не само значение, присвоенное переменной и хранящееся в элементах памяти по этому адресу. Однако оператор получения адреса вместе с именем переменной (&tax) может быть использован только в выражении. Он не является переменной, и поэтому инструкция типа

&tax = 25;

будет ошибочной.

Указателем называется переменная, которая содержит значение адреса элемента памяти, где хранится значение другой переменной. Если значение переменной tax хранится в элементе, расположенном по адресу 21260, то и указатель на переменную tax будет иметь значение 21260*.

Чтобы создать указатель, используйте синтаксис, показанный на рис.11.9. Начать следует с определения типа данных, которые хранятся в элементах памяти, определяемых указателем. Символ «звездочка» говорит компилятору, что вы создаете указатель. В конце указывается имя переменной. Например, инструкция

int *taxptr;

создаст переменную типа указатель (об этом говорит звездочка перед именем) с именем taxptr, которая будет содержать адрес некой целочисленной переменной. Инструкция

float *net;

создаст указатель с именем net, который будет содержать адрес переменной типа float.

____________________

* Здесь имеется в виду только смещение переменной относительно начала сегмента данных (при работе в больших моделях памяти). Чтобы отобразить на экране полный адрес, нужно воспользоваться указателем формата "%p", а не "%d", как в приведенном примере. (Прим.перев.)

Присвоение начального значения - student2.ru
Рис. 11.9. Определение указателя

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

taxptr = &tax;

В этих инструкциях мы помещаем адрес, по которому содержится значение переменной tax, в переменную taxptr (рис.11.10). Если значение переменной tax хранится по адресу 21260, переменная taxptr получит значение 21260.

Присвоение начального значения - student2.ru
Рис. 11.10. Присваивание адреса указателю

В качестве примера рассмотрим программу:

main() { int *taxptr; int tax; taxptr = &tax; tax = 35; printf("Значение переменной tax равно %d\n", tax); printf("Указатель переменной tax имеет значение %d\n", taxptr); }

Эта программа выведет на дисплей сообщения:

Значение переменной tax равно 35Указатель переменной tax имеет значение 21260

Возможно, вы спросите: «Ну и что из этого? В конце концов, того же самого результата можно было добиться, присвоив значение адреса переменной tax обычной целочисленной переменной, а не какому-то указателю». Преимущество использования указателя состоит в том, что к нему можно обращаться как к

*taxptr. Звездочка в данном случае будет сообщать компилятору, что мы интересуемся содержимым области памяти, адрес которой является значением указателя. То есть нас интересует не значение 21260, а содержимое памяти по этому адресу. В то время как значение переменной taxptr равно 21260, значение *taxptr равно 35.

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

taxptr = 21260;

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

taxptr = &tax;

Однако можно присвоить значение указателю *taxptr, написав инструкцию

*taxptr = 35;

Эта инструкция означает: «Поместить значение 35 в элемент памяти, на который указывает переменная taxptr». Поскольку указатель содержит значение 21260, число 35 будет размещено в памяти именно по адресу 21260, а так как эту область памяти занимает переменная tax, она приобретет значение 35 (рис.11.11).

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

Присвоение начального значения - student2.ru
Рис. 11.11. Присваивание значения указателю

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

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

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

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

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

/*letcount.c*/main() { char name[20], letter; int number, start; puts("Введите имя\n"); gets(name); printf("Введите символ"); letter = getchar(); countlet(name, letter, &number, &start); printf("\nСимвол %c встречается в имени %s %d раз\n", letter, name, number); printf("Первый раз символ встречается в %d позиции", start); }countlet(ndplume, alpha, count, first)char ndplume[], alpha;int *count, *first; { int index, flag; *count = 0; index = 0; flag = 0; *first = 0; while (ndplume[index != '\0') { if (ndplume[index] == alpha) { *count = *count+1; if (flag == 0) { *first = index+1; flag = 1; } } index++; } }

После ввода строки и символа мы передаем функции countlet() четыре переменных: строку, символ и адреса переменных count и start, определенных с помощью оператора получения адреса.

Функция присваивает адреса получающим указателям: адрес переменной number хранится в *count, а адрес переменной start содержится в *first (рис.11.12).

Присвоение начального значения - student2.ru
Рис. 11.12. Передача адресов функции

Присвоение начального значения - student2.ru
Рис. 11.13. Фигурные скобки отмечают блоки инструкций

Переменные инициализируются путем присваивания нулевого значения, а затем начинает выполняться цикл while:

while (ndplum[index] != '\0')

Цикл проверяет каждый символ в строке и продолжает проверку до тех пор, пока не встретит нулевой символ. Как только выясняется, что очередной символ является нулевым, выполнение цикла прекращается.

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

*count = *count + 1;

Это приводит к тому, что происходит увеличение на единицу значения, содержащегося в области памяти, адрес которой является значением указателя. Так как указатель содержит адрес переменной number, значение number увеличивается на 1, хотя эта переменная является локальной, определенной внутри функции main(). Обратите внимание, что в данном случае нельзя использовать синтаксис

*count++;

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

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

После выполнения условия if значение индекса увеличивается, в результате чего при следующем прохождении цикла будет проверяться следующий символ строки. Весьма существенно, чтобы инструкция index++; помещалась после закрывающей фигурной скобки внешней инструкции if, но перед двумя заключительными закрывающими скобками, одна из которых отмечает конец инструкции while, а вторая завершает функцию. Если указанная инструкция будет помещена в каком-нибудь другом месте, функция не сможет успешно работать.

После завершения выполнения функции countlet() управление передается назад в main(), где осуществляется вывод информации, включающий вывод строки, символа, количества появлений символа в строке и первого случая появления символа в строке.

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

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

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

______________________________

* В этом случае можно использовать инструкцию (*count)++;.(Прим.перев.)

Присвоение начального значения - student2.ru <> Вопросы
  1. Где может быть использован структурный тип данных?
  2. Как определить структуру?
  3. В чем заключается различие между типом записи структуры и структурной переменной?
  4. Как обратиться к элементу структуры?
  5. Может ли структура содержать элементы одного типа?
  6. Что такое указатель?
  7. Если в программе присутствует определение типа float *num, в чем будут выражаться различия между num и *num?
  8. Для чего в языке Си используют указатели?
  9. Каким образом указатели передаются функции?
Присвоение начального значения - student2.ru <> Упражнения
  1. Напишите программу, в которой структура используется для составления инвентарной описи. Информация включает в себя название продукта, цену, количество, имя поставщика.Внесите изменения в программу из упражнения 1 с тем, чтобы можно было вводить информацию в массив структур, состоящий из 20 элементов.
  2. Внесите изменения в программу из упражнения 2 так, чтобы выводить на экран общую стоимость включенных в опись товаров.
  3. Напишите программу, в которой две переменные типа float определяются в main() как локальные, а затем используются в функции, вычисляющей квадраты обоих чисел.
4. Объясните, почему следующая программа написана неверно: main() { struct CD { char description[40]; char category[12]; char name[20]; float cost; int number; } disc; puts("Введите сведения о диске"); printf("Введите название: "); gets(name); printf("Введите описание: "); gets(description); printf("Введите категорию: "); gets(category); printf("Введите цену: "); scanf("%f", &cost); printf("Введите номер ячейки: "); scanf("%d", &number); puts("Введена следующая информация о диске: "); printf("Название: %s\n", name); printf("Описание: %s\n", description); printf("Категория: %s\n", category); printf("Цена: %6.2f\n", cost); printf("Номер п/п: %d\n", number); }

Наши рекомендации