Основы программирования на языке 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 можно использовать для определения символьных и строковых констант. В первом случае необходимо использовать "апостроф", а во втором кавычки.

Например,

#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.1. Операции в порядке уменьшения уровня старшинстваОперацииПорядок вычисления
( )слева направо
- (унарный) слева направо
* / слева направо
+ - (вычитание) слева направо
= справа налево

Операция дополнения до 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 Истина, если ae1 меньше, чем ae2.
Пример:
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< Двоичное представление ie1 сдвигается влево на ie2 разрядов, освобождающие разряды заполняются нулями.
Пример:
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 #include /* подключение библиотеки математических функций */ const float pi=3.14159; float r=10; float l=50; float s; main() { s=pi*r*l; printf("\n Площадь боковой поверхности s=%f",s); }
    /* Задача № 2 (пример на использование операций +, * и /. Тело движется прямолинейно с ускорением. Даны а(м/сек2) - ускорение, V(M/C) - начальная скорость. Требуется определить, какой путь пройдет тело за t секунд.*/ #include float а, v, t, s ; main() { a=20.2; v=50.3; t=65; s=(v*t)+(a*t*t/2); printf("\n Путь s=%f M",S); } /* Задача № 3 (пример на использование операций =, / и вычисления степени). Вычислите среднее арифметическое и среднее геометрическое трех чисел а,b,с.*/ #include #include float a=b=c=2005.1; float x,у; main () { x=(a+b+c)/3; y=pow( (a*b*c),(1/3)); /*функция pow берется из библиотеки math.h*/ printf("\n среднее арифметическое x=%f",x); printf("\n среднее геометрическое y=%f",y); } /* Задача № 4 (пример на использование операций =,*, / и функции hypot. Даны катеты прямоугольного треугольника. Найти его гипотенузу и площадь.*/ #include #include float x=y=50.7; float z,s; main() { z=hypot(х, у) ;/* вычисление гипотенузы*/ s=x*y/2; printf("\n гипотенуза=%f см",z); printf("\n Плoщaдь=%f кв см",s); }

    Основные операции

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

    Перечень операций языка Си

    Рассмотрим множество операций языка Си. Описанные операции входят в это множество. Язык Си отличается их большим разнообразием. В этом разделе будет обсуждаться их семантика. Каждая операция характеризуется уровнем приоритета и порядком, в котором эти операции выполняются - слева направо или справа налево. Если все операции выражения имеют один и тот же уровень приоритета, то значение выражения вычисляется слева направо в соответствии с порядком выполнения операций. Все операции с одним и тем же уровнем приоритета имеют один и тот же порядок. Однако если в выражении имеются операции с различными уровнями приоритета, то сначала выполняются операции с наивысшим уровнем приоритета, затем - следующего за ним приоритета и так далее в порядке убывания приоритета. Операции одного уровня приоритета выполняются в последовательности, указанной их порядком.

    Каждому аргументу из списка, следующего

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

    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 #include float km=10; int d; main() { clrscr(); /* чистка экрана, функция берется из головного файла conio.h*/ d=1; /* первый день, за который велосипедист проехал 10 км.*/ while(km<20) /* пока выполнено условие цикла, подсчитываются километры и дни*/ { km+=(km*0.5); d++; } printf("велосипедист был в пути %d дней",d); getch(); }
    on_load_lecture()
    Оператор цикла while
    Оператор цикла while
    Дальше "
    Оператор цикла while
    Если Вы заметили ошибку - сообщите нам.
    Оператор цикла while
    Страницы:
    1
    |
    2
    |
    3
    |
    4
    |
    вопросы | "
    |
    учебники
    |
    для печати и PDA
    Оператор цикла while
    Оператор цикла while
    Оператор цикла while

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +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( ) следующие:
  • Отсутствует спецификация %g.
  • Спецификации %f и %e эквивалентны. Обе спецификации допускают наличие или отсутствие знака строки цифр с десятичной точкой или без нее и поля показателя степени.
  • Для чтения целых чисел типа short применяется спецификация %h.

  • Функция 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 в зависимости от того, какое подвыражение второй операции будет вычислено первым. Таким образом, с использованием разных компиляторов можно получить различные результаты.

    Арифметические преобразования

    Арифметические операторы языка Си преобразуют операнды к соответствующим типам автоматически, если операнды не имели таких типов с самого начала. Схема преобразования, используемая этими операторами, называется обычные арифметические преобразования; эта схема может быть описана следующими правилами:
  • Преобразовать операнды типов char и short int к типу int; преобразовать операнды типа float к типу double.
  • Если хотя бы один из операндов имеет тип double, то и другой операнд преобразуется к типу double (если он другого типа); результат имеет тип double.
  • Если хотя бы один операнд имеет тип long, то и другой операнд преобразуется к типу long (если он другого типа); результат имеет тип long.
  • Если хотя бы один из операндов имеет тип unsigned, то и другой операнд преобразуется к типу unsigned (если его тип не unsigned); результат имеет тип unsigned.
  • Если ни один из случаев 1-4 не имеет места, то оба операнда должны иметь тип int; такой же тип будет и у результата.


  • Явные преобразования типов

    Выражения могут быть преобразованы из одного типа в другой явным указанием. Выражение E может быть явно преобразовано к типу имя-типа с помощью записи вида
    (имя - типа) Е
    где имя типа представляется в форме
    указатель-типа абстрактный-описатель
    Абстрактный описатель аналогичен описателю, за исключением того, что он не содержит определяемого или описываемого идентификатора. Смысл слов имя-типа, представляемого в форме
    Т абстрактный описатель
    где Т является указателем типа, может быть определен одним из таких способов:
  • форма абстрактного описателя - смысл слов "Т абстрактный описатель";
  • пустой (абстрактный описатель) - абстрактный описатель типа Т;
  • *(абстрактный описатель) - указатель на тип Т;
  • абстрактный описатель ( ) - функция, возвращающая значение типа Т;
  • абстрактный описатель [n] - массив с n элементами типа Т, n - выражение с постоянным значением;

  • Приведем примеры явного преобразования. Предположим, что даны следующие определения и описания:
    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::Интернет-Университет Информационных Технологий - дистанционное образование


    Преобразование типов

    В операторах и выражениях должны использоваться переменные и константы только одного типа. Если все же мы смешиваем типы в одном выражении, то компилятор с языка Си не считает программу неправильной, как это произошло бы при программировании на языке Паскаль. Вместо этого компилятор использует набор правил для автоматического преобразования типов. Это очень удобно, но может оказаться и опасным, особенно если мы допустили смешение типов нечаянно. Приведем
    несколько основных правил, касающихся преобразования типов:
  • Если операция выполняется над данными двух различных типов, обе величины приводятся к высшему из двух типов. Этот процесс называется повышением типа.
  • Последовательность имен типов, упорядоченных от высшего типа к низшему, выглядит так: double, float, long, int, short, char. Применение ключевого слова unsigned повышает ранг соответствующего типа данных со знаком.
  • В операторе присваивания конечный результат вычисления выражения в правой части приводится к типу переменной, которой должно быть присвоено это значение. Данный процесс может привести к повышению типа, как описано выше, или к понижению, при котором величина приводится к типу данных, имеющему более низкий приоритет.

  • Повышение типа обычно происходит гладко, в то время как понижение может привести к затруднениям. Причина этого проста: все число целиком может не поместиться в элементе данных низшего типа. Переменная типа 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, если это диктуется соответствующим оператором описания.

    Синтаксис типов

    Можно отметить, что синтаксис типов в языке Си нерегулярен и беспорядочен, о чем свидетельствуют:
  • Трудность создания форматеров.
  • Большое число ошибок, допускаемых даже опытными программистами.
  • Трудность создания синтаксических анализаторов для трансляторов с языка Си - не существует двух трансляторов с языка Си, синтаксис входного языка для которых полностью совпадал бы.
  • Отсутствие формального описания синтаксиса языка Си - даже книга (Kernighan, B.W., and D.M. Ritchie, The C Programming Language, prentice-Hall, Engleewood Cliffs, NJ (1978)) может служить лишь черновым описанием такового. Сказанное касается в полной мере и синтаксиса типов. Имеется три случая, когда необходимо использовать типы:
  • описания, связывающие тип с именем, как например, в случае описания (глобальных) переменных и формальных параметров;
  • описания, связывающие значения (и тип) с именем, как, например, описания программ;
  • приведения, обеспечивающие, например, возможность трактовки символов как целых.

  • 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 #define STOP '!' /*дает символу '!' символическое имя */ main( ) { char ch; ch=getchar( ); /***9***/ while(ch != STOP) { /***10***/ putchar( ch); /***11***/ ch=getchar( ); /***12***/ } }

    В данном примере при первом прохождении тела цикла функция putchar( ) получает значение своего аргумента в результате выполнения оператора, расположенного в строке 9. В дальнейшем, вплоть до завершения работы цикла, значением этого аргумента является символ, передаваемый программе функцией getchar, расположенной в строке 12. Цикл while будет осуществлять чтение и печать символов до тех пор, пока не поступит признак STOP.

    !Программа, приведенная ниже, делает то же самое, но стиль ее написания лучше отвечает духу языка Си:

    /* ввод-вывод */ #include #define STOP '!' main( ) { char ch; while ((ch=getchar( )) != STOP) /***8***/ putchar(ch); }

    Одна строка 8 этой программы заменяет строки 9, 10, 12 предыдущей программы.

    Чтение файла

    Если нам нужно читать большие порции данных, например из файла, каким должен быть признак STOP? Это должен быть такой символ, который обычно не используется в тексте и, следовательно, не приводит к ситуации, когда он случайно встретится при вводе, и работа программы будет остановлена раньше, чем бы мы хотели. Файлом можно назвать участок памяти, в который помещена некоторая информация. Обычно файл хранится в некоторой долговременной памяти, например на гибких или жестких дисках или на магнитной ленте. Чтобы отмечать, где кончается один файл и начинается другой, полезно иметь специальный символ, указывающий на конец файла, чтобы отмечать конец файла и начинать другой. Это должен быть символ, который не может появиться где-то в середине файла. Решением указанной проблемы служит введение специального признака, называемого "End-of-File", конец файла, или EOF. Выбор конкретного признака EOF зависит от типа системы. Он может состоять даже из нескольких символов. Обычно определение EOF содержится в файле . Общеупотребительным является определение
    #define EOF (-1)
    Пример:
    /* ввод-вывод_ф */ #include main( ) { int ch; while ((ch = getchar( )) != EOF) putchar(ch); }
    Это надо помнить:
  • Не нужно самим определять признак EOF. Он описан в файле .
  • Мы можем не интересоваться фактическим значением символа EOF, поскольку директива #define, имеющаяся в файле , позволяет нам использовать его символическое представление.
  • Мы изменили в нашей программе тип переменной ch с char на int. Это мы сделали, потому что значением переменных типа char является целое без знака в диапазоне от 0 до 255, а признак EOF может иметь числовое значение -1. Эта величина недопустима для переменной типа char. Функция getchar( ) фактически возвращает значение типа int, поэтому она в состоянии прочесть символ EOF.
  • Переменная ch целого типа никак не может повлиять на работу функции putchar( ). Она просто выводит на печать символьный эквивалент значения аргумента.
  • При работе с данной программой, когда символы вводятся с клавиатуры, необходимо уметь вводить признак EOF. В большинстве реализаций операционной системы UNIX, например, ввод [CTRL/d] (нажать на клавишу [d], держа нажатой клавишу [CTRL] интерпретируется как признак EOF. Во многих микрокомпьютерах для той же цели используется знак [CTRL/z].


  • Пусть мы ввели фразу с клавиатуры. Приведем результат работы программы "ввод-вывод_ф" в системе, с буферизованным вводом:

    Спрос на высокопрофессиональных ИТ-специалистов Спрос на высокопрофессиональных ИТ-специалистов растет как со стороны государственных, так и частных компаний растет как со стороны государственных, так и частных компаний [CTRL/z]

    Каждый раз при нажатии клавиши Enter производится обработка символов, попавших в буфер, и копия строки выводится на печать. Это продолжается до тех пор, пока мы не введем признак EOF. Программа "ввод-вывод_ф" осуществляет вывод на экран символов независимо от того, откуда они поступают. Наша программа могла бы просматривать содержимое файлов, создавать новые файлы и получать копии файлов. Решение этих проблем - в управлении вводом и выводом.

    Чтение одной строки

    Усложним пример ввода-вывода:
    /* подсчет символов */ #include #define STOP '!' main( ) { char ch; /*инициализация счетчика символов 0 */ int count = 0; while ((ch=getchar( )) != STOP) { putchar(ch); count++; /* прибавить 1 к счетчику */ } printf("\n Всего было прочитано %d символа.\n", count); }
    Если мы хотим просто подсчитать число введенных символов без отображения их на экране, функцию 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? Все отличия можно разделить на две группы:
  • В других операционных системах реализована операция переключения.
  • Компиляторы с языка Си предоставляют возможность использовать операцию переключения.

  • У нас нет возможности рассмотреть все компиляторы с языка Си. Однако в пяти из шести версий компилятора, предназначенных для микрокомпьютеров, для указания операции переключения используются символы < >. Операция переключения отличается от аналогичной операции в двух аспектах:
  • Указанная операция выполняется при работе программ, написанных только на Си, в то время как в OC UNIX она может использоваться при работе любой программы.
  • Между именем файла с откомпилированной программой и знаком операции должен быть один пробел, а между знаком операции и именем файла пробел должен отсутствовать. Например:
  • get_put on_load_lecture()
    Операционные системы, отличные от OC UNIX
    Операционные системы, отличные от OC UNIX
    Перейти к вопросам "
    Операционные системы, отличные от OC UNIX
    Если Вы заметили ошибку - сообщите нам.
    Операционные системы, отличные от OC UNIX
    Страницы:
    " |
    1
    |
    2
    |
    3
    |
    вопросы | "
    |
    учебники
    |
    для печати и PDA
    Операционные системы, отличные от OC UNIX
    Операционные системы, отличные от OC UNIX
    Операционные системы, отличные от OC UNIX

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Переключение и работа с файлами

    Понятие ввода-вывода включает в себя функции, данные и устройства. Рассмотрим, например, программу "ввод-вывод_ф". В ней используется функция 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 main( ) { char ch; ch=getchar( ); /***1***/ putchar(ch); /***2***/ }
    Для большинства систем спецификации функции getchar и putchar содержатся в системном файле stdio.h, поэтому мы указали данный файл в программе. Функция getchar( ) аргументов не имеет, т.е. при ее вызове в круглые скобки не помещается никакая величина. Она просто получает очередной поступающий символ, и сама возвращает его значение выполняемой программе. Оператор, приведенный в строке 1, присваивает значение функции getchar( ) переменной ch. Функция putchar( ) имеет один аргумент. При ее вызове необходимо в скобках указать символ, который требуется вывести на печать. Аргументом может быть одиночный символ (включая знаки, представляемые управляющими последовательностями), переменная или функция, значением которой является одиночный символ. Правильным обращением к функции putchar( ) является указание любого из этих аргументов при ее вызове:
    putchar('D'); putchar('\n'); putchar('\007'); putchar(ch); /* переменная типа char */ putchar(getchar( ));
    Модифицируем нашу программу:
    #include main( ) { putchar(getchar( )); }
    Такая запись очень компактна и не требует введения вспомогательных переменных. В результате компиляции такая программа оказывается более эффективной.

    Что такое истина

    Мы ответим на этот вопрос, как он решается в языке Си. В Си выражение всегда имеет значение. Это утверждение остается верным даже для условных выражений, как показывает пример, приведенный ниже. В нем определяются значения двух условных выражений, одно из которых оказывается истинным, а второе - ложным:
    /* истина и ложь*/ 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
    Логические операции
    Логические операции
    Логические операции

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Множественный выбор

    Когда в программе нужно осуществить выбор одного из нескольких вариантов, мы можем сделать это, используя конструкцию 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); }
    где
  • e - целое выражение (или выражение, которое может быть преобразовано в целое выражение);
  • ci - целое выражение с постоянным значением (или выражение, которое может быть преобразовано к такому выражению);
  • si обозначает операторы, число которых может быть больше или равно нулю.

  • Метки ci, обозначающие альтернативы case, должны быть уникальными; двух одинаковых меток быть не может. Только одна альтернатива может получить префикс default.
    Результатом выполнения оператора switch является выбор альтернативы с меткой ci, которая равна значению выражения переключателя e; в этом случае выполняются операторы si. В случае, если выражение переключателя не равно ни одному из выражений альтернатив case, то выполняются операторы s(k+1); при отсутствии альтернативы default не выполняется ни одна из альтернатив оператора switch.
    Пример:
    /* Реализация работы калькулятора. Сначала задается число - сколько раз нужно подсчитать. Вводятся левый операнд, операция, правый операнд. Оператор выбора определяет, какой оператор должен работать. Результат выводится на экран. */ #include #include #include main() { float a,b; char opr; float result=0; int I,i=0; clrscr(); printf("Сколько раз будете считать?:\n"); scanf("%I",&I); printf("Введите операнд, операцию, операнд:\n"); while(i

    Операции отношения

    Операции отношения используются для сравнений. Мы уже обсуждали их, но здесь мы остановимся на их обсуждении подробнее.
    Операциясмысл
    <меньше
    <=меньше или равно
    ==равно
    >=больше или равно
    >больше
    !=не равно

    Этот список хорошо соответствует возможным числовым соотношениям.
    !Главное предостережение, которое мы хотим сделать, состоит в том, чтобы не использовать знак = вместо ==. С помощью операции присваивания некоторое значение присваивается переменной слева от знака равенства. В то же время с помощью операции отношения "равенство" проверяется: равно ли выражение, стоящее слева от знака, выражению справа от него. Эта операция не изменяет значения переменной в левой части, если она там присутствует. При программировании требуется аккуратность, потому что в ряде случаев компилятор не сможет обнаружить ошибки, связанные с неправильным использованием знаков этих отношений, что приведет к результатам, отличным от тех, которые вы должны были бы получить.

    Операции отношения применяются при формировании условных выражений, используемых в операторе if и while. Указанные операторы проверяют, истинно или ложно данное выражение.
    Пример:
    #include main( ) { int mark; mark = getchar( ); if(mark == 4) printf("Оценка 4.\n"); else if (mark > 4) printf("Оценка больше 4!\n"); else /* случай, когда оценка меньше 4 */ { mark++; printf("Теперь оценка на 1 больше.\n"); } }
    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::Интернет-Университет Информационных Технологий - дистанционное образование


    Операция условия: ?:

    В языке Си имеется короткий способ записи одного из видов оператора 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-else. Однако условные выражения более компактны, и их применение обычно приводит к получению более компактного машинного кода.


    Оператор if

    Подсчитаем число строк в файле. Это можно сделать путем счета числа символов "новая строка" в файле.
    Пример:
    /*подсчет строк */ #include main( ) { int ch; int line_count=0; while((ch = getchar( )) != EOF) if(ch == '\n') line_count++; printf("Мы насчитали %d строк. \n", line_count); }
    Оператор if служит указанием компьютеру увеличить значение переменной line_count на 1, если только что прочитанный символ, содержимое переменной ch, представляет собой символ "новая строка". Что происходит в случае, когда значение переменной ch не является символом "новая строка"? Тогда в цикле while производится чтение следующего символа. Оператор if считается одиночным оператором, начинающимся от ключевого слова if и завершающимся символом "точка с запятой". Модифицируем программу. Подсчитаем одновременно число символов и строк в файле:
    /* подсчет числа строк и символов */ #include main( ) { int ch = 0; int line_count = 0; int char_count = 0; while((ch = getchar( )) != EOF) { char_count++; if(ch == '\n') line_count++; } printf("Мы насчитали %d символов и %d строк.\n", char_count, line_count); }
    Теперь в цикл 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 main( ) { int i; i=getchar( ); if(i > 5) /* одиночный оператор*/ printf("Значение больше 5.\n"); if(i < 5) { /* составной оператор*/ printf("Значение\n"); printf(" меньше 5.\n"); } }
    Простая форма оператора if позволяет выбрать оператор, возможно, составной, или пропустить его. Язык Си предоставляет также возможность выбрать любой из двух операторов путем использования конструкции if-else.
    Напишем программу, заменяющую каждый символ из таблицы ASCII на следующий символ, кроме символа "новая строка".
    Пример:
    /* код_1*/ #include main( ) { char ch; while ((ch=getch( )) != EOF) { /* оставить символ "новая строка неизменным" */ if (ch == '\n') putchar(ch); else /* заменить остальные символы*/ putchar(ch + 1); } }
    Общий вид оператора 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 в конструкциях с вложенными операторами if. При необходимости можно воспользоваться пустым оператором. Например, вторая интерпретация вышеприведенного оператора if может быть записана как
    if(e1) if(e2) оператор1; else ; /* точка с запятой здесь обозначает пустой оператор*/ else оператор2;

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

    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 while - это конструкция с условием на выходе.
    Использовать цикл do while лучше всего в тех случаях, когда должна быть выполнена по крайней мере одна итерация. К примеру, мы могли бы применить цикл do while в нашей программе угадывания числа. На псевдокоде алгоритм работы программы можно тогда записать следующим образом:
    do { выдвиньте предположение получите ответ вида y,n } while(ответ не совпадает с y);
    !
    Вы должны избегать использование цикла do while, структура которого аналогична представленной ниже операции: cпросите пользователя, хочет ли он продолжать
    do оператор while (ответ будет да)
    В данном случае, после того как пользователь ответит "нет", "оператор" будет выполнен, поскольку проверка осуществляется слишком поздно.

    Пример:
    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 main( ) { int i = 1; char res; printf("Задумайте целое число от 1 до 100. Компьютер попытается угадать его. \n"); printf ("Отвечайте y если догадка правильна и"); printf("\n n, если программа ошибается \n"); printf("И так, ваше число %d?\n",i); /*получение ответа */ while((res = getchar( )) !='y') if(res !='\n') /* пропуск символа новая строка */ printf("Ну тогда оно равно %d\n" ,++i); printf("Число угадано!\n"); }
    !Это довольно простая программа. Она написана правильно и решает поставленную задачу, но делает это крайне неэффективно. Данный пример показывает, что правильность написания - не единственный критерий, по которому необходимо оценивать программу. При этом очень важна ее эффективность!

    В наших примерах до сих пор использовались условные выражения, но вообще говоря, это могут быть выражения произвольного типа. В качестве оператора можно использовать простой оператор с символом "точка с запятой" в конце или составной оператор, заключенный в фигурные скобки. Если выражение истинно (или в общем случае равно единице), то оператор, входящий в цикл while, выполняется один раз, а затем выражение проверяется снова. Эта последовательность действий, состоящая из проверки и выполнения оператора, периодически повторяется до тех пор, пока выражение не станет ложным (или в общем случае равным нулю). Каждый такой шаг называется итерация. Данная структура аналогична структуре оператора if. Основное отличие заключается в том, что в операторе if проверка условия и (возможное) выполнение оператора осуществляется только один раз, а в цикле while эти действия производятся, вообще говоря, неоднократно.
    !При построении цикла while вы должны включить в него какие-нибудь конструкции, изменяющие величину проверяемого выражения так, чтобы в конце концов оно стало ложным. В противном случае выполнение цикла никогда не завершится.


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

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

    Оператор while определяет операции, которые циклически выполняются до тех пор, пока проверяемое выражение не станет ложным, или равным нулю. Оператор while - это цикл с предусловием. Решение, выполнить ли в очередной раз тело цикла, принимается перед началом его прохождения. Поэтому вполне возможно, что тело цикла не будет выполнено ни разу. Оператор, образующий тело цикла, может быть либо простым, либо составным. Форма записи:

    while (выражение) оператор

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

    Цикл со счетчиком

    Оператор цикла for
    for(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(;;) { ... }

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

  • Первое выражение не обязательно должно инициализировать переменную. Вместо этого, например, там мог бы стоять оператор printf( ). Необходимо помнить только, что первое выражение вычисляется только один раз перед тем, как остальные части цикла начнут выполняться.

    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! С тех пор как Дейкстра опубликовал свой знаменитый труд "Вредность оператора GOTO", специалисты бились над методами структурного программирования. Полное описание структурного программирования выходит за рамки этого курса лекций, однако отметим, что нужно очень осторожно использовать операторы: goto, break, continue, return.

    Если без операторов goto, break, continue, return никак не обойтись, то при использовании goto переходите вперед по коду, а не назад.
    Оператор break лучше не использовать для преждевременного выхода из цикла, его полезно использовать внутри оператора switch.
    Оператор continue нежелательно использовать для модификации логики циклов.
    Почему нежелательно использовать функции со многими операторами return. Один из принципов структурного программирования состоит в том, что программа должна иметь одну точку входа и одну точку выхода. Функции со многими операторами return более сложны для чтения, чем те, которые имеют лишь один оператор return в конце тела функции.
    on_load_lecture()
    Оператор goto
    Оператор goto
    Перейти к вопросам "
    Оператор goto
    Если Вы заметили ошибку - сообщите нам.
    Оператор goto
    Страницы:
    " |
    1
    |
    2
    |
    3
    |
    4
    |
    вопросы | "
    |
    учебники
    |
    для печати и PDA
    Оператор goto
    Оператор goto
    Оператор goto

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Структурное программирование

    Методология структурного программирования основана на предположении, что логичность и понятность программы обеспечивает надежность, облегчает модификацию и ускоряет разработку программы.
    Характерными чертами структурного программирования являются:
  • отказ от неструктурных передач управления;
  • ограниченное использование глобальных переменных;
  • модульность.


  • Аргументы функции

    Формальный аргумент - переменная в вызываемой программе, а фактический аргумент - конкретное значение, присвоенное этой переменной вызывающей программой. Фактический аргумент может быть константой, переменной или более сложным выражением. Независимо от типа фактического аргумента он вначале вычисляется, а затем его величина передается функции. Фактический аргумент - это конкретное значение, которое присваивается переменной, называемой формальным аргументом.
    Если для связи с некоторой функцией требуется более одного аргумента, то наряду с именем функции можно задать список аргументов, разделенных запятыми. Например:
    print_num(i,j) int i,j; { printf("значение i=%d. Значение j=%d.", i,j); }
    Обращение в программе к данной функции будет таковым:
    print_num(6,19);
    Особое внимание нужно уделить правилам передачи аргументов при обращении к функциям. Синтаксис языка Си предусматривает только один способ передачи аргументов - передачу по значениям. Это означает, что формальные параметры (аргументы) функции локализованы в ней, то есть недоступны вне определения функции и никакие операции над формальными параметрами в теле функции не изменяют значения фактических параметров. Передача параметров по значению предусматривает следующие действия:
  • При компиляции функции выделяются участки памяти для формальных параметров. Формальные параметры оказываются внутренними объектами функции. При этом для параметров типа float формируются объекты типа double. Для параметров типа char, short int создаются объекты типа int. Если параметром является массив, то формируется указатель на начало этого массива и он служит представлением массива-параметра в теле функции (об указателе массивов описано в 12 лекции данного курса).
  • Вычисляются значения выражений, использованных в качестве фактических параметров при вызове функции.
  • Значения выражений-фактических параметров заносятся в участки памяти, выделенные для формальных параметров функции. При этом float преобразуется в double, a char, short int - в тип int.
  • В теле функции выполняется обработка с использованием значений внутренних объектов-параметров, и результат передается в точку вызова функции как возвращаемое ею значение.
  • Никакого влияния на фактические параметры функция не оказывает.


  • Функции с переменным количеством аргументов

    В языке Си допустимы функции, количество аргументов у которых при компиляции функции не фиксировано. Количество и тип аргументов становится известным только в момент вызова функции, когда явно задан список фактических аргументов (параметров). При определении и описании таких функций, имеющих списки параметров неопределенной длины. Спецификация формальных параметров заканчивается запятой и многоточием:
    тип имя(спецификация-явных-параметров,...);
    Здесь тип - тип возвращаемого функцией значения; имя - имя функции.
    Спецификация явных параметров - список спецификации параметров, количество и типы которых фиксированы и известны в момент компиляции. Эти параметры обязательны. Каждая функция с переменным количеством параметров должна иметь хотя бы один обязательный параметр. После списка явных (обязательных) параметров ставится запятая, а затем - многоточие. Компилятор знает, что дальнейший контроль соответствия количества и типов параметров при обработке вызова функции проводить не нужно. Чтобы функция с переменным количеством параметров могла воспринимать параметры различных типов, необходимо в качестве исходных данных каким-то образом передавать ей информацию о типах параметров.
    Пример:
    #include /* Функция суммирует значения своих параметров */ long summa(int m,...) /*m - число параметров*/ { int *p=&m; /*настроили указатель на параметр m*/ long t=0; for(;m;m--) t+=*(++p); return t; } void main() { printf("\n summa(2,6,4)=%d",summa(2,6,4)); printf("\n summa(6,1,2,3,4,5,6)=%d", summa(6,1,2,3,4,5,6)); }
    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::Интернет-Университет Информационных Технологий - дистанционное образование


    Локальные переменные

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

    Нахождение адресов

    В результате выполнения операции & определяется адрес ячейки памяти, которая соответствует переменной. Если 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
    Описание указателей
    Описание указателей
    Описание указателей

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Подведем итоги по указателям

    Когда за знаком & следует имя переменной, результатом операции является адрес указанной переменной.
    Когда за знаком * следует указатель на переменную, результатом операции является величина, помещенная в ячейку с указанным адресом.
    Пример:
    age = 105; ptr =&age;/*указатель на age*/ val= *ptr;
    Результатом выполнения этого фрагмента является присваивание значения 105 переменной val.
    !Пользуйтесь первой формой, если входное значение необходимо функции для некоторых вычислений или действий, и второй формой, если функция должна будет изменять значения переменных в вызывающей программе. Программисты, работающие на языке Паскаль, могут заметить, что первая форма вызова аналогична обращению с параметром-значением, а вторая-с параметром-переменной.

    Типичные определения функции имеет следующий вид:
    имя (список аргументов) описание аргументов тело функции
    Наличие списка аргументов и описаний не обязательны. Переменные, отличные от аргументов, описываются внутри тела, которое заключается в фигурные скобки.
    Аргументы используются для передачи значений из вызывающей программы в функцию. Использование ключевого слова return позволяет передавать в вызывающую программу одно значение из вызываемой функции. Обычно выполнение функции не оказывает никакого влияния на значения переменных вызывающей программы. Чтобы иметь возможность непосредственно изменять значения переменных вызывающей программы, необходимо использовать указатели в качестве аргументов. Это может оказаться необходимым в случае, если в вызывающую программу требуется передать более чем одно значение.
    Функции должны иметь тот же тип, что и значения, которые они возвращают в качестве результатов. По умолчанию предполагается, что функции имеют тип int. Если функция имеет другой тип, он должен быть указан и в вызывающей программе, и в самом определении функции.

    Создание и использование функций

    Принципы программирования на языке Си основаны на понятии функции. Мы уже рассмотрели несколько функций: printf( ), scanf( ), getchar( ), putchar( ). Эти функции являются системными, однако мы создали и несколько своих собственных функций под общим именем main( ). Выполнение программы всегда начинается с команд, содержащихся в функции main( ), затем последняя вызывает другие функции. Рассмотрим вопрос, как создавать свои собственные функции и делать их доступными для функции main( ), а также для других функций.
    Функция - это самостоятельная единица программы, спроектированная для реализации конкретной задачи. Вызов функций приводит к выполнению некоторых действий. Например, при обращении к функции printf( ) осуществляется вывод данных на экран. В общем, функции могут выполнять действия и получать значения величин, используемых в программе.
    Почему мы пользуемся функциями? Во-первых, они избавляют нас от повторного программирования. Если конкретную задачу в программе необходимо выполнить несколько раз, мы напишем соответствующую функцию только один раз, а затем будем вызывать ее всегда, когда требуется. Во-вторых, мы можем применять одну функцию, например putchar( ), в различных программах.
    !Если некоторая задача выполняется только в одной программе, лучше оформить ее решение в виде функции, т.к. функции повышают уровень модульности программы и, следовательно, облегчают ее чтение, внесение изменений и коррекцию ошибок. Дополнительное преимущество указанного подхода заключается в том, что если мы создадим функции общего вида, то их можно будет использовать и в других программах.

    Многие программисты думают о функции, как о "черном ящике". Они задают ее через поступающую информацию и полученные результаты. Все, что происходит внутри черного ящика, их не касается. Что нам требуется знать о функциях? Нужно знать, как их можно определять, как к ним обращаться и как устанавливать связи между функцией и программой, ее вызывающей.
    Абстракция управления в языке Си обеспечивается с помощью функций. Все функции могут быть рекурсивными. В языке Си отсутствуют подпрограммы (процедуры), однако возврат функцией значения в вызывающую программу не обязателен. Следовательно, функции могут быть разделены на две категории - функции, возвращающие значения, и функции, не возвращающие значения в вызывающую программу (подпрограммы) .
    Определение функций, возвращающих значение, имеет следующий формат:

    [static] тип-результата имя-функции ( формальные аргументы) описание формальных параметров { тело функции }

    где имя функции - правильный идентификатор, а тело функции имеет вид

    определения и описания операторы
    !Все что взято в квадратные скобки может и не быть static - мы рассмотрим в лекции 10. Указание типа-результата функции в языке Си не является обязательным. Если тип результата не указан, то предполагается, что результат имеет тип int. Поскольку указание типа функции приводит к большей ясности и легкости чтения программы, а также упрощает нахождение в ней ошибок, тип функции всегда должен быть указан явно.
    В качестве результата функция не может возвращать массив (см. лекцию 13) или функцию, но может возвращать указатель на массив или функцию. Выполнение функции, возвращающей значения, обязано завершиться оператором return вида

    return e;

    который обеспечивает выдачу результата e. Функция, возвращающая значение, может содержать более одного оператора return.

    Определения функции, не возвращающей значения, имеют следующий формат:

    [static] void имя-функции(формальные аргументы) описание формальных параметров { тело функции }

    Выполнение такой функции завершается, если выполнено ее тело или оператор return вида

    return;

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

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

    [static или extern] тип-результата имя-функции( ); [static или extern] void имя-функции( );

    Если в описании не указан класс памяти (см. лекцию 10) , то по умолчанию, предполагается extern.

    !

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

    #define void int

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

    Указатели, первое знакомство

    Указатель - некоторое символическое представление адреса. В описанном примере &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
    Автоматические переменные
    Автоматические переменные
    Автоматические переменные

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Классы памяти и область действия

    Классы памяти языка Си дают возможность определить, с какими функциями связаны какие переменные, и как долго переменная сохраняется в программе. Мы уже упоминали, что локальные переменные известны только функциям, содержащим их. В языке Си предполагается также, что о глобальных переменных знают сразу несколько функций:
    /* глобальная переменная 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 указывает на то, что этот идентификатор определен в другом файле, и здесь ему память не выделяется, а его описание дано лишь для проверки типа и для генерации кода.
    !При описании внешних массивов максимальное значение первого индекса массива указывать нет необходимости. Оно будет получено из соответствующего определения, что создает дополнительные удобства для пользователя.


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

    !Явное указание памяти 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
    Номер строки и имя файла
    Номер строки и имя файла
    Номер строки и имя файла

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Общие сведения

    В интегрированную среду подготовки программ на Си или в компилятор языка как обязательный компонент входит препроцессор. Назначение препроцессора - обработка исходного текста программы до ее компиляции. Препроцессорная обработка включает несколько стадий, выполняемых последовательно. Конкретная реализация может объединять несколько стадий, но результат должен быть таким, как если бы они выполнялись в следующем порядке:
  • Все системно-зависимые обозначения перекодируются в стандартные коды.
  • Каждая пара из символов '\' и "конец строки" вместе с пробелами между ними убираются, и тем самым следующая строка исходного текста присоединяется к строке, в которой находилась эта пара символов.
  • В тексте распознаются директивы и лексемы препроцессора, а каждый комментарий заменяется одним символом пустого промежутка.
  • Выполняются директивы препроцессора и производятся макроподстановки.
  • Эскейп-последовательности в символьных константах и символьных строках заменяются на их эквиваленты.
  • Смежные символьные строки конкатинируются, то есть соединяются в одну строку.
  • Каждая препроцессорная лексема преобразуется в текст на языке Си.

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


  • Прагмы

    #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()
    Включение файла: #include
    Включение файла: #include
    Дальше "
    Включение файла: #include
    Если Вы заметили ошибку - сообщите нам.
    Включение файла: #include
    Страницы:
    " |
    1
    |
    2
    |
    3
    |
    4
    |
    5
    |
    вопросы | "
    |
    учебники
    |
    для печати и PDA
    Включение файла: #include
    Включение файла: #include
    Включение файла: #include

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Встроенные макроимена

    Существуют встроенные (заранее определенные) макроимена, доступные препроцессору во время обработки. Они позволяют получить следующую информацию:
    _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
    Встроенные макроимена
    Встроенные макроимена
    Встроенные макроимена

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Замена идентификаторов

    #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
    Динамические объекты
    Динамические объекты
    Динамические объекты

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Доступ к динамическим объектам

    Присваивание значения объекту, ссылка на который задана указателем 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
    Операции с указателями
    Операции с указателями
    Операции с указателями

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Создание динамических объектов

    По стандарту аргументы функций 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.
    !Нет ничего необычного не только в интерпретации переменных типа string, т.е. массивов знаков как указателей, но и в интерпретации строк, которые также могут рассматриваться двояко - как массивы и как указатели - и все в одной программе! Это особенно важно, когда строки передаются как аргументы функции. Вызывающая программа может рассматривать строку как массив знаков, а вызываемая функция может рассматривать ее как знаковый указатель. Если длина строки непостоянна, то использование знаковых указателей для строк имеет определенные преимущества. Хотя строки переменной длины могут быть также реализованы с использованием массивов, такая реализация оказывается слишком неэкономной с точки зрения использования памяти и налагает ограничения на максимальную длину строки. Например, для размещения строк разной длины может быть создан массив знаковых указателей. Альтернативное решение с использованием двумерного массива знаков в общем случае будет использовать память неэффективно, так как в этом случае потребовалось бы сделать число столбцов равным числу знаков в строке наибольшей возможной длины.


    Связь между указателями и массивами

    В языке Си массивы и указатели тесно связаны. Имя каждого массива может рассматриваться как указатель на первый элемент массива. Элемент массива 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
    Связь между указателями и массивами
    Связь между указателями и массивами
    Связь между указателями и массивами

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Указатели и массивы

    Допустимо бесконечно большое число различных типов указателей и массивов. Далее следуют типовые примеры.
    Указатель на основной тип:
    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
    Массивы символьных строк и их инициализация
    Массивы символьных строк и их инициализация
    Массивы символьных строк и их инициализация

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Обработка строк

    Для выполнения описанных в этом подразделе функций необходимо включить в программу файл string.h командой
    #include
    strcat - сцепить две строки.
    Определение: char *strcat(s1,s2) char *s1, *s2;
    Пример 1:
    /* сцепить две строки */ /* в головном файле conio.h содержится функция очистки экрана clrscr( ) */ #include #include #include int main(void) { clrscr(); char destination[25]; char *blank = " ", *c = "C++", *turbo = "Turbo"; strcpy(destination, turbo); strcat(destination, blank); strcat(destination, c); printf("%s\n", destination); getch(); return 0; }
    strncat - сцепить две строки, причем из второй строки копировать не более n символов.
    Определение: char *strncat(s1,s2,n) char *s1, *s2; int n;
    Пример 2:
    /* cцепить две строки, причем из второй строки копировать не более n символов */ #include #include #include int main(void) { clrscr(); char destination[25]; char *source = "structured "; strcpy(destination, "programming"); strncat(destination, source, 11); printf("%s\n", destination); getch(); return 0; }
    strcmp - сравнить две строки в лексикографическом порядке.
    Определение: int strcmp(s1,s2) char *s1, *s2;
    Пример 3:
    #include #include #include int main(void) { char *buf1 = "aaa", *buf2 = "bbb", *buf3 = "ccc"; int ptr; clrscr(); ptr = strcmp(buf2, buf1); if (ptr > 0) printf("buffer 2 is greater than buffer 1\n"); else printf("buffer 2 is less than buffer 1\n"); ptr = strcmp(buf2, buf3); if (ptr > 0) printf("buffer 2 is greater than buffer 3\n"); else printf("buffer 2 is less than buffer 3\n"); getch(); return 0; }

    Строковые константы

    Строковая константа представляется последовательностью символов кода 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
    Указатели и строки
    Указатели и строки
    Указатели и строки

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Ввод-вывод строк

    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
    Массив структур
    Массив структур
    Массив структур

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Объединения

    Объединение описывает переменную, которая может иметь любой тип из некоторого множества типов.
    Определение объединенного типа данных аналогично определению структурного типа данных:
    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, но со следующими тремя изменениями:
  • В отличие от #define спецификатор typedef дает символические имена, но ограничивается только типами данных.
  • Спецификатор typedef выполняется компилятором, а не препроцессором.
  • В своих пределах спецификатор 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
    Перечисления
    Перечисления
    Перечисления

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Переименование типов

    Формат
    typedef старый_тип новый_тип
    Примеры:
    typedef long large; /* определяется тип large, эквивалентный типу long */ typedef char *string; /* тип string, эквивалентен типу char* */
    Переименование типов используется для введения осмысленных или сокращенных имен типов, что повышает понятность программ, и для улучшения переносимости программ (имена одного типа данных могут различаться на разных ЭВМ).
    Пример:
    /* Реализован алгоритм, который позволяет определить строки матриц, состоящие из одинаковых целых, расположенных в различных столбцах. Используются двумерные массивы и структуры. Сначала выполняется сортировка строк по возрастанию. Отсортированные строки сравниваются и выводятся на экран номера одинаковых строк */ #include #include #include #include #define n 4 /*количество строк */ #define m 4 /*количество столбцов*/ typedef struct mas{int i,i1;} mas; int m1[n][m]; /*исходный массив*/ struct mas{int i,i1;}; mas a[n*2]; /*массив типа mas, где будут лежать одинаковые строки, a[1].i и a[1].i1 - одинаковые строки*/ void main() { clrscr(); int i,j; randomize(); for(i=0;i=min) { m1[i][p]=m1[i][s];m1[i][s]=min; } /* меняем местами s-й и p-й элемент,если s-й > p-го(минимального) */ } } printf("\n"); for(i=0;i

    Переменные структуры

    В языках, таких как Ада и Паскаль, имеется тип данных, называемых переменная запись, объекты которой содержат набор одних и тех же компонентов плюс компоненты, не являющиеся общими для всех остальных объектов. В языке Си также имеется тип данных, подобный переменной записи, называемой переменной структурой, которая может быть реализована с использованием комбинации структуры и объединения. В общем случае переменные структуры будут состоять из трех частей: набора общих компонентов, метки активного компонента и части с меняющимися компонентами. Общая форма переменной структуры имеет следующий вид:
    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 main( ) { FILE *fi; int age; fi=fopen("File","r"); /* считывание */ fscanf(fi,"%d",&age); /* fi указывает на File */ fclose(fi); fi=fopen("Data", "a"); /*дополнение*/ fprintf(fi,"Data is %d.\n",age); /*fi указывает на Data*/ fclose(fi); }
    В отличие от getc( ) и putc( ) эти функции получают указатель типа FILE в качестве первого аргумента.

    Функция calloc( )

    Другую возможность распределения памяти дает нам применение функции calloc( ).
    char *calloc( ); long *newmem; newmem=(long *) calloc(100,sizeof(long));
    Функция calloc( ) возвращает указатель на char. Нужно использовать оператор приведения типа, если вы хотите запомнить другой тип. calloc( ) имеет два аргумента, и оба они должны быть целыми без знака. Первый аргумент содержит количество требуемых ячеек памяти. Второй аргумент - размер каждой ячейки в байтах. Функция calloc( ) обнуляет содержимое всего блока. Ваша библиотека языка Си возможно представляет несколько других функций управления памятью, вы можете исследовать их самостоятельно!
    on_load_lecture()
    Функция calloc( )
    Функция calloc( )
    Перейти к вопросам "
    Функция calloc( )
    Если Вы заметили ошибку - сообщите нам.
    Функция calloc( )
    Страницы:
    " |
    1
    |
    2
    |
    3
    |
    4
    |
    вопросы | "
    |
    учебники
    |
    для печати и PDA
    Функция calloc( )
    Функция calloc( )
    Функция calloc( )

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Функция fgets( )

    Эта функция имеет три аргумента, в то время как gets( ) имеет лишь один. Пример ее использования:
    /* Программа считывает файл строка за строкой */ #include #define MAX 80 main( ) { FILE *f1; char *string[MAX] f1=fopen("File","r"); while (fgets(string,MAX,f1) != NULL) puts(string); }
    Мы расположили вводимую информацию в символьном массиве string. Первый из трех аргументов функции fgets( ) является указателем на местоположение считываемой строки. Второй аргумент содержит предельную длину считываемой строки. Функция прекращает работу после считывания символа новой строки или после считывания символов общим числом MAX-1, в зависимости от того, что произойдет раньше. В любом случае нуль-символ '\0' добавляется в самый конец строки. Третий аргумент указывает на файл, который будет читаться. Разница между gets( ) и fgets( ) заключается в том, что gets( ) заменяет символ новой строки на '\0', в то время как fgets( ) сохраняет символ новой строки. Подобно gets( ) функция fgets( ) возвращает значение NULL, если встречает символ EOF . Это позволяет нам проверить, достигли ли мы конца файла.

    Функция fputs( )

    Эта функция похожа на функцию puts( ). Оператор
    l=fputs("Строка", fi);Код Положение в файле
    0начало файла
    1текущая позиция
    2конец файла

    Передает строку "Строка" в файл, на который ссылается указатель fi типа FILE. Конечно, сначала нужно открыть файл при помощи функции fopen( ).
    l является целым числом, которое устанавливается в EOF, если fputs( ) встречает EOF или ошибку. Эта функция не ставит завершающий символ '\0' в конце копируемой строки. В отличии puts функция fputs( ) не добавляет символ новой строки в ее вывод.
    on_load_lecture()
    Функция fputs( )
    Функция fputs( )
    Дальше "
    Функция fputs( )
    Если Вы заметили ошибку - сообщите нам.
    Функция fputs( )
    Страницы:
    " |
    1
    |
    2
    |
    3
    |
    4
    |
    вопросы | "
    |
    учебники
    |
    для печати и PDA
    Функция fputs( )
    Функция fputs( )
    Функция fputs( )

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Функция fseek( )

    Функция fseek( ) позволяет нам обрабатывать файл подобно массиву и непосредственно достигать любого определенного байта в файле, открытом функцией fopen( ). fseek( ) имеет три аргумента и возвращает значение типа int.
    Покажем на примере работу fseek( ):
    /* использование fseek( ) для печати содержимого файла */ #include int main(int number, char *names[]) { FILE *fp; long set = 0L; if(number<2) puts("Введите имя файла в качестве аргумента."); else { if ((fp=fopen(names[1],"r")) == 0) printf("Нельзя открыть %s\n",names[1]); else { while(fseek(fp, set++,0) ==0) putchar(getc(fp)); fclose(fp); } } }
    Первый из трех аргументов функции 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 #include #include #define STOP "\n" /* сигнал прекращения ввода */ #define BLOCK 100 /*байты памяти */ #define LIM 40 /*предельная длина вводимой строки*/ #define MAX 50 /*максимальное число вводимых строк */ #define TIME 20000 /* большая задержка времени */ main ( ) { char store[BLOCK]; /* исходный блок памяти*/ char symph[LIM]; /* приемник вводимых строк*/ char *end; /* указывает на конец памяти */ char *starts[MAX]; /* указывает на начала строк*/ int index = 0; /*количество вводимых строк */ int count; /* счетчик*/ сhar malloc( ); / * распределитель памяти */ starts[0]=store; end=starts[0]+BLOCK-1; puts("Вводите строки по одной"); puts("для завершения ввода в начале строке нажимите клавишу [ввод]"); puts("Начинайте!."); while(indexend - starts[index]) { /* действия при недостатке памяти для запоминания вводимых данных*/ puts("подождите, программа попробует найти дополнительную память"); starts[index]=malloc(BLOCK); end=starts[index]+BLOCK- 1; for(count=0; count
    Давайте посмотрим, что делает функция malloc( ). Она берет аргумент в виде целого без знака, которое представляет количество требуемых байтов памяти. Так, malloc(BLOCK) требует 100 байт. Функция возвращает указатель на тип char в начало нового блока памяти. Мы использовали описание

    char *malloc( );

    чтобы предупредить компилятор, что malloc( ) возвращает указатель на тип char. Поэтому мы присвоили значение этого указателя элементу массива starts[index] при помощи оператора

    starts[index]=malloc(BLOCK);

    Предположим, что мы хотим работать с памятью типа int, а не char. Mожете и здесь использовать malloc( ). Вот как это делается:

    char malloc( ); /* по-прежнему описываем как указатель на char */ int *newmem; newmem = (int *)malloc(100); /* используем операцию приведения типа */

    Снова требуется 100 байт. Операция приведения типа преобразует значение, возвращенное указателем на тип char, в указатель на тип int. Если в системе int. занимает два байта памяти, это значит, что 100 байт можно использовать для запоминания 50 целых чисел.

    Открытие файла: fopen( )

    Функцией fopen( ) управляют три основных параметра. Первый - имя файла, который следует открыть. Он является и первым аргументом fopen( ). В нашем примере это "File". Второй параметр описывает, как должен использоваться файл:
    "r" - файл нужно считать,
    "w" - файл нужно записать,
    "a" - файл нужно дополнить.
    "w+" - новый текстовый файл открывается для записи и последующих многократных исправлений. Если файл уже существует, то предыдущее содержимое стирается. Последующие после открытия файла запись и чтение из него допустимы в любом месте файла, в том числе запись разрешена и в конце файла, т.е. файл может увеличиваться.
    "r+" - существующий текстовый файл открывается как для чтения, так и для записи в любом месте файла; однако в этом режиме невозможна запись в конец файла, то есть недопустимо увеличение размеров файла.
    "a+" - текстовый файл открывается или создается, если файла нет, и становится доступным для изменений, т.е. для записи и для чтения в любом месте; при этом в отличие от режима "w+"можно открыть существующий файл и не уничтожать его содержимое; в отличие от режима "r+" в режиме "a+" можно вести запись в конец файла, то есть увеличивать его размеры.
    Некоторые системы предоставляют еще дополнительные возможности, которые мы здесь не будем рассматривать. Используемые коды являются строками, а не символьными константами. При применении "r" открывается существующий файл. При двух других применениях тоже будет открываться существующий файл, но если такого файла нет, он будет создан. Если вы используете "w" для существующего файла, то старая версия его стирается, и ваша программа начинает записывать на чистое место. Третий параметр является указателем на файл. Это значение возвращается самой функцией:
    FILE *in; in=fopen("File","r");
    Теперь in является указателем на файл "File". С этого момента программа ссылается на файл при помощи указателя in, а не по имени File. (Файл stdio.h содержит строку

    Стандартные библиотечные функции

    Библиотека языка Си содержит множество функций и макроопределений. Библиотеки меняются от системы к системе, но есть ядро функций (стандартная библиотека).
    Эти функции используются для:
  • манипулирования данными, их преобразования и шифрования;
  • определения пользователями функций с переменным числом аргументов;
  • динамического управления памятью;
  • представления показаний системных часов в стандартных форматах даты и времени;
  • получения системной информации.

  • Главными преимуществами стандартных библиотечных функций Си являются мобильность и низкие затраты на сопровождение пользовательских приложений. Библиотечные функции не подвержены частым изменениям, поэтому программы, в которых они используются, легки в сопровождении. Некоторые из этих библиотечных функций соответствуют стандарту ANSI С - стандарту Си Американского национального института, благодаря чему они приемлемы для всех систем, соответствующих этому стандарту. Чтобы сократить затраты и время на разработку приложений, рекомендуется использовать библиотечные функции Си всякий раз, когда это оказывается возможным.
    Стандартные библиотечные функции Си объявляются в наборе файлов-заголовков, которые в UNIX-системах обычно расположены в каталоге /usr/include. Опишем библиотечные функции ANSI C, определенные в файлах-заголовков, перечисленных ниже:

    Кроме указанных, в большинстве UNIX-систем есть файлы заголовков, которые не определены в ANSI C:
    .
    В этих файлах заголовков объявляются функции, которые помогают получить доступ к информации о бюджетах пользователей и групп в UNIX-системах. В указанных системах они определены в библиотеке libc.a. Эти функции полезны для разработки приложений.
    В файле заголовков объявляется тип данных FILE, который используется в Си-программах для обозначения потоковых файлов, или просто потоков, т.е. файлов, обмен с которыми осуществляется с помощью функций потокового ввода-вывода. Имеется также набор макрокоманд и функций, предназначенных для манипулирования потоковыми файлами. Ниже приведены некоторые из этих макрокоманд и функций, которые уже должны быть знакомы из предыдущих лекций.
    Потоковая функция или макрокоманда Назначение
    fopen Открывает поток для чтения и (или) записи
    fclose Закрывает поток
    fread Читает блок данных из потока
    fgets Читает строку текста из потока
    fscanf Читает форматированные данные из потока
    fwrite Записывает блок данных в поток
    fputs Записывает строку текста в поток
    fprintf Записывает форматированные данные в поток
    fseek Перемещает указатель чтения или записи в потоке
    ftell Возвращает текущую позицию в потоке, начиная с которой будет выполнена следующая операция чтения или записи. Возвращаемое значение - это количество байтов смещения относительно начала потока
    freopen Повторно использует указатель потока для ссылки на новый файл
    fdopen Открывает потоковый файл с указанным дескриптором
    feof Макрокоманда, которая возвращает ненулевое значение, если в данном потоке обнаружен символ конца файла, в противном случае - нулевое значение
    ferror Макрокоманда, которая возвращает ненулевое значение, если в данном потоке была обнаружена ошибка или символ конца файла, в противном случае - нулевое значение
    clearer Макрокоманда, которая сбрасывает флаг наличия ошибок в данном потоке
    fileno Макрокоманда, которая возвращает дескриптор данного потокового файла


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

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

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

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

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

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

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

    В заголовке определяется набор функций, предназначенных для получения учетной информации о группах, содержащейся в UNIX-файле /etc/group.

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

    Связь с файлами

    Один способ организации связи программы с файлом заключается в использовании операций переключения < и >. Этот метод прост, но ограничен. Язык Си предоставляет и более мощные методы связи с файлами. Рассмотрим использование функции fopen( ), которая открывает файл, затем применяются специальные функции ввода-вывода для чтения файла или записи в этот файл и далее используется функция fclose( ) для закрытия файла. Прежде чем исследовать эти функции, кратко познакомимся с сущностью файла.
    Файл является частью памяти, обычно на диске, со своим именем. Мы считаем, что он содержит некоторую полезную информацию. Для операционной системы файл более сложен, но это системные проблемы, а не наши. Но мы должны знать, что означает файл для программы на языке Си. В предлагаемых для обсуждения функциях, работающих с файлами, язык Си рассматривает файл как структуру. Вот типичный пример, взятый из IBM-версии компилятора Lattice C:
    struct_iobuf { char*_ptr; /* текущий указатель буфера*/ int_cnt; /* текущий счетчик байтов*/ char*_base; /* базовый адрес буфера ввода-вывода*/ char_flag; /* управляющий признак*/ char_file; /* номер файла*/ } #define FILE struct_iobuf /* краткая запись*/
    Здесь мы не собираемся разбираться детально в этом определении. Главное состоит в том, что файл является структурой, и что краткое наименование шаблона - FILE. Многие системы используют директиву typedef для установления этого соответствия. Таким образом, программа, имеющая дело с файлами, будет использовать тип структуры FILE, чтобы делать так.
    Рассмотрим пример чтения содержимого файла, названного File, и вывода его на экран:
    #include main( ) { FILE *in; /* описываем указатель на файл */ int ch; if ((in = fopen("File", "r") ) != NULL) { /* открываем File для чтения, проверяя существует ли он */ /* указатель FILE ссылается теперь на File */ while ((ch = getc(in) != EOF)) /* получаем символ из in */ putc(ch, stdout); /* посылаем ch на стандартный вывод*/ fclose(in); /* закрываем фаил */ } else printf (" Файл не открывается\"File\".\n); }
    Объясним работу: fopen( ), fclose и использование функций ввода-вывода файла.

    Текстовые файлы с буферизацией

    Функции fopen( ) и fclose( ) работают с текстовыми файлами с "буферизацией". Под буферизацией мы понимаем, что вводимые и выводимые данные запоминаются во временной области памяти, называемой буфером. Если буфер заполнился, содержимое его передается в блок, и процесс буферизации начинается снова. Одна из основных задач fclose( ) заключается в том, чтобы освободить любые частично заполненные буферы, если файл закрыт. Текстовым считается файл, в котором информация запоминается в виде символов в коде ASCII или аналогичном. Текстовый файл
    отличается от двоичного файла, который обычно используется для запоминания кодов машинного языка.

    Включение библиотеки

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

    Включение файла

    Если функция задана как макроопределение, то можно директивой #include включить файл, содержащий ее определение. Часто подобные функции могут быть собраны в соответствующим образом названный заголовочный файл. Например, некоторые системы имеют файл ctype.h, содержащий макроопределения, задающие тип символа (прописная буква, цифра и т.д.)

    Ввод-вывод файла: fprintf( ), fscanf( ), fgets( ), fputs( )

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

    Ввод-вывод текстового файла: getc( ), putc( )

    Две функции getc( ) и putc( ) работают аналогично функциям getchar( ) и putchar( ) (описанным в предыдущих лекциях). Разница заключается в том, что вы должны сообщить, какой файл следует использовать.
    char ch; ch=getchar( );
    предназначена для получения символа от стандартного ввода, а
    ch=getc(in);
    - для получения символа от файла, на который указывает in.
    putchar(ch);
    выводит символ на стандартный файл вывода.
    putc(ch,t);
    предназначена для записи символа ch в файл, на который ссылается указатель t типа FILE.

    Закрытие файла: fclose( )

    В нашем примере показано, как нужно закрывать файл:
    fclose(in);
    Аргумент функции является указателем на файл. Для более серьезной программы нужно смотреть, успешно ли закрыт файл. Функция fclose( ) возвращает значение 0, если файл закрыт успешно, и EOF в противном случае.

    Функция получения случайных чисел

    Сейчас мы покажем генератор псевдослучайных чисел. Это означает, что фактическая последовательность чисел предсказуема, но они разбросаны довольно равномерно в пределах возможного диапазона значений.
    Схема начинает с числа, называемого "зерно". Она использует его для создания нового числа, которое становится новым зерном. Затем новое зерно можно использовать для создания более нового зерна и т.д. Чтобы эта схема работала, функция случайных чисел должна помнить зерно, которое она использовала при последнем вызове. Отметим, здесь нужно использовать статическую переменную!
    /* Первая версия функции rand( )*/ rand( ) { static int randx = 1; randx = (randx * 25173 + 13849)%65536; /* магическая формула */ return(randx); }
    Статическая переменная randx начинает со значения 1 и изменяется при помощи магической формулы каждый раз при вызове функции. Результатом в нашей системе является число, находящееся в диапазоне -32768 до 32767. Системы с разной длиной переменной типа int будут давать различные результаты.
    Проверим работу функции при помощи этого простого драйвера:
    /*драйвер 1 функции rand( ) */ main( ) { int count; for(count = 1; count <= 5; count++) printf(" %d\n",rand( )); }
    Получим результат:
    -26514 -4449 20196 -20531 3882
    Эта последовательность чисел выглядит довольно случайной. Запустим драйвер еще раз. Теперь имеем
    -26514 -4449 20196 -20531 3882
    Получилось абсолютно то же самое. Это и есть псевдоэффект. Каждый раз, когда работает основная программа, мы начинаем с одного и того же значения зерна, равного 1. Можно обойти эту проблему, введя вторую функцию srand( ), которая позволяет вновь устанавливать зерно в начальное значение. Хитрость заключается в том, чтобы сделать randx внешней статической переменной, известной только функциям rand( ) и srand( ). Эти две функции нужно хранить в своем собственном файле и компилировать этот файл отдельно. Вот модификатор программы:
    /* Файл для rand( ) и srand( ) */ static int randx = 1; rand( ) { randx = (randx * 25173 + 13849) % 65536; return(randx); } srand(x) unsigned x; { randx = x; }

    Используем другой драйвер:

    /* драйвер 2 функции rand( ) */ main( ) { int count; int seed; printf(" Введите свое значение зерна.\n"); scanf("%d", & seed); srand(seed); /* установите зерно в начальное значение */ for(count = 1; count <= 5; count++) printf("%\n",rand( )); }

    Программа проработала один раз:

    Введите свое значение зерна.

    1 -26514 -4449 20196 -20531 3882

    Используя значение 1 для переменной seed, получаем те же значения, что и прежде. Введем значение 2 и посмотрим, что получится:

    2 23832 20241 -1858 -30417 -16204

    Мы получили другую последовательность чисел.

    Матрица инцидентности

    Матрица I(G) размером n x m (n - число вершин, m - число ребер/дуг графа G), (i,j)-й элемент, которой равен 1, если вершина ni инцидентна ребру eij в неориентированном графе или если ni есть начало дуги ej равен -1, если вершина ni есть конец дуги ej (только для ориентированных графов), и равен 0 в остальных случаях.
    В данной задаче задается граф и строится его матрица инцидентности:
    //Построение матрицы инцидентности #include #include #include #include #include struct elem { int num; /* Номер вершины */ int suns; /* Количество сыновей */ char str[20]; /* Строка с номерами сыновей */ elem *next; /* Указатель на следующую вершину */ } *head, *w1, *w2; int connected(int i, int j) { int k; char *str1; w2 = head; if(i == j) return 0; for(k=1; knext; if( strchr(w2->str, j) ) return 1; return 0; } void main() { int tops; int i,j,k,l; char *str1; clrscr(); printf("Введите количество вершин \n"); scanf("%d", &tops); head = (elem *)malloc(sizeof(elem)); head->num = 1; head->suns = 0; head->str[0] = '\0'; head->next = NULL; w1 = head; for(i=2;i<=tops;i++) { w2 = (elem *)malloc(sizeof(elem)); w2->num = i; w2->suns = 0; w2->str[0] = '\0'; w2->next = NULL; w1->next = w2; w1 = w2; } w1 = head; for(i=1; i<=tops; i++) { // clrscr(); printf("Введите количество путей из вершины %d\n", i); scanf("%d", &k); for(j=1; j<=k; j++) { printf("Введите связь %d\n", j); scanf("%d", &l); if((l<=0) || (l > tops)) { printf("Такой вершины нет, повторите попытку\n"); l = 0; j--; continue; } w1->str[w1->suns++] = l; w1->str[w1->suns] = '\0'; if(w1->suns == 49) { printf("Слишком много связей !"); exit(1); } } w1 = w1->next; } clrscr(); printf("\n\n Матрица инциндентности :\n"); for(i=1; i<=tops; i++) { printf("\n %d) ", i); for(j=1; j<=tops; j++) { printf("%d ", connected(i, j)); } } printf("\n\n Нажмите любую клавишу..."); getch(); }

    Очереди

    Очередь - одномерная структура данных, для которой загрузка или извлечение элементов осуществляется с помощью указателей начала (head) и конца (tail) очереди в соответствии с правилом FIFO (first-in, first-out - первым введен, первым выведен).
  • Начальная установка:
    head=1; tail=1;
  • Добавление элемента:
    queue[tail]=x; tail=tail+1; if(tail>qd) tail=1;
    Здесь qd - размерность очереди.
  • Исключение элемента:
    x=queue[head]; head=head+1; if(head>qd) tail=1;
  • Проверка переполнения очереди и включение в нее элемента:
    temp=tail+1; if(temp=head) {Переполнение} else {queue[tail]=x; tail=temp}
  • Проверка наличия элементов и исключение элемента х:
    if(head==tail) { очередь пуста} else{ x=queue[head]; head=head+1; if(head>qd) head=1;}

  • Отметим, что при извлечении элемента из очереди все элементы могут также перемещаться на один шаг к ее началу.
    on_load_lecture()
    Очереди
    Очереди
    Дальше "
    Очереди
    Если Вы заметили ошибку - сообщите нам.
    Очереди
    Страницы:
    " |
    1
    |
    2
    |
    3
    |
    4
    |
    5
    |
    6
    |
    вопросы | "
    |
    учебники
    |
    для печати и PDA
    Очереди
    Очереди
    Очереди

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Подведем итог

    Следует обратить внимание на самый существенный момент: программы нужно проектировать, а не создавать их методом проб и ошибок. Мы должны внимательно подумать о форме и содержании ввода и вывода для программы. Необходимо разделить программу на хорошо определенные задачи, затем раздельно запрограммировать, принимая во внимание их взаимодействие друг с другом. Идея заключается в достижении модульности. Если необходимо, разбивайте модули на еще более мелкие модули. Используйте функции для повышения степени модульности и простоты программы.
    При проектировании программы попытайтесь предвидеть, что может идти неправильно, и программируйте, исходя из этого. Используйте локализацию ошибок, чтобы контролировать действия в местах потенциальных затруднений, или, по крайней мере, предупреждать пользователя, что может возникнуть осложнение. Гораздо лучше дать пользователю еще одну возможность ввести данные, чем продолжать выполнять программу и прийти к аварийной ситуации. Если создается функция, сначала определите, как она будет взаимодействовать с вызывающей программой. Решите также, какая информация будет входить в нее, а какая - выходить. Какими должны быть аргументы? Если вы примете во внимание все эти параметры, то можете обратить внимание на работу самой функции. Используйте эти идеи, и ваша программа будет более надежной и менее подверженной аварийным ситуациям. Вы получите тело функции, которое сможете применять в других программах. Программирование в таком случае потребует меньше времени. Не забывайте о классах памяти. Переменные можно определять вне функции. В этом случае их называют внешними или глобальными и они доступны более чем для одной функции. Переменные, определенные внутри функции, являются локальными для нее, и не известны другим функциям. Если можно, используйте автоматическую разновидность локальных переменных. Они охраняют переменные одной функции от взаимодействия других функций.
    on_load_lecture()
    Подведем итог
    Подведем итог

    Перейти к вопросам "
    Подведем итог

    Если Вы заметили ошибку - сообщите нам.

    Поиск узлов из простых чисел

    Всякий, кто изучает простые числа, бывает очарован ими и одновременно ощущает собственное бессилие. Определение простых чисел так просто и очевидно. Найти очередное простое число так легко, разложение на простые сомножители - такое естественное действие! Почему же тогда простые числа столь упорно сопротивляются нашим попыткам постичь порядок и закономерности их расположения? Может быть, в них вообще нет порядка, или же мы так слепы, что не видим их?
    Какой-то порядок в простых числах, несомненно, есть. Простые числа можно отсеять от составных решетом Эратосфена. Начнем с того, что 2 - простое число. Теперь выбросим все большие четные числа (делящиеся на 2). Первое из уцелевших за двойкой чисел, 3, также должно быть простым. Удалим все его кратные, останется 5. После удаления кратных пяти останется 7. Будем продолжать в том же духе. Все числа, прошедшие через решето, будут простыми. Это регулярная, хотя и медленная процедура находит все простые числа. Оказывается, что все известные методы построения таблицы простых чисел - не что иное, как вариации метода решета. Эйлер придумал формулу x2+x+41. Для всех x от нуля до 39 эта формула дает простые числа. Однако, никакая полиномиальная формула не может давать подряд бесконечный ряд простых чисел. Формула Эйлера терпит фиаско при x=40. Закономерность появления простых чисел проявляется, когда целые числа отображаются на плоскость (или в пространство).
    Напишем программу, которая отображает целые числа на плоскость некоторым регулярным образом, и отмечает на рисунке места, где находятся простые числа:
    // Построить матрицу А(15x15)таким образом: // А(7,7)=1, затем, по спирали против // часовой стрелки, увеличивая значение // очередного элемента на единицу и // выделяя все простые числа красным цветом // заполнить матрицу #include #include void main(void) { clrscr(); int mas[15][15]; int n=1,x=6,y=6,k=1; int i,j; while(1){ mas[x][y]=k++; switch(n){ case 1: x++;break; case 2: y--;break; case 3: x--;break; case 4: y++;break; } if(x==15) break; if(x==y && x<6) n=4; else if(x+y==12 && x<6) n=1; else if(x+y==12 && x>6) n=3; else if(x==y+1 && x>6) n=2; } for(i=0;i<15;i++) { for(j=0;j<15;j++) { textcolor(12); if(mas[j][i]>2) for(k=2;k

    Стеки

    Стеком называется структура данных, загрузка или увеличение элементов для которой осуществляется с помощью указателя стека в соответствии с правилом LIFO (last-in, first-out - последним введен, первым выведен).
    Указатель стека sp (stack pointer) содержит в любой момент времени индекс (адрес) текущего элемента, который является единственным элементом стека, доступным в данный момент времени для работы со стеком (для случая, когда указатель стека всегда задает ячейку, находящуюся непосредственно над его верхним элементом).
  • Начальная установка:
    sp=1;
  • Загрузка элемента х в стек:
    stack[sp]=x; sp=sp+1;
  • Извлечение элемента из стека:
    sp=sp-1; x=stack[sp];
  • Проверка на переполнения и загрузка элемента в стек:
    if(sp<=sd) {stack[sp]=x;sp=sp+1} else// переполнение
    Здесь sd - размерность стека.
  • Проверка наличия элементов и извлечение элемента стека:
    if (sp>1){sp=sp-1;x=stack[sp]} //антипереполнение
  • Чтение данных из указателя стека без извлечения элемента:
    x=stack[sp-1];


  • Структуры данных

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

    Связанные списки

    Связанный список представляет собой структуру данных, состоящую из узлов (как правило, записей), содержащих указатели на следующий узел. Указатель, который ни на что не указывает, снабжается значением null. Таким образом, в каждый элемент связанного списка добавляется указатель (звено связи).
    Приведем основные базисные операции для работы с однонаправленным связанным списком.
  • Включение элемента после элемента p:
    link[q]=link[p]; link[p]=q;
    Здесь q - индекс элемента, который должен быть вставлен в список после элемента с индексом.
  • Исключение преемника элемента x:
    if(link[x] != NULL) link[x]=[link[x]] else {элемент не имеет преемника}
    Отметим, что элемент, следующий в списке за элементом x, называется преемником элемента x, а элемент, расположенный перед элементом x, называется предшественником элемента x. Если элемент x не имеет преемника, то содержащемуся в нем указателю присваивается значение null.
  • Включение элемента y перед элементом x:
    prev=0; while((link[prev]!=null) && (link[prev]!=x)) do prev=link[prev]; if (link[prev]==x) { link[prev]=y; link[y]=x } else { элемент x не найден };

  • Здесь link[0] является началом списка.
    Отметим, что исключение последнего элемента из однонаправленного списка связано с просмотром всего списка.
    В двунаправленном связанном списке каждый элемент имеет два указателя (succlink - описывает связь элемента с преемником, predlink - с предшественником).
    Приведем основные базисные операции для работы с двунаправленным связанным списком.
  • Включение элемента перед элементом x:
    succlink[y]=x; predlink[y]=predlink[x]; succlink[predlink[x]]=y; predlink[x]=y;
  • Включение элемента y после элемента x:
    succlink[y]=succlink[x]; predlink[y]=x; predlink[succlink[x]]=y; succlink[x]=y;
  • Исключение элемента x:
    predlink[succlink[x]]=predlink[x]; succlink[predlink[x]]=succlink[x];

  • Пример 1:
    /* В список помещаются цифры 1...10 Вводится число 11, сначала вставляется за цифрой 10, затем рвется связь между 3 и 4, между ними вставляется число 11. Пример дает навык работы: - с динамической памятью; - создания абстрактной структуры данных - список и модификации списка; - со структурами данных; - с функциями. */ #include #include #include #include #include #include struct List { int i; List *next; }; /* структура данных, первое поле для хранения целого, второе поле-адрес в динамической памяти*/ List*head=NULL; /* начальный адрес*/ void Hed(int i) /* функция, которая создает очередной элемент списка */ { if(head==NULL) { head=( List*)malloc(sizeof(List)); head->i=1; head->next=NULL; } else { struct List *p,*p1; p=head; while(p->next!=NULL) p=p->next; p1=new List; p1->i=i; p1->next=NULL; p->next=p1; } } int s=0; void Print(List*p)/* вывод списка на экран */ { printf(" %d",p->i); if(p->next!=NULL)Print(p->next); } void delist()/* освобождение динамической памяти */ { List*p; while(head!=NULL) { p=head; head=head->next; free(p); } } void Vstavka(int i1,int c) /*вставка нового элемента*/ { List*p=head,*p1; while(p->i!=i1) p=p->next; p1=new List; p1->i=c; p1->next=p->next; p->next=p1; } void main()/* вход в программу */ { clrscr();/* очистить экран */ for(int i=1;i<=10;i++) Hed(i);/* создание списка */ Print(head);/* вывод списка на экран */ Vstavka(10,11); printf("\n"); Print(head); Vstavka(3,11); printf("\n"); Print(head); delist(); getch(); }

    Все операции со стеком

    Структура данных стека. Принципы выборки и записи в стек описаны выше.
    // Работа со стеком. Проверка, пуст ли стек. // Добавить в стек. Выбрать из стека. // Стек полон #include #include #include #include #include #include #define max_size 200 char s[max_size]; //компоненты стека int next=0; // позиция стека int Empty() { return next==0; } int Full() { return next==max_size; } void Push() { if (next==max_size) { cout<<"Ошибка: стек полон"<> s[next-1]; } } void OUTst() { int i=0; if (next==0) { cout<<"Cтек пуст"> c; clrscr(); switch (c) { case '0':OUTst();getch();break; case '1':Push();break; case '2':Del();getch();break; case '3':cout<< "Hомер "<

    Будущее языка Си

    Многие фирмы, производящие программное обеспечение, все чаще обращаются к Си как к удобному языку для реализации своих проектов, поскольку известно, что Си позволяет получить компактные и эффективные программы. И эти программы могут быть легко модифицированы и адаптированы к новым моделям ЭВМ! Языки программирования как С++, Java, С#, UML и т.д. имеют "сишную" семантику. Си используется фирмами, производящими программное обеспечение, студентами, обучающимися программированию. И если вы хотите работать в сфере программотехники, то один из первых вопросов, на который вы должны будете отвечать "Да" - это вопрос "Умеете ли Вы программировать на Си?"

    Дополнительный пример

    Здесь мы приведем еще пример. Мы использовали только стандартную функцию printf( ). В данном примере мы демонстрируем, как включить и использовать функцию, которую мы сами написали:
    dir( )/* dir*/ { printf("На сайте проекта www.intuit.ru \n"); printf(" большое количество учебных курсов\n"); } main () { dir (); printf("Над их созданием работают \n"); printf(" профессора российских вузов.\n"); }
    Результат работы программы выглядит следующим образом:
    На сайте проекта www.intuit.ru большое количество учебных курсов Над их созданием работают профессора российских вузов.
    Функция dir( ) определяется точно так же, как и функция main( ) - ее тело заключено в фигурные скобки. Вызов функции осуществляется путем простого указания ее имени, включая круглые скобки.
    on_load_lecture()
    Дополнительный пример
    Дополнительный пример
    Перейти к вопросам "
    Дополнительный пример
    Если Вы заметили ошибку - сообщите нам.
    Дополнительный пример
    Страницы:
    " |
    1
    |
    2
    |
    3
    |
    вопросы | "
    |
    учебники
    |
    для печати и PDA
    Дополнительный пример
    Дополнительный пример
    Дополнительный пример

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Достоинства языка Си

    Особое значение придается гибкости. Язык Си компактен, является относительно маленьким языком программирования. Ввод-вывод не считается частью языка Си, а определяется стандартной библиотекой. Вседозволенность в языке Си является следствием желания как можно больше расширить область его применения. Язык Си удалось сделать относительно маленьким языком программирования за счет того, что в его состав не были включены ввод-вывод и средства для работы со строками. Язык Си был задуман настолько гибким, что эти возможности могли быть реализованы в каждом конкретном случае наиболее удачным образом. Практический опыт использования языка Си показал правильность такого подхода. Большая часть операционной системы UNIX и все утилиты этой операционной системы, включая и несколько трансляторов с языка Паскаль, реализованы на языке Си. Язык программирования является языком программирования с сильной типизацией, если:
  • каждый объект в этом языке программирования принадлежит точно одному из существующих в этом языке программирования типу данных;
  • преобразование типов осуществляется только путем преобразования значения из одного типа в другой;
  • преобразование типов не производится путем трактовки представления значения как данных различных типов.

  • Эксперименты показали, что языки программирования с сильной типизацией способствуют увеличению ясности и надежности программ. Механизм трактовки представления значения как данных различных типов приводит к тому, что использующие его программы не обладают ни надежностью, ни мобильностью. В языке Си допускается неявное преобразование типов для всех базовых типов и указателей. Однако Мобильный Транслятор с языка Си выводит предупреждение о каждом встретившимся в программе случае неявного преобразования типов, в котором участвует указатель.
    Язык Си быстро становится одним из наиболее важных и популярных языков программирования. Его использование все более расширяется, поскольку часто программисты предпочитают язык Си всем другим языкам после первого знакомства с ним. Сейчас мы упомянем лишь некоторые достоинства Си.
    Си - современный язык. Он включает в себя те управляющие конструкции, которые рекомендуются теоретическим и практическим программированием. Его структура побуждает программиста использовать в своей работе нисходящее проектирование, структурное программирование и пошаговую разработку модулей. Результатом такого подхода является надежная и читаемая программа.
    Си - эффективный язык. Его структура позволяет наилучшим образом использовать возможности современных ЭВМ. Написанные на языке Си программы обычно отличаются компактностью и быстротой исполнения.
    Си - переносимый (или мобильный) язык. Это означает, что программа, написанная на Си для одной вычислительной системы, может быть перенесена с небольшими изменениями или вообще без них, на другую.
    Си - мощный и гибкий язык. Например, большая часть мощной и гибкой OC UNIX написана на языке Си. Речь идет о компиляторах и интерпретаторах других языков, таких, как Фортран, АПЛ, Паскаль, Лисп, Лого и Бейсик. Кроме того, программы, написанные на Си, используются для решения физических и технических проблем, компьютерной графики и даже производства мультипликационных фильмов.
    Си - обладает рядом конструкций управления, обычно ассоциируемых с ассемблерами.
    Си - удобный язык. Он достаточно структурирован, чтобы поддерживать хороший стиль программирования, и вместе с тем не связывает ограничениями.

    Исходные и выполняемые файлы

    Приведем простенькую программу на языке Си:
    #include main( ) { printf("Добро пожаловать!\n"); }
    Все конструкции языка мы рассмотрим в последующих лекциях. Если эту программу оттранслировать, то получим файл с выполняемой программой. В результате работы этой программы на дисплей будет выведено предложение: "Добро пожаловать!". Наша программа, несмотря на свою лаконичность и простоту, для компьютера является совершенно бессмысленным набором символов, так как он не понимает директив #include или printf. Компьютер понимает только специальный язык, называемый машинным кодом, т.е. набор последовательностей двоичных цифр, например 101000101. Если мы хотим, чтобы компьютер выполнил программу, мы должны осуществить перевод (трансляцию) кода, написанного на Си (исходного) в ее код (машинный). В результате этих действий будет получен выполняемый файл. Процесс перевода (трансляции) удалось переложить на сам компьютер. Программы, переводящие исходный код в машинный код, называются компиляторами. Детали процесса перевода зависят от особенностей конкретной системы. В некоторых компиляторах с языка Си, работающих на персональных ЭВМ, реализован альтернативный способ трансляции. В процессе перевода получается файл с расширением .obj, затем используется системный компоновщик для получения файла с выполняемой программой, т.е. файла с расширением .exe. Почему компиляция, а не интерпретация? Дело в том, что после компиляции получается более эффективный конечный продукт, чем при интерпретации.

    Использование языка Си

    Си - язык компилируемого типа. Пример языков компилируемого типа: Паскаль, Фортран. Пример языков интерпретируемого типа: Бейсик, Лого. Чтобы дать первое представление о процессе создания программы, приведем упрощенную схему того, что необходимо сделать - начиная от написания программы и заканчивая ее выполнением.
  • Для создания программы на языке Си используйте редактор текстов.
  • Попробуйте оттранслировать вашу программу с помощью удобного для вас компилятора. Компилятор проведет проверку правильности вашей программы. Если компилятор обнаружит ошибки в вашей программе, он выдаст сообщение об этом. Если ошибок не будет, компилятор выполнит перевод программы на внутренний язык ЭВМ, и поместит результат в новый файл.
  • Набрав имя этого нового файла на клавиатуре дисплея, вы можете запустить программу.

  • В некоторых вычислительных системах второй этап может быть разбит на два или три шага.

    Использование текстового редактора для подготовки программ

    У Си нет собственного текстового редактора. В качестве него можно использовать любой из редакторов общего типа. В операционной системе UNIX это чаще всего редакторы ed, ex, edit, emacs, vi. На персональном компьютере это может быть ed, edlin, Wordstar, Volkswriter или любой другой из большого набора редакторов. При работе с некоторыми из них необходимо определить конкретную версию редактора путем задания соответствующих параметров. Например, при использовании редактора Wordstar необходимо ввести параметр N, указывающий на отсутствие документирования.
    При работе с редактором лучше не ошибаться, набирая текст программы на пульте дисплея и правильно задать имя файла, в который она будет помещена. Имя файла должно быть допустимым именем в вашей вычислительной системе и иметь расширение .c.
    Например,
    sort.c Sort.c
    Первая часть имени должна напоминать, что программа делает или хотя бы того, кто разработал алгоритм, который в ней реализован. Вторая часть, так называемое расширение файла .с указывает на то, что данный файл содержит текст программы, написанный на языке Си. Расширение файла используется для того, чтобы информировать пользователя и вычислительную систему о типе файла. Предположим, что при помощи редактора мы подготовили программу и поместили ее в файл с именем Nina.c. Текст, который мы набрали на клавиатуре, называется исходным кодом и содержится в исходном файле. Исходный файл - это начальный пункт процесса программирования.
    on_load_lecture()
    Использование текстового редактора для подготовки программ
    Использование текстового редактора для подготовки программ
    Дальше "
    Использование текстового редактора для подготовки программ
    Если Вы заметили ошибку - сообщите нам.
    Использование текстового редактора для подготовки программ
    Страницы:
    1
    |
    2
    |
    3
    |
    вопросы | "
    |
    учебники
    |
    для печати и PDA
    Использование текстового редактора для подготовки программ
    Использование текстового редактора для подготовки программ
    Использование текстового редактора для подготовки программ

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Первый просмотр

    #include - включение другого файла.
    Эта строка указывает компилятору, что нужно включить информацию, содержащуюся в файле stdio.h.
    main( ) - имя функции
    Любая программа, написанная на языке Си, состоит из одной или более функций, являющихся основными модулями, из которых она собирается. Наша программа состоит из одной функции main( ), а круглые скобки указывают именно на то, что main( ) - имя функции.
    /* простая программа*/ - комментарий
    /*, */ - открывающая и закрывающая скобки многострокового комментария.
    Комментарии - это примечания, помогающие понять смысл программы. Они предназначены для читателя, и игнорируются компилятором.
    { - начало тела функции
    Открывающая фигурная скобка отмечает начало последовательности операторов - тело, или определение, функции. Конец определения отмечается закрывающей фигурной скобкой }.
    int courses; - оператор описания
    С помощью такого оператора мы объявляем, что будем использовать в программе переменную courses, которая принимает целые (int) значения.
    courses = 30; - оператор присваивания
    Этот оператор служит для присваивания переменной courses значения 30.
    printf("Сколько учебных курсов на сайте"); - оператор вывода на печать
    С его помощью выводится на печать фраза, заключенная в кавычки:
    Сколько учебных курсов на сайте
    printf(" www.intuit.ru?\n"); - еще один оператор вывода на печать. Этот оператор добавляет слова www.intuit.ru? в конец последней печатаемой фразы. Комбинация символов \n указывает компилятору на начало новой строки.
    printf("Более %d. Но будет еще больше!\n",courses);
    Этот оператор выводит на печать значение переменной courses, равное 30, содержащееся в кавычках. Символы %d указывают компилятору, где и в какой форме печатать значение переменной courses.
    } - конец
    Программа завершается закрывающей фигурной скобкой.

    Пояснения к программе

    Мы выполним два просмотра текста программы: во время первого просмотра объясним смысл каждой строки, а во время второго - рассмотрим детали.

    Пример простой программы на языке Си

    Рассмотрим простую программу на языке Си:
    #include main( )/*простая программа*/ { int courses; courses=30; printf("Сколько учебных курсов на сайте"); printf(" www.intuit.ru?\n"); printf("Более %d. Но будет еще больше!\n", courses); }
    Давайте выполним эту программу. Сначала используем текстовый редактор для создания файла, содержащего текст программы. Этому файлу нужно присвоить какое-то имя. Допустим - intuit.c . Выполним компиляцию программы. После запуска программы, при условии отсутствия синтаксических ошибок, результат должен выглядеть следующим образом:
    Сколько учебных курсов на сайте www.intuit.ru? Более 30. Но будет еще больше!

    Происхождение языка Си

    Язык программирования Си был разработан и реализован в 1972 году сотрудником фирмы AT&T Bell Laboratories Денисом Ритчи. Прообразом языка Си для Д. Ритчи послужил язык Би, разработанный Кеном Томпсоном. Он является результатом эволюционного развития языков BCPL (Richards, M., "BCPL: A. Tool for Compiler Writing and System Programming", Proc. AFIPS SJCC, 34, 557-566, 1969) и Би (Johnson, S. C., and B. W. Kernighan, "The Programming Language B", Comp. Sci. Tech. Rep. No. 8, Bell Laboratories. 1973). Основным достоинством языка Си по сравнению с языками BCPL и Би является введение в него типов данных. Язык Си был разработан во время создания операционной системы UNIX (OC UNIX). Развитие языка Си продолжалось и после окончания его разработки и касалось, в частности, проверки типов данных и средств, облегчающих перенос программ в другую среду. Например, разработка проекта переноса OC UNIX на компьютер Interdata 8/32 привела к некоторым добавлениям в язык Си, а именно, к включению в язык таких средств, как объединение (union). Позднее были сделаны попытки включения в язык Си средств абстрагирования данных. В настоящее время рассматривается проект стандарта ANSI C - стандарт языка Си Американского национального института и the C Programming Language - Reference Manual, AT&T Bell Laboratories. С языка Си разработаны совместимые по входному языку трансляторы для 40 типов вычислительных систем, начиная от 8-разрядных микропроцессоров и кончая CRAY-1 - одним из самых мощных в настоящее время суперкомпьютеров. В ходе работ по созданию Мобильного Транслятора с языка Си сам он был переработан для повышения мобильности написанных на нем программ.

    Структура простой программы

    Познакомимся с несколькими общими правилами, касающимися программ, написанных на языке Си. Программа состоит из одной или более функций, причем какая-нибудь из них (главная) обязательно должна называться main( ). Описание функции состоит из заголовка и тела. Заголовок состоит из директив препроцессора типа #include и имени функции. Отличительным признаком имени функции служат круглые скобки, а аргумент может и отсутствовать. Тело функции заключено в фигурные скобки и представляет собой набор операторов, каждый из которых оканчивается символом "точка с запятой".

    Второй просмотр

    #include
    Файл с именем stdio.h является частью пакета, имеющегося в любом компиляторе языка Си и содержащего информацию о вводе-выводе. В качестве имени файла используется аббревиатура английских слов:
    standard input/output header - заголовок стандартного ввода-вывода.
    Программисты называют набор данных, содержащийся в начале файла, заголовком.
    Строка "#include " даже не является оператором языка Си. Символ # указывает, что она должна быть обработана "препроцессором" языка Си. Препроцессор осуществляет некоторую предварительную обработку текста программы перед началом компиляции.
    main( )
    Программа, написанная на языке Си, всегда начинает выполняться с функции, называемой main( ). Скобки указывают на то, что main( ) - имя функции.
    Функция - это основные модули программы, написанные на языке Си. В круглых скобках в общем случае содержится информация, передаваемая этой функции. В нашем случае передача информации отсутствует и, следовательно, в скобках ничего не содержится.
    Файл, содержащий программу, может иметь любое имя с тем ограничением, что оно должно удовлетворять системным соглашениям, и оканчиваться символом с. Например, game.c.
    /* простая программа*/
    Комментарии облегчают процесс понимания программы. Длинный комментарий может помещаться на отдельной строке или даже занимать несколько строк. Все, что находится между символом, указывающим на начало комментария /*, и символом, указывающим на его конец */, игнорируется компилятором.
    {,}
    Фигурные скобки { } отмечают начало и конец тела функции.
    int courses;
    Это оператор описания переменной. В нашей программе в теле функции используется переменная courses, и с помощью слова int объявляется, что переменная courses принимает целые значения. Точка с запятой в конце строки указывает на то, что в ней содержится оператор языка Си, причем этот символ является частью оператора, а не разделителем операторов. int служит ключевым словом, определяющим один из основных типов данных языка Си.
    Ключевыми словами называются специальные зарезервированные слова, используемые для построения фраз языка.
    В языке Си все переменные должны быть объявлены. Это означает, что мы должны привести список всех используемых переменных и указать тип каждой из них.
    Имя переменной нужно давать осмысленно. Оно может содержать от одного до восьми символов. Фактически мы можем использовать и большее их число, но компилятор пропустит все символы, начиная с девятого.
    Идентификатор переменной - имя переменной. Для обозначения имени переменной разрешается использовать строчные и прописные буквы, цифры и символ подчеркивания, считающийся буквой. Первым символом должна быть обязательно буква.
    Например, courses, cat_1, _total - правильные идентификаторы, а $courses*, 1cat, -total - неправильные.
    Переменные можно описывать по мере необходимости, но лучше размещать операторы объявления переменных в начале программы. Любая программа, написанная на языке Си, не будет выполняться, если
    не описать все используемые переменные.
    courses=30;

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

    В данной программе используется стандартная функция языка Си - printf( ). Строка символов, заключенная в скобки, является информацией, передаваемой функции printf( ) из главной функции main( ). Такая информация называется аргументом. В первом случае аргументом является "Сколько учебных курсов на сайте". Данная строка дает пример того, как вызывать функцию или обратиться к ней, программируя на языке Си. Для этого требуется только указать имя функции и заключить требуемый аргумент, или аргументы, в скобки. Когда при выполнении функции программа достигнет этой строки, управление будет передано указанной функции. Когда выполнение функции будет завершено, управление вернется обратно в исходную, вызывающую программу.

    Символы \n служат директивой начать новую строку на устройстве вывода. Комбинация \n представляет один символ, называемый "новая строка". Его смысл формулируется так: начать вывод новой строки с самой левой колонки. Символ "новая строка" служит одним из примеров того, что называется "управляющей последовательностью". Управляющая последовательность начинается с "\".

    

        Биржевая торговля: Механические торговые системы - Создание - Программирование