На главную страницу | Новости  |  Ссылки | Контакты

Spyphy Farnsworth
Квантовая реальность. Кибернетика. Искусственный интеллект


Программирование на чистом Си (без ++)


Не смотря на постоянный рост количества языков программирования, язык Си (без ++) остается одним из основных языков разработки программного обеспечения, особенно под линуксом, а также широко используется для программирования микроконтроллеров, где нужно быстродействие и ограничены ресурсы памяти.

Это не учебник по Си, а скорее краткая справка, где перечислены основные моменты, которые необходимо знать для программирования на этом языке. Что касается базовых основ, то их читайте у классиков - Шилдт, Страуструп и т.п.

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


 #include "stdio.h"
 int main() {
     printf("Hello World \n");
     return 0;
}

Функция main - это точка входа в программу, с которой компьютер начинает выполнение программы.

Допускается из main возвращать void, хотя это не по стандарту, так что лучше int.

В функцию main можно передавать аргументы командной строки:

int main(int argc, char* argv[]) { }

Структура памяти программы:


- куча - для динамического выделения памяти

- стек - локальные переменные класса памяти auto (включая аргументы функций)

- DATA - константы

- CODE - исполняемый код, инструкции процессора

Некоторые элементы синтаксис языка:


// и /* */ - комментарии

\ - команда продолжается на следующей строке

"go" "to" - воспринимается как одна строка "goto"

Типы данных в Си


-Базовые типы данных: char, int, float, double.

-Модификаторы знака: signed, unsigned.

-Модификаторы знака: long, short.

void - тип без значения

При этом:

int = signed int = signed // 16 или 32 бит (зависит от платформы )
unsigned = unsigned int	
char = signed char	// 8 бит (от -128 до 127) (ASCII)	
unsigned char		// 8 бит (от 0 до 255)
wchar_t 				// UNICODE

В Си логический тип реализован неявно (с помощью int): false = нуль, true = не нуль.

Введение псевдонимов для ранее описанных типов данных:



typedef тип имя

где тип - любой существующий тип данных, имя - новое имя для этого типа.

Пример: typedef unsigned char byte;

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


Если операнды операции имеют разные типы, то происходит неявное приведение типов:

double a = 1.222;
int i = a;		// дробная часть отсекается!
double x = 2/5;	// результат будет 0 !  
 

(чтобы здесь получить 0.4 нужно было бы написать x=2.0/5 или 2/5.0)

Явное приведение типов:

int a=2, b=5;
double b = (double)a / b;	// результат будет b=0.4

Принудительное преобразование типов:


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

(желательно вообще избегать преобразования типов)

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


Переменная представляет собой блок памяти, на который мы ссылаемся по её имени (идентификатору).

Декларация переменных (вместе с инициализацией):


[класс памяти] [квалификаторы] [модификаторы] тип идентификатор = инициатор;

Например,

static const unsigned char x = 100;

Здесь ";" - составляющая часть конструкции, завершающая часть.

Допустима (хотя и редко используется) запись: const x = 100; (по умолчанию int).

Квалификаторы (или "модификаторы доступа"): const, volatile.


const - означает, что переменные не могут изменяться во время выполнения программы; инициалиировать можно только при декларации;

volatile - содержимое переменной может измениться само собой (используется в многопоточных программах при взаимодействии процессов)

Возможен вариант const volatile, когда писать могут только снаружи.

Спецификторы хранения (описатель класса памяти): auto, register, extern, static.


auto - локальные переменных (по умолчанию) - программный стек.

register - просьба компилятору положить переменную в регистр ЦПУ (но он эту просьбу редко выполняет);

extern - объявление (declaration) переменных, но не определение (definition) (определение где-то в другом месте); определение может идти ниже по файлу (но как глобальная) или в другом файле.

static - статические локальные переменные, которые хранят своё значение между вызовами функций (они предпочтильнее, чем глобальные переменные). Статические глобальные переменные видны только в пределах данного файла.

--

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

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

Описание области действия идентификаторов (имен):


- внутреннее (локальное) - внутри блока {...}

- внешнее (глобальное) - вне всех блоков

Идентификатор, описанный внутри блока, известен только в этом блоке (локальный идентификатор).

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

Вообще стоит избегать использования глобальных имен.

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

В языке C/C++ предусмотрено три категории связей: внешние, внутренние связи и их отсутствие.

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

Ключевое слово extern указывает, что объявляемый объект обладает внешними связями в рамках всей программы.

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

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

Однако ключевое словое extern ставится только для объявления, но при определении переменной слово extern не ставится:

int b;		// определение (выделяется память). 
extern int b;	// объявление	(память не выделяется, только правила использования)
void func(int);
extern void func(int);	// для ф-ций extern можно не писать (т.к. не может возникнуть коллизии)

Определение должно быть только одним. Объявлений может быть много в пределах одного файла.

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

Если компилятор С встречает переменную, которая не была объявлена, то компилятор проверяет, соответствует ли она какой-либо глобальной переменной. Если это так, то компилятор предполагает, что эта переменная ссылается на глобальную.

Связь по внешним именам реализует линкер. Линкер работает только с именами и не обращает внимание не тип переменных. Поэтому если в разных файлах будет одно именя с разными типами, то программа запустится, но будет работать непредсказуемо. Чтобы это избежать можно все объявления (со словом extern) вынести в отдельный h-файл и включать его во все c-файлы.

Переменные с классом памяти static видны только в пределах текущего блока (для локальных) или в пределах файла (для объявленных глобально).

Статические переменные хранятся в сегменте данных (data) и по умолчанию инициализируются нулем. Т.е. память под static-переменные выделяется при старте программы и существует до конца программы.

Замечание: Инициализация выполняется одни раз при выделении памяти!

void f(void) {
    static int a = 1;    /* - это инициализация (но не присваивание!), 
			т. е. переменная инициализурется единицей только один раз при старте программы! 
			(или 0 по умолчанию, т.е. если бы было просто static int a;) */
    a++;
}

Статическими могут быть также функции. Такая ф-ция может исп-ся только внутри данного файла.

Следует различать присваивание и инициализацию:


- Присваивание: имя_переменной = выражение;

- Многочисленное присваивание: x = y = z = 0;

- Инициализация переменных: тип имя_переменной = константа;

Константы


Константы являются частью машинных команд и под них память не выделяется.

Константы бывают:

- целые:

10-я система: 127; -127; +127;

8-я система: 0127; (начинается с нуля - значит 8-ричная!)

16-я система: 0x7F; (x или X, f или F - регистр не влияет)

- вещественные: 3.14; 2. ; .25 (0 можно опускать); 2E3; 2e3; 2E-3; 2.0E+3;

- символьные: 8-битные ASCII: 'A', '=', '\n', '\t', '\370', '\xF8' (символ градуса);

- строковые литералы (в двойных кавычках): "Hello, world!\n". Строки заканчиваются нулевым байтом - '\0'.

Макроопределения:

 #define WIDTH 80	//(подробнее ниже)

Операции и операторы


Оператор (инструкция, англ. statement) - это единица выполнения программы.

В языке Си любое выражение, заканчивающееся символом "точка с запятой" (;), является оператором.

Фигурные скобки { } - это составной оператор.

Например,

{y = x;  x++;}

Кроме того { } является отдельным блоком и в нем можно определять локальные переменные.

; - пустой оператор.

Операции:


- Арифметические операторы: - + * / %

- Инкрименты и декрименты: ++a, --a, a++, a-- (могут выполняться быстрее)

- Операторы сравнения (отн-ний): > >= < <= == != (возвращают 1 или 0)

- Логические операторы: && || ! (возвращают 1 или 0)

- Битовые операторы: & | ^ ~ >> <<

- Оператор ?: x ? y : z, напр.: r = 10>9 ? 100 : 200

- sizeof - унарный оператор для вычисления размера переменной или типа

- , - оператор запятая (последовательное вычисление): a = (b=3, b+2);

Приоритеты операций:


1) ::->. [] () - разрешение контекста, извлечение; индекс массива, вызов ф-ии и преобр-ие типа;

2) - + (унарные), ++ -- ~ !, &(адрес) *(разрешение указателя), new, delete, sizeof;

3) * / %;

4) ->* .* извлечение;

5) + - (бинарные);

6) << >> сдвиги;

7) < <= > => сравнение;

8) == != равно, не равно;

9) & побитовое И;

10) ^ XOR (исключающее ИЛИ);

11) | побитовое ИЛИ;

12) && И логическое;

13) || ИЛИ логическое;

14) ?: тернарная операция (x ? y : z);

15) = *= /= %= += и т.д. - операция присвоения [Справа-налево];

16) , следование.

Порядок выполнения операторов:


- Унарные операторы выполняются справа-налево.

- Бинарные выполняются слева-направо.

- Присваивание выполняется справа-налево.

Порядок можно менять с помощью скобок!

Примеры:

a=10;  r=!!a==a;  // результат будет 0, т. к. !!a вернет 1;

Выражение а + b + c интерпретируется как (а + b) + с.

r = (2==2==2);  // результат будет 0, т. к. 2==2 вернет 1 и сравнит с 2;  - и вычисляется слева направо.
5 < 3 < 2;	// результат будет 1, т. к. 5<3 вернет 0;
a = b = c = 2;	// сначала c=2, потом b=c, потом a = b;

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

Например, результат y = (x=10)*2 + x*y будет зависеть от компилятора.

4 исключения: && и || (всегда вычисляет 1-ый оператор; причем если он равен true(false), то 2-ой не вычисляется); условие (x ? y : z); оператор запятая (,).

Поэтому условие if-else можно реализовать так:

a == b && printf("yes") || printf("no");

sizeof() - возвращает длину в байтах переменной или типа; sizeof(int); sizeof(a);

sizeof выражение; - само выражение не вычисляется.

Оператор запятая:


x = (y = 3, y+1);

левая сторона оператора вычисляется как void и не выдаёт значения, переменной x присвается значение выражения в правой стороне, т.е. y+1.

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


& - оператор "получение адреса" объекта;

* - доступ к значению объекта по указанному адресу;

p = &number;  // - получение адреса переменной number;
q = *p;  // - получение значения переменной по указанному адресу:
st.a;	   // - обращение к полю структуры st;
pst->a;	//  -  обращение к полю структуры по её указателю;

Указатели:


Указатель -- такая переменная, которая хранить адрес некоторого объекта и связана с типом этого объекта.

Объявление указателя:


класс_памяти квалификатор тип * квалификатор идентификатор = инициатор;

Минимальный вариант:


тип *имя;

Примеры:

int *p;
int x, *px;
const char *s;		// нельзя менять данные, на которые ссылается указатель s
char * const t = s;	// нельзя менять адрес (т. е. значение t)

Основные операции над указателями:


p = q; - копирование адреса. Обычно одно типа. Если разного типа, то это не безопасно!

Указатель p может ссылаться на тип void (используется в C для обобщенных алгоритмов).

p = NULL; - нулевой указатель - это признак отсутствия значения у указателя. Такой указатель нельзя использовать для доступа к памяти, т. к. это приведет к сбою программы во время выполнения.

Арифметика указателей отличается от обычной и зависит от типа:

шаг = sizeof(type);

Унарные операции * и ++ имеют одинаковый приоритет и выполняются справа налево, т. е.

	*++p 	==   *(++p);	
	++*p 	==   ++(*p);
	*p++ 	==  *(p++); 		(инкримент указателя)
	(*p)++;			(инкримент значения по адресу p)

Например,

   int x[2] = {0,0};
   int *p = &x[0];
   ++*p;		// изменится x[0] на 1
   *++p = 1;		// изменится x[0] на 1  (сначала ++p, потом присваивание)
   (*p)++;		// изменится x[0] на 1 (сначала ссылка на переменную, потом её инкримент)
   *p++ = 1;		// изменится x[1] на 1  (сначала присваивание *p=1, потом p++)

int x=4;
int *p = &x;
int **q = &p;		// двухуровневая глубина косвенной адресации.
**q;	// вернет 4, то  **q = x;
 

Передача адреса в функцию:


void func(int *a)  {  *a++; }
int x = 4;
func(&x);	// передача адреса (после этого будет x=5).

Ссылки:



Type& Name

int a;           //переменная с именем "a" типа int размещена по адресу 0xbfd86d6c
int &ra = a; //задано альтернативное имя (ra) для переменной по адресу 0xbfd86d6c

Например,

int A = 5;
int& refA = A;
refA = 6;

Эквивалентные конструкции:


a[i]        =    *(a+i) 
mass	   =    &mas[0]
(*p).f     =    p->f

---------------

Операторы управления программой


Условный оператор:


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

Тринадный оператор:


выражение1 ? выражение2 : выражение3

пример:

y = x > 9 ? 100 : 200

Оператор выбора:


switch (выражение) {			// выполняет проверку только строгих равенств
   case константа_1: операторы; break;   	// если break отсутствует, то продолжается выполение след.
   case константа_2: операторы; break;
      ...
   default: операторы; 
}

Циклы:


1) for (инициализация; условие; изменение) операторы;
2) while (условие) оператор;
3) do { операторы } while (условие);

Операторы передачи управления:


break; - принудительное окончание всего цикла (выход только из объемлющей конструкции);

continue; - переход на новую итерацию (к проверке условия)

goto метка; - переход по метке (не рекомендуется);

return; или return выражение; -- выход или возврат выражения из функции

Ассемблерная вставка в Си:


_asm
{ инструкции на ассемблере }

Общая структура программы на Си и функция main()


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


1. Директивы (#include)

2. Функция main();

3. Описание локальных переменных (в языке "С" лок. переменные должны идти перед инструкциями, но в С++ рекомендуется объявлять переменные в месте их использования).

4. Исполняемые инструкции.

5. Оператор завершения: return 0; (хотя компилятор Си сам добавляет return 0; автоматически) - под ОС.

Функция main()


В программе должна быть ровно одна функция main().

-1) программа под управлением ОС.

В стандарте описано всего две перегруженных функции main:

int main();	
int main(int argc, char *argv[]);	 //где argc - кол-во аргументов, argv[0] - имя exe-файла

Не правильно писать main(void), т. к. ОС передает некоторые параметры.

При этом ф-ция main() должна возвращать некоторые значение - код возврата, передаваемый операционной системе, с помощью оператора return.

-2) программа для микроконтроллера:

void main(void) {}

Здесь пишется main(void), т.к. ОС там нет.

Функции пользователя



тип имя_функции (параметры) {тело функции}

Пример:

double sum (double x, double y)
{
	double z = x + y;
	return z;
}

return x; - возврат значения

return; - немедленный выход из функции

тип = void - означает, что функция не возвращает значения.

аргументы = void - означает, что функция не получает никаких аргументов.

В конце функции-процедуры return можно не дописать (компилятор добавить автоматически)

Передача параметров в функции:


1) по значению - копирование содержимого аргумента в формальный параметр

2) по ссылке - копируется адрес аргумента,

Пример:

int f(int *x) {return *x;}
int y; 
f(&y); 

Прототип функции:


тип имя_функции (параметры);

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

Пример: double pow (double, double); // названия аргументов можно не указывать.

func(...) - если многоточие в прототипе, то кол-во аргументов не считается.

Возврат указателя из функции:


int *func(int x; return &x;)

Указатели на функции:


int (*f)(int, int);

Указатель на функцию - это адрес точки входа в соответствующую функцию.

double (*pf)(double);	 // объявление указателя на функцию (скобки обязательны) (можно локально)
pf = sin;  // присвоить указателю ссылку на конкретную функцию 
(*pf)(x);  // вызов функции по указателю

где pf - указатель на функцию, *pf - сама функция, а (*pf)(x) - обращение к этой функции.

pf(x);		// упрощенная форма
integral(0., 3.14, 20, pf);  или integral(0., 3.14, 20, sin);  //   передача имени функции как параметра в другую функцию:    
double integral(double a, double b, int n, double (*f)(double))  { f(a); или (*f)(a);}

Пример:

int func1(int x, int y) {return x*y;}  
f = func1;  
y=f(2,3);

Можно также использовать массив указателей на функции:

int f1(int x) {return 1*x;}
int (*mf[3])(int) = {f1, f2, f3};  
f = mf[0];  
f(3);  
mf[0](3);

При вызове функции в программый стек помещаются (в следующем порядке):

- 1) аргументы функции

- 2) адрес возврата (из функции)

- 3) локальные переменные

Массивы в Си


Массив в ОЗУ занимает непрерывный блок (без разрывов).

В C/C++ нумерация массива начинается с нуля: a[0] - 1-ый элемент, a[SIZE-1] - последний. В Си нет контроля выхода за пределы массива! Типичный цикл:

for(i=0; i < SIZE; ++i) { ... a[i] ... }

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

- Одномерный массив: тип имя_массива [размер];

тогда общее число байт = sizeof(тип)*размер

- Многомерный массив: тип имя_массива [размерN]...[размер2][размер1];

- с инициализацией: тип имя_массива [размерN]...[размер1] = {список значений};

пр.:

int m[3][2] = {a11, a12, a21, a22, a31, a32};  - матрица из 3 строк и 2 столбца

тоже самое что:

int m[3][2] = {{a11, a12}, {a21, a22}, {a31, a32}};  	// добавление скобок для красоты

В случае инициализации можно не указывать размер массива (посчитает автоматически):

int b[] = {1,2,3,4,5};
const int n = sizeof b / sizeof (int);	// узнать кол-во элементов, или
const int n = sizeof b / sizeof b[0];	// узнать кол-во элементов
сhar str[] = "hello";     
//или тоже самое:  
сhar str[6] = {'h','e','l','l','o','\n'};   

(строка - это массив типа char)

Передача массивов или строк в функцию:


func(char str[]);
int func(int array[][MAXLEN], int rows, int columns)

Индексация указателей:


a[j][k]  == *((тип *) a + (j*длина_строки) + k)

Имя массива есть адрес первого элемента массива!!! Примеры:

 &a[0][0]  == a;	
 &p[5]  == *(p+5);	// 6-ой элемент массива;
 double list[10], *ptr = list;
 list		// адрес элемента list[0]
 list + 1		// адрес элемента list[1] и т. д.  (автоматически масштабируется под размер типа)
 ptr	== &list[0];
 ptr + i	== &list[i]	 // - адрес list[i];
 *(ptr + i) == ptr[i] == list[i];		// ptr[i] - сокращенная запись

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

Пример.

double *p = list + 2;	// тогда p[0] = list[2]
p[-2];		// == list[0];

Передача массивов в функции.


Два способа:

1) с индексированием:

int func(int a[])  {   x = a[i];   }

2) с помощью указателя:

int func(int *a)  {    x = *a++;  y = *a;   }

3) комбинированные варианты:

int func(int a[])  {    x = *a;   }
int func(int *a)  {    x = a[i];   }

ОДНАКО между указателями и массивами есть 2 большие разницы:

- имя массива есть указатель-константа, т. е. нельзя изменить его значение;

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

Указатель можно настроить на блок памяти 2-мя способами:

- под ранее выделенный блок памяти;

- выделить память динамически из кучи (выделяется ына этапе выполнения программы).

Динамические массивы


Создание динамического массива (использует свободную память, называемую кучей). Функции библиотеки

(здесь size_t - тип для измерения размера памяти для данной платформы).

void *malloc (size_t число_байт); - выделение памяти и возврат указателя типа void*; возвращает NULL в случае ошибки.

void *сalloc (size_t кол-во_блоков, size_t число_байт); - выделяет nitems*size байт и кроме того выполняется очистка памяти (обнуление).

void *realloc( void *block, size_t newsize ); - изменяет первоначальный размер выделенного блока (при необходимости осуществляется перенос данных в новое место в памяти).

void free (void *p); - возвращает выделенную память в кучу, где p - указатель на блок (важно, чтобы этот указатель был таким же как при выделении памяти! поэтому его желательно сделать const).

Пример:

extern int n;k
int *const p = malloc(n * sizeof(int));
if (!p) { ... }
for (i=0; i < n; i++) { p[i]; }
free(p);

Замечание про C++:


С++ (в отличии от С) не поддерживает автоматического преобразования типов. Поэтому в С++ надо:

	char *p;  p = (char *) malloc(1000);	// получение 1000 байт
	int *m; m = (int *) malloc(1000*sizeof(int));   // память под массив из 1000 чисел типа int

В С++ рекомендуется использовать new.

Строки как массивы


Примеры:

const char *p = "string";	

Здесь p - указатель на константную строку. Сама строка хранится в сигменте данных (read only). Слово const защищает от попытки записать в что-то в эту строку (иначе возможна ошибка при выполнении).

int main(int argc, const char *argv[], const char *env[])
//или тоже самое:    
int main(int argc, const char **argv, const char **env)
void strcopy(char *t, const char *s) { while(*t++ = *s++); }

Структуры, объединения, перечисления


Структуры (struct)


Описание (объявление структуры:

    struct date { char day; char mon; int year; } ;//	(после объявления точка с запятой!)

Определение структры, т. е. выделение памяти:

    struct data today;

Можно, но не рекомендуется (для больших проектов) совмещать описание и определение переменных:

   struct employee { int age;} emp, *pt=&emp;

Можно создать псевдоним

	typedef struct date date;

и затем писать короче:

	date today;
	

Или сразу с псевдонимом:

	typedef struct {char day, mon; int year;} date;

Определение с инициализацией:

	date birthday = {1, 4, 1980};

Доступ к элементам структуры - с помощью операции точка (.):

	today.day = 1;

Указатель на структуру:

	data *p = &today;

Доступ через указатель с помощью операции ( -> ):

p -> day     ==    (*p).day    ==  today.day;

Битовые поля


Битовое поле - это специальный тип структуры, который определяет, какой будет длина в битах каждого элемента структуры.

В С битовое поле может рассматриваться:

- как целое со знаком - signed int (по умолчанию просто int),

- или без знака - unsigned int.

Битовые поля длиной 1 должны объявляться как unsigned, поскольку 1 бит не может иметь знака.

struct имя структуры {
 тип имя1: длина;
 тип имя2: длина;
 ...
 тип имяN: длина;
 }
 

Пример:

struct fields 
{
	int i : 2;
	unsigned j : 5;
	int k : 1;
} a;

Объединения (union)


Предназначены для хранения нескольких переменных разных типов в (точнее совпадает адрес начала):

union pw { int i; char ch; }; 			// объявление объединения (в конце точка с запятой)
union pw wrd;				// объявление переменной
wrd.i = 10; wrd.ch = 'A';

Здесь выделяется 4 байта под pw, символ ch помещается в байт номер 0, int занимает 4 байта.

Перечисления (enum)


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

	enum имя {список_перечислений} список_переменных; 

По умолчанию, целые значения присваиваются элементам перечисления в возрастающем порядке, начиная с нуля. Имя не обязательно. Пример:

	enum Keyword {Q, W, E, R, T, Y};   // компилятор нумерует по порядку 0,1,2,...
	enum Keyword {Q=-1, W=2, E, R, T, Y};   // со своей нумерацией (не обязательно)
	enum Keyword key;	// создание переменной.
	key = T;
	if (key == T) cout << "key = T" << endl;

-----------------

Препроцессор, директивы


Код обрабатывается препроцессором перед тем, как поступить на вход компилятора.

(C++ не рекомендует исп-ть препроцессор и предлагает более безопасный путь)

Директивы - команды препроцессора. Начинаются с символа # (заканчиваются переводом строки)

 #include	 // вставляет вместо этой строки текст всего файла.
 #include <имя_файла>	// стандарные хэдер-файлы
 #include "имя_файла"	// поиск в текущем проекте или по указанному пути, напр. "..\\..\\file.h"

Основные стандартные хэдер-файлы:


stdio.h - ф-ции ввода/вывода

stdlib.h - функции стандартной библиотеки (STL)

math.h - математические функции

limits.h и float.h - системно-зависимые значения

ctype.h , string.h , time.h , stdarg.h , locale.h - другие стандартные заголовочные файлы

conio.h - нестандартные функции работы с консолью

Макроопределения


 #define PI 3.14		// правило макроподстановки (рекомендуется большыми буквами)
 #define WIDTH 80
 #define LENGTH (WIDTH+10)
 #define MAX(x,y) ((x)>(y))?(x):(y)
 #define VAR(i,j) i##j		// склейка элементов - замена на ij
 #define SQR(x) x*x
 SQR(y-1);	// будет преобразовано в y-1*y-1
 //поэтому правильно
 #define SQR(x) (x)*(x)
 #undef идентификатор	// отмена макроопределения

В С++ (в отличии от С) константные переменные можно использовать для определения размера массива (позволяет избавиться от использования #define):

	const int size=10;
	int m[size];

#define TRACE(flag) printf(#flag "=%d\n",flag)
 
val = 1024; TRACE(val);

Макросы и встраиваемые функции - сравнение:

 #define SOUNDERON()  PORTD.7=1
 inline void SounderON() {  PORTD.7 = 1; }

- результат тот же, но 2-ой вариант более современный для с++ (и удобнее).

Директивы условной компиляции:


 #if константное_выражение
 #ifdef идентификатор
 #ifndef идентификатор
 #elif константное_выражение
 #else
 #endif
 #if defined(symbol)    // эквивалентна  #ifdef symbol
 #if !defined(STUDENT_H) // эквивалентно #ifndef STUDENT_H

Защита от повторного включения хэдер-файла file.h!

 #ifndef FILE_H
 #define FILE_H
 // содержимое файла
 #endif

Другие директивы:

 #line номер_строки
 #error сообщение
 #pragma инструкция	// компилятор будет игнорировать директиву, если он не может распознать содержащуюся в ней инструкцию (исп-ся для переносимости)

--------------------

Ввод/вывод в C


putchar(ch);	//  вывод символа
puts(str);	//  функция выводит строку символов на экран.
printf("x=%d", i);	// форматированный вывод
%d и %i - целое в 10-ной записи,  %o - 8-ричная,  %x - 16-ричная
%f, %e, %g (%E, %G)	// с плавающей точной
%c - символ,  %s  - строка, %p - адреса (или указатели).

char ch = getchar();		// получить символ
char *gets(char *string);	// нет контроля. не рекомендуется
scanf()	 - ввод в заданном формате. Например:
scanf("x=%d, y=%2d", &x, &y);	- не забывать ставить & !!!

//Обмен с массивом в памяти:
int sprintf( char *buffer, const char *format,... ); 
int sscanf( const char *buffer, const char *format,...);

//Преобразование числовых значений в символьное представление:
char *itoa( int  value, char *string, int radix );	 //	(radix - основание системы счисления)
char *ltoa( long value, char *string, int radix );
char *gcvt( double value, int ndec, char *str ); 	//	(ndec - кол-во значащих циф)

//Преобразование строк в числа:
int atoi( const char *string );
long atol( const char *string );
long  strtol( const char *string, char **endptr, int radix );
double strtod( const char *string, char **endptr );

Файлы


stdio.h - более высокий уровень, чем io.h, но более низкий, чем в С++.

В Си работа с файли реализована в форме потоков байт.

В начале выполнения программы среда исполнения С открывает 3 предопределенных потока:

stdin - стандартный входной поток,

stdout - стандартный выходной поток,

stderr - поток для сообщений об ошибках.

По способу организации обмена потоки бывают:

1) Небуферизованные (stderr);

2) Буферизованные:

- Строчно-буферизованные (stdin , stdout);

- Блочно-буферизованные (для обмена с дисковыми файлами).

По способу преобразования передаваемой информации:

- Бинарные;

- Текстовые.

FILE - структура для работы с файлом, определена в .

FILE *fopen(const char *filename, const char *mode); // открывает файл с именем filename и возвращает указатель на связанный с этим файлом  поток или  NULL при ошибке.
int fclose(  FILE  *fp  );  // закрывает указанный файл поток ( 0 = ok,  EOF - ошибка),  предварительно опустошив его буфер.
int fcloseall( void );	// закрывает все файлы (потоки), кроме предопределенных.

mode - способ доступа к файлу ("+" - двунаправленный поток):

r - открыть только для чтения;

w - открыть новый файл для записи (сущ-щий файл перезаписывается);

a - открыть для записи в конец файла или создать новый, если файл не существует;

r+ - открыть существующий файл для чтения и записи;

w+ - создать новый файл для чтения и записи (сущ-щий файл перезаписывается);

a+ - открыть (или создать, если не существует) для чтения/записи в конце файла.

Для указания типа потока к способу доступа добавляется символ t (текстовый) или b (бинарный), например, wt, a+b и т.д. По умолчанию задается значением глобальной переменной _fmode

Стандартные функции:

int fgetc(FILE *stream);	// ввод 1 символа
int fputc (int ch, FILE *stream); 
char *fgets(char *string, int n, FILE *stream); // ввод строки, n - макс. количество байт
int fputs(const char *string, FILE *stream);
int fscanf (FILE *stream, const char *format,...); 
int fprintf(FILE *stream, const char *format,...); 
size_t fread (void *ptr, size_t size, size_t n,  FILE *fp ); 
size_t fwrite(void *ptr, size_t size, size_t n,  FILE *fp );
int fseek( FILE *fp,  long offset,  int whence ) // возвращает  0 при нормальном завершении и ненулевое  - при ошибке.
    SEEK_SET ( ==0 )  - начало файла;
    SEEK_CUR ( ==1 )  - текущее положение;
    SEEK_END ( ==2 )  - конец файла.
int  ferror( FILE *fp ); // возвращает ненулевое значение, если в результате операции с файлом  fp устанавливается индикатор ошибки.
int  feof( FILE *fp );   // возвращает ненулевое значение, если при операции с файлом  fp установился индикатор конца файла.
int  fflush( FILE *fp );  /* опустошает буфер указанного потока (если поток входной, 
		то буфер чистится, если выходной  - его содержимое выводится в поток),  
		поток остается открытым; функция возвращает 0 приуспешном завершении и  EOF при ошибке. Или */
int flushall( void );

Некоторые другие полезные функции

int rand(void)	 // генерация случ. числа в диапазоне от 0 до RAND_MAX
void srand(unsigned seed); //  устанавливает начальное состояние генератора.
void exit(int status); //  завершает процесс выполнения  программы, 
int system(const char *command); //  вызывает командный процессор для исполнения 

Пример:

system("pause");

Для Windows полезные команды: pause, cls.

---------------

Литература:


http://ru.wikipedia.org/wiki/Ссылка_(C%2B%2B)





galaxy