Основы программирования на языке C
Описание различных типов, переменные и константы
Целые числа. У целого числа никогда не бывает дробной части. Представив целое число в двоичном виде, его нетрудно разместить в машине. Например, число 3 в двоичном виде выглядит как 11. Если его поместить в слово 32-разрядной машины, необходимо первые 30 бит установить в 0, а последние 2 бита - в 1.Числа с плавающей точкой. Числа с плавающей точкой соответствуют тому, что математики называют вещественными числами. Способ кодирования, используемый для помещения в память числа с плавающей точкой, полностью отличается от размещения целого числа. Числа с плавающей точкой представляют в виде дробной части и порядка числа, а затем обе части размещают в памяти.
Все данные типов int, short, long являются числами со знаками, т.е. значениями этих типов могут быть только целые числа - положительные, отрицательные и нуль. Один бит используется для указания знака числа, поэтому максимальное число со знаком, которое можно представить в слове, меньше, чем максимальное число без знака.
Описание данных целого типа. При описании данных необходимо написать только тип, за которым должен следовать список имен переменных. Например, int dog, rad, nina. В качестве разделителя между именами переменных необходимо использовать запятую.
Целые константы. Согласно правилам языка Си, число без десятичной точки и без показателя степени рассматривается как целое. Поэтому компилятор по записи константы определяет, целая она или вещественная. Если нужно ввести константу типа long, то нужно указать признак L или l в конце числа. Если при записи константы целое начинается с цифры 0, то эта константа интерпретируется как восьмеричное число, если же целое начинается с символа 0x или 0X - как шестнадцатеричное число.
Инициализация переменных целого типа. Константы применяются при инициализации переменных. Это означает присваивание переменной некоторого значения перед началом обработки. Можно инициализировать переменную в операторе описания.
Например,
int dog=5; int rad=077; int nina =0X99;
| ! | В языке Си введено три класса целых чисел, имеющих различные размеры. Тем самым пользователю языка Си предоставили возможность выбора типа переменной с требованием задачи. Например, если переменная типа int занимает одно слово, а переменная типа long занимает два слова, значит тип long позволяет обрабатывать большие числа. Если в задаче не используются большие числа, то незачем вводить в программу переменные типа long, т.к. если вместо числа, занимающего одно слово в памяти, используется число, занимающее два слова, работа машины замедляется. |
Описание данных типа unsigned. Этот тип является модификатором типов: int, short, long. Мы можем использовать комбинацию ключевых слов unsigned int, unsigned short, unsigned long, т.е. переменная не может принимать отрицательного значения. Для указания типа unsigned int достаточно написать unsigned. Целые беззнаковые константы записываются так же, как и обычные константы, запрещено только использование знака минус.
Например, unsigned age;
Описание данных типа char. Этот тип определяет целые числа без знака в диапазоне от 0 до 255. Такое целое обычно размещается в одном байте памяти. Для описания символьной переменной применяется ключевое слово char. Правила описания более чем одной переменной и инициализации переменных остаются теми же, что и для других основных типов.
Например, char dog, cat;
Символьные константы. Символы в языке Си заключаются в апострофы.
Например, char dog; dog='b';
Если апострофы опущены, то компилятор считает, что используется неописанная переменная b. В стандартном языке Си значением переменной или константы типа char могут быть только одиночные символы.
Примеры символьных констант: 'A', 'a', '7', '$'.
Специальные (управляющие) символьные константы.
| Новая строка (перевод строки) | '\n' |
| Горизонтальная табуляция | '\t' |
| Вертикальная табуляция | '\v' |
| Возврат на шаг | '\b' |
| Возврат каретки | '\r' |
| Перевод формата | '\f' |
| Обратная косая | '\\' |
| Апостроф | '\'' |
| Кавычки | '\"' |
| Нулевой символ (пусто) | '\0' |
Кроме того, любой символ может быть представлен последовательностью трех восьмеричных цифр: '\ddd'. Символьные константы считаются данными типа int.
Основные типы данных
Чтобы реализовать алгоритм, программам необходимо работать с данными - числами, символами, т.е. объектами, которые несут в себе информацию, предназначенную для использования. Некоторые данные устанавливаются равными определенным значениям еще до того, как программа начинает выполняться, а после ее запуска такие значения сохраняются неизменными на всем протяжении работы программ. Эти данные называются константами. Данные, которые могут изменяться, или же им могут быть присвоены значения во время выполнения программы, называются переменными. Различие между переменной и константой очевидно: во время выполнения программы значение переменной может быть изменено (например, с помощью присваивания), а значение константы изменить нельзя.Кроме различия между переменными и константами существует еще различие между типами данных. Некоторые данные в программе являются числами, некоторые - символами. Компилятор должен уметь идентифицировать и обрабатывать данные любого типа. В языке Си предусмотрено использование нескольких основных типов данных. Если величина есть константа, то компилятор может распознать ее тип только по тому виду, в котором она присутствует в программе. В случае переменной необходимо, чтобы ее тип был объявлен в операторе описания. В стандарте языка Си используется семь ключевых слов, указывающих на различные типы данных:
int long short unsigned char float double
Первые четыре ключевых слова используются для представления целых, т.е. целых чисел без десятичной, дробной части. Если мы хотим подчеркнуть, что целое не может быть отрицательным, то нужно к целому подписывать ключевое слово unsigned, например, unsigned short. char предназначено для указания на буквы и другие символы. float, double используются для представления чисел с десятичной точкой. Типы, обозначаемые этими ключевыми словами, можно разделить на два класса по принципу размещения в памяти машины. Первые пять ключевых слов определяют целые типы данных, последние два - типы данных с плавающей точкой. Дадим краткое объяснение их смысла. Термины бит, байт, слово используются для описания как элементов данных, которые обрабатывает компьютер, так и элементов памяти. Рассмотрим эти понятия относительно памяти. Наименьшая единица памяти называется бит. Она может принимать одно из двух значений: 0 или 1. Байт в большинстве машин состоит из 8 бит. Всего в байтовом формате можно представить 256 (два в восьмой степени) различных комбинаций из нулей и единиц. Эти комбинации можно использовать для представления целых чисел в диапазоне от 0 до 255 или для кодирования набора символов. Слово является естественным элементом памяти. Есть ЭВМ, у которых слово равно 8 битам, 16 битам, 32 битам или 64 битам.
Препроцессор языка Си
Вернемся к константам. Чтобы ввести ту или иную константу в программу, нужно указать ее фактическое значение, как было сказано выше. Можно использовать вместо этого "символические константы" и позволить компилятору заменить символические константы числами. Как можно создать такую константу? Можно это сделать так:float cost = 0.0012;
Такой способ задания констант в больших программах неэкономичен. В Си имеется другой, лучший способ. Этот способ реализуется с помощью препроцессора языка Си. Препроцессор дает возможность задавать константы. Для этого в начало программы нужно добавить строку, аналогичную следующей:
#define COST 0.0012
При компиляции программы каждый раз, когда появится переменная COST, она будет заменяться величиной 0.0012. Такой процесс называется подстановкой во время компиляции. Замечание по поводу формата: сначала идет ключевое слово #define (оно должно начинаться с самой левой позиции), потом идет символическое имя константы, а затем ее величина. Символ "точка с запятой" не используется, потому что это не оператор языка Си.
Если в качестве первого символа в строке программы используется символ #, то эта строка является командной строкой препроцессора (макропроцессора). Командная строка препроцессора заканчивается символом перевода на новую строку. Если непосредственно перед концом строки поставить символ обратной косой (\), то командная строка будет продолжаться на следующей строке программы.
Препроцессор используется для обработки текста программы до этапа ее компиляции. Обычно препроцессоры служили средством расширения языков с целью обеспечения дополнительных возможностей. Несмотря на бесчисленное множество препроцессоров, созданных для расширения возможностей языков программирования, все они были нестандартными. Для некоторых языков, например для языков общего назначения ПЛ/1 и Си, препроцессоры поставлялись как часть их стандартной среды. Препроцессор для языка Си обеспечивает средства для определения макросов, определения констант, включения файлов и условную компиляцию. Препроцессор языка Си вызывается автоматически при обращении к компилятору. Программа может быть обработана только препроцессором без компиляции, если в команде сс указать ключ -Е:
cc -E имя_файла
Результат работы препроцессора помещается в поток стандартного вывода stdout. Обработка программы препроцессором без компиляции позволяет программисту проанализировать действие определений препроцессора и макровызовов.
| ! | Символическую константу после #define лучше писать прописными буквами. В процессе использования языка Си выработалась традиция писать константы большими буквами. Если при просмотре программы встречается имя, написанное прописными буквами, сразу становится ясно, что это константа, а не переменная. Давайте не нарушать традицию! |
Например,
#define NULL '\0' #define USA '$' #define RUSSIA "Рубль" #undef USA
Команда #undef USA отменяет предыдущее определение для идентификатора USA. Через команду #define можно задавать выражения - макросы, которые вычисляются и при компиляции подставляются в программу. Во избежании ошибок при вычислении выражений макроопределения необходимо заключать в скобки:
#define идентификатор1 (идентификатор2,_) строка
Пример:
#define abs(A) (((A)>0) ? (A): - (A))
Каждое вхождение выражения abs(arg) в тексте программы заменяется на ((arg)>0) ? (arg): -(arg)), причем параметр макроопределения A заменяется на arg.
Пpимер:
#define nmem(P,N)\ (P)->p_mem[N].u_long
Символ \ продолжает макроопределение на вторую строку. Это макроопределение уменьшает сложность выражения, описывающего массив объединений внутри структуры.
Макроопределения препроцессора языка Си имеют две формы - простую и параметризованную,
#define идентификатор строка_замены #define идентификатор(x1,x2,_,xn) строка_замены
где строка_замены может содержать идентификаторы, ключевые слова, разделители, такие как круглая или прямоугольная скобка, или строки знаков, не содержащие каких-либо разделителей.
Замечание. Командная строка #include может встречаться в любом месте программы, но обычно все включения размещаются в начале исходного текста:
#include <имя_файла>
Пример:
#include
Препроцессор заменяет эту строку содержимым файла math.h. Угловые скобки обозначают, что файл math.h будет взят из некоторого стандартного каталога, обычно это /usr/include. Текущий каталог не просматривается:
#include "имя_файла"
Пример:
#include "ABC"
Препроцессор заменяет эту строку содержимым файла ABC. Так как имя файла заключено в кавычки, то поиск производится в текущем каталоге, в котором содержится основной файл исходного текста. Если в текущем каталоге данного файла нет, то поиск производится в каталогах, определенных именем пути в опции -i препроцессора. Если и там файла нет, то просматривается стандартный каталог.
Командные строки препроцессора используются для условной компиляции различных частей исходного текста в зависимости от внешних условий. Условной компиляцией называется выборочная компиляция только тех частей программы, которые удовлетворяют определенным условиям. Например, можно скомпилировать только те части программы, которые необходимы для конкретной версии системы.
on_load_lecture()




" |
1
|
2
|
3
|
4
|
вопросы | "
учебники
|
для печати и PDA



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


![]() | © 2003-2007 INTUIT.ru. Все права защищены. |
Символьные строки
Символьная строка - это последовательность символов, возможно пустая (""). Рассмотрим пример:"Большой спрос на образование в области информационных дисциплин объясняется не только популярностью компьютеров в современном обществе, но и реальной пользой от их применения."
Кавычки не являются частью строки. Они вводятся только для того, чтобы отметить ее начало и конец. В языке Си нет специального типа, который можно было бы использовать для описания строк. Вместо этого строки представляются в виде "набора" элементов типа char. Это означает, что символы в строке можно представить расположенными в соседних ячейках памяти - по одному символу в ячейке. Символ \0 в языке Си используется для того, чтобы отмечать конец строки. Нуль-символ не выводится на печать и в таблице кода ASCII (American Standard Code for Information Interchange) имеет номер 0. Наличие нуль-символа означает, что количество ячеек массива символов должно быть на одну больше, чем число символов строки. Массив можно представить как совокупность нескольких ячеек памяти, объединенных в одну строку. Массив - это упорядоченная последовательность элементов данных одного типа. В нашем примере мы создали массив из 177 ячеек памяти, в каждую из которых можно поместить один символ типа char. Это можно сделать с помощью оператора описания:
char String[177];
Квадратные скобки указывают, что переменная String - массив из 177 элементов, а char задает тип каждого элемента. Длину строки в символах (без завершающего символа) определяет функция strlen( ). Обращение к ней в нашем примере выглядит так:
strlen(String);
Результат - целое число.
on_load_lecture()




" |
1
|
2
|
3
|
4
|
вопросы | "
учебники
|
для печати и PDA



| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
Дополнительные операции
В языке Си имеется около сорока операций. Те операции, которые мы рассмотрели, являются наиболее общеупотребительными. Рассмотрим еще три операции, наиболее используемые программистами.В качестве операций языка Си
Операции вызова функции, индексирования и выбора. В качестве операций языка Си рассматриваются также скобки в вызове функции, квадратные скобки для индексирования массивов, точка и стрелка вправо для выбора компонентов структуры или объединения. Уровень этих операторов равен 1, все операции выполняются слева направо.Для унарных операций требуется только
Унарные операции. Для унарных операций требуется только один операнд; эти операции либо префиксные, либо префиксные и постфиксные. Операция sizeof имеет два варианта: префиксная операция и унарная операция.Порядок выполнения мультипликативных операций
Мультипликативные операции. Порядок выполнения мультипликативных операций - слева направо.Уровень приоритета аддитивных операций равен
Уровень приоритета аддитивных операций равен 4. Выполняются эти операции слева направо.Уровень приоритета операций сдвига равен
Уровень приоритета операций сдвига равен 5. Порядок выполнения операций - слева направо.неравенства равен 7, выполняются они
Уровень приоритетов операций равенства/ неравенства равен 7, выполняются они слева направо.Уровень приоритета операций отношения равен
Уровень приоритета операций отношения равен 6. Выполняются эти операции слева направо.Операция больше или равно: >=
Тип операндов - арифметический или указатель. Тип результата - int.Использование:
ae1>=ae
Истина, если ae1 больше или равно ae2.
Операция больше: >
Тип операндов - арифметический или указатель. Тип результата - int.Использование:
ae1>ae2
Истина, если ae1 больше, чем ae2.
Пример:
if(x>0) printf("positive");
Операция деления по модулю: %
Эта операция используется в целочисленной арифметике. Ее результатом является остаток от деления целого числа, стоящего слева от знака операции, на число, расположенное справа от нее. Например, 63%5, читается как 63 по модулю 5, имеет значение 3, т.к. 12*5+3.В результате выполнения оператора
minutes=time%60;
переменной minutes будет присвоено значение остатка от деления time на 60.
Операция деления: /
В языке Си символ / указывает на операцию деления. Величина, стоящая слева от этого знака, делится на величину, расположенную справа от этого знака. Например, в результате выполнения оператораl = 126.0 / 2.0;
переменной l будет присвоено значение 63.0. Над данными целого типа операция деления производится не так, как над данными с плавающей точкой: в первом случае результат будет целым числом, а во втором - числом с плавающей точкой. В языке Си принято правило, согласно которому дробная часть у результата деления целых чисел отбрасывается. Это действие называется "усечением".
Рассмотрим пример, как выполняется усечение и чем деление целых чисел отличается от деления чисел с плавающей точкой:
main( ) { printf("деление целых: 5/4 это %d \n", 5/4); printf("деление целых: 6/3 это %d \n", 6/3); printf("деление целых: 7/4 это %d \n", 7/4); printf("деление чисел с плавающей точкой: 7./4. это %2.2f \n", 7./4.); printf("смешанное деление: 7./4 это %2.2f \n", 7./4); }
Последний пример на использование смешанных типов, т.е. деление вещественного числа на целое. Язык Си менее строго подходит к подобным вопросам, и позволяет выполнять такие операции.
| ! | Смешения типов следует избегать! |
Результат выполнения указанной программы:
деление целых: 5/4 это 1 деление целых: 6/3 это 2 деление целых: 7/4 это 1 деление чисел с плавающей точкой: 7./4 это 1.75 смешанное деление: 7./4 это 1.75
Результат деления целых чисел округляется не до ближайшего целого, а всегда до меньшего целого числа. Когда мы смешиваем целые числа и числа с плавающей точкой, результат будет таким же, как если бы оба операнда были числами с плавающей точкой. В этом случае перед делением целое преобразуется в число с плавающей точкой. Для того чтобы понять, что происходит в тех случаях, когда в одном операторе используется несколько операций, рассмотрим порядок выполнения операций. Совершенно очевидно, что изменение порядка выполнения действий может приводить к различным результатам. В языке Си каждой операции назначается уровень старшинства. Умножение и деление имеют более высокий уровень, чем сложение и вычитание, поэтому они выполняются первыми. Если же две операции имеют один и тот же уровень старшинства, они выполняются в том порядке, в котором присутствуют в операторе. Для большинства операций обычный порядок - слева направо. Операция = является исключением из этого правила. Многие программисты предпочитают представлять порядок вычислений с помощью дерева выражений. Например, выражение
(a+b)*c
будет выглядеть так:
* / \ + c a b
Мы можем составить таблицу правил описанных нами операций. В дальнейшем мы приведем таблицу, где содержатся правила, относящиеся ко всем операциям языка Си.
| ( ) | слева направо |
| - (унарный) | слева направо |
| * / | слева направо |
| + - (вычитание) | слева направо |
| = | справа налево |
Операция дополнения до 1: ~
Тип операнда - интегральный. Тип результата: int, long, unsigned.Пример:
opposite=~mask;
Дополнение до единицы значения mask. Результат присваивается переменной opposite.
Операция индексирования массива: []
Синтаксис:array [2]
Значением выражения является третий элемент массива.
Присвоение значения 26 одиннадцатому элементу массива записывается таким образом:
array[10]=26;
Первый элемент массива описывается выражением array[0] (более подробно о массивах описано в лекции 12).
Операция изменения знака: -
Знак минус используется для указания или изменения алгебраического знака некоторой величины. Например, в результате выполнения последовательности операторовteg = -15; get = -teg;
переменной get будет присвоено значение 15. Когда знак используется подобным образом, данная операция называется "унарной". Такое название указывает на то, что она имеет дело только с одним операндом.
Пример:
x = -x;
Операция изменяет алгебраический знак x.
Операция косвенной ссылки: *
Это указатель на любой тип T, кроме void. Тип результата T.Использование:
*pe
Значением выражения является переменная, адресуемая указателем pe.
Пример 1:
*ptr=c;
Пример 2:
*fpe;
Значением выражения является функция, адресуемая указателем fpe.
Пример 3:
fpe=*funcname; (*fpe)(arg1, arg2);
Операция логическое И: &&
Тип операндов - арифметический или указатель. Тип результата - int. Если первый операнд равен 0, то результат равен 0. В противном случае результат будет равен 1, если второй операнд не равен 0, и равен 0, если второй операнд равен 0 (если первый операнд равен 0, то второй операнд не вычисляется).Использование:
e1&&e2
Логическая операция И значений e1 и e2. Вначале проверяется значение e1; значение e2 проверяется только в том случае, если значение e1 -Истина. Значением выражения является Истина, если значения e1 и e2 - Истина.
Пример:
if(p!=NULL&&*p>7) n++;
Если p - не нулевой указатель и значение переменной, на которую указывает p, больше, чем 7, то в этом случае n увеличивается на 1. Обратите внимание, что если значение указателя p равно NULL(0), то выражение *p не имеет смысла.
Операция логическое ИЛИ: ||
Тип операндов - арифметический или указатель. Тип результата int. Если хотя бы один операнд не равен 0, результат равен 1; иначе результат будет равен 0.Использование:
e1 || e2
Логическая операция ИЛИ значений e1 и e2. Вначале проверяется значение e1; значение e2 проверяется только в том случае, если значение e1 - ложь. Значением выражения является Истина, если истинно любое значение e1 или e2.
Пример 1:
if(xB) printf("out of range");
on_load_lecture()




" |
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
вопросы | "
учебники
|
для печати и PDA



| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
Операция логического отрицания: !
Тип операнда - арифметический или указатель. Тип результата - int. Если операнд равен 0, то результат равен 1 и наоборот.Пример:
if(!good) printf("not good");
Операция меньше или равно: <=
Тип операндов - арифметический или указатель. Тип результата - int.Использование:
ae1<=ae2
Истина, если ae1 меньше или равно ae2.
Операция меньше: <
Тип операндов - арифметический или указатель. Тип результата - int.Использование:
ae1
Пример:
if(x<0) printf("negative");
Операция неравенство: !=
Тип операндов - арифметический или указатель. Тип результата -int.Единственным целым значением, с которым можно сравнивать указатели, является нулевое значение!
Использование:
ie1!=ie2
Истина, если ie1 не равно ie2.
Пример:
while (i!=0) i=func;
on_load_lecture()




" |
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
вопросы | "
учебники
|
для печати и PDA



| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
Операция определения требуемой памяти в байтах: sizeof
Тип операнда - значение любого типа или имени типа. Тип результата - unsigned. Используется как sizeof (выражение) или sizeof (имя типа).Пример:
n=sizeof(arname)/sizeof(int);
Число элементов в массиве целых чисел, определяемое как число байт в массиве, поделенное на число байт, занимаемых одним элементом массива.
Операция отрицания: -
Тип операнда - арифметический. Тип результата: unsigned, long, double, int.on_load_lecture()




" |
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
вопросы | "
учебники
|
для печати и PDA



| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
Операция получения адреса: &
У этой операции тип операнда есть переменная любого типа, кроме void. Тип результата - указатель на Т.Использование:
&v
Значением выражения является адрес переменной v.
Пример:
myptr=&n;
Операция получения остатка: %
Тип операндов - интегральный. Тип результата int, unsigned, long. Знак остатка машинно-зависим.Использование:
ae1%ae2
Остаток от деления по модулю.
Пример:
j=i%2;
Если i четное число, то j равно нулю.
Операция поразрядное И: &
Тип операндов - интегральный. Тип результата: int, long, unsigned.Использование:
ie1 & ie2
Побитовая операция И двоичных представлений ie1 и ie2. Значение выражения содержит 1 во всех разрядах, в которых и ie1 и ie2 содержат 1, и 0 во всех остальных разрядах.
Пример:
flag=((x&mask)!=0);
Операция поразрядное исключающее или: ^
Тип операндов - интегральный. Тип результата: int, long, unsigned.Использование:
ie1^ie2
Побитовая операция исключающее ИЛИ двоичных представлений ie1 и ie2. Значение выражения содержит 1 в тех разрядах, в которых и ie1 и ie2 имеют разные двоичные значения, и 0 во всех остальных разрядах.
Пример:
diffbits=x^y;
Операция поразрядное включающее или: |
Тип операндов - интегральный. Тип результата: int, long, unsigned.Использование:
ie1 | ie2
Побитовая операция ИЛИ двоичных представлений ie1 и ie2. Значение выражения содержит 1 во всех разрядах, в которых ie1 или ie2 содержат 1, и 0 во всех остальных разрядах.
Пример:
attrsum=attr1 | attr2;
Операция присваивания
v <знак>= eприблизительно эквивалентна оператору присваивания
v = v<знак> e
Пример 1:
y+=2; /* Увеличение переменной y на 2 */ p+=n; x-=3; ptr-=2; timesx*=x; x/=2; x%=10; x>>=4; x<<=1; remitems&=mask; control^=seton; additems |=mask;
Типы операндов и результата сложного оператора присваивания можно определить на основании этой эквивалентности. Однако приведенный эквивалент для сложного оператора присваивания не совсем точен. В выражении
v <знак>= e
операнд v вычисляется только один раз, в то время как в выражении
v = v <знак> e
этот операнд вычисляется дважды. Это различие проявляется в побочных эффектах, связанных с вычислением операнда v, например, при изменении значения какой-либо переменной. Рассмотрим это на примерах:
a[i++]* = n;
При выполнении вычисление левого операнда дает побочный эффект - увеличение значения переменной i. Следовательно, это присваивание не эквивалентно присваиванию
a[i++] = a[i++]*n;.
Эквивалентом первой операции присваивания может служить последовательность операций
a[i]=a[i]*n; i=i+1;
а эквивалентом второй - последовательность операций
a[i]=a[i+1]*n; i=i+2;
или последовательность операций
a[i+1]=a[i]*n; i=i+2;
в зависимости от того, какая часть операции присваивания вычисляется раньше - левая или правая. Порядок таких вычислений не определен.
on_load_lecture()




" |
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
вопросы | "
учебники
|
для печати и PDA



| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
Операция присваивания: =
В языке Си знак равенства не означает "равно". Он означает операцию присваивания некоторого значения. С помощью оператораyar=2004;
переменной c именем yar присваивается значение 2004, т.е. элемент слева от знака = - это имя переменной, а элемент справа - ее значение. Мы называем символ = операцией присваивания. В этой операции действие выполняется справа налево.
Возможно, различие между именем переменной и ее значением покажется незначительным? В таком случае давайте рассмотрим следующий оператор:
i=i+1;
C математической точки зрения это бессмыслица. Если вы прибавляете единицу к конечному числу, результат не может быть равен исходному числу. Но как оператор присваивания данная строка имеет вполне определенный смысл, который можно выразить, например, такой фразой. Взять значение переменной с именем i, к нему прибавить 1, а затем присвоить новое значение переменной с именем i.
Оператор вида
2004=yar;
на языке Си не имеет смысла, поскольку 2004 - число. Мы не можем присвоить константе какое-то значение; ее значением является она сама. Поэтому, помните, что элемент, стоящий слева от знака =, всегда должен быть именем переменной. Операнд - это то, над чем выполняются операции. Например, можно описать процесс "поедания" картошки как применения операции "поедание" к операнду "картошка".
Операция присваивания в языке Си представляется более интересной, чем в большинстве других языков. Рассмотрим простую программу:
/* таблица результатов по шахматам */ main( ) { int l, m, n; n=m=l=165; printf("l m n\n"); printf("Счет первой партии %4d %8d %8d\n", l, m, n); }
Присваивания выполняются справа налево: сначала переменная l получает значение 165, затем переменная m и наконец n. В языке Си имеется несколько других операций присваивания, которые отличаются от описанной операции. Их мы рассмотрим попозже.
Представим еще один пример простой программы:
/* использование операции присваивания */ main( ) { /* переменные number, ouzo, cost инициализируются конкретными значениями*/ int number=5; float ouzo=13.5; int cost=31000; printf("number ouzo cost\n"); printf("number=%d ouzo=%f cost=%d\n", number, ouzo, cost); }
Операция присваивания
v <знак>= eприблизительно эквивалентна оператору присваивания
v = v<знак> e
Пример 1:
y+=2; /* Увеличение переменной y на 2 */ p+=n; x-=3; ptr-=2; timesx*=x; x/=2; x%=10; x>>=4; x<<=1; remitems&=mask; control^=seton; additems |=mask;
Типы операндов и результата сложного оператора присваивания можно определить на основании этой эквивалентности. Однако приведенный эквивалент для сложного оператора присваивания не совсем точен. В выражении
v <знак>= e
операнд v вычисляется только один раз, в то время как в выражении
v = v <знак> e
этот операнд вычисляется дважды. Это различие проявляется в побочных эффектах, связанных с вычислением операнда v, например, при изменении значения какой-либо переменной. Рассмотрим это на примерах:
a[i++]* = n;
При выполнении вычисление левого операнда дает побочный эффект - увеличение значения переменной i. Следовательно, это присваивание не эквивалентно присваиванию
a[i++] = a[i++]*n;.
Эквивалентом первой операции присваивания может служить последовательность операций
a[i]=a[i]*n; i=i+1;
а эквивалентом второй - последовательность операций
a[i]=a[i+1]*n; i=i+2;
или последовательность операций
a[i+1]=a[i]*n; i=i+2;
в зависимости от того, какая часть операции присваивания вычисляется раньше - левая или правая. Порядок таких вычислений не определен.
Операция присваивания: =
В языке Си знак равенства не означает "равно". Он означает операцию присваивания некоторого значения. С помощью оператораyar=2004;
переменной c именем yar присваивается значение 2004, т.е. элемент слева от знака = - это имя переменной, а элемент справа - ее значение. Мы называем символ = операцией присваивания. В этой операции действие выполняется справа налево.
Возможно, различие между именем переменной и ее значением покажется незначительным? В таком случае давайте рассмотрим следующий оператор:
i=i+1;
C математической точки зрения это бессмыслица. Если вы прибавляете единицу к конечному числу, результат не может быть равен исходному числу. Но как оператор присваивания данная строка имеет вполне определенный смысл, который можно выразить, например, такой фразой. Взять значение переменной с именем i, к нему прибавить 1, а затем присвоить новое значение переменной с именем i.
Оператор вида
2004=yar;
на языке Си не имеет смысла, поскольку 2004 - число. Мы не можем присвоить константе какое-то значение; ее значением является она сама. Поэтому, помните, что элемент, стоящий слева от знака =, всегда должен быть именем переменной. Операнд - это то, над чем выполняются операции. Например, можно описать процесс "поедания" картошки как применения операции "поедание" к операнду "картошка".
Операция присваивания в языке Си представляется более интересной, чем в большинстве других языков. Рассмотрим простую программу:
/* таблица результатов по шахматам */ main( ) { int l, m, n; n=m=l=165; printf("l m n\n"); printf("Счет первой партии %4d %8d %8d\n", l, m, n); }
Присваивания выполняются справа налево: сначала переменная l получает значение 165, затем переменная m и наконец n. В языке Си имеется несколько других операций присваивания, которые отличаются от описанной операции. Их мы рассмотрим попозже.
Представим еще один пример простой программы:
/* использование операции присваивания */ main( ) { /* переменные number, ouzo, cost инициализируются конкретными значениями*/ int number=5; float ouzo=13.5; int cost=31000; printf("number ouzo cost\n"); printf("number=%d ouzo=%f cost=%d\n", number, ouzo, cost); }
Операция простое присваивание: =
Тип операндов: арифметические, указатели, объединения или структуры. Тип результата: если оба операнда имеют арифметический тип, то значение правого операнда преобразуется к типу левого операнда.Использование:
v=e
Присваивание значения e переменной v.
Пример:
x=y;
Операция равенство: ==
Тип операндов - арифметический или указатель. Тип результата - int.Использование:
ie1==ie2
Истина, если ie1 равно ie2, иначе - ложь.
Операция сдвига влево: <<
Тип операнда - интегральный. Тип результата - такой же, как у левого операнда. Правый операнд преобразуется к типу int. Левый операнд сдвигается на число разрядов, равное значению правого операнда. Освобождающие разряды заполняются нулями.Использование:
ie1<
Пример:
four=x<<2;
Операция сдвига вправо: >>
Тип операнда - интегральный. Тип результата - такой же, как у левого операнда. Правый операнд преобразуется к типу int. Левый операнд сдвигается на число разрядов, равное значению правого операнда. Сдвиг будет логическим сдвигом, если левый операнд имеет тип unsigned.Использование:
ie1>>ie2
Двоичное представление ie1 сдвигается вправо на ie2 разрядов. Сдвиг вправо может быть арифметическим (т. е. освобождающиеся слева разряды заполняются значениями знакового разряда) или логическим в зависимости от реализации, однако гарантируется, что сдвиг вправо целых чисел без знака будет логическим и освобождающиеся слева разряды будут заполняться нулями.
Пример:
x=x>>3;
Операция сложения: +
Тип операндов:Тип результата: int, unsigned, long, double, указатель. Перед сложением значение целого операнда умножается на величину элемента данных, тип которых соответствует типу указателя.
Использование:
ae1+ae2
Сумма значений ae1 и ae2.
Пример 1:
i=i+100;
Первоначальное значение i увеличивает на 100.
Пример 2:
last=arname+arsize-1;
Присваивает переменной last адрес последнего элемента массива arname.
Операция сложения: +
Выполнение операции + приводит к сложению двух величин, стоящих слева и справа от этого знака. Например, в результате работы оператораprintf("%d", 100 + 65);
на печать будет выведено число 165, а не выражение 100+65. Операнды могут быть как переменными, так и константами. Операция + называется "бинарной", или "диадической". Эти названия отражают тот факт, что она имеет дело с двумя операндами.
Пример:
i=j+2;
Переменной i присваивается значение переменной j плюс 2.
Операция сложения: +
Тип операндов:Тип результата: int, unsigned, long, double, указатель. Перед сложением значение целого операнда умножается на величину элемента данных, тип которых соответствует типу указателя.
Использование:
ae1+ae2
Сумма значений ae1 и ae2.
Пример 1:
i=i+100;
Первоначальное значение i увеличивает на 100.
Пример 2:
last=arname+arsize-1;
Присваивает переменной last адрес последнего элемента массива arname.
Операция сложения: +
Выполнение операции + приводит к сложению двух величин, стоящих слева и справа от этого знака. Например, в результате работы оператораprintf("%d", 100 + 65);
на печать будет выведено число 165, а не выражение 100+65. Операнды могут быть как переменными, так и константами. Операция + называется "бинарной", или "диадической". Эти названия отражают тот факт, что она имеет дело с двумя операндами.
Пример:
i=j+2;
Переменной i присваивается значение переменной j плюс 2.
Операция сложное присваивание: =
Обозначим <знак> один из знаков : +, -, *, /, %, >>, <<, &, ^, |.В результате выполнения операции присваивания
v=e
где v - переменная, а e - выражение, значение выражения становится новым значением переменной v.
Операция уменьшения: --
Тип операнда - те же, что и для ++. Тип результата - те же, что и для ++.Использование:
--iv
Уменьшение iv на 1. Значением этого выражения является значение iv после уменьшения.
Пример:
i=--j;
Операция уменьшения: --
Каждой операции увеличения соответствует некоторая операция уменьшения, при этом вместо символов ++ мы используем --. Когда символы -- находятся слева от операнда - "префиксная" форма операции уменьшения. Если символы -- стоят справа от операнда - это "постфиксная" форма операции уменьшения.В соответствии с принятым в языке Си порядком вычислений операции увеличения и уменьшения имеют очень высокий уровень старшинства. Только круглые скобки обладают более высоким приоритетом. Поэтому выражение a/b++ означает (a)/(b++), а не (a/b)++.
| ! | Не применяйте операции увеличения или уменьшения к переменной, присутствующей в более чем одном аргументе функции. Не применяйте операции увеличения или уменьшения к переменной, которая входит в выражение более одного раза. |
Операция уменьшения: --
Тип операнда - те же, что и для ++. Тип результата - те же, что и для ++.Использование:
--iv
Уменьшение iv на 1. Значением этого выражения является значение iv после уменьшения.
Пример:
i=--j;
Операция уменьшения (постфиксная): --
Тип операнда - те же, что и для ++(постфиксная).Использование:
iv--
Уменьшение iv на 1. Значением этого выражения является значение iv до уменьшения.
Пример:
j=i--;
Операция уменьшения: --
Каждой операции увеличения соответствует некоторая операция уменьшения, при этом вместо символов ++ мы используем --. Когда символы -- находятся слева от операнда - "префиксная" форма операции уменьшения. Если символы -- стоят справа от операнда - это "постфиксная" форма операции уменьшения.В соответствии с принятым в языке Си порядком вычислений операции увеличения и уменьшения имеют очень высокий уровень старшинства. Только круглые скобки обладают более высоким приоритетом. Поэтому выражение a/b++ означает (a)/(b++), а не (a/b)++.
| ! | Не применяйте операции увеличения или уменьшения к переменной, присутствующей в более чем одном аргументе функции. Не применяйте операции увеличения или уменьшения к переменной, которая входит в выражение более одного раза. |
on_load_lecture()




" |
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
вопросы | "
учебники
|
для печати и PDA



| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
Операция умножения: *
Тип операндов - арифметический. Тип результатов: int, unsigned, long, double.Использование:
ae1*ae2
Произведение значений ae1*ae2.
Пример:
z=35*5;
on_load_lecture()




" |
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
вопросы | "
учебники
|
для печати и PDA



| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
Операция умножения: *
Операция умножения обозначается знаком *. При выполнении оператораz = 3 * x
значение переменной x умножается на 3, и результат присваивается переменной z.
Операция умножения: *
Тип операндов - арифметический. Тип результатов: int, unsigned, long, double.Использование:
ae1*ae2
Произведение значений ae1*ae2.
Пример:
z=35*5;
Операция умножения: *
Операция умножения обозначается знаком *. При выполнении оператораz = 3 * x
значение переменной x умножается на 3, и результат присваивается переменной z.
on_load_lecture()




1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
вопросы | "
учебники
|
для печати и PDA



| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
Уровень приоритета операции поразрядное включающее
Уровень приоритета операции поразрядное включающее или равен 10, порядок выполнения таких операторов - слева направо.и равен 11. Выполняется операция
Уровень приоритета логической (условной) операции и равен 11. Выполняется операция слева направо.Уровень приоритета операции равен 12,
Уровень приоритета операции равен 12, выполняются такие операции слева направо.Уровень приоритета операции равен 13,
Уровень приоритета операции равен 13, выполняются такие операции слева направо.Уровень приоритета операции равен 14,
Уровень приоритета операции равен 14, выполняются такие операции справа налево.Уровень приоритета операции равен 15,
Уровень приоритета операции равен 15, выполняются такие операции слева направо.И равен 8. Выполняются такие
Уровень приоритета операции поразрядное И равен 8. Выполняются такие операции слева направо.Уровень приоритета операции поразрядное исключающее
Уровень приоритета операции поразрядное исключающее ИЛИ равен 9. Порядок выполнения таких операций слева направо.Операция условный оператор: ?
Тип операндов - арифметические; второй и третий операнды могут быть указателями, структурами, объединениями. Тип результата: int, long, unsigned, double, указатель, структура или объединение. Второй и третий операнды преобразуются к одному и тому же типу.Условный оператор является единственным оператором, для которого необходимы три операнда; используется он следующим образом:
a ? b : c
где a, b, c - выражения. Если a не равно 0, то результат выражения a ? b : c равен b; иначе результат равен c. Из двух последних операндов вычисляется только один.
Использование:
ae?e1:e2
или
pe?e1:e2 (где pe - указатель)
Если истинно ae или pe, то выполняется e1; иначе выполняется e2. Значением этого выражения является значение выражения e1 или e2.
Пример:
abs=(i<=0)?-i:i;
Операция увеличения: ++
Тип операнда - арифметический или указатель. Тип результата: int, unsigned, long, double, указатель. Значение операнда увеличивается, и становится новым значением операнда. Значение указателя увеличивается на величину указываемого объекта, значения других операндов увеличиваются на единицу.Операция увеличения: ++
Операция увеличения осуществляет следующее простое действие: она увеличивает значение своего операнда на единицу. Существуют две возможности использования данной операции: первая, когда символы ++ находятся слева от переменной (операнда), - "префиксная" форма, и вторая, когда символы ++ стоят справа от переменной, - "постфиксная" форма. Эти две формы указанной операции различаются между собой только тем, в какой момент осуществляется увеличение операнда. Префиксная форма изменяет значение операнда перед тем, как операнд используется. Постфиксная форма изменяет значение после того как операнд использовался.В примере,
j=i++;
переменной j сначала присваивается значение i, затем значение переменной i увеличивается на 1.
Операция увеличения: ++
Тип операнда - арифметический или указатель. Тип результата: int, unsigned, long, double, указатель. Значение операнда увеличивается, и становится новым значением операнда. Значение указателя увеличивается на величину указываемого объекта, значения других операндов увеличиваются на единицу.Операция увеличения, постфиксная: ++
Тип операнда - арифметический или указатель. Тип результата: int, unsigned, long, double, указатель. Значение операнда увеличивается, но возвращается старое значение операнда. Значение указателя увеличивается на величину указываемого объекта, другие операнды увеличиваются на единицу.Использование:
iv++
Увеличение iv на 1. Значением этого выражения является значение iv до увеличения.
Пример:
j=i++;
Использование:
pv++;
Увеличение указателя pv на 1, так что он будет указывать на следующий объект того же типа. Значением этого выражения является значение pv до увеличения.
Пример:
*ptr++=0;
Присвоить значение 0 переменной, на которую указывает ptr, затем увеличить значение указателя ptr так, чтобы он указывал на следующую переменную того же типа.
Операция увеличения: ++
Операция увеличения осуществляет следующее простое действие: она увеличивает значение своего операнда на единицу. Существуют две возможности использования данной операции: первая, когда символы ++ находятся слева от переменной (операнда), - "префиксная" форма, и вторая, когда символы ++ стоят справа от переменной, - "постфиксная" форма. Эти две формы указанной операции различаются между собой только тем, в какой момент осуществляется увеличение операнда. Префиксная форма изменяет значение операнда перед тем, как операнд используется. Постфиксная форма изменяет значение после того как операнд использовался.В примере,
j=i++;
переменной j сначала присваивается значение i, затем значение переменной i увеличивается на 1.
Операция выбора компонентов структуры или объединения: .
Синтаксис:struct.element
Значением этого выражения является элемент element структуры struct или объединения (см. лекцию 14). Оператор:
struct.element=1963;
присваивает значение 1963 этому элементу.
Операция выбора компонентов структуры с указателем: ->
Синтаксис:my_birthday->day,
my_birthday - указатель на структуру. Оператор:
my_birthday->day=26;
присваивает значение 26 структурной переменной day, на которую указывает my_birthday.
Операция вычитания: -
Тип операндов:В первом случае тип результата: int, unsigned, long, double. Во втором случае тип результата - указатель. До вычитания значение целого операнда умножается на величину элемента данных, тип которых соответствует типу указателя. В третьем случае тип результата - int. Результат - число объектов, отделенных двумя указателями.
Арифметические действия с указателями. Арифметические действия с указателями отличаются от арифметических действий с обычными целыми значениями, и определяются следующим образом. Предположим, что i является целым выражением, а указатели p и q указывают на элементы типа Т. Сложение значения i и p эквивалентно сложению числа ячеек памяти в байтах, занятых i элементами типа Т. Аналогично определяется операция вычитания. Результат вычитания двух указателей типа *Т является не разностью значений двух указателей, а числом элементов типа Т, размещенных между ячейками, ссылки на которые обеспечиваются указателями. Никакие другие арифметические действия с указателями не допускаются. Вычитание указателей имеет смысл только для указателей, ссылающихся на элементы одного и того же массива, поскольку только в этом случае разность адресов элементов массива всегда равна произведению целого значения на величину элемента.
Использование:
ae1-ae2
Разность значений ae1 и ae2.
Пример 1:
i=j-100;
Использование:
pe-ie
Адрес переменной типа pe, меньше на ie адреса, заданного указателем pe.
Пример 2:
first=last-arsize+1;
Использование:
pe1-pe2
Число переменных типа pe в диапазоне от pe2 до pe1.
Пример 3:
arsize=last-first;
on_load_lecture()




" |
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
вопросы | "
учебники
|
для печати и PDA



| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
Операция вычитания: -
Выполнение операции вычитания приводит к вычитанию числа, расположенного справа от знака -, из числа, стоящего слева от этого знака. Операторn = 163.00 - 100.00;
присваивает переменной n значение 63.00.
Операция вычитания: -
Тип операндов:В первом случае тип результата: int, unsigned, long, double. Во втором случае тип результата - указатель. До вычитания значение целого операнда умножается на величину элемента данных, тип которых соответствует типу указателя. В третьем случае тип результата - int. Результат - число объектов, отделенных двумя указателями.
Арифметические действия с указателями. Арифметические действия с указателями отличаются от арифметических действий с обычными целыми значениями, и определяются следующим образом. Предположим, что i является целым выражением, а указатели p и q указывают на элементы типа Т. Сложение значения i и p эквивалентно сложению числа ячеек памяти в байтах, занятых i элементами типа Т. Аналогично определяется операция вычитания. Результат вычитания двух указателей типа *Т является не разностью значений двух указателей, а числом элементов типа Т, размещенных между ячейками, ссылки на которые обеспечиваются указателями. Никакие другие арифметические действия с указателями не допускаются. Вычитание указателей имеет смысл только для указателей, ссылающихся на элементы одного и того же массива, поскольку только в этом случае разность адресов элементов массива всегда равна произведению целого значения на величину элемента.
Использование:
ae1-ae2
Разность значений ae1 и ae2.
Пример 1:
i=j-100;
Использование:
pe-ie
Адрес переменной типа pe, меньше на ie адреса, заданного указателем pe.
Пример 2:
first=last-arsize+1;
Использование:
pe1-pe2
Число переменных типа pe в диапазоне от pe2 до pe1.
Пример 3:
arsize=last-first;
Операция вычитания: -
Выполнение операции вычитания приводит к вычитанию числа, расположенного справа от знака -, из числа, стоящего слева от этого знака. Операторn = 163.00 - 100.00;
присваивает переменной n значение 63.00.
Операция вызова функции: ( )
Пример 1:fe(e1, e2,...,en);
Вызов функции fe с аргументами e1, e2, ..., en. Значением этого выражения является значение, которое возвращает функция.
Пример 2:
x = sqrt (y);
Операция запятая: ,
Тип результата совпадает с типом правого операнда. Операция объединяет два выражения в одно выражение, значением которого является значение правого операнда; значение левого операнда вычисляется только для получения побочных эффектов.Использование:
el,e2
Сначала выполняется выражение e1, потом выражение е2. Значением всего выражения является значение выражения е2.
Пример:
for(i=A,j=B;i
/* Задача № 1 (пример на использование операции *) Определить площадь боковой поверхности конуса радиуса r и имеющего длину образующей l.*/ #include
/* Задача № 2 (пример на использование операций +, * и /. Тело движется прямолинейно с ускорением. Даны а(м/сек2) - ускорение, V(M/C) - начальная скорость. Требуется определить, какой путь пройдет тело за t секунд.*/ #include
Основные операции
Рассмотрим способы обработки данных - для этого язык Си имеет широкий набор возможностей. Основные арифметические операции: сложения, вычитания, умножения, деления. Операции в языке Си применяются для представления арифметических действий. Например, выполнение операции + приводит к сложению двух величин, стоящих слева и справа от этого знака. Рассмотрим операции =, +, -, *, /. В языке Си нет операции возведения в степень.Перечень операций языка Си
Рассмотрим множество операций языка Си. Описанные операции входят в это множество. Язык Си отличается их большим разнообразием. В этом разделе будет обсуждаться их семантика. Каждая операция характеризуется уровнем приоритета и порядком, в котором эти операции выполняются - слева направо или справа налево. Если все операции выражения имеют один и тот же уровень приоритета, то значение выражения вычисляется слева направо в соответствии с порядком выполнения операций. Все операции с одним и тем же уровнем приоритета имеют один и тот же порядок. Однако если в выражении имеются операции с различными уровнями приоритета, то сначала выполняются операции с наивысшим уровнем приоритета, затем - следующего за ним приоритета и так далее в порядке убывания приоритета. Операции одного уровня приоритета выполняются в последовательности, указанной их порядком.Каждому аргументу из списка, следующего
Каждому аргументу из списка, следующего за управляющей строкой, должна соответствовать одна спецификация преобразования. Если нужно напечатать какую-нибудь фразу, то нет необходимости использовать спецификацию преобразования; если же требуется только вывести данные на печать, то можно обойтись и без использования комментария. Поэтому каждый из операторов, приведенных ниже, вполне приемлем:printf("Эта книга не очень дорогая!\n"); printf("%c%d\n",'$',cost);
Если нужно напечатать сам символ %, то компилятор примет его за ошибочную спецификацию преобразования. Выходом из создавшейся ситуации служит довольно простое решение - писать два символа % подряд.
Например:
int i=2+3; printf("Только %d%% населения способно учиться самостоятельно! \n",i);
Результат работы программы будет выглядеть следующим образом:
Только 5% населения способно учиться самостоятельно!
Мы можем расширить основное определение спецификации преобразования, поместив модификаторы между знаком % и символом, определяющим тип преобразования. При использовании одновременно нескольких модификаторов они должны быть указаны в том порядке, в котором перечислены в таблице.
| - | Аргумент будет печататься с левой позиции поля заданной ширины. Обычно печать аргумента оканчивается в самой правой позиции поля Пример: %-10 |
| строка цифр | Задает минимальную ширину поля. Большее поле будет использоваться, если печатаемое число или строка не помещается в исходном поле Пример: %4d |
| строка цифр | Определяет точность: для типов данных с плавающей точкой число печатаемых цифр справа от десятичной точки; для символьных строк - максимальное число печатаемых символов Пример: %4.2f (две десятичные цифры для поля шириной в четыре символа) |
| l | Соответствующий элемент данных имеет тип long, а не int Пример: %ld |
main( ) { printf("/%d/\n",135); printf("/%2d/\n",135); printf("/%10d/\n",135); printf("/%-10d/\n",135); }
Первая спецификация преобразования %d не содержит модификаторов. Это так называемый выбор по умолчанию, т. е. результат действия компилятора в случае, если вы не дали ему никаких дополнительных инструкций. Вторая спецификация преобразования - %2d. Она указывает, что ширина поля должна равняться 2, но, поскольку число состоит из трех цифр, поле автоматически расширяется до необходимого размера. Следующая спецификация %10d показывает, что ширина поля равна 10. Последняя спецификация %-10d также указывает ширину поля, равную 10, а знак - приводит к сдвигу всего числа к левому краю.
Если Вы заметили ошибку
on_load_lecture()



" |
1
|
2
|
3
|
4
|
вопросы | "
учебники
|
для печати и PDA



|
Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
Каждому аргументу из списка, следующего
Каждому аргументу из списка, следующего за управляющей строкой, должна соответствовать одна спецификация преобразования. Если нужно напечатать какую-нибудь фразу, то нет необходимости использовать спецификацию преобразования; если же требуется только вывести данные на печать, то можно обойтись и без использования комментария. Поэтому каждый из операторов, приведенных ниже, вполне приемлем:printf("Эта книга не очень дорогая!\n"); printf("%c%d\n",'$',cost);
Если нужно напечатать сам символ %, то компилятор примет его за ошибочную спецификацию преобразования. Выходом из создавшейся ситуации служит довольно простое решение - писать два символа % подряд.
Например:
int i=2+3; printf("Только %d%% населения способно учиться самостоятельно! \n",i);
Результат работы программы будет выглядеть следующим образом:
Только 5% населения способно учиться самостоятельно!
Мы можем расширить основное определение спецификации преобразования, поместив модификаторы между знаком % и символом, определяющим тип преобразования. При использовании одновременно нескольких модификаторов они должны быть указаны в том порядке, в котором перечислены в таблице.
| - | Аргумент будет печататься с левой позиции поля заданной ширины. Обычно печать аргумента оканчивается в самой правой позиции поля Пример: %-10 |
| строка цифр | Задает минимальную ширину поля. Большее поле будет использоваться, если печатаемое число или строка не помещается в исходном поле Пример: %4d |
| строка цифр | Определяет точность: для типов данных с плавающей точкой число печатаемых цифр справа от десятичной точки; для символьных строк - максимальное число печатаемых символов Пример: %4.2f (две десятичные цифры для поля шириной в четыре символа) |
| l | Соответствующий элемент данных имеет тип long, а не int Пример: %ld |
main( ) { printf("/%d/\n",135); printf("/%2d/\n",135); printf("/%10d/\n",135); printf("/%-10d/\n",135); }
Первая спецификация преобразования %d не содержит модификаторов. Это так называемый выбор по умолчанию, т. е. результат действия компилятора в случае, если вы не дали ему никаких дополнительных инструкций. Вторая спецификация преобразования - %2d. Она указывает, что ширина поля должна равняться 2, но, поскольку число состоит из трех цифр, поле автоматически расширяется до необходимого размера. Следующая спецификация %10d показывает, что ширина поля равна 10. Последняя спецификация %-10d также указывает ширину поля, равную 10, а знак - приводит к сдвигу всего числа к левому краю.
Изучение и использование функций printf( ) и scanf( )
Функции printf( ) и scanf( ) дают нам возможность взаимодействовать с программой. Мы называем их функциями ввода-вывода. Это не единственные функции, которыми мы можем воспользоваться для ввода и вывода данных с помощью программ на языке Си, но они наиболее универсальны. Эти функции входят в описание языка Си и они даны в библиотеке stdio.h. Обычно функции printf( ) и scanf( ) работают во многом одинаково - каждая использует управляющую строку и список аргументов. Сначала мы рассмотрим работу функции printf( ), затем scanf( ).| %d | десятичное целое число |
| %c | один символ |
| %s | строка символов |
| %e | экспоненциальная запись |
| %f | число с плавающей точкой, десятичная запись |
| %g | используется вместо записи %f или %e |
| %u | десятичное целое число без знака |
| %o | восьмеричное целое число без знака |
| %x | шестнадцатеричное целое число без знака |
Инструкции, передаваемые функции printf( ), когда мы хотим напечатать некоторую переменную, зависят от того, какого типа эта переменная. Например, при выводе на печать целого числа применяется формат %d, а при выводе символа - %c. Форматы перечислены в таблице.
Посмотрим теперь, как эти формы применяются:
/* печать */ #define PI 3.14159 main( ) { int number = 2003; printf("Интернет-университет информационных технологий был открыт в %d году \n", number); printf("Значение числа pi равно %f.\n", PI); }
Формат, указываемый при обращении к функции printf( ), выглядит следующим образом:
printf(Управляющая строка, аргумент1, аргумент2,_);
аргумент 1, аргумент 2 и т.д. - это печатаемые параметры, которые могут быть переменными, константами или даже выражениями, вычисляемыми перед выводом на печать.
Управляющая строка - строка символов, показывающая, как должны быть напечатаны параметры. Например, в операторе
printf("%d студентов получили оценку %f.\n", number, z);
управляющей строкой служит фраза в кавычках, а number и z - аргументы или в данном случае значения двух переменных.
Мы видим, что в управляющей строке содержится информация двух различных типов:
Оператор цикла while
Пусть нам нужно каким-то образом заставить компьютер выполнять повторяющиеся вычисления. Язык Си предлагает несколько способов реализации повторяющихся вычислений. Сейчас мы коротко обсудим один из них. Данный способ называется "циклом с предусловием while". Цикл while работает следующим образом. Когда программа в процессе выполнения впервые достигает оператора while, осуществляется проверка истинности условия, заключенного в круглые скобки. Затем идет тело цикла, заключенное в фигурные скобки. В теле цикла перевычисляется переменная, которая анализируется в условии, там где встречается закрывающая фигурная скобка (конец тела цикла while), управление передается на оператор while. Если условие не выполняется, то управление передается за тело цикла, т.е. за закрывающую фигурную скобку.Все операторы цикла языка Си рассматриваются в 8-ой лекции.
Пример 1:
/* От города А до города В расстояние равно 20 км. Велосипедист выехал из А и в первый день проехал 10 км. В последующие дни он проезжал со скоростью, на 0,5 раз большей, чем в предыдущий день. За сколько дней велосипедист доберется до города В.*/ #include
on_load_lecture()




1
|
2
|
3
|
4
|
вопросы | "
учебники
|
для печати и PDA



| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
Операторы
Любое выражение может быть преобразовано в оператор добавлением к нему точки с запятой. Запись видавыражение;
является оператором. Значение выражения игнорируется. Действие такого оператора состоит в создании побочного эффекта вычислением значения выражения.
Операторы служат основными строительными блоками программы. Программа состоит из последовательности операторов с добавлением небольшого количества знаков пунктуации. Оператор является законченной инструкцией для компиляторов. "Точка с запятой" является указанием на наличие оператора. Поэтому
line = 5
- это всего лишь выражение, которое может быть частью большого выражения, но
line = 5;
является оператором.
Выражение
5+5
не является законченной инструкцией, а служит указанием компьютеру сложить 5 и 5, но не говорит, что делать с результатом.
dog=5+5;
служит указанием компилятору, а затем компьютеру, поместить результат 10 в ячейку памяти, помеченную именем dog. После занесения в память числа 10 компьютер может приступить к выполнению следующих действий.
Применение функции scanf( )
Поскольку мы будем пользоваться функцией scanf( ) эпизодически, мы рассмотрим здесь только основные особенности ее применения. Для функции scanf( ) указывается управляющая строка и следующий за ней список аргументов. Основное различие функций printf( ) и scanf( ) заключается в особенностях данного списка. Функция printf( ) использует имена переменных, константы и выражения, а функция scanf( ) - только указатели на переменные. Мы ничего не должны знать о таких указателях. Необходимо помнить только два правила:Пример:
main() { int age; float assets; char fio[50]; printf("Введите ваш возраст, оклад, фамилию. \n"); scanf("%d %f", &age, &assets); scanf("%s", fio); /* & отсутствует при указании массива символов */ printf("%d $%.0f %s\n",age, assets, fio); }
Функция scanf( ) использует некоторые специальные знаки, пробелы, символы табуляции и "новая строка", для разбиения входного потока символов на отдельные поля. Она согласует последовательность спецификаций преобразования с последовательностью полей, опуская упомянутые специальные знаки между ними. Исключением является спецификация %c , обеспечивающая чтение каждого следующего символа даже в том случае, если это пустой символ.
Функция scanf( ) использует тот же набор символов спецификации преобразования, что и функция printf( ). Основные отличия функции scanf( ) следующие:
Функция scanf( ) не является одной из наиболее часто используемых функций языка Си. Мы обсуждаем ее из-за универсальности.
Особенности работы с языком Си. Задание фиксированной ширины полей оказывается полезным при печати данных столбиком.
Например:
printf("%d %d %5d\n",val1,val2, val3);
Результат выглядит так:
11 222 3333 4 5 23 22222 3332 11111
Эти же данные можно представить в улучшенном виде, если задать достаточно большую фиксированную ширину поля:
printf("%9d %9d %9d\n" val1,val2, val3);
Результат будет выглядеть так:
11 222 3333 4 5 23 22222 3332 11111
Если печатаемое число включено в некоторую фразу, то часто при его выводе оказывается удобным задать ширину поля равной или меньше требуемой. Это дает возможность включить число в фразу без добавления лишних пробелов.
А теперь рассмотрим два примера работы с циклом while:
/*квадраты чисел*/ main( ) /*получение квадратов*/ { int n=1; while(n < 11) { printf("%10d %10d\n", n, n*n); n=n+1; } }
Эта программа выводит на печать первые 10 чисел и их квадраты.
Второй пример.
Согласно легенде, один правитель обещал наградить ученого, оказавшего ему большую услугу. Ученый, когда его спросили, что бы он хотел получить в награду, указал на шахматную доску и сказал: "Положите одно пшеничное зерно на первую клетку, два - на вторую, четыре на третью, восемь на следующую и т.д." Правитель был поражен, услышав такую скромную просьбу. Программа, приведенная ниже, показывает, в какое положение попал правитель!
/* пшеница*/ #define NUMBER 64 /* число клеток на шахматной доске*/ #define CROP 7E14 /* весь урожай пшеницы, выраженный в числе зерен*/ main( ) { double current, total; int count =1; printf("КЛЕТКА ЧИСЛО СУММА ЗЕРЕН ДОЛЯ\n"); total = current = 1.0;/*начинаем с одного зерна*/ printf("%4d %15.2e %13.2e %12.2e\n",count, current, total, total/CROP); while(count < NUMBER) { count = count + 1; current = 2.0*current; /*удвоенное число зерен на следующей клетке */ total = total +current; /* коррекция суммы*/ printf ("%4d %15.2e %13.2e %12.2e\n",count, current, total, total/CROP); } }
Это пример составного оператора. От открывающей фигурной скобки оператора while до закрывающей фигурной скобки.
Простейшие выражения
Простейшими выражениями называются выражения, сформированные с использованием констант типов int, char и enum, операции sizeof, унарных операторов - и ~, бинарных операторов + ~ * / % & | ^ << >> = = != < > <= >= и тернарной операции ?:.Простейшие выражения используются в операторе switch, в инициализаторах границ массивов и в операторе препроцессора #if.
Логические операции ||, && являются условными логическими операциями, т. к. второй операнд вычисляется только при необходимости. В других языках программирования, например, в языке Паскаль, в логических операторах всегда вычисляются значения обоих операндов, даже если результат может быть определен вычислением одного операнда.
Важным свойством языка Си является то, что каждое выражение в Си имеет значение. Приведем несколько выражений и их значения:
| -14+16 | 2 |
| a=3+8 | 11 |
| 5>3 | 1 |
| 14<3 | 0 |
| 6+(c=3+8) | 17 |
Составные операторы
Составной оператор представляет собой два или более операторов, объединенных с помощью фигурных скобок; он называется также блоком. Чтобы быть свободными в обсуждении составных операторов, рассмотрим один из операторов цикла языка Си и использования в Си функций printf( ) и scanf( ).Выражения
Выражение представляет собой объединение операций и операндов. Напомним, что операндом называется то, над чем выполняется операция. Простейшее выражение состоит из одного операнда. Опираясь на это понятие выражения, мы можем строить более сложные конструкции. Приведем несколько выражений:100 1904 +100 a*(c-d) x=0 x=y++ x>3
Выражениями называются компоненты программы, составленные с использованием операций, литералов, констант, переменных (включая массивы, структуры и объединения) и вызовов функций. Порядок вычисления выражений определен лишь требованиями соответствия семантике операторов и соблюдения правил приоритета и порядка выполнения операций. При выполнении этих требований компилятор свободен в выборе порядка вычисления выражения, даже если вычисление подвыражений может привести к побочным эффектам.
В отличие от большинства других языков, в языке Си для задания определенного порядка вычисления выражения недостаточно только соответствующей расстановки скобок, так как компилятор может произвольно переупорядочивать выражения, включающие ассоциативные и коммутативные операторы (*,+,|,^) даже при наличии скобок. Для задания желаемого порядка выполнения выражения нужно использовать дополнительные присваивания, если требуется, с использованием временных переменных.
Необходимо с осторожностью использовать выражения, при вычислении которых возможны побочные результаты, так как результаты вычисления таких выражений часто проявляются не сразу и, кроме того, зависят от используемого компилятора. Например, в результате вычисления операторов присваивания
j=3; i=(k=j+1)+(j=5);
значение переменной i будет равно 9 или 11 в зависимости от того, какое подвыражение второй операции будет вычислено первым. Таким образом, с использованием разных компиляторов можно получить различные результаты.
Арифметические преобразования
Арифметические операторы языка Си преобразуют операнды к соответствующим типам автоматически, если операнды не имели таких типов с самого начала. Схема преобразования, используемая этими операторами, называется обычные арифметические преобразования; эта схема может быть описана следующими правилами:Явные преобразования типов
Выражения могут быть преобразованы из одного типа в другой явным указанием. Выражение E может быть явно преобразовано к типу имя-типа с помощью записи вида(имя - типа) Е
где имя типа представляется в форме
указатель-типа абстрактный-описатель
Абстрактный описатель аналогичен описателю, за исключением того, что он не содержит определяемого или описываемого идентификатора. Смысл слов имя-типа, представляемого в форме
Т абстрактный описатель
где Т является указателем типа, может быть определен одним из таких способов:
Приведем примеры явного преобразования. Предположим, что даны следующие определения и описания:
int i; char *pc, *name; char *calloc( ), *strcpy( );
тогда можно привести следующие примеры явных преобразований типов:
(char) i - преобразует значение типа int в значение типа char.
pc=(char *) 0777 - преобразует восьмеричный литер 0777 в значение указателя на знак таким образом, что оно может быть присвоено переменной "pc".
(emp *) calloc(1,sizeof(emp)) - преобразует значение "знакового" указателя, возвращаемого функцией calloc, в значение указателя emp.
(void) strcpy(name,"gehani") - опускает значение, возвращенное функцией strcpy.
Эквивалентность типов
Существует несколько схем для определения того, являются ли типы двух объектов эквивалентными. Две схемы, наиболее часто используемые, называются структурная эквивалентность типов и именная эквивалентность типов. В соответствии со схемой структурной эквивалентности типов два объекта относятся к одному и тому же типу только в том случае, если их компоненты имеют одинаковые типы. В соответствии со схемой именной эквивалентности типов два объекта имеют один и тот же тип только в случае их определения с использованием имени того же типа.Большинство реализаций языка Си используют схему структурной эквивалентности типов. Однако в книге (Ritche, D.M. 1980/ The C Programming Language - Reference Manual/ AT&T Bell Laboratories, Murray Hill, N.J. 07974) вопрос об эквивалентности типов игнорируется, и при каждой реализации может быть выбрана своя схема определения эквивалентности типов. Следовательно, вполне возможно, что результаты правильно работающей программы станут неверными при замене компилятора!
Неявное преобразование типа
Неявные преобразования типа выполняются главным образом для согласования аргументов оператора или функции (если это возможно) со значениями, предполагаемыми в этих операторах или функциях. Все неявные преобразования типа, которые могут встретиться, перечислены ниже (слева указывается преобразуемый тип, а справа - список типов, в которые он может быть преобразован):char - int, short int, long int (Преобразование к значению с большим числом двоичных разрядов может включать, а может не включать расширение знакового разряда - это зависит от реализации языка. Для элементов заданного набора знаков гарантируется преобразование в неотрицательные целые значения).
int - char, short int, long int ( преобразование к целому большей длины включает расширение знакового разряда. Преобразование к целому меньшей длины вызывает отбрасывание лишних старших разрядов). float, double, unsigned int (интерпретация комбинации битов в виде беззнакового целого значения).
short int - аналогично типу int.
long int - аналогично типу int.
float - double, int, short int, long int (машинно-зависимое преобразование, если преобразуемое значение слишком велико, то результат неопределен).
double - float (преобразование с округлением и последующим отбрасыванием лишних разрядов), int, short int, long int.
Операция приведения
Самое лучшее - это вообще избегать преобразования типов, особенно в порядке убывания ранга. Но иногда оказывается удобным применять такие преобразования при условии, что мы ясно представляем смысл выполняемых действий. Преобразования типов, которые мы обсуждали до сих пор, выполнялись автоматически. Существует возможность точно указывать тип данных, к которому необходимо привести величину. Этот способ называется приведением типов, и используется следующим образом: перед данной величиной в круглых скобках записывается имя требуемого типа. Скобки и имя типа вместе образуют операцию приведения. В общем виде она записывается так:(тип)
где фактическое имя требуемого типа представляется вместо слова тип.
Рассмотрим пример:
int nice; nice = 1.6+1.7; nice = (int)1.6+(int)1.7;
В первом примере используется автоматическое преобразование типов. Сначала числа 1.6 и 1.7 складываются - результат равен 3.3. Затем путем отбрасывания дробной части полученное число преобразуется в 3 для согласования с типом int переменной nice. Во втором примере 1.6 и 1.7 преобразуются в целые числа 1, так что переменной nice присваивается значение, равное 1+1, или 2.
Особенности работы с языком Си. Мы не должны смешивать типы; вот почему в некоторых языках это запрещено. Но бывают ситуации, когда это оказывается полезным. Философия языка Си заключается в том, чтобы не устанавливать барьеров на вашем пути, но при этом возложить на вас всю ответственность за злоупотребление предоставленной свободой.
on_load_lecture()




1
|
2
|
вопросы | "
учебники
|
для печати и PDA



| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
Преобразование типов
В операторах и выражениях должны использоваться переменные и константы только одного типа. Если все же мы смешиваем типы в одном выражении, то компилятор с языка Си не считает программу неправильной, как это произошло бы при программировании на языке Паскаль. Вместо этого компилятор использует набор правил для автоматического преобразования типов. Это очень удобно, но может оказаться и опасным, особенно если мы допустили смешение типов нечаянно. Приведемнесколько основных правил, касающихся преобразования типов:
Повышение типа обычно происходит гладко, в то время как понижение может привести к затруднениям. Причина этого проста: все число целиком может не поместиться в элементе данных низшего типа. Переменная типа char может иметь целое значение 101, но не 22225.
Пример, приведенный ниже, иллюстрирует применение этих правил:
/*преобразования*/ main( ) { char ch; int i; float f1; f1=i=ch='A'; /***8***/ printf("ch=%c,i=%d,f1=%2.2f\n",ch,i,f1); ch=ch+1; /***10***/ i=f1=f1+2*ch; /***11***/ f1=2.0*ch+i; /***12***/ printf("ch=%c,i=%d,f1=%2.2f\n",ch,i,f1); ch=2.0e30;/***14***/ printf("Теперь ch=%c\n",ch); }
Выполнив программу "преобразования", получим следующие результаты:
ch = A, i = 65, f1 = 65.00 ch = B, i = 197, f1= 329.00 Теперь ch =
Разбор программы
Строки 8 и 9: величина 'A' присваивается символьной переменной ch. Переменная i получает целое значение, являющееся преобразованием символа 'A' в целое число, т.е. '65'. Переменная f1 получает значение 65.00, являющееся преобразованием числа 65 в число с плавающей точкой.Строки 10 и 13: значение символьной переменной 'A' преобразуется в целое число 65, к которому затем добавляется 1. После этого получившееся в результате число 66 преобразуется в код символа В и помещается в переменную ch.
Строки 11 и 13: при умножении на 2 значение переменной ch преобразуется в целое число 66. При сложении с величиной переменной f1 получившееся в результате число 132 преобразуется в число с плавающей точкой. Результат 197.00 преобразуется в число целого типа и присваивается переменной i.
Строки 12 и 13: перед умножением на 2.0 значение переменной ch('B') преобразуется в число с плавающей точкой. Перед выполнением сложения величина переменной i(197) преобразуется в число с плавающей точкой, а результат 329.00 присваивается переменной f1.
Строки 14 и 15: здесь производится попытка осуществить преобразование типов в порядке убывания старшинства - переменная ch полагается равной сравнительно большому числу. Результаты оказываются неутешительными. Независимо от переполнения и усечения, которые имеют место, в итоге мы получили код, соответствующий какому-то непечатаемому знаку.
Существует еще один вид преобразования типов. Для сохранения точности вычислений при арифметических операциях все величины типа float преобразуются в данные типа double. Это существенно уменьшает ошибку округления. Конечный результат преобразуется обратно в число типа float, если это диктуется соответствующим оператором описания.
Синтаксис типов
Можно отметить, что синтаксис типов в языке Си нерегулярен и беспорядочен, о чем свидетельствуют:on_load_lecture()




" |
1
|
2
|
вопросы | "
учебники
|
для печати и PDA



| Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
Буферы
При выполнении данной программы (любой из двух ее версий), вводимый символ в одних вычислительных системах немедленно появится на экране (эхо-печать), в других же вычислительных системах ничего не происходит до тех пор, пока мы не нажмем клавишу Enter. Первый случай относится к небуферизованному (прямому) вводу, означающему, что выводимый символ оказывается немедленно доступным ожидающей программе. Второй случай служит примером буферизованного ввода, когда вводимые символы собираются и помещаются в некоторой области временной памяти, называемую буфером. Нажатие клавиши Enter приводит к тому, что блок символов, или один символ, становится доступным программе. В нашей программе применяется только первый символ, поскольку функция getchar( ) вызывается в ней один раз.Зачем нужны буферы? Во-первых, оказывается, что передачу нескольких символов в виде одного блока можно осуществить гораздо быстрее, чем передавать их последовательно по одному. Во-вторых, если при вводе символов допущена ошибка, мы сможем воспользоваться корректирующими средствами терминала, чтобы ее исправить. И когда мы нажмем клавишу Enter, будет произведена передача откорректированной строки. Однако, для некоторых диалоговых программ небуферизованный ввод может оказаться приемлемым. Например, в программах обработки текстов было бы желательно, чтобы каждая команда вводилась, как только мы нажимаем соответствующую клавишу. Поэтому как буферизованный, так и небуферизованный ввод имеет свои достоинства.
Рассмотрим вывод на печать групп символов. Желательно, чтобы в любой момент можно было остановить работу программы. Для этого напишем программу так, чтобы она прекращала работу при получении какого-нибудь специального символа, например "!":
/* ввод-вывод */ /* ввод и печать символов до поступления завершающего символа */ #include
В данном примере при первом прохождении тела цикла функция putchar( ) получает значение своего аргумента в результате выполнения оператора, расположенного в строке 9. В дальнейшем, вплоть до завершения работы цикла, значением этого аргумента является символ, передаваемый программе функцией getchar, расположенной в строке 12. Цикл while будет осуществлять чтение и печать символов до тех пор, пока не поступит признак STOP.
| ! | Программа, приведенная ниже, делает то же самое, но стиль ее написания лучше отвечает духу языка Си: /* ввод-вывод */ #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 |