Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Сабуров С.В. - Язык программирования C и C++ - 2006

.pdf
Скачиваний:
312
Добавлен:
13.08.2013
Размер:
1.42 Mб
Скачать

Язык программирования Си

Следующие примеры иллюстрируют некоторые комментарии:

/* Comments can separate and document lines of a program. */

/* Comments can contain keywords such as for and while */ /******************************************* Comments can occupy several lines.

*******************************************/

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

/* You cannot/* nest */ comments */

Компилятор распознает первую комбинацию */ после слова nest как конец комментария. Затем, компилятор попытается обрабатывать оставшийся текст и выработает сообщение об ошибке. Чтобы обойти компиляцию комментариев больших размеров, нужно использовать директиву #if препроцессора.

Лексемы

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

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

17

Язык программирования Си

Например, рассмотрим следующее выражение:

i+++j

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

Исходная программа

Исходная программа — это совокупность следующих объектов: директив, указаний компилятору, объявлений и определений. Директивы задают действия препроцессора по преобразованию текста программы перед компиляцией. Указания компилятору — это команды, выполняемые компилятором во время процесса компиляции. Объявления задают имена и атрибуты переменных, функций и типов, используемых в программе. Определения — это объявления, определяющие переменные и функции.

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

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

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

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

18

Язык программирования Си

В следующем примере иллюстрируется простая исходная

программа на языке Си.

 

 

int x

= 1;

/* Variable definitions

*/

int

y

=

2;

 

 

 

extern int printf(char *,...);/* Function declaration */

main

()

/*

Function

definition

 

for

main

function */

 

 

int

z;

 

/*

Variable

declarations */

 

int

w;

 

 

 

 

 

z =

y

+

x;

/* Executable statements

*/

w = y x;

printf("z = %d \nw = %d \n", z, x);

Эта исходная программа определяет функцию с именем main и объявляет функцию printf. Переменные x и y задаются своими определениями. Переменные z и w только объявляются.

Исходные файлы

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

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

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

19

Язык программирования Си

Директивы исходного файла относятся только к этому исходному файлу и файлам, включающим его (посредством #include). Кроме того, каждая директива относится только к части файла, которая следует за ней. Если множество директив должно относиться ко всей исходной программе, то все исходные файлы должны содержать эти директивы.

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

В нижеследующем примере исходная программа состоит из двух исходных файлов. Функции main и max представлены в отдельных файлах. Функция main использует функцию max при своем выполнении.

/*********************************************** Sourse file 1 main function

***********************************************/

#define

ONE

1

#define

TWO

2

#define

THREE

3

extern

int max(int,

int);

/*

Function declaration */

main

()

/*

Function definition

*/

int w = ONE, x = TWO, y = THREE;

int

z = 0;

 

 

 

 

z

=

max(x,y);

 

 

 

w

=

max(z,w);

 

 

 

/*************************************************

Sourse file 2 max function

 

 

*************************************************/

int

max(a,b)

/*

Function

definition */

int

a,

b;

 

 

 

 

if ( a > b )

 

 

 

 

return

(a);

 

 

 

 

else

 

 

 

 

 

 

return

(b);

 

 

 

 

В первом исходном файле функция max объявлена без ее определения. Это объявление известно как forward объявление. Определение функции main включает вызов функции max.

20

Язык программирования Си

Строки, начинающиеся с символа номер #, являются директивами препроцессора. Директивы инструктируют препроцессор о замене в первом исходном файле имен ONE, TWO, THREE на соответствующие им значения. Область действия директив не распространяется на второй исходный файл.

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

Выполнение программ

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

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

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

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

Соглашение Си требует, чтобы первые два параметра функции main назывались argc и argv.

Параметр argc определяет общее число аргументов, передаваемых функции main. Параметр argv объявляется как массив указателей, каждый элемент которого ссылается на строковое представление аргумента, передаваемого функции main. Третий параметр функции main (если он есть) традиционно

21

Язык программирования Си

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

Операционная система поддерживает передачу значений для argc, argv и envp параметров, а пользователь поддерживает задание значений фактических параметров для функции main.

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

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

«Время жизни» и «Видимость»

Концепции «Время жизни» и «Видимость» являются очень важными для понимания структуры программ на Си. Время жизни переменной может быть или глобальным или локальным. Объект с глобальным временем жизни характеризуется определенной памятью и значением на протяжении всей жизни программы. Объект с локальным временем жизни захватывает новую память при каждом входе в блок, в котором он определен или объявлен. Когда блок завершается, локальный объект пропадает, а следовательно пропадает его значение. Определение блоков описывается ниже.

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

Блок — это составной оператор. Составные операторы состоят из объявлений и операторов.

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

Объявления и определения внутри блоков находятся на внутреннем уровне. Объявления и определения вне блоков находятся на внешнем уровне. Переменные и функции могут быть объявлены как на внешнем уровне, так и на внутреннем.

22

Язык программирования Си

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

Все функции имеют глобальное время жизни, невзирая на то, где они объявлены. Переменные, объявленные на внешнем уровне, всегда имеют глобальное время жизни. Переменные, объявленные на внутреннем уровне, всегда имеют локальное время жизни, однако, классы памяти, специфицированные как static и extern, могут быть использованы для объявления глобальных переменных или ссылок на них внутри блока.

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

Однако, переменные, заданные классом памяти static на внешнем уровне, видимы только внутри исходного файла, в котором они определены.

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

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

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

23

Язык программирования Си

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

/* i defined at external level */ int i = 1;

/* main function defined at external level */ main ()

/* prints 1 (value of external level i) */ printf("%d\n", i);

/* first nested block */

/* i and j defined at internal level */ int i = 2, j = 3;

/* prints 2, 3 */ printf("%d\n%d\n", i, j); /* second nested block */

/* i is redefined */ int i = 0;

/* prints 0, 3 */ printf("%d\n%d\n", i, j);

/* end of second nested block */

/* prints 2 (outer definition restored) */ printf("%d\n", i);

/* end of first nested block */

/* prints 1 (external level definition restored */ printf("%d\n", i);

В этом примере показано четыре уровня видимости: самый внешний уровень и три уровня, образованных блоками. Предполагается, что функция printf определена где нибудь в другом месте программы. Функция main печатает значения 1, 2, 3, 0, 3, 2, 1.

Классы имен

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

24

Язык программирования Си

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

Чтобы различать идентификаторы для различного рода объектов, компилятор устанавливает так называемые Классы имен. Для избегания противоречий, имена внутри одного класса должны быть уникальными, однако в различных классах могут появляться идентичные имена. Это означает, что можно использовать один и тот же идентификатор для двух или более различных объектов, если объекты принадлежат к различным классам имен. Однозначное разрешение ссылок компилятор осуществляет по контексту данного идентификатора в программе. Ниже описываются виды объектов, которые можно именовать в программе на Си, а также правила их именования.

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

Однако, имена переменных могут быть переопределены внутри программных блоков.

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

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

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

Имена typedef относятся к одному классу имен вместе с именами переменных и функций. Поэтому они должны быть

25

Язык программирования Си

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

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

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

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

Пример: struct student

char student[20]; int class;

int id; student;

В этом примере имя тега структуры, элемента структуры и переменной относятся к трем различным классам имен, поэтому не будет противоречия между тремя объектами с именем student. Компилятор определит по контексту программы как интерпретировать каждый из случаев. Например, когда имя student появится после ключевого слова struct, это будет означать, что появился tag структуры. Когда имя student появится после операции выбора элемента > или ., то это означает, что имя ссылается на элемент структуры. В другом контексте имя student является переменной структурного типа.

26

Соседние файлы в предмете Программирование на C++