Краткий справочник по языку Си








Начнём с комментариев…


// Однострочный комментарий

/*
Многострочный комментарий
*/


Булевые типы

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


hello world

Всё выше описаное, можно смело применять в программировании Arduino...

Брайан Керниган, Деннис Ритчи 3-е издание.



  • +362
  • 56903
Поддержать автора


Telegram-чат istarik

Задать вопрос по статье
Telegram-канал istarik

Известит Вас о новых публикациях






Комментарии (0)

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