Главная · Поиск книг · Поступления книг · Top 40 · Форумы · Ссылки · Читатели

Настройка текста
Перенос строк


    Прохождения игр    
Stoneshard |#11| Battle at the castle
Stoneshard |#10| A busy reaper
The Elder Scrolls IV: Oblivion Remastered - Trash review
Stoneshard |#9| A Million Liches

Другие игры...


liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня
Rambler's Top100
Образование - Страустрап Б. Весь текст 579.17 Kb

Язык С++

Предыдущая страница Следующая страница
1 ... 23 24 25 26 27 28 29  30 31 32 33 34 35 36 ... 50
assoc. Итератору  нужен доступ  к данным, которые хранятся в assoc,
поэтому он сделан другом:

  class assoc {
  friend class assoc_iterator;
      pair* vec;
      int max;
      int free;
  public:
      assoc(int);
      int& operator[](char*);
  };

Итератор определяется как

  class assoc_iterator{
      assoc* cs;  // текущий массив assoc
      int i;      // текущий индекс
  public:
      assoc_iterator(assoc& s) { cs = &s; i = 0; }
      pair* operator()()
          { return (ifree)? &cs->vec[i++] : 0; }
  };

Надо инициализировать  assoc_iterator для массива assoc, после чего
он будет  возвращать указатель на новую pair из этого массива вский
раз, когда  его будут  активизировать операцией  (). По  достижении
конца массива он возвращает 0:

  main()    // считает вхождения каждого слова во вводе
  {
      const MAX = 256;  // больше самого большого слова
      char buf[MAX];
      assoc vec(512);
      while (cin>>buf) vec[buf]++;
      assoc_iterator next(vec);
      pair* p;
      while ( p = next() )
          cout << p->name << ": " << p->val << "\n";
  }

                             - стр 191 -

Итераторный  тип  вроде  этого  имеет  преимущество  перед  набором
функций, которые  выполняют ту  же работу:  у него есть собственные
закрытые данные  для хранения  хода  итерации.  К  тому  же  обычно
существенно, чтобы  одновременно могли  работать  много  итераторов
этого типа.
  Конечно, такое  применение объектов  для представления итераторов
никак особенно  с перегрузкой  операций не  связано.  Многие  любят
использовать итераторы  с такими  операциями, как first(), next() и
last() (первый, следующий и последний).

     6.9 Класс Строка

  Вот  довольно   реалистичный  пример   класса   string.   В   нем
производится  учет   ссылок  на   строку  с   целью  минимизировать
копирование  и   в  качестве   констант   применяются   стандартные
символьные строки C++.

  #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; }

  };

Конструкторы и деструкторы просты (как обычно):

                             - стр 192 -

  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;
  }

  Благоразумно обеспечить,  чтобы присваивание  объекта самому себе
работало правилньо:

                             - стр 193 -

  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";
  }

  Операция ввода  использует стандартную  функцию ввода  символьной
строки (#8.4.1).

  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,  которая завершает  сохранение слов  в строках, или не
встретит конец  файла. После  этого она  печатает строки в обратном
порядке и завершается.

                             - стр 194 -

  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];
  }

     6.10 Друзья и Члены

  Теперь, наконец,  можно обсудить,  в каких  случаях для доступа к
закрытой части определяемого пользователем типа использовать члены,
а в  каких  -  друзей.  Некоторые  операции  должны  быть  членами:
конструкторы, деструкторы  и  виртуальные  функции  (см.  следующую
главу), но обчно это зависит от выбора.
  Рассмотрим простой класс 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 в качестве операндов (+, -, || и т.д.).

                             - стр 195 -

  Если никакие  преобразования типа  не определены, то оказывается,
что нет  никаких существенных  оснований в  пользу члена, если есть
друг, который  получает ссылочный параметр, и наоборот. В некоторых
случаях  программист   может  предпочитать  один  синтаксис  вызова
другому. Например,  оказывается, что  большинство предпочитает  для
обращения  матрицы   m  запись   m.inv().   Конечно,   если   inv()
действительно обращает  матрицу m,  а не  просто  возвращает  новую
матрицу, обратную m, ей следут быть другом.
  При прочих  равных условиях выбирайте, чтобы функция была членом:
никто  не  знает,  вдруг  когда-нибудь  кто-то  определит  операцию
преобразования.  Невозможно   предсказать,  потребуют   ли  будущие
изменения изменить  статус объекта.  Синтаксис вызова функции члена
ясно указывает  пользователю, что  объект можно изменить; ссылочный
параметр является  далеко не столь очевидным. Кроме того, выражения
в члене  могут быть  заметно короче  выражений в  друге. В  функции
друге надо  использовать явный  параметр, тогда  как в  члене можно
использовать неявный  this. Если  только не применяется перегрузка,
имена членов обычно короче имен друзей.

     6.11 Предостережение

  Как и  большая  часть  возможностей  в  языках  программирования,
перегрузка  операций   может  применяться   как  правильно,  так  и
неправильно. В  частности, можно  так  воспользоваться  возможность
определять новые  значения старых  операций, что  они станут  почти
совсем непостижимы.  Представьте, например,  с  какими  сложностями
столкнется человек,  читающий программу,  в которой операция + была
переопределена для обозначения вычитания.
  Данный аппарат  должен уберечь  программиста/читателя  от  худших
крайностей   применения    перегрузки,   потому   что   программист
предохранен от  изменения  значения  операций  для  основных  типов
данных вроде  int,  а  также  потому,  что  синтаксис  выражений  и
приоритеты операций сохраняются.
  Может быть. разумно применять перегрузку операций главным образом
так, чтобы  подражать  общепринятому  применению  операций.  В  тех
случаях, когда  нет  общепринятой  операции  или  имеющееся  в  C++
множество  операций   не  подходит   для   имитации   общепринятого
применения, можно использовать запись вызова функции.

     6.12 Упражнения

  1. (*2)  Определите   итератор  для   класса  string.  Определите
     операцию конкатенации  + и  операцию "добавить  в  конец"  +=.
     Какие еще операции над string вы хотели бы осуществлять?
  2. (*1.5) Задайте  с помощью  перегрузки  ()  операцию  выделения
     подстроки для класса строк.
  3. (*3) Постройте  класс string  так,  чтобы  операция  выделения
     подстроки могла  использоваться в  левой  части  присваивания.
     Напишите сначала  версию, в которой строка может присваиваться
     подстроке той  же длины,  а потом  версию, где эти длины могут
     быть разными.
  4. (*2) Постройте  класс  string  так,  чтобы  для  присваивания,
     передачи параметров  и т.п.  он имел семантику по значению, то

                             - стр 196 -

     есть в  тех случаях, когда копируется строковое представление,
     а не просто управляющая структура данных класса sring.
  5. (*3) Модифицируйте  класс string  из предыдущего примера таким
     образом,  чтобы   строка   копировалась   только   когда   это
     необходимо.   То    есть,   храните   совместно   используемое
     представление двух  строк, пока  одна из  этих строк  не будет
     изменена. Не  пытайтесь одновременно  с  этим  иметь  операцию
     выделения подстроки,  которая  может  использоваться  в  левой
     части.
  6. (*4) Разработайте  класс  string  с  семантикой  по  значению,
     копированием с  задержкой и операцией подстроки, которая может
     стоять в левой части.
  7. (*2) Какие  преобразования  используются  в  каждом  выражении
     следующей программы:

       struct X {
          int i;
          X(int);
          operator+(int);
       };

       struct Y {
          int i;
          Y(X);
          operator+(X);
          operator int();
       };

       X operator* (X,Y);
       int f(X);

       X x = 1;
       Y y = x;
       int i = 2;

       main()
       {
          i + 10;
          y + 10;
          y + 10 * y;
          x + y + i;
          x * x + i;
          f(7);
          f(y);
          y + y;
          106 + y;
       }

     Определите X  и Y  так, чтобы  они  оба  были  целыми  типами.
     Измените программу так, чтобы она работала и печатала значения
Предыдущая страница Следующая страница
1 ... 23 24 25 26 27 28 29  30 31 32 33 34 35 36 ... 50
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 
Комментарии (4)

Реклама