Бегун.Рекомендую - рекламодателю
Здесь может быть ваша реклама
|

 Глава 3.Обзор языка С

Глава 3.Обзор языка С

 Как уже говорилось, хорошее понимание языка С представляется необходимым для успешного программирования на C++. C++Builder в полной мере поддерживает стандарт ANSI С и, кроме того, некоторые другие версии языка (Керниган & Ричи, Unix V). Мы в основном будем ориентироваться на стандарт ANSI, который, кстати, является в какой-то мере результатом “обратного воздействия” языка C++.

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

Элементы простой программы

Давайте немного поближе познакомимся со строением консольной программы Hello World, написанной в предыдущей главе:

#pragma hdrstop

#include <stdio.h>

#include <conio.h>

#include <condefs.h>

//---------------------------------------------

#pragma argsused

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

{

printf("Hello World from Console!n");

printf("Press any key...");

getch() ;

return 0;

}

Комментарии

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

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

Вышеприведенная строка — комментарий в стиле C++. Стандартные компиляторы С таких комментариев не допускают. В языке С комментарий начинается с комбинации дробной черты и звездочки (/*) и заканчивается теми же символами в обратном порядке (*/). Он может занимать несколько строк, а может быть вставлен в середину строки (такие случаи бывают). Комментарий в стиле C++ начинается, как уже говорилось, с двои ной дробной черты и продолжается до конца текущей строки. Язык C++ поддерживает оба типа комментариев.

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

/ *

** Простейшая консольная программа C++Builder.

** Выводит на экран "Hello World" и ждет, пока

** пользователь не нажмет какую-нибудь клавишу.

*/

Он занимает, как видите, пять строк. А вот комментарий в стиле C++:

getch(); // Ожидание нажатия клавиши.

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

Директивы # pragma

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

Примечание

Часто директивы tpragma эквивалентны некоторым установкам компилятора, задаваемым в диалоге Project Options. Например, упомянутые выше сообщения о неиспользуемых параметрах можно было бы запретить, открыв этот диалог (Project | Options... в главном меню) на странице Compiler и нажав кнопку Warnings..., после чего будет открыто окно со списком всех возможных предупреждений; в нем следует сбросить флажок напротив сообщения “Parameter 'parameter' is never used (-wpar)”.

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

Подробнее о #pragma и других директивах мы поговорим в следующей главе.

 Директивы #include

Директива # include заменяется препроцессором на содержимое указанного в ней файла. Обычно это заголовочные файлы с расширением .h. Они содержат информацию, обеспечивающую раздельную компиляцию файлов исходного кода и корректное подключение различных библиотек. Имя файла может быть заключено либо в угловые скобки (о, меньше—больше), либо в обычные двойные кавычки (""). Эти случаи различаются порядком поиска включаемых файлов; если использованы угловые скобки, поиск будет сначала производиться в стандартных каталогах C++Builder, если кавычки — в текущемкаталоге.

Функция main()

После всех директив в программе расположено определение функции па in () . Как уже говорилось, в строгом смысле любая программа на С содержит эту функцию, которая является ее входной точкой. Однако в среде Windows вместо main () часто используется WinMain () .

Функция main () — это, конечно, частный случай функции вообще. Функции являются основными “строительными блоками” программы, или подпрограммами. Они, в свою очередь, строятся из операторов, составляющих тело функции. Каждый оператор оканчивается точкой с запятой (;). В общем виде функция определяется таким образом:

Возвращаемый_ тип_ имя функции(список_ параметров)

{

// В фигурных скобках заключено тело функции,

// составленное из отдельных операторов.

тело_функции

}

Примечание

Функции — единственный тип подпрограмм С, в отличие, например, от языка Pascal, который различает функции и процедуры. Под процедурой обычно понимают подпрограмму, не возвращающую никакого значения. В С формально любая функция возвращает какой-либо тип, хотя в ANSI С этот тип может быть пустым (void). В первоначальном варианте языка (Керниган & Ричи) функция, для которой возвращаемый тип не определялся, считалась возвращающей int (целое значение). Мы иногда будем называть функции С процедурами, хотя это, строго говоря, и неправильно.

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

Параметры функции main()

Примечание

Параметры функции main () служат для передачи программе аргументов командной строки, т. е. имен файлов, ключей, опций и вообще всего, что вы вводите с клавиатуры после подсказки DOS, запуская программу. Конечно, программа не обязана воспринимать какие-либо команды, указываемые в строке запуска, однако в любом случае функции main () передаются два параметра — число аргументов/включая имя, под которым запущена программа (argc), и массив указателей (argv) на отдельные аргументы (выделенные элементы командной строки). Забегая вперед, приведем пример, который распечатывает по отдельности все “аргументы” строки, введенной пользователем при запуске:

#include <stdio.h>

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

{

int i;

for (i=0; i<argc; i++)

printf ( "%sn", argv[i]);

return 0;

}

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

Представление данных в С

Любая программа так или иначе обрабатывает данные. Наша маленькая программа обрабатывает свои данные — строку сообщения “Hello World”, выводя ее на экран. Рассмотрим, какие возможны варианты представления информации в С.

Литералы

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

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

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

В таблице 3.1 перечислены все упомянутые выше виды литеральных констант и даны соответствующие примеры.

Таблица 3.1. Литеральные константы

Литерал

Описание

Примеры

Символьный

Одиночный символ, заключенный в апострофы

'W', '&', 'Ф'

Строковый

Последовательность символов, заключенная в обычные (двойные) кавычки

"Это строка n"

Целый

Десятичный — последовательность цифр, не начинающаяся с нуля

123, 1999

 

 

Восьмеричный — последовательность цифр от нуля до семерки, начинающаяся с нуля

011, 0177

 

 

Шестнадцатеричный — последовательность шестнадцатеричных цифр (0 - 9 и А - F), перед которой стоит 0X или Оx

ОХ9А, Oxffff

Вещественный

Десятичный — [цифры].[цифры]

123., 3.14, .99

 

 

Экспоненциальный — [цифры]Е|е[+|-] цифры

Зе-10, 1.17е6

 

Примечание

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

#define PI 3.14159265

#define TRUE 1

#define FALSE 0

Примечание

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

Встроенные типы данных

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

Таблица 3.2. Встроенные типы данных

Тип данных

Размер (бит)

Диапазон

char

8

-128 - 127

signed char

8

-128 - 127

unsigned char

8

0 - 255

short

16

-32768 - 32767

unsigned short

16

0 - 65535

int

32

-2147483648 - 2147483647

unsigned int

32

0 - 4294967295 .

long

32

-2147483648 - 2147483647

unsigned long

32

0 - 4294967295

float

32

3.410-38 - 3.41038

double

64

1.71010-308 - 1.710308

long double

80

3.410-4932 - 3.4104932

 

Примечание

Может быть, стоит напомнить, что отрицательные целые числа представляются в машине в форме дополнения до двух. Чтобы изменить знак числа на противоположный, нужно инвертировать все его разряды (0 заменить на 1 и наоборот и прибавить к полученному числу единицу. Например, взяв +1 типа char (00000001), инвертировав все биты (11111110) и прибавив 1, мы получим -1 (11 111 111).

Ключевые слова short, long и unsigned являются, строго говоря, модификаторами для типа int. Однако допускается сокращенная запись. Так, unsigned short — на самом деле сокращение для unsigned short int.

Следует, вероятно, повторить, что мы говорим здесь о C++Builder 5, т. е. 32-разрядном компиляторе. Размер и допустимый диапазон значений приведены именно для данного случая. Поэтому, например, тип int имеет размер 32 бита (4 байта) и эквивалентен типу long; на 16-разрядной машине int имел бы размер 2 байта, как short. О таких вещах не следует забывать, особенно если вы занимаетесь переносом программ на машину с другой разрядностью.

Переменные

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

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

short i;

// Объявление короткой целой

// переменной.

char quit = 'Q';

// Инициализация символьной

// переменной.

float fl, factor = 3.0, f2;

// Три переменных типа float,

// одна из которых

// инициализируется.

Синтаксис оператора объявления можно описать примерно так:

тип имя_переменной [= инициализирующее_значение][, ...];

Как и любой другой оператор С, он оканчивается точкой с запятой.

Примечание

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

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

Примечание

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

 

Типизированные константы

Разновидностью переменных являются типизированные константы. Это переменные, значение которых (заданное при инициализации) нельзя изменить. Создание типизированной константы ничем не отличается от инициализации переменной, за исключением того, что перед оператором объявления ставится ключевое слово const:

const тип имя_константы = значение [, ...];

Например:

const double Pi = 3.14159265;

Ранее мы демонстрировали определение символической константы:

#define PI 3.14159265

Примечание

Чем этот макрос отличается от показанной выше типизированной константы? Здесь следует иметь в виду два момента. Во-первых, типизированная константа по своему смыслу относится к конкретному типу данных, поэтому компилятор генерирует совершенно определенное представление для ее значения. Представление символической константы не определено.

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

Операции и выражения

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

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

aResult = (first - second * RATE) <<3

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

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

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

Таблица 3.3. Операции языка С

Операция

Описание

Приоритет

Ассоциация

Первичные и постфиксные операции

[]

индексация массива

16

слева направо

()

вызов функции

16

слева направо

.

элемент структуры

16

слева направо

->

элемент указателя

16

слева направо

++

постфиксный инкремент

15

слева направо

--

постфиксный декремент

15

слева направо

Одноместные операции

++

префиксный инкремент

14

справа налево

--

префиксный декремент

14

справа налево

sizeof

размер в байтах

14

справа налево

(тип)

приведение типа

14

справа налево

~

поразрядное NOT

14

справа налево

!

логическое NOT

14

справа налево

-

унарный минус

14

справа налево

&

взятие адреса

14

справа налево

*

разыменование указателя

14

справа налево

Двухместные и трехместные операции

Мультипликативные

*

умножение

13

слева направо

/

деление

13

слева направо

%

взятие по модулю

13

слева направо

Аддитивные

+

сложение

12

слева направо

-

вычитание

12

слева направо

Поразрядного сдвига

<<

сдвиг влево

11

слева направо

>>

сдвиг вправо

11

слева направо

Отношения

<

меньше

10

слева направо

<=

меньше или равно

10

слева направо

>

больше

10

слева направо

>=

больше или равно

10

слева направо

==

равно

9

слева направо

Операция

Описание

Приоритет

Ассоциация

! =

не равно

9

слева направо

Поразрядные

&

поразрядное AND

8

слева направо

^

поразрядное XOR

7

слева направо

|

поразрядное OR

6

слева направо

Логические

&&

логическое AND

5

слева направо

||

логическое OR

4

слева направо

Условные

? :

условная операция

3

справа налево

Присваивания

=

присваивание

2

справа налево

*=

присвоение произведения

2

справа налево

/=

присвоение частного

2

справа налево

%=

присвоение модуля

2

справа налево

+=

присвоение суммы

2

справа налево

-=

присвоение разности

2

справа налево

<<=

присвоение левого сдвига

2

справа налево

>>=

присвоение правого сдвига

2

справа налево

&=

присвоение AND

2

справа налево

^=

присвоение XOR

2

справа налево

|=

присвоение OR

2

справа налево

,

запятая

1

слева направо

 

Семантика операций

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

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

К арифметическим мы отнесем те операции, которые перечислены в таблице под рубриками “Мультипликативные” и “Аддитивные”. Нужно сказать, что только эти операции (да и то за исключением взятия по модулю) имеет смысл применять к вещественным операндам (типам float, double и long double). Для таких операндов все обстоит вполне понятным и конвенциональным образом; это обычные умножение, деление, сложение и вычитание.

Операция взятия по модулю применяется только к целочисленным операндам (char, short, int. long) и дает остаток от деления первого операнда на второй. Специальной операции деления нацело в С нет — для него применяется обычная операция деления (/). Если оба операнда ее являются целыми, то результат этой операции также будет целым, равным частному от деления с остатком первого операнда на второй.

Примечание

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

volume = 4/3 * Pi * r*r*r;

Все операции в выражении правой части имеют одинаковый приоритет, и оценка выражения производится в последовательности слева направо. На первом шаге производится деление 4/3, но это будет делением нацело с результатом, равным 1. Эта единица преобразуется далее в вещественное 1.0 (возведение типа, описанное ниже), а дальше все идет как положено. Коэффициент в формуле, таким образом, получается равным 1.0 вместо ожидаемого 1.333...

Операции присваивания

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

а = b = с = someExpression;

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

s += i;

эквивалентно

s = s + i;

Другими словами, оператор вроде

х *= 10;

означает “присвоить переменной х ее текущее значение, умноженное на 10”.

Примечание

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

Приведение типа

Если в операторе присваивания тип результата, полученного при оценке выражения в правой части, отличен от типа переменной слева, компилятор выполнит автоматическое приведение типа (по-английски typecast или просто cast) результата к типу переменной. Например, если оценка выражения дает вещественный результат, который присваивается целой переменной, то дробная часть результата будет отброшена, после чего будет выполнено присваивание. Ниже показан и обратный случай приведения:

int p;

double pReal = 2.718281828;

 

p = pReal; // p получает значение 2

pReal = p; // pReal теперь равно 2.0

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

р = рО + (int)(pReal + 0.5); // Округление pReal

Примечание

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

Смешанные выражения

В арифметическом выражении могут присутствовать операнды различных типов — как целые, так и вещественные, а кроме того, и те и другие могут иметь различную длину (short, long и т. д.), в то время как оба операнда любой арифметической операции должны иметь один и тот же тип.
 
MKPortal©2003-2008 mkportal.it
MultiBoard ©2007-2009 RusMKPortal