Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
DOROGOVA.pdf
Скачиваний:
245
Добавлен:
05.06.2015
Размер:
853.4 Кб
Скачать

Вопросы для самопроверки

1.Что такое функция и для чего она используется.

2.Определение, прототип и вызов функции: поясните эти понятия.

3.Что определяет тип функции?

4.Сколько значений может возвращать функция?

5.В каком случае можно обойтись без прототипа функции.

6.В каком случае нельзя обойтись без прототипа функции.

7.Что такое класс памяти, какие характеристики переменных он определяет?

8.Какие классы памяти Вы знаете?

9.Назовите основное отличие между статическим и автоматическим классом памяти.

10.К какому классу памяти относятся переменные, определенные в теле функции?

11.Поясните понятия: формальные параметры функции, фактические параметры функции.

12.Приведите пример передачи параметров в функцию по значению.

13.В каких случаях передача параметров по значению не дает нужного результата?

14.В каких случаях в качестве формальных параметров функции нужно использовать указатели?

15.Приведите два способа передачи массива в функцию.

16.Опишите механизм обращения из функции к переменной в вызывающей программе.

17.Что такое "ссылочный тип" данных, для каких целей используются ссылки?

18.Что общего можно найти между ссылкой и указателем?

19.Какие различия существуют между ссылкой и указателем?

20.Приведите пример передачи параметров в функцию по ссылке.

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

9. Пользовательские типы данных

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

В отличии от простых типов, которые заранее определенны в языке С, сложные типы определяет сам пользователь (отсюда и название пользовательские типы). В стандарте языка С имеется четыре

"базовых кирпичика" из которых строится, вся работа по построению пользовательских типов:

структуры (structures);

объединения, или союзы (unions);

битовые поля (bit fields);

перечисления (enumerations).

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

9.1. Структурный тип данных

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

В простейших программах, например в наших примерах, каждый элемент данных представлен в виде переменной, определенной простым типом float, int, char или как строка. Но при программировании более сложных задач приходится иметь дело с объектами, которые содержат не один, а несколько типов данных - в этом случае используется структурный тип данных.

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

int day;

// день

char month[15];

// месяц

int year;

// год

Однако этими тремя переменными мы собираемся пользоваться не по отдельности, а как единым понятием "дата", то есть требуется объединить данные разных типов в единое множество, в нашем случае - это две переменные типа int и текстовая строка, и такая совокупность данных называется структурой. Дадим более точное определение структурного типа данных.

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

9.1.1. Определение структуры

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

дает имя структуре, которое является идентификатором нового типа данных;

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

Определение структуры имеет следующий формат: struct тип

{тип элемента_1 имя элемента_1;

.........

тип элемента_n имя элемента_n; };

Именем элемента может быть любой идентификатор.

Пример: определение нового типа date , состоящего из трёх полей (элементов):

sruct date

 

{ int day;

// день

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

char month[15];

// месяц

int year;

// год

};

 

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

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

Написав определение структуры, можно пользоваться новым типом данных - создавать переменные, указатели, массивы, и тому подобное. Название нового типа в нашем примере будет sruct date.

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

Пример: Выделение памяти под переменную days типа struct date

struct date days;

После такого определения компилятор выделяет память под структурную переменную days, которая состоит из двух переменных типа int и массива из 15 элементов типа char.

Выделение памяти под структурные объекты можно совместить с определением структурного типа.

Пример: struct date

{int day;

char month[15]; int year;

} a, b, c;

При этом выделяется соответствующая память под три переменные - a, b и c.

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

Пример: Объявляется указатель на структуру с имененм p_date и переменная birthday. struct date *p_date, birthday;

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

struct tovar

 

{

 

char* name ;

/ / наименование

double price ;

/ / цена

int vol ;

/ / количество

date in_date ;

/ / дата поступления

} food;

 

Такое определение создает новый структурный тип tovar и выделяет память под структурную переменную с именем food. Переменные, подобные food называют объектами структурного типа или же структурными переменными, но чаще всего - структурами. Из-за этого может возникнуть некоторая терминологическая путаница, так как имя нового структурного типа тоже можно назвать структурой. Эти два понятия различают по-контексту, структурный тип создается один раз, и затем используется при создании программных объектов. Сами же объекты (структуры) создаются каждый раз, когда необходимо выделить память для размещения данных.

Примеры использования нового типа struct tovar для создания различных объектов:

struct tovar book;

// переменная-структура с именем book

struct tovar sklad2[1000];

// массив структур с именем sklad2

struct tovar *poin_f; // указатель на структуру tovar с именем point_f.

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

В следующем примере инициализируются переменные birthday (тип sruct date) и food (тип sruct tovar).

char name[]= "Масло растительное"; sruct date birthday={20, "февраля", 1975}; sruct tovar food ={&name, 20.6, 50, \

{11, "февраля",2005}

}

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

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

Пример: В примере определяется структура man, для хранения информации о человеке - имя, фамилия, дата рождения, ИНН.

struct man

{ char name[20], fam[30]; struct date bd;

int inn; };

Структура man включает четыре элемента name, fam, bd, inn. Первые два - name[20] и fam[30] - это символьные массивы различной длины, переменная bd представлена составным элементом (вложенной структурой) типа date, а переменная inn - это целое число (имеет тип int).

Напомним, что определенный выше тип data имеет три элемента: day, month, year. Теперь можно определить объекты, значения которых принадлежат новому типу:

struct man list[100];

Здесь определен массив list, состоящий из 100 структур типа man.

9.1.2. Оператор typedef

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

Синтаксис typedef-объявления: typedef тип имя;

Пример: Введение нового имени типа для обозначения целого числа. typedef int INTEGER;

После такого объявления можно создавать целые числа, используя новое имя типа: INTEGER а, b;

Оно будет выполнять то же самое, что и привычное объявление int a,b;

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

При работе со структурами оператор typedef определяет структурный тип и присваивает ему

обозначение (имя):

 

typedef struct tovar

 

{ char* name ;

/ / наименование

double price ;

/ / цена

int vol ;

/ / количество

date in_date ;

/ / дата

} sklad_1 ;

 

Ниже для одной и той же структуры введены два имени:

-tovar стандартным образом

-sklad_1 с помощью оператора typedef

Теперь структурные объекты могут определяться как с помощью типа sklad_1 , так и с помощью

обозначения struct tovar

Пример:

//три структуры (tea, meat, broad) типа sklad_1 sklad_1 tea, meat, broad ;

//две структуры (pen, book) типа struct tovar

struct tovar pen, book ;

Обратите внимание: при введении структурного типа с помощью typedef в определении объектов не указывается спецификатор struct.

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

Следующий вариант определения структур является некоторым упрощением приведенных выше вариантов. Можно определять структуры, вводя "внутреннее строение" (то есть элементы структуры), но, не вводя его имени. Такой безымянный структурный тип используется для однократного определения структур.

Пример: Определить структуру для введения данных о компьютере. struct

{ char processor [10] ;

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

int frequency ; int memory ; int disk ;

} IBM , DEC, COMPAQ ;

После такого определения программист может работать со структурными объектами IBM, DEC, COMPAQ, но не может вводить в программу новые объекты. Если все же понадобиться создание дополнительных объектов, придется полностью повторить приведенное выше определение структурного типа.

9.1.4. Доступ к элементам структуры

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

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

Для доступа к полям структуры используются два специальных оператора:

. (точка), когда работают с именем структуры; -> (минус и знак "больше"), когда работают с указателем на структуру.

Заметим, что у операций доступа к структуре ("точка", "стрелка") самый высокий приоритет наряду со скобками, поэтому в выражениях сначала выполняется доступ к полю структуры, а затем проводятся необходимые вычисления, иными словами поля структур участвуют в вычислениях так же как обычные переменные.

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

Пример: Обращение к полям структуры с использованием оператора "точка" и через указатель. #include <stdio.h>

#include <string.h>

void main()

 

{struct date

 

{ int

day;

char month[15]; int year;

}; // объявление объектов структуры

sruct date s_day={10, "september",1988}, e_day, *p1, *p2; /*Использование операции «точка» */

e_day. day

 

= 22;

 

memcpy (e_day.month, s_day.month,15);

 

e_day. year

 

= s_day. year;

 

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

 

p1 = &s_day;

// установка указателей на

 

p2 = &e_day;

// переменные s_day и e_day

 

p2->day

= 22;

 

 

memcpy (p2->month, p1-> month,15);

 

p2-> year

= p1-> year;

 

printf("\ne_day: day=%d\tmonth=%s\tyear=%d\n", e_day. day, e_day. month, e_day. year);

}

 

 

 

Определены следующие объекты структуры:

 

Переменные

s_day и e_day, причем для

s_day одновременно с созданием проведена

инициализация;

Указатели на структуру с именами p1 и p2;

В примере выполнены одинаковые действия двумя различными способами - с применением имен объектов и с безимянным использованием через указатели. Оператор e_day.day=22 записывавет число 22 в

поле day переменной e_day. Обратите внимание, что перед точкой стоит не имя типа структуры, а имя объекта, для которого выделена память. В поле e_day.year заносим элемент другой структуры - s_day. year. Третье поле e_day.month представляет собой строку текста. Как известно, в стандарте языка С нет встроенных средств работы со строками, язык С позволяет работать только с отдельными символами строки, поэтому для работы с целыми строками приходиться использовать библиотечные функции. В

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

нашем примере используется функция memcpy() из библиотеки string.h, которая копирует 15 символов из строки s_day.month в строку e_day.month.

Работа с полями структуры - через указатель аналогична. Следует особо отметить, что правила работы с отдельными полями структуры ничем не отличаются от правил работы с переменными, то есть на первом этапе следует определить указатель соответствующего типа, установить его на объект, а затем обращаться к объекту через указатель. Обращение к данным структуры выглядит следующим образом:

для обращения к полю day структуры e_day: p2->day = 22;

для копирования строки month из структуры s_day в структуру e_day : memcpy(p2->month, p1-> month,15).

При работе со структурами можно использовать комбинацию доступа через указатель и операцию "точка".

(*p2) . daу

= 22;

memcpy((*p2) .month, = (*p1) .month,15); (*p2) . year = (*p1) .year;

Здесь используется операция разименования указателя (*) для доступа к переменной структурного типа, а затем через операцию "точка" – получаем доступ к полю структуры. Следующий пример демонстрирует доступ к вложенным структурам. В структурном типе tovar поле in_date представляет собой элемент структурного типа date, то есть структурный тип date вложен в структурный тип tovar.

Пример:

…..

sruct tovar tea, sklad[1000], *pt;

tea. in_date. day

= e_day. day;

tea. in_date. year

= 1985;

//установка указателя на 5 элемент массива

pt = &sklad[5];

 

pt->vol

= 20;

pt->in_date.day

=25;

(*pt). in_date. year

=2000;

Обращение к элементу внутренней (вложенной) структуры типа date проходит в два этапа, сначала выбирается поле структуры tea (в данном случае структура in_date), а затем поле структуры in_date (например, day). Во вложенных структурах также возможна работа с операцией "точка" и указателем. В нашем примере это:

tea. in_date. day= e_day.day;

pt = &sklad[5];

pt ->in_date.day=25;

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

Пример: Инициализация структуры с выводом на экран ее элементов. #include < stdio.h >

void main()

// определение структурного типа

{struct tovar

{ char* name ;

/ / наименование

double price ;

/ / цена

int vol ;

/ / количество

date in_date ;

/ / дата

} ;

 

struct tovar meat={"баранина", 90.78 , 15 , 220 , {15, "января" , 2005}} ; printf (“\nНаименование:\t%s ” , meat. name) ;

printf (“\nЦена поставки:\t%5.2f руб.” , meat. price) ; printf (“\nКоличество:\t%d Кг” , meat. vol) ;

printf (“\nДата прихода:|

\t%d %s %d” , meat. in_date. day, meat. in_date.month, meat. in_date.year) ;

}

Результат выполнения программы:

Наименование:

Баранина

Цена поставки:

98.78 руб.

Количество:

220 Кг

Дата прихода:

15 января 2005

Что еще можно делать со структурой? Разумеется, передавать в функцию в качестве параметра и возвращать результат. Однако здесь есть одна особенность - нельзя передавать в функцию и возвращать из нее саму структуру, то есть работать со структурой "по значению", как с переменной. Параметр-

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

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

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

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

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

Пример:

#include <stdio.h>

//определение структурного типа COMPLEX typedef struct

{ double re;

// действительная часть числа

double im;

// мнимая часть числа

} COMPLEX;

// запись значений в структуру COMPLEX

void set_complex(COMPLEX* n,double re,double im)

{n->re = re; n->im = im;

}

// сложение двух переменных COMPLEX

COMPLEX* add_complex (COMPLEX* n1, COMPLEX* n2)

{

static COMPLEX result; result.re = n1->re + n2->re; result.im = n1->im + n2->im; return &result;

}

//печать переменной COMPLEX void print_complex(COMPLEX* n)

{ printf("действительная часть:%f\tмнимая часть: %f\n", n->re, n->im);

}

//головная функция программы

void main()

{COMPLEX num1, num2, *ptr; set_complex (&num1, 1.0, 1.0); set_complex (&num2, 3.0, 0.0); ptr=add_complex (&num1, &num2); print_complex (ptr);

}

Новый структурный тип COMPLEX введен с помощью директивы typedef, что позволяет в дальнейшем при обращении к новому типу опускать ключевое слово struct. Определение структурного типа включает два поля - для действительной и мнимой части комплексного числа.

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

Функция add_complex() складывает два комплексных числа и возвращает их сумму в вызывающую программу. Оба параметра и возвращаемое значение - указатели на структурные объекты типа COMPLEX. Обращаю ваше внимание, что возвращаемое значение - это указатель на переменную result, которая определяется в теле функции add_complex(). Для того, чтобы переменная result была доступна из вызывающей программы, она объявляется как статическая переменная, и поэтому не уничтожается при выходе их функции. Оператор return возвращает адрес переменной result, при помощи явного обращения к оператору взятия адреса &.

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

Функция print_complex() выводит на печать комплексное число и комментарий. Функция не возвращает никакого значения, а в качестве параметра ожидает указатель на объект типа COMPLEX (комплексное число).

Обратите внимание, что когда в качестве формального параметра функции объявлен указатель (в нашем примере на структуру), то при вызове функции передается адрес переменной. В первом случае, при вызове функций set_complex() и add_complex() явно, путем передачи адресов переменных num1 и num2, а во втором случае, при вызове функции print_complex() - неявно, путем передачи указателя (в котором уже записан адрес переменной).

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

Для этого, в функции main определяем две переменные для хранения комплексных чисел num1, num2, и указатель *ptr. В дальнейшем, для работы со сложным объектом, каковым является комплексное число, будем использовать соответствующую функцию, в этом случае в вызывающей программе "не видны" подробности строения структурного объекта, то есть, нет обращения к отдельным элементам структуры, все эти подробности "спрятаны" внутри соответствующей функции.

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

9.1.5. Операции присваивания и сравнения для структур

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

Пример: Использование имен массивов без индексации приведет к ошибке. float x[]={1.1 ,2.2, 3.3 , 4.4 , 5.5 }, z[10] ;

int j ;

for ( j=0; j < sizeof(x)/sizeof(x[0]) ; j++ ) z [j] = x[j] ; // правильное использование

 

z = x ;

// ошибка

Выражение sizeof(x)/sizeof(x[0]) дает количество элементов массива (размер всего массива делим на размер одного элемента).

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

Если не обращать внимания на смысл введенной информации, то разрешается следующее присваивание: sklad_1 tea, meat, broad ;

meat = tea ; // ошибки нет

Напротив, операции сравнения для структур, как единого целого не определены, можно сравнивать лишь отдельные элементы структуры.

Напимер, возможны такие логические выражения : tea.price > meat.price

meat.in_date.day != broad.in_date.day ,

но ошибкой будет сравнивать структуры целиком: meat > tea

9.1.6.Определение размера структуры. Оператор sizeof

Спомощью операции sizeof можно определить размер памяти, которая необходима идентификатору или типу. Операция sizeof имеет следующий формат:

sizeof (выражение).

В качестве выражения может быть использован любой идентификатор объекта, либо имя типа, заключенное в скобки. Отметим, что не может быть использовано имя типа void, а идентификатор не может относиться к полю битов или быть именем функции.

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

Когда sizeof() применяется к пользовательским типам данных, то есть имени типа структуры или объединения или к идентификатору, имеющему тип структуры или объединения, то результатом является

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

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