відповідним чином проініціалізований, його можна використовувати наприклад так:
my_out.put("Hello, world\n");
За допомогою операції крапка вибирається член класу для даного об'єкту цього класу. Тут для об'єкту my_out викликається функція put().
Функція може визначатися так:
void ostream::put(char* p)
{
while (*p) buf.sputc(*p++);
}
де sputc() - функція, яка поміщає символ в streambuf.
Префікс ostream необхідний, щоб відрізнити put() ostream'а від інших функцій з ім'ям put().
Для звернення до функції-члену повинен бути вказаний об'єкт класу. В функції-члені можна посилатися на цей об'єкт неявно, як це робилося вище в ostream::put(): в кожному виклику buf відноситься до члену buf об'єкту, для якого функція викликана.
Можна також посилатися на цей об'єкт явно за допомогою покажчика з ім'ям this. У функції-члені класу X this неявно описаний як X* (покажчик на X) і ініціалізувався покажчиком на той об'єкт, для якого ця функція викликана. Визначення ostream::put() можна також записати у вигляді:
void ostream::put(char* p)
{
while (*p) this->buf.sputc(*p++);
}
Операція -> застосовується для вибору члена об'єкту, заданого покажчиком.
2.2 Перевантаження операцій
Справжній клас ostream визначає операцію <<, щоб зробити зручним виведення декількох об'єктів одним оператором. Давайте подивимося, як це зроблено. Щоб визначити @, де @ - деяка операція мови C++, для кожного визначеного користувачем типу визначаєте функцію з ім'ям operator@, яка одержує параметри відповідного типу. Наприклад:
class ostream {
//...
ostream operator<<(char*); };
ostream ostream::operator<<(char* p)
{ while (*p) buf.sputc(*p++); return *this; }
визначає операцію << як член класу ostream, тому s< ");
а якщо застосувати операцію отримання адреси, то ви одержите адресу об'єкту, на який посилається посилання:
&s1 == &my_out
Перша очевидна користь від посилань полягає в тому, щоб забезпечити передачу адреси об'єкту, а не самого об'єкту, у функцію виводу (в деяких мовах це називається передачею параметра за посиланням):
ostream& operator<<(ostream& s, complex z) { return s << "(" << z.real << "," << z.imag << ")"; }
Достатньо цікаво, що тіло функції залишилося без змін, але якщо здійснювати привласнення s, то це впливатиме на сам об'єкт, а не на його копію. В даному випадку те, що повертається посилання, також підвищує ефективність, оскільки очевидний спосіб реалізації посилання - це покажчик, а передача покажчика набагато дешевше, ніж передача великої структури даних. Посилання також істотні для визначення потоку введення, оскільки операція введення одержує як операнд змінну для прочитування. Якби посилання не використовувалися, то користувач повинен був би явно передавати покажчики у функції введення.
class istream {
//...
int state;
public:
istream& operator>>(char&);
istream& operator>>(char*);
istream& operator>>(int&);
istream& operator>>(long&);
//...
};
Для читання long і int використовуються різні функції тоді як для їх друку була потрібна тільки одна. Це цілком звично і причина в тому, що int може бути перетворене в long за стандартними правилами неявного перетворення, позбавляючи таким чином програміста від турботи з приводу написання обох функцій введення.
2.3 Конструктори
Визначення ostream як класу зробило члени-дані закритими. Тільки функція-член має доступ до закритих членів, тому треба передбачити функцію для ініціалізації. Така функція називається конструктором і відрізняється тим, що має те ж ім'я, що і її клас:
class ostream {
//...
ostream(streambuf*);
ostream(int size, char* s);
};
Тут задано два конструктори. Один одержує вищезазначений streambuf для реального виводу, інший одержує розмір і покажчик на символ для форматування рядка. В описі необхідний для конструктора список параметрів приєднується до імені. Тепер ви можете, наприклад, описати такі потоки:
ostream my_out(&some_stream_buffer);
char xx[256];
ostream xx_stream(256,xx);
Опис my_out не тільки задає відповідний об'єм пам'яті десь у іншому місці, він також викликає конструктор ostream::ostream(streambuf*), щоб ініціалізувати його параметром &some_stream_buffer, покажчиком на відповідний об'єкт класу streambuf. Опис конструкторів для класу не тільки дає спосіб ініціалізації об'єктів, але також забезпечує те, що всі об'єкти цього класу будуть проініціалізовані. Якщо для класу були описані конструктори, то неможливо описати змінну цього класу так, щоб конструктор не був викликаний. Якщо клас має конструктор, не одержуючий параметрів, то цей конструктор викликатиметься в тому випадку, якщо в описі немає ні одного параметра.
2.4 Похідні класи
У конструкції
агрег ідентифікатор:public opt typedef-ім'я
typedef-ім'я повинне означати раніше описаний клас, що називається базовим класом для класу, що підлягає опису. На члени базового класу можна посилатися, неначебто вони були членами похідного класу за винятком тих випадків, коли ім'я базового члена було перевизначено в похідному класі; в цьому випадку для посилання на приховане ім'я може використовуватися такий запис:
typedef-ім'я :: ідентифікатор
Наприклад:
struct base
{
int а;
int b;
};
struct derived : public base
{
int b;
int з;
};
derived d;
d.a = 1;
d.base::b = 2;
d.b = 3;
d.c = 4;
здійснює привласнення чотирьом членам d.
Похідний тип сам може використовуватися як базовий.
2.5 Віртуальні функції
Припустимо, що ми пишемо програму для зображення фігур на екрані. Загальні атрибути фігури представлені класом shape, а спеціальні атрибути - спеціальними класами:
class shape {
point сеnter;
color col;
//...
public:
void move(point to) { center=to; draw(); }
point where() { return сеnter; }
virual void draw();
virtual void rotate(int);
//...
};
Функції, які можна визначити не знаючи точно певної фігури (наприклад, move і where, тобто, "пересунути" і "де"), можна описати як завжди. Решта функцій описується як virual, та є такі, які повинні визначатися в похідному класі. Наприклад:
class circle: public shape {
int radius;
public:
void draw();
void rotatte(int i){}
//...
};
2.6 Конструктори
Член-функція з ім'ям, співпадаючим з ім'ям її класу, називається конструктором. Конструктор не має типу значення, що повертається; він