Основы языка C

Материал из roboforum.ru Wiki
Перейти к: навигация, поиск

Введение

Язык C был разработан в начале 70-х годов 20 века компанией AT&T, и до сих пор является одним из основных языков системного программирования. Являясь языком высокого уровня, C в то же время предоставляет возможности, присущие программированию на ассемблере, такие как низкоуровневый доступ к памяти. Программы, написанные на C, близки по эффективности к ассемблерным программам.

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

Для перевода программы, написанной на C, в машинный код, необходима специальная программа - компилятор. Компилятор состоит из 4 основных частей - препроцессора, собственно компилятора, ассемблера и компоновщика. Препроцессор производит предварительную обработку текста программы в соответствии с содержащимися в тексте специальными директивами. Компилятор переводит текст на C в программу на языке ассемблера, которая затем обрабатывается ассемблером. В результате получается объектный файл, содержащий наряду с машинным кодом служебную информацию, например список объявленных в исходном файле переменных и функций. Язык C поддерживает раздельную компиляцию и модульное программирование. Это значит, что текст программы может быть разбит на несколько файлов (модулей), каждый из которых компилируется отдельно. В результате получается набор объектных файлов, из которых собирается готовая программа. Обычно в отдельный модуль выделяются функции, реализующие определенный набор функциональности. Удобство такого подхода в том, что один раз скомпилированный модуль может быть повторно использован в других программах. Например, если в устройстве на микроконтроллере используется жидкокристаллический дисплей, имеет смысл все функции для работы с дисплеем вынести в отдельный модуль. Тогда при проектировании другого устройства, использующего такой же дисплей, можно будет использовать уже готовый программный модуль. Компоновщик как раз и занимается сборкой программы из отдельно скомпилированных модулей.

Структура программы

Простейшая структура программы

Простейшая законченная программа состоит из одной функции main:

<source lang="c"> int main(void) {

   return 0;

} </source>

Функции и процедуры

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

Определение функции выглядит следующим образом:

<source lang="c"> int plus(int a, int b) {

   return a + b;

} </source>

Этот код определяет функцию с именем plus, которая принимает 2 аргумента a и b, являющихся целыми числами (int), и возвращает целое число. В фигурные скобки заключено тело функции, то есть последовательность операций, которые будут выполняться при каждом вызове. В данном примере, функция возвращает (return) сумму своих аргументов.

Для указания того, что функция не принимает аргументов и/или не возвращает значения, используется зарезервированное слово void:

<source lang="c">

/* Не принимает аргументы, не возвращает значение */ void init(void) {

   /* Тело функции */

}

/* Принимает аргументы, не возвращает значение */ void start(int number) {

   /* Тело функции */

}

/* Не принимает аргументы, возвращает значение */ int receive(void) {

   return 0;

}

</source>

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

Тело функции состоит из объявлений переменных и операторов. Оператор - это наименьшая структурная единица языка, команда. Словом оператор также нередко называют операцию (например, умножение). Объявление переменных будет рассмотрено в следующих разделах.

Последовательные операторы отделяются друг от друга символом ; (точка с запятой). Основные виды операторов:

  • Присваивание <source lang="c">a = 12; // переменной a присваивается значение 12</source>
  • Вызов функции <source lang="c">do_something(); </source>
  • Блок инструкций (составной оператор)

<source lang="c">{a = 12; b=14;} // присваиваются значения переменным a и b. // Последовательность двух операторов рассматривается как один оператор </source>

  • Условный оператор. Исполняет инструкцию, только если выполняется определенное условие. <source lang="c"> if (b > 10) a = 0; // переменной a присваивается значение 0, если значение переменной b больше 10</source>
  • Цикл. Циклически исполняет инструкцию, пока выполняется определенное условие. <source lang="c"> while (b > 10) do_something(); // вызывать функцию do_something(), пока значение переменной b больше 10</source>

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

Переменные и типы данных

Определение переменных

Переменная - это участок памяти, к которому можно обратиться в программе по его имени. C относится к языкам со строгой типизацией. Это значит, что перед использованием переменной, она должна быть объявлена, и переменной должен быть назначен тип данных, которые она может содержать. Объявление переменной выглядит следующим образом: <source lang="c"> int x; </source> int - это тип данных (целое число со знаком), x - имя переменной. Обратите внимание на точку с запятой, отделяющую объявление переменной от последующих операторов. Начальное значение переменной - ноль. Если необходимо указать начальное значение, отличное от ноля, используется обявление с инициализацией: <source lang="c"> int x = 1234; </source> Несколько переменных одного типа могут быть объединены в одно объявление: <source lang="c"> int x = 1234, y, z, alpha = 3; </source>

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

Каждая переменная характеризуется областью действия (или областью видимости). Область видимости определяет, какие части программы могут обращаться к переменной. Глобальные переменные объявляются вне функций и доступны из любого места программы. Переменные, объявленные внутри блока, доступны только из этого блока - для всех остальных частей программы они не существуют. Под блоком понимается участок кода, заключенный в фигурные скобки (тело функции или составной оператор). Такая переменная называется локальной. Имя локальной переменной может совпадать с именем глобальной переменной (причем локальная переменная может иметь другой тип). В этом случае локальная переменная перекрывает глобальную, делая ее недоступной из блока. То же самое справедливо и для вложенных блоков. Проиллюстрируем все сказанное примером:

<source lang="c"> int x; // это глобальная переменная

void func1(void) {

   char x; // это локальная переменная функции func1. Она перекрывает глобальную переменную
   x = 'a'; // обращение к локальной переменной функции
   {
       long x; // это локальная переменная блока. Она перекрывает локальную переменную функции
       x = 133; // обращение к локальной переменной блока
   }
   x = 'b'; // обращение к локальной переменной функции

}

void func2(void) {

   x = 3; обращение к глобальной переменной

}

</source>


Модификаторы

Модификаторы - это ключевые слова, добавляемые к объявлению переменной или функции, и изменяющие определенным образом их свойства. Наиболее употребительные модификаторы - const, volatile, static и extern.

Модификатор const сообщает компилятору, что переменная инициализируется при создании, и впоследствии изменяться не может. Объявление переменной с модификатором const обязательно должно содержать инициализацию. Представьте себе торговый автомат, принимающий монеты достоинством 2 рубля. В различных местах программы будет использоваться число 2. Если затем нужно будет перенастроить автомат на монеты в 5 рублей, нужно будет найти все места, где используется 2, и заменить на 5. Это может быть трудоемким процессом, к тому же высока вероятность ошибок. Поэтому можно объявить переменную coin, и использовать ее в программе, там, где нужно достоинство монеты: <source lang="c"> const int coin = 2; int money = 0;

int accept_coin(void) // принята монета {

   money = money + coin;
   return money;

}

int return_coin(void) // возвращена монета {

   money = money - coin;
   return money;

} </source> Использование модификатора const защищает от потенциальных ошибок программиста - если в программе будет попытка записи в переменную, представляющую константу, компилятор выдаст сообщение об ошибке. Теперь, чтобы перенастроить автомат, нужно будет изменить 2 на 5 только в одном месте программы.


Модификатор volatile сообщает компилятору, что значение переменной может внезапно измениться. Обычно это относится к переменным, значение которых изменяется в прерываниях.

<source lang="c"> volatile int time = 0; </source>

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

Пример различий между локальной переменной с модификатором static и без него: <source lang="c"> int count1(void) {

   /* 
       Переменная count объявлена как static, поэтому начальное
       значение 0 будет присвоено при старте программы. С каждым
       вызовом функции значение переменной будет увеличиваться на 1,
       и функция будет возвращать количество раз, которое она была
      вызвана.
   */
   static int count = 0;
   count = count + 1;
   return count;

}

int count2(void) {

   /* 
       Переменная count объявлена без модификатора static, поэтому начальное
       значение 0 будет присвоено при каждом вызове функции. После этого
       значение переменной будет увеличено на 1,
       и функция будет всегда возвращать единицу.
   */
   int count = 0;
   count = count + 1;
   return count;

}

</source>

Модификатор static может также применяться и к глобальным переменном, но при этом он имеет другое значение. Программа на языке C может состоять из нескольких исходных файлов (модулей). Каждый модуль обрабатывается компилятором отдельно. Модуль может содержать глобальные переменные, которые должны быть доступны только функциям из того же модуля, но не видны другим модулям - в этом случае переменные объявляются с модификатором static. Этот же модификатор может применяться и к функциям, и имеет для них такое же значение.


Модификатор extern будет рассмотрен далее, в главе, посвященной препроцессору.

Базовые типы данных

Массивы

Структуры

Указатели

Операции

Условные операторы и циклы

Условный оператор if

Условный оператор, варианты использования:

  1. простейший:
    • if( a==1 ){ b=2 };
  2. с веткой "иначе":
    • if( a==1 ){ b=2 }else{ c=3 };
  3. с 2 ветками "иначеесли" и веткой "иначе":
    • if( a==1 ){ b=2 }elseif( a==2 ){ c=3 }elseif( a==3 ){ d=4 }else{ e=5 };
  4. со сложным условием:
    • if( a==1 && (c==0 || q==3) ){ b=2 };

Условный оператор switch

Цикл for

Оператор цикла, варианты использования:

  1. простейший:
    • for( int a=1; a<10; a++ ){ b=b+a };
  2. с двойной переменной:
    • for( int a=1,b=2; a<10 && b>5 ; a++,b-- ){ c=c+3*b+a };

Цикл while

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

Что такое препроцессор

Подключаемые файлы и файлы заголовков

Дополнительные определения