На главную страницу | Новости | FAQ | Ссылки | Для детей | Контакты
Язык Си (без ++) - один из основных языков для программирования микроконтроллеров, поскольку здесь требуется высокая скорость, а оперативной памяти не бывает много.
Это текст программы компиляторов типа AvrStudio, CodeVisionAVR и т.п.
#include // заголовочный файл для ввода-вывода
#include
#define BV(x) (1 << x)
int main(void)
{
DDRC=0xFF; // порт PORTC настроен на выход
PORTC=0xFF; // установка уровней на порте PORTC
while(1) // начало цикла
{
PORTC=~(BV(5)); // переключение 5-го бита порта PORTC
_delay_ms(1000); // задержка 1 секунда
PORTC=BV(5); // переключение 5-го бита порта PORTC обратно
_delay_ms(1000);
}
return 0;
}
Функция main - это точка входа в программу, с которой компьютер начинает выполнение программы.
Допускается из main возвращать void, хотя это не по стандарту, так что лучше int.
В функцию main можно передавать аргументы командной строки:
int main(int argc, char* argv[]) { }
Вообще говоря, мы можем писать программу для MK AVR также на языке Processing/Wiring. Это тот же Си, но упрощенный. Но компилироваться это будет только в Arduino IDE или т.п., а потом можно загружать полученный hex в наш микроконтроллер. При этом не обязательно, чтобы МК стоял на плате Arduino. Разницы то нет.
Вот так выглядит аналогичная программа на Processing/Wiring:
int ledPin = 13;
void setup()
{
pinMode(ledPin, OUTPUT);
}
void loop()
{
digitalWrite(ledPin, HIGH);
delay(1000);
digitalWrite(ledPin, LOW);
delay(1000);
}
Здесь не надо подключать хеддеры для МК, т.к. они подключатся автоматом. Но для внешних модулей могут понадобится. Короче, про Ардуино подробнее читайте здесь
а пока мы вернемся к языку Си.
- куча - для динамического выделения памяти
- стек - локальные переменные класса памяти auto (включая аргументы функций)
- DATA - константы
- CODE - исполняемый код, инструкции процессора
-Базовые типы данных: 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 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, когда писать могут только снаружи.
auto - локальные переменных (по умолчанию) - программный стек.
register - просьба компилятору положить переменную в регистр ЦПУ (но он эту просьбу редко выполняет);
extern - объявление (declaration) переменных, но не определение (definition) (определение где-то в другом месте); определение может идти ниже по файлу (но как глобальная) или в другом файле.
static - статические локальные переменные, которые хранят своё значение между вызовами функций (они предпочтильнее, чем глобальные переменные). Статические глобальные переменные видны только в пределах данного файла.
--
Внешние и статические объекты существуют и сохраняют свои значения на протяжении всего времени выполнения программы.
Автоматические и регистровые объекты создаются и существуют только внутри блока, в котором они описаны, и уничтожаются при выходе из этого блока.
- внутреннее (локальное) - внутри блока {...}
- внешнее (глобальное) - вне всех блоков
Идентификатор, описанный внутри блока, известен только в этом блоке (локальный идентификатор).
Идентификатор, описанный на самом внешнем уровне, известен от места появления этого описания до конца входного файла, в котором он описан (глобальный идентификатор).
Стоит избегать использования глобальных имен.
Переменные с классом памяти 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);
- Унарные операторы выполняются справа-налево.
- Бинарные выполняются слева-направо.
- Присваивание выполняется справа-налево.
Порядок можно менять с помощью скобок!
Примеры:
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;
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).
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
{ инструкции на ассемблере }
1. Директивы (#include)
2. Функция main();
3. Описание локальных переменных (в языке "С" лок. переменные должны идти перед инструкциями, но в С++ рекомендуется объявлять переменные в месте их использования).
4. Исполняемые инструкции.
5. Оператор завершения: return 0; (хотя компилятор Си сам добавляет return 0; автоматически) - под ОС.
В программе должна быть ровно одна функция 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);
С++ (в отличии от С) не поддерживает автоматического преобразования типов. Поэтому в С++ надо:
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 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 pw { int i; char ch; }; // объявление объединения (в конце точка с запятой)
union pw wrd; // объявление переменной
wrd.i = 10; wrd.ch = 'A';
Здесь выделяется 4 байта под pw, символ ch помещается в байт номер 0, int занимает 4 байта.
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 инструкция // компилятор будет игнорировать директиву, если он не может распознать содержащуюся в ней инструкцию (исп-ся для переносимости)
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 );
int rand(void) // генерация случ. числа в диапазоне от 0 до RAND_MAX
void srand(unsigned seed); // устанавливает начальное состояние генератора.
void exit(int status); // завершает процесс выполнения программы,
Now 24.11.24 6:20:44, Your IP: 18.119.122.140; arduino.zl3p.com/cpp/01_c
ePN