Изучите основы языка программирования C (СИ) всего за несколько часов 🔥

Изучите основы языка программирования C (СИ) всего за несколько часов 🔥 ◾️ C

Это руководство для начинающих на Си следует правилу 80/20. Вы выучите 80% языка программирования C за 20% времени.

Такой подход даст вам всесторонний обзор языка.

Это руководство не пытается охватить все, что связано с C. Оно фокусируется на основе языка, пытаясь упростить более сложные темы.

Введение в C

C, вероятно, является наиболее широко известным языком программирования. Он используется в качестве справочного языка для курсов по информатике во всем мире, и, вероятно, это язык, который люди изучают в школе больше всего, наряду с Python и Java.

Я помню, что это был мой второй язык программирования после Паскаля.

C — это не только то, что студенты используют для изучения программирования. Это не академический язык. И я бы сказал, что это не самый простой язык, потому что C — язык программирования довольно низкого уровня.

Сегодня C широко используется во встроенных устройствах и поддерживает большинство серверов Интернета, построенных с использованием Linux. Ядро Linux построено с использованием C, и это также означает, что C поддерживает ядро ​​всех устройств Android. Можно сказать, что код C работает в значительной части всего мира. Прямо сейчас. Довольно замечательно.

Когда он был создан, C считался языком высокого уровня, потому что он был переносимым между машинами. Сегодня мы как бы считаем само собой разумеющимся, что можем запускать программу, написанную на Mac, в Windows или Linux, возможно, используя Node.js или Python.

Когда-то это было совсем не так. То, что C привнесло в таблицу, было языком, который было просто реализовать, и в котором был компилятор, который можно было легко перенести на другие машины.

Я сказал компилятору: C — это компилируемый язык программирования, такой как Go, Java, Swift или Rust. Возможна интерпретация других популярных языков программирования, таких как Python, Ruby или JavaScript. Разница постоянна: скомпилированный язык генерирует двоичный файл, который можно напрямую выполнять и распространять.

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

C не скрывает сложности и возможностей машины. У вас будет много сил, если вы знаете, на что вы способны.

Я хочу представить вам первую программу на языке C, которую мы назовем «Hello, World!»

Привет

#include <stdio.h>

int main(void) {
    printf("Hello, World!");
}

Опишем исходный код программы: сначала импортируем stdioбиблиотеку (название — стандартная библиотека ввода-вывода).

Эта библиотека дает нам доступ к функциям ввода / вывода.

C — очень маленький язык по своей сути, и все, что не является частью ядра, предоставляется библиотеками. Некоторые из этих библиотек созданы обычными программистами и доступны для использования другими. Некоторые другие библиотеки встроены в компилятор. Вроде stdioи другие.

stdio— это библиотека, которая предоставляет printf()функцию.

Эта функция заключена в main()функцию. main()Функция является точкой входа любой программы C.

Но что же такое функция?

Функция — это процедура, которая принимает один или несколько аргументов и возвращает одно значение.

В случае main(), функция не получает аргументов и возвращает целое число. Мы идентифицируем это, используя voidключевое слово для аргумента и intключевое слово для возвращаемого значения.

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

Как printf()видите, функция написана иначе. Для него не определено возвращаемое значение, и мы передаем строку, заключенную в двойные кавычки. Мы не указали тип аргумента.

Это потому, что это вызов функции. Где-то внутри stdioбиблиотеки printfопределяется как

int printf(const char *format, ...);

Вам не нужно сейчас понимать, что это значит, но, вкратце, это определение. И когда мы вызываем printf("Hello, World!");, функция запускается именно здесь.

main()Функция мы определили выше:

#include <stdio.h>

int main(void) {
    printf("Hello, World!");
}

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

Как мы выполняем программу на C?

Как уже упоминалось, C — это компилируемый язык. Чтобы запустить программу, мы должны сначала ее скомпилировать. Любой компьютер с Linux или macOS уже поставляется со встроенным компилятором C. Для Windows вы можете использовать подсистему Windows для Linux (WSL).

В любом случае, когда вы открываете окно терминала, вы можете ввести gcc, и эта команда должна вернуть ошибку о том, что вы не указали какой-либо файл:

Это хорошо. Это означает, что компилятор C есть, и мы можем начать его использовать.

Теперь введите указанную выше программу в hello.cфайл. Вы можете использовать любой редактор, но для простоты я собираюсь использовать nanoредактор в командной строке:

Введите программу:

Теперь нажмите ctrl-Xдля выхода:

Подтвердите нажатием yклавиши, затем нажмите Enter, чтобы подтвердить имя файла:

Вот и все, теперь мы должны вернуться к терминалу:

Теперь введите

gcc hello.c -o hello

Программа не должна выдавать ошибок:

но он должен был создать helloисполняемый файл. Теперь введите

./hello

чтобы запустить его:

Я добавляю ./имя программы, чтобы сообщить терминалу, что команда находится в текущей папке.

Потрясающие!

Теперь, если вы позвоните ls -al hello, вы увидите, что размер программы всего 12 КБ:

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

Переменные и типы

C — это статически типизированный язык.

Это означает, что любая переменная имеет связанный тип, и этот тип известен во время компиляции.

Это сильно отличается от того, как вы работаете с переменными в Python, JavaScript, PHP и других интерпретируемых языках.

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

В этом примере мы инициализируем переменную ageс типом int:

int age;

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

Вы также можете инициализировать переменную при объявлении, указав начальное значение:

int age = 37;

После объявления переменной вы сможете использовать ее в своем программном коде. Вы можете изменить его значение в любое время, используя =оператор, например, как in age = 100;(при условии, что новое значение имеет тот же тип).

В таком случае:

#include <stdio.h>

int main(void) {
    int age = 0;
    age = 37.2;
    printf("%u", age);
}

компилятор выдаст предупреждение во время компиляции и преобразует десятичное число в целое число.

C встроенные типы данных intcharshortlongfloatdoublelong double. Давайте узнаем о них больше.

Целые числа

C предоставляет нам следующие типы для определения целочисленных значений:

  • char
  • int
  • short
  • long

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

charТипа обычно используются для хранения букв ASCII графика, но он может быть использован для хранения небольших целых чисел от -128до 127. Требуется минимум 1 байт.

intзанимает не менее 2 байтов. shortзанимает не менее 2 байтов. longзанимает не менее 4 байтов.

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

Нам гарантировано, что shortэто не дольше int. И мы гарантированно longне короче int.

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

Если вы программируете C на Arduino, разные платы будут иметь разные ограничения.

На плате Arduino Uno intхранит 2-байтовое значение в диапазоне от -32,768до 32,767. На Arduino MKR 1010 intхранит 4-байтовое значение в диапазоне от -2,147,483,648до 2,147,483,647. Довольно большая разница.

На всех платах Arduino shortхранит 2-байтовое значение в диапазоне от -32,768до 32,767longхранить 4 байта в диапазоне от -2,147,483,648до 2,147,483,647.

Беззнаковые целые числа

Для всех вышеуказанных типов данных мы можем добавить unsignedначало диапазона с 0 вместо отрицательного числа. Во многих случаях это может иметь смысл.

  • unsigned charбудет варьироваться от 0как минимум до255
  • unsigned intбудет варьироваться от 0как минимум до65,535
  • unsigned shortбудет варьироваться от 0как минимум до65,535
  • unsigned longбудет варьироваться от 0как минимум до4,294,967,295

Проблема с переливом

Учитывая все эти ограничения, может возникнуть вопрос: как мы можем убедиться, что наши числа не превышают лимит? А что будет, если мы превысим лимит?

Если у вас есть unsigned intчисло 255 и вы увеличиваете его, вы получите взамен 256. Как и ожидалось. Если у вас есть unsigned charчисло 255, и вы увеличиваете его, вы получите 0 взамен. Он сбрасывается, начиная с начального возможного значения.

Если у вас есть unsigned charчисло 255 и вы добавите к нему 10, вы получите число 9:

#include <stdio.h>

int main(void) {
  unsigned char j = 255;
  j = j + 10;
  printf("%u", j); /* 9 */
}

Если у вас нет значения со знаком, поведение не определено. Это в основном даст вам огромное количество, которое может варьироваться, например, в этом случае:

#include <stdio.h>

int main(void) {
  char j = 127;
  j = j + 10;
  printf("%u", j); /* 4294967177 */
}

Другими словами, C не защищает вас от выхода за пределы типа. Вам нужно позаботиться об этом самостоятельно.

Предупреждения при объявлении неправильного типа

Когда вы объявляете переменную и инициализируете ее неправильным значением, gccкомпилятор (тот, который вы, вероятно, используете) должен предупредить вас:

#include <stdio.h>

int main(void) {
  char j = 1000;
}
hello.c:4:11: warning: implicit conversion 
  from 'int' to
      'char' changes value from 1000 to -24
      [-Wconstant-conversion]
        char j = 1000;
             ~   ^~~~
1 warning generated.

А также предупреждает при прямых заданиях:

#include <stdio.h>

int main(void) {
  char j;
  j = 1000;
}

Но не если вы увеличиваете число, например, с помощью +=:

#include <stdio.h>

int main(void) {
  char j = 0;
  j += 1000;
}

Числа с плавающей запятой

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

Используя числа с плавающей запятой, мы представляем числа как десятичные числа, умноженные на степень 10.

Вы можете увидеть числа с плавающей запятой, записанные как

  • 1.29e-3
  • -2.3e+5

и другими, казалось бы, странными способами.

Следующие виды:

  • float
  • double
  • long double

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

Минимальные требования для любой реализации C — это то, что floatможет представлять диапазон от 10 ^ -37 до 10 ^ + 37 и обычно реализуется с использованием 32 бит. doubleможет представлять больший набор чисел. long doubleможет содержать еще больше чисел.

Точные цифры, как и для целых значений, зависят от реализации.

На современном Mac a floatпредставлен в 32-битном формате с точностью до 24 значащих битов. 8 бит используются для кодирования экспоненты.

doubleЧисло представлено в 64 битах, с точностью до 53 значащих бит. 11 бит используются для кодирования экспоненты.

Тип long doubleпредставлен 80 битами, имеет точность 64 значащих бита. 15 бит используются для кодирования экспоненты.

На вашем конкретном компьютере, как вы можете определить конкретный размер типов? Вы можете написать программу для этого:

#include <stdio.h>

int main(void) {
  printf("char size: %lu bytes\n", sizeof(char));
  printf("int size: %lu bytes\n", sizeof(int));
  printf("short size: %lu bytes\n", sizeof(short));
  printf("long size: %lu bytes\n", sizeof(long));
  printf("float size: %lu bytes\n", sizeof(float));
  printf("double size: %lu bytes\n", 
    sizeof(double));
  printf("long double size: %lu bytes\n", 
    sizeof(long double));
}

В моей системе, современном Mac, он печатает:

char size: 1 bytes
int size: 4 bytes
short size: 2 bytes
long size: 8 bytes
float size: 4 bytes
double size: 8 bytes
long double size: 16 bytes

Константы

Теперь поговорим о константах.

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

Как это:

const int age = 37;

Это вполне допустимый C, хотя обычно константы объявляются в верхнем регистре, например:

const int AGE = 37;

Это просто соглашение, но оно может очень помочь вам при чтении или написании программы на C, поскольку оно улучшает читаемость. Имя в верхнем регистре означает константу, имя в нижнем регистре означает переменную.

Имя константы следует тем же правилам для имен переменных: может содержать любую заглавную или строчную букву, может содержать цифры и символ подчеркивания, но не может начинаться с цифры. AGEи Age10являются допустимыми именами переменных, 1AGEне является.

Другой способ определения констант — использовать этот синтаксис:

#define AGE 37

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

Компилятор C определит тип из указанного значения во время компиляции.

Операторы

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

В частности, мы можем выделить различные группы операторов:

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

В этом разделе я собираюсь подробно описать их все, используя 2 мнимые переменные aи в bкачестве примеров.

Я исключил из этого списка побитовые операторы, операторы структуры и операторы указателей, чтобы все было проще.

Арифметические операторы

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

Бинарные операторы работают с двумя операндами:

ОПЕРАТОРИМЯПРИМЕР
=Назначениеa = b
+Добавлениеa + b
-Вычитаниеa - b
*Умножениеa * b
/Разделениеa / b
%По модулюa % b

Унарные операторы принимают только один операнд:

ОПЕРАТОРИМЯПРИМЕР
+Унарный плюс+a
-Унарный минус-a
++Инкрементa++ или же ++a
--Декрементa-- или же --a

Разница между a++и ++aзаключается в том, что a++значение aпеременной увеличивается на единицу после ее использования. ++aувеличивает aпеременную перед ее использованием.

Например:

int a = 2;
int b;
b = a++ /* b is 2, a is 3 */
b = ++a /* b is 4, a is 4 */

То же самое и с оператором декремента.

Операторы сравнения

ОПЕРАТОРИМЯПРИМЕР
==Равный операторa == b
!=Неравный операторa != b
>Больше чемa > b
<Меньше, чемa < b
>=Больше или равноa >= b
<=Меньше или равноa <= b

Логические операторы

  • !NOT (пример: !a)
  • &&И (пример: a && b)
  • ||ИЛИ (пример: a || b)

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

Составные операторы присваивания

Эти операторы полезны для выполнения присваивания и в то же время выполнения арифметической операции:

ОПЕРАТОРИМЯПРИМЕР
+=Дополнительное заданиеa += b
-=Присваивание вычитанияa -= b
*=Присваивание умноженияa *= b
/=Назначение дивизионаa /= b
%=Присваивание по модулюa %= b

Тернарный оператор

Тернарный оператор — единственный оператор в C, который работает с 3 операндами, и это короткий способ выражения условий.

Вот как это выглядит:

<condition> ? <expression> : <expression>

Пример:

a ? b : c

Если aоценивается true, то bинструкция выполняется, в противном случае c-.

Тернарный оператор функционально такой же, как условный оператор if / else, за исключением того, что он короче для выражения и может быть встроен в выражение.

размер

В sizeofвозвращаете оператора размера операнда вы передаете. Вы можете передать переменную или даже тип.

Пример использования:

#include <stdio.h>

int main(void) {
  int age = 37;
  printf("%ld\n", sizeof(age));
  printf("%ld", sizeof(int));
}

Приоритет оператора

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

Предположим, у нас есть такая операция:

int a = 2;
int b = 4;
int c = b + a * a / b - a;

В чем ценность c? Получаем ли мы, что сложение выполняется до умножения и деления?

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

В порядке от меньшего приоритета к большему у нас есть:

  • =оператор присваивания
  • +и - бинарные операторы
  • *и /операторы
  • +и -унарные операторы

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

В:

int c = b + a * a / b - a;

Сначала мы выполняем a * a / b, которое, поскольку оно написано слева направо, мы можем разделить на a * aи результат / b2 * 2 = 44 / 4 = 1.

Затем мы можем выполнить суммирование и вычитание: 4 + 1 — 2. Значение cравно 3.

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

Круглые скобки имеют более высокий приоритет над чем-либо еще.

Вышеупомянутый пример выражения можно переписать как:

int c = b + ((a * a) / b) - a;

и нам не нужно так много думать об этом.

Условные

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

Мы хотим сделать X в некоторых случаях и Y в других случаях.

Мы хотим проверять данные и делать выбор в зависимости от состояния этих данных.

C предоставляет нам 2 способа сделать это.

Первый — это ifоператор с его elseпомощником, а второй — это switchоператор.

если

В ifоператоре вы можете проверить выполнение условия, а затем выполнить блок, указанный в фигурных скобках:

int a = 1;

if (a == 1) {
  /* do something */
}

Вы можете добавить elseблок для выполнения другого блока, если исходное условие окажется ложным:

int a = 1;

if (a == 2) {
  /* do something */
} else {
  /* do something else */
}

Остерегайтесь одного распространенного источника ошибок — всегда используйте оператор ==сравнения при сравнении, а не оператор присваивания =. Если вы этого не сделаете, ifусловная проверка всегда будет истинной, за исключением аргументов 0, например, если вы это сделаете:

int a = 0;

if (a = 0) {
  /* never invoked */
}

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

Вы можете создать несколько elseблоков, сложив вместе несколько ifоператоров:

int a = 1;

if (a == 2) {
  /* do something */
} else if (a == 1) {
  /* do something else */
} else {
  /* do something else again */
}

выключатель

Если вам нужно сделать слишком много блоков if / else / if для выполнения проверки, возможно, потому, что вам нужно проверить точное значение переменной, это switchможет быть очень полезно для вас.

Вы можете указать переменную в качестве условия и серию caseточек входа для каждого ожидаемого значения:

int a = 1;

switch (a) {
  case 0:
    /* do something */
    break;
  case 1:
    /* do something else */
    break;
  case 2:
    /* do something else */
    break;
}

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

Вы можете добавить в конце «всеобъемлющее» дело с надписью default:

int a = 1;

switch (a) {
  case 0:
    /* do something */
    break;
  case 1:
    /* do something else */
    break;
  case 2:
    /* do something else */
    break;
  default:
    /* handle all the other cases */
    break;
}

Петли

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

Для петель

Первый и, вероятно, наиболее распространенный способ выполнения цикла — это цикл .

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

Как это:

for (int i = 0; i <= 10; i++) {
  /* instructions to be repeated */
}

(int i = 0; i <= 10; i++)Блок содержит 3 частей петлевых деталей:

  • начальное условие ( int i = 0)
  • тест ( i <= 10)
  • приращение ( i++)

Сначала мы определяем переменную цикла, в данном случае с именем ii— это общее имя переменной, которое будет использоваться для циклов, а также jдля вложенных циклов (цикл внутри другого цикла). Это просто условность.

Переменная инициализируется значением 0, и выполняется первая итерация. Затем он увеличивается, как говорит часть приращения ( i++в данном случае, увеличивается на 1), и весь цикл повторяется, пока вы не дойдете до числа 10.

Внутри основного блока цикла мы можем получить доступ к переменной, iчтобы узнать, на какой итерации мы находимся. Эта программа должна напечатать 0 1 2 3 4 5 5 6 7 8 9 10:

for (int i = 0; i <= 10; i++) {
  /* instructions to be repeated */
  printf("%u ", i);
}

Циклы также могут начинаться с большого числа и идти с меньшего числа, например:

for (int i = 10; i > 0; i--) {
  /* instructions to be repeated */
}

Вы также можете увеличить переменную цикла на 2 или другое значение:

for (int i = 0; i < 1000; i = i + 30) {
  /* instructions to be repeated */
}

Пока петли

Циклыfor while писать проще, чем цикл, потому что это требует от вас немного больше работы.

Вместо того, чтобы определять все данные цикла заранее, когда вы запускаете цикл, как вы это делаете в forцикле, whileвы просто проверяете условие:

while (i < 10) {

}

Это предполагает, что iон уже определен и инициализирован значением.

И этот цикл будет бесконечным, если вы не увеличите iпеременную в некоторой точке внутри цикла. Бесконечный цикл — это плохо, потому что он блокирует программу, не позволяя больше ничего делать.

Вот что вам нужно для «правильного» цикла while:

int i = 0;

while (i < 10) {
  /* do something */

  i++;
}

Есть одно исключение из этого, и мы увидим это через минуту. Прежде позвольте мне представить do while.

Делайте циклы while

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

Это делается с помощью do whileключевого слова. В некотором смысле это очень похоже на whileцикл, но немного отличается:

int i = 0;

do {
  /* do something */

  i++;
} while (i < 10);

Блок, содержащий /* do something */комментарий, всегда выполняется хотя бы один раз, независимо от проверки условия внизу.

Затем, пока не iстанет меньше 10, повторяем блок.

Выход из цикла с помощью break

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

Это делается с помощью breakключевого слова.

Это полезно во многих случаях. Вы можете проверить значение переменной, например:

for (int i = 0; i <= 10; i++) {
  if (i == 4 && someVariable == 10) {
    break;
  }
}

Наличие этой опции выхода из цикла особенно интересно для whileциклов (и do whileтоже), потому что мы можем создавать кажущиеся бесконечными циклы, которые заканчиваются при возникновении условия. Вы определяете это внутри блока цикла:

int i = 0;
while (1) {
  /* do something */

  i++;
  if (i == 10) break;
}

Такой цикл довольно часто встречается в C.

Массивы

Массив — это переменная, в которой хранится несколько значений.

Каждое значение в массиве на языке C должно иметь один и тот же тип . Это означает, что у вас будут массивы intзначений, массивы doubleзначений и многое другое.

Вы можете определить такой массив intзначений:

int prices[5];

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

Вы можете использовать константу для определения размера:

const int SIZE = 5;
int prices[SIZE];

Вы можете инициализировать массив во время определения, например:

int prices[5] = { 1, 2, 3, 4, 5 };

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

int prices[5];

prices[0] = 1;
prices[1] = 2;
prices[2] = 3;
prices[3] = 4;
prices[4] = 5;

Или, что более практично, используя цикл:

int prices[5];

for (int i = 0; i < 5; i++) {
  prices[i] = i + 1;
}

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

prices[0]; /* array item value: 1 */
prices[1]; /* array item value: 2 */

Индексы массивов начинаются с 0, поэтому в массиве из 5 элементов, как в pricesприведенном выше массиве, будут элементы в диапазоне от prices[0]до prices[4].

Что интересно в массивах C, так это то, что все элементы массива хранятся последовательно, один за другим. Не то, что обычно происходит с языками программирования более высокого уровня.

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

Подробнее об указателях скоро.

Strings

В C строки — это особый вид массива: строка — это массив char значений:

char name[7];

Я ввел этот char тип, когда представил типы, но вкратце он обычно используется для хранения букв диаграммы ASCII.

Строку можно инициализировать так же, как вы инициализируете обычный массив:

char name[7] = { "F", "l", "a", "v", "i", "o" };

Или, что более удобно, со строковым литералом (также называемым строковой константой), последовательностью символов, заключенной в двойные кавычки:

char name[7] = "Flavio";

Вы можете распечатать строку, printf()используя %s:

printf("%s", name);

Вы заметили, что «Флавио» состоит из 6 символов, но я определил массив длиной 7? Почему? Это связано с тем, что последний символ в строке должен быть   0значением, символом конца строки, и мы должны освободить для него место.

Это важно помнить, особенно при работе со строками.

Говоря об управлении строками, есть одна важная стандартная библиотека, предоставляемая C : string.h.

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

Вы можете загрузить библиотеку в свою программу, добавив сверху:

#include <string.h>

И как только вы это сделаете, у вас будет доступ к:

  • strcpy() скопировать строку поверх другой строки
  • strcat() чтобы добавить строку к другой строке
  • strcmp() для сравнения двух строк на равенство
  • strncmp()для сравнения первых nсимволов двух строк
  • strlen() для вычисления длины строки

и многие, многие другие.

Указатели

На мой взгляд, указатели — одна из самых запутанных / сложных частей Си. Особенно, если вы новичок в программировании, но также если вы пришли из языка программирования более высокого уровня, такого как Python или JavaScript.

В этом разделе я хочу представить их в простейшем, но не слишком упрощенном виде.

Указатель — это адрес блока памяти, который содержит переменную.

Когда вы объявляете целое число следующим образом:

int age = 37;

Мы можем использовать &оператор, чтобы получить значение адреса в памяти переменной:

printf("%p", &age); /* 0x7ffeef7dcb9c */

Я использовал %pформат, указанный в, printf()для печати значения адреса.

Мы можем присвоить адрес переменной:

int *address = &age;

Используя int *addressв объявлении, мы объявляем не целочисленную переменную, а скорее указатель на целое число .

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

int age = 37;
int *address = &age;
printf("%u", *address); /* 37 */

На этот раз мы снова используем оператор указателя, но поскольку на этот раз это не объявление, это означает «значение переменной, на которую указывает этот указатель».

В этом примере мы объявляем ageпеременную и используем указатель для инициализации значения:

int age;
int *address = &age;
*address = 37;
printf("%u", *address);

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

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

Массивы — один из примеров. Когда вы объявляете массив:

int prices[3] = { 5, 4, 3 };

pricesПеременное это указатель на первый элемент массива. Вы можете получить значение первого элемента, используя эту printf()функцию в этом случае:

printf("%u", *prices); /* 5 */

Круто то, что мы можем получить второй элемент, добавив к pricesуказателю 1:

printf("%u", *(prices + 1)); /* 4 */

И так далее для всех остальных значений.

Мы также можем выполнять множество хороших операций по манипулированию строками, поскольку строки — это скрытые массивы.

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

Функции

Функции — это способ структурировать наш код в подпрограммы, которые мы можем:

  1. дать имя
  2. позвони, когда они нам понадобятся

Начиная с самой первой программы, «Hello, World!», Вы сразу же пользуетесь функциями C:

#include <stdio.h>

int main(void) {
    printf("Hello, World!");
}

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

Вот еще одна функция:

void doSomething(int value) {
    printf("%u", value);
}

У функций есть 4 важных аспекта:

  1. у них есть имя, поэтому мы можем вызывать («вызывать») их позже
  2. они указывают возвращаемое значение
  3. у них могут быть аргументы
  4. у них есть тело, закутанное в фигурные скобки

Тело функции — это набор инструкций, которые выполняются каждый раз, когда мы вызываем функцию.

Если функция не имеет возвращаемого значения, вы можете использовать ключевое слово voidперед именем функции. В противном случае вы указываете тип возвращаемого значения функции ( intдля целого числа, floatдля значения с плавающей запятой, const char *для строки и т. Д.).

Вы не можете вернуть более одного значения из функции.

У функции могут быть аргументы. Они не обязательны. Если их нет, внутри скобок вставляем void, вот так:

void doSomething(void) {
   /* ... */
}

В этом случае, когда мы вызываем функцию, мы вызываем ее без каких-либо скобок:

doSomething();

Если у нас есть один параметр, мы указываем тип и имя параметра, например:

void doSomething(int value) {
   /* ... */
}

Когда мы вызываем функцию, мы передаем этот параметр в круглых скобках, например:

doSomething(3);

У нас может быть несколько параметров, и если это так, мы разделяем их запятыми как в объявлении, так и в вызове:

void doSomething(int value1, int value2) {
   /* ... */
}

doSomething(3, 4);

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

Если вы передадите указатель в качестве параметра, вы можете изменить значение этой переменной, потому что теперь вы можете получить к ней доступ напрямую, используя ее адрес в памяти.

Вы не можете определить значение по умолчанию для параметра. C ++ может это делать (и программы на языке Arduino могут), а C — нет.

Убедитесь, что вы определили функцию перед ее вызовом, иначе компилятор выдаст предупреждение и ошибку:

➜  ~ gcc hello.c -o hello; ./hello
hello.c:13:3: warning: implicit declaration of
      function 'doSomething' is invalid in C99
      [-Wimplicit-function-declaration]
  doSomething(3, 4);
  ^
hello.c:17:6: error: conflicting types for
      'doSomething'
void doSomething(int value1, char value2) {
     ^
hello.c:13:3: note: previous implicit declaration
      is here
  doSomething(3, 4);
  ^
1 warning and 1 error generated.

Предупреждение, которое вы получите в отношении заказа, о котором я уже упоминал.

Ошибка в другом, связанном. Поскольку C не «видит» объявление функции перед вызовом, он должен делать предположения. И предполагается, что функция вернется int. Однако функция возвращает void, отсюда и ошибка.

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

int doSomething(int value1, int value2) {
  printf("%d %d\n", value1, value2);
  return 1;
}

вы просто получите предупреждение, а не ошибку:

➜  ~ gcc hello.c -o hello; ./hello
hello.c:14:3: warning: implicit declaration of
      function 'doSomething' is invalid in C99
      [-Wimplicit-function-declaration]
  doSomething(3, 4);
  ^
1 warning generated.

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

Внутри функции вы можете объявлять переменные.

void doSomething(int value) {
  int doubleValue = value * 2;
}

Переменная создается в момент вызова функции и уничтожается, когда функция завершается. Снаружи не видно.

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

Ввод и вывод

C — небольшой язык, и «ядро» C не включает никаких функций ввода / вывода (I / O).

Конечно, это не что-то уникальное для C. Обычно ядро ​​языка не зависит от ввода-вывода.

В случае C ввод / вывод предоставляется стандартной библиотекой C через набор функций, определенных в stdio.hфайле заголовка.

Вы можете импортировать эту библиотеку, используя:

#include <stdio.h>

поверх вашего файла C.

Эта библиотека предоставляет нам, среди многих других функций:

  • printf()
  • scanf()
  • sscanf()
  • fgets()
  • fprintf()

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

У нас есть 3 вида потоков ввода-вывода в C:

  • stdin (стандартный ввод)
  • stdout (стандартный вывод)
  • stderr (стандартная ошибка)

С функциями ввода-вывода мы всегда работаем с потоками. Поток — это интерфейс высокого уровня, который может представлять устройство или файл. С точки зрения C, у нас нет никакой разницы в чтении из файла или чтении из командной строки: в любом случае это поток ввода-вывода.

Об этом нужно помнить.

Некоторые функции предназначены для работы с определенным потоком, например printf(), который мы используем для печати символов stdout. Используя его более общий аналог fprintf(), мы можем указать, в какой поток писать.

Раз printf()уж я начал говорить , давайте представим это сейчас.

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

В простейшей форме использования вы передаете ему строковый литерал:

printf("hey!");

и программа выведет на экран содержимое строки.

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

int age = 37;

printf("My age is %d", age);

Мы можем распечатать более одной переменной, используя запятые:

int age_yesterday = 37;
int age_today = 36;

printf("Yesterday my age was %d and today is %d", age_yesterday, age_today);

Существуют и другие спецификаторы формата, например %d:

  • %c для угля
  • %s для угля
  • %f для чисел с плавающей запятой
  • %p для указателей

и многое другое.

Мы можем использовать escape-символы в printf(), например, \nкоторые мы можем использовать для создания новой строки вывода.

scanf()

printf()используется как функция вывода. Я хочу , чтобы ввести функцию входа в настоящее время, так что мы можем сказать , что мы можем сделать все I / O вещи: scanf().

Эта функция используется для получения значения от пользователя, запускающего программу, из командной строки.

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

int age;

Затем мы вызываем scanf()с двумя аргументами: формат (тип) переменной и адрес переменной:

scanf("%d", &age);

Если мы хотим получить строку в качестве входных данных, помните, что имя строки является указателем на первый символ, поэтому вам не нужен &символ перед ним:

char name[20];
scanf("%s", name);

Вот небольшая программа, которая использует printf()и scanf():

#include <stdio.h>

int main(void) {
  char name[20];
  printf("Enter your name: ");
  scanf("%s", name);
  printf("you entered %s", name);
}

Переменная область видимости

Когда вы определяете переменную в программе C, в зависимости от того, где вы ее объявляете, она будет иметь другую область видимости .

Это означает, что в одних местах он будет доступен, а в других — нет.

Позиция определяет 2 типа переменных:

  • глобальные переменные
  • локальные переменные

В этом разница: переменная, объявленная внутри функции, является локальной переменной, например:

int main(void) {
  int age = 37;
}

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

Переменная, определенная вне функции, является глобальной переменной, как в этом примере:

int age = 37;

int main(void) {
  /* ... */
}

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

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

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

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

Внутри функции вы можете инициализировать статическую переменную с помощью staticключевого слова.

Я сказал «внутри функции», потому что глобальные переменные по умолчанию статичны, поэтому нет необходимости добавлять ключевое слово.

Что такое статическая переменная? Статическая переменная инициализируется значением 0, если начальное значение не указано, и сохраняет значение при вызовах функций.

Рассмотрим эту функцию:

int incrementAge() {
  int age = 0;
  age++;
  return age;
}

Если мы вызовем incrementAge()один раз, мы получим 1возвращаемое значение. Если мы вызовем его более одного раза, мы всегда получим 1 обратно, потому что ageэто локальная переменная, и она повторно инициализируется 0при каждом вызове функции.

Если мы изменим функцию на:

int incrementAge() {
  static int age = 0;
  age++;
  return age;
}

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

printf("%d\n", incrementAge());
printf("%d\n", incrementAge());
printf("%d\n", incrementAge());

даст нам

1
2
3

Мы также можем опустить инициализацию ageдо 0 static int age = 0;и просто написать, static int age;потому что статические переменные автоматически устанавливаются на 0 при создании.

У нас также могут быть статические массивы. В этом случае каждый отдельный элемент в массиве инициализируется значением 0:

int incrementAge() {
  static int ages[3];
  ages[0]++;
  return ages[0];
}

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

В этом разделе я хочу подробнее поговорить о разнице между глобальными и локальными переменными .

Локальная переменная определена внутри функции, и он доступен только внутри этой функции.

Как это:

#include <stdio.h>

int main(void) {
  char j = 0;
  j += 10;
  printf("%u", j); //10
}

jнедоступен нигде вне mainфункции.

Глобальная переменная определяется вне любой функции, например:

#include <stdio.h>

char i = 0;

int main(void) {
  i += 10;
  printf("%u", i); //10
}

Доступ к глобальной переменной может получить любая функция в программе. Доступ не ограничивается чтением значения: переменная может быть обновлена ​​любой функцией.

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

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

Глобальные переменные освобождаются только после завершения программы.

Определения типов

typedefКлючевое слово в C позволяет определенные новые типы.

Начиная со встроенных типов C, мы можем создавать наши собственные типы, используя этот синтаксис:

typedef existingtype NEWTYPE

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

Это для того, чтобы его было легче отличить и сразу же распознать как тип.

Например, мы можем определить новый NUMBERтип int:

typedef int NUMBER

и как только вы это сделаете, вы можете определить новые NUMBERпеременные:

NUMBER one = 1;

Теперь вы можете спросить: почему? Почему бы intвместо этого просто не использовать встроенный тип ?

Что ж, typedefстановится действительно полезным в сочетании с двумя вещами: перечисляемыми типами и структурами.

Перечислимые типы

Использование typedefи enumключевых слов , мы можем определить тип , который может иметь либо одно значение или другое.

Это одно из самых важных применений typedefключевого слова.

Это синтаксис перечислимого типа:

typedef enum {
  //...values
} TYPENAME;

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

Вот простой пример:

typedef enum {
  true,
  false
} BOOLEAN;

C поставляется с boolтипом, поэтому этот пример не совсем практичен, но вы поняли идею.

Другой пример — определение дней недели:

typedef enum {
  monday,  
  tuesday,
  wednesday,
  thursday,
  friday,
  saturday,
  sunday
} WEEKDAY;

Вот простая программа, использующая этот перечислимый тип:

#include <stdio.h>

typedef enum {
  monday,  
  tuesday,
  wednesday,
  thursday,
  friday,
  saturday,
  sunday
} WEEKDAY;

int main(void) {
  WEEKDAY day = monday;

  if (day == monday) {
    printf("It's monday!"); 
  } else {
    printf("It's not monday"); 
  }
}

Каждый элемент в определении перечисления внутренне связан с целым числом. Итак, в этом примере mondayэто 0, tuesdayравно 1 и так далее.

Это означает, что условное выражение могло быть if (day == 0)вместо if (day == monday), но нам, людям, проще рассуждать с помощью имен, а не чисел, так что это очень удобный синтаксис.

Структуры

Используя structключевое слово, мы можем создавать сложные структуры данных, используя базовые типы C.

Структура — это набор значений разных типов. Массивы в C ограничены типом, поэтому структуры могут оказаться очень интересными во многих случаях использования.

Это синтаксис структуры:

struct <structname> {
  //...variables
};

Пример:

struct person {
  int age;
  char *name;
};

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

struct person {
  int age;
  char *name;
} flavio;

Или несколько, например:

struct person {
  int age;
  char *name;
} flavio, people[20];

В этом случае я объявляю одну personпеременную с именем flavioи массив из 20 personимен people.

Мы также можем объявить переменные позже, используя этот синтаксис:

struct person {
  int age;
  char *name;
};

struct person flavio;

Мы можем инициализировать структуру во время объявления:

struct person {
  int age;
  char *name;
};

struct person flavio = { 37, "Flavio" };

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

struct person {
  int age;
  char *name;
};

struct person flavio = { 37, "Flavio" };
printf("%s, age %u", flavio.name, flavio.age);

Мы также можем изменить значения, используя точечный синтаксис:

struct person {
  int age;
  char *name;
};

struct person flavio = { 37, "Flavio" };

flavio.age = 38;

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

Важно отметить, что структуры передаются по копии , если, конечно, вы не передаете указатель на структуру, и в этом случае он передается по ссылке.

С его помощью typedefможно упростить код при работе со структурами.

Давайте посмотрим на пример:

typedef struct {
  int age;
  char *name;
} PERSON;

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

Теперь мы можем объявить новые PERSONпеременные следующим образом:

PERSON flavio;

и мы можем инициализировать их при объявлении следующим образом:

PERSON flavio = { 37, "Flavio" };

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

В ваших программах на C вам может потребоваться принять параметры из командной строки при запуске команды.

Для простых целей все, что вам нужно сделать, это изменить main()сигнатуру функции с

int main(void)

к

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

argc — целое число, содержащее количество параметров, указанных в командной строке.

argv представляет собой массив строк.

Когда программа запускается, нам предоставляются аргументы в этих двух параметрах.

Обратите внимание, что в argvмассиве всегда есть хотя бы один элемент : имя программы.

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

gcc hello.c -o hello

Если бы это была наша программа, у нас было бы argc4 и argvмассив, содержащий

  • gcc
  • hello.c
  • -o
  • hello

Напишем программу, которая печатает полученные аргументы:

#include <stdio.h>

int main (int argc, char *argv[]) {
  for (int i = 0; i < argc; i++) {
    printf("%s\n", argv[i]);
  }
}

Если имя нашей программы — helloи мы запустим ее так:, ./helloто на выходе мы получим следующее:

./hello

Если мы передадим некоторые случайные параметры, например: ./hello a b cмы получим этот вывод на терминал:

./hello
a
b
c

Эта система отлично подходит для простых нужд. Для более сложных задач обычно используются пакеты, такие как getopt .

Заголовочные файлы

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

Вы можете переместить части программы в отдельный файл. Затем вы создаете файл заголовка .

Заголовочный файл выглядит как обычный файл C, за исключением того, что он заканчивается на .hвместо .c. Вместо реализации ваших функций и других частей программы он содержит объявления .

Вы уже использовали файлы заголовков, когда впервые использовали printf()функцию или другую функцию ввода-вывода, и вам нужно было ввести:

#include <stdio.h>

использовать это.

#include это директива препроцессора.

Препроцессор ищет stdio.hфайл в стандартной библиотеке, потому что вы заключили его в скобки. Чтобы включить свои собственные файлы заголовков, вы будете использовать кавычки, например:

#include "myfile.h"

Вышеупомянутое будет искать myfile.hв текущей папке.

Вы также можете использовать структуру папок для библиотек:

#include "myfolder/myfile.h"

Давайте посмотрим на пример. Эта программа рассчитывает количество лет, прошедших с данного года:

#include <stdio.h>

int calculateAge(int year) {
  const int CURRENT_YEAR = 2020;
  return CURRENT_YEAR - year;
}

int main(void) {
  printf("%u", calculateAge(1983));
}

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

Создаю calculate_age.cфайл:

int calculateAge(int year) {
  const int CURRENT_YEAR = 2020;
  return CURRENT_YEAR - year;
}

И calculate_age.hфайл, в который я поместил прототип функции , который совпадает с функцией в .cфайле, за исключением тела:

int calculateAge(int year);

Теперь в основном .cфайле мы можем пойти и удалить calculateAge()определение функции, и мы можем импортировать calculate_age.h, что сделает calculateAge()функцию доступной:

#include <stdio.h>
#include "calculate_age.h"

int main(void) {
  printf("%u", calculateAge(1983));
}

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

gcc -o main main.c calculate_age.c

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

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

Препроцессор — это инструмент, который очень помогает нам при программировании на C. Он является частью стандарта C, как и язык, компилятор и стандартная библиотека.

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

Что он делает на практике?

Например, он ищет все файлы заголовков, которые вы включаете в #includeдирективу.

Он также проверяет каждую константу, которую вы определили с помощью, #defineи заменяет ее ее фактическим значением.

Это только начало. Я упомянул эти две операции, потому что они самые распространенные. Препроцессор может намного больше.

Ты заметил ? #includeа также ? #defineесть #в начале? Это общее для всех директив препроцессора. Если строка начинается с #, об этом позаботится препроцессор.

Условные

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

Например, мы можем проверить, DEBUGравна ли константа 0:

#include <stdio.h>

const int DEBUG = 0;

int main(void) {
#if DEBUG == 0
  printf("I am NOT debugging\n");
#else
  printf("I am debugging\n");
#endif
}

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

Мы можем определить символическую константу :

#define VALUE 1
#define PI 3.14
#define NAME "Flavio"

Когда мы используем NAME, PI или VALUE в нашей программе, препроцессор заменяет его имя значением перед выполнением программы.

Символьные константы очень полезны, потому что мы можем давать имена значениям, не создавая переменных во время компиляции.

Макросы

С помощью #defineмы также можем определить макрос . Разница между макросом и символьной константой заключается в том, что макрос может принимать аргумент и обычно содержит код, а символьная константа — это значение:

#define POWER(x) ((x) * (x))

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

Затем мы можем использовать это в нашем коде следующим образом:

printf("%u\n", POWER(4)); //16

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

Макросы, однако, ограничены определением одной строки.

Если определено

Мы можем проверить, определена ли символьная константа или макрос, используя #ifdef:

#include <stdio.h>
#define VALUE 1

int main(void) {
#ifdef VALUE
  printf("Value is defined\n");
#else
  printf("Value is not defined\n");
#endif
}

Мы также должны #ifndevпроверить обратное (макрос не определен).

Мы также можем использовать #if definedи #if !definedдля выполнения той же задачи.

Обычно некоторый блок кода помещают в такой блок:

#if 0

#endif

чтобы временно предотвратить его запуск или использовать символическую константу DEBUG:

#define DEBUG 0

#if DEBUG
  //code only sent to the compiler
  //if DEBUG is not 0
#endif

Предопределенные символьные константы, которые вы можете использовать

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

  • __LINE__ переводится в текущую строку в файле исходного кода
  • __FILE__ переводится как имя файла
  • __DATE__переводится на дату компиляции в Mmm gg aaaaформате
  • __TIME__переводится во время компиляции в hh:mm:ssформате

Заключение

Большое спасибо за прочтение этого справочника!

Я надеюсь, что это вдохновит вас узнать больше о C.

Оцените статью
FLOOP
Добавить комментарий