fbpx

Каталог статей

Каталог статей для размещения статей информационного характера

Как выучить

Руководство для начинающих по языку Си: Изучите основы языка программирования Си всего за несколько часов

Справочник начинающего программиста на языке Си: Изучите основы языка программирования Си всего за несколько часов

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

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

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

Оглавление

Введение в язык C

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

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

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

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

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

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

Я сказал “компилятор”: Си – это компилируемый язык программирования, как Go, Java, Swift или Rust. Другие популярные языки программирования, такие как Python, Ruby или JavaScript, являются интерпретируемыми. Разница последовательна: компилируемый язык генерирует двоичный файл, который может быть непосредственно исполнен и распространен.

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

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

Сейчас я хочу представить первую программу на Си, которую мы назовем “Hello, World!”.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Напечатайте программу:

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

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

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

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

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

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

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

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

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

Си – статически типизированный язык.

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

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

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

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

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

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

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

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

Встроенные типы данных языка C – это int, char, short, long, float, double, long double. Давайте узнаем о них подробнее.

Целочисленные числа

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

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

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

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

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

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

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

Если вы программируете 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,767 . long хранит 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.

Проблема переполнения

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

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

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

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

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

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

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

И он также предупреждает вас при прямом присваивании:

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

Числа с плавающей точкой

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

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

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

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

Следующие типы:

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

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

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

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

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

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

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

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

Constants

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

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

Это совершенно правильный язык Си, хотя принято объявлять константы в верхнем регистре, например, так:

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

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

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

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

Компилятор языка Си вычислит тип по указанному значению во время компиляции.

Операторы

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

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

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

В этом разделе я собираюсь подробно описать их все, используя в качестве примера 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 до ее использования.

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

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

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

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

  • ! NOT (пример: !a )
  • && AND (пример: a && b )
  • || OR (пример: a || b )

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

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

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

Оператор Имя Пример
+= Присвоение сложения a += b
-= Присвоение вычитания a -= b
*= Задание на умножение a *= b
/= Задание на деление a /= b
%= Присвоение модуля a %= b

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

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

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

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

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

sizeof

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

Старшинство операторов

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

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

Каково значение c? Выполняется ли сложение перед умножением и делением?

Существует ряд правил, которые помогают нам решить эту головоломку.

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

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

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

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

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

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

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

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

, и нам не придется так много об этом думать.

Условные обозначения

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

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

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

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

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

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

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

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

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

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

switch

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

Вы можете обеспечить

Си предлагает нам три способа выполнения цикла: цикл for, цикл while и цикл do while. Все они позволяют выполнять итерации над массивами, но с некоторыми отличиями. Давайте рассмотрим их подробнее.

Циклы for

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

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

начальное условие ( int i = 0 )

проверка ( i

приращение ( i++ )

  • Сначала мы определяем переменную цикла, в данном случае под именем i. i – это общее имя переменной, используемое для циклов, наряду с j для вложенных циклов (цикл внутри другого цикла). Это просто соглашение.
  • Переменная инициализируется значением 0, и выполняется первая итерация. Затем она увеличивается, как сказано в части increment (в данном случае i++, увеличение на 1), и весь цикл повторяется, пока не будет достигнуто число 10.
  • Внутри основного блока цикла мы можем обратиться к переменной i, чтобы узнать, на какой итерации мы находимся. Эта программа должна вывести 0 1 2 3 4 5 6 7 8 9 10 :

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

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

Циклы While

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

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

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

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

Это то, что вам нужно для “правильного” цикла while:

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

Циклы do while

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

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

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

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

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

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

Для этого используется ключевое слово break.

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

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

В Си довольно часто встречаются циклы такого типа.

Массивы

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

Каждое значение в массиве, в языке Си, должно иметь t

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

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

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

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

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

Подробнее об указателях мы расскажем в ближайшее время.

Строки

В языке C строки представляют собой особый вид массива: строка – это массив значений типа char:

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

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

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

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

Вы заметили, что “Flavio” имеет длину 6 символов, но я определил массив длиной 7? Почему? Потому что последний символ в строке должен быть равен 0, это терминатор строки, и мы должны выделить для него место.

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

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

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

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

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

strcpy() для копирования строки поверх другой строки

strcat() для добавления строки к другой строке

strcmp() для сравнения двух строк на равенство

strncmp() для сравнения первых n символов двух строк

strlen() для вычисления длины строки

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

Указатели

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

  • В этом разделе я хочу представить их в самом простом, но не отупляющем виде.
  • Указатель – это адрес блока памяти, содержащего переменную.
  • Когда вы объявляете целое число, например, так:
  • Мы можем использовать оператор & для получения значения адреса в памяти переменной:
  • Я использовал формат %p, указанный в printf(), чтобы вывести значение адреса.

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

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

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

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

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

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

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

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

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

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

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

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

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

Функции

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

дать имя

вызывать, когда они нам нужны

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

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

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

Функции имеют 4 важных аспекта:

у них есть имя, поэтому мы можем вызывать их в дальнейшем

они указывают возвращаемое значение

  1. у них могут быть аргументы
  2. у них есть тело, заключенное в фигурные скобки.

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

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

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

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

  1. В этом случае, когда мы вызываем функцию, мы вызываем ее без скобок:
  2. Если у нас есть один параметр, мы указываем тип и имя параметра, как здесь:
  3. Когда мы вызываем функцию, мы передаем этот параметр в круглых скобках, вот так:
  4. У нас может быть несколько параметров, и в этом случае мы разделяем их запятой, как в объявлении, так и в вызове:

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

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

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

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

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

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

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

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

В любом случае, убедитесь, что вы объявили функцию до того, как

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

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

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

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

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

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

printf()

scanf()

sscanf()

fgets()

fprintf()

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

В языке Си есть 3 вида потоков ввода/вывода:

stdin (стандартный ввод)

stdout (стандартный вывод)

stderr (стандартная ошибка)

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

Это один момент, который следует иметь в виду.

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

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

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

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

%f для чисел с плавающей запятой

%p для указателей

В printf() мы можем использовать управляющие символы, такие как

, с помощью которых мы можем заставить вывод создавать новую строку.

scanf()

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

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

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

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

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

  • Вот небольшая программа, которая использует как printf(), так и scanf():
  • Область видимости переменной
  • Когда вы определяете переменную в программе на языке Си, в зависимости от того, где вы ее объявляете, она будет иметь разную область видимости.
  • Это означает, что она будет доступна в одних местах, но не доступна в других.

Область видимости определяет 2 типа переменных:

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

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

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

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

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

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

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

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

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

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

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

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

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

Теперь при каждом вызове этой функции мы будем получать увеличенное значение:

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

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

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

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

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

Она недоступна нигде за пределами главной функции.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Каждый элемент в определении перечисления сопряжен с целым числом. Так что в этом примере понедельник – 0, вторник – 1 и так далее.

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

Структуры

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

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

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

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

Или несколько, как здесь:

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

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

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

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

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

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

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

Используя typedef, мы можем упростить код при работе со структурами.

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

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

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

, и можем инициализировать их при объявлении таким образом:

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

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

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

argc – целое число, содержащее количество параметров, которые были предоставлены в командной строке.

argv – массив строк.

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

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

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

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

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

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

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

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

Файлы заголовков

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

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

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

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

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

Препроцессор идет и ищет файл stdio.h в стандартном каталоге.

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

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

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

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

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

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

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

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

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

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

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

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

Условные обозначения

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

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

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

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

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

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

Макросы

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

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

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

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

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

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

Проверить, определена ли символическая константа или макрос, можно с помощью #ifdef :

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

Условные обозначения

Обычно некоторый блок кода заворачивают в такой блок:

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

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

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

Справочник начинающего программиста на языке Си: Изучите основы языка программирования Си всего за несколько часов

Screen-Shot-2020-01-29-at-10.10.50

Screen-Shot-2020-01-29-at-10.11.39

Screen-Shot-2020-01-29-at-10.16.52

Screen-Shot-2020-01-29-at-10.18.11

Screen-Shot-2020-01-29-at-10.18.15

Screen-Shot-2020-01-29-at-10.13.46

Screen-Shot-2020-01-29-at-10.16.31

Screen-Shot-2020-01-29-at-10.19.20

Screen-Shot-2020-01-29-at-10.19.55

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *