![]() Главная страница Случайная страница КАТЕГОРИИ: АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника |
Листинг 10.4
#include #include class string { struct srep { char* s; // указатель на данные int n; // счетчик ссылок }; srep *p; public: string(char *); // string x = " abc" string(); // string x; string(string &); // string x = string... string& operator=(char *); string& operator=(string &); ~string(); char& operator[](int i); friend ostream& operator< < (ostream&, string&); friend istream& operator> > (istream&, string&); friend int operator==(string& x, char* s) { return strcmp(x.p-> s, s) == 0; } friend int operator==(string& x, string& y) { return strcmp(x.p-> s, y.p-> s) == 0; } friend int operator! =(string& x, char* s) { return strcmp(x.p-> s, s)! = 0; } friend int operator! =(string& x, string& y) { return strcmp(x.p-> s, y.p-> s)! = 0; } };
Конструкторы и деструкторы просты (как обычно):
string:: string() { p = new srep; p-> s = 0; p-> n = 1; } string:: string(char* s) { p = new srep; p-> s = new char[ strlen(s)+1 ]; strcpy(p-> s, s); p-> n = 1; } string:: string(string& x) { x.p-> n++; p = x.p; } string:: ~string() { if (--p-> n == 0) { delete p-> s; delete p; } }
Как обычно, операции присваивания очень похожи на конструкторы. Они должны обрабатывать очистку своего первого (левого) операнда:
string& string:: operator=(char* s) { if (p-> n > 1) { // разъединить себя p-n--; p = new srep; } else if (p-> n == 1) delete p-> s; p-> s = new char[ strlen(s)+1 ]; strcpy(p-> s, s); p-> n = 1; return *this; }
Благоразумно обеспечить, чтобы присваивание объекта самому себе работало правильно:
string& string:: operator=(string& x) { x.p-> n++; if (--p-> n == 0) { delete p-> s; delete p; } p = x.p; return *this; }
Операция вывода задумана так, чтобы продемонстрировать применение учета ссылок. Она повторяет каждую вводимую строку (с помощью операции < <, которая определяется позднее):
ostream& operator< < (ostream& s, string& x) { return s < < x.p-> s < < " [" < < x.p-> n < < " ]\n"; }
Операция ввода использует стандартную функцию ввода символьной строки:
istream& operator> > (istream& s, string& x) { char buf[256]; s > > buf; x = buf; cout < < " echo: " < < x < < " \n"; return s; }
Для доступа к отдельным символам предоставлена операция индексирования. Осуществляется проверка индекса:
void error(char* p) { cerr < < p < < " \n"; exit(1); } char& string:: operator[](int i) { if (i< 0 || strlen(p-> s)s[i]; }
Головная программа просто немного опробует действия над строками. Она читает слова со ввода в строки, а потом эти строки печатает. Она продолжает это делать до тех пор, пока не распознает строку done, которая завершает сохранение слов в строках, или не встретит конец файла. После этого она печатает строки в обратном порядке и завершается.
main() { string x[100]; int n; cout < < " отсюда начнем\n"; for (n = 0; cin> > x[n]; n++) { string y; if (n==100) error(" слишком много строк"); cout < < (y = x[n]); if (y==" done") break; } cout < < " отсюда мы пройдем обратно\n"; for (int i=n-1; 0< =i; i--) cout < < x[i]; }
Друзья и Члены. Теперь, наконец, можно обсудить, в каких случаях для доступа к закрытой части определяемого пользователем типа использовать члены, а в каких - друзей. Некоторые операции должны быть членами: конструкторы, деструкторы и виртуальные функции, но обычно это зависит от выбора. Рассмотрим простой класс X:
class X { //... X(int); int m(); friend int f(X&); };
Внешне не видно никаких причин делать f(X&) другом дополнительно к члену X:: m() (или наоборот), чтобы реализовать действия над классом X. Однако член X:: m() можно вызывать только для " настоящего объекта", в то время как друг f() может вызываться для объекта, созданного с помощью неявного преобразования типа. Например:
void g() { 1.m(); // ошибка f(1); // f(x(1)); }
Поэтому операция, изменяющее состояние объекта, должно быть членом, а не другом. Для определяемых пользователем типов операции, требующие в случае фундаментальных типов операнд lvalue (=, *=, ++ и т.д.), наиболее естественно определяются как члены. И наоборот, если нужно иметь неявное преобразование для всех операндов операции, то реализующая ее функция должна быть другом, а не членом. Это часто имеет место для функций, которые реализуют операции, не требующие при применении к фундаментальным типам lvalue в качестве операндов (+, -, || и т.д.). Если никакие преобразования типа не определены, то оказывается, что нет никаких существенных оснований в пользу члена, если есть друг, который получает ссылочный параметр, и наоборот. В некоторых случаях программист может предпочитать один синтаксис вызова другому. Например, оказывается, что большинство предпочитает для обращения матрицы m запись m.inv(). Конечно, если inv() действительно обращает матрицу m, а не просто возвращает новую матрицу, обратную m, ей следует быть другом. При прочих равных условиях выбирайте, чтобы функция была членом: никто не знает, вдруг когда-нибудь кто-то определит операцию преобразования. Невозможно предсказать, потребуют ли будущие изменения изменить статус объекта. Синтаксис вызова функции члена ясно указывает пользователю, что объект можно изменить; ссылочный параметр является далеко не столь очевидным. Кроме того, выражения в члене могут быть заметно короче выражений в друге. В функции друге надо использовать явный параметр, тогда как в члене можно использовать неявный this. Если только не применяется перегрузка, имена членов обычно короче имен друзей. Композиция классов. Включение нескольких объектов других классов в данный класс с тем, чтобы данный класс мог брать нужные сведения из других классов называется композицией.
|