| ! | В языке Си введено три класса целых чисел, имеющих различные размеры. Тем самым пользователю языка Си предоставили возможность выбора типа переменной с требованием задачи. Например, если переменная типа int занимает одно слово, а переменная типа long занимает два слова, значит тип long позволяет обрабатывать большие числа. Если в задаче не используются большие числа, то незачем вводить в программу переменные типа long, т.к. если вместо числа, занимающего одно слово в памяти, используется число, занимающее два слова, работа машины замедляется. |
| Новая строка (перевод строки) | '\n' |
| Горизонтальная табуляция | '\t' |
| Вертикальная табуляция | '\v' |
| Возврат на шаг | '\b' |
| Возврат каретки | '\r' |
| Перевод формата | '\f' |
| Обратная косая | '\\' |
| Апостроф | '\'' |
| Кавычки | '\"' |
| Нулевой символ (пусто) | '\0' |
| ! | Символическую константу после #define лучше писать прописными буквами. В процессе использования языка Си выработалась традиция писать константы большими буквами. Если при просмотре программы встречается имя, написанное прописными буквами, сразу становится ясно, что это константа, а не переменная. Давайте не нарушать традицию! |







|
Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |


![]() | © 2003-2007 INTUIT.ru. Все права защищены. |







| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
| ! | Смешения типов следует избегать! |
| ( ) | слева направо |
| - (унарный) | слева направо |
| * / | слева направо |
| + - (вычитание) | слева направо |
| = | справа налево |







| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |







| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |







| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |







| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
| ! | Не применяйте операции увеличения или уменьшения к переменной, присутствующей в более чем одном аргументе функции. Не применяйте операции увеличения или уменьшения к переменной, которая входит в выражение более одного раза. |
| ! | Не применяйте операции увеличения или уменьшения к переменной, присутствующей в более чем одном аргументе функции. Не применяйте операции увеличения или уменьшения к переменной, которая входит в выражение более одного раза. |







| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |







| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |







| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |







| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
| - | Аргумент будет печататься с левой позиции поля заданной ширины. Обычно печать аргумента оканчивается в самой правой позиции поля Пример: %-10 |
| строка цифр | Задает минимальную ширину поля. Большее поле будет использоваться, если печатаемое число или строка не помещается в исходном поле Пример: %4d |
| строка цифр | Определяет точность: для типов данных с плавающей точкой число печатаемых цифр справа от десятичной точки; для символьных строк - максимальное число печатаемых символов Пример: %4.2f (две десятичные цифры для поля шириной в четыре символа) |
| l | Соответствующий элемент данных имеет тип long, а не int Пример: %ld |







|
Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
| - | Аргумент будет печататься с левой позиции поля заданной ширины. Обычно печать аргумента оканчивается в самой правой позиции поля Пример: %-10 |
| строка цифр | Задает минимальную ширину поля. Большее поле будет использоваться, если печатаемое число или строка не помещается в исходном поле Пример: %4d |
| строка цифр | Определяет точность: для типов данных с плавающей точкой число печатаемых цифр справа от десятичной точки; для символьных строк - максимальное число печатаемых символов Пример: %4.2f (две десятичные цифры для поля шириной в четыре символа) |
| l | Соответствующий элемент данных имеет тип long, а не int Пример: %ld |
| %d | десятичное целое число |
| %c | один символ |
| %s | строка символов |
| %e | экспоненциальная запись |
| %f | число с плавающей точкой, десятичная запись |
| %g | используется вместо записи %f или %e |
| %u | десятичное целое число без знака |
| %o | восьмеричное целое число без знака |
| %x | шестнадцатеричное целое число без знака |







| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
| -14+16 | 2 |
| a=3+8 | 11 |
| 5>3 | 1 |
| 14<3 | 0 |
| 6+(c=3+8) | 17 |







| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |







| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
| ! | Программа, приведенная ниже, делает то же самое, но стиль ее написания лучше отвечает духу языка Си: /* ввод-вывод */ #include Одна строка 8 этой программы заменяет строки 9, 10, 12 предыдущей программы. Чтение файлаЕсли нам нужно читать большие порции данных, например из файла, каким должен быть признак STOP? Это должен быть такой символ, который обычно не используется в тексте и, следовательно, не приводит к ситуации, когда он случайно встретится при вводе, и работа программы будет остановлена раньше, чем бы мы хотели. Файлом можно назвать участок памяти, в который помещена некоторая информация. Обычно файл хранится в некоторой долговременной памяти, например на гибких или жестких дисках или на магнитной ленте. Чтобы отмечать, где кончается один файл и начинается другой, полезно иметь специальный символ, указывающий на конец файла, чтобы отмечать конец файла и начинать другой. Это должен быть символ, который не может появиться где-то в середине файла. Решением указанной проблемы служит введение специального признака, называемого "End-of-File", конец файла, или EOF. Выбор конкретного признака EOF зависит от типа системы. Он может состоять даже из нескольких символов. Обычно определение EOF содержится в файле#define EOF (-1) Пример: /* ввод-вывод_ф */ #include Это надо помнить: Пусть мы ввели фразу с клавиатуры. Приведем результат работы программы "ввод-вывод_ф" в системе, с буферизованным вводом: Спрос на высокопрофессиональных ИТ-специалистов Спрос на высокопрофессиональных ИТ-специалистов растет как со стороны государственных, так и частных компаний растет как со стороны государственных, так и частных компаний [CTRL/z] Каждый раз при нажатии клавиши Enter производится обработка символов, попавших в буфер, и копия строки выводится на печать. Это продолжается до тех пор, пока мы не введем признак EOF. Программа "ввод-вывод_ф" осуществляет вывод на экран символов независимо от того, откуда они поступают. Наша программа могла бы просматривать содержимое файлов, создавать новые файлы и получать копии файлов. Решение этих проблем - в управлении вводом и выводом. Чтение одной строкиУсложним пример ввода-вывода:/* подсчет символов */ #include Если мы хотим просто подсчитать число введенных символов без отображения их на экране, функцию putchar( ) можно опустить. Заменим признак окончания ввода данных, используем символ новая строка \n. Для этого нужно переопределить признак STOP: #define STOP '\n' Символ новая строка пересылается при нажатии клавиши Enter. Предположим, что мы внесли указанное изменение в программу "подсчет символов", а затем при выполнении ввели следующую строку: На экране тридцать четыре символа.[Enter] В ответ на экране появятся следующие строки: На экране тридцать четыре символа. Признак, появляющийся в результате нажатия клавиши Enter, не входит в число символов 34, подсчитанных программой, поскольку подсчет осуществляется внутри цикла. Теперь у нас есть программа, которая может прочесть одну строку. Комбинированное переключениеПредположим, что мы хотим создать копию файла my_words и назвать его my_words2. Нужно ввести для этого командуget_put < my_words > my_words2 и требуемое задание будет выполнено. Команда get_put > my_words2 < my_words приведет к такому же результату, поскольку порядок указания операций переключения не имеет значения. Нельзя в одной команде использовать один и тот же файл и для ввода, и для вывода. Операционные системы, отличные от OC UNIXЧем отличаются другие операционные системы от OC UNIX? Все отличия можно разделить на две группы:У нас нет возможности рассмотреть все компиляторы с языка Си. Однако в пяти из шести версий компилятора, предназначенных для микрокомпьютеров, для указания операции переключения используются символы < >. Операция переключения отличается от аналогичной операции в двух аспектах: ![]() ![]() Перейти к вопросам " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Переключение и работа с файламиПонятие ввода-вывода включает в себя функции, данные и устройства. Рассмотрим, например, программу "ввод-вывод_ф". В ней используется функция getchar( ), осуществляющая ввод, причем устройство ввода - клавиатура (в соответствии с нашими предположениями), а выходные данные - отдельные символы. Изменим источник поступления в программу данных. По умолчанию Си-программа рассматривает стандартный ввод как источник поступления данных. Стандартным вводом называется устройство, принятое в качестве обычного средства ввода данных в машину. Это может быть устройство чтения данных с магнитной ленты телетайпа или терминал. Мы можем сами выбирать устройство данных из любого источника. Ну, например, мы можем написать в программе, что источник входных данных - файл, а не клавиатура.Существуют два способа написания программ, работающих с файлами. Первый способ заключается в явном использовании специальных функций, которые открывают и закрывают файлы, организуют чтение и запись данных и т.д. Этот вопрос мы будем обсуждать в 15 лекции. Второй способ состоит в том, чтобы использовать программу, спроектированную первоначально в предположении, что данные в нее вводятся с клавиатуры и выводятся на экран, но переключить ввод и вывод на другие информационные каналы, например, из файла в файл. Этот способ в некоторых отношениях обладает меньшими возможностями, чем первый, но зато гораздо проще в использовании. Операция переключения - это средство OC UNIX, а не самого языка Си. Но она оказалась настолько полезной, что при переносе компилятора с языка Си на другие вычислительные системы часто вместе с ним переносится и эта операция. Многие из вновь созданных операционных систем, таких, как MS-DOS 2, включают в себя данное средство. Сначала мы обсудим возможности этой операции в OC UNIX, а за тем и в других системах. Переключение вывода. Предположим, мы осуществили компиляцию программы "ввод-вывод_ф" и поместили выполняемый объектный код в файл с именем get_put. Затем, чтобы запустить данную программу, мы вводим с терминала только имя файла get_put и программа выполняется так, как было описано выше, т.е. получает в качестве входных данных символы, вводимые с клавиатуры. Теперь предположим, что мы хотим посмотреть, как наша программа работает с текстовым файлом с именем words. Текстовый файл - это файл, содержащий некоторый текст, т. е. данные в виде символов. Это может быть, например, рассказ или программа на языке Си. Файл, содержащий команды на машинном языке, не является текстовым. Так как наша программа занимается обработкой символов, то она должна использоваться вместе с текстовым файлом. Для этого надо ввести следующую команду: get_put < words Символ < служит обозначением операции переключения, используемой в OC UNIX. Выполнение указанной операции приводит к тому, что содержимое файла words будет направлено в файл с именем get_put. Сама программа "ввод-вывод_ф" не знает, что входные данные поступают из некоторого файла, а не с терминала. На ее вход просто поступает поток символов, она читает их, и последовательно, по одному, выводит на печать до тех пор, пока не встретит признак EOF. Если мы наберем команду get_put < words то в результате на экране может появиться, например, следующий текст: Анализ данных, прогнозы, организация связи, создание программного обеспечения, построение моделей процессов - вот далеко неполный список областей применения знаний для компьютерных специалистов. если этот текст находится в текстовом файле words. Переключение вводаТеперь рассмотрим случай, когда нам нужно ввести текст с клавиатуры в файл с именем my_words. Для этого мы должны ввести командуget_put > my_words и начать ввод символов. Символ > служит обозначением операции переключения, используемой в OC UNIX. Ее выполнение приводит к тому, что создается новый файл с именем my_words, а затем результат работы программы "ввод-вывод_ф", представляющий собой копию вводимых символов, направляется в данный файл. Если файл с именем my_words уже существует, он обычно уничтожается, и вместо него создается новый. На экране появляются вводимые слова. Их копии будут направлены в указанный файл. Чтобы закончить работу, мы вводим EOF, в OC UNIX это обычно [CTRL/d]. Ввод и вывод одного символаВ данном разделе мы рассмотрим функции, применяемые при вводе и выводе. кроме того мы коснемся других аспектов этого понятия. Под функциями ввода-вывода подразумеваются функции, которые выполняют транспортировку данных в программу и из нее. Мы уже использовали две такие функции: printf( ) и scanf( ). Теперь рассмотрим несколько других возможностей, предоставляемых языком Си.Функции ввода-вывода не входят в определение языка Си. Их разработка возложена на программистов, реализующих компилятор с языка Си. С другой стороны, выгода использования стандартного набора функций ввода-вывода на всех системах очевидна. Это дает возможность писать переносимые программы, которые легко можно применять на разных машинах. В языке Си имеется много функций ввода-вывода такого типа, например printf( ) и scanf( ). Ниже мы рассмотрим функции getchar( ) и putchar( ). Эти две функции осуществляют ввод и вывод одного символа при каждом обращении к ним. Этот способ ввода данных лучше соответствует возможностям машины. Более того, такой подход служит основой построения большинства программ обработки текстов, являющихся последовательностями обычных слов. Мы увидим, как можно применять эти функции в программах, занимающихся подсчетом символов, чтением и копированием файлов. Узнаем про буферы, эхо-печать и переключение ввода-вывода. Функция getchar( ) получает один символ, поступающий с пульта терминала (и поэтому имеющий название), и передает его выполняющейся в данный момент программе. Функция putchar( ) получает один символ, поступающий из программы, и пересылает его для вывода на экран. Рассмотрим пример программы, которая принимает один символ с клавиатуры, и выводит его на экран: /*ввод-вывод*/ #include Для большинства систем спецификации функции getchar и putchar содержатся в системном файле stdio.h, поэтому мы указали данный файл в программе. Функция getchar( ) аргументов не имеет, т.е. при ее вызове в круглые скобки не помещается никакая величина. Она просто получает очередной поступающий символ, и сама возвращает его значение выполняемой программе. Оператор, приведенный в строке 1, присваивает значение функции getchar( ) переменной ch. Функция putchar( ) имеет один аргумент. При ее вызове необходимо в скобках указать символ, который требуется вывести на печать. Аргументом может быть одиночный символ (включая знаки, представляемые управляющими последовательностями), переменная или функция, значением которой является одиночный символ. Правильным обращением к функции putchar( ) является указание любого из этих аргументов при ее вызове: putchar('D'); putchar('\n'); putchar('\007'); putchar(ch); /* переменная типа char */ putchar(getchar( )); Модифицируем нашу программу: #include Такая запись очень компактна и не требует введения вспомогательных переменных. В результате компиляции такая программа оказывается более эффективной. Что такое истинаМы ответим на этот вопрос, как он решается в языке Си. В Си выражение всегда имеет значение. Это утверждение остается верным даже для условных выражений, как показывает пример, приведенный ниже. В нем определяются значения двух условных выражений, одно из которых оказывается истинным, а второе - ложным:/* истина и ложь*/ main( ) { int true, false; true = (12 > 2); /*отношение истинно*/ false = (12 == 2) ; /*отношение ложно*/ printf("true = %d; false = %d\n",true,false); } При выполнении программы получим следующие результаты: true = 1, false = 0. В языке Си значение истина равно 1, а значение ложь равно 0. В языке Си все ненулевые значения являются истинными, и только 0 являются ложью. Например, два оператора if(i !=0 ) можно заменить на if(i) поскольку выражение (i !=0 ) и выражение (i) оба примут значения 0, или ложь, только в том случае, если значение переменной i равно 0. Логические операцииИногда бывает полезным объединить два или более условных выражения. Например, нам нужно подсчитать, сколько символов не является пробелами, символами новая строка и табуляции. Напишем такую программу:/* число символов */ main( ) { int ch; int count=0; while((ch=getchar( )) != EOF) if(ch !=' ' && ch != '\n' && ch != '\t') count++; printf("Всего %d непустых символов. \n", count); } Оператор, использующий логическую операцию и, обозначаемую &&. Смысл действий, осуществляемых оператором if, можно пояснить так:
Если прочитанный символ - не пробел и не символ "новая строка", и не символ табуляции, то происходит увеличение значения переменной count на единицу. Все выражение будет истинным, если указанные три условия - истины. В языке Си имеются три логические операции. on_load_lecture() ![]() ![]() Дальше " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | 4 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Множественный выборКогда в программе нужно осуществить выбор одного из нескольких вариантов, мы можем сделать это, используя конструкцию if-else if-else_...else Во многих случаях оказывается более удобным применять оператор switch.Оператор switch (переключатель) используется для разветвления программы по нескольким направлениям. Он имеет следующую форму: switch(e) { сase c1: s1; break; case c2: s2; break; ... case ck: sk; break; default: s(k+1); } где Метки ci, обозначающие альтернативы case, должны быть уникальными; двух одинаковых меток быть не может. Только одна альтернатива может получить префикс default. Результатом выполнения оператора switch является выбор альтернативы с меткой ci, которая равна значению выражения переключателя e; в этом случае выполняются операторы si. В случае, если выражение переключателя не равно ни одному из выражений альтернатив case, то выполняются операторы s(k+1); при отсутствии альтернативы default не выполняется ни одна из альтернатив оператора switch. Пример: /* Реализация работы калькулятора. Сначала задается число - сколько раз нужно подсчитать. Вводятся левый операнд, операция, правый операнд. Оператор выбора определяет, какой оператор должен работать. Результат выводится на экран. */ #include Операции отношенияОперации отношения используются для сравнений. Мы уже обсуждали их, но здесь мы остановимся на их обсуждении подробнее.
Этот список хорошо соответствует возможным числовым соотношениям.
Операции отношения применяются при формировании условных выражений, используемых в операторе if и while. Указанные операторы проверяют, истинно или ложно данное выражение. Пример: #include on_load_lecture() ![]() ![]() Дальше " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | 4 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Операция условия: ?:В языке Си имеется короткий способ записи одного из видов оператора if-else. Он называется "условным выражением" и использует операцию условия - ?:. Эта операция состоит из двух частей и содержит три операнда. Рассмотрим, как можно найти абсолютное значение числа:x = (y < 0) ? -y : y; Все, что находится между знаком = и символом "точка с запятой" представляет собой условное выражение. Смысл этого оператора заключается в следующем: если y меньше 0, то x=-y; в противном случае x=y. В терминах оператора if-else данный оператор мог выглядеть так: if (y < 0) x=-y; else x=y; В общем виде условное выражение можно записать следующим образом: выражение1 ? выражение2 : выражение3 Если выражение1 истинно (не равно нулю), то значением всего условного выражения является величина выражение2; если выражение1 ложно (равно 0), то значение всего условного выражения - величина выражение3.
Оператор ifПодсчитаем число строк в файле. Это можно сделать путем счета числа символов "новая строка" в файле.Пример: /*подсчет строк */ #include Оператор if служит указанием компьютеру увеличить значение переменной line_count на 1, если только что прочитанный символ, содержимое переменной ch, представляет собой символ "новая строка". Что происходит в случае, когда значение переменной ch не является символом "новая строка"? Тогда в цикле while производится чтение следующего символа. Оператор if считается одиночным оператором, начинающимся от ключевого слова if и завершающимся символом "точка с запятой". Модифицируем программу. Подсчитаем одновременно число символов и строк в файле: /* подсчет числа строк и символов */ #include Теперь в цикл while входят два оператора, поэтому мы использовали фигурные скобки, чтобы отметить начало и конец цикла. Осложнение с понятием истинаРассмотрим следующую программу:/* занятость */ main( ) { int age =20; while (age++ <= 63) { /* делится ли возраст на 20*/ if((age % 20) == 0) printf(" Вам %d.\n", age); if(age = 63) printf("Вам уже %d.\n",age); } } Мы хотели написать программу, чтобы результат работы программы выглядел так: Вам 40. Вам 60. Вам уже 63. На самом деле выход будет таким: Вам уже 63. Вам уже 63. Вам уже 63. и т. д. - до бесконечности. Это произошло, потому что мы ошибочно написали if(age = 63) вместо if(age == 63) В операторе if(age = 63) переменной age присваивается значение 63, т. е. результат этого оператора - истина и выполняется оператор printf("Вам %d.\n",age); В операторе while(age++ <= 63) условие выполняется, и программа зацикливается. Приоритеты операций отношения мы описали в лекции 3. Расширение оператора ifПростейшей формой оператора if являетсяif(выражение) оператор Под выражением здесь понимаем условное выражение. С его помощью сравниваются значения двух величин. Если такое выражение истинно, то оператор выполняется. В противном случае он пропускается. В общем случае в качестве условия может быть использовано любое выражение, и если его значение равно 0, то оно считается ложным. Оператор может быть простым или составным, т. е. блоком. Пример: /* пример одиночного оператора и блока */ #include Простая форма оператора if позволяет выбрать оператор, возможно, составной, или пропустить его. Язык Си предоставляет также возможность выбрать любой из двух операторов путем использования конструкции if-else. Напишем программу, заменяющую каждый символ из таблицы ASCII на следующий символ, кроме символа "новая строка". Пример: /* код_1*/ #include Общий вид оператора if-else выглядит следующим образом: if(выражение) оператор else оператор Если выражение истинно, то выполняется первый оператор, а если ложно, то выполняется оператор, следующий за ключевым словом else. Операторы могут быть простыми или составными. if(выражение) оператор1; else оператор2; Если в результате вычисления значения выражения получено значение "истина" (ненулевое значение), то в обоих формах оператора if выполняется оператор1. Если вычисленное значение выражения равно значению "ложь" (нулевое), тогда выполнение оператора if, представленного в первой форме, заканчивается, а в операторе, имеющем вторую форму, выполняется оператор 2. Совместное использование обеих форм оператора if приводит к неоднозначности, называемой "проблемой висящего else". Например, вложенный оператор if if(e1) if( e2) оператор1; else оператор2; может быть интерпретирован так if (e1) if(e2) оператор1; else оператор2; или как if(e1) if(e2) оператор1; else оператор2; Эта неоднозначность разрешается в языке Си с помощью правила, в соответствии с которым часть else оператора всегда относится к синтаксически самому правому, игнорируя любые отступы, оператору if без части else. Следовательно, первая интерпретация является интерпретацией, принятой в языке Си.
Для явного указания намерений программиста можно использовать и фигурные скобки. Например, обе вышеприведенные интерпретации можно записать явно так: if(e1) { if(e2) оператор1; else оператор2 } и if(e1) { if(e2) оператор1; } else оператор2; Выбор вариантовЯзык Си обеспечивает три основные формы управления процессом выполнения программ. Согласно теории вычислительных систем, хороший язык должен обеспечивать реализацию следующих трех форм управления процессом выполнения программ:Первая форма нам уже хорошо известна. Все наши предшествующие программы представляли собой некоторую последовательность операторов. Цикл while служит одним из примеров использования второй формы, другие способы будут рассмотрены в следующей лекции. Последняя форма делает программы гораздо более интеллектуальными, и чрезвычайно увеличивает эффективность работы компьютера Цикл с постусловиемЦиклdo оператор while(e); выполняется до тех пор, пока выражение e имеет значение "истина". В отличие от цикла while , в котором проверка условия окончания цикла делается до выполнения тела цикла, в цикле do такая проверка имеет место после выполнения тела цикла. Следовательно, тело цикла do будет выполнено хотя бы один раз, даже если выражение e имеет значение "ложь" c самого начала. Цикл do аналогичен циклу repeat в языке Паскаль, отличаясь от него лишь тем, что цикл repeat выполняется до тех пор, пока некоторое условие выходa из цикла не становится истинным, а цикл do выполняется все время, пока некоторое условие остается истинным!
Пример: do scanf("%d", &number); while(number!=50); Подведем итоги. Оператор do while определяет действия, которые циклически выполняются до тех пор, пока проверяемое выражение не станет ложным, или равным нулю. Оператор do while - это цикл с постусловием. Решение о том выполнять или нет в очередной раз тело цикла, принимается после его прохождения. Поэтому тело цикла будет выполнено по крайней мере один раз. Оператор, образующий тело цикла, может быть как простым, так и составным. Особенности работы с языком Си. Циклом какого вида лучше всего воспользоваться? Во-первых, решите, нужен ли вам цикл с предусловием или же с постусловием. Чаще вам нужен будет цикл с предусловием. По оценкам Кернигана и Ритчи, в среднем циклы с постусловием составляют только 5% общего числа используемых циклов. Существует несколько причин, по которым программисты предпочитают пользоваться циклами с предусловием. В их числе один общий принцип, согласно которому лучше посмотреть, куда вы прыгаете, до прыжка, а не после. Вторым моментом является то, что программу легче читать, если проверяемое условие находится в начале цикла. И наконец, во многих случаях важно, чтобы тело цикла игнорировалось полностью, если условие вначале не выполняется. Вложенные циклы. Вложенным называется цикл, находящийся внутри другого цикла. Пример: /* Простое число - это такое число, которое делится нацело только на 1 и само на себя. Первыми простыми числами будут 2, 3, 5, 7 и 11. */ /*простые числа*/ main() { int number, divisor, limit; int count=0; printf("Укажите, пожалуйста, верхний предел для поиска простых чисел. \n"); printf("Bерхний предел должен быть 2 или больше.\n"); scanf("%d",&limit); while (limit<2) { /* вторая попытка, если ошибка при вводе */ printf("Bы были невнимательны! Попробуйте еще раз\n"); scanf("%d",&limit); } printf("Ceйчac напечатаем простые числа !\n") for (number=2;number<=limit; number++) /*внешний цикл*/ { for(divisor=2; number%divisor!=0; divisor++) ; if(divisor==number) { printf("%5d",number); if(++count%10==0) printf("\n");/* новая строка начинается через каждые 10 простых чисел */ } } printf("Pa6oта завершена!\n"); } Во внешнем цикле каждое число, начиная с 2 и кончая величиной limit, последовательно берется для проверки. Указанная проверка осуществляется во внутреннем цикле. В count хранится счетчик получаемых простых чисел. При печати каждое одиннадцатое простое число печатается с новой строки. Цикл с предусловиемСуществует три вида циклов: while, for и do. Цикл while имеет следующую форму:while (e) s; Оператор s выполняется до тех пор, пока значение выражения e равно "истина". Значение e вычисляется перед каждым выполнением оператора s. В предшествующих лекциях мы пользовались этой формой цикла. Рассмотрим еще один пример его работы: /* угадывание числа */ # include
В наших примерах до сих пор использовались условные выражения, но вообще говоря, это могут быть выражения произвольного типа. В качестве оператора можно использовать простой оператор с символом "точка с запятой" в конце или составной оператор, заключенный в фигурные скобки. Если выражение истинно (или в общем случае равно единице), то оператор, входящий в цикл while, выполняется один раз, а затем выражение проверяется снова. Эта последовательность действий, состоящая из проверки и выполнения оператора, периодически повторяется до тех пор, пока выражение не станет ложным (или в общем случае равным нулю). Каждый такой шаг называется итерация. Данная структура аналогична структуре оператора if. Основное отличие заключается в том, что в операторе if проверка условия и (возможное) выполнение оператора осуществляется только один раз, а в цикле while эти действия производятся, вообще говоря, неоднократно.
Цикл while является условным циклом, использующим предусловие, т.е. условие на входе. Он называется условным, потому что выполнение оператора зависит от истинности условия, описываемого с помощью выражения. Подобное выражение задает предусловие, поскольку выполнение этого условия должно быть проверено перед началом выполнения тела цикла.
Оператор while определяет операции, которые циклически выполняются до тех пор, пока проверяемое выражение не станет ложным, или равным нулю. Оператор while - это цикл с предусловием. Решение, выполнить ли в очередной раз тело цикла, принимается перед началом его прохождения. Поэтому вполне возможно, что тело цикла не будет выполнено ни разу. Оператор, образующий тело цикла, может быть либо простым, либо составным. Форма записи: while (выражение) оператор Выполнение оператора циклически повторяется до тех пор, пока выражение не станет ложным, или равным нулю. Цикл со счетчикомОператор цикла forfor(e1; e2; e3) s является удобной сокращенной записью для цикла while вида e1; while(e2) { s; e3; } Выражение e1 служит для задания начальных условий выполнения цикла, выражение e2 обеспечивает проверку условия выхода из цикла, а выражение e3 модифицирует условия, заданные выражением e1. Любое из выражений e1, e2, e3 может быть опущено. Если опущено e2, то по умолчанию вместо него подставляется значение TRUE. Например, цикл for for(;e2;) s; с опущенными e1,e3 эквивалентен циклу while(e2) s; Цикл for(;;) s; со всеми опущенными выражениями эквивалентен циклу while(TRUE) s; т.е. эквивалентен бесконечному циклу. Такой цикл может быть прерван только явным выходом из него с помощью операторов break, goto, return, содержащихся в теле цикла s. for(n=10; n>0; n--) printf("%d секунд!\n",n); printf("Пуск! \n"); for (n=2; n<60;n=n+13); printf("%d\n",n); for(ch = 'a';ch<='z';ch++) printf("Величина кода ASCII для %с равна %d.\n",ch,ch); При выполнении этого оператора будут выведены на печать все буквы от а до z вместе с их кодами ASCII. Этот оператор работает, поскольку символы в памяти машины размещаются в виде чисел, и потому в данном фрагменте ведется счет с использованием целых чисел. /* таблица кубов */ main( ) { int num; for(num=1; num<=6; num++) printf("%5d %5d\n", num,num*num*num); } А теперь заменим спецификацию for(num=1; num<=6; num++) на for(num=1; num<6; num++) Это было бы целесообразно в случае, если бы нас больше занимало ограничение максимального значения диапазона кубов чисел, а не количество итераций. for(x=100.0; x<150.0; x=x*1.1) printf("Ваш долг теперь %3.2f.\n",x); В этом фрагменте программы значение переменной x умножается на 1.1 на каждом шаге цикла, что увеличивает ее на 10%. Результат выглядит следующим образом: Ваш долг теперь 100.00. Ваш долг теперь 110.00. Ваш долг теперь 121.00. Ваш долг теперь 133.10. Ваш долг теперь 146.41. for (x=y=1; y<=75; y=5*x++) printf("%10d %10d\n",x,y); Обратите внимание, что в спецификации цикла проверяется значение y, а не x. В каждом из трех выражений, управляющих работой цикла for, могут использоваться любые переменные.
for(;;) { ... } будет выполняться бесконечное число раз, поскольку пустое условие всегда считается истинным. for(printf("Запоминайте числа! \n"); num != 6;) scanf("%d",&num); printf("Это как раз то, что я хочу!\n"); В этом фрагменте первое сообщение оказывается выведенным на печать один раз, а затем осуществляется прием вводимых чисел до тех пор, пока не поступит число 6. for(n=1; n<1000; n+t) И если после нескольких итераций наша программа решает, что величина t слишком мала или велика, то оператор if внутри цикла может изменить значение параметра. В диалоговой программе пользователь может изменить этот параметр в процессе выполнения цикла. Подведем итоги. В операторе for используются три выражения, управляющие работой цикла. Они разделены символом "точка с запятой". Инициализирующее выражение вычисляется только один раз до начала выполнения какого-нибудь из операторов цикла. Если проверяемое выражение оказывается истинным, или не равным нулю, тело цикла выполняется один раз. Затем вычисляется величина корректируемого выражения, и значение проверяемого выражения определяется вновь. Оператор for - это цикл с предусловием. Решение о том, выполнить ли в очередной раз тело цикла или нет, принимается до начала его прохождения. Поэтому может случиться так, что тело цикла не будет выполнено ни разу. Оператор, образующий тело цикла, может быть как простым, так и составным. Операция "запятая" увеличивает гибкость использования цикла for, позволяя включить в его спецификацию несколько инициализирующих или корректирующих выражений. Другие управляющие операторыОператоры, определяющие циклические вычисления, которые мы рассмотрели, и условные операторы if, if-else и switch, являются важнейшими средствами управления выполнением программы на языке Си. Они должны использоваться для реализации общей структуры программы. Три оператора, рассматриваемые ниже, обычно рассматриваются реже, поскольку слишком частое их использование ухудшает читаемость программы, увеличивает вероятность ошибок и затрудняет ее модификацию. Никлаус Вирт дал определение структурного программирования, как программирования без goto.Оператор breakОператор break используется для выхода из оператора while, do, for, switch, непосредственно его содержащего. Управление передается на оператор, следующий за оператором, из которого осуществлен выход. Оператор break имеет формуbreak; Пример: while((ch=getchar()) != EOF) /* читается символ ch=getchar(). Если он не совпадает с EOF, выполняется тело оператора while */ { if(ch=='\n') break; putchar(ch); } Работа цикла полностью прекращается, как только при вводе встречается символ "новая строка". Оператор continueОператор continue служит для пропуска оставшейся части выполняемой итерации цикла, непосредственно его содержащего. Если условиями цикла допускается новая итерация, то она выполняется, в противном случае цикл завершается. Оператор continue имеет следующую форму:continue; Пример: while((ch=getchar()) != EOF) /* читается символ ch=getchar(). Если он не совпадает с EOF, выполняется тело оператора while */ { if(ch=='\n') continue; putchar(ch); } В версии с оператором continue просто пропускаются символы "новая строка", а выход из цикла происходит, только когда читается признак EOF. Оператор gotoОператор goto предназначен для безусловной передачи управления к оператору с указанной меткой. Он имеет следующую форму:goto метка; Керниган и Ритчи считают оператор goto "чрезвычайно плохим" средством и предлагают применять его как можно реже или не применять совсем. Приведем пример записи оператора: goto part1; Чтобы этот оператор выполнялся правильно, необходимо наличие другого оператора, имеющего метку part1. В этом случае запись оператора начинается с метки, за которой следует двоеточие: part1: printf("точка перехода\n");
Если без операторов goto, break, continue, return никак не обойтись, то при использовании goto переходите вперед по коду, а не назад. Оператор break лучше не использовать для преждевременного выхода из цикла, его полезно использовать внутри оператора switch. Оператор continue нежелательно использовать для модификации логики циклов. Почему нежелательно использовать функции со многими операторами return. Один из принципов структурного программирования состоит в том, что программа должна иметь одну точку входа и одну точку выхода. Функции со многими операторами return более сложны для чтения, чем те, которые имеют лишь один оператор return в конце тела функции. on_load_lecture() ![]() ![]() Перейти к вопросам " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | 4 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Структурное программированиеМетодология структурного программирования основана на предположении, что логичность и понятность программы обеспечивает надежность, облегчает модификацию и ускоряет разработку программы.Характерными чертами структурного программирования являются: Аргументы функцииФормальный аргумент - переменная в вызываемой программе, а фактический аргумент - конкретное значение, присвоенное этой переменной вызывающей программой. Фактический аргумент может быть константой, переменной или более сложным выражением. Независимо от типа фактического аргумента он вначале вычисляется, а затем его величина передается функции. Фактический аргумент - это конкретное значение, которое присваивается переменной, называемой формальным аргументом.Если для связи с некоторой функцией требуется более одного аргумента, то наряду с именем функции можно задать список аргументов, разделенных запятыми. Например: print_num(i,j) int i,j; { printf("значение i=%d. Значение j=%d.", i,j); } Обращение в программе к данной функции будет таковым: print_num(6,19); Особое внимание нужно уделить правилам передачи аргументов при обращении к функциям. Синтаксис языка Си предусматривает только один способ передачи аргументов - передачу по значениям. Это означает, что формальные параметры (аргументы) функции локализованы в ней, то есть недоступны вне определения функции и никакие операции над формальными параметрами в теле функции не изменяют значения фактических параметров. Передача параметров по значению предусматривает следующие действия: Функции с переменным количеством аргументовВ языке Си допустимы функции, количество аргументов у которых при компиляции функции не фиксировано. Количество и тип аргументов становится известным только в момент вызова функции, когда явно задан список фактических аргументов (параметров). При определении и описании таких функций, имеющих списки параметров неопределенной длины. Спецификация формальных параметров заканчивается запятой и многоточием:тип имя(спецификация-явных-параметров,...); Здесь тип - тип возвращаемого функцией значения; имя - имя функции. Спецификация явных параметров - список спецификации параметров, количество и типы которых фиксированы и известны в момент компиляции. Эти параметры обязательны. Каждая функция с переменным количеством параметров должна иметь хотя бы один обязательный параметр. После списка явных (обязательных) параметров ставится запятая, а затем - многоточие. Компилятор знает, что дальнейший контроль соответствия количества и типов параметров при обработке вызова функции проводить не нужно. Чтобы функция с переменным количеством параметров могла воспринимать параметры различных типов, необходимо в качестве исходных данных каким-то образом передавать ей информацию о типах параметров. Пример: #include on_load_lecture() ![]() ![]() Перейти к вопросам " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | 4 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Локальные переменныеПеременные, известные только одной функции, а именно той, которая их содержит, называются локальными переменными. Теперь мы подчеркиваем, что локальные переменные являются действительно локальными. Даже в том случае, когда мы используем одно и то же имя для переменных в двух различных функциях, компилятор считает их разными переменными.Нахождение адресовВ результате выполнения операции & определяется адрес ячейки памяти, которая соответствует переменной. Если age - имя переменной, то &age - ее адрес. Можно представить себе адрес как ячейку памяти, но можно рассматривать его и как метку, которая используется компилятором для идентификации переменной. Предположим, мы имеем операторage=105; Пусть также адрес ячейки, где размещается переменная age - 15125. В результате выполнения оператора printf("%d %d\n", age, &age); получим 105 15125. Операция косвенной адресации *Предположим, мы знаем, что в переменной ptr содержится ссылка на переменную name. Тогда для доступа к значению этой переменной можно воспользоваться операцией косвенной адресации *:val = *ptr; /* определение значения, на которое указывает ptr */ Последние два оператора, взятые вместе, эквивалентны следующему: val = name; Описание указателейПримеры описания указателей:int *pi; /*указатель на переменную типа целого*/ char *pc; /*указатель на символьную переменную*/ float *pf, *pg; /* указатели на переменные с плавающей точкой*/ Спецификация типа задает тип переменной, на которую ссылается указатель, а символ звездочка (*) определяет саму переменную как указатель. Описание вида int *pi говорит , что pi - это указатель и что *pi - величина типа int. При вызове функции информация о переменной может передаваться функции в двух видах. Если мы используем форму обращения function1(x); происходит передача значения переменной x. Если же мы используем форму обращения function2(&x); происходит передача адреса переменной x. Первая форма обращения требует, чтобы определение функции включило в себя формальный аргумент того же типа, что и x: function1(num) int num; Вторая форма обращения требует, чтобы определение функции включало в себя формальный аргумент, являющийся указателем на объект соответствующего типа: function2(ptr) int *ptr; on_load_lecture() ![]() ![]() Дальше " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | 4 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Подведем итоги по указателямКогда за знаком & следует имя переменной, результатом операции является адрес указанной переменной.Когда за знаком * следует указатель на переменную, результатом операции является величина, помещенная в ячейку с указанным адресом. Пример: age = 105; ptr =&age;/*указатель на age*/ val= *ptr; Результатом выполнения этого фрагмента является присваивание значения 105 переменной val.
Типичные определения функции имеет следующий вид: имя (список аргументов) описание аргументов тело функции Наличие списка аргументов и описаний не обязательны. Переменные, отличные от аргументов, описываются внутри тела, которое заключается в фигурные скобки. Аргументы используются для передачи значений из вызывающей программы в функцию. Использование ключевого слова return позволяет передавать в вызывающую программу одно значение из вызываемой функции. Обычно выполнение функции не оказывает никакого влияния на значения переменных вызывающей программы. Чтобы иметь возможность непосредственно изменять значения переменных вызывающей программы, необходимо использовать указатели в качестве аргументов. Это может оказаться необходимым в случае, если в вызывающую программу требуется передать более чем одно значение. Функции должны иметь тот же тип, что и значения, которые они возвращают в качестве результатов. По умолчанию предполагается, что функции имеют тип int. Если функция имеет другой тип, он должен быть указан и в вызывающей программе, и в самом определении функции. Создание и использование функцийПринципы программирования на языке Си основаны на понятии функции. Мы уже рассмотрели несколько функций: printf( ), scanf( ), getchar( ), putchar( ). Эти функции являются системными, однако мы создали и несколько своих собственных функций под общим именем main( ). Выполнение программы всегда начинается с команд, содержащихся в функции main( ), затем последняя вызывает другие функции. Рассмотрим вопрос, как создавать свои собственные функции и делать их доступными для функции main( ), а также для других функций.Функция - это самостоятельная единица программы, спроектированная для реализации конкретной задачи. Вызов функций приводит к выполнению некоторых действий. Например, при обращении к функции printf( ) осуществляется вывод данных на экран. В общем, функции могут выполнять действия и получать значения величин, используемых в программе. Почему мы пользуемся функциями? Во-первых, они избавляют нас от повторного программирования. Если конкретную задачу в программе необходимо выполнить несколько раз, мы напишем соответствующую функцию только один раз, а затем будем вызывать ее всегда, когда требуется. Во-вторых, мы можем применять одну функцию, например putchar( ), в различных программах.
Многие программисты думают о функции, как о "черном ящике". Они задают ее через поступающую информацию и полученные результаты. Все, что происходит внутри черного ящика, их не касается. Что нам требуется знать о функциях? Нужно знать, как их можно определять, как к ним обращаться и как устанавливать связи между функцией и программой, ее вызывающей. Абстракция управления в языке Си обеспечивается с помощью функций. Все функции могут быть рекурсивными. В языке Си отсутствуют подпрограммы (процедуры), однако возврат функцией значения в вызывающую программу не обязателен. Следовательно, функции могут быть разделены на две категории - функции, возвращающие значения, и функции, не возвращающие значения в вызывающую программу (подпрограммы) . Определение функций, возвращающих значение, имеет следующий формат: [static] тип-результата имя-функции ( формальные аргументы) описание формальных параметров { тело функции } где имя функции - правильный идентификатор, а тело функции имеет вид определения и описания операторы
return e; который обеспечивает выдачу результата e. Функция, возвращающая значение, может содержать более одного оператора return. Определения функции, не возвращающей значения, имеют следующий формат: [static] void имя-функции(формальные аргументы) описание формальных параметров { тело функции } Выполнение такой функции завершается, если выполнено ее тело или оператор return вида return; Функция, не возвращающая значения, может содержать более одного оператора return. Класс памяти static (необязательный) ограничивает видимость функции и других внешних определений. Функция с классом памяти static невидима вне содержащего ее файла. Если в тексте программы есть обращение к функции, то необходимо описание функции, которое в тексте должно быть помещено раньше ее определения. Описания функции имеют следующую форму: [static или extern] тип-результата имя-функции( ); [static или extern] void имя-функции( ); Если в описании не указан класс памяти (см. лекцию 10) , то по умолчанию, предполагается extern.
Указатели, первое знакомствоУказатель - некоторое символическое представление адреса. В описанном примере &age означает указатель на переменную age. Фактический адрес - это число, а символическое представление адреса &age является константой типа указатель. Адрес ячейки, отводимой переменной age, в процессе выполнения программы не меняется. В языке Си имеются и переменные типа указатель. Точно так же как значением переменной типа char является символ, а значением переменной типа int - целое число, значением переменной типа указатель служит адрес некоторой величины. Если мы дадим указателю имя ptr, то сможем написать, например, такой оператор:ptr = &age; /* присваивает адрес age переменной ptr */ Мы говорим в этом случае, что ptr указывает на age. Различие между двумя формами записи, ptr и &age, заключается в том, что ptr - это переменная, в то время как &age - константа. В случае необходимости мы можем сделать так, чтобы переменная ptr указывала на какой-нибудь другой объект: ptr = &name; /*ptr указывает на name, а не на age*/ Теперь значением переменной ptr является адрес переменной name. Возвращение значенийНапишем функцию, вычисляющую абсолютную величину числа. Абсолютная величина числа - это его значение, если отбросить знак. Например, абсолютная величина 125 - это 125, а абсолютная величина числа (-125) - это тоже 125. Назовем эту функцию abs( ). Входом для этой функции будет любое число, для которого мы хотим найти абсолютную величину. Выходная величина возвращается при помощи ключевого слова языка Си - return. Поскольку функция abs( ) должна быть вызвана другой функцией, мы создадим простую функцию main( ), основной целью которой будет проверка, работает ли функция abs( ). Программа, спроектированная для того, чтобы проверить работу функции именно таким образом, называется драйвером. Драйвер подвергает функцию последовательным проверкам. Если результаты оказываются удовлетворительными, то ее можно поместить в программу, заслуживающую большего внимания. Термин драйвер обычно относиттся к программам, управляющим работой устройств:/*драйвер*/ main( ) { int a=100, b=0, c=-122; int d,e,f; d=abs(a); e=abs(b); f=abs(c); printf("%d, %d, %d\n",d,e,f); } abs(x) /* функция, вычисляющая величину числа */ int x; { int y; y = (x < 0) ? -x : x; /*возвращает значение y вызывающей программе*/ return(y); } Результат работы программы выглядит так: 100 0 122 Ключевое слово return указывает на то, что значение выражения, заключенного в круглые скобки, будет присвоено функции, содержащей это ключевое слово (оператор). Поэтому, когда функция abs( ) впервые вызывается драйвером, значением abs(a) будет число 100, которое затем присваивается переменной d. Переменная y является внутренним объектом функции abs( ), но значение y передается в вызывающую программу с помощью оператора return. Действие, оказываемое оператором d=abs(a); по другому можно выразить так: abs(a); d=y; Такой записью мы воспользоваться не можем! Вызывающая программа даже не подозревает о том, что переменная y существует. Оператор return оказывает и другое действие. Он завершает выполнение функции и передает управление следующему оператору в вызывающей функции. Это происходит даже в том случае, если оператор return является не последним оператором тела функции: /* Функция, вычисляющая абсолютную величину числа, вторая версия */ abs(x) int x; { if(x < 0) return(-x); else return(x); } Эта версия программы проще. Для пользователя обе версии неразличимы, т. к. у них имеется один и тот же вход, и они обеспечивают один и тот же выход. Только внутренние структуры обоих функций различны. /* третья версия функции abs( ) */ abs(x) int x; { if(x < 0) return(-x); else return(x); printf("Работа завершена!\n"); } Наличие оператора return препятствует тому, чтобы оператор печати когда-нибудь выполнился в программе. Можно пользоваться оператором return; Его применение приводит к тому, что функция, в которой он содержится, завершает свое выполнение и управление возвращается в вызывающую функцию. Поскольку у данного оператора отсутствует выражение в скобках, никакое значение не передается функции. Автоматические переменныеПо умолчанию переменные, описанные внутри функции, являются автоматическими. Можно, однако, это подчеркнуть явно с помощью ключевого слова auto:main( ) { auto int kat; } Так поступают, если хотят, например, показать, что определение переменной не нужно искать вне функции. Автоматические переменные имеют локальную область действия. Только функция, в которой переменная определена, знает ее. Другие функции могут использовать переменные с тем же самым именем, но это будут независимые переменные, находящиеся в разных ячейках памяти. Автоматическая переменная начинает существовать при вызове функции, содержащей ее. Когда функция завершает свою работу и возвращает управление туда, откуда ее вызвали, автоматическая переменная исчезает. Область действия автоматической переменной ограничена блоком, т.е. { }, в котором переменная описана.
on_load_lecture() ![]() ![]() Дальше " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | 1 | 2 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Классы памяти и область действияКлассы памяти языка Си дают возможность определить, с какими функциями связаны какие переменные, и как долго переменная сохраняется в программе. Мы уже упоминали, что локальные переменные известны только функциям, содержащим их. В языке Си предполагается также, что о глобальных переменных знают сразу несколько функций:/* глобальная переменная ext */ int ext; /* внешняя переменная */ main( ) { extern int ext; printf("Сколько курсов на сайте intuit.ru?\n"); scanf("%d",&ext); while(ext != 30) critic( ); printf("Посмотрите на сайте!\n"); } critic( ) { extern int ext; printf("Ошибка. Попытайтесь снова.\n"); scanf("%d",&ext); } Результат: Сколько курсов на сайте intuit.ru? 20 Ошибка. Попытайтесь снова. 30 Посмотрите на сайте! Мы сделали переменную ext внешней, описав ее вне любого определения функции. Внутри функции, использующей эту переменную, мы объявляем ее внешней при помощи ключевого слова extern, предшествующего спецификации типа переменной. Компилятор ищет определение этой переменной вне функции. Если бы мы опустили ключевое слово extern в функции critic( ), то компилятор создал бы в функции critic новую переменную и тоже назвал бы ее ext. Тогда другая переменная ext, которая находится в main( ), никогда не получила бы нового значения. Каждая переменная имеет тип и принадлежит к некоторому классу памяти. Время жизни и область действия идентификатора определяются ассоциированным с ним классом памяти. Существуют четыре разновидности классов памяти: auto - автоматический - локальные идентификаторы, память для которых выделяется при входе в блок, т.е. составной оператор, и освобождается при выходе из блока. Слово auto является сокращением слова automatic. static - статический - локальные идентификаторы, существующие в процессе всех выполнений блока. В отличие от идентификаторов типа auto, для идентификаторов типа static память выделяется только один раз - в начале выполнения программы, и они существуют, пока программа выполняется. extern - внешний - идентификаторы, называемые внешними, external, используются для связи между функциями, в том числе независимо скомпилированными функциями, которые могут находиться в различных файлах. Память, ассоциированная с этими идентификаторами, является постоянной, однако ее содержимое может меняться. Эти идентификаторы описываются вне функции. register - регистровый - идентификаторы, подобные идентификаторам типа auto. Их значения, если это возможно, должны помещаться в регистрах машины для обеспечения быстрого доступа к данным. Если класс памяти идентификатора не указан явно, то его класс памяти задается положением его определения в тексте программы. Если идентификатор определяется внутри функции, тогда его класс памяти auto, в остальных случаях идентификатор имеет класс памяти extern. Предположим, что имеется программа на языке Си, исходный текст которой содержится в нескольких файлах. Для разделения данных (для связи) в функциях в этих файлах используются идентификаторы, определенные как extern. Если функция ссылается на внешний идентификатор, то файл, содержащий его, должен иметь описание или определение этого идентификатора. Явное задание класса памяти extern указывает на то, что этот идентификатор определен в другом файле, и здесь ему память не выделяется, а его описание дано лишь для проверки типа и для генерации кода.
Для внешнего идентификатора память выделяется только в том случае, если класс памяти не указан явно.
Определение класса памяти переменной зависит от того, где переменная описана и какое ключевое слово, если оно есть, используется. Класс памяти позволяет установить два факта. Во-первых, определить, какие функции имеют доступ к переменной. Пределы, до которых переменная доступна, характеризуют ее область действия. Во-вторых, определить, как долго переменная находится в памяти. Теперь подробнее рассмотрим свойства каждого типа. Регистровые переменныеОбычно переменные хранятся в памяти машины. Регистровые переменные запоминаются в регистрах центрального процессора, где доступ к ним и работа с ними выполняются гораздо быстрее, чем в памяти. В остальном регистровые переменные аналогичны автоматическим переменным.Пример: main( ) { register int pleat; } Компилятор сравнивает наши требования с количеством доступных регистров, поэтому мы можем и не получить то, что хотим. В этом случае переменная становится простой автоматической переменной. Особенности работы с языком Си. Какой класс памяти применять? Ответ на вопрос - автоматический. Этот класс памяти выбран по умолчанию. Использование внешних переменных очень соблазнительно. Если описать все переменные как внешние, то не будет забот при использовании аргументов и указателей для связи между функциями в прямом и обратном направлениях. Но тогда возникает проблема с функцией С, изменяющей переменные в функции А, а мы этого не хотели! Такая проблема значительно перевешивает кажущуюся привлекательность широкого использования внешних переменных. Одно из золотых правил программирования заключается в соблюдении принципа "необходимо знать только то, что нужно". Организуйте работу каждой функции автономно, насколько это возможно, и используйте глобальные переменные только тогда, когда это действительно необходимо! Операция получения адреса & неприменима к регистровым переменным. Любые переменные в блоке, кроме формальных параметров функции, могут быть определены как статические. Подведем итог. Классы памяти, которые описываются внутри функции: Классы памяти, которые определяются вне функции: Статические переменныеСтатические здесь означает, что переменные остаются в работе. Они имеют такую же область действия, как автоматические переменные, но они не исчезают, когда содержащая их функция закончит свою работу. Компилятор хранит их значения от одного вызова функции до другого.Пример: /* Статическая переменная */ main( ) { int count for(count = 1;count <= 3; count ++) { printf("Подсчет студентов %d:\n", count); man_woman ( ); } } man_woman( ) { int man = 1; static int woman = 1; printf("юношей = %d и девушек = %d\n", man++, woman++); } Функция man_woman увеличивает каждую переменную после печати ее значения. Работа этой программы дает следующие результаты: Подсчет студентов 1: юношей = 1 и девушек = 1 Подсчет студентов 2: юношей = 1 и девушек = 2 Подсчет студентов 3: юношей = 1 и девушек = 3 Статическая переменная woman помнит, что ее значение было увеличено на 1, в то время как для переменной man начальное значение устанавливается каждый раз заново. Это указывает на разницу в инициализации: man инициализируется каждый раз, когда вызывается man_woman ( ), в то время как woman инициализируется только один раз при компиляции функции man_woman ( ). Внешние переменныеПеременная, описанная вне функции, является внешней.Глобальные переменные определяются на том же уровне, что и функции, т.е. они не локальны ни в каком блоке. Постоянные глобальные переменные инициализируются нулем, если явно не задано другое начальное значение. Областью действия является вся программа. Они должны быть описаны во всех файлах программы, в которых к ним есть обращения. Некоторые компиляторы требуют, чтобы глобальные переменные были определены только в одном файле, и описаны как внешние в других файлах, где они используются. Глобальные переменные должны быть описаны в файле до первого использования. Пример: int global_flag; Внешнюю переменную можно описать и в функции, которая использует ее, при помощи ключевого слова extern. Группу extern-описаний можно совсем опустить, если исходные определения переменных появляются в том же файле и перед функцией, которая их использует. Включение ключевого слова extern позволяет функции использовать внешнюю переменную, даже если она определяется позже в этом или другом файле. Оба файла должны быть скомпилированы, связаны или собраны в одно и то же время. Если слово extern не включено в описание внутри функции, то под этим именем создается новая автоматическая переменная. Мы можем пометить вторую переменную как автоматическую с помощью слова auto. Внешние статические переменныеМожно описать статические переменные вне любой функции. Это создает внешнюю статическую переменную. Разница между внешней переменной и внешней статической переменной заключается в области их действия. Обычная внешняя переменная может использоваться функциями в любом файле, а внешняя статическая переменная может использоваться только функциями того же самого файла, причем после определения переменной. Статическую переменную мы описываем вне любой функции.Использование аргументов с #defineВо избежание ошибок при вычислении выражений параметры макроопределения необходимо заключать в скобки.#define идентификатор1 (идентификатор2, . . .) строка Пример: #define abs(A) (((A) > 0)?(A) : -(A)) Каждое вхождение выражения abs(arg) в тексте программы заменяется на ((arg) > 0) ? (arg) : -(arg), причем параметр макроопределения А заменяется на arg. Пример: #define nmem (P,N)\ (P) -> p_mem[N].u_long Символ \ продолжает макроопределение на вторую строчку. Это макроопределение уменьшает сложность выражения, описывающего массив объединений внутри структуры. Макроопределение с аргументами очень похоже на функцию, поскольку аргументы его заключены в скобки: /* макроопределение с аргументами */ #define SQUARE(x) x*x #define PR(x) printf("x равно %d.\n", x) main( ) { int x = 4; int z; z = SQUARE(x); PR(z); z = SQUARE(2); PR(z); PR(SQUARE(x)); PR(SQUARE(x+2)); PR(100/SQUARE(2)); PR(SQUARE(++x)); } Всюду, где в нашей программе появляется макроопределение SQUARE(x), оно заменяется на x*x . В отличие от наших прежних примеров, при использовании этого макроопределения мы можем совершенно свободно применять символы, отличные от x. В макроопределении 'x' замещается символом, использованным в макровызове программы. Поэтому макроопределение SQUARE(2) замещается на 2*2. Таким образом, x действует как аргумент. Однако, аргумент макроопределения не работает - точно так же, как аргумент функции. Вот результаты выполнения программы: z равно 16. z равно 4. SQUARE(x) равно 16. SQUARE(x+2) равно 14. 100/SQUARE(2) равно 100. SQUARE(++x) равно 30. printf("SQUARE(x) равно %d.\n",x*x); и выводит на печать SQUARE(x) равно x*x. Если макроопределение включает аргумент с двойными кавычками, то аргумент будет замещаться строкой из макровызова. Но после этого он в дальнейшем не расширяется, даже если строка является еще одним макроопределением. В нашем примере переменная x стала макроопределением SQUARE(x) и осталась им. Вспомним, что x=4. Это позволяет предположить, что SQUARE(x+2) будет равно 6*6 или 36. Но напечатанный результат говорит, что получается число 14. Причина такого результата такова: препроцессор не делает вычислений. Он только замещает строку. Всюду, где наше определение указывает на x, препроцессор подставит строку x+2. Таким образом, x*x становится x+2*x+2 Если x равно 4, то получается 4+2*4+2=4+8+2=14 Вызов функции передает значение аргумента в функцию во время выполнения программы. Макровызов передает строку аргументов в программу до ее компиляции. Макроопределение или функция?
Макроопределения должны использоваться скорее как хитрости, а не как обычные функции. Они могут иметь нежелательные побочные эффекты. Некоторые компиляторы ограничивают макроопределения одной строкой, и, по-видимому, лучше соблюдать такое ограничение, даже если ваш компилятор этого не делает. Выбор макроопределения приводит к увеличению объема памяти, а выбор функции - к увеличению времени работы программы. Макроопределение создает строчный код, т.е. мы получаем оператор в программе. Если макроопределение применить 20 раз, то в программу вставится 20 строк кода. Если мы используем функцию 20 раз, то у нас будет только одна копия операторов функции. Однако управление программой следует передать туда, где находится функция, а затем вернуться в вызывающую программу, а на это потребуется больше времени, чем при работе со строчными кодами. Так что думайте, что выбирать! Преимущество макроопределений заключается в том, что при их использовании нам не нужно беспокоиться о типах переменных, т.к. макроопределения имеют дело с символьными строками, а не с фактическими значениями. Tак наше макроопределение SQUARE(x) можно использовать одинаково хорошо с переменными типа int или float. Запомним! Предположим, что мы разработали несколько макрофункций по своему усмотрению. Если мы пишем новую программу, мы не должны их переопределять. Нужно использовать директиву #include. Номер строки и имя файла#line целая_константа "имя_файла"Пример: #line 20 "ABC" Препроцессор изменяет номер текущей строки и имя компилируемого файла. Имя файла может быть опущено. Одна из целей использования условной компиляции - сделать программу более мобильной. Изменяя несколько ключевых определений в начале файла, мы можем устанавливать различные значения и включать различные файлы для разных систем. Пример: #define N 3/*определение константы */ void main( ) { #line 55 "file.c" double x[3*N]; } on_load_lecture() ![]() ![]() Дальше " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | 4 | 5 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Общие сведенияВ интегрированную среду подготовки программ на Си или в компилятор языка как обязательный компонент входит препроцессор. Назначение препроцессора - обработка исходного текста программы до ее компиляции. Препроцессорная обработка включает несколько стадий, выполняемых последовательно. Конкретная реализация может объединять несколько стадий, но результат должен быть таким, как если бы они выполнялись в следующем порядке:Поясним, что понимается под препроцессорными лексемами или лексемами препроцессора. К ним относятся символьные константы, имена включаемых файлов, идентификаторы, знаки операций, препроцессорные числа, знаки препинания, строковые константы и любые символы, отличные от пробела. Стадия обработки директив препроцессора. При ее выполнении возможны следующие действия: Прагмы#pragmaЭта директива определяет действия, зависящие от конкретной реализации компилятора. Например в некоторые компиляторы входит вариант этой директивы для извещения компилятора о наличии в тексте программы команд на языке Ассемблер. Возможности команды #pragma могут быть разнообазными. Стандарта для них не существует. Если конкретный препроцессор встречает прагму, которая ему неизвестна, он ее просто игнорирует как пустую директиву. В некоторых реализациях включена прагма. #pragma pack(n), где n= 1,2,4. Прагма pack позволяет влиять на упаковку смежных элементов в структурах и объединениях (см. лекцию 14). Соглашение может быть таким: pack(1) - выравнивание элементов по границам байтов; pack(2) - выравнивание элементов по границам слов; pack(4) - выравнивание элементов по границам двойных слов; В некоторые компиляторы включены прагмы, позволяющие изменять способ передачи параметров функциям, порядок помещения параметров в стек и т.д. Пустая директива#Использование этой директивы не вызывает никаких действий. Реакция на ошибки#error последовательность лексемОбработка директивы приводит к выдаче диагностического сообщения в виде, определенном последовательностью лексем. Применение этой директивы совместно с условными препроцессорными командами. Пример: #define NAME 15 В дальнейшем можно проверить ее значение и выдать сообщение, если у NAME окажется другое значение: #if (NAME !=15) #error NAME должно быть равно 15! Сообщение будет выглядеть так: error <имя_файла><номер_строки >; error directive: NAME должно быть равно 15! Символические константы: #defineЕсли в качестве первого символа в строке программы используется символ #, то эта строка является командной строкой препроцессора (макропроцессора). Командная строка препроцессора заканчивается символом перевода на новую строку. Если непосредственно перед концом строки поставить символ обратной косой черты "\", то командная строка будет продолжена на следующую строку программы.Директива #define, подобно всем директивам препроцессора, начинается c символа # в самой левой позиции. Она может появиться в любом месте исходного файла, а даваемое определение имеет силу от места появления до конца файла. Мы активно используем эту директиву для определения символических констант в наших примерах программ, однако она имеет более широкое применение, что мы покажем дальше. Условная компиляцияКомандные строки препроцессора используются для условной компиляции различных частей исходного текста в зависимости от внешних условий.#if константное_выражение Пример: #if ABC + 3 Истина, если константное выражение ABC + 3 не равно нулю. #ifdef идентификатор Пример: #ifdef ABC истина, если идентификатор ABC определен ранее командой #define. #ifndef идентификатор Пример: #ifndef ABC истина, если идентификатор ABC не определен в настоящий момент. #else . . . #endif Если предшествующие проверки #if, #ifdef или #ifndef дают значение "Истина", то строки от #else до #endif игнорируются при компиляции. Если эти проверки дают значение "Ложь", то строчки от проверки до #else (а при отсутствии #else - до #endif) игнорируются. Команда #endif обозначает конец условной компиляции. Пример: #ifdef DEBUG fprintf (stderr, "location: x = %d\n", x); #endif Включение файла: #includeПеречень обозначений заголовочных файлов для работы с библиотеками компиляторов утвержден стандартом языка. Ниже приведены названия этих файлов, а также краткие сведения о тех описаниях и определениях, которые в них включены. Большинство описаний - прототипы стандартных функций, а определены в основном константы, например EOF, необходимые для работы с библиотечными функциями.assert.h - диагностика программ ctype.h - преобразование и проверка символов errno.h - проверка ошибок float.h - работа с вещественными данными limits.h - предельные значения целочисленных данных locale.h - поддержка национальной среды math.h - математические вычисления setjump.h - возможности нелокальных переходов signal.h - обработка исключительных ситуаций stdarg.h - поддержка переменного числа параметров stddef.h - дополнительные определения stdio.h - средства ввода-вывода stdlib.h - функции общего назначения (работа с памятью) string.h - работа со строками символов time.h - определение дат и времени В конкретных реализациях количество и наименование заголовочных файлов могут быть и другими. Например, в компиляторах для MS-DOS активно используются заголовочные файлы mem.h, alloc.h, conio.h, dos.h и другие. В компиляторах Turbo C, Borland C++ для связи с графической библиотекой применяется заголовочный файл graphics.h. on_load_lecture() ![]() ![]() Дальше " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | 4 | 5 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Встроенные макроименаСуществуют встроенные (заранее определенные) макроимена, доступные препроцессору во время обработки. Они позволяют получить следующую информацию:_DATA_ - строка символов в формате: "месяц число год", определяющая дату начала обработки исходного файла. Например, после препроцессорной обработки текста программы, выполненной 29 января 2005 года, оператор printf(_DATA_); станет таким printf("January 29 2005"); _LINE_ - десятичная константа - номер текущей обрабатываемой строки файла с программой на Си. Принято, что номер первой строки исходного файла равен 1; _FILE_ - строка символов - имя компилируемого файла. Имя изменяется всякий раз, когда препроцессор встречает директиву #include с указанием имени другого файла. Когда включения файла по команде #include завершаются, востанавливается предыдущее значение макроимени _FILE_; _TIME_ - строка символов вида "часы:минуты:секунды", определяющая время начала обработки препроцессором исходного файла; _STDC_ - константа, равная 1, если компилятор работает в соответствии с ANSI-стандартом. В противном случае значение микроимени _STDC_ не определено. Стандарт языка Си предполагает, что наличие имени _STDC_ определяется реализацией, так как макрос _STDC_ относится к нововведениям стандарта. В конкретных реализациях набор предопределенных имен гораздо шире. Для получения более полных сведений о предопределенных препроцессорных именах следует обращаться к документации по конкретному компилятору. on_load_lecture() ![]() ![]() Перейти к вопросам " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | 4 | 5 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Замена идентификаторов#define идентификатор строкаПример: #define ABC 100 Заменяет каждое вхождение идентификатора ABC в тексте программы на 100: #undef идентификатор Пример: #undef ABC Отменяет предыдущее определение для идентификатора ABC. Пример: /* Простые примеры директивы препроцессора */ #define TWO 2 /* можно использовать комментарии*/ #define MSG "Текст 1.\ Продолжение текста 1" /* обратная косая черта продолжает определение на следующую строку */ #define FOUR TWO*TWO #define PX printf("X равен %d.\n", x) #define FMT "X равен %d.\n" main( ) { int x = TWO; PX; x = FOUR; printf(FMT,x); printf("%s\n",MSG); printf("TWO:MSG\n"); } В результате выполнения нашего примера будем иметь: X равен 2 X равен 4 Текст 1. Продолжение текста 1 TWO: MSG Разберем, что произошло. Оператор int x = TWO; превращается в int x = 2; Затем оператор PX; превращается в printf("X равно %d.\n",x); поскольку сделана полная замена. Теперь мы видим, что макроопределение может представлять любую строку, даже целое выражение на языке Си. Заметим, что это константная строка. PX напечатает только переменную, названную x. #define HAL 'X' определяет символьную константу, а #define HAR "X" определяет символьную строку X\0 Обычно препроцессор, встречая одно из макроопределений в программе, очень точно заменяет их эквивалентной строкой замещения. Если эта строка также содержит макроопределения, они тоже замещаются. Единственным исключением при замене является макроопределение, находящееся внутри двойных кавычек. Поэтому printf("TWO: MSG"); печатает буквально TWO: MSG вместо печати следующего текста: 2: "Текст 1. Продолжение текста 1" Если нам нужно напечатать этот текст, можно использовать оператор printf("%d: %s\n",TWO,MSG); потому что здесь макроопределения находятся вне кавычек. Когда следует использовать символические константы? Вероятно, мы должны применять их для большинства чисел. Если число является константой, используемой в вычислениях, то символическое имя делает яснее ее смысл. Если число - размер массива, то символическое имя упрощает изменение вашей программы при работе с большим массивом. Если число является системным кодом, скажем для символа EOF, то символическое представление делает программу более переносимой. Изменяется только определение EOF. Мнемоническое значение, легкость изменения, переносимость: все это делает символические константы заслуживающими внимания! Динамические объектыУказатели используются при создании и обработке динамических объектов. Заранее определяемые объекты создаются с помощью определений. Динамические объекты, в отличие от заранее определяемых, создаются динамически и явно в процессе выполнения программы. Для создания динамических объектов служат функции malloc и calloc. В отличие от заранее определенных объектов, число динамических объектов не фиксировано тем, что записано в тексте программы, - по желанию динамические объекты могут создаваться и уничтожаться в процессе ее выполнения. Динамические объекты, в отличие от заранее определенных, не имеют имен, и ссылка на них выполняется с помощью указателей.Значение 0 может быть присвоено указателям любого типа. Это значение показывает, что данный указатель не содержит ссылки на какой-либо объект. Попытка использовать это значение для обращения к объекту может привести к ошибке, но только в операционных системах с защитой памяти. По соглашению, для обозначения константы с нулевым значением используется идентификатор NULL, описание которого находится в библиотеке stddef.h и является системозависимым. on_load_lecture() ![]() ![]() Дальше " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | 1 | 2 | 3 | 4 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Доступ к динамическим объектамПрисваивание значения объекту, ссылка на который задана указателем pi, выполняется с помощью имени указателя *pi, например:*pi = 55; Одно и то же значение может быть присвоено более чем одной переменной-указателю. Таким образом, можно ссылаться на динамический объект с помощью более одного указателя. Про объект, к которому можно обращаться с использованием более чем одного указателя, говорят, что он имеет псевдоимена (alias). Например, в результате присваивания qi = pi; и qi, и pi указывают на один и тот же объект, т.е. они являются псевдоименами. Неуправляемое использование псевдоимен может нанести ущерб пониманию текста программы, так как возможность доступа к одному и тому же объекту и его модификация с помощью различных псевдоимен не всегда очевидны при анализе части программы. Функции, массивы и указателиМассивы можно использовать в программе двояко. Во-первых, их можно описать в теле функции. Во-вторых, они могут быть аргументами функции. Все, что было сказано о массивах, относится к первому их применению. Теперь рассмотрим массивы в качестве аргументов. Проанализируем скелет программы, обращая внимание на описания./* массив-аргумент */ main( ) { int ages[50]; /* массив из 50 элементов */ convert(ages); _ } convert(years); int years[ ];/* каков размер массива? */ { _ } Очевидно, что массив ages состоит из 50 элементов. А что можно сказать о массиве years? Оказывается в программе нет такого массива. Описатель int years[ ]; создает не массив, а указатель на него! Посмотрим, почему это так. Вот вызов нашей функции: convert(ages); ages - аргумент функции convert. Имя ages является указателем на первый элемент массива, состоящего из 50 элементов. Таким образом, оператор вызова функции передает ей указатель, т. е. адрес функции convert( ). Это значит, что аргумент функции является указателем, и мы можем написать функцию convert( ) следующим образом: convert(years); int *years; { _ } Действительно, операторы int years[ ]; int *years; - синонимы. Оба они объявляют переменную years указателем массива целых чисел. Однако главное их отличие состоит в том, что первый из них напоминает нам, что указатель years ссылается на массив. Как теперь связать его с массивом ages? При использовании указателя в качестве аргумента, функция взаимодействует с соответствующей переменной в вызывающей программе, т.е. операторы, использующие указатель years в функции convert( ), фактически работают с массивом ages, находящимся в теле функции main( ). Короче говоря, когда имя массива применяется в качестве аргумента, функции передается указатель. Затем функция использует этот указатель для выполнения изменений в исходном массиве, принадлежащем программе, вызывающей функцию. Инициализация массивов и классы памятиМы знаем, что скалярные переменные можно инициализировать в описании типа при помощи таких выражений, как например:int fix = 1; float flax = PI*2; при этом предполагается, что PI - ранее введенное макроопределение. Можно ли инициализировать массивы? Внешние, статические и автоматические массивы можно инициализировать! Регистровые массивы инициализировать нельзя! Если ничего не засылать в массив перед началом работы с ним, то внешние, статические и автоматические массивы инициализируются для числовых типов нулем и '\0' (null) для символьных типов, а регистровые массивы содержат какой-то мусор, оставшийся в этой части памяти. Если в статическом, внешнем или автоматическом массиве нам нужны первоначальные значения, отличные от нуля, в этом случае мы можем делать так: /* дни месяца */ int days[12]={31,28,31,30,31,30,31,31,30,31,30,31}; main( ) { int index; extern int days[];/*необязательное описание */ for(index = 0; index<12; index++) printf("Месяц %d имеет %d дней.\n", index+1, days[index]); } Результат: Месяц 1 имеет 31 дней. Месяц 2 имеет 28 дней. Месяц 3 имеет 31 дней. Месяц 4 имеет 30 дней. Месяц 5 имеет 31 дней. Месяц 6 имеет 30 дней. Месяц 7 имеет 31 дней. Месяц 8 имеет 31 дней. Месяц 9 имеет 30 дней. Месяц 10 имеет 31 дней. Месяц 11 имеет 30 дней. Месяц 12 имеет 31 дней. Количество элементов в списке инициализации должно соответствовать размеру массива. Если список меньше размера массива, то элементы массива, на которых не хватило списка, будут забиты нулями. Если же список больше массива, то компилятор выдаст синтаксическую ошибку. Надо просто выделить массив, размер которого будет достаточен для размещения списка. Предыдущую программу лучше переписать так: int days[ ] = {31,28,31,30,31,30,31,31,30,31,30,31}; main( ) { int index; extern int days[ ];/* необязательное описание */ for(index=0;index Первое: если мы используем пустые скобки для инициализации массива, то компилятор сам определит количество элементов в списке и выделит для него массив нужного размера. Второе: оно касается добавления, сделанного в управляющем операторе for. Не полагаясь на свои вычислительные способности, мы возложили задачу подсчета размера массива на компилятор. Оператор sizeof определяет размер в байтах объекта или типа, следующего за ним. Предположим в нашей вычислительной системе размер каждого элемента типа int равен двум байтам, поэтому для получения количества элементов массива мы делим общее число байтов, занимаемое массивом, на 2. Однако в других системах элемент типа int может иметь иной размер. Поэтому в общем случае выполняется деление на значение переменной sizeof для элемента типа int. В результате работы этой программы мы получаем точно 12 значений. Наш метод, позволяющий программе самой находить размер массива, не позволил нам напечатать конец массива. МассивыМассив является сложным объектом, состоящим из объектов-компонентов, называемых элементами одного и того же типа. Простые определения массива имеют видТип данных x[n1][n2]...[nk] Где x - идентификатор, определяемый в качестве имени массива, а ni - размерности массива. Массив x называется k-мерным массивом с элементами типа тип данных. Элементы i-го измерения имеют индексы от 0 до ni-1. Тип элемента массива может быть одним из основных типов, типом другого массива, типом указателя (pointer), типом структуры (struct) или типом объединения (union). Хотя элементы массива не могут быть функциями, они могут быть указателями на функции. Ниже приведены некоторые примеры определений массива: int page[10]; /* одномерный массив из 10 элементов, перенумерованный с 0 до 9 */ char line[81]; float big[10][10], sales[10][5][8]; /*двумерный массив и трехмерный массив*/ Ссылки на элемент k-мерного массива x делаются с помощью следующего обозначения: x[i1][i2]...[ik] где ij - целое выражение, при этом 0<=ij<=nj-1, а nj - максимальное значение j-го индекса массива x. Например: page[5] line[i+j-1] big[i][j] Указывая только первые p индексов, можно ссылаться на p-мерный подмассив k-мерного массива (p<=k), например, sales[i] /* ссылка на двумерный подмассив массива sales */ sales[i][j] /* ссылка на одномерный подмассив */ sales[i][j][k] /* ссылка на элемент массива*/ Операции с указателямиon_load_lecture() ![]() ![]() Перейти к вопросам " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | 4 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Создание динамических объектовПо стандарту аргументы функций malloc, calloc имеют тип возвращаемого объекта void*.char s = (char*)malloc(size); unsigned size; /* объем памяти, который необходимо выделить */ char *s = (char *)calloc(nelem,elsize); unsigned nelem; /* число элементов, для которых нужно выделить память */ unsigned elsize; /* объем памяти, который необходимо выделить для каждого элемента */ /* либо просто заменив char* на void* void* calloc(nelem, elsize); unsigned nelem; unsigned elsize; */ Обе функции возвращают знаковый указатель, указывающий на выделенную память. Для определения необходимого объема памяти можно использовать оператор sizeof: sizeof (выражение) Объем памяти, необходимый для хранения выражения: sizeof(T) Объем памяти, необходимый для хранения значений типа T. Функции malloc и calloc возвращают указатель на созданный динамический объект. Фактически функции возвращают знаковые указатели, которые могут быть явно преобразованы к подходящему типу указателя. Значения, возвращенные функциями распределения памяти, используются для ссылок на динамические объекты. Например, с помощью оператора pi = (int *) malloc(sizeof(int)); выделяется память для одного целого значения. Адрес этой области памяти присваивается переменной pi после его преобразования из типа char * (указатель на знак), с которым он возвращается функцией malloc, к типу int * (указатель на целое), т.е. типу переменной pi. Строки - дополнительные сведения о тесной связи между указателями и массивамиСтроки - это массивы знаков. По соглашению, последним знаком строки должен быть нулевой знак \0. Поскольку имя массива фактически является указателем на первый элемент массива, переменные типа string могут также рассматриваться, как имеющие тип char *. Например, вторая переменная string_array в определенииchar *string_pointer, string_array[81]; может рассматриваться также как знаковый указатель. Для строки, представленной первой переменной string_pointer, память должна быть выделена явно. С другой стороны, для массива string_array память является указателем на нее. Заметим, что память должна быть также выделена или зарезервирована для признака конца строки \0.
Связь между указателями и массивамиВ языке Си массивы и указатели тесно связаны. Имя каждого массива может рассматриваться как указатель на первый элемент массива. Элемент массива a[i] есть элемент массива, на который указывает значение a+i, т.е. *(a+i), где значение а является адресом первого элемента массива а, а именно a[0]. Выражение a+i является примером арифметических действий с указателями - целое значение i складывается со значением указателя, адресом первого элемента массива а. Значение этого выражения есть а плюс объем памяти, занимаемый i элементами массива a. Предположим, что x - двумерный массив. Тогда ссылка на подмассив x[i] является ссылкой на i-ю строку массива x. x[i] дает адрес первого элемента этой строки, т.е. *(x+i). Элементы каждой строки занимают непрерывную область памяти, так как массивы хранятся записанными по строкам, т.е. при записи элементов массива в память быстрее всех изменяется последний индекс.Аналогично, ссылка на y[i], где y - n-мерный (n>1) массив, является ссылкой на (n-1)-мерный подмассив с элементами y[i, j2,j3,_jn], где значения jk соответствуют определению массива y. y[i] дает адрес первого элемента этого подмассива, т.е. *(y+i). Все элементы этого (n-1)-мерного подмассива занимают непрерывную область памяти. on_load_lecture() ![]() ![]() Дальше " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | 4 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Указатели и массивыДопустимо бесконечно большое число различных типов указателей и массивов. Далее следуют типовые примеры.Указатель на основной тип: char *p; Переменная р является указателем на символ, т.е. этой переменной должен присваиваться адрес символа. Указатель на указатель: char **t; Переменная t - указатель на указатель символа. Одномерный массив: int a[50]; Переменная а - массив из 50 целых чисел. Двумерный массив: char m[7][50]; Переменная m - массив из семи массивов, каждый из которых состоит из 50 символов. Массив из семи указателей: char *r[7]; Массив r состоит из указателей на символы. Указатель на функцию: int (*f)(); f - указатель на функцию, возвращающую целое значение. УказателиУказателем называется компонент заданного типа, являющийся ссылкой на некоторую область памяти. Определение указателя имеет следующий вид:тип-данных *id1, *id2,_, *idn Тип переменных id1, id2,_,idn определяется как тип указателей на тип-данных. Эти переменные служат ссылками на объекты типа тип-данных. Этот тип называется базовым типом переменных-указателей. Ниже приведены несколько примеров определений указателей: int *pi, *qi;/* указатели на целые объекты */ char *c; /* указатель на символьный объект */ Время жизни динамического объектаПамять, занимаемая динамическими объектами, если она необходима для других целей, должна быть освобождена явным указанием. В противном случае эта память может быть потеряна, т.е. станет невозможным ее повторное использование. Явное освобождение выполняется использованием фунукции free, которая имеет следующую спецификацию:free(ptr) char *ptr; Необходимо предпринимать меры предосторожности для избежания ошибок, связанных со ссылками на объект, память для которого уже освобождена - проблема висящей ссылки (Horowwitz, E. 1983. Fundamentals of Programming Languages. Computer Science Press). Если реализация языка обеспечивает сборку мусора, то память, занимаемая объектами, к которым невозможен доступ, может быть автоматически подготовлена для повторного использования. Однако в языке Си, в отличие от языков Лисп и Снобол, такая возможность отсутствует. Указание на заранее определенные объекты. Указатели могут обеспечивать ссылку на заранее определенные объекты. Адрес такого объекта может быть определен использованием оператора адресации & (address of operator). Например, рассмотрим переменные i и pi, определенные как int i, *pi; Присваивание pi = &i; pi позволяет ссылаться на объект с именем i также с помощью указателя pi, используя обозначение *pi. Имена i и *pi - псевдоимена. Оператор & является также стандартным средством моделирования передачи параметров по ссылке. Однако его употребление может привести к проблеме висящей ссылки. Указание на произвольную ячейку памяти. С помощью явных преобразований можно получить указатель на произвольную ячейку памяти. Например, предположим, что pt является указателем типа T*. Тогда указатель на ячейку памяти 0777000 можно получить с помощью следующей записи: pt = (T*)0777000; Обращение к конкретным ячейкам памяти часто бывает необходимо в программах, взаимодействующих с оборудованием, например в драйверах устройств, когда для управления устройствами нужно иметь доступ к таким ячейкам памяти, как регистры состояния или ячейки буфера устройства. Хотя такие возможности полезны и даже необходимы для некоторых приложений, пользоваться ими следует с осторожностью. Массив и указатель: различияВ нижеследующем тексте мы обсудим различия в использовании описаний этих двух видов:static char heart[ ] = "Я люблю язык Cи!"; char *head = "Я люблю язык Pascal!"; Основное отличие состоит в том, что указатель heart является константой, в то время как указатель head - переменной. Посмотрим, что на самом деле дает эта разница. Во-первых, и в том и в другом случае можно использовать операцию сложения с указателем: for(i=0;i<7;i++) putchar(* (heart+i)); putchar('\n'); for(i=0;i<7;i++) putchar(* (head+i)); putchar('\n'); В результате получаем Я люблю Я люблю Но операцию увеличения можно использовать только с указателем: while ((*head) != '\0') /* останов в конце строки */ putchar(*(head++)); /* печать символа и перемещение указателя */ В результате получаем: Я люблю язык Pascal! Предположим, мы хотим изменить head на heart. Можно так: head=heart; /* теперь head указывает на массив heart */ но теперь можно и так heart = head; /* запрещенная конструкция */ Ситуация аналогична x = 5 или 5 = x. Левая часть оператора присваивания должна быть именем переменной. В данном случае head = heart, не уничтожит строку про язык Cи, а только изменит адрес , записанный в head. Вот каким путем можно изменить обращение к head и проникнуть в сам массив: heart[13] = 'C'; или *(heart+8)='C'; Переменными являются элементы массива, но не имя! Массивы символьных строк и их инициализацияПри определении массива символьных строк необходимо сообщить компилятору требуемый размер памяти. Один из способов сделать это - инициализировать массив при помощи строковой константы. Например, операторchar m1[ ]="Только ограничьтесь одной строкой."; инициализировал внешний по умолчанию массив m1 для указанной строки. Этот вид инициализации является краткой формой стандартной инициализации массива char m1[ ]={ 'T','o','л','ь','k','o',' ','o','г'','p','a','н','и','ч','ь','т','e','c','ь',' ',o','д','н','o','й',' ','c','т','p','o','k','o','й','.','\0' } Без символа 0 мы имеем массив символов, а не строку. Для той и другой формы компилятор подсчитывает символы и таким образом получает размер памяти. Как и для других массивов, имя m1 является указателем на первый элемент массива: m1 == &m1[0], *m1 == 'T', и *(m1+1) == m1[1] == 'o' и т.д. Действительно, мы можем использовать указатель для создания строки. Например: char *m3="\n Символьная строка."; Это почти то же самое, что и static char m3[ ]="\n Символьная строка."; Оба описания говорят об одном: m3 является указателем строки со словами "Символьная строка". В том и другом случае сама строка определяет размер памяти, необходимой для ее размещения. Однако вид их не идентичен. on_load_lecture() ![]() ![]() Дальше " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | 1 | 2 | 3 | 4 | 5 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Обработка строкДля выполнения описанных в этом подразделе функций необходимо включить в программу файл string.h командой#include strcat - сцепить две строки. Определение: char *strcat(s1,s2) char *s1, *s2; Пример 1: /* сцепить две строки */ /* в головном файле conio.h содержится функция очистки экрана clrscr( ) */ #include strncat - сцепить две строки, причем из второй строки копировать не более n символов. Определение: char *strncat(s1,s2,n) char *s1, *s2; int n; Пример 2: /* cцепить две строки, причем из второй строки копировать не более n символов */ #include strcmp - сравнить две строки в лексикографическом порядке. Определение: int strcmp(s1,s2) char *s1, *s2; Пример 3: #include Строковые константыСтроковая константа представляется последовательностью символов кода ASCII, заключённой в кавычки: "...". Она имеет тип char[].Примеры: "This is character string" "Это строковая константа" "A" "1234567890" "0" "$" В конце каждой строки компилятор помещает нулевой символ '\0', отмечающий конец данной строки. Каждая строковая константа, даже если она идентична другой строковой константе, сохраняется в отдельном месте памяти. Если необходимо ввести в строку символ кавычек ("), то перед ним надо поставить символ обратной косой (\). В строку могут быть введены любые специальные символьные константы, перед которыми стоит символ \. Символ \ и следующий за ним символ новой строки игнорируется. Строковые константы размещаются в статической памяти. Вся фраза в кавычках является указателем на место в памяти, где записана строка. Это аналогично использованию имени массива, служащего указателем на расположение массива. Если это действительно так, то как выглядит оператор, который выводит строку? /* Строки в качестве указателей */ main( ) { printf("%s, %u, %c\n", "We", "love", *"Pascal"); } Итак, формат %s выводит строку We. Формат %u выводит целое без знака. Если слово "love" является указателем, то выдается его значение, являющееся адресом первого символа строки. Наконец, *"Pascal" должно выдать значение, на которое ссылается адрес, т.е. первый символ строки "Pascal". Вот что выдаст наша программа: We, 34, P Указатели и строкиБольшинство операций языка Си, имеющих дело со строками, работают с указателями. Рассмотрим, например, приведенную ниже бесполезную, но поучительную программу:/* Указатели и строки */ #define PX(X) printf("X = %s; значение = %u; &X = %u\n",X,X,&X) main( ) { static char *mesg = "Сообщение"; static char *copy; copy = mesg; printf("%s\n",copy); PX(mesg); PX(copy); } Мы можем подумать, что эта программа копирует строку "Сообщение", и при беглом взгляде на вывод может показаться правильным это предположение: Сообщение mesg = Сообщение; значение = 14; &mesg = 32 copy = Сообщение; значение = 14; © = 34 Но изучим вывод PX( ). Сначала X, который последовательно является mesg и copy, печатается как строка (%s). Здесь нет сюрприза. Все строки содержат "Сообщение". Третьим элементом в каждой строке является &X, т. е. адрес X. Указатели mesg и copy записаны в ячейках 32 и 34, соответственно. Теперь о втором элементе, который мы называем значением. Это сам X . Значением указателя является адрес, который он содержит. Мы видим, что mesg ссылается на ячейку 14, и поэтому выполняется copy. Смысл заключается в том, что сама строка никогда не копируется. Оператор copy = mesg; создает второй указатель, ссылающийся на ту же самую строку. Зачем все эти предосторожности? Почему бы не скопировать всю строку? Хорошо, а что эффективнее - копировать один адрес или, скажем, 70 отдельных элементов? Часто бывает, что адрес - это все, что необходимо для выполнения работы. on_load_lecture() ![]() ![]() Дальше " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | 4 | 5 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Ввод-вывод строкfgets - прочитать строку из входного потока, включая символ новой строки.Определение: char *fgets (s, n, stream) char *s; int n; FILE *stream; gets - прочитать строку из стандартного файла ввода stdin. Определение: char *gets (s) char *s; fputs - записать строку в поток stream. Определение: int fputs (s, stream) char *s; FILE *stream; puts - записать строку в стандартный файл вывода stdout. В конце строк записывается символ новой строки. Определение: int puts (s) char *s; Доступ к компонентам структурыТакой доступ осуществляется с помощью специального обозначения для выделенного компонента, имеющего следующий вид:s.c где s является именем структуры или значением структуры с компонентом c. s может быть выражением, дающим в результате значение структуры. Например, s может быть вызовом функции со структурой в качестве ее значения. К компонентам определенной выше структуры date1 можно обратиться, указав их обозначения: date1.year date1.month date1.day Массив структурПроцесс описания массива структур совершенно аналогичен описанию любого другого типа массива:struct book libry[MAXBKS]; Этот оператор объявляет libry массивом, состоящим из MAXBKS-элементов. Каждый элемент массива представляет собой структуру типа book. Таким образом, libry[0] является book-структурой, libry[1] - второй book-структурой и т.д. Определение элементов массива структур. При определении элементов массива структур мы применяем те же самые правила, которые используются для отдельных структур: сопровождаем имя структуры операцией получения элемента и имени элемента: libry[0].value value - первый элемент массива libry[4].title title - пятый элемент массива on_load_lecture() ![]() ![]() Дальше " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | 4 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
ОбъединенияОбъединение описывает переменную, которая может иметь любой тип из некоторого множества типов.Определение объединенного типа данных аналогично определению структурного типа данных: union имя_объединения { Описания_элементов }; Пример: union bigword { long bg_long; char *bg_char [4]; }; Данные типа union bigword занимают память, необходимую для размещения наибольшего из своих элементов, и выравниваются в памяти к границе, удовлетворяющей ограничениям по адресации как для типа long, так и для типа char *[4]. Описание переменной объединенного типа: Пример: union bigword x; union bigword *p; union bigword a[100]; Определение структурных переменныхСтруктура объединяет логически связанные данные разных типов. Структурный тип данных определяется следующим описанием:struct имя_структуры { Описание_элементов }; Пример: struct dinner { char *plase; float cost; struct dinner *next; }; Структурная переменная описывается с помощью переменной структурного типа. Примеры: struct dinner week_days [7]; /* массив структур */ struct dinner best_one; /* одна структурная переменная */ struct dinner *p; /* указатель на структурную переменную */ Структура, запись в терминологии языка Паскаль и Ада, - это составной объект, в который входят компоненты любых типов, за исключением функций. В отличие от массива, который является однородным объектом, структура может быть неоднородной. Тип структуры указывается записью вида struct{ список описаний } В структуре должен быть указан хотя бы один компонент. Указатель типа структуры используется для определения структур. Определения структур имеют следующий вид: тип-данных описатели; где тип-данных указывает тип структуры для объектов, определяемых в описателях. В своей простейшей форме описатели представляют собой обычные имена переменных, массивов, указателей и функций. Например, с помощью определения struct { double x,y; } a,b,c[9]; переменные a и b определяются как структуры, каждая из которых состоит из двух компонентов - x и y. Переменная с определяется как массив из девяти таких структур. Из определения struct { int year; short int month, day; } date1,date2; следует, что каждая из двух переменных date1, date2 состоит из трех компонентов: year, month, day. С типом структуры может быть ассоциировано имя, которое задается описанием типа в форме typedef struct { список описаний } имя-типа-структуры; Спецификатор typedef (определяет класс памяти) позволяет нам создать свое собственное имя типа. Это напоминает директиву #define, но со следующими тремя изменениями: В дальнейшем эти имена могут использоваться для определения структур. Ниже приведен пример описания типа структуры с именем employee: typedef struct { char name[30]; int id; dept d; family f; } employee; где слова dept, family указывают типы, а именно типы структур, предварительно определенные пользователем. Тип структуры employee может быть использован для определения переменных. Например, определение employee chairperson, president, e1, e2; описывает переменные chairperson, president, e1, e2 как структуры типа employee. Существует и другой способ ассоциирования имени с типом структуры. Этот способ основан на применении меток структуры. Метки структуры аналогичны меткам перечисляемого типа. Метка структуры описывается следующим образом: struct метка{ список описаний } где метка является идентификатором. В приведенном ниже примере слово student описывается как метка структуры: struct student { char name[25]; int id,age; char sex; }; Метки структуры используются для определения структур записью вида struct метка список-идентификаторов; Использование меток структуры необходимо для описания рекурсивных структур, так как одного только оператора typedef недостаточно. В приведенном ниже примере описания рекурсивной метки структуры struct node { int data; struct node *next; }; ПеречисленияДанные перечислимого типа относятся к некоторому ограниченному множеству данных.Определение перечислимого типа данных: enum имя_перечислимого_типа { Список_значений }; Каждое значение данного перечислимого типа задается идентификатором. Пример: enum color { red, green, yellow }; Описание переменной перечислимого типа: enum color chair; enum color suite [40]; Использование переменной перечислимого типа в выражении. Пример: chair = red; suite[5] != yellow; on_load_lecture() ![]() ![]() Дальше " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | 4 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Переименование типовФорматtypedef старый_тип новый_тип Примеры: typedef long large; /* определяется тип large, эквивалентный типу long */ typedef char *string; /* тип string, эквивалентен типу char* */ Переименование типов используется для введения осмысленных или сокращенных имен типов, что повышает понятность программ, и для улучшения переносимости программ (имена одного типа данных могут различаться на разных ЭВМ). Пример: /* Реализован алгоритм, который позволяет определить строки матриц, состоящие из одинаковых целых, расположенных в различных столбцах. Используются двумерные массивы и структуры. Сначала выполняется сортировка строк по возрастанию. Отсортированные строки сравниваются и выводятся на экран номера одинаковых строк */ #include Переменные структурыВ языках, таких как Ада и Паскаль, имеется тип данных, называемых переменная запись, объекты которой содержат набор одних и тех же компонентов плюс компоненты, не являющиеся общими для всех остальных объектов. В языке Си также имеется тип данных, подобный переменной записи, называемой переменной структурой, которая может быть реализована с использованием комбинации структуры и объединения. В общем случае переменные структуры будут состоять из трех частей: набора общих компонентов, метки активного компонента и части с меняющимися компонентами. Общая форма переменной структуры имеет следующий вид:struct { общие компоненты; метка активного компонента; union { описание компонента 1 описание компонента 2 ... описание компонента n } идентификатор; } Ниже приведен пример определения переменной структуры health_record: struct { /* общая информация */ char name[25]; int age; char sex; /* метка активного компонента */ marital_status ms; /* переменная часть */ union { /* холост */ /* нет компонентов */ /* женат */ struct { char marriage_date[8]; char spouse_name[25]; int no_children; } /* разведен */ char date_divorced[8]; } marital_info; } health_record; где тип marital_status, т.е. тип метки активного компонента ms, описан как typedef enum {SINGL,MARRIED, DIVORCED} marital_status; Ниже приведены несколько примеров ссылки на компоненты переменной структуры: health_record.name healts_record.ms health_record.marital_info.marriage_date Поля битов в структурахПоле битов - это элемент структуры, определенный как некоторое число бит, обычно меньшее, чем число бит в целом числе. Поля битов предназначены для экономного размещения в памяти данных небольшого диапазона.Пример: struct bfeg { unsigned int bf_flg1 : 10; unsigned int bf_flg2 : 6; }; Данная структура описывает 10-битовое поле, которое для вычислений преобразуется в значение типа unsigned int, и 6-битовое поле, которое обрабатывается как значение типа unsigned int. Указатели и структурыРассмотрим метку структуры student, описание которой было дано выше какstruct student { char name[25]; int id, age; char sex; } Указатель new_student определен как struct student *new_student; Предположим, что память выделена таким образом, чтобы new_student указывал на объект student. Тогда на компоненты этого объекта можно ссылаться следующим образом: (*new_student).name (*new_student).id (*new_student).age (*new_student).sex Поскольку указатели часто используются для указания на структуры, в языке Си специально для ссылок на компоненты таких структур введен оператор выбора стрелка вправо ->. Например, ссылки на вышеприведенные компоненты структуры можно записать с использованием оператора стрелки вправо -> как: new_student->name new_student->id new_student->age new_student->sex Автоматический доступВо многих больших системах UNIX вы только компилируете программы. А доступ к более общим библиотечным функциям выполняется автоматически.Доступ в библиотеку языка СиПолучение доступа к библиотеке зависит от системы. Во-первых, есть несколько различных мест расположения библиотечных функций, Например, getchar( ) обычно задают как макроопределение в файле stdio.h, в то время как strlen( ) обычно хранится в библиотечном файле. Во-вторых, различные системы имеют разные способы доступа к этим функциям. Вот три из них.Функции fprintf( ) и fscanf( )Эти функции ввода-вывода работают почти как printf( ) и scanf( ) (см. лекцию 4), но им нужен дополнительный аргумент для ссылки на сам файл. Он является первым в списке аргументов. Пример, иллюстрирующий обращение к этим функциям:#include В отличие от getc( ) и putc( ) эти функции получают указатель типа FILE в качестве первого аргумента. Функция calloc( )Другую возможность распределения памяти дает нам применение функции calloc( ).char *calloc( ); long *newmem; newmem=(long *) calloc(100,sizeof(long)); Функция calloc( ) возвращает указатель на char. Нужно использовать оператор приведения типа, если вы хотите запомнить другой тип. calloc( ) имеет два аргумента, и оба они должны быть целыми без знака. Первый аргумент содержит количество требуемых ячеек памяти. Второй аргумент - размер каждой ячейки в байтах. Функция calloc( ) обнуляет содержимое всего блока. Ваша библиотека языка Си возможно представляет несколько других функций управления памятью, вы можете исследовать их самостоятельно! on_load_lecture() ![]() ![]() Перейти к вопросам " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | 4 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Функция fgets( )Эта функция имеет три аргумента, в то время как gets( ) имеет лишь один. Пример ее использования:/* Программа считывает файл строка за строкой */ #include Мы расположили вводимую информацию в символьном массиве string. Первый из трех аргументов функции fgets( ) является указателем на местоположение считываемой строки. Второй аргумент содержит предельную длину считываемой строки. Функция прекращает работу после считывания символа новой строки или после считывания символов общим числом MAX-1, в зависимости от того, что произойдет раньше. В любом случае нуль-символ '\0' добавляется в самый конец строки. Третий аргумент указывает на файл, который будет читаться. Разница между gets( ) и fgets( ) заключается в том, что gets( ) заменяет символ новой строки на '\0', в то время как fgets( ) сохраняет символ новой строки. Подобно gets( ) функция fgets( ) возвращает значение NULL, если встречает символ EOF . Это позволяет нам проверить, достигли ли мы конца файла. Функция fputs( )Эта функция похожа на функцию puts( ). Операторl=fputs("Строка", fi);
Передает строку "Строка" в файл, на который ссылается указатель fi типа FILE. Конечно, сначала нужно открыть файл при помощи функции fopen( ). l является целым числом, которое устанавливается в EOF, если fputs( ) встречает EOF или ошибку. Эта функция не ставит завершающий символ '\0' в конце копируемой строки. В отличии puts функция fputs( ) не добавляет символ новой строки в ее вывод. on_load_lecture() ![]() ![]() Дальше " | ![]() Если Вы заметили ошибку - сообщите нам. | ![]() Страницы: | " | 1 | 2 | 3 | 4 | вопросы | "
| | | учебники | для печати и PDA
| ![]() ![]() ![]()
Функция fseek( )Функция fseek( ) позволяет нам обрабатывать файл подобно массиву и непосредственно достигать любого определенного байта в файле, открытом функцией fopen( ). fseek( ) имеет три аргумента и возвращает значение типа int.Покажем на примере работу fseek( ): /* использование fseek( ) для печати содержимого файла */ #include Первый из трех аргументов функции fseek( ) является указателем типа FILE на файл, в котором ведется поиск. Файл следует открыть, используя функцию fopen( ). Второй аргумент "set" . Этот аргумент сообщает, как далеко следует передвинуться от начальной точки (см. ниже). Он должен иметь значение типа long, которое может быть положительным (движение вперед) или отрицательным (движение назад). Третий аргумент является кодом, определяющим начальную точку. Функция fseek( ) возвращает 0, если все хорошо, и -1, если есть ошибка. Поскольку переменная set инициализирована нулем, при первом прохождении через цикл while(fseek(fp,set++,0)==0) putchar(getc(fp)); мы имеем выражение fseek(fp,OL,0); означающее, что мы идем в файл, на который ссылается указатель fp, и находим байт, отстоящий на 0 байт от начала, т.е. первый байт. Затем функция putchar( ) печатает содержимое этого байта. При следующем прохождении через цикл переменная set увеличивается до 1L, и печатается следующий байт. То есть, переменная set действует подобно индексу для элементов файла. Процесс продолжается до тех пор, пока set не попытается попасть в fseek( ) после конца файла. В этом случае fseek( ) возвращает значение -1 и цикл прекращается. Функция malloc( )Пусть нам нужно распределить память для запоминания используемых данных. Некоторые ячейки памяти распределяются автоматически, Например, мы можем объявитьchar str[ ] = "Символьная строка"; Будет выделена память, достаточная для запоминания этой строки. Мы можем запросить определенный объем памяти: int mas[150]; Это описание выделяет 150 ячеек памяти, каждая из которых предназначена для запоминания целого значения. Но язык Си позволяет нам распределять дополнительную память во время работы программы. Предположим, мы пишем программу и не знаем, сколько данных нам придется вводить. Тогда можно выделить нужный нам, по нашему предположению, объем памяти, а затем, если понадобится, потребовать еще. Чтобы сделать это, нужно использовать функцию malloc( ). И без указателей тут не обойтись! /* добавляем память, если необходимо */ #include |