
-
Глава 3. Обзор языка с
-
Элементы простой программыr
-
Комментарии
-
Директивы #pragma
-
Директивы #inclide
-
Функция Main()
-
-
Представление данных в С
-
Литералы
-
Встроенные типы данных
-
Переменные
-
Типизированные константы
-
-
Операции и выражения
-
Семантика операций
-
-
Функциия
-
Ввод и вывод в С
-
Пример функции
-
Область действия переменных и связанные с ней понятия
-
-
Управляющие конструкции С
-
Условный оператор if...else
-
Оператор выбора Switch
-
Циклы
-
Операторы преобразования блока
-
Блоки и локальные переменные
-
-
Массивы и указатели
-
Массивы
-
Указатели
-
Указатели и массивы
-
-
Типы, определяемые пользователем
-
Переименование типов
-
Перечислимые типы
-
Структуры
-
Объединения
-
-
Элементы простой программы
Познакомимся со строением консольной программы Hello World.
#pragma hdrstop
#include <stdio.h>
#include <conio.h>
// Это комментарий
#pragma argsused
int main(int argc, char* argv[])
{
printf("Это консольное приложение\n");
printf("Press any key...");
getch() ;
return 0;
}
Строка нашей программы, начинающаяся с двух знаков дроби (//), является комментарием. В данном случае этот “комментарий” ничего не сообщает. Комментарии служат для вставки в текст программы пояснений, позволяющих другим программистам разобраться в назначении и работе тех или иных фрагментов кода, и для того, чтобы помочь самому программисту вспомнить, что же он написал полгода или месяц назад.
Комментарии совершенно игнорируются при компиляции программы, поэтому они могут содержать что угодно.
Вышеприведенная строка — комментарий в стиле C++. Стандартные компиляторы С таких комментариев не допускают. В языке С комментарий начинается с комбинации дробной черты и звездочки (/*) и заканчивается теми же символами в обратном порядке (*/). Он может занимать несколько строк, а может быть вставлен в середину строки (такие случаи бывают). Комментарий в стиле C++ начинается, как уже говорилось, с двойной дробной черты и продолжается до конца текущей строки. Язык C++ поддерживает оба типа комментариев.
Вот пример комментария в стиле С, который можно было бы поместить в самое начало исходного файла:
/ *
** Простейшая консольная программа C++Builder.
** Выводит на экран текст и ждет, пока
** пользователь не нажмет какую-нибудь клавишу.
*/
Он занимает, как видите, пять строк. А вот комментарий в стиле C++:
getch(); // Ожидание нажатия клавиши.
В данном случае комментарий размещен в конце строки и поясняет смысл расположенного в ней оператора.
Директивы # pragma
Строки исходного кода, начинающиеся со знака #, являются, как правило, директивами препроцессора, т. е. управляют обработкой текста программы еще до его передачи собственно компилятору (сюда относятся текстовые подстановки, вставка содержимого других файлов и некоторые специальные операции).
Директивы #pragma в этом смысле являются исключением, поскольку они адресованы непосредственно компилятору и служат для передачи ему различных указаний. Например, #pragma argsused говорит компилятору, что следует подавить выдачу предупреждающего сообщения о том, что параметры функции main () никак в ней не используются.
Часто
директивы #pragma эквивалентны некоторым
установкам компилятора, задаваемым в
диалоге Project Options. Например, упомянутые
выше сообщения о неиспользуемых
параметрах можно было бы запретить,
открыв диалог (Project | Options... в главном
меню) на странице Compiler и нажав кнопку
Warnings..., после чего будет открыто окно
со списком всех возможных предупреждений;
в нем следует сбросить флажок напротив
сообщения “Parameter 'parameter' is never used (-wpar)”.
Правда, тем самым в проекте будут запрещены все такие предупреждения, в то время как директива argsused позволяет управлять ими для каждой из функций в отдельности.
Подробнее о #pragma и других директивах мы поговорим в следующей главе.
Директивы #include
Директива # include заменяется препроцессором на содержимое указанного в ней файла. Обычно это заголовочные файлы с расширением .h. Они содержат информацию, обеспечивающую раздельную компиляцию файлов исходного кода и корректное подключение различных библиотек. Имя файла может быть заключено либо в угловые скобки , либо в обычные двойные кавычки (""). Эти случаи различаются порядком поиска включаемых файлов; если использованы угловые скобки, поиск будет сначала производиться в стандартных каталогах C++Builder, если кавычки — в текущем каталоге.
Функция main()
После всех директив в программе расположено определение функции main () . Любая программа на С содержит эту функцию, которая является ее входной точкой. Однако в среде Windows вместо main () часто используется WinMain () .
Функция main () — это, конечно, частный случай функции вообще. Функции являются основными “строительными блоками” программы, или подпрограммами. Они, в свою очередь, строятся из операторов, составляющих тело функции. Каждый оператор оканчивается точкой с запятой (;). В общем виде функция определяется таким образом:
Возвращаемый_тип имя_функции(список_параметров)
{
// В фигурных скобках заключено тело функции,
// составленное из отдельных операторов.
}
Функции
— единственный тип подпрограмм С, в
отличие, например, от языка Pascal, который
различает функции и процедуры. Под
процедурой обычно понимают подпрограмму,
не возвращающую никакого значения. В С
формально любая функция возвращает
какой-либо тип, хотя этот тип может быть
пустым (void).
В нашем случае тело функции состоит из четырех операторов, первые три из которых являются, в свою очередь, вызовами функций. Значения, возвращаемые функциями, здесь игнорируются, т. е. функции вызываются аналогично процедурам языка 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 ( "%s\n", 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 и т. д.), в то время как оба операнда любой арифметической операции должны иметь один и тот же тип. В процессе оценки таких выражений компилятор следует алгоритму т. н. приведения типов, который заключается в следующем.
На каждом шаге оценки выражения выполняется одна операция и имеются два операнда. Если их тип различен, операнд меньшего “ранга экстенсивности” приводится к типу более “экстенсивного”. Под экстенсивностью понимается диапазон значений, который поддерживается данным типом. По возрастанию экстенсивности типы следуют в очевидном порядке:
char short
int, long
float
double
long double
Кроме того, если в операции участвуют знаковый и беззнаковый целочисленные типы, то знаковый операнд приводится к беззнаковому типу. Результат тоже будет беззнаковым. Во избежание ошибок нужно точно представлять себе, что при этом происходит, и при необходимости применять операцию приведения, явно преобразующую тот или иной операнд.
Некоторые
считают, что в выражении все операнды
заранее приводятся к наиболее экстенсивному
типу, а уж потом производится оценка.
Это, конечно, не так. Возведение типов
выполняется последовательно для каждой
текущей пары операндов.
Логические операции и операции отношения
В языке С нет специального логического или булева типа данных(есть в C++ !!!).
Для представления логических значений используются целочисленные типы. Нулевое значение считается ложным (false), любое ненулевое — истинным (true).
Операции отношения служат для сравнения (больше — меньше) или проверки на равенство двух числовых операндов. Операции возвращают “логическое” значение, т. е. ненулевое целое в случае, если условие отношения удовлетворяется, и нулевое в противном случае.
Логические операции и отношения мы рассмотрим подробнее, когда будем обсуждать управляющие структуры С.
Поразрядные операции и сдвиги
Эти операции применяются к целочисленным данным. Последние рассматриваются просто как набор отдельных битов.
При поразрядных операциях каждый бит одного операнда комбинируется (в зависимости от операции) с одноименным битом другого, давая бит результата. При единственной одноместной поразрядной операции — отрицании (~) — биты результата являются инверсией соответствующих битов ее операнда.
При сдвиге влево биты первого операнда перемещаются влево (в сторону старших битов) на заданное вторым операндом число позиций. Старшие биты, оказавшиеся за пределами разрядной сетки, теряются; справа результат дополняется нулями.
Результат сдвига вправо зависит от того, является ли операнд знаковым или без знаковым. Биты операнда перемещаются вправо на заданное число позиций. Младшие биты теряются. Если операнд — целое со знаком, производится расширение знакового бита (старшего), т. е. освободившиеся позиции принимают значение 0 в случае положительного числа и 1 — в случае отрицательного. При без знаковом операнде старшие биты заполняются нулями.
Сдвиг влево эквивалентен умножению на соответствующую степень двойки, сдвиг вправо — делению. Например,
aNumber = aNumber <<4;
умножает aNumber на 16.
Инкремент и декремент
Операции инкремента (++) и декремента (--) соответственно увеличивают или уменьшают свой операнд (обязательно переменную) на единицу. Они изменяют значение самой переменной, т. е. являются скрытыми присваиваниями. Иногда эти операции применяют в качестве самостоятельного оператора:
i++; или ++i;
И то и другое эквивалентно
i = i + 1;
Но эти операции могут использоваться и в выражениях:
sum - sum + х * --i;
Инкремент и декремент реализуются в двух формах: префиксной (++i) и постфиксной (i--). Префиксные операции выполняются перед тем, как будет производиться оценка всего выражения. Все постфиксные операции выполняются уже после оценки выражения, в которое они входят.
Условная операция
Условная операция (? :) позволяет составить условное выражение, т. е. выражение, принимающее различные значения в зависимости от некоторого условия. Эта операция является трехместной. Если ее условие (первый операнд) истинно, оценкой выражения будет второй операнд; если ложно — третий. Классический пример:
max_ab = a > b? а : b;
Запятая
Помимо того, что запятая в С служит разделителем различных списков (как в списке параметров функции), она может использоваться и как операция. Запятая в этом качестве также является разделителем, но обладает некоторыми дополнительными свойствами.
Везде, где предполагается выражение, может использоваться список выражений, возможно, заключенный в скобки (так как операция-запятая имеет наинизший приоритет). Другими словами,
Выражение1, выражение2[, ...]
также будет выражением, оценкой которого является значение последнего элемента списка. При этом операция-запятая гарантирует, что оценка выражений в списке будет производиться по порядку слева направо. Вот два примера с операцией-запятой:
i++, j++; // Значение выражения игнорируется.
res = (j = 4, j += n, j++); // res присваивается n + 4.
// j равно n + 5.
Операция-запятая применяется довольно редко, обычно только в управляющих выражениях циклов.
Функции
Функция, как уже говорилось, является основным структурным элементом языка С. Выше мы уже показывали синтаксис определения функции:
возвращаемый_тип имя_функции(список_параметров)
{
<тело_функции>
}
Тело функции состоит из операторов, каждый из которых завершается точкой с запятой. (В отличие, скажем, от языка Pascal, точка с запятой является в С элементом оператора, а не разделителем.) Заметьте, что сам заголовок функции (его иногда называют сигнатурой) не содержит точки с запятой.
Оператор
в С не обязан располагаться в одной
строке. Он может занимать и несколько
строк; переход на следующую строку с
точки зрения компилятора эквивалентен
простому пробелу. Говоря точнее, перевод
строки является одним из пробельных
символов (whitespace), таких, как пробел,
табуляция и переход на новую страницу.
Пробельный символ может быть вставлен
между любыми соседними лексическими
элементами С.
Помимо определения для функции обычно пишется также ее объявление, или прототип, который размещается в заголовочном файле с расширением .h и служит для проверки корректности обращений к функции при раздельной компиляции исходных файлов. Прототип идентичен заголовку функции, но заканчивается точкой с запятой. Тело функции отсутствует:
возвращаемый тип имя функции(список параметров);
Функции пишутся для того, чтобы можно было их вызывать в различных местах программы. Вызов функции является выражением и принадлежит к типу, указанному в ее определении; он имеет вид
имя_функции(параметры)
Параметры, используемые при вызове функции, часто называют аргументами.
Значение,
возвращаемое функцией, можно игнорировать,
т. е. использовать функцию в качестве
процедуры:
DoSomething(argi, arg2);
Мы так и поступали, когда выводили на экран сообщения функцией
printf().
Функции,
“возвращающие значение” типа void, могут
вызываться только таким образом. С
другой стороны, возвращаемое функцией
значение можно использовать в выражениях
наряду с переменными и константами:
aResult = 1. - cos(arg);
Функция
в С может иметь переменное или, точнее,
неопределенное число параметров. В этом
случае за последним обязательным
параметром в заголовке функции следует
многоточие (...). Подобным образом
объявляется функция printf:
int printf(const char *format, ...);
Неопределенное число параметров означает, что количество и тип действительных аргументов в вызове должно так или иначе ей сообщаться, как это и происходит в случае printf () — там число аргументов определяется по числу спецификаторов в строке формата (см. следующий параграф). Тело функции с переменным числом параметров должно быть реализовано на языке ассемблера или, возможно, при помощи каких-то не вполне “законных” ухищрений.
Пока мы имели дело всего с тремя функциями: main (), printf () и getch () . Давайте поближе познакомимся с printf () и другими функциями ввода-вывода стандартной библиотеки С.
Ввод и вывод в С
printf () является функцией стандартной библиотеки с переменным числом аргументов. Она всегда имеет по крайней мере один аргумент — строку формата, чаще всего строковый литерал. Строка может содержать спецификаторы преобразования. Функция сканирует строку и передает ее символы на стандартный вывод программы, по умолчанию консоль, пока не встретит спецификатор преобразования. В этом случае printf () ищет дополнительный аргумент, который форматируется и выводится в соответствии со спецификацией. Таким образом, вызов printf () должен содержать столько дополнительных аргументов, сколько спецификаторов преобразования имеется в строке формата.
Спецификация преобразования
Синтаксис спецификатора преобразования имеет такой вид:
%[флаги] [поле][.точность][размер]символ типа
Как видите, обязательными элементами спецификатора являются только начальный знак процента и символ, задающий тип преобразования. Следующая таблица перечисляет возможные варианты различных элементов спецификации.
Таблица 3.4. Элементы спецификатора преобразования
Элемент |
Символ |
Аргумент |
Описание |
флаг |
- |
|
Выровнять вывод по левому краю поля. |
|
0 |
|
Заполнить свободные позиции нулями вместо пробелов. |
|
+ |
|
Всегда выводить знак числа. |
|
пробел |
|
Вывести пробел на месте знака, если число положительное. |
|
# |
|
Вывести 0 перед восьмеричным или Ох перед шестнадцатеричным значением. |
поле |
число |
|
Минимальная ширина поля вывода. |
точность |
число |
|
Для строк — максимальное число выводимых символов; для целых — минимальное число выводимых цифр; для вещественных — число цифр дробной части. |
размер |
h |
|
Аргумент -- короткое целое. |
|
1 |
|
Аргумент — длинное целое. |
|
L |
|
Аргумент имеет тип long double. |
Элемент |
Символ |
Аргумент |
Описание |
символ типа |
d |
целое |
Форматировать как десятичное целое со знаком. |
|
i |
целое |
То же, что и d. |
|
о |
целое |
Форматировать как восьмеричное без знака. |
|
U |
целое |
Форматировать как десятичное без знака. |
|
х |
целое |
Форматировать как шестнадцатеричное в нижнем регистре. |
|
Х |
целое |
Форматировать как шестнадцатеричное в верхнем регистре. |
|
f |
вещественное |
Вещественное в форме [-]dddd.dddd. |
|
е |
вещественное |
Вещественное в форме [-]d.dddde[+|-]dd. |
|
Е |
вещественное |
То же, что и е, с заменой е на Е. |
|
ё |
вещественное |
Использовать форму f или е в зависимости от величины числа и ширины поля. |
|
G |
вещественное |
То же, что и g — но форма f или Е. |
|
с, |
символ |
Вывести одиночный символ. |
|
s |
строка |
Вывести строку. |
|
п |
указатель |
Аргумент — указатель на переменную типа int. В нее записывается количество выведенных к данному моменту символов. |
|
р |
указатель |
Вывести указатель в виде шестнадцатеричного числа ХХХХХХХХ. |
Как видите, флаги задают “стиль” представления чисел на выводе, поле и точность определяют характеристики поля, отведенного под вывод аргумента, размер уточняет тип аргумента и символ_типа задает собственно тип преобразования. Следующий пример показывает возможности форматирования функции printf () . Советую не полениться и поэкспериментировать с этим кодом, меняя флаги и параметры поля вывода.
Листинг 3.1. Возможности функции printf ()
/*
** Printf.с: Демонстрация форматирования вывода на консоль
** функцией printf().
* /
#pragma hdrstop
#include <stdio.h>
#include <conio.h>
#pragma argsused
int main(int argc, char *argv[])
{
double p = 27182.81828;
int j = 255;
char s[] = "Press any key...";
/* Вывести 4 цифры; вывести обязательный знак: */
printf("Test integer formatting: %13.4d %4-8d\n", j, j);
/* Вывести по левому краю со знаком; заполнить нулями: */ printf("More integer formatting: %-+13d % 08d\n", j, j);
printf("Test octal and hex: %#13o %#8.6x\n", j, j);
printf("\nTest e and f conversion: %13.7e %8.2f\n", p, p) ;
printf("\n%s", s); /* Вывести строку подсказки. */
getch () ;
return 0;
}
Рис. 3.1 Тестирование функции printf()
Escape-последовательности
В строках языка С для представления специальных (например, непечатаемых) символов используются escape-последователъности, состоящие из обратной дробной черты, за которой следует один или несколько символов. (Название появилось по аналогии с командами управления терминалом или принтером, которые действительно представляли собой последовательности переменной длины, начинающиеся с кода ESC.) В приведенных примерах функции printf () вы уже встречались с одной такой последовательностью — \n. Сама обратная косая черта называется escape-символом..
В таблице 3.5 перечислены возможные esc-последовательности.
Таблица 3.5. Escape-последовательности языка С
Последовательность |
Название |
Описание |
\а |
Звонок |
Подает звуковой сигнал. |
\b |
Возврат на шаг |
Возврат курсора на одну позицию назад. |
\f |
Перевод страницы |
Начинает новую страницу. |
\n |
Перевод строки |
Начинает новую строку. |
\r |
Возврат каретки |
Возврат курсора к началу текущей строки. |
\t |
Табуляция |
Переход к следующей позиции табуляции. |
\v |
Вертикальная табуляция |
Переход на несколько строк вниз. |
\\ |
|
Выводит обратную дробную черту. |
\' |
|
Выводит апостроф (одинарную кавычку). |
\" |
|
Выводит кавычку (двойную). |
Кроме того, esc-последовательности могут представлять символы в ASCII-коде — в восьмеричном или шестнадцатеричном формате:
\000 |
От одной до трех восьмеричных цифр после esc-символа. |
\хНН или \ХНН |
Одна или две шестнадцатеричных цифры после esc-символа. |
Функции ввода строки — scanf() и gets()
В языке С для ввода имеется “зеркальный двойник” printf() — функция scanf (). Функция читает данные со стандартного ввода, по умолчанию — клавиатуры. Она так же, как и printf () , принимает строку формата с несколькими спецификаторами преобразования и несколько дополнительных параметров, которые должны быть адресами переменных, куда будут записаны введенные значения.
В
языке С функция не может изменять
значение передаваемых ей аргументов,
поскольку ей передается только временная
копия содержимого соответствующей
переменной. Это называется передачей
параметра по значению.
( В языкеС++ возможна также передача по ссылке, позволяющая функции изменить саму переменную-аргумент).
Чтобы передать из функции некоторое значение через параметр, ее вызывают с указателем на переменную (грубо говоря, ее адресом), подлежащую модификации. Функция не может изменить переданный ей аргумент, т. е. сам адрес, но она может записать информацию в память по этому адресу. Адрес получают с помощью операции &, например, &aVar. Подробнее мы обсудим это, когда будем говорить об указателях.
Примером вызова scanf () может служить следующий фрагмент кода:
int age;
printf("Enter your age: "); //' Запросить ввод возраста
scanf ("%d", &age); // Прочитать введенное число.
Функция возвращает число успешно сканированных полей, которое в приведенном фрагменте игнорируется. При необходимости вы можете найти полную информацию по scanf () в оперативной справке C++Builder. Однако следует сказать, что программисты не любят эту функцию и пользуются ей очень редко. Причина в том, что опечатка при вводе (скажем, наличие буквы в поле, предполагающем ввод числа и т. п.) может привести к непредсказуемым результатам. Контролировать корректность ввода и обеспечить адекватную реакцию программы на ошибку при работе со scanf () довольно сложно. Поэтому часто предпочитают прочитать целиком всю строку, введенную пользователем, в некоторый буфер, а затем самостоятельно декодировать ее, выделяя отдельные лексемы и преобразуя их в соответствующие значения. В этом случае можно контролировать каждый отдельный шаг процесса преобразования.
Ввод строки с клавиатуры производится функцией gets ():
char s[80] ;
gets (s) ;
Буфером,
в который помещается введенная строка,
является здесь символьный массив s [80
]. О массивах чуть позже, пока же скажем,
что в данном случае определяется буфер,
достаточный для хранения строки длиной
в 79 символов — на единицу меньше, чем
объявленная длина массива. Одна
дополнительная позиция необходима для
хранения признака конца строки; все
строки в С должны оканчиваться
нуль-символом \0, о котором программисту,
как правило, заботиться не нужно. Функции
обработки строк сами распознают эти
символы или, как gets (), автоматически
добавляют нуль-символ в конец
строки-результата. Функция gets () возвращает
данные через параметр, поэтому, как
говорилось выше, ей нужно передать в
качестве параметра адрес соответствующего
символьного массива. Операция взятия
адреса, однако, здесь не нужна, поскольку
имя массива (без индекса) само по себе
является указателем на начало массива.
Забегая вперед, скажем, что показанная
нотация эквивалентна
gets(&s[0]);
Для преобразования строк, содержащих цифровое представление чисел, в численные типы данных могут применяться функции atoi(), ato1 () и atof (). Они преобразуют строки соответственно в целые, длинные целые и вещественные числа (типы int, long и double). Входная строка может содержать начальные пробелы; первый встреченный символ, который не может входить в число, завершает преобразование. Прототипы этих функций находятся в файле stdlib.h.
Пример функции
Теперь мы напишем небольшую программу, которая проиллюстрирует все существенные моменты создания функции; в программе применяются некоторые из функций для работы со строками, описанных выше.
Пользователю предлагается ввести имя (в произвольной форме — только имя, имя и фамилию и т. п.), а затем номер телефона, просто как 7-значное число без пробелов или дефисов. После этого программа распечатывает полученные данные, выводя номер телефона в более привычном формате (рис. 3.2).
Листинг 3.2. Пример создания функции
/*
** Convert.с: Пример функции, преобразующей число
** в строку специального вида.
*/
#pragma hdrstop
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
/* Прототип функции */
void Convert(char *buffer, long num);
//-----------------------------------------
#pragma argsused