С++ обзор языка



страница6/10
Дата22.06.2019
Размер1.54 Mb.
1   2   3   4   5   6   7   8   9   10

8.3.6. Указатель на функцию
С функцией в С++ можно делать только две вещи: вызывать ее и брать ее адрес. Указатель, полученный взятием адреса функции, можно затем использовать для ее вызова. Пример:
void error(char* p) { /* . . . */};

void (*efct)(char*); // указатель на функцию

void f()

{

efct = &error; // efct указывает на error

(*efct)("error"); // вызов error через efct

}
Чтобы вызвать функцию через указатель, его надо сначала разыменовать. Поскольку операция вызова функции () имеет более высокий приоритет, чем операция разыменования "*", нельзя написать просто *efct("error"), что означает *(efct("error")). Можно, однако, написать efct("error"), и компилятор обнаружит, что efct - это указатель и правильно вызовет функцию.
Часто бывает полезен массив указателей на функции.
8.4. Инициализаторы
Описатель может задавать начальное значение для объявляемого идентификатора.
инициализатор:

= выражение-присваивания

= {список-инициализаторов , opt}

(список-выражений)


список-инициализаторов:

выражение

список-инициализаторов, выражение

{список-инициализаторов , opt}


Вариант {список-инициализаторов , opt} взят из С и хорошо служит при инициализации массивов и структур данных, например:
struct Conf {

char* month;

int year;

char* location;

} cpp[] = {{"November", 1987, "Santa Fe"},

{"October", 1988, "Denver"},

{"November", 1989, "San Francisco}}:
Вариант = выражение-присваивания также взят из С и наиболее естественен при инициализации простых переменных, например:
int i = 1;

complex z = complex(2.3, 4.0);
Вариант (список-выражений) взят из Симулы и служит для создания сложного объекта, например:
Task ring-monitor = Task("monitor", 1024, SHARED);
Указатель типа const T*, т.е. указатель на константу можно инициализировать указателем типа Т*, но обратная инициализация запрещена. Объекты типа Т всегда можно инициализировать объектами типа Т, независимо от модификаторов const и volatile как у инициализируемой переменной, так и у инициализатора, например:
int a;
const int b = a;

int c = b;

const int* p0 = &a;

const int* p1 = &b;

int* p2 = &b; //ошибка: указатель не на константу будет

// указывать на константу

int *const p3 = &a;

int *const p4 = p1; //ошибка: указатель не на константу будет

// указывать на константу

const int* p5 = p1;
В обоих случаях допущение инициализаций позволило бы изменять через указатель значение объекта, объявленного константой.
8.4.1. Агрегаты
Агрегат - это массив или объект некоторого класса. При инициализировании агрегата инициализатор может быть списком-инициализаторов, состоящим из заключенных в фигурные скобки и разделенных запятыми инициализаторов для компонентов агрегата, записанных по возрастанию индекса или в порядке объявления компонентов. Если в списке меньше инициализаторов, чем в агрегате - компонентов, оставшиеся компоненты инициализируются нулями соответствующих типов. Например,
struct S { int a; char b; int c};

S ss = {1, "asdf"};
инициализирует с нулем.
Вложенные фигурные скобки можно опускать:
float y[4][3] = {

{1, 3, 5},

{2, 4, 6},

{3, 5, 7},

};
эквивалентно
float y[4][3] = {1, 3, 5, 2, 4, 6, 3, 5, 7};
В обих случаях y[3] инициализируется нулями.
Инициализаторов должно быть не больше, чем элементов, требующих инициализации.
8.4.2. Массивы литер
Массив литер может инициализироваться литеральной-строкой:
char msg[] = "Syntax error on line %s\n";
В данном примере: поскольку "\n" - это одна литера и добавляется завершающий 0, длина строки - sizeof(msg) - равна 25.
Инициализация литерных массивов - это единственное применение строк в С++ (присваивание строк массиву невозможно).
8.4.3. Ссылки
Переменная, обявленная как T&, т.е. типа "ссылка типа Т", должна быть инициализирована объектом типа Т или приводимым к нему. Пример:
void f()

{

int i;

int& r = i; //r ссылается на i

r = 1; // значение i становится равным 1

int* p = &r // p указывает на i

int& rr = r // rr ссылается на то же, что и r, т.е. на i
После своей инициализации ссылка не может быть изменена на ссылку на другой объект.
Инициализатором для "обычного" Т должно быть l-значение (т.е. объект, адрес которого можно взять). Однако инициализатор для const T& (ссылка на константу) не обязательно должен быть l-значением, и даже может быть не типа Т. В таком случае:

1) если необходимо, применяется преобразование типа;

2) полученное значение помещается во временную переменную;

3) ее адрес используется в качестве инициализатора.


Пример. Объявление
double& dr = 1; // ошибка: необходимо l-значение

const double& cdr = 1 // OK
интерпретируется так:
double* cdr // ссылка, представленная как указатель

double temp;

temp = double(1);

cdr = &temp;
Инициализатор может быть опущен для ссылки только в случае объявления параметра функции (8.2.5) или типа ее результата, в объявлении компонента класса внутри объявления самого класса (9.2), а также при наличии спецификатора extern.

9. Классы
Тип данных - это конкретное представление некоторой идеи или пониятия. Например, тип float с его операциями обеспечивает ограниченную, но конкретную версию математического понятия действительного (вещественного) числа. При определении нового типа основная идея - отделить несущественные подробности реализации (например, форматы данных, используемые для представления объектов данного типа) от тех качеств, которые существенны для его правильного использования (например, список операций, применимых к объектам данного типа).
Класс - это определенный пользователем тип. Его имя становится именем-класса (9.1), т.е. зарезервированным словом внутри его области действия.
имя-класса:

идентификатор


Для объявления имен-классов служат спецификаторы-класса и уточненные-спецификаторы-типа (7.1.6). Объект некоторого класса состоит из (возможно, пустой) последовательности компонентов.
спецификатор-класса:

заголовок-класса {список-компонентов opt}


заголовок-класса:

ключевое-слово-класса идентификатор opt спецификация-базы opt



ключевое-слово-класса имя-класса спецификация-базы opt
ключевое-слово-класса:

class

struct

union
Имя класса можно употреблять как имя-класса уже внутри списка-компонентов этого самого класса. Пример:
class link { link* next};
Cпецификатор-класса обычно называется объявлением класса. Класс полагается описанным, когда известен его спецификатор-класса, даже если еще не описаны его компонентные функции.
Объекты класса можно присваивать, передавать в качестве параметров функции и возвращать как ее результат. Программистом могут быть определены и другие естественные операции вроде проверки на равенство (13.4).
Структура (structure) - это класс, объявленный с ключевым-словом-класса struct; ее компоненты и базовые классы (10) являются открытыми (public) по определению (11). Объединение (union) - класс, объявленный с ключевым-словом-класса union, его компоненты и базовые классы также являются открытыми по определению; объединение всежда содержит лишь один компонент класса (9.5).
9.1. Имена классов
Объявление класса вводит новый тип. Например,
struct X {int a;};

struct Y {int a;};

X a1;

Y a2;

int a3;
объявляет три переменные трех разных типов. Из этого следует, что
а1 = а2; // ошибка: Y присваивается X

а1 = а3; // ошибка: int присваивается X
дает несоответствие типов, а
int f(X);

int f(Y);
объявляет две функции с совмещенным именем. Таким образом, С++ использует принцип эквивалентности имен для контроля типов.
Если имя класса объявляется в области действия, где уже объявлен объект, функция или перечислитель с тем же именем, этот класс можно именовать лишь уточненным-спецификатором-типа (7.1.6). Пример:
struct stat {

// . . .

}
stat gstat; // описыывается переменная типа stat
int stat(struct stat*) // переопределяем stat как функцию
void f()

{

struct stat* ps; // префикс struct для именования структуры stat

// . . .

stat(ps); // вызов stat()

// . . .

}
Уточненный-спецификатор-типа с ключевым-словом-класса, за которым нет объявляемого объекта или функции, явно вводит имя класса без его описания:
struct s { int a; };
void g()

{

struct s; // скрывает глобальную структуру "s"

s* p; // ссылается на локальную структуру "s"

struct s {char* p;}; // описание локальной структуры "s"

}
Уточненный-спецификатор-типа (7.1.6) может также употребляться в объявлениях объектов и функций. Он отличается от объявления класса в том, что если в области действия виден класс с этим уточненным именем, оно будет относиться именно к этому классу. Пример:
struct s { int a; };
void g()

{

struct s* p = new s; // s здесь именует глобальную структуру "s"

p->a = 1;

}
9.2. Компоненты класса
список-компонентов:

объявление-компонента список-компонентов opt

спецификатор доступа: список-компонентов opt
объявление-компонента:

decl-спецификаторы opt список-описателей-компонентов opt ;

описание-функции ; opt

уточненное-имя ;


список-описателей-компонентов:

описатель-компонента

список-описателей-компонентов, описатель-компонента
описатель-компонента:

описатель чистый-спецификатор opt

идентификатор opt: константное-выражение
чистый-спецификатор:

= 0
В списке-компонентов можно объявлять данные, функции, классы, перечисления (7.2), битовые поля (9.6), дружественные функции и классы (11.4) и имена типов (7.1.3, 9.1). Список-компонентов может также содержать объявления, корректирующие доступ к компонентам класса (11.3). Компонент класса не может объявляться в списке-компонентов дважды.


Описатель-компонента не может содержать инициализатора (8.4): компонент можно инициализировать посредством конструктора (12.1).
Компонент класса не может быть автоматическим, внешним или регистровым (auto, extern, register).
Decl-спецификаторы могут быть опущены только в объявлении функций. Список-описателей-компонентов может быть опущен только после спецификатора-класса, спецификатора-перечисления или decl-спецификатора в форме friend уточненный-спецификатор-типа. Чистый-спецификатор может появляться только в объявлении виртуальной функции (10.2).
Компоненты класса, являющиеся объектами классов, должны быть объектами ранее объявленных классов. В частности, класс не может содержать свой собственный объект, но он может содержать ссылку на него.
Если нестатический компонент класса является массивом, у него должны быть заданы все размерности.
Простым примером объявления класса является структура:
struct tnode {

char tword[20];

int count;

tnode *left;

tnode *right;

}
которая содержит массив из 20 чисел, целое число и два указателя на такие же структуры. Объявление
tnode s, *sp;
объявляет s структурой типа tnode, а sp - указателем на структуру типа tnode.
Нестатические компонентные данные, между которыми нет спецификатора-доступа, размещаются в памяти объекта по возрастанию адресов в порядке их объявления. Порядок размещени нестатических компонентных данных, разделенных спецификатором-доступа, зависит от реализации.
Компонентная функция класса (9.3), имя которой совпадает с именем класса, называется конструктором (12.1). Остальные компоненты не могут иметь имя, совпадающее с именем класса.
9.3. Компонентные функции класса
Функция, объявленная в классе без спецификатора friend, называется компонентной функцией класса. Ее вызов имеет соответствующий синтаксис (5.2.4). Пример:
struct tnode {

char tword[20];

int count;

tnode *left;

tnode *right;

void set(char*, tnode* l, tnode* r);

}
Здесь set является компонентной функцией класса и может быть вызвана следующим образом:
void f(tnode n1, tnode n2)

{

n1.set("abc", &n2, 0);

n2.set("def", 0, 0);

}
Таким образом, имя компонентной функции при ее вызове уточняется именем объекта ее класса. Другой пример:
struct date {

int day, month, year;

void set(int, int, int);

void get(int*, int*, int*);

void next();

void print();

};
date today; //

date my_birthday; //
void f()

{

my_birthday.set(12, 2, 1943);

today.set(28, 8, 1995);

today.print();
Описание компонентной функции класса относится к области действия этого класса. Это означает, что компонентная функция класса может непосредственно использовать имена компонентов своего класса. Статическая компонентная функция может непосредственно употреблять только имена статических компонентов, перечислителей и вложенных типов. Если компонентная функция описывается вне текста объявления класса, ее имя должно уточняться его именем, так как разные классы могут иметь функции с одним и тем же именем, например:
void tnode::set(char* w, tnode* l, tnode* r);

{

count = strlen(w)+1;

if (sizeof(tword) <= count)

error("строка tnode слишком длинна");

strcopy(tword, w);

left = l;

right = r;

}
Здесь tnode::set означает, что функция set есть компонент класса tnode и лежит в области его действия. Имена компонентов класса tword, count, left и right относятся к компонентам того объекта, для которого вызывается функция. В вызове n1.set("abc", &n2, 0) имя tword относится к n1.tword, а в вызове n2.set("def", 0, 0) - к n2.tword.
Каждая компонентная функция класса должна иметь ровно одно описание в программе.
Иногда полезно определить компонентную функцию так, чтобы она могла считывать значение объекта, к которому она применяется, но не менять его. Такая функция объявляется с суффиксом const и называется константной функцией. Константная компонентная функция класса может быть вызвана и для константных и для переменных объектов, в то время как неконстантная компонентная функция класса может быть вызвана только для переменных объектов. Пример:
struct s {

int a;

int f() const;

int g();

}
void k(s& x, const s& y)

{

x.f(); // OK

x.g(); // OK

y.f(); // OK

y.g(); // ошибка

{
Конструкторы (12.1) и деструкторы (12.4) могут быть вызваны для константных и подвижных объектов, но не могут быть объявлены таковыми.
9.3.1. Указатель this
В нестатической компонентной функции класса ключевое слово this обозначает указатель на объект, для которого вызвана эта функция. Этот указатель в компонентной функции класса Х имеет тип X *const, если только функция не объявлена как const или volatile; в этих случаях тип указателя this есть, соответственно, const X *const и volatile X *const. В функции, объявленной одновременно как const и volatile, тип указателя this есть const volatile X *const. Пример:
struct s {

int a;

int f() const;

int g() {return a++;};

int h const {return a++} // ошибка

}
int s::f() const {return a}
Выражение а++ в теле функции s::h недопустимо, поскольку оно изменяет часть "а" объекта, для которого вызывается s::h(). Это как раз делать нельзя в константной компонентной функции класса, где this (а - короткая запись this->a) есть указатель на const, т.е. *this - константа.
Как видно из предыдущего примера, при ссылке на компонент использование this излишне. Главным образом, он используется при написании компонентных функций, работающих непосредственно с указателями. При этом указатель на объект, для которого вызвана компонентная функция, является ее скрытым параметром. Типичный пример - функция, вставляющая звено в двусвязный список:
struct dlink {

dlink* pre;

dlink* suc;

void append(dlink* p);

};
void dlink::append(dlink* p)

{

p->suc = suc; // то есть, p->suc = this->suc

p->pre = this; // явное использование this

suc = p; // то есть, this->suc = p

};
dlink* list_head;
void f(dlink* a, dlink*b)

{

list_head->append(a);

list_head->append(b);

};
9.3.2. Встраиваемые компонентные функции класса
Компонентная функция класса может быть описана внутри объявления класса; в этом случае она считается встраиваемой функцией. Описание функции в объявлении класса эквивалентно явному ее объявлению как встраиваемой и ее описанию сразу после объявления класса. Так,
struct x {

char* f() {return b};

char* b;

}
равносильно
struct x {

char* f();

char* b;

};
inline char* x::f() {return b}
9.4. Статические компоненты класса
Класс - это тип, а не объект данных, и в каждом его объекте имеется своя копия его компонентных данных. Однако некоторые типы наиболее элегантно реализуются, когда все объекты данного типа могут совместно использовать некоторые данные. Предпочтительно, чтобы такие данные были объявлены как часть класса. Поэтому при объявлении класса компонентные данные и функции можно объявлять статическими (static). Существует лишь один экземпляр статических компонентных данных класса, используемый всеми объектами этого класса в программе. Статические компоненты глобального класса имеют внешнее связывание (3.3). Примером может служить список задач, используемый для управления задачами в операционной системе:
class task {

// . . .

public: static task* chain;

// . . .

};
Статическая компонентная функция класса не получает параметром указатель this и потому может обращаться к нестатическим компонентам своего класса только посредством операций "." или "->". Статическая компонентная функция класса не может быть виртуальной.
Статические компоненты локального класса (9.8) не имеют связывания и не могут быть описаны вне объявления класса.
Статический компонент mem класса с1 можно обозначать как c1::mem (5.1), т.е. независимо от объекта. Пример:
if (task::chain = 0) // что-то делаем
На статический компонент можно также ссылаться посредством оперaций доступа к компонентам класса "." и "->" (5.2.4), при этом выражение слева от операции не вычисляется (нужен только его тип). Статический компонент класса будет существовать, даже если не создан ни один объект данного класса.
Статические компоненты глобального класса подчиняются обычным правилам доступа к компонентам класса (11), не считая того, что они должны быть инициализированы в глобальной области, например:
task* task::chain = 0;
Использование статических компонентов класса может заметно снизить потребность в глобальных переменных. Объявление компонента как статического ограничивает его видимость и делает его независимым от индивидуальных объектов класса.
9.5. Объединения
Объединение является структурой, у которой в любой момент времени существует значение только одного из компонентов. По этой причине при хранении объекта-объединения достаточно выделить память под максимальный компонент. Объединение может иметь компонентные функции (в том числе конструкторы и деструкторы), но не виртуальные функции (10.2). Объединение не может иметь базовых классов и само не может быть базовым классом; оно не может также иметь статических компонентных данных. Пример:
union tok_val {

char* p; // строка

char v[8]; // массив из 8 символов

long i; // целое

double d; // вещественное двойной точности

};
Cложность в использовании объединений заключается в том, что компилятор не может знать, какой компонент используется в данный момент и проконтролировать его, например:
void strange (int i)

{

tok_val x:

if (i)

x.p = "2";

else

x.d = 2;

sqrt(x.d); // ошибка, если i != 0

};
По этой причине при использовании объединений необходимо отслеживать текущий тип значения (например, посредством дополнительного параметра).
Объединение вида
union {список-компонентов}
называется безымянным (anonymous); оно определяет неименованный объект (а не тип). Имена компонентов безымянного объединения должны отличаться от других имен из его области действия и используются непосредственно. т.е. без обычного для доступа к компонентам объединения (5.2.4). Пример:
void f()

{

union {int a; char* p};

a = 1;

// . . .

p = "Jenny";

// . . .

}
Здесь а и р употребляются как обычные переменные, но, поскольку они являются компонентами объединения, имеют один и тот же адрес.
Глобальное безымянное объединение должно быть объявлено как статическое (static), чтобы не иметь внешнего связывания и сопутствующих проблем согласования типов в разных файлах.
Объединение, при котором объявлены объекты или указатели, не считается безымянным. Пример:
union {int aa; char* p} obj, *ptr = &obj;

aa =1; // ошибка

ptr->aa = 1 // нормально
Присваивание просто компоненте "аа" неверно, поскольку имя компонента объединения не ассоциируется ни с каким конкретным объектом.


Поделитесь с Вашими друзьями:
1   2   3   4   5   6   7   8   9   10


База данных защищена авторским правом ©vossta.ru 2019
обратиться к администрации

    Главная страница