Программирование >>  Инициализация объектов класса, структура 

1 ... 10 11 12 [ 13 ] 14 15 16 ... 395


myArray0 =

инструкция присваивания вызывает -

функцию-член myArray0.operator=(myArray1) myArray1;

инструкция сравнения -

вызывает функцию-член myArray0.operator==(myArray1) if (myArray0 == myArray1)

обычным образом:

cout << Ура! Оператор присваивания сработал!\n ;

Спецификаторы доступа public и private определяют уровень доступа к членам класса. К тем членам, которые перечислены после public, можно обращаться из любого места программы, а к тем, которые объявлены после private, могут обращаться только функции-члены данного класса. (Помимо функций-членов, существуют еще функции-друзья класса, но м1 не будем говорить о них вплоть до раздела 15.2.)

В общем случае открытые члены класса составляют его открытый интерфейс, то есть набор операций, которые определяют поведение класса. Закрытые члены класса обеспечивают его скрытую реализацию.

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

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

если в реализации класса обнаруживается ошибка, то обычно для ее исправления достаточно проверить код, составляющий именно скрытую реализацию, а не весь код программы, где данный класс используется.

Какие же внутренние данные потребуются для реализации класса IntArray? Необходимо где-то сохранить размер массива и сами его элементы. Мы будем хранить их в массиве встроенного типа, память для которого выделяется динамически. Так что нам потребуется

class IntArray { public:

...

int size() const { return size; } private:

внутренние данные-члены

указатель на этот массив. Вот как будут выглядеть определения этих данных-членов:

int size; int *ia;

IntArray myArray0, myArray1; Инструкции присваивания и сравнения с этими объектами выглядят совершенно



IntArray array;

мы изменили спецификатор доступа члена size на public):

int array size = array.size(); array size = array. size;

Действительно, вызов функции гораздо менее эффективен, чем прямой доступ к памяти, как во втором операторе. Так что же, принцип сокрытия информации заставляет нас жертвовать эффективностью?

На самом деле, нет. С++ имеет механизм встроенных (inline) функций. Текст встроенной функции подставляется компилятором в то место, где записано обращение к ней. (Это напоминает механизм макросов, реализованный во многих языках, в том числе и в С++. Однако есть онределенн1е отличия, о которых м1 сейчас говорить не будем.)

for (int index=0; index<array.size(); ++index)

Вот пример. Если у нас есть следующий фрагмент кода:

...

то функция size() не будет вызываться size раз во время исполнения. Вместо вызова компилятор подставит ее текст, и результат компиляции предыдущего кода будет в точности таким же, как если бы мы написали:

for (int index=0; index<array. size; ++index)

...

Если функция определена внутри тела класса (как в нашем случае), она автоматически считается встроенной. Существует также ключевое слово inline, позволяющее объявить встроенной любую функцию3.

Мы до сих пор ничего не сказали о том, как будем инициализировать наш массив.

Одна из самых распространенных ошибок при программировании (на любом языке) состоит в том, что объект используется без предварительной инициализации. Чтобы помочь избежать этой ошибки, С++ обеспечивает механизм автоматической инициализации для определяемых пользователем классов - конструктор класса.

3 Объявление функции inline - это всего лишь подсказка компилятору. Однако компилятор не всегда может сделать функцию встроенной, существуют некоторые ограничения. Подробнее об этом сказано в разделе 7.6.

Поскольку мы поместили член size в закрытую секцию, пользователь класса не имеет возможности обратиться к нему напрямую. Чтобы позволить внешней программе узнать размер массива, мы написали функцию-член size() , которая возвращает значение члена size. Нам пришлось добавить символ подчеркивания к имени нашего скрытого члена size, поскольку функция-член с именем size() уже определена. Члены класса -функции и данные - не могут иметь одинаковые имена.

Может показаться, что реализуя подобным образом доступ к скрытым данным класса, мы очень сильно проигрываем в эффективности. Сравним два выражения (предположим, что



список перегруженн функций min()

каждая функция отличается от других списком параметров

size);

#include <string>

int min (const int *pia,int int min (int, int); int min (const char *str); char min (string);

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

string min (string,string);

Поведение перегруженных функций во время выполнения ничем не отличается от поведения обычных. Компилятор определяет нужную функцию и помещает в объектный код именно ее вызов. (В главе 9 подробно обсуждается механизм перегрузки.)

Итак, вернемся к нашему классу IntArray. Давайте определим для него три

class IntArray {

public:

explicit IntArray (int sz = DefaultAArraySize); IntAArray (int *array, int array size) ; IntArray (const IntArray Srhs); ... private:

static const int DefaultArraySize = 12;

конструктора:

Первый из перечисленных конструкторов

IntAArray (int sz = DefaultArraySize);

называется конструктором по умолчанию, потому что он может быть вызван без параметров. (Пока не будем объяснять ключевое слово explicit.) Если при создании объекта ему задается параметр типа int, например

Конструктор - это специальная функция-член, которая вызывается автоматически при создании объекта типа класса. Конструктор пишется разработчиком класса, причем у одного класса может быть несколько конструкторов.

Функция-член класса, носящее то же имя, что и сам класс, считается конструктором. (Нет никаких специальных ключевых слов, позволяющих определить конструктор как-то по-другому.) М1 уже сказали, что конструкторов может быть несколько. Как же так: разные функции с одинаковыми именами?

В С++ это возможно. Разные функции могут иметь одно и то же имя, если у этих функций различны количество и/или типы параметров. Это называется перегрузкой функции. Обрабатывая вызов перегруженной функции, компилятор смотрит не только на ее имя, но и на список параметров. По количеству и типам передаваемых параметров компилятор может определить, какую же из одноименных функций нужно вызывать в данном случае. Рассмотрим пример. Мы можем определить следующий набор перегруженных функций min() . (Перегружаться могут как обычные функции, так и



1 ... 10 11 12 [ 13 ] 14 15 16 ... 395

© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки.
Яндекс.Метрика