+
должен обозначать сложение; бинарный оператор *
— умножение; оператор []
— доступ; оператор ()
— вызов функции и т.д. Это просто совет, а не правило языка, но это хороший совет: общепринятое использование операторов, такое как символ +
для сложения, значительно облегчает понимание программы. Помимо всего прочего, этот совет является результатом сотен лет опыта использования математических обозначений.
Малопонятные операторы и необычное использование операторов могут запутать программу и стать источником ошибок. Более на эту тему мы распространяться не будем. Просто в следующих главах применим перегрузку операторов в соответствующих местах.
Интересно, что чаще всего для перегрузки выбирают не операторы +
, –
, *
, и /
, как можно было бы предположить, а =
, ==
, !=
, <
, []
и ()
.
9.7. Интерфейсы классов
struct
, некоторые профессионалы могут не согласиться с этим утверждением. Однако как разработать хороший интерфейс? Чем хороший интерфейс отличается от плохого? Частично на эти вопросы можно ответить только с помощью примеров, но существует несколько общих принципов, которые поддерживаются в языке С++.
• Интерфейс должен быть полным.
• Интерфейс должен быть минимальным.
• Класс должен иметь конструкторы.
• Класс доложен поддерживать копирование (или явно запрещать его) (см. раздел 14.2.4).
• Следует предусмотреть тщательную проверку типов аргументов.
• Необходимо идентифицировать немодифицирующие функции-члены (см. раздел 9.7.4).
• Деструктор должен освобождать все ресурсы (см. раздел 17.5). См. также раздел 5.5, в котором описано, как выявлять ошибки и сообщать о них на этапе выполнения программы.
Первые два принципа можно подытожить так: “Интерфейс должен быть как можно более маленьким, но не меньше необходимого”. Интерфейс должен быть маленьким, потому что его легче изучить и запомнить, а программист, занимающийся реализацией класса, не будет терять время на реализацию излишних или редко используемых функций. Кроме того, небольшой интерфейс означает, что если что-то пойдет не так, как задумано, для поиска причины потребуется проверить лишь несколько функций. В среднем чем больше открытых функций, тем труднее найти ошибку, — пожалуйста, не усложняйте себе жизнь, создавая классы с открытыми данными. Но, разумеется, интерфейс должен быть полным, в противном случае он будет бесполезным. Нам не нужен интерфейс, который не позволяет нам делать то, что действительно необходимо.
Перейдем к изучению менее абстрактных и более реалистичных понятий, поддерживаемых в языке С++.
9.7.1. Типы аргументов
Определяя конструктор класса Date
в разделе 9.4.3, мы использовали в качестве аргументов три переменные типа int
. Это породило несколько проблем.
Date d1(4,5,2005); // Ой! Год 4, день 2005
Date d2(2005,4,5); // 5 апреля или 4 мая?
Первая проблема (недопустимый день месяца) легко решается путем проверки в конструкторе. Однако вторую проблему (путаницу между месяцем и днем месяца) невозможно выявить с помощью кода, написанного пользователем. Она возникает из-за того, что существуют разные соглашения о записи дат; например, 4/5 в США означает 5 апреля, а в Англии — 4 мая. Поскольку эту проблему невозможно устранить с помощью вычислений, мы должны придумать что-то еще. Очевидно, следует использовать систему типов.
// простой класс Date (использует тип Month)
class Date {
public:
enum Month {
jan=1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec
};
Date(int y, Month m, int d); // проверка даты и инициализация
// ...
private:
int y; // год
Month m;
int d; // день
};
Когда мы используем тип Month
, компилятор выдаст ошибку, если мы поменяем местами месяц и день. Кроме того, перечисление Month
позволяет использовать символические имена. Такие имена, как правило, легче читать и записывать, чем работать с числами, подвергаясь риску ошибиться.
Date dx1(1998, 4, 3); // ошибка: 2-й аргумент не имеет
// тип Month
Date dx2(1998, 4, Date::mar); // ошибка: 2-й аргумент не имеет
// тип Month
Date dx2(4, Date::mar, 1998); // Ой: ошибка на этапе выполнения:
// день 1998