Бьерн Страуструп. Язык программирования С++



бет62/124
Дата16.07.2016
өлшемі3.27 Mb.
#204081
түріКнига
1   ...   58   59   60   61   62   63   64   65   ...   124

7.11 Строковый класс


Теперь можно привести более осмысленный вариант класса string.

В нем подсчитывается число ссылок на строку, чтобы минимизировать

копирование, и используются как константы стандартные строки C++.


#include

#include


class string {

struct srep {

char* s; // указатель на строку

int n; // счетчик числа ссылок

srep() { n = 1; }

};

srep *p;


public:

string(const char *); // string x = "abc"

string(); // string x;

string(const string &); // string x = string ...

string& operator=(const char *);

string& operator=(const string &);

~string();

char& operator[](int i);


friend ostream& operator<<(ostream&, const string&);

friend istream& operator>>(istream&, string&);


friend int operator==(const string &x, const char *s)

{ return strcmp(x.p->s,s) == 0; }


friend int operator==(const string &x, const string &y)

{ return strcmp(x.p->s,y.p->s) == 0; }


friend int operator!=(const string &x, const char *s)

{ return strcmp(x.p->s,s) != 0; }


friend int operator!=(const string &x, const string &y)

{ return strcmp(x.p->s,y.p->s) != 0; }

};
Конструкторы и деструкторы тривиальны:
string::string()

{

p = new srep;



p->s = 0;

}
string::string(const string& x)

{

x.p->n++;



p = x.p;

}
string::string(const char* s)

{

p = new srep;



p->s = new char[ strlen(s)+1 ];

strcpy(p->s, s);

}
string::~string()

{

if (--p->n == 0) {



delete[] p->s;

delete p;

}

}
Как и всегда операции присваивания похожи на конструкторы. В них



нужно позаботиться об удалении первого операнда, задающего левую

часть присваивания:


string& string::operator=(const char* s)

{

if (p->n > 1) { // отсоединяемся от старой строки



p->n--;

p = new srep;

}

else // освобождаем строку со старым значением



delete[] p->s;
p->s = new char[ strlen(s)+1 ];

strcpy(p->s, s);

return *this;

}
string& string::operator=(const string& x)

{

x.p->n++; // защита от случая ``st = st''



if (--p->n == 0) {

delete[] p->s;

delete p

}

p = x.p;



return *this;

}
Операция вывода показывает как используется счетчик числа ссылок.

Она сопровождает как эхо каждую введенную строку (ввод происходит

с помощью операции << , приведенной ниже):


ostream& operator<<(ostream& s, const string& x)

{

return s << x.p->s << " [" << x.p->n << "]\n";



}
Операция ввода происходит с помощью стандартной функции ввода

символьной строки ($$10.3.1):


istream& operator>>(istream& s, string& x)

{

char buf[256];



s >> buf; // ненадежно: возможно переполнение buf

// правильное решение см. в $$10.3.1

x = buf;

cout << "echo: " << x << '\n';

return s;

}
Операция индексации нужна для доступа к отдельным символам.

Индекс контролируется:
void error(const char* p)

{

cerr << p << '\n';



exit(1);

}
char& string::operator[](int i)

{

if (i<0 || strlen(p->s)

return p->s[i];

}
В основной программе просто даны несколько примеров применения

строковых операций. Слова из входного потока читаются в строки,

а затем строки печатаются. Это продолжается до тех пор, пока не

будет обнаружена строка done, или закончатся строки для записи

слов, или закончится входной поток. Затем печатаются все строки

в обратном порядке и программа завершается.
int main()

{

string x[100];



int n;
cout << " здесь начало \n";
for ( n = 0; cin>>x[n]; n++) {

if (n==100) {

error("слишком много слов");

return 99;

}

string y;



cout << (y = x[n]);

if (y == "done") break;


}

cout << "теперь мы идем по словам в обратном порядке \n";

for (int i=n-1; 0<=i; i--) cout << x[i];

return 0;

}

7.12 Друзья и члены


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

пользовательского типа стоит использовать функции-члены, а когда

функции-друзья. Некоторые функции, например конструкторы, деструкторы

и виртуальные функции ($$R.12), обязаны быть членами, но для других

есть возможность выбора. Поскольку, описывая функцию как член, мы

не вводим нового глобального имени, при отсутствии других доводов

следует использовать функции-члены.

Рассмотрим простой класс X:
class X {

// ...
X(int);


int m1();

int m2() const;


friend int f1(X&);

friend int f2(const X&);

friend int f3(X);

};
Вначале укажем, что члены X::m1() и X::m2() можно вызывать только

для объектов класса X. Преобразование X(int) не будет применяться

к объекту, для которого вызваны X::m1() или X::m2():


void g()

{

1.m1(); // ошибка: X(1).m1() не используется



1.m2(); // ошибка: X(1).m2() не используется

}
Глобальная функция f1() имеет то же свойство ($$4.6.3), поскольку

ее параметр - ссылка без спецификации const. С функциями f2() и

f3() ситуация иная:


void h()

{

f1(1); // ошибка: f1(X(1)) не используется



f2(1); // нормально: f2(X(1));

f3(1); // нормально: f3(X(1));

}
Следовательно операция, изменяющая состояние объекта класса,

должна быть членом или глобальной функцией с параметром-ссылкой

без спецификации const. Операции над основными типами, которые

требуют в качестве операндов адреса (=, *, ++ и т.д.),

для пользовательских типов естественно определять как члены.

Обратно, если требуется неявное преобразование типа для всех

операндов некоторой операции, то реализующая ее функция должна

быть не членом, а глобальной функцией и иметь параметр типа ссылки

со спецификацией const или нессылочный параметр. Так обычно обстоит

дело с функциями, реализующими операции, которые для основных

типов не требуют адресов в качестве операндов (+, -, || и т.д.).

Если операции преобразования типа не определены, то нет

неопровержимых доводов в пользу функции-члена перед функцией-другом

с параметром-ссылкой и наоборот. Бывает, что программисту просто

одна форма записи вызова нравится больше, чем другая.

Например, многим для обозначения функции обращения матрицы m больше

нравится запись inv(m), чем m.inv(). Конечно, если функция

inv() обращает саму матрицу m, а не возвращает новую, обратную m,

матрицу, то inv() должна быть членом.

При всех прочих равных условиях лучше все-таки остановиться

на функции-члене. Можно привести такие доводы. Нельзя гарантировать,

что когда-нибудь не будет определена операция обращения. Нельзя во

всех случаях гарантировать, что будущие изменения не повлекут за

собой изменения в состоянии объекта. Запись вызова функции-члена

ясно показывает программисту, что объект может быть изменен, тогда

как запись с параметром-ссылкой далеко не столь очевидна. Далее,

выражения допустимые в функции-члене могут быть существенно

короче эквивалентных выражений в глобальной функции. Глобальная

функция должна использовать явно заданные параметры, а в

функции-члене можно неявно использовать указатель this. Наконец,

поскольку имена членов не являются глобальными именами, они обычно

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





Достарыңызбен бөлісу:
1   ...   58   59   60   61   62   63   64   65   ...   124




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет