![]() |
Главная Случайная страница Контакты | Мы поможем в написании вашей работы! | |
|
#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. Если только не применяется перегрузка, имена членов обычно короче имен друзей.
Композиция классов. Включение нескольких объектов других классов в данный класс с тем, чтобы данный класс мог брать нужные сведения из других классов называется композицией.
Дата публикования: 2015-10-09; Прочитано: 188 | Нарушение авторского права страницы | Мы поможем в написании вашей работы!