Добавил:
t.me Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

2 семестр / Литература / Язык программирования С++. Краткий курс. Страуструп

.pdf
Скачиваний:
9
Добавлен:
16.07.2023
Размер:
31.34 Mб
Скачать

2

• • • • • •

Пользовательские типы

-

Не паникуй!

 

Дуглас Адамс

Введение Структуры Классы Объединения Перечисления Советы

2.1.

Введение

Мы

называем типы,

которые могут быть построены

из фундаментальных

типов

(§1.4), модификатора

const (§1.6)

и операторов

деклараторов

(§1.7),

встроенными типами.

С++

имеет богатый

набор встроенных типов и

опера­

ций, который

сознательно

ограничен

низким

уровнем.

Они

непосредственно

и

эффективно

отражают

возможности

обычной

компьютерной

техники,

но

при

этом

не

предоставляют

программисту

средства

высокого

уровня

для

соз­

дания

сложных

приложений.

Вместо

этого

С++

дополняет

встроенные

типы

и

операции

сложным

набором

механизмов

абстракции,

на

основе

которых

про­

граммисты могут построить необходимые возможности высокого уровня.

Механизмы абстракции С++ разработаны прежде всего для того, чтобы

по­

зволить

программистам

проектировать

и

реализовывать

их

собственные

типы

с

соответствующими

представлениями

и

операциями

и

изящно

использовать

такие

типы

в

своих

приложениях.

Типы,

построенные

из

других

типов

с

по­

мощью

механизмов

абстракции

С++,

называют

пользовательскими

типами

(или

типами,

определенными

пользователями).

Таковыми

типами

являются

клас­

сы

и

перечисленwt.

Пользовательские

типы

могут

быть

построены

как

из

встро­

енных

типов,

так

и

из

других

пользовательских

типов.

Большая

часть

этой

книги

посвящена

дизайну,

реализации

и

использованию

пользовательских

ти­

пов.

Пользовательские

типы

зачастую

оказываются

предпочтительнее

встро­

енных,

потому

что

их

легче

использовать,

они

менее

подвержены

ошибкам

и

2.3.

Классы

45

Здесь представление вектора

(члены elem и sz) доступно

рез интерфейс, предоставляемый

с помощью открытых членов:

только че­

Vector (),

operator

[]

()

и

size

().Пример

read

_

and

_

sum

()из

§2.2

упрощается:

douЫe read_and_sum(int s)

 

(

 

 

 

 

Vector

v(s);

 

//Создание вектора

for(int

i=O;

i!=v.size();

++i)

cin>>v[i];

//Чтение

элементов

douЫe

sum =

О;

 

 

for(int

i=O;

i!=v.size();

++i)

sum+=v[i];

//Вычисление суммы

return

sum;

 

 

 

из

s

элементов

элементов

"Функция"-член

с

тем

же

именем,

что

и

имя

класса,

называется

кон­

структором,

т.е.

функцией,

используемой

для

конструирования

(создания)

объектов

класса.

Таким

образом,

конструктор

Vector

()

заменяет

функцию

vector

_

ini

t

()

из

§2.2. В

отличие

от

обычной

функции

конструктор

гаран­

тированно используется для инициализации

образом, определение конструктора решает

объектов

своего класса. Таким

проблему

неинициализирован­

ных

переменных

класса.

 

Vector (int)

определяет, как будут создаваться

В

частности, он

указывает, что для этого

требуется

число используется в качестве количества

элементов.

объекты типа Vector. целое число. Это целое Конструктор инициали­

зирует

члены

Vector,

используя

список

инициализаторов

членов:

:elem{new

douЬle[s]

),

sz(s)

То есть сначала выполняется инициализация elem указателем на память для s элементов типа douЫe, полученную из свободной памяти. Затем выполня­

ется инициализация sz значением s.

Обращение к элементам выполняется

с

помощью

функции

индекса,

име­

нуемой

operator

[].

Она

возвращает

ссылку

на

соответствующий элемент

(douЬle&,

которая

позволяет

читать

и

записывать

этот

элемент).

Функция

size

()

возвращает

пользователям

количество

хранящихся эле­

ментов. Очевидно,

что

обработка

ошибок

здесь

полностью

отсутствует,

но

мы

вер­

немся к этому позже, в

§3.5. Точно так

же мы не

предоставляем

"возврата" в свободную

память массива

douЬle,

полученного с

механизм помощью

оператора

new;

в

§4.2.2

показано,

как

элегантно

это сделать с

использованием

деструктора. Между структурой

и

классом

нет

никакой

принципиальной

разницы;

struct

-

это

просто

класс с

членами,

открытыми

(puЫic)

по умолчанию.

46

Глава

2.

Пользовательские

типы

Например,

вы

структуры.

 

можете

определить

конструкторы

и

другие

функции-члены

для

2.4.

Объединения

Объединение (union)

представляет

собой

структуру

(struct),

в

которой

все

члены

располагаются

по

одному

и

тому

же

адресу,

так

что

union

зани­

мает

столько

же

памяти,

сколько

и

его

наибольший

член.

Естественно,

union

может

хранить

одновременно

значение

только

одного

члена.

Например,

рас­

смотрим

запись

таблицы

символов,

которая

хранит

имя

и

значение.

Значение

может

иметь

тип

либо

Node*,

либо

int:

enum

Туре

{

ptr,

num

};

//

Туре

может

хранить

значения

ptr

и

пит

(§2.5)

struct Entry

{

 

string

name;

Туре t;

Node*

р;

int i;

 

};

 

//

string -

тип

стандартной

библиотеки

11

Используем

р,

если

t==ptr

 

11

Используем

i,

если

t==num

 

void

f{Entry*

ре)

if 11

(pe->t cout

==

<<

num) pe->i;

Члены

р

и

i

никогда

не

используются

одновременно,

так что

получается

пустая трата

нами union:

памяти.

Ее

легко

избежать,

указав,

что

оба

члена

являются

чле­

union

Value

Node*

р;

 

int

i;

 

} ;

 

 

Язык не отслеживает,

какие

должен делать программист :

значения хранятся

в

объединении,

так что

это

struct Entry

 

{

 

 

string name;

Туре

t;

 

Value

v;

11

Используем

v.p,

если

t==ptr;

и

v.i,

если

t==num

);

2.5.

Перечисления

void

f(Entry*

ре)

{

 

 

 

 

 

if

(pe->t

==

num)

 

 

cout

<<

pe->v.i;

 

11

 

 

 

47

Поддержание

соответствия

между

полем

типа

(здесь

-

t)

и

типом,

содер­

жащимся

в

объединении,

чревато ошибками.

Чтобы

избежать ошибок,

можно

обеспечить

это

соответствие,

инкапсулируя

объединение

и

поле

типа

в

класс

и

предлагая

доступ

только

через

функции-члены,

которые

гарантируют

кор­

ректное

использование

объединения.

На

уровне

приложений

абстракции,

основанные

на

таких

маркированных

объединениях

(tagged unions),

являют­

ся достаточно распространенными и полезными.

Использование

же "голых"

объединений лучше

всего свести к минимуму.

 

 

 

 

Тип

стандартной

библиотеки variant

может

использоваться

 

для устра­

нения

большинства

прямых применений

объединений.

variant

сохраняет

значение одного из

множества альтернативных

типов

(§13.5.1).

 

Например,

variant<Node*, int> может содержать либо Node*, либо int.

С помощью variant пример Entry может быть записан следующим

об­

разом:

struct Entry { string name; variant<Node*,int> };

v;

void

f(Entry*

ре)

 

if

(holds_alternative<int>(pe->v))

 

 

cout

<< get<int>(pe->v);

 

11

 

 

// *ре хранит int? (§13.5.1)

//Получаем этот int

Для

множества

применений

variant проще

и безопаснее,

чем

union.

2.5.

Перечисления

В дополнение к классам С++ поддерживает простую разновидность зовательских типов, для которой мы можем перечислить значения:

поль­

enum enum

class class

Color { red, Traffic_light

Ыuе, green };

{ green, yellow,

red

};

Color col = Color::red; Traffic_light light = Traffic

light::red;

2.6.

Советы

49

Если

вы

не

хотите

явно

квалифицировать

имена

перечислителей

и

хотите,

чтобы

их

значения

были

целыми

числами

(без

необходимости

явного

преоб­

разования),

можете

удалить

слово

class

из

enum

class

и

получить "обыч­

ный"

enum.

Перечислители

такого

"обычного"

enum

находятся

в

той

же

об­

ласти

видимости,

что

и

имя

их

перечисления,

и

неявно

преобразуются

в

свои

целые

значения.

Например:

enum int

Color col =

{ red, green;

green,

Ыuе

1;

Здесь

col

получает

значение

1. По

умолчанию

целочисленные

значения

счет­

чиков

начинаются

с

О

и

увеличиваются

на единицу

для

каждого

очередного

перечислителя.

"Обычное"

перечисление

было

в

С++

С)

с

самых

первых

дней,

так

что

несмотря

на то,

что

они

менее

хорошо

себя

ведут,

они

широко

распространены

в современном коде.

2.6.

Советы

[1]

[2]

[3]

[4]

[5]

[6]

[7]

[8]

[9]

Предпочитайте хорошо определенные

пользовательские типы

встроен­

ным, если последние оказываются слишком низкоуровневыми;

§2.1.

Организовывайте связанные данные в

структуры (struct или

class);

§2.2; [CG:C. l ].

 

 

 

Представляйте различие между интерфейсом и

реализацией с помощью

class; §2.3; [CG:C.3].

 

 

 

struct представляет собой просто class, все

члены которого

по умол­

чанию являются puЫic; §2.3.

 

 

 

Определите конструкторы для гарантии и простоты инициализации

классов; §2.3; [CG:C.2).

 

 

 

Избегайте "голых" объединений; заворачивайте их в класс вместе с по­

лем типа; §2.4; [CG:C.181 ].

 

 

 

Используйте перечисления для представления

множеств именованных

констант; §2.5; [CG:Enum.2].

 

 

 

Предпочитайте перечисления class

enum "обычным" перечислениям

enum для минимизации неожиданностей; §2.5; [CG:Enum.3].

 

Определите операции над перечислениями для безопасного и простого

использования; §2.5; [CG:Enum.4].