KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программирование » Скотт Майерс - Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ

Скотт Майерс - Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Скотт Майерс, "Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ" бесплатно, без регистрации.
Перейти на страницу:

Определение (definition) сообщает компилятору детали, которые опущены в объявлении. Для объекта определение – это то место, где компилятор выделяет для него память. Для функции или шаблона функции определение содержит тело функции. В определении класса или шаблона класса перечисляются его члены:


int x; // определение объекта

std::size_t numDigits(int number) // определение функции

{ // (эта функция возвращает количество

std::size_t digitsSoFar = 1; // десятичных знаков в своем параметре)

while((number /= 10) != 0) ++digitsSoFar;

return digitsSoFar;

}

class Widget { // определение класса

public:

Widget();

~Widget();

...

};

template<typename T> // определение шаблона

class GraphNode {

public:

GraphNode();

~GraphNode();

...

};


Инициализация (initialization) – это процесс присваивания объекту начального значения. Для объектов пользовательских типов инициализация выполняется конструкторами. Конструктор по умолчанию (default constructor) – это конструктор, который может быть вызван без аргументов. Такой конструктор либо не имеет параметров вовсе, либо имеет значение по умолчанию для каждого параметра:


class A {

public:

A(); // конструктор по умолчанию

};

class B {

public:

explicit B(int x = 0; bool b = true); // конструктор по умолчанию,

}; // см. далее объяснение

// ключевого слова “explicit”

class C {

public:

explicit C(int x); // это не конструктор по

// умолчанию

};


Конструкторы классов B и C объявлены в ключевым словом explicit (явный). Это предотвращает их использование для неявных преобразований типов, хотя не запрещает применения, если преобразование указано явно:


void doSomething(B bObject); // функция принимает объект типа B

B bObj1; // объект типа B

doSomething(bObj1); // нормально, B передается doSomething

B bObj(28); // нормально, создает B из целого 28

// (параметр bool по умолчанию true)

doSomething(28); // ошибка! doSomething принимает B,

// а не int, и не существует неявного

// преобразования из int в B

doSomething(B(28)); // нормально, используется конструктор

// B для явного преобразования (приведения)

// int в B (см. в правиле 27 информацию

// о приведении типов)


Конструкторы, объявленные как explicit, обычно более предпочтительны, потому что предотвращают выполнение компиляторами неявных преобразований типа (часто нежелательных). Если нет основательной причины для использования конструкторов в неявных преобразованиях типов, я всегда объявляю их explicit. Советую и вам придерживаться того же принципа.

Обратите внимание, что в предшествующем примере приведение выделено. Я и дальше буду использовать такое выделение, чтобы подчеркнуть важность излагаемого материала. (Также я выделяю номера глав, но это только потому, что мне кажется, это выглядит симпатично.)

Конструктор копирования (copy constructor) используется для инициализации объекта значением другого объекта того же самого типа, а копирующий оператор присваивания (copy assignment operator) применяется для копирования значения одного объекта в другой – того же типа:


class Widget {

public:

Widget(); // конструктор по умолчанию

Widget(const Widget& rhs); // конструктор копирования

Widget& operator=(const Widget& rhs); // копирующий оператор присваивания

...

};

Widget w1; // вызов конструктора по умолчанию

Widget w2(w1); // вызов конструктора копирования

w1 = w2; // вызов оператора присваивания

// копированием


Будьте внимательны, когда видите конструкцию, похожую на присваивание, потому что синтаксис «=» также может быть использован для вызова конструктора копирования:


Widget w3 = w2; // вызов конструктора копирования!


К счастью, конструктор копирования легко отличить от присваивания. Если новый объект определяется (как w3 в последнем предложении), то должен вызываться конструктор, это не может быть присваивание. Если же никакого нового объекта не создается (как в «w1=w2»), то конструктор не применяется и это – присваивание.

Конструктор копирования – особенно важная функция, потому что она определяет, как объект передается по значению. Например, рассмотрим следующий фрагмент:


bool hasAcceptableQuality(Widget w);

...

Widget aWidget;

if (hasAcceptableQuality(aWidget)) ...


Параметр w передается функции hasAcceptableQuality по значению, поэтому в приведенном примере вызова aWidget копируется в w. Копирование осуществляется конструктором копирования из класса Widget. Вообще передача по значению означает вызов конструктора копирования. (Но, строго говоря, передавать пользовательские типы по значению – плохая идея. Обычно лучший вариант – передача по ссылке на константу, подробности см. в правиле 20.)

STL – стандартная библиотека шаблонов (Standard Template Library) – это часть стандартной библиотеки, касающаяся контейнеров (то есть vector, list, set, map и т. д.), итераторов (то есть vector<int>::iterator, set<string>::iterator и т. д.), алгоритмов (то есть for_each, find, sort и т. д.) и всей связанной с этим функциональности. В ней очень широко используются объекты-функции (function objects), то есть объекты, ведущие себя подобно функциям. Такие объекты представлены классами, в которых перегружен оператор вызова operator(). Если вы не знакомы с STL, вам понадобится, помимо настоящей книги, какое-нибудь достойное руководство, посвященное этой теме, ведь библиотека STL настолько удобна, что не воспользоваться ее преимуществами было бы непростительно. Стоит лишь начать работать с ней, и вы сами это почувствуете.

Программистам, пришедшим к C++ от языков вроде Java или C#, может показаться странным понятие неопределенного поведения. По различным причинам поведение некоторых конструкций в C++ действительно не определено: вы не можете уверенно предсказать, что произойдет во время исполнения. Вот два примера такого рода:


int *p = 0; // p – нулевой указатель

std::cout << *p; // разыменование нулевого указателя

char name[] = “Daria” // name – массив длины 6 (не забудьте про

// завершающий нуль!)

char c = name[10]; // указание неправильного индекса массива

// порождает неопределенное поведение


Дабы подчеркнуть, что результаты неопределенного поведения невозможно предсказать и что они могут быть весьма неприятны, опытные программисты на C++ часто говорят, что программы с неопределенным поведением могут стереть содержимое жесткого диска. Это правда: такая программа может стереть ваш жесткий диск, но может этого и не сделать. Более вероятно, что она будет вести себя по-разному: иногда нормально, иногда аварийно завершаться, а иногда – просто выдавать неправильные результаты. Мудрые программисты на C++ придерживаются правила – избегать неопределенного поведения. В этой книге во многих местах я указываю, как это сделать.

Иной термин, который может смутить программистов, пришедших из других языков, – это интерфейс. В Java и. NET-совместимых языках интерфейсы являются частью языка, но в C++ ничего подобного нет, хотя в правиле 31 рассматривается некоторое приближение. Когда я использую термин «интерфейс», то обычно имею в виду сигнатуры функций, доступные члены класса («открытый интерфейс», «защищенный интерфейс», «закрытый интерфейс») или выражения, допустимые в качестве параметров типа для шаблонов (см. правило 41). То есть под интерфейсом я понимаю общую концепцию проектирования.

Понятие клиент – это нечто или некто, использующий написанный вами код (обычно через интерфейсы). Так, например, клиентами функции являются ее пользователи: части кода, которые вызывают функцию (или берут ее адрес), а также люди, которые пишут и сопровождают такой код. Клиентами класса или шаблона являются части программы, использующие этот класс или шаблон, а равно программисты, которые пишут или сопровождают эти части. Когда речь заходит о клиентах, я обычно имею в виду программистов, поскольку именно они могут быть введены в заблуждение или недовольство плохо разработанным интерфейсом. Коду, который они пишут, такие эмоции недоступны.

Возможно, вы не привыкли думать о клиентах, но я постараюсь убедить вас в необходимости облегчить им жизнь, насколько это возможно. В конце концов, вы сами – клиент программного обеспечения, которое разрабатывал кто-то другой. Ведь вы хотели бы, чтоб его авторы облегчили вам работу? Помимо того, рано или поздно вы окажетесь в положении, когда сами станете клиентом собственного кода (то есть будете использовать код, написанный вами), и тогда оцените, что при разработке интерфейсов нужно помнить об интересах клиентов.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*