Справочная система C guide

         

Оператор завершения break


break;

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

Пример

for (i = 0, i < n; i++) if((a[i] =b[i])==0) break,

Описание внешних объектов


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

Примеры

extern int Global_var;

extern char *Name;

extern int func();

Можно опускать длину внешнего одномерного массива.

Пример



extern float Num_array [ ];

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

Описания используются для определения переменных


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

Определение глобальных переменных


Глобальные переменные

Пример

int Global_flag;

Определяются на том же уровне, что и функции, т. е. не локальны ни в каком блоке. Постоянные. Инициализируются нулем, если явно не задано другое начальное значение. Областью действия является вся программа. Должны быть описаны во всех файлах программы, в которых к ним есть обращения. 3 амечание. Некоторые компиляторы требуют, чтобы глобальные переменные UHggonpenei^ лены только в одном файле и описаны как внешние вдругахфай-лах, где они используются (см. с. 51). Должны быть описаны в файле до первого использования.

Статические переменные

Пример

static mt File _ flag;

Постоянные. Областью действия является файл, в котором данная переменная определена. Должны быть описаны до первого использования в файле.

Определение локальных переменных


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

Замечание 2 Все описания в блоке должны предшествовать первому оператору Автоматические переменные Пример

mt x, /* х - это автоматическая переменная */ }

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

Регистровые переменные

Пример

{

register mt у;

}"

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

Формальные параметры

Примеры

mt func(x), int func(x) int x; register int x;

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

Статические переменные

Пример

{ static int flag }

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

Основные типы


Примеры

char с,

int х;

Основными типами являются:

char - символ (один байт) ;

int - целое (обычно одно слово);

unsigned - неотрицательное целое (такого же размера, как целое);

short - короткое целое (слово или полуслово);

long - длинное целое (слово или двойное слово);

float - число с плавающей точкой (ординарной точности);

double - число с плавающей точкой (двойной точности);

void - отсутствие значения (используется для нейтрализации

значения, возвращаемого функцией). Символы (char) в зависимости от компилятора могут быть со знаком или без знака. Рассматриваемые как целые, символы со знаком имеют значения от -127 до 128, а символы без знака - от 0 до 256. Некоторые реализации допускают явный тип unsigned char. Данные целого типа int могут иметь такой же диапазон, как данные типа long иди short. Описание типа unsigned эквивалентно описанию типа unsigned int. Описание unsigned может сочетаться с описанием типа char, short или long, формируя описания типов unsigned char, unsigned short, unsigned long. Описания типов short и long эквивалентны описаниям типов short int и long mt. Диапазон данных типа long обычно в два раза больше диапазона данных типа short

Перечислимые константы


Имена, указанные в описании перечисяимых констант, трактуются как целые константы

Побитовые операции


~ Использование: ie Дополнение до единицы значения ie. Значение выражения содержит 1 во всех разрядах, в которых ie содержит 0, и 0 во всех разрядах, в которых ie содержит 1. Пример:

opposite = mask,

” Использование: iel ” ie2 Двоичное представление iel сдвигается вправо на ie2 разрядов. Сдвиг вправо может быть арифметическим (т. е. освобождающиеся слева разряды заполняются значением знакового разряда) или логическим в зависимости от реализации, однако гарантируется, что сдвиг вправо целых чисел без знака будет логическим и освобождающиеся слева разряды будут заполняться нулями. Пример: х= х” 3;

“ Использование: iel “ ie2 Двоичное представление iel сдвигается влево на ie2 разрядов; освобождающиеся справа разряды заполняются нулями. Пример: fourx= x“ 2;

& Использование: iel & ie2 Побитовая операция И двоичных представлений iel и ie2. Значение выражения содержит 1 во всех разрядах, в которых и iel и ie2 содержат 1, и 0 во всех остальных разрядах. Пример: flag = ((х & mask) != 0);

I Использование: iel | ie2 Побитовая операция ИЛИ двоичных представлений iel и ie2. Значение выражения содержит 1 во всех разрядах, в которых iel или ie2 содержит 1, и 0 во всех остальных разрядах. Пример: attrsum= attrl I attr2:

*> Использование: iel* ie2 Побитовая операция исключающее ИЛИ двоичных представлений iel и ie2. Значение выражения содержит 1 в тех разрядах, в которых iel и ie2 имеют разные двоичные значения, и 0 во всех остальных разрядах. Пример:

diifbits= x'y;

Преобразование символов


Замечание. Макроопределения, описанные в этом разделе, определены в файле ctype h, который должен быть включен в программу, использующую эти макросы, командой #mclude <ctype h>

Определение int имя_макроса (с)

int с,

toascu - преобразование целого в символ кода ASCII tolower - преобразование буквы в нижний регистр toupper - преобразование буквы в верхний регистр _tolower - такая же, как tolower, но более быстрая и ограниченная

функция. _toupper - такая же, как toupper, но более быстрая и ограниченная функция

Преобразование строки в число


strtol — преобразование строки в длинное целое число

Определение long strtol (sir, ptr, base) char *str, **ptr, int base,

atol — преобразование строки в длинное целое число (специальный

вариант функции strtol())

Определение: long atol (str) char *str,

atoi — преобразование строки в целое.

Преобразование в тип int результата, возвращаемого функцией atol()

Определение int atoi (str)

char *str, 

atof — преобразование строки в число двойной точности с плавающей точкой

Определение double atof (str) char *str,

strtod — преобразование строки в число двойной точности с плавающей точкой

Определение, double strtod (str, ptr) char *str, *ptr,

Препроцессор


Пример

int func();

Функция, не возвращающая значения, должна описываться как имеющая тип void. Если тип функции явно не задан, считается, что она имеет тип int. Областью действия описания на внешнем уровне является остаток файла; внутри блока областью действия является данный блок. Обычно внешние описания располагаются в начале файла. Некоторые компиляторы допускают описание переменных на внешнем уровне без прилагательного extern. Многократные описания внешних переменных компоновщик сводит к одному определению.

Если в качестве первого символа в строке программы используется символ #, то эта строка является командной строкой препроцессора (макропроцессора). Командная строка препроцессора заканчивается символом перевода на новую строку. Если непосредственно перед концом строки поставить символ обратной косой (\), то командная строка будет продолжена на следующую строку программы.

Проверка символов


Замечание. Макроопределения, описанные в этом подразделе, определены в файле ctype.h, который должен быть включен в программы командой

#include <ctype.h>

Определение: int имя_макроса (с)

int с

isalpha Символ с — буква

isupper Символ с - прописная буква (в верхнем регистре)

islower Символ с - строчная буква (в нижнем регистре)

isdigit Символ с - цифра (0 - 9).

isxdigit Символ с - шестнадцатеричная цифра (0 - 9), прописная (А - F) иди строчная (а - f) буква

isalnum Символ с — буква или цифра

isspace Символ с - символ пробела, табуляции, перевода строки, новой строки, вертикальной табуляции или перевода фор мата

ispunct Символ с - символ пунктуации, т е не управляющий сим вол, не буква, не цифра и не пробел

isprint Символ с - печатный, т е имеющий значение (в коде ASCII) от 040 (пробел) до 0176 (тильда)

isgraph Символ с - графический, т е печатный, за исключением пробела

iscntrl Символ с - управляющий (0 - 037) или символ удаления (0177)

isascii Символ с — символ кода ASCII, т е его значение лежит в диапазоне от 0 до 0200

Пустые символы


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

Распределение памяти


malloc - выделение памяти размером size байт.

Определение: char *malloc (size) unsigned size;

calloc - выделение памяти и обнуление ее.

Определение: char *calloc (nelem, elsize) unsigned nelem, elsize;

realloc — изменение размера ранее выделенной памяти.

Определение: char *realloc (ptr, size) char *ptr;

unsigned size;

free - освобождение ранее выделенной памяти.

Определение: void free (ptr) char *ptr;

Символьные константы


Символьная константа состоит из одного символа кода ASCII', заключенного в апострофы (см. с. 89) .

Примеры:

'А' 'а' 'Т '$' Специальные (управляющие) символьные константы

Новая строка (перевод строки) HL (LF) '\п'

Горизонтальная табуляция НТ '\t'

Вертикальная табуляция VT '\v'

Возврат на шаг BS '\Ь'

Возврат каретки CR '\г'

Перевод формата FF '\f

Обратная косая \ '\\'

Апостроф ' '\"

Кавычки " '\" '

Нулевой символ (пусто) NUL '\0.'

Кроме этого любой символ может быть представлен последовательностью трех восьмеричных цифр: '\ddd'. Замечание. Символьные константы считаются данными типа int.

Составной оператор


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

Пример

{х=1; у=2; z=3;}

Состояние файла


clearerr - обнулить признаки ошибки потока. Определение: void clearerr (stream) FILE *stream;

feof - проверить состояние конца файла в потоке. Определение: int feof (stream) FILE *stream,

ferror — проверить состояние ошибки в потоке. Определение: int terror (stream) FILE *stream;

fileno - связать дескриптор файла, открытого функцией open существующим потоком. Определение: int fileno (stream) FILE * stream;

Спецификация преобразования


% [*] [ширина] /дополнительные j символ

L признаки J преобразования

Символ * обозначает пропуск при вводе поля, определенного данной спецификацией; вводимое значение не присваивается никакой переменной.

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

Спецификация ввода целого числа


%[*] [ширина] ^] ^

Буква 1 определяет тип вводимых данных как long, буква h - как short. По умолчанию принимается тип int. Символы преобразования

d - десятичное целое со знаком;

u — десятичное целое без знака;

о — восьмеричное целое без знака;

х - шестнадцатеричное целое без знака.

Спецификация ввода числа с плавающей точкой


%[*] [ширина][I] ^ е ^ Iе)

Буква 1 определяет тип вводимых данных как double, по умолчанию принимается тип float. Символы преобразования f, e, g являются синонимами.

Спецификация ввода по образцу


% [*] [ширина] образец

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

Примеры

[abed]

[А321]

Непрерывный (в коде ASCII) диапазон символов образца описывается первым и последним символами диапазона.

Примеры

[а-z]

[А - FO - 9]

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

Пример

[О-9]

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

Спецификация ввода символа


% [*] [ширина] с

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

Спецификация ввода строки


% [*] [ширина] s

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

ведущие пустые символы игнорируются.

Спецификация вывода целого числа без знака


("I % [-][#] [ширина] [1])

Символ # определяет вывод начального нуля в восьмеричном формате или вывод начальных Ох или ОХ в шестнадцатеричном формате. Символ 1 необходим для данных типа long. Символы преобразования

u — десятичное без знака;

о - восьмеричное без знака;

х — шестнадцатеричное без знака;

Х - шестнадцатеричное без знака с прописными буквами А - F.

Примеры (для 32-разрядных чисел)

%u

777626577

%о 5626321721

%#о 05626321721

%х 2e59a3dl

%#Х OX2E59A3D1

Спецификация вывода целого числа со знаком


% Hf^f [ширина] [l]d

Для отрицательных чисел автоматически выводится знак - (минус). Для положительных чисел знак + (плюс) выводится только в том случае, если задан признак +; если в спецификации задан пробел ^>, то в позиции знака выводится пробел. Символы преобразования

l — необходим для данных типа long';

d - определяет вывод данных типа int в десятичном формате

со знаком.

Примеры

%d 43

%+d +43

%d

Спецификация вывода числа с плавающей точкой


%[-] /+

[#] [ширина] [.точность] 1 Е

Для отрицательных чисел автоматически выводится знак - (минус). Для положительных чисел выводится знак + (плюс), если задан признак +; если в спецификации задан пробел ^, то в позиции знака выводится пробел.

Завершающие нули не выводятся, если в спецификацию не включен признак #. Этот признак также обусловливает вывод десятичной точки даже при нулевой точности.

Точность определяет число цифр после десятичной точки для форматов f, е и Е или число значащих цифр для форматов g и G. Округление "делается отбрасыванием. По умолчанию принимается точность в шесть десятичных цифр.

Спецификация вывода символа


% /—7 [ширина] с

Примеры

%с А

%3с WA %-3с AW

Спецификация вывода строки


%[-] [ширина] [.точность] s

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

Пример

%10s abcdefghijklrnn

%-10.5s abcde^^

%10.5s )W^abcde

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


Строковая константа представляется последовательностью символов кода ASCII, заключенной в кавычки: "... ".

Примеры:

"This is a character string"

"Это строковая константа"

"А" "1234567890" "О" "$"

Строковая константа - это массив символов, заключенный в кавычки; она имеет тип char [ ].

В конце каждой строки компилятор помещает нулевой символ '\0', отмечающий конец данной строки.

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

СТРУКТУРА ПРОГРАММЫ


Программа, описанная в следующем примере, вводит до MAXLINES строк со стандартного входа, сортирует строки в лексикографическом порядке, возрастающем или уменьшающемся в зависимости от признака, передаваемого функции mainQ через аргумент argv Затем программа записывает отсортированные строки на стандартный выход Стандартными входом и выходом (stdin и stdout) могут быть терминал, канал или некоторый файл

//файл bblsort.h

#define MAXLINES 100

#define LINLSIZE (132+1)

main.c

include <stdio h>

include "bblsort h"

char Lme[MAXLINES]ELINESIZE], /* буфер строк */ С int Revfig , /* признак направления * сортировки */

/*

* сортировка строк текста в лексикографическом порядке

*/

main(argc, argv)

D char **argv, /* аргументы вызова программы */

int argc, /* число аргументов */

{

int rdlinesO;

void bblsortO, wrlines();

int numlines;

G Revflg = (argc > 1 && |argv[l][0] [ == '-');

numlines = rdlinesO;

bblsort(numlmes);

wrlines(numlines),

/* запись строк со стандартного выхода*/

Н static int rdlinesO

chai^ *fgets(), / register int l;

/ foi(i=0,i< MAXLINES; i++)

if (fgets(Lme[i], LINESIZE, stdin)

/ == ] (char *)NULL ) break;

return (i);

}

/* запись строк на стандартный выход*/

Структура программы 57

К static void wrhnes(n) L register mt n; /* число строк */

{

register int l,

for (i = 0; i < n; i++) fputs(Lme [i], stdout);

bblsort.c

# include "bblsort.h"

N extern char Line [] [LINESIZE]; [

/* bubble sort */

void bblsort(n) register int n; /* число строк */

int lexcmp();

void swap();

regicterint i,j;

for (i = 1; i <= n - 1; i++) for(J=n- 1;j>= i;J—) if (lexcmp(j - 1, J)) swapO - l.j);

/* лексикографическое сравнение двух строк */

static int lexcmp(i,J)

register int i, j; /* элементы массива строк */ {

strcmp();

extern int Revflg;

register int Ic;

Ic = strcmp(Line [i], Line [j]);

return ((Ic < 0 && Revflg)

(Ic > 0 && '.Revflg));

/* обмен строк */

static void swap(iJ) register int i, j; /* элементы массива строк */


char *strcpy();

char temp [LINESIZE];

strcpy(temp. Line [i]);

strcpy(Line [i], Lme [j]);

strepy(Line [j], temp);

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

А)

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

В)

Включаемые файлы обычно помещаются в начало некоторого файла программы. Файл stdio h содержит описания файлов stdm. stdout и константы NULL, необходимых для использования функций fgets() и fputsQ

С)

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

D) Если функция main() использует формальные параметры, то они должны быть описаны. Функция main() выполняется первой.

Е) Типы функций, вызываемых в теле функции, обычно описываются в начале тела функции.

F)

Переменная numlmes описана как локальная в блоке автоматическая целая переменная

G) По соглашению первый аргумент argv, передаваемый функции main(), является именем программы; argv [l] — это второй аргумент и argv[l] [О] - это первый символ второго аргумента. Обратите внимание, что перед обращением к argv [l ] [0] проверяется число аргументов argc, так как при отсутствии второго аргумента выражение argv [l] [О] не имеет смысла.

H)

Функция rdlmesQ возвращает целое число прочитанных строк, так как она определена как имеющая тип mt. Прилагательное static указывает, что функция используется только в данном файле.

I) Переменная i определена как локальная в блоке. Описание register — это попытка ускорить выполнение цикла for

J) Поскольку функция fgetsO возвращает значение типа char *,то это значение должно сравниваться с указателем на символ. Поэтому нулевой указатель NULL преобразуется к типу (char *).

К) Функция wrhnesO не возвращает значения, поэтому она опреде лена как имеющая тип void.

L)

Формальный параметр п определен как register для ускорения цикла.



М)

Включение файла bblsort.h определяет поименованную константу LINESIZE.

N)

Это описание массива Line относится ко всему последующему файлу исходного кода. Здесь описывается тип Line, но сам массив определен в предыдущем файле.

О)

Функция bblsortO не описывается как static, потому что она вызывается функцией main(), которая определена в другом файле.

Р)

Формальные параметры i и j объявлены с помощью одного описания. Порядок параметров в таком описании несуществен.

Q)

Глобальный признак Revfig должен быть описан как extern, чтобы показать, что эта переменная определена в другом файле. Это описание может располагаться в начале текущего файла вместе с описанием массива Line

R)

Значением этого выражения является или Истина (1), или Ложь (0), кодируемые целыми значениями. Поэтому функция 1ехстр() определена как имеющая тип int.

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


Замечание. Допустимо бесконечно большое число различных типов указателей и массивов. Далее следуют типовые примеры.

Указатель на основной тип

Пример

char *p,

Переменная р является указателем на символ, т. е. этой переменной должен присваиваться адрес символа. Указатель на указатель

Пример

char **t,

Переменная t - указатель на указатель символа. Одномерный массив

Пример

int a [50];

Переменная а - массив из 50 целых чисел. Двухмерный массив

Пример

char m[7][50];

Переменная m - массив из семи массивов, каждый из которых

состоит из 50 символов. Массив из семи указателей

Пример

char *G[7];

Массив G состоит из указателей на символы. Указатель на функцию

Пример

int (*f)();

f - указатель на функцию, возвращающую целое значение. Структуры Структура' объединяет логически связанные данные разных типов. Структурный тип данных определяется следующим описанием:

struct имя _структуры {

описания_элементов

Пример

struct dinner {

char * place,

float cost,

struct dinner *next, };

Структурная переменная описывается с помощью структурного типа.

Примеры

struct dinner week_days [7], /* массив структур */

struct dinner best_one, /* одна структурная переменная */

struct dinner *p; /* указатель на структурную переменную */ Поля бит в структурах Поле бит - это элемент структуры, определенный как некоторое число бит, обычно меньшее, чем число бит в целом числе. Поля бит предназначены для экономного размещения в памяти данных небольшого диапазона. Пр имер

struct bfeg {

unsigned int of_fldl 10;

unsigned int bf_fld2 :

Данная структура описывает 10-битовое поле, которое для вычислений преобразуется в значение типа unsigned int, и 6-битовое поле, которое обрабатывается как значение типа unsigned int. Объединения Объединение' описывает переменную, которая может иметь любой тип из некоторого множества типов.

Определение объединенного типа данных аналогично определению структурного типа данных

union имя объединения { описания_элементов },


Пример

union bigword {long bg_long, char *bg_char[4]}

Данные типа union bigword занимают память, необходимую для размещения наибольшего из своих элементов, и выравниваются в памяти к границе, удовлетворяющей ограничениям по адресации как для типа long, так и для типа char * [4]

Описание переменной объединенного типа

Пример

union bigword x,

union bigword *p,

union bigword a[l00],

7.6. Перечисления

Данные перечислимого типа относятся к некоторому ограниченному множеству данных Определение перечислимого типа данных

enum имя _перечислимого типа { список значении}

Каждое значение данного перечислимого типа задается идентификатором

Пример

enum color {

red green yellow

}

Описание переменной перечислимого типа

Пример

enum color chair,

enum color suite [40];

Использование переменной перечислимого типа в выражении

Пример

char = red,

suite [5] '= yellow

7.7. Переименование типов Формат

typedef старый _тип новый_тип

Примеры

typedef long large

/* определяется тип large эквивалентный типу long */

typedef char *stnng

/* определяется тип string эквивалентный типу char * */ Переименование типов используется для введения осмысленных или сокращенных имен типов, что повышает понятность программ, и для улучшения переносимости программ (имена одного типа данных могут различаться на разных ЭВМ)

Условная компиляция


Командные строки препроцессора используются для условной ком пиляции различных частей исходного текста в зависимости от внешних условий # if константное _выражение

Пример

#if АВС + 3

Истина, если конвтантное выражение АВС + 3 не равно нулю #fdef идентификатор

Пример

#ifdef АВС

Истина, если идентификатор АВС определен ранее командой #define #ifndef идентификатор

Пример

#ifndef АВС

Истина, если идентификатор АВС не определен в настоящий момент #else

#endif

Если предшествующие проверки #if, #ifdef или #ifndef дают значение Истина, то строки от #else до #endif игнорируются при компиляции

Если эти проверки дают значение Ложь, то строки 01 проверки до #else (а при отсутствии #else — до #endif) игнорируются Команда #endif обозначает конец условной компиляции

Пример

#ifdef DEBUG

iprmtf (stderr, "location x = %d\ n", x), #endif

Условный оператор if-else


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

Если выражение истинно, то выполняется оператор. Если выражение ложно, то ничего не делается.

Пример

if (а == х) temp = 3;

temp = 5,

if (выражение)

оператор} else

оператор2

Если выражение истинно, то выполняется оператор и управление передается на оператор, следующий за оператором (т. е. оператор2 не выполняется). Если выражение ложно, то выполняется оператор2.

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

Примеры

Часть else относится ко второму оператору if:

if (х > 1) if (у==2)

z= 5; else z=6,

Часть else относится к первому оператору if if(x>l){ if (У==2)

z= 5; else z= б,

Вложенные операторы

if (х == 'а') У=1,

else if (x=='b'){

У=2; z=3; у else if (x == 'c')

У =4; else

printf("ERROR"),

Верификатор lint


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

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

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

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


Замечание. Командная строка #include может встречаться в любом месте программы, но обычно все включения размешаются в начале файла исходного текста.

#include (имя _файла'1

Пример

#mclude <riath.h>

Препроцессор заменяет эту строку содержимым файла math.h. Угловые скобки обозначают, что файл math.h будет взят из некоторого стандартного каталога (обычно это /usr/mclude). Текущий каталог не просматривается. #include "имя_файла"

Пример

#mclude "ABC"

Препроцессор заменяет эту строку содержимым файла ABC. Так как имя файла заключено в кавычки, то поиск производится в текущем каталоге (в котором содержится основной файл исходного текста). Если в текущем каталоге данного файла нет, то поиск производится в каталогах, определенных именем пути в опции -1 препроцессора Если и там файла нет, то просматривается стандартный каталог

Временные файлы


Замечание. Для выполнения описанных в этом подразделе функций необходимо включить в программу файл stdio.h

tmpnam — создать временное имя файла.

Определение: char *tmpnam (s) char *s;

tempnam - создать временное имя файла, используя каталог dir и

префикс файла pfx.

Определение: char *tempnam (dir, pfx) char *dir, *pfx,

mktemp - создать уникальное имя файла по шаблону, записанному

в строке template.

Определение: char *mktemp (template) char *template;

tmpfile - создать временный файл.

Определение: FILE *tmpfile ()

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


fgets — прочитать строку из входного потока, включая символ новой строки.

Определение: char *fgets (s, n, stream) char *<;

int n, FILE *stream;

gets — прочитать строку из стандартного файла ввода stdm. Определение: char *gets(s) char *s;

fputs - записать строку в поток stream. Определение: int fputs (s, stream) char *s;

FILE *stream, puts — записать строку в стандартный файл вывода stdout. В конце

строки записывается символ новой строки. Определение: int puts (s) char *s;

Ввод символа

fgetc — прочитать следующий символ из входного потока stream Определение: int fgetc (stream) FILE •stream;

Замечание. Функции getc(), getchar(), ungetc() являются макроопределениями.

getc - прочитать следующий символ из входного потока.

Определение: int getc (stream)

FILE *stream, getchar - прочитай, следующий символ из стандартного файла ввода.

Определение: int getchar ()

ungetc - вернуть символ во входной поток. Определение: int ungetc (с, stream) int с, FILE * stream;

Выполнение команд языка shell


Замечание. Для выполнения описанной в этом подразделе функции необходимо включить в программу файл stdio.h командой

#include <stdio.h>

system - выполнить команду языка shell, описанную как строка string.

Определение: int system (string) char * string

Вывод символа


fputc — записать символ в поток. Определение: int fputc (с, stream) int с;

FILE * stream;

Замечание. Функции putc() и putchar() являются макроопределениями.

putc - записать символ в поток. Определение: int putc (с, stream) int с, FILE *stream,

putchar - записать символ в стандартный файл вьшода. Определение: int putchar (с) int с;

Вызов функции


Существуют два способа вызова функции:

имя_функцш (е1, е2,. .., eN)

(указатель _на_функцию) (е1, е2,..., eN) Указатель _на_функцию - это переменная, содержащая адрес функции. Адрес функции может быть присвоен указателю оператором

указатель_на_функцию = имя _функции;

Аргументы (фактические параметры) передаются по значению, т. е. каждое выражение el, . . . , eN вычисляется и значение передается функции, например, загрузкой в стек. Порядок вычисления выражений и порядок загрузки значений в стек не гарантируются. Во время выполнения не производится проверка числа или типа аргументов, переданных функции. Такую проверку можно произвести с помощью программы lint до компиляции (см. с. 78). Вызов функции - это выражение, значением которого является значение, возвращаемое функцией. Описанный тип функции должен соответствовать типу возвращаемого значения. Например, если функция linfunc возвращает значение типа double, то эта функция должна быть описана до вызова:

extern double ' linfunc();

Замечание. Такое описание не определяет функцию, а только описывает тип возвращаемого значения; оно не нужно, если функция определена в том же файле до ее вызова (см. с. 42).

Примеры

Правильно: extern double linfuncO;

float у;

у = linfunc (3.05, 4.0, le-3), Значение функции перед присваиванием переменной у преобразуется из типа double в тип float.

Неправильно: float х;

float у;

х=3.05;

у = linfunc (х, 4, le-3);

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

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


#define идентификатор строка

Пример

#define ABC 100

Заменяет каждое вхождение идентификатора ABC а тексте программы на 100.

#undef идентификатор

Пример

#undef ABC Отменяет предыдущее определение для идентификатора ABC.

Зависимость от ЭВМ


Не полагайтесь на определенный размер машинного слова.

Размер данных типа mt зависит от размера машинного слова, различающегося у разных ЭВМ. Если вы не уверены в результате операций над целыми числами, используйте тип long, чтобы избежать проблемы переполнения.

Не гарантируется, что размер данных типа int совпадает с размером машинного слова. В языке Си определяется только, что размер данных типа short меньше или равен размеру данных типа

int. который, в свою очередь, меньше или равен размеру данных типа long. Размер слова может сказаться на обработке двоичных масок. Пример

#define MASK 0177770 /* неправильно*/

mt х,

х &= MASK;

В этом примере три правых бита целого х будут обнуляться только в том случае, если данные типа mt занимают 16 бит. Но если размер данных типа int больше 16 бит, то кроме этого будут обнуляться левые биты данного х. Чтобы избежать таких проблем, используйте следующее макроопределение:

#define MASK (~07) /* правильно */ int х;

х &=- MASK,

Этот пример корректен для всех ЭВМ независимо от размера

данных типа int

Тщательно проверяйте операции сдвига.

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

Перед сдвигом преобразуйте целые значения к типу unsigned. На некоторых ЭВМ сдвиг выполняется логически, т. е. освобождающиеся разряды обнуляются. На других сдвиг производится арифметически, и освобождающиеся разряды заполняются значением знакового разряда. Однако в языке Си гарантируется, что значения типа unsigned сдвигаются логически.

Используйте поименованные константы.

Использование в программе числовых констант, особенно когда смысл их неочевиден, является плохим стилем программирования. Числовые константы лучше определять в программе символическими именами, связанными с числовыми константами командой препроцессора #defme. Такие определения легко находить и модифицировать, если они размещены в некотором стандартном месте. Обычно это начало программы или файл заголовка.


Пример

#define SCREENWIDTH 80

Такое определение позволяет использовать поименованную константу SCREENWIDTH вместо числа 80. Определяйте размер объекта операцией sizeof Для определения размера некоторого объекта часто используют константы, что снижает мобильность программ. Использование операции sizeof позволяет решить эту проблему.

Пример

#define NUMELEM(ARRAY)

(sizeof(ARRAY) / sizeof(*(ARRAY))) Такое макроопределение обеспечивает мобильный способ определения числа элементов в массиве ARRAY.

Не используйте несколько символов в одной символьной константе.

Поскольку символьные константы представляются значениями типа int, определение языка Си позволяет в принципе задать символьную константу, состоящую из нескольких символов. Однако порядок размещения символов в машинном слове различен на разных ЭВМ.

Не полагайтесь на внутреннюю кодировку целых чисел.

Большинство ЭВМ представляет целые числа в дополнительном коде, но некоторые - в обратном коде. Поэтому не используйте возможности, которые предоставляет дополнительный код. Например, сдвиг на 1 бит влево отрицательного числа (чтобы уменьшить его значение в два раза) не приведет к желаемому результату на ЭВМ с обратным двоичным кодом. ' Формат чисел с плавающей точкой различен на разных ЭВМ.

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

Не полагайтесь на определенный порядок и число байт в слове.

Число байт и порядок их размещения а машинном слове различны у разных ЭВМ.

Пример

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

#define STDOUT 1

putchar(c), /* неправильно */

mt с;

write(STDOUT, (char *) &c, 1);

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



Не полагайтесь на определенное число бит в байте.

Поскольку число бит в байте у разных ЭВМ различно, то не предполагайте, что байт всегда занимает 8 бит. Чтобы определить число бит в байте, используйте поименованную константу, содержащуюся в стандартном файле: /usr/include/values.h

Замечание. Все системные файлы-заголовки размещаются в каталоге /usr/include.

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

Пример

Если на данной ЭВМ допустимы символы со знаком, то существует опасность, что индексация символом некоторого массива приведет к выходу за его границы:

#define TABSIZE 256 char с;

extern char table [TABSIZE];

с = table [c]; /* неправильно */

Мобильность программ на языке Си 83

Чтобы избежать этого, опишите переменную с как имеющую тип unsigned char или индексируйте таблицу значением, преобразованным к типу unsigned char.

Пример

Следующий фрагмент программы ошибочен - конец файла никогда не будет обнаружен, если символы представляются как беззнаковые значения:

#include <stdio.h> char с; /* неправильно */ if ((с = getcharQ) != EOF)

Если значение символа не может быть отрицательным, то переменная с никогда не станет равной поименованной константе EOF, которая равна -1. Библиотечная функция getchar (см. с. 64) возвращает значение типа int, поэтому с надо описать как переменную типа int. Не комбинируйте разные поля бит.

Не используйте поля бит для представления данных на внешних носителях.

Поля бит можно сделать мобильными, если не объединять разные поля. Максимальный размер поля бит зависит от размера машинного слова, и поля бит не могут пересекать границу слова. Кроме того, порядок размещения полей в слове (слева направо или справа налево) зависит от типа ЭВМ.



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

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

Учитывайте выравнивание при изменении значения указателя.

Если вы преобразуете указатели из одного типа в другой, то

при выполнении программы может возникнуть ошибка адресации вследствие ограничений на выравнивание данных в машинной памя ти Используйте библиотечную функцию malloc (см с 70),воэвра щающую указатель на символ, выравненный в памяти в соответствии с требованиями данной ЭВМ, так что этот указатель может быть преобразован в указатель любого типа

Следите за сравнением указателей, имеющих знак.

Некоторые ЭВМ выполняют сравнение указателей с учетом знака, другие делают беззнаковое сравнение Это различие несущественно, если сравнивать указатели, содержащие правильные адреса Если указателю будет присвоено значение -1, то в зависимости от ЭВМ оно бупет рассматриваться или как наибольшее допустимое значение, или как недопустимое значение (меньше минимально до пустимого)

Единственная константа, которую можно "безопасно" присваивать указателю, - это нуль, преобразованный к типу соответствующего указателя

Следите за переполнением значения указателей.

Арифметические преобразования указателей могут привести к переполнению или потере значимости Такие циклические преобра зования значений (от наибольшего к наименьшему или наоборот) могут возникнуть при адресации массива, расположенного в начале или в конце машинной памяти



Пример

Этот фрагмент программы показывает возможность появления потери значимости

struct large x [SIZE] *p, /* неправильно */ for (p=x [SIZE l],p>=x,p -)

Если массив х расположен в начале памяти, то возможна ситуация, при которой х - 1 будет не меньше, а больше х вслед ствие перехода через нижнюю границу диапазона значений ука зателей (потери значимости)

Не полагайтесь на конкретную кодировку символов.

Не- используйте в программе предположения, что символы в кодовом наборе располагаются последовательно

Пример

char с,

if (с >= а && с <= 'z ) /* неправильно */ Такая проверка символа с на принадлежность к строчным буквам не является мобильной Чтобы такая проверка правильно выполнялась на других ЭВМ, сделайте так char с,

if (islower(c)) /* правильно */

Библиотечная функция islower (см с 67) определена в библи отеке стандартных функций ввода-вывода и является машин но-зависимой Поскольку ее спецификация мобильна, то данная функция обеспечивает мобильную проверку символов Учтите, что разные символьные коды могут отличаться по числу входящих в них символов Не используйте разность значений двух букв для вычисления лексикографического расстояния между ними

Не используйте программные трюки, зависящие от аппаратуры.

Любое повышение эффективности выполнения программы, достигаемое за счет знания особенностей конкретной ЭВМ, обычно не оправдывает связанную с этим потерю мобильности

Хорошо организованные программы

Программа называется хорошо организованной, если ее легко читать, модифицировать, эксплуатировать и, следовательно, переносить на другие ЭВМ

Все определения, связанные с конкретной операционной средой и конкретной ЭВМ, помещайте в файл заголовка.

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



Пример

#include <values.h>

С помощью этой команды в программу включается стандартный файл заголовка /usr/include/values.h, который содержит аппаратные константы.

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

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

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

Для локализации программных фрагментов, зависящих от ЭВМ, используйте функции, условную компиляцию и команду #define.

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

Фрагменты исходного кода, зависящие от аппаратуры, заключайте в команды условной компиляции (см. с. 54).

Пример

Следующий фрагмент программы описывает стек, который может наращиваться в разных направлениях в зависимости от аппаратных особенностей ЭВМ. int *stackptr,

#ifdef MACHINE

*--stackptr = datum; /* растет вниз */

#else

*++stackptr = datum; /* растет вверх */

#endif

Для локализации характеристик конкретной ЭВМ можно использовать макроопределения (см. с. 53).

Пример

#defme BITSPERBYTE 8

#define BITS(TYPE)\

(sizeof(TYPE) * BITSPERBYTE)

Спецификация поименованной константы BITSPERBYTE мобильна, реализация - не мобильна. В макроопределении BITS мобильна как спецификация, так и реализация'.

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

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



В файле /usr/mclude/varargs. h описаны средства для мобильного определения функций с переменным числом аргументов. Например, библиотечная функция pintf реализована с использованием этих средств.

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

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

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

Спецификацией здесь называется определяемая лексема, а реализацией - определяющее константное выражение.

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

Используйте описание typedef для локализации определения типов данных, зависящих от ЭВМ.

Описание typedef обеспечивает локальные определения типов тех данных, которые зависят от конкретной ЭВМ Если вы измените определение типа, заданное описанием typedef, то соответственно изменятся все переменные, описанные с помощью этого производного типа Система обеспечивает набор стандартных определений в файле

/usr/mclude/sys/types h

Пример

typedef unsigned short ino_t, /* индекс файла */

Этот пример показывает типичное использование определения

типа в файле /usr/mclude/sys/types h

Зависимость от компилятора


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