(хотя в этом случае компилятор и выдаст предупреждение) или
int a = 256+255;
char c = a;
int b = c; .
Переменная с будет содержать двоичный код 11111111 - "все едини-цы", который при присваивании его переменной b никак не может превра-титься в 511.
Все, что предполагается относительно типов, это:
1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long)
sizeof(float) <= sizeof(double)
Из основных типов могут выводится производные типы посредством операций описания:
*- указатель,[]- вектор,
&- ссылка,()- функция.
и механизма определения структур.
Например:
int* a;
float v[10];
char* p[20]; // вектор из 20 указателей на символ
void f(int); /* функция не возвращающая значения, аргументом
которой является целое */
struct string { short length; char* p; };
enum week_day { Sunday, Monday, Tuesday, Wednesday, Thursday,
Friday, Saturday };
struct compounent { week_day day; int i; };
compounent array[10]; .
Здесь в качестве иллюстраций введения новых типов приведены описания перечислимого типа week_day, который использован при определении типа compounent - структуры, содержащей переменную day, типа week_day и целую переменную i. В свою очередь этот тип используется при описании массива array. B языке C последние два описания должны были бы выглядеть так:
struct compounent { enum week_day day; int i; };
struct compounent array[10]; .
Этот пример иллюстрирует тот факт, что в С++ имена шаблонов трактуются как типы.
Перечислимые типы были введены в С еще в начале 80-х годов. В С++ перечислимые типы трактуются как целые переменные, как это принято в ANSI C. Когда имена такого типа перечисляются без начальных значений, список этих переменных неявно инициализируется последовательными целыми, начиная с нуля. Следующие два примера приводят эквивалентные описания:
а)
enum week_day { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };
enum week_day { Sunday=0, Monday=1, Tuesday=2, Wednesday=3, Thursday=4,Friday=5, Saturday=6 };
б)
enum week_day { Sunday=7, Monday,=1 Tuesday, Wednesday,
Thursday, Friday, Saturday };
enum week_day { Sunday=7, Monday=1, Tuesday=2, Wednesday=3, Thursday=4,Friday=5, Saturday=6 };
Идентификаторы именуют целые константы, которые не могут быть изменены в процессе выполнения программы.
Пример
Ниже приводится функция, инициализирующая колоду из 52 карт.
enum suit { clubs, diamonds, hearts, spades };
struct card { suit s; int pips; };
card deck[52];
void init_deck(card d[])
{
for (int i=0; i<52; ++i) {
d[i].s = (i/13); // перечислимое, как целое
d[i].pips = 1 + i%13;
}
}
Существует еще один тип - void (пустой), который определяет пустое множество значений. Значение объекта такого типа нельзя использовать никаким образом, поскольку пустое выражение обозначает несуществующее значение.
Ключевое слово void используется для описания типа функций, не возвращающих значения, и/или пустого списка аргументов функций. Другое его назначение связано с использованием обобщенного указателя void*. Указателю такого типа может быть присвоена величина указателя любого типа. Но такой указатель не может быть разименован. (Операция разименовывания *, воздействуя на указатель объекта, делает возможным доступ к самому объекту.)
Пример
#include <stdio.h>
void memcpy(void* s1, const void* s2, unsigned n)
{
while (n--) *((char*)s1+n)=*((char*)s2+n);
}
void main(void)
{
char *s1="Первая строка";
char *s2="Вторая строка";
memcpy(s1,s2,4);
printf("%s\n",s1);
int i1=10;
int i2=20;
memcpy(&i1,&i2,sizeof(int));
printf("i1=%d\n",i1);
}
Результатом выполнения программы является
Вторая строка
i1=20
Использование типа void* для первого и второго аргументов функции memcpy - копирования из памяти в память позволило обращаться к ней как с аргументами, являющимися строками, так и с аргументами - целыми переменными.
Встраевыемые функции (Inline-подстановки)
При программировании на языке С зачастую с целью повышения эффективности вместо описания небольших, часто использующихся функций, используются макросы. Поскольку семантика использования макросов и семантика применения функций принципиально отличаются, то иногда это приводит к появлению ошибок. Например, макроопреде-ление
#define SQ(x) x*x
при следующем обращении к этому макросу
SQ(a+b)
породит код
a+b*a+b
Указанный макрос мог бы быть более правильно записан в виде
#define SQ(x) (x)*(x)
Однако второй пример показывает, что и этот рецепт тоже не универсален. Функция skipblanks, предназначавшаяся для удаления пробелов и символов табуляции, будет выполняться совсем не так, как ожидалось
#include <stdio.h>
#define toblank(c) ((c) != '\t' ? (c) : ' ')
int skipblanks()
{
int c;
while(toblank(c=getchar()) == ' ');
return c;
}
void main(void)
{
int c;
while( (c=skipblanks()) != EOF ) putchar(c);
}
И в результате обработки строки
a b c d e f g h
букв от a до h, разделенных двумя пробелами, получится строка
bdfh.
Совместить эффективность макросов с корректностью семантики функций позволило введение в язык С++ функций, представляющих собой открытые подстановки. Описанный в последнем примере макрос мог бы быть записан в таком виде, как
inline int toblank(int c) { return c != '\t' ? c : ' '; }.
Функции, представляющие собой открытые подстановки, имеют ту же семантику и область действия, что и "обычные" функции.
Прототипы функций
В языке С функция могла быть описана до ее определения в виде
Type name Function name () ;
Это описание анонсировало, что где-то описана функция с именем Function name, возвращающая в качестве результата значение указанного типа Type name. Никакой информации о типе и количестве аргументов функции не предполагалось. Наиболее часто встречающейся ошибкой в С было обращение к функции, например, имеющей вещественный аргумент, с целой переменной. Получаемый результат вычислений мог озадачить, но никаких сообщений об ошибках не поступало, т.к. контроль типов при вызове функций в С не предусмотрен.
В С++ сделан шаг в сторону жестко типизированных языков. Описание функции поэтому имеет вид
Type_name Function_name ( Arguments declaration list ) ;
т.е. дополнительно сообщаются количество и типы аргументов.
Если типы фактического и формального аргументов при обращении к функции не совпадают и не определено преобразование одного типа в другой, то транслятор языка С++ выводит сообщение об ошибке.
Примеры
double sqrt(double);
void fun(char*, int);
void qsrt(void);
int printf(char* format,...); .
Описание функции printf, имеющее существенное отличие от предыдущих описаний, сообщает транслятору, что число и тип остальных аргументов функции не известен. Такой подход к функциям с переменным числом аргументов не дает возможности получить универсальное реше-ние, однако при этом удается обнаружить довольно большое число часто встречающихся ошибок.
Пример
Ниже приводится программа, демонстрирующая способ работы с функциями, число аргументов у которых заранее не известно.
#include <stdio.h>
#include <stdarg.h>
int average( int first, ... );
void main()
{// Вызов с 3 целыми аргументами
//(-1 - используется как ограничитель).
printf( "Average is: %d\n", average( 2, 3, 4, -1 ) );
// Вызов с 4 целыми аргументами.
printf( "Average is: %d\n", average( 5, 7, 9, 11, -1 ) );
// Вызов только с -1 - ограничителем.
printf( "Average is: %d\n", average( -1 ) );
}
/* Вычисление среднего значения от набора целых. */
int average( int first, ... )
{
int count = 0, sum = 0, i = first;
va_list marker;
va_start( marker, first ); //Инициализация переменных аргументов
while( i != -1 )
{
sum += i;
count++;
i = va_arg( marker, int );
}
va_end( marker );// Восстановление списка
return( sum ? (sum / count) : 0 );
}
Результат выполнения этой программы следующий:
Average is: 3
Average is: 8
Average is: 0
При обращении к функции
s=sqrt(4);
целая величина 4 будет преобразована к типу double до обращения к функции, что позволит получить правильный результат при вычислении квадратного корня.
Обращение к функции с различным числом аргументов может быть введено путем задания некоторых значений аргументов функции по умолчанию, т.е. при обращении к функции с постоянным и заранее известным числом аргументов могут быть указаны не все необходимые аргументы. Аргументы, значения которых могут использоваться по умолчанию, должны быть последними в списке аргументов функции.
Примером программы такого рода могла бы быть первая функция ERR в программе из следующего ниже примера. В данном случае второй аргумент функции ERR может не указываться. При этом его значением будет пустая строка.
Перегружаемые функции
Проводящийся в С++ жесткий контроль типов при обращении к функции позволяет использовать одно и то же имя для функций, имеющих одинаковое число аргументов, различающихся типами. Транслятор анализирует тип аргументов и обеспечивает обращение к соответствующей из перегружаемых функций.
Пример
Приведенная ниже программа позволяет выводить на экран терминала строку файла с указанным номером. Эта программа, конечно, имеет небольшую практическую ценность, однако достаточно наглядно демонстрирует возможность использования перегружаемых функций.
#include <stdio.h>
#include <stdlib.h>
inline void ERR(char* s1, char* s2="") { printf("%s %s\n",s1,s2); exit(1); }
inline void ERR(char* s1, int s2) { printf("%s %d\n",s1,s2); exit(1); }
void main(int argn, char** argv)
{
FILE *f;
int c,n,i;
if (argn<2) ERR("Укажите имя файла");
f=fopen(*(argv+1),"r");
if( f==NULL ) ERR(*(argv+1),"- файл не найден");
printf("Показать строку номер - ");
scanf("%d",&n); i=n;
if ( n<1 ) ERR("Номер строки должен быть больше нуля");
while( n && (c=fgetc(f)) != EOF ) if ( c=='\n' ) n--;
if ( n ) ERR("В файле нет строки номер ",i);
else while( (c=fgetc(f)) != EOF && c!='\n') putchar(c);
putchar('\n');
}
Отметим попутно также, что в ранних версиях С++ необходимо было описать имя ERR в предложении overload. В последующих версиях этого не требуется, т.к. все имена считаются перегружаемыми. Возможность перегрузки функций тесно связана с поддержкой абстракции данных; подробнее на этом остановимся ниже.
Возможность введения перегружаемых функций неизбежно ставит вопрос о там, как определить в том или ином случае, к какой именно из перегружаемых функций произойдет обращение. Для функций с одним параметром этот вопрос решается путем сопоставления типа фактического аргумента, указанного при обращении к функции, и типов формальных аргументов перегружаемых функций. Определены пять уровней соответствия пар аргументов:
1. Не требуется никакого преобразования фактического аргумента или это преобразование является эквивалентным (к такого сорта преобразованиям относятся преобразования имени массива в указатель, имени функции в указатель на функцию, аргумента типа Т в тип const Т).
2. Преобразование является обобщающим, не приводящим к потере информации (например, преобразование из float в double, char в int, short в int и т.п.).
3. Требуется стандартное преобразование типа фактического аргумен-та, которое, вообще говоря, может привести и к потере информации (таким преобразованием являются преобразования из int в float и double, из float и double в int, из unsigned в int и т.п.).
4. Существует определенное пользователем преобразование типа фактического аргумента (с использованием конструкторов и операции преобразования типа см. стр. 41)
5. Фактическому аргументу соответствует параметр многоточие.
Транслятор строит обращение к той из функций, соответствие типов формального и фактического аргументов у которой наилучшее, т.е. имеет наимень¬ший номер в приведенном списке. В случае, когда проблема выбора не может быть решена на основании приведенных правил (например, когда фактический аргумент может быть с одинаковым успехом приведен к типам формальных аргументов хотя бы двух функций), компилятор выдаст сообщение об ошибке.
Пример
float abs( float );
double abs( double );
void main( void )
{
f( 5 );// ошибка; преобразования из int в float и
// из int в double имеют одинаковый приоритет
}
Пара с преобразованием к типу void* будет выбираться в случае, когда не подходит никакая другая пара. Если формальные аргументы двух перегружаемых функций ссылочного типа различаются по const, то его наличие является существенным и учитывается при выборе подходящей пары.
Если перегружаемые функции имеют несколько аргументов, то выбирается та из них, у которой по меньшей мере для одной из пар аргументов соответствие наилучшее, чем у любой другой функции.
Ссылки
В С++ имеется возможность ссылаться на объекты. Описание вида
Type name &Identifier = Object ;
указывает на то, что имя Identifier есть другое имя объекта Object. Например, описания
int n;
int& nn=n;
double a[10];
double& last=a[9];
вводят имена nn и n для одного и того же целого объекта, и имя last для последнего элемента вещественного массива a.
Основное использование ссылок - список формальных параметров функций. Это использование позволяет передавать аргументы по наименованию, а не только по значению, как это было принято в С.
Функция, предназначенная для упорядочения двух целых величин по возрастанию, записанная на С и использующая указатели и операцию разыменования:
#include <stdio.h>
void sequence(a,b)
int *a,*b;
{
int rab;
if( *a < *b ) { rab=*a; *a=*b; *b=rab; }
}
void main(void)
{
int a,b;
printf("Ведите два целых числа - ");
scanf("%d%d",&a,&b);
sequence(&a,&b);
}
могла бы на С++ выглядеть так:
#include <stdio.h>
void sequence(int& a,int& b)
{
if( a < b ) { int rab=a; a=b; b=rab; }
}
void main(void)
{
int a,b;
printf("Ведите два целых числа - ");
scanf("%d%d",&a,&b);
sequence(a,b);
printf(" Max - %d, Min - %d\n",a,b);
}
Использование динамической памяти
В С++ определены два унарных оператора, предназначенных для работы со свободной памятью. Они более удобны в использовании и заменяют определенные в С библиотечные функции malloc, calloc и free. Оператор new может быть записан в виде