Краткий справочник по языку Си
Начнём с комментариев…
// Однострочный комментарий
/*
Многострочный комментарий
*/
Булевые типы
true — верно
false — не верно
Подключение файлов с помощью #include
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
Файлы в < угловых скобочках > будут подключаться из стандартной библиотеки.
Свои файлы необходимо подключать с помощью " двойных кавычек ".
#include "header.h"
Функции
Объявление функций должно происходить в .h-файлах или в начале .c-файла.
void function_1();
void function_2();
Точка входа в программу – это функция main(). Работа программы начинается с неё, вне зависимости от места расположения в коде.
int main()
{
Ваш код
}
Для вывода в консоль используется printf
%d – означает, что будем выводить целое число
\n переводит указатель на новую строчку
printf("%d\n", 0);
Напечатает 0
Типы
int обычно имеет длину 4 байта
int x_int = 0;
short имеет длину 2 байта
short x_short = 0;
char имеет длину 1 байт
char x_char = 0;
Одиночные символы заключаются в ' одинарные кавычки '
char y_char = 'y';
long как правило занимает от 4 до 8 байт
long long занимает как минимум 64 бита
long x_long = 0;
long long x_long_long = 0;
float это 32-битное число с плавающей точкой (дробное число)
float x_float = 0.0;
double это 64-битное число с плавающей точкой
double x_double = 0.0;
Целые типы могут быть беззнаковыми
unsigned short ux_short;
unsigned int ux_int;
unsigned long long ux_long_long;
sizeof(T) возвращает размер переменной типа Т в байтах
sizeof(object) возвращает размер объекта object в байтах.
printf("%zu\n", sizeof(int));
Если аргуметом sizeof будет выражение, то этот аргумент вычисляется во время компиляции кода (кроме динамических массивов)
int a = 1;
size_t это беззнаковый целый тип который использует как минимум 2 байта для записи размера объекта
size_t size = sizeof(a++); // a++ не выполнится
printf("sizeof(a++) = %zu, где a = %d\n", size, a);
Выведет строку «sizeof(a++) = 4, где a = 1» (на 32-битной архитектуре)
Можно задать размер массива при объявлении
char my_char_array[20]; // Этот массив занимает 1 * 20 = 20 байт
int my_int_array[20]; // Этот массив занимает 4 * 20 = 80 байт (сумма 4-битных слов)
Можно обнулить массив при объявлении
char my_array[20] = {0};
Индексация массива происходит также как и в других Си-подобных языках
my_array[0];
Массивы изменяемы, как и другие переменные
my_array[1] = 2;
printf("%d\n", my_array[1]);
Массив может быть объявлен динамически, размер не обязательно рассчитывать при компиляции
printf("Enter the array size: "); // спрашиваем юзера размер массива
char buf[0x100];
fgets(buf, sizeof buf, stdin);
size_t size = strtoul(buf, NULL, 10); // strtoul парсит строку в беззнаковое целое
int var_length_array[size]; // объявление динамического массива
printf("sizeof array = %zu\n", sizeof var_length_array);
Вывод программы (в зависимости от архитектуры) будет таким:
sizeof array = 40
Строка – это просто массив символов, оканчивающийся нулевым (NUL (0x00)) байтом, представляемым в строке специальным символом '\0'. Его не нужно вставлять в строку, компилятор всё сделает сам.
%s — означает, что будем выводить строку
char a_string[20] = "This is a string";
printf("%s\n", a_string);
printf("%d\n", a_string[16]);
напечатает 0
17, 18, 19 и 20-ый байты, тоже будут равны нулю
Если между одинарными кавычками есть символ – это символьный литерал, но это тип int, а не char (по историческим причинам).
int cha = 'a'; // правильно
char chb = 'a'; // тоже правильно, int преобразовывается в char
Операторы
Переменные можно объявлять через запятую
int i1 = 1, i2 = 2;
float f1 = 1.0, f2 = 2.0;
Арифметика
i1 + i2; // => 3
i2 - i1; // => 1
i2 * i1; // => 2
i1 / i2; // => 0 (0.5, но обрезается до 0)
f1 / f2; // => 0.5, плюс-минус погрешность потому что, цифры с плавающей точкой вычисляются неточно!
Операции сравнения
== — равно
!= — не равно (символ ! — отрицание, применяется в разных конструкциях)
>, < — больше-меньше
<= — меньше или равно
>= — больше или равно
3 == 2; // неверно
3 != 2; // верно
3 > 2; // верно
3 < 2; // неверно
2 <= 2; // верно
2 >= 2; // верно
В Си, нет булевого типа, вместо него используется int. 0 это false, всё остальное это true.
Логика
! — отрицание
&& — логическое И
|| — логическое ИЛИ
!3;
1 && 1;
0 || 1;
Битовые операторы
~0x0F; // => 0xF0 (побитовое отрицание)
0x0F & 0xF0; // => 0x00 (побитовое И)
0x0F | 0xF0; // => 0xFF (побитовое ИЛИ)
0x04 ^ 0x0F; // => 0x0B (исключающее ИЛИ (XOR))
0x01 << 1; // => 0x02 (побитовый сдвиг влево (на 1))
0x02 >> 1; // => 0x01 (побитовый сдвиг вправо (на 1))
Структуры ветвления
Условный оператор
if — если
else if — иначе если
else — иначе
if (что-то) {
printf("I am never run\n");
} else if (что-то) {
printf("I am also never run\n");
} else {
printf("I print\n");
}
Цикл с предусловием
while — выполняется пока выражение не примет значение false
int i = 0;
while (i < 10) {
printf("%d, ", i++); // напечатает "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "
}
Цикл с постусловием
do while — код выполнится хотя бы один раз
Условие проверяется в конце цикла, а не в начале, так что код в теле цикла будет выполнен по крайней мере один раз.
int kk = 0;
do {
printf("%d, ", kk); // напечатает "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "
} while (++kk < 10);
Цикл со счётчиком
for — Цикл будет работать до тех пор, пока i < 10, при этом после каждой итерации переменная i будет увеличиваться на 1
int jj;
for (jj=0; jj < 10; jj++) {
printf("%d, ", jj); // напечатает "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "
}
Ветвление с множественным выбором
switch — это оператор управления, выбирает какой-то из вариантов case и выполняет содержащейся в нём код.
switch (1) {
case 1: // значения могут быть цифрой, символом или строкой
do_stuff(); // что-то делает...
break; // выходит из цикла switch
case 2:
do_something_else();
break;
default: // если не было совпадений, то выполняется блок default:
fputs("ничего не совпало\n", stderr);
exit(-1);
break;
}
Форматирование вывода
Каждое выражение в Си имеет тип, но можно привести один тип к другому.
int x_hex = 0x01; // Вы можете назначать переменные с помощью шеснадцатеричного кода
printf("%d\n", x_hex); // 1
printf("%d\n", (short) x_hex); // 1
printf("%d\n", (char) x_hex); // 1
Целые типы могут быть приведены к вещественным и наоборот.
printf("%f\n", (float)100); // %f formats a float
printf("%lf\n", (double)100); // %lf formats a double
printf("%d\n", (char)100.0);
Указатели
Для того чтобы объявить указатель, который будет ссылаться на переменную, необходимо сначала получить адрес этой переменной. Чтобы получить адрес памяти переменной (её расположение в памяти), нужно использовать знак & перед именем переменной. Это позволяет узнать адрес ячейки памяти, в которой хранится значение переменной. Эта операция называется — операция взятия адреса и выглядит вот так:
int var = 5; // простое объявление переменной с предварительной инициализацией
int *ptrVar; // объявили указатель, однако он пока ни на что не указывает
ptrVar = &var; // теперь наш указатель ссылается на адрес в памяти, где хранится число 5
В третьей строке использовалась операция взятия адреса, мы взяли адрес переменной var и присвоили его указателю ptrVar.
Рассмотрим программу, которая наглядно покажет всю мощь указателей:
#include <stdio.h>
int main()
{
int var; // обычная целочисленная переменная
int *ptrVar; // целочисленный указатель (ptrVar должен быть типа int, так как он будет ссылаться на переменную типа int)
ptrVar = &var; // присвоили указателю адрес ячейки в памяти, где лежит значение переменной var
scanf( "%d", &var ); // в переменную var положили значение, введенное с клавиатуры
printf( "%d\n", *ptrVar ); // вывод значения через указатель
getchar();
}
Указатель – это переменная которая хранит адрес в памяти.
При объявлении указателя указывается тип данных переменной на которую он будет ссылаться.
Можно получить адрес любой переменной, а потом работать с ним.
Используйте & для получения адреса переменной.
int x = 0;
printf("%p\n", (void *)&x); // Напечатает адрес в памяти, где лежит переменная x (%p выводит указатель на void *)
Для объявления указателя нужно поставить * перед именем.
int *px, not_a_pointer; // px это указатель на int
px = &x; // сохранит адрес x в px
printf("%p\n", (void *)px); // Напечатает адрес в памяти, где лежит переменная px
printf("%zu, %zu\n", sizeof(px), sizeof(not_a_pointer)); // Напечатает "8, 4" в 64 битной системе
Для того, чтобы получить значение по адресу, напечатайте * перед именем.
printf("%d\n", *px); // Напечаатет 0, значение перемененной x
Вы также можете изменять значение, на которое указывает указатель.
(*px)++; // Увеличивает значение на которое указывает px на единицу
printf("%d\n", *px); // => Напечатает 1
printf("%d\n", x); // => Напечатает 1
Массивы
Используются для большого количества однотипных данных.
int x_array[20];
int xx;
for (xx = 0; xx < 20; xx++) {
x_array[xx] = 20 - xx; // объявление массива (x_array) с значениями 20, 19, 18,... 2, 1
}
Строки это массивы символов, но обычно они представляются как указатели на первый элемент массива.
Хорошей практикой считается использование `const char *' при объявлении строчного литерала. При таком подходе литерал не может быть изменён.
const char *my_str = "This is my very own string literal";
printf("%c\n", *my_str); // 'T'
char foo[] = "foo";
foo[0] = 'a'; // это выполнится и строка теперь "aoo"
Функции
Синтаксис объявления функции:
<возвращаемый тип> <имя функции>(аргументы)
int add_two_ints(int x1, int x2) {
return x1 + x2; // Используйте return для возврата значения
}
Приставка void означает, что функция ничего не возвращает
void str_reverse(char *str_in) {
char tmp;
int ii = 0;
size_t len = strlen(str_in); // `strlen()` является частью стандартной библиотеки
for (ii = 0; ii < len / 2; ii++) {
tmp = str_in[ii];
str_in[ii] = str_in[len - ii - 1]; // ii-тый символ с конца
str_in[len - ii - 1] = tmp;
}
}
char c[] = "This is a test.";
str_reverse©;
printf("%s\n", c); // покажет ".tset a si sihT"
Типы и структуры определяемые пользователем
typedef — используется для задания стандартным типам своих названий
typedef int my_type;
my_type my_type_var = 0;
Структуры — это просто коллекция данных, память для которых выделяется последовательно.
struct rectangle {
int width;
int height;
};
sizeof(struct rectangle) == sizeof(int) + sizeof(int) – не всегда верно из-за особенностей компиляции.
void function_1() {
struct rectangle my_rec;
// Доступ к структурам через точку
my_rec.width = 10;
my_rec.height = 20;
// Можно объявить указатель на структуру
struct rectangle *my_rec_ptr = &my_rec;
// Можно через указатель получить структуру
(*my_rec_ptr).width = 30;
// Можно использовать оператор -> для лучшей читабельночти
my_rec_ptr->height = 10; // то же что и (*my_rec_ptr).height = 10;
}
Можно применить typedef к структуре, для удобства.
typedef struct rectangle rect;
int area(rect r) {
return r.width * r.height;
}
Если структура большая, то (чтоб не копировать) её можно получить «по указателю».
int area(const rect *r) {
return r->width * r->height;
}
Указатели на функции
Во время исполнения, функции располагаются по известным адресам в памяти.
Указатель на функцию может быть использован для непосредственного вызова функции.
Однако синтаксис может сбивать с толку.
Пример использования str_reverse по указателю
void str_reverse_through_pointer(char *str_in) {
// Определение функции через указатель.
void (*f)(char *); // сигнатура должна полность совпадать с целевой функцией.
f = &str_reverse; // присвоение фактического адреса
//Имя функции (как и массива) возвращает указатель на начало.
(*f)(str_in); // вызываем функцию через указатель.
}
Первая программа на Си — «Hello World»
Создать в любом текстовом редакторе файл hello world.c
#include <stdio.h>
int main (void)
{
puts ("Hello, World!");
return 0;
}
Компиляция...
gcc hello.c -o hello
Запуск...
~/hello
Всё выше описаное, можно смело применять в программировании Arduino...
Брайан Керниган, Деннис Ритчи 3-е издание.
- +362
- stD
60983
Поддержать автора
Комментарии (0)