Язык Си - Уэйт, Прата, Мартин. M. уэит с. Прата д. Мартин
Скачать 4.69 Mb.
|
- > позволяет нам добавить два вклада для первого Джонса. Это действительно очень похоже на последний пример. Далее, цикл for увеличивает указатель money на 1. Теперь он ссылается на следующую структуру, jones[1], и остаток вкладов может быть добавлен к total. Вот два основных замечания: 1. Имя массива можно использовать для передачи в функцию указателя на первую структуру в массиве. 2. Затем можно использовать арифметическую операцию над указателем, чтобы передвигать его на последующие структуры в массиве. Заметим, что вызов функции sum(&jones[0]) дал бы тот же самый эффект, что и применение имени массива, так как оба они ссылаются на один и тот же адрес. Использование имени массива является просто косвенным способом передачи адреса структуры. СТРУКТУРЫ: ЧТО ДАЛЬШЕ? Далее Содержание Мы не будем больше рассказывать о структурах, но хотелось бы отметить одно очень важное использование структур: создание новых типов данных. Пользователи компьютеров разработали новые типы данных, гораздо более эффективные для определенных задач, чем массивы и простые структуры, которые мы описали. Эти типы имеют такие названия, как очереди, двоичные деревья, неупорядоченные массивы, рандомизированные таблицы и графы. Многие из этих типов создаются из "связанных" структур. Обычно каждая структура будет содержать один или два типа данных плюс один или два указателя на другие структуры такого же типа. Указатели служат для связи одной структуры с другой и для обеспечения пути, позволяющего вам вести поиск по всей структуре. Например, на рис. 14.9 показано двоичное дерево, в котором каждая отдельная структура (или "узел") связана с двумя, расположенными ниже. РИС. 14.9. Структура двоичного дерева. Является ли эта разветвленная конструкция более эффективной чем массив? 291 Рассмотрим случай дерева с 10 уровнями узлов. Если вы составите его, то найдете 1023 узла, в которых вы можете запомнить, скажем, 1023 слова. Если слова упорядочены, согласно некоторому разумному плану, вы можете начать с верхнего уровня и находить любое слово в лучшем случае за 9 перемещений, если ваш поиск идет сверху вниз с одного уровня на следующий. Если слова находятся в массиве, вам, может быть, придется перебрать все 1023 элемента, прежде чем вы найдете нужное слово. Когда вас интересуют более прогрессивные структуры данных, обратитесь к литературе по вычислительной технике. Используя структуры языка Си, вы сможете создавать типы, о которых вы прочитали. Это наше последнее слово о структурах. Далее мы хотим вкратце ознакомить вас с двумя другими средствами языка Си для работы с данными: объединением и функцией typedef. ОБЪЕДИНЕНИЯ - КРАТКИЙ ОБЗОР Далее Содержание Объединение - это средство, позволяющее запоминать данные различных типов в одном и том же месте памяти. Типичным использованием его было бы создание таблицы, содержащей смесь типов в некотором порядке, который не является постоянным и не известен заранее. Объединение позволяет создавать массив, состоящий из элементов одинакового размера, каждый из которых может содержать различные типы данных. Объединения устанавливаются таким же способом, как и структуры. Есть шаблон объединения и переменные объединения. Они могут определяться одновременно или, если используется имя объединения, последовательно за два шага. Вот пример шаблона с именем объединения: union holders { int digit; double bigf1; char letter; }; А вот пример определения переменных объединения типа holdem: union holdem fit; /* переменная объединения типа holdem */ union holdem save[10]; /* массив из 10 переменных объединения */ union holdem *pu; /* указатель на переменную типа holdem */ Первое описание создаст одну переменную fit. Компилятор выделяет достаточно памяти для размещения самой большой из описанных переменных. В этом случае наибольшей из возможных является переменная double, для которой требуется в нашей системе 64 разряда или 8 байтов. Массив save имел бы 10 элементов, каждый по 8 байтов. Вот как используется объединение: fit.digit = 23; /* 23 записывается в fit; используется 2 байта */ fit.double = 2.0; /* 23 стирается, 2.0 записывается; используется 8 байтов */ fit.letter = 'h'; /* 2.0 стирается, h записывается; используется 1 байт */ Вы применяете операцию получения элемента, чтобы показать, какие типы данных используются. В каждый момент времени запоминается только одно значение; нельзя записать char и int одновременно, даже если для этого достаточно памяти. Вы сами должны следить за типом данных, записываемых в данный момент в объединение; приведенная ниже последовательность операторов показывает, что нельзя делать: 292 fit.lеtter = 'A'; finum = 3.02*fit.double; /* ОШИБКА ОШИБКА ОШИБКА */ Ошибка заключается в том, что записано значение типа char, a следующая строка предполагает, что содержимое fit имеет тип double. Можно использовать операцию - > с объединениями таким же образом, как это делалось для структур: pu = &fit; х = рu -> digit; /* то же, что и х=fit.digit */ Рассмотрим теперь еще одно средство языка для работы с данными. typedef - КРАТКИЙ ОБЗОР Далее Содержание Функция typedef позволяет нам создать свое собственное имя типа. Это напоминает директиву #define, но со следующими тремя изменениями: 1. В отличие от #define функция typedef дает символические имена, но ограничивается только типами данных. 2. Функция typedef выполняется компилятором, а не препроцессором. 3. В своих пределах функция typedef более гибка, чем #define. Посмотрим, как она работает. Предположим, вы хотите использовать термин real для чисел типа float. Тогда вы определяете термин real, как если бы он был переменной типа float, и перед его определением ставите ключевое слово typedef: typedef float real; С этого момента вы можете использовать real для определения переменных: real х, у[25], *рr; Область действия такого определения зависит от расположения оператора typedef. Если определение находится внутри функции, то область действия локальна и ограничена этой функцией. Если определение расположено вне функции, то область действия глобальна. Часто в этих определениях используются прописные буквы, чтобы напомнить пользователю, что имя типа является на самом деле символической аббревиатурой: typedef float REAL; В последнем примере можно было бы применить директиву #define. А здесь это делать нельзя: typedef char *STRING; Без ключевого слова typedef оператор определял бы STRING как указатель на тип char. С ключевым словом оператор делает STRING идентификатором указателей на тип char. Так, STRING name, sign; означает char *name, *sign; 293 Мы можем использовать typedef и для структур. Вот пример: typedef struct COMPLEX { float real; float imag; }; Кроме того, можно использовать тип COMPLEX для представления комплексных чисел. Одна из причин использования typedef заключается в создании удобных, распознаваемых имен для часто встречающихся типов. Например, многие пользователи предпочитают применять STRING или его эквивалент, как это мы делали выше. Вторая причина: имена typedef часто используются для сложных типов. Например, описание typedef char *FRPTC ( ) [5]; приводит к тому, что FRPTC объявляет тип, являющийся функцией, которая возвращает указатель на пятиэлементный массив типа char. (См. "Причудливые описания".) Третья причина использования typedef заключается в том, чтобы сделать программы более мобильными. Предположим, например, что вашей программе нужно использовать 16-разрядные числа. В некоторых системах это был бы тип short, в других же он может быть типом int. Если вы использовали в ваших описаниях short или int, то должны изменить все описания, когда перейдете от одной системы к другой. Вместо этого сделайте следующее, В файле директивы #include есть такое определение: typedef short TWOBYTE; Используйте TWOBYTE в ваших программах для переменных типа short, которые должны быть 16-разрядными. Тогда если вы перемешаете программу туда, где необходимо использовать тип int, то следует только изменить одно определение в вашем файле директивы #include: typedef int TWOBYTE; Это пример того, что делает язык Си столь мобильным. При использовании typedеf следует иметь в виду, что он не создаст новых типов, он только создает удобные метки. ПРИЧУДЛИВЫЕ ОПИСАНИЯ Язык Си позволяет вам создавать сложные формы данных. Обычно мы придерживаемся более простых форм, но считаем споим долгом указать ни потенциальные возможности языка. При создании описания мы используем имя (или "идентификатор"), которое можно изменять при помощи модификатора: Модификатор значение * указатель ( ) функция [ ] массив Язык Си позволяет использовать одновременно более одного модификатора, что даст возможность создавать множество типов: int board[8] [8]; /* массив массивов типа int */ int **ptr; /* указатель на указатель на тип int */ int *risks[10]; /* 10-элементный массив указателей на тип int */ int (*wisks) [10]; /* указатель на 10-элемснтный массив типа int */ int *oof[3] [4]: /* 3-элементныи массив указателей на 4-элементный массив типа int */ 294 int (*uuf) [3][4]; /* указатель на массив 3х4 типа int */ Для распутывания этих описаний нужно понять, в каком порядке следует применять модификаторы. Три правила помогут вам справиться с этим. 1. Чем ближе кодификатор стоит к идентификатору, тем выше его приоритет. 2. Модификатиры [ ] и ( ) имеют приоритет выше, чем *. 3. Круглые скобки используются для объединения частей выражения, имеющих самый высокий приоритет. Давайте применим эти правила к описанию int *oof[3] [4]; * и [3] примыкают к oof и имеют более высокий приоритет, чем [4] (правило 1). [3] имеет приоритет более высокий, чем * (правило 2). Следовательно, oof является 3-элементным массивом (перпый МОДИФИКАТОР) указателей (второй модификатор) на 4-элементный массив (третий модификатор) типа int (описание типа). В описании int (*uuf)[3][4]; скобки говорят, что модификатор * должен иметь первый приоритет, а это делает uuf указателем, как показано в предыдущем описании. Эти правила создают также следующие типы: char *fump( ); /* функция, возвращающая указатель на тип char */ char (*frump) ( ); /* указатель на функцию, возвращающую тип char */ char *flump ( ) [3] /* функция, возвращающая указатель на 3-элементный массив типа char */ char *flimp[3] ( ) /* 3-элементный массив указателей на функцию, которая возвращает тип char */ Если вы примените структуры к этим примерам, то увидите, что возможности для описаний действительно растут причудливо. А применения ... так и быть, МЫ оставим их для более опытных программистов. Язык Си со структурами, объединениями и typedef дает нам средства для эффективной и мобильной обработки данных. ЧТО ВЫ ДОЛЖНЫ БЫЛИ УЗНАТЬ В ЭТОЙ ГЛАВЕ Далее Содержание Что такое структурный шаблон, и как его определять Что такое имя структуры и как оно используется Как определить структурную переменную: struct car honda; Как обратиться к элементу структуры: honda.mpg Как обратиться к указателю на структуру: struct car *ptcar; Как обратиться к элементу при помощи указателя: ptcar->mpg Как передать в функцию элемент структуры: eval(honda.mpg) Как сообщить функции о структуре: rate(&honda) Как создать вложенную структуру Как обратиться к элементу вложенной структуры: honda.civic.cost Как создавать и использовать массивы структур: struct car gm[5]; Как создать объединение: подобно структуре Как использовать typedef: typedef struct car CRATE; 295 ВОПРОСЫ И ОТВЕТЫ Далее Содержание Вопросы 1. Что неправильно в этом шаблоне? structure { char itible; int num [20]; char *togs; }; 2. Вот фрагмент программы; что она напечатает? struct house { float sqft; int rooms; int stories; char *address; }; main ( ) { static struct house fruzt = { 1560.0, 6, 1, " 22 Spiffo Road"; struct house *sign; sign = &fruzt; printf(" %d %d\n" , fruzt.rooms, sign-> stories); printf(" %s\n", frurt.address); prinlf(" %c %c \n" sign- >address[3], fruzt.address[4]); } 3. Придумайте структурный шаблон, который будет содержать название месяца, трехбуквенную аббревиатуру месяца, количество дней в месяце и номер месяца. 4. Определите массив, состоящий из двенадцати структур того же типа, что и в вопросе 3, и инициализируйте его для невисокосного года. 5. Напишите функцию, которая получает номер месяца, а возвращает общее число дней года вплоть до этого месяца. Считайте, что структурный шаблон и массив из вопросов 3 и 4 описаны как внешние. 6. Взяв за основу нижеследующую функцию typedet, опишите 10-элементный массив указанной структуры. Затем, используя присваивание отдельного элемента попытайтесь описать третьим элементом'массива линзу Ремаркатара с фокусным расстоянием 500 мм и апертурой f / 2.0. typedef struct { /* описатель линзы */ float foclen; /* фокусное расстояние, мм */ float fstop; /* апертура */ char *brand; /* фирменная марка */ } LENS; Ответы: 1. Должно быть ключевое слово struct, а не structure. Шаблон требует либо имени структуры перед открывающей скобкой или имени переменной после закрывающей скобки. Кроме того, точка с запятой должна стоять после *togs и в конце шаблона. 2. 296 6 1 22 Spiffo Road S p Элемент fruzt.address является символьной строкой, а fruzt.address[4] является пятым элементом этого массива. 3. struct month { char name[10]; /* или char *name; */ char abbrev[4]; /* или char *abbrev; */ int days; int monumb; }; 4. struct month months [12] = { {" Январь" , " Янв" , 31, 1} , {" Февраль" , " Фев" , 28, 2} , и т. л. {"Декабрь", "Дек" , 31, 12} 5. days(monlh); inl month; { int index, tolal; if(month < 1 || month > 12) return (-1); /* признак ошибки */ else for(index = 0, total = 0; index < month; index++) total + = months [index].days; return (total);} Заметим, что index содержит номер месяца, уменьшенный на единицу, так как массивы начинаются с индекса 0; следовательно, мы используем выражение index < month вместо index <= month. 6. ЛИНЗА tubby [10]; tubby [2].foclen = 300.0; tubby [2].fstop = 2.0; tubby [2].brand = "Рсмаркатар"; УПРАЖНЕНИЯ 1. Переделайте вопрос 5, используя в качестве аргумента написанное буквами название месяца вместо номера месяца. [Не забывайте о функции strcmp( ).] 2. Напишите программу, которая запрашивает у пользователя день, месяц и год. Месяц может обозначаться номером, названием месяца или его аббревиатурой. После работы программа выдает общее количество дней в году вплоть до данного дня. 3. Переделайте нашу программу инвентаризации книг таким образом, чтобы она печатала информацию о книгах, упорядоченную в алфавитном порядке по названиям книг, и затем печатала общую стоимость книг. [Содержание] [Вверх] 297 Язык Си Структуры и другие типы данных M. Уэйт, С. Прата, Д. Мартин Язык Си [Содержание] [Вниз] БИБЛИОТЕКА ЯЗЫКА СИ ФАЙЛЫ В ЯЗЫКЕ СИ ФУНКЦИИ РАБОТЫ С ФАЙЛОМ МАКРООПРЕДЕЛЕНИЯ ДЛЯ ПРОВЕРКИ СИМВОЛОВ ФУНКЦИИ РАСПРЕДЕЛЕНИЯ ПАМЯТИ Всякий раз, когда нам нужно использовать такие функции, как printf( ), getchar( ) и strlen( ), мы обращаемся в библиотеку языка Си. Она содержит множество функций и макроопределений. Библиотеки меняются от системы к системе, но есть ядро функций (называемое стандартной библиотекой), которое используется чаще всего. В этой главе мы рассмотрим пятнадцать наиболее общих из этих функций, уделяя больше внимания функциям ввода-вывода и использованию файлов. Однако сначала давайте поговорим о том, как пользоваться библиотекой. ДОСТУП В БИБЛИОТЕКУ ЯЗЫКА СИ Далее Содержание Получение доступа к библиотеке зависит от системы, поэтому вам нужно посмотреть в своей системе, как применять наиболее распространенные операторы. Во-первых, есть несколько различных мест расположения библиотечных функций. Например, getchar( ) обычно задают как макроопределение в файле stdio.h, в то время как strlen( ) обычно хранится в библиотечном файле. Во-вторых, различные системы имеют разные способы доступа к этим функциям. Вот три из них. Автоматический доступ Далее Содержание Во многих больших системах UNIX вы только компилируете программы, а доступ к более общим библиотечным функциям выполняется автоматически. Включение файла Далее Содержание Если функция задана как макроопределение, то можно директивой #include включить файл, содержащий ее определение. Часто подобные функции могут быть собраны в соответствующим образом названный заголовочный файл. Например, некоторые системы имеют файл ctype.h, содержащий макроопределения, задающие тип символа: прописная буква, цифра и т. д. Включение библиотеки Далее Содержание 298 На некотором этапе компиляции или загрузки программы вы можете выбрать библиотеку. В нашей системе, например, есть файл lc.lib, содержащий скомпилированную версию библиотечных функций, и мы предлагаем редактору связей IBM PC использовать эту библиотеку. Даже система, которая автоматически контролирует свою стандартную библиотеку, может иметь другие библиотеки редко применяемых функций, и эти библиотеки следует запрашивать явно, указывая соответствующий признак во время компиляции. Очевидно, мы не сможем рассмотреть все особенности всех систем, но эти три примера должны показать, что вас ожидает. Теперь давайте рассмотрим некоторые функции. |