Справочник-конспект-С++

advertisement
ОСНОВЫ ПРОГРАММИРОВАНИЯ НА ЯЗЫКЕ С++
1. Объекты и их атрибуты. Атрибуты идентификатора
Объект - область памяти, выделенная для хранения информационной единицы (значения) некоторого типа.
Объект имеет 5 атрибутов: адрес, размер, идентификатор, значение, тип.
Адрес объекта это адрес его первого байта.
Размер - величина объекта в байтах.
Идентификатор представляет собой имя объекта, выбираемое по определенным правилам: это любая
последовательность латинских букв, цифр и некоторых специальных символов, которая начинается с буквы.
Длина идентификатора произвольная, но значащими являются только первые 32 символа. При различении
идентификаторов учитывается регистр. Никакой идентификатор не должен совпадать с ключевым словом.
Значение - информация, которая заносится путем выполнения операций присваивания, инициализации,
ввода.
Тип объекта определяет:
- множество, которому могут принадлежать его значения;
- способ двоичного представления значения;
- правила выполнения операций над его значениями;
- размер объекта.
По способу создания и удаления все объекты можно разделить на три категории:
a) статические,
б) локальные,
в) динамические.
Статические объекты создаются еще до начала выполнения программы, а удаляются уже после завершения
ее работы. Выполняются эти операции операционной системой, запускающей программу на выполнение.
Локальные объекты создаются в точке его объявления и удаляются после выхода из сферы действия
объекта. Выполняются эти операции программой автоматически.
Размещаются статические и локальные объекты в сегменте данных или в стеке.
Динамические объекты создаются операциями new и new[] и удаляются при выполнении операций delete и
delete[]. Размещаются они в куче (heap-область памяти).
В объектно-ориентированном программировании объект имеет структуру, которая позволяет сохранять не
только информацию, но также и процедуры (функции) обработки этой информации. По сравнению с
процедурным программированием В ООП объект в гораздо большей степени напоминает модель некоторой
сущности.
Пространство имен - область программы, в пределах которой данный идентификатор должен быть
уникальным. Например: для меток пространством имен является функция, для имен компонентов класса –
класс, для локальной переменной – блок.
Ниже приведен пример программы, в которой реализованы 4 пространства имен: одно глобальное и еще 3
вложенных.
#include <stdio.h>
int R=0;
 переменная в глобальном пространстве
void main()
(уровень 0)
{ int R=1;
 переменная в пространстве уровня 1
{ int R=2;
 переменная в пространстве уровня 2
{ int R=3;
 переменная в пространстве уровня 3
printf(“Уровень 3: R=%d \n”, R);
}
printf(“Уровень 2: R=%d \n”, R);
}
printf(“Уровень 1: R=%d \n”, R);
printf(“Уровень 0: R=%d \n”, ::R);
}
2.2. Операторы
Любой оператор завершается точкой с запятой.
Различают 5 видов операторов:
1) оператор-объявление;
2) стандартный оператор;
3) вызов процедуры;
4) оператор-выражение;
5) составной оператор.
Оператор-объявление содержит описание атрибутов объектов. При выполнении оператора-объявления
обычно (но не всегда!) выполняется две операции: создается один или несколько объектов.
Стандартные операторы реализуют типовые элементы алгоритмов, такие как циклы, ветвления, условные и
2
безусловные переходы и т.д.
Процедурой будем называть функцию, которая не возвращает значения. Вызов процедуры завершается
символом ";" и является, таким образом, оператором.
Оператор-выражение это выражение, которое завершается точкой с запятой.
Составной оператор - последовательность операторов, заключенная в фигурные скобки:
{ опер_1; опер_2; ... } .
Составной оператор можно писать всюду, где можно использовать простой оператор.
Блоком называется составной оператор, который содержит, по крайней мере, один оператор-объявление.
Метки
Перед любым оператором можно поставить метку:
метка: оператор;
Метка есть идентификатор.
Ниже приведены форматы стандартных операторов.
Оператор перехода на метку имеет формат:
goto метка;
Особенностью этого оператора является то, что он не может указывать переход вперед через объявление
переменной с инициализацией. Компилятор выдает сообщение “Goto bypasses initialization of a local variable”. В
то же время назад такой переход возможен.
Оператор выбора имеет формат:
if (S) опер_1; [ else опер_2; ]
Оператор-переключатель имеет формат:
switch (S)
{
case C1: операторы
case C2: операторы
..............................
[default : операторы]
};
Выражение S может иметь любой целый тип (char, short, int, long, enum, bool). Константа С1 (С2, ...) наз.
меткой варианта. Если значение выражения S совпадает с одной из меток варианта, делается переход на нее.
После этого выполняются все последующие операторы. Если S не совпадает ни с одной меткой варианта,
делается переход на строку default (если она есть). Выражение S должно иметь целый тип. В списке
"операторы" обычно последним записывают break .
Пример использования оператора-переключателя switch. Программа обрабатывает нажатие клавиш “y”,
“n”, Esc.
#include <syst.h>
void main()
{ char x;
char *s1 = “Нажата клавиша ‘y’”;
char *s2 = “Нажата клавиша ‘n’”;
for (;;) { x=getkey();
switch (x) { case ‘y’: puts(s1); break;
case ‘n’: puts(s2); break;
case 27: exit(0);
}
}
}
Оператор цикла while:
while (S) оператор;
Оператор (тело цикла) выполняется, если S истина (т.е. не ноль).
Пример использования оператора while для определения наибольшего общего делителя двух целых чисел:
int a=125, b=2500, r;
while (r = a%b) { a=b; b=r; }
printf(“НОД = %d \n”, b);
Оператор цикла do-while:
do оператор; while (S);
Оператор выполняется еще раз, если S истина (не ноль).
Оператор цикла for:
for (иниц; ЛВ; модиф) оператор;
Здесь иниц - объявление и инициализация (или только инициализация) переменных;
ЛВ
выражение, вычисляемое до выполнения оператора – тела цикла. Если оно истинно - оператор выполняется,
если ложно - работа оператора for завершается;
модиф - операторы, изменяющие значения переменных.
Один из элементов в круглых скобках может отсутствовать, однако сим-волы-разделители ";" сохраняются.
Например:
3
for (;;) оператор; - бесконечный цикл без инициализации и модификации;
for (int k=0;;) оператор; - бесконечный цикл с объявлением и инициализацией переменной k;
for (i=0,j=N;;i++,j--) оператор; - цикл с инициализацией переменных и их модификацией, но без
проверки условия.
Оператор выхода из блока:
break;
осуществляет выход из тела оператора do, for, while, switch.
Оператор перехода в начало тела цикла:
continue;
осуществляет переход в начало тела оператора do, for, while.
Оператор выхода из функции:
return [выражение];
формирует возвращаемое значение и осуществляет выход из функции.
2.3. Выражения, операции и приоритеты
Выражение – фрагмент кода, выполнение которого формирует (вычисляет) некоторое значение.
Выполнение выражения может приводить к изменению значений некоторых переменных. Для сохранения
значения, формируемого при выполнении выражения, создается временный объект. После завершения
текущего оператора временный объект уничтожается.
Выражение может иметь один из двух видов: Lvalue, Rvalue.
Rvalue - результатом выполнения выражения является значение некоторого типа;
Lvalue - результатом выполнения выражения является имя или ссылка для некоторого объекта (можно
говорить, что результатом является объект).
Rvalue может находиться только справа от знака присваивания, Lvalue может находиться как слева, так и
справа.
В качестве примера отметим следующее. Пусть a - переменная типа int. Тогда ++a является Lvalue, а a++
есть Rvalue. Почему?
Вопрос. Пусть имеется объявление:
int x, a=2, b=3;
Будет ли выполнен оператор:
x = a+++++b;
Если он не выполняется, то почему?
Ответ. Выражение справа компилятор интерпретирует как ((a++)++)+b , однако инкрементирование
(a++)++ выполнить нельзя, т.к. a++ не Lvalue! В то же время такой оператор выполняется:
x = (a++)+(++b);
Вопрос. Если a = 2 , то чему равны значения x, a после выполнения такого оператора:
x = (++a)++;
Вопрос. Если начальное значение а = 5, то чему равно значение а после выполнения оператора:
++++a = a+++1;
Ответ: а = 9 . Отметим, что вначале вычисляется выражение слева от символа присваивания, затем
выражение справа и после этого выполняется присваивание.
Элементами, из которых строится выражение, являются: константы, переменные, функции, символы
операций (operators), скобки.
Операции
По числу операндов все операции делятся на унарные, бинарные и тернарные.
Кроме того, операции можно разделить на операции=функции и операции-процедуры. К последним относят
операции, которые изменяют значения своих операндов.
Имеется 49 операций, которые можно разделить на 5 групп:
- арифметические;
- операции отношения и логические операции;
- побитовые операции;
- операции присваивания;
- другие операции.
По сравнению с языком С в язык С++ добавлено несколько операций.
Арифметические операции:
+
- сложение;
- вычитание или унарный минус;
*
- умножение;
/
- деление;
%
- остаток от деления;
++
- инкрементирование;
-- декрементирование.
Операции отношения и логические операции:
==
- равно;
!=
- не равно;
4
<
- меньше;
<=
- меньше или равно;
>
- больше;
>=
- больше или равно;
!
- логич. отрицание;
||
- логич. ИЛИ;
&& - логич. И.
Побитовые операции:
~
- дополнение;
>>
- сдвиг вправо;
<<
- сдвиг влево;
&
- операция И;
^
- исключающее ИЛИ (неэквивалентность);
|
- операция ИЛИ.
Операции присваивания:
=
- присвоить;
+=
- выполнить операцию и присвоить значение левому операнду;
-=
--- " --;
*=
--- " --;
/=
--- " --;
%=
--- " --;
>>= --- " --;
<<= --- " --;
&=
--- " --;
|=
--- " --;
^=
--- " --.
Присваивание является бинарной операцией. Левым операндом может быть выражение вида Lvalue, правым
- выражение Rvalue или Lvalue.
Вопрос. Имеется оператор вида:
выражение_1 = выражение_2;
Какое выражение, первое или второе, вычисляется сначала?
Ответ: Компилятор Borland вначале вычисляет левый операнд, затем правый, после этого выполняется
присваивание. Результатом выполнения операции "=" является значение левого операнда. Убедиться в этом
можно проанализировав, например, выполнение операторов:
int a=5;
++++a = a+++1;
Стандарт С++ не регламентирует порядок вычисления левого и правого выражений в операторе
присваивания. Отсюда следует такая рекомендация: не используйте операторы, в которых последовательность
вычисления операндов операции присваивания является значимой.
Другие операции:
&x
- адрес переменной x;
*p
- объект, на который указывает указатель p;
::x
- доступ к скрытому объекту;
S.x
- компонент x объекта S;
S.*p
- компонент объекта S, на который указывает указатель p;
p->x
- компонент x объекта, на который указывает p;
pS->*p
- из объекта, на который указывает указ. pS, выбрать компонент, на
который указывает указ. p
s?x:y
- операция выбора;
sizeof(x) или sizeof x - размер объекта x;
sizeof(тип) - размер объекта указанного типа;
(тип)x
- преобразование x к указанному типу;
(тип*)p
- преобразование указателя p к указанному типу;
new тип
- создание объекта указанного типа;
new тип [размер] - создание массива объектов указанного типа;
delete p
- удаление объекта, на который указывает p;
delete[] p
- удаление массива объектов, на который указывает p;
A,B
- выполнить выражения A,B, вернуть значение B;
[...]
- выбрать элемент массива;
имя(...) - вызвать функцию.
Категории приоритетов и ассоциативность операций не изменились. Символы операций, категории
приоритетов и ассоциативность приведены ниже в таблице.
Таблица 2.3.1. Категории приоритетов и ассоциативность операций.
5
Приоритет
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Операции
() []
-> ::
.
! ~
-++
&
sizeof new delete
* (тип)
Ассоц.


.* ->*
* / %
+ << >>
< <= > >=
== !=
&
^
|
&&
||
?:
= *= /= %= += - =
&= ^= |= <<= >>=













Пример использования тернарной операции <?:>. Программа подсчитывает количество нечетных элементов в
заданном числовом массиве.
{
void main()
const N = 12;
int x[N] = { 12,3,5,8,5,9,10,23,34,57,79,11 };
int i, K = 0;
for (i=0;i<N;i++) K+= x[i]%2 ? 1 : 0 ;
printf(“K = %d \n”, K);
}
2.4. Константы и константные объекты
Константы могут быть:
- целые,
- вещественные;
- символьные;
- строковые;
- типа enum.
Целые константы могут иметь значения:
Десятичные
- до 4294967295 ;
16-ричные
- до 0xFFFFFFFF .
16-рич. константа начинается с символов 0x . При записи целых констант могут использоваться суффиксы:
L
- для длинных констант (4 байта),
U
- для беззнаковых констант.
Вещественная константа содержит точку и/или символ е. При отсутствии суффикса имеет тип double.
Суффикс ‘F’ или ‘f’ делает тип float, а суффикс ‘L’ или ‘l’ делает тип long double.
Символьная константа - один символ, заключенный в апострофы, например: ‘A’ , 'B’ , '$’ . Символ можно
указать шестнадцатиричным числовым кодом: например, символ ‘\xА1’ представляет собой русскую букву ‘б’.
Специальные символы записываются с помощью символа “\”. Пример наиболее часто используемых спец.
символов:
\\
- символ "\";
\'
- апостроф;
\"
- кавычки;
\n
- переход в следующую строку;
\r
- возврат в начало текущей строки;
\0
- нулевой символ.
Строковая константа - последовательность символов в кавычках, например:
“IBM PC XT/AT” . В строковую константу автоматически заносится завершающий символ “\0”.
Константы типа enum - идентификаторы-значения типа enum.
Константные выражения
Всюду, где используются константы, можно использовать константные выражения.
Именованные константные объекты объявляются с применением служебного слова const :
const double pi = 3.141592 ;
6
Правило.
Объявление любого константного объекта должно содержать инициализацию.
Никакой оператор не может изменить значение объявленного константного объекта.
Константным может быть объявлен объект любого типа.
2.5. Объявления переменных и инициализация
Объявление переменной записывается след. образом:
тип имена переменных ;
Например:
double a,b,c;
Объявление переменной может записываться в любой точке программы (не обязательно в начале).
Объявление переменной может содержать инициализацию:
double a = 2 , b = 3 , c = 2*a+b ;
int m(125);
Для инициализации могут использоваться выражения соответствующего типа, которые могут быть
вычислены.
При создании объектов, их инициализации и удалении используются специальные функции - конструкторы
и деструкторы. В случае встроенных типов конструкторы и деструкторы используются компилятором неявно.
2.5. Типы и их классификация
Все типы, используемые в С++, делятся на 3 вида:
- встроенные (базовые),
- производные,
- непредопределенные.
Перечень встроенных типов приведен ниже.
- [signed]
char
- unsigned
char
- [signed]
short
- unsigned short
целые
- [signed]
int
- unsigned
int
- [signed]
long
- unsigned
long
- float
- double
вещественные
- long double
При этом тип int для 16-разр. платформы совпадает с типом short, а в случае 32-разр. платформы он
совпадает с типом long. Тип char, в зависимости от установленной опции, интерпретируется или как signed char,
или как unsigned char.
К производным типам относятся:
- указатель ,
- ссылка
.
Непредопределенные типы относятся к одной из следующих категорий:
- enum
( перечисление )
- struct
( структура )
классовые
- class
( класс )
типы
- union
( объединение )
Различают понятия: объявление типа и определение типа. Объявление типа имеет предварительный
характер и содержит информацию только об имени типа (и, может быть, о некоторых его свойствах).
Тип
Signed
Тип
Характеристики целых числовых типов (платформа Win32)
Размер
Диапазон значений
char
1
-128 .. 127
Unsigned char
1
Signed
short (int)
Unsigned short (int)
Signed
long
2
2
4
-32768 .. 32767
0 .. 65535
-2147483648 .. 2147483647
Unsigned long
4
0 .. 4294967295
Размер
0 .. 255
Характеристики вещественных типов
Диапазон значений
Точность
Дл. мант.
7
Float
Double
Long double
4
8
10
-38
310 .. 31038
210-308 .. 210308
310-4932 .. 1104932
3.010-08
5.610-17
2.710-20
24
53
64
Логический тип bool
Стандарт ANSI C++ содержит несколько новых служебных слов языка С++: bool, explicit, mutable, typename
и другие. Служебное слово bool явлется именем нового встроенного типа (логический тип). Пременная q
объявленная как
bool q;
может принимать значения false (или FALSE) и true (или TRUE). Тип bool определен примерно следующим
образом:
enum bool { true = (1 == 1) , false = (1 == 0) };
Переменная типа bool может использоваться в составе (или в качестве) логического выражения.
2.6. Указатели
Указатель (pointer) - переменная, значением которой может быть адрес некоторого объекта.
Можно использовать типизированные и нетипизированные (родовые) указатели.
Пример объявления типизированного указателя:
float* pf;
Пример объявления нетипизированного указателя:
void* pv;
Нетипизированному указателю можно присвоить адрес объекта любого типа без указания операции
приведения типа:
double x,y;
void* px = &x;
Однако при выполнении операции с объектом через нетипизированный указатель операцию приведения
типа необходимо записывать:
y = fabs(*pv);
 ошибка!
y = fabs(*(double*) pv);
 правильно!
Запись вида (тип*) является символом унарной операции преобразования типа указателя.
Унарная операция «*» , действующая на указатель, дает объект (значение вида Lvalue), адрес которого
равен значению указателя. В следующем примере
float a = 5;
float* p = &a;
*p = *p/2;
последний оператор действует также, как оператор
a = a/2;
Значение 0 или NULL для указателя означает, что он не указывает ни на какой объект.
Арифметика указателей
Арифметические операции над указателями выполняются по особым правилам (адресная арифметика).
Увеличение значения указателя на единицу устанавливает его на следующий объект соответствующего типа.
Увеличение указателя на число k перемещает его на k объектов вперед.
Основное применение указателей: работа с массивами и динамическими объектами.
Указатели и постоянные объекты
Необходимо различать указатель на постоянный объект и постоянный указатель на объект:
const double* p;
 p указывает на пост. объект
double* const p;
 p является константой
const double* const p;
 p константа, указывающая на постоянный
объект
Ниже приведен интересный пример использования указателя на тип char для построения функции strcpy,
осуществляющей копирование строки s2 в строку s1:
void strcpy(char* s1 , char* s2)
{
while (*s1++ = *s2++);
}
Указатели на функции
Указатели на функцию объявляется с использованием специального синтаксиса:
int (*pfun) (float, float);
Тут pfun – указатель на функцию, которая принимает два параметра типа float и возврацаеи значение типа int.
Такие объявления указателей неудобны, поэтому для объявления указателей на функции обычно
используют объявление typedef :
typedef (*ptrffi) (float,float);
ptrffi pfun;
8
Типичные ошибки при использовании указателей:
- операции с неопределенными указателями;
- потеря объекта при выходе из блока.
Пример выполнения операции с неопределенным указателем:
float *p;
*p=12.5; ошибка! Указатель p еще не указывает ни какой
объект.
Еще один пример ошибки:
char s = "Borland Builder 6.0";
s = "Intel";
Область памяти, в которой сохраняется первая строка, потеряна навсегда!
Ссылки
Ссылка (reference, ссылочный тип) тип объявляется с применением символа &. С этим типом связаны
следующие понятия:
- переменная типа ссылка,
- параметр функции типа ссылка;
- возвращаемое значение типа ссылка.
Переменная ссылочного типа представляет собой имя (может быть дополнительное) некоторого объекта.
Например:
float A;
float &R=A;
Теперь R дополнительное имя (синоним) объекта A.
Объявление ссылочной переменной должно обязательно содержать инициализацию.
В отличие от указателя, ссылка не является самостоятельным объектом.
Константная ссылка имеет ряд особенностей.
Рассмотрим пример:
int a = 3;
const int &b = a;
 b есть константная ссылка
a = 4;
 нормально
b = 4;
 нельзя!
В этом примере один объект имеет два имени. Через имя a допускается модификация объекта, а через имя b
- нет.
Ссылка может использоваться для работы с динамическим объектом как с именованным:
#include <syst.h>
void main()
{ setalmem();
double &a= *(new double);
// Создание динамического объекта с именем a
printf("mem1= %ld \n",almem());
a=3.3333;
printf("a= %f \n", a);
delete &a;
// Удаление объекта
printf("mem2= %ld \n",almem());
}
В примере, приведенном ниже, в объявлении double* &a= p; a является ссылкой на указатель p и может
использоваться для доступа к элементам массива. В то же время, в объявлении double &b = *p; b есть ссылка
на первый элемент массива, она не может быть использована для доступа к элементу массива, кроме нулевого.
#include <syst.h>
void main()
{ double *p= new double[10];
double* &a= p;
double &b= *p;
int i;
for (i=0;i<10;i++) a[i]=1;
for (i=0;i<10;i++) printf("%5.2f ",a[i]); puts("");
for (i=0;i<10;i++) b=2;
// <-- изменение элемента a[0]
for (i=0;i<10;i++) printf("%5.2f ",a[i]); puts("");
pause;
}
Использование ссылок в качестве параметров функции и возвращаемого значения будет рассмотрено позже.
6. Функции простого ввода/вывода
9
Определения функций ввода/вывода, о которых говорится ниже, находятся в <stdio.h>.
Ввод данных с клавиатуры выполняется с помощью функции
scanf(format, p1, p2,... ) ;
где format - спецификация формата, а p1, p2, ... - адреса объектов.
Общий вид спецификации формата для ввода:
" %символ " ,
где символ - один из символов преобразования формата.
Вывод данных на экран выполняется с помощью функции
printf(format, e1, e2, ... );
где e1, e2, ... - выражения.
Общий вид спецификации формата для вывода:
" %[ширина][.точность]символ " ,
где ширина
- ширина поля вывода;
точность
- число цифр после дес. точки.
Ниже приведены наиболее часто используемые форматы.
%c
- ввод/вывод символа,
%s
- ввод/вывод строки символов,
%d
- ввод целого числа типа short, int, вывод целого числа типа char, short, int,
%ld
- ввод/вывод целого числа типа long,
%u
- ввод/вывод целого числа типа unsigned char, unsigned short, unsigned int;
%lu
- ввод/вывод целого числа типа unsigned long,
%[l]x - ввод/вывод 16-рич. целого;
%f
- ввод/вывод веществ. числа типа float,
%lf
- ввод/вывод веществ. числа типа double,
%Lf
- ввод/вывод веществ. числа типа long double,
%*d
- при вводе пропустить значение типа d (можно указать любой другой тип).
Предусмотрены форматы ввода по образцу.
Ввод одного символа можно осуществлять с помощью функций getchar() и getch().
Функция getchar() возвращает очередной символ из буфера клавиатуры. Если буфер пуст, ожидается ввод,
который должен завершаться клавишей Enter.
Фунцкия getch() вводит символ нажатой клавиши немедленно, без эхо-отображения.
Для ввода строки можно использовать функцию gets(), для вывода - процедуру puts(s), где s - переменная
типа char*. Оператор puts(“”) вызывает переход курсора в следующую строку.
Тип enum
Тип enum - это множество слов-идентификаторов. Пример объявления этого типа с именем DAY:
enum DAY { mon, tus, wed, th, fri, sat, sun };
Пример объявления и инициализации переменных типа DAY:
DAY x=mon, y=tus;
Переменные x,y можно объявить и непосредственно:
enum { mon, tus, wed, th, fri, sat, sun } x=mon, y=tus;
Каждому enum-слову ставится в соответствие целое число – его порядковый номер. Компилятор
интерпретирует значения типа enum как значения числового типа unsigned char.
Для типа enum нет стандартных функций ввода/вывода, однако их нетрудно написать самому.
Используя тип enum легко создать отсутствующий в С++ логический тип:
enum boolean { false, true };
Интересной возможностью использования типа enum является применение enum-значений в качестве
именованных констант:
enum { small=10, large=1000 };
// глобальный уровень
Теперь small и large можно использовать в качестве констант:
int a[small], A[large];
8. Массивы
Массивы могут быть статическими и динамическими. Статические массивы создаются в точке объявления и
существуют до выхода идентификатора из сферы действия. Динамические массивы создаются и уничтожаются
специальными операциями new и delete с применением указателей.
Примеры объявления статических массивов:
int A[100];
float B[10][8];
Размер массива должен быть константой. Минимальное значение индекса принято равным нулю. Так, в
первой строке приведенного примера объявлен массив: a[0], a[1], ... a[99].
Двухмерный массив представляется как массив массивов.
Объявление массива может включать его инициализацию:
10
int A[5] = { 25,12,6,14,8 };
Если размер инициализируемого массива не указан, он определяется по количеству указанных значений:
int B[] = { 0,1,2,3,4,5 };
Для инициализации двухмерного массива используются вложенные скобки:
int M[3][4] =
{ { 0 , 1 , 2 , 3 },
{ 4 , 5 , 6 , 7 },
{ 8 , 9 , 0 , 1 } };
Первый размер многомерного массива, объявляемого с инициализацией, можно не указывать.
Если размер массива задан явно, то значения, не указанные в списке инициализации, принимаются равными
нулю. Объявить массив и инициализировать его нулями можно следующим образом:
int A[15] = {0};
double X[10][16] = {0.0};
Операция sizeof(...) для массива дает размер массива. Например,
int A[10][10];
printf("Размер массива = %d", sizeof(A));
дает:
Размер массива = 200 .
Массивы больших размеров
Если массив должен иметь размер больше, чем 64k, то следует использовать одну из двух возможностей:
- объявить глобальный массив с применением ключевого слова huge,
- создать динамический массив.
Символьные массивы
Символьная строка - это массив типа char. Например, объявить строковую переменную T и
инициализировать ее значением “Ukraine Technology” можно следующим образом:
char T[20] = "Ukraine Technology";
Для вывода значения переменной S можно использовать оператор
printf(“%s”, T);
или оператор
puts(T);
Пример объявления массива строк с инициализацией:
#include <syst.h>
void main()
{ clrscr();
char* A[20] = { "Понедельник",
"Вторник",
"Среда",
"Четверг",
"Пятница",
"Суббота",
"Воскресенье" };
for (int i=0;i<7;i++) printf("%s \n",A[i]);
}
2.8. Функции
Структура объявления (определения) функции, возвращающей значение, имеет следующий вид:
тип fun(параметры)
{ тело функции
}
Если функция не возвращает никакого значения, возвращаемый тип записывается как void. Такую функцию
удобно называть процедурой.
Параметры функции
В качестве параметра функции может использоваться переменная, указатель или ссылка:
int F(int k, int *p, int &x) { … }
Параметры функции являются, по существу, локальными переменными. В момент вызова функции
происходит копирование значений фактических копируется в соответствующие формальные параметры
функции.
Различие между параметрами-указателями и параметрами-ссылками иллюстрируется в следующем примере
для функции swap, которая осуществляет обмен значениями двух объектов:
Вариант 1
void swap(long *px, long *py)
{ long R;
R=*px; *px=*py; *py=R;
}
Вариант 2
void swap(long &x, long &y)
{ long R=x; x=y; y=R;
}
11
вызов функции:
long a,b;
swap(&a,&b);
вызов функции:
long a,b;
swap(a,b);
Параметры-массивы никогда не передаются по значению: передается адрес первого элемента массива.
Можно объявить функцию с переменным числом параметров. Переменная часть списка параметров в
заголовке такой функции указывается многоточием, например:
int F(int a, ...);
Для обработки списка параметров в этом случае используются средства из модуля <stdarg.h> .
Инициализация параметров функции
Заголовок функции может содержать инициализацию аргументов:
long F(int a, long x=0, long y=1)
Инициализируемые параметры должны быть последними в списке. Они могут не использоваться при вызове
функции. Все следующие вызовы функции F являются правильными: F(n,a,b) , F(n,a) , F(n) .
Возвращаемое значение
Функция может возвращать:
- значение любого типа,
- адрес объекта,
- ссылку на объект.
Рассмотрим случаи, когда функция возвращает ссылку.
Пример 1
Функция Rm(i,j) позволяет обращаться к одномерному глобальному массиву R[400] как к двумерной
матрице размером 20х20:
#include <syst.h>
int R[400];
int& Rm(int i, int j){ return R[20*i+j];
};
void main(){ Rm10,15)= 7777;
// записать
printf("%i \n", Rm(10,15));
// прочитать
}
Пример 2
Пусть функция char& cell(int x, int y) возвращает ссылку на тот байт видеопамяти, которому соответствует
позиция на текстовом экране с координатами x,y. Тогда оператор
cell(45,15) = 'A';
записывает в 45 позицию 15 строки символ 'A' , а оператор
cell(1,20) = cell(50,10);
копирует символ из одной позиции экрана в другую.
Прототипы
Прототип является предварительным объявлением функции. Он содержит информацию о типе функции,
типах ее аргументов и их количестве. Прототип используется компилятором для контроля правильности вызова
функции, а также для автоматического приведения аргументов к нужному типу. Пример:
int fun(int, float *, double &);
Можно использовать прототип функции с переменным числом параметров. Прототип может содержать
инициализацию аргументов.
inline-функции - исполняемый код тела функции размещается в точке объявления.
Интерфейс функции и ее реализация
Интерфейс функции - ее заголовок или прототип. Содержит информацию о количестве и типах параметров
и о типе возвращаемого значения.
Реализация функции - ее полное определение (заголовок + тело функции).
Указатели на функции
Объявление указателя на функцию имеет вид:
тип (*pf)(параметры);
Теперь pf - указатель на функцию указанного типа. Тип функции означает количество и типы ее параметров
и тип возвращаемого значения.
Имя функции F (так же как и в случае массивов) является указателем-константой. Указателю на функцию pf
можно присвоить адрес функции соответствующего типа:
pf = F;
Теперь вызов функции F можно записать так:
pf(параметры),
или так:
(*pf)(параметры),
что эквивалентно вызову
F(параметры).
Такие объявления указателей неудобны, поэтому для объявления указателей на функции обычно
используют объявление typedef :
12
typedef (*fun)(float,float);
fun F;
Особенно удобно так делать при объявлении массива указателей на функции:
fun pf[10];
2.9. Специальные виды функций
Функция main()
Для связи с операционной средой функция main() имеет 3 необязательных аргумента:
main(int N, char** p, char** q)
где
N - число параметров командной строки,
p - указатель для вектора параметров командной строки,
q - указатель для вектора строк - параметров окружения процессора.
Массив указателей на строки *q завершается значением 0 (NULL).
Пример простой программы, которая выполняет печать параметров командной строки и параметров
окружения процессора:
#include <syst.h>;
void main(int N, char** p, char** q)
{ int i;
printf("N=%d \n",N);
for (i=0;i<N;i++) printf("%s \n",p[i]);
puts("--------------------------------");
for (i=0;q[i]!=0;i++) printf("%s \n",q[i]);
}
Функции с шаблонами
При объявлении функции с шаблонами используются параметризованные типы-шаблоны. Пример
определения функции, которая возвращает значение меньшего из двух параметров:
template <class type>
type min(type A, type B)
{ return A<B? A:B; }
Примеры вызовов функции:
min(10,15)
<-- вызов int min(int,int);
min(8.5,4.3) <-- вызов min(double,double);
Шаблонные функции могут быть перегружены несколько раз.
Компилятор генерирует тело функции (создает конкретизацию функции) в тот момент, когда встречает ее
вызов. Это приводит к тому, что если программа состоит из нескольких файлов, могут возникнуть проблемы.
Шаблоны типа имеют глобальный статус, они не могут быть вложены в класс или быть локальными в
некоторой функции.
Если используется шаблон, указывающий тип возвращаемого значения, то он должен совпадать с шаблоном
одного из параметров. Например, следующая конструкция не будет работать (надежно):
template <class alfa, class beta)
alfa fun(beta x)
{ ………………… }
Примеры двух полезных функций с шаблонами.
Вычисление абсолютной величины:
template <class T>
inline T abs(T x)
{ if (x<0) return -x; else return x;
}
Последняя функция заменяет функции: abs(x), labs(x), fabs(x), fabsl(x), cabs(x), cabsl(x).
Функция парного обмена:
template <class TYPE>
inline void swp(TYPE& a, TYPE& b)
{ TYPE R;
R=a; a=b; b=R;
}
2.10. Перегруженные функции и простой полиморфизм
Перегруженные функции в С++ являются реализацией простого полиморфизма.
Пример построения удобной перегруженной функции для инициализации графического режима.
#include <graphics.h>
void initgraph()
{ int dm, dr = DETECT;
13
initgraph(&dr,&dm,"");
}
void main()
{ initgraph();
.........................
}
Пример построения функции abs, вычисляющей модуль (абс. величину) заданного значения.
template <class T>
inline T abs(T x)
{ if (x<0) return -x; else return x;
}
Теперь функция abs будет работать для следующих вариантов:
labs(long), fabs(double), fabsl(long double).
Для случаев abs(int), abs(complex) будут по-прежнему работать основные версии функции abs.
2.11. Динамические объекты
Динамическими наз. объекты, которые создаются и уничтожаются в динамически распределяемой области
памяти (куче) с помощью операций new и delete. Для работы с динамическими объектами могут использоваться
указатели и ссылки:
double p = new double;
double& A = (new double);
......................
......................
delete p;
delete &A;
Операция delete не устанавливает в ноль указатель p. Операция delete с указателем, имеющим нулевое
значение, не выполняется.
Доступ к динамическому объекту возможен как через указатель, так и через ссылку:
double *p;
p = new double;
 создан объект
double &a= *p;
 теперь созданный объект имеет имя a
*p = *p+3;
 доступ через указатель p
a = a+3;
 доступ через ссылку a
Если для создания объекта операцией new нет достаточной памяти, эта операция возвращает 0 (NULL).
Пример создания динамического объекта с контролем:
p = new тип;
if (p == NULL) { puts("Нет памяти!"); exit(0); }
или так:
p = new double[32568];
errhalt(p==0 , “Нет памяти!”);
Замечание: в случае 16-разрядного приложения, запускаемого из командной строки в среде
Windows 95, такой прием может не работать.
Операция new отводит память блоками по 16 байт, даже если объект фактически имеет размер 1 байт.
Операция new может содержать значение для инициализации создаваемого объекта, например:
double *p = new double(3.3333);
Динамические массивы
Для удаления динамического массива следует использовать операцию delete[]. Последнее относится даже к
символьным массивам:
char *p= new char[100];
……………………………..
delete[] p;
Динамическую матрицу с фиксированными размерами можно создать следующим образом:
const N = 20, M = 30;
int (*p)[M] = new int[N][M];
Здесь int (*p)[M] объявляет указатель p на объект типа int[M] (строка матрицы), а new int[N][M]
создает массив таких объектов с числом элементов N.
Удаляется матрица обычным способом:
delete[] p;
Матрица с переменным числом строк (и с фикс. длиной строки) создается и удаляется точно также.
Ниже приведены операторы создания и удаления матрицы с фиксированным числом строк, но с переменной
длиной каждой строки.
const int N=280;
int* p[N];
int i;
for (i=0;i<N;i++) p[i]= new int[m];............................
for (i=0;i<N;i++) delete p[i];
14
Ниже приведены операторы, которые создают и удаляют прямоугольную матрицу с переменным
числом строк и с переменой длиной каждой строки.
int **R;
R = new int*[N];
for (i=0; i<N; i++)
R[i] = new int[N];
...........................................
for (i=0; i<N; i++)
delete[] R[i];
delete[] R;
Пример создания и удаления трехмерного динамического массива:
const N=20, M=30, P=40;
int (*p)[M][P]= new int[N][M][P];
................................
delete[] p;
13. Операции ввода/вывода для файлов
Функции fscanf и fprintf из <stdin.h> выполняют ввод из файла и вывод в файл с форматным
преобразованием:
int fscanf(f, format, p1, p2, ...);
fprintf(f, format, e1, e2, ...);
Здесь f - переменная типа FILE* (поток). Присвоить значение этой переменной можно с помощью функции
fopen которая создает поток, т.е. открывает некоторый файл и возвращает указатель на созданный поток:
FILE* fopen(char* filename , char* mode);
где параметр mode имеет значения:
"r"
- открыть файл для чтения;
"w" - создать файл и открыть для записи;
"a"
- открыть файл для продолжения записи.
Для обработки файла в текстовом режиме к указанным символам добавляется символ "t": "rt", "wt", "at".
Для задания режима обработки на байтовом уровне добавляется символ "b": "rb", "wb", "ab".
Для работы в режиме Update (прямого доступа) добавляется символ "+" (файл открывается для чтения и
записи):
"r+" - открыть существующий файл;
"w+" - создать новый или открыть существующий;
"a+" - создать новый или открыть существующий с возможностью пополнения информации.
Если filename = "CON", то ввод будет происходить с клавиатуры, а вывод на экран.
При достижении конца файла функция fscanf возвращает символ EOF.
Функция int fclose(FILE* f) закрывает поток f.
Некоторые полезные функции
Функция feof(f) возвращает не ноль, если прочитан символ EOF.
Функция-процедура int rewind(FILE*) устанавливает номер текущей позиции файла в ноль (в начало).
Замечание. Для бинарного ввода/вывода удобно определить функции такого вида:
void fprint(FILE* f , &x)
{ fwrite(&x , sizeof(x) , 1 , f);
}
14. Ввод/вывод и потоки
Поток - абстрактное понятие, относящееся к переносу данных от источника к приемнику. В роли потока
обычно выступает файл или устройство ввода/вывода.
Операция ввода в поток называется вставкой, операция вывода из потока - извлечением.
Для работы с потоками в файлах <iostream.h> и <fstream.h> определена иерархическая система потоковых
классов.
Стандартные потоки являются объектами класса iostream:
cin
- стандартный ввод ( соотв. stdin);
cout
- стандартный вывод ( соотв. stdout);
cerr
- стандартный вывод ошибок, очистка буфера происходит при каждой новой вставке;
clog - полностью буферизованная версия cerr.
Вывод (запись) в поток называется вставкой, ввод (чтение) из потока – извлечением из потока.
Перегруженная операция "<<" (вставка) является альтернативой семейству функций вывода printf:
cout << "Hello! \n";
Левый операнд - объект класса ostream. Правый операнд - выражение любого типа из допустимых.
Операция "<<" возвращает ссылку на левый операнд и обладает ассоциативностью слева, поэтому можно
записывать цепочки операций вставки:
cout << "i=" << i << ", d=" << d << "\n" ;
15
Допустимые типы для правого операнда:
- char , char*
- int , long
- float , double , long double
- void*
При передаче значения в поток выполняются стандартные форматные преобразования. Режим форматных
преобразований управляется с помощью флагов состояния формата - статических объектов, определенных в
классе ios .
Примеры некоторых флагов форматирования:
ios::hex
- вывод в 16-ричном формате;
ios::uppercase - при выводе 16-ричных чисел буквы A-F выводить
в верхнем регистре;
ios::showbase
- пред 16-ричным числом вставляются символы
“0x”;
ios::scientific
- вывод веществ. чисел в научной нотации.
Функция setf устанавливает, а функция unsetf снимает флаг форматирования. Пример:
cout.setf(ios::hex | ios::uppercase);
Манипуляторы - специальные функции, влияющие на состояние потока.
Пример:
cout << setw(4) << i << setw(6) << j << endl;
что эквивалентно
cout.width(4);
cout << i;
cout.width(6);
cout << j;
cout << "\n";
где width(n) - функция, устанавливающая минимальную ширину поля вывода.
Определения манипуляторов находятся в <iomanip.h>, некоторые из них приведены ниже:
dec - выполнять десятичные преобразования;
hex - выполнять 16-рич. преобразования;
endl - вставка символа новой строки и очистка потока;
ends - вставка конечного нулевого символа в строку;
setprecision(n) - установка точности для чисел с пл. запятой;
setw(n) - установка ширины поля ввода/вывода.
Ввод осуществляется перегруженной операцией ">>" (извлечение), которая является альтернативой
семейству функций scanf:
cin >> x;
Левый операнд - объект класса istream. Правый операнд - переменная любого допустимого типа (как и для
операции "<<").
Операция возвращает ссылку на левый операнд.
Так же, как и для операции вставки, преобразования формата зависят от типа x и установки флагов
состояния формата. Операция извлечения также обладает ассоциативностью слева:
cin >> i >> d;
Здесь из cin сначала извлекается i, затем d.
Ввод/вывод без форматных преобразований
Используя компонентную функцию write класса iostream можно осуществить вставку значения объекта x
без форматного преобразования:
cout.write((char*)&x , sizeof(x));
Как видно из предыдущей строки, функция write просто копирует последовательность байтов указанной
длины.
Извлечение без форматного преобразования делается компонентной функцией read :
cin.read((char*)&x , sizeof(x));
Определение операций вставки и извлечения для других типов
Операции "<<" и ">>" легко переопределяются (или доопределяются). Пусть имеем тип PERSON:
struct PERSON { char* name;
int
age;
float weight;
};
Обобщение операции вставки может быть записано следующим образом:
ostream& operator<<(ostream& S, PERSON& A)
{ S << A.name << " " << A.age << " " << A.weight << endl;
}
16
15. Потоковые операции с файлами
Заголовочный файл <fstream.h> содержит определения классов-потоков:
ifstream - для ввода из файла,
ofstream - для вывода в файл.
Пример программы, которая копирует побайтно файл "A.dat" в файл "B.dat":
#include <syst.h>
#include <fstream.h>
void main()
{ char x;
ifstream fa("A.dat");
errhalt(fa,”Файл не открыт”);
ofstream fb("B.dat);
while (fa.get(x)) fb.put(x);
}
Оператор ifstream fa("A.dat"); создает поток с указателем fa и открывает соответствующий ему файл A.dat
для чтения.
Оператор ofstream fb("B.dat"); создает поток с указателем fb и открывает соответствующий ему файл B.dat
для записи.
Если функция get(x) не завершается успешно, она возвращает 0. Если файл открыть не удается, указатель
потока устанавливается в 0. Это дает возможность предусмотреть контроль успешности открытия файла
(оператор errhalt( … ); ).
Компонентная функция open позволяет связать ранее объявленный указатель на поток с некоторым
файлом:
ifstream fA;
...............
fA.open("A.dat");
16. Графический вывод. Модуль rgraph.h
Ниже приводится сведения о средствах графического вывода языка С для 16-разряд-ных приложений.
Приводимые функции определены в модуле <graphics.h>.
Функции управления
initgraph(int* drn, int* drm, char* drpath) - осуществляет инициализацию графической системы. Параметры :
drn - номер графического драйвера,
drm - номер графического режима,
drpath
- путь для каталога BGI или каталога, в котором находится используемый драйвер.
Если значение drn установить равным DETECT или 0 , тогда значения параметров drn, drm будут
определены автоматически. Запись в качестве параметра drpath пустой строки “” означает, что графический
драйвер находится в текущем каталоге.
closegraph() - перейти в текстовый режим;
cleardevice() - очистка графического экрана;
setviewport() - установить графическое окно;
setcolor()
- установить цвет вывода рисунка;
setbkcolor() - установить цвет фона.
Функции графического вывода
moveto(x,y) - установить курсор в заданную точку;
line(x1,y1,x2,y2)
- вывести отрезок прямой;
lineto(x,y)
- провести прямую от текущего положения курсора;
rectangle(x1,y1,x2,y2) - вывести прямоугольник;
circle(x,y,r) - вывести окружность.
Функции получения информации
getmaxx() - максимальное значение координаты x;
getmaxy() - максимальное значение координаты y.
Для инициализации графического режима удобно использовать следующую перегру-женную версию
функции initgraph:
void initgraph()
{ int dm , dr = DETECT;
initgraph(&dr, &dm, “”);
}
Модуль rgraph.h
Модуль rgraph.h (В.П.Пинчук) был разработан с целью расширения возможностей графического вывода
средствами языка С++.
17
В модуле подключаются библиотечные h-файлы: string.h, graphics.h. Кроме того, под-ключается модуль
syst.h, который, в свою очередь, содержит подключение еще ряда библиотечных файлов.
Ниже приведены два примера использования модуля rgraph.h
/* Построение графика функции, представленной функцией */
#include "rgraph.h"
float f(float x)
{ return sin(10*x)*exp(-x/2);
}
void main()
{ ropen();
wind.setup(0.8,0.75);
fram.setup(white, lightred, solidln);
fram.draw("0 2 4 6 8 10", "-1.0 -0.5 0.0 0.5 1.0");
curv.setup(600,white,solidln,normal);
curv.fx(wind.Xmin,wind.Xmax,f);
pause;
rclose();
}
/* Построение графика функции, заданной таблично, с интерполяцией сплайнами */#include "rgraph.h" void
main(){ float x[10]= { 1, 2, 3, 4, 5, 6, 7, 8, 9,11 };
float y[10]= { 2, 4, 7,11,11, 8, 7, 4, 2, 0 };
ropen();
wind.setup(0.9,0.8);
fram.setup(white, lightred, solidln);
fram.draw( "0 2 4 6 8 10 12" , "-2 0 2 4 6 8 10 12" );
point.setup(yellow,red,rounds,0.012);
curv.setup(640,white,solidln,normal); curv.spline(10,x,y);
pause;
rclose();
}
17. Операции преобразования типов
При использовании простых типов операция преобразования типа может записываться одним из двух
эквивалентных способов:
(тип)x или тип(x) ,
где x - выражение.
C++ допускает явное преобразование значения к любому типу. Ответственность за правильное
использование этой операции возложена на программиста.
Приведем пример, когда явное преобразование
типа выгодно:
int a=2;
float x=3.5;
a = a+int(x);
Последняя операция будет выполняться быстрее, чем
a = a+x;
Операцию приведения типа приходится записывать, если требуется получить резуль-тат вещественного
типа от деления целых значений:
x = 3/(float)4 ;
18. Оператор typedef
Объявление typedef создает новое имя для некоторого типа ( предопределенного, производного или
определенного пользователем):
typedef unsigned char byte
Теперь объявление
byte a,b;
эквивалентно такому
unsigned char a,b;
Объявление typedef обычно используется для упрощения сложных объявлений. Например, объявление
typedef unsigned long (*ulint)(int);
вводит в действие новое имя типа ulint - указатель на функцию, принимающей аргумент типа int и
возвращающей значение типа unsigned long.
Теперь объявить, например, массив указателей на функцию такого типа очень просто:
18
ulint pf[25];
20. Директивы и макросы
Директива всегда начинается с символа # , обрабатывется она во время первой фазы компиляции (отсюда
название «директива препроцессора»). Ниже приведены описания некоторых часто используемых директив.
Создание макросов с помощью директивы #define
Макрос определяет некоторое правило преобразования текста программы. Различают простые макросы –
символические константы и макросы-функции. Определения простого макроса имеет следующий формат:
#define идентификатор строка
Каждое вхождение идентификатора в тексте программы заменяется на его значение – строку символов.
Например, следующая директива определяет макрос pi - новое имя для константы M_PI, определенной в
math.h:
#define pi M_PI
Определения макроса-функции с одним параметром имеет такой формат:
#define fun(x) выражение_содержащее_(x)
Каждое вхождение fun(arg) заменяется на выражение,_содержащее_(arg);
Следующая директива отменяет действие макроса хххх:
#undef хххх
Директива #include осуществляет вставку текста из заданного файла в текст программы:
#include <имя_файла>
#include "имя_файла"
Угловые скобки означают, что файл берется из системного каталога \INCLUDE, кавычки - файл ищется
вначале в текущем каталоге, затем в стандартном.
Ряд макросов – символических констант определяет компилятор автоматически, они делятся на две группы:
макросы ANSI и макросы Borland C++. К макросам ANSI, в частности, относятся такие:
__DATE__
- строка, представляющая дату компиляции в формате mmm dd yyyy;
__TIME__
- строка, представляющая время компиляции в формате hh:mm:ss;
__FILE__
- строка, представляющая имя текущего файла в двойных кавычках.
Предотвращение повторного включения h-файла
Для того, чтобы избежать повторное включение в текст программы некоторого h-файла на этапе
препроцессорной обработки можно использовать директиву условной компиляции #ifndef:
#ifndef SYST_H
#define SYST_H
… текст h-файла …
#endif
Контроль параметров компиляции
Borland C++ определяет ряд макросов, позволяющих контролировать параметры компиляции, например:
__LARGE__
- определен, если установлена модель памяти LARGE;
__FLAT__
- определен, если установлен режим построения 32-битного приложения с
моделью памяти FLAT.
Вывод сообщения на этапе компиляции
Директива вида
#error сообщение
вызывает вывод сообщения на этапе компиляции прекращение компиляции.
Download