Главная · Поиск книг · Поступления книг · 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 ... 21 22 23 24 25 26 27  28 29 30 31 32 33 34 ... 50

  Относительно  смысла  операций,  определяемых  пользователем,  не
делается  никаких   предположений.  В   частности,   поскольку   не
предполагается,  что  перегруженное  =  реализует  присваивание  ее
первому   операнду,    не   делается    никакой   проверки,   чтобы
удостовериться, является ли этот операнд lvalue (#с.6).
  Значения   некоторых    встроенный   операций    определены   как
равносильные определенным  комбинациям другий  операций над теми же
аргументами. Например,  если a  является int, то ++a означает a+=1,
что  в   свою  очередь   означает  a=a+1.   Такие  соотношения  для
определенных пользователем  операций не выполняются, если только не
случилось так,  что пользователь  сам определил  их таким  образом.
Например, определение  operator+=() для  типа complex не может быть
выведено      из       определений      complex::operator+()      и
complex::operator=().
  По историческому совпадению операции = и & имеют предопределенный
смысл  для   объектов   классов.   Никакого   элегантного   сполоба
"неопределить" эти  две операции  не существует.  Их можно, однако,
сделать недееспособными  для класса  X.  Можно,  например,  описать
X::operator&(),  не  задав  ее  определения.  Если  где-либо  будет

                             - стр 179 -

браться адрес объекта класса X, то компоновщик обнаружит отсутствие
определения*. Или,  другой способ,  можно определить X::operator&()
так, чтобы вызывала ошибку во время выполнения.

     6.2.3 Операции и Определяемые Пользователем Типы

  Функция операция  должна или быть членом, или получать в качестве
параметра по  меньшей мере  один объект  класса (функциям,  которые
переопределяют операции  new и  delete, это  делать необязательно).
Это правило  гарантирует, что  пользователь не может изменить смысл
никакого   выражения,   не   включающего   в   себя   определенного
пользователем типа.  В частности,  невозможно  определить  функцию,
которая действует исключительно на указтели.
  Функция  операция,   первым  параметром   которой  предполагается
осповной тип,  не может быть функцией членом. Рассмотрим, например,
сложение комплексной переменной aa с целым 2: aa+2,  при подходящим
образом описанной функции члене, может быть проинтерпретировано как
aa.operator+(2), но  с 2+aa  это не  может быть сделано, потому что
нет такого класса int, для которого можно было бы определить + так,
чтобы это  означало 2.operator+(aa). Даже если бы такой тип был, то
для того,  чтобы обработать  и 2+aa  и aa+2,  понадобилось  бы  две
различных функции  члена. Так  как компилятор  не знает  смысла  +,
определенного пользователем,  то  не  может  предполагать,  что  он
коммутативен, и  интерпретировать 2+aa  как aa+2.  С этим  примером
могут легко справиться функции друзья.
  Все функции операции по определению перегружены. Функция операция
задает новый смысл операции в дополнение к встроенному определению,
и может  существовать несколько  функций операций  с одним и тем же
именем, если  в типах их параметров имеются отличия, различимые для
компилятора, чтобы он мог различать их при обращении (см. #4.6.7).

     6.3 Определяемое Преобразование Типа

  Приведенная во  введении  реализация  комплексных  чисел  слишком
ограничена, чтобы  она могла  устроить кого-либо,  поэтому ее нужно
расширить. Это будет в основном повторением описанных выше методов.
Например:

____________________
  *  В   некоторых  системах   компоновщик  настолько  "умен",  что
ругается, даже  если неопределена  неиспользуемая функция.  В таких
системах этим методом воспользоваться нельзя. (прим автора)

                             - стр 180 -

  class complex {
      double re, im;
  public:
      complex(double r, double i) { re=r; im=i; }

      friend complex operator+(complex, complex);
      friend complex operator+(complex, double);
      friend complex operator+(double, complex);

      friend complex operator-(complex, complex);
      friend complex operator-(complex, double);
      friend complex operator-(double, complex);
      complex operator-()     // унарный -

      friend complex operator*(complex, complex);
      friend complex operator*(complex, double);
      friend complex operator*(double, complex);

      // ...
  };

  Теперь, имея описание complex, мы можем написать:

  void f()
  {
      complex a(1,1), b(2,2), c(3,3), d(4,4), e(5,5);
      a = -b-c;
      b = c*2.0*c;
      c = (d+e)*a;
  }

Но писать  функцию для  каждого сочетания complex и double, как это
делалось  выше  для  operator+(),  невыносимо  нудно.  Кроме  того,
близкие  к   реальности  средства   комплексной  арифметики  должны
предоставлять по  меньшей мере  дюжину таких  функций;  посмотрите,
например, на тип complex, описаннчй в .

     6.3.1 Конструкторы

  Альтенативу  использованию   нескольких  функций  (перегруженных)
составлет  описание   конструктора,  который  по  заданному  double
создает complex. Например:

  class complex {
      // ...
      complex(double r) { re=r; im=0; }
  };

Конструктор, требующий только один параметр, необязательно вызывать
явно:

  complex z1 = complex(23);
  complex z2 = 23;

И z1, и z2 будут инициализированы вызовом complex(23).

                             - стр 181 -

  Конструктор -  это предписание,  как создавать  значение  данного
типа. Когда  требуется значение  типа, и когда такое значение может
быть создано  конструктором, тогда,  если такое значение дается для
присваивания, вызывается конструктор. Например, класс complex можно
было бы описать так:

  class complex {
      double re, im;
  public:
      complex(double r, double i = 0) { re=r; im=i; }

      friend complex operator+(complex, complex);
      friend complex operator*(complex, complex);
  };

и действия,  в которые  будут входить  переменные complex  и  целые
константы,   стали    бы   допустимы.    Целая   константа    будет
интерпретироваться как  complex с  нулевой мнимой частью. Например,
a=b*2 означает:

  a=operator*( b, complex( double(2), double(0) ) )

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

     6.3.2 Операции Преобразования

  Использование  конструктора   для  задания   преобразования  типа
является удобным,  но  имеет  следствия,  которые  могут  оказаться
нежелательными:
  [1]  Не  может  быть  неявного  преобразования  из  определенного
     пользователем типа  в основной тип (поскольку основные типы не
     являются классами);
  [2] Невозможно  задать преобразование из нового типа в старый, не
     изменяя описание старого; и
  [3] Невозможно  иметь конструктор с одним параметром, не имея при
     этом преобразования.
  Последнее не  является серьезной  проблемой, а  с  первыми  двумя
можно   справиться,   определив   для   исходного   типа   операцию
преобразования. Функция  член X::operator  T(), где  T -  имя типа,
определяет преобразование  из X в T. Например, можно определить тип
tiny (крошечный),  который может  иметь значение только в диапазоне
0...63,  но   все  равно  может  свободно  сочетаться  в  целыми  в
арифметических операциях:

                             - стр 182 -

  class tiny {
      char v;
      int assign(int i)
      { return v = (i&~63) ? (error("ошибка диапазона"),0) : i; }
  public:
      tiny(int i)            { assign(i); }
      tiny(tiny& i)          { v = t.v; }
      int operator=(tiny& i) { return v = t.v; }
      int operator=(int i)   { return assign(i); }
      operator int()         { return v; }
  }

Диапазон значения  проверяется всегда,  когда tiny инициализируется
int, и  всегда,  когда  ему  присваивается  int.  Одно  tiny  может
присваиваться  другому  без  проверки  диапазона.  Чтобы  разрешить
выполнять над переменными tiny обычные целые операции, определяется
tiny::operator int(), неявное преобразование из int в tiny. Всегда,
когда в том месте, где требуется int, появляется tiny, используется
соответствующее ему int. Например:

  void main()
  {
      tiny c1 = 2;
      tiny c2 = 62;
      tiny c3 = c2 - c1;  // c3 = 60
      tiny c4 = c3;       // нет проверки диапазона (необязательна)
      int i = c1 + c2;    // i = 64
      c1 = c2 + 2 * c1;   // ошибка диапазона: c1 = 0 (а не 66)
      c2 = c1 -i;         // ошибка диапазона: c2 = 0
      c3 = c2;            // нет проверки диапазона (необязательна)
  }

  Тип вектор  из tiny  может оказаться более полезным, поскольку он
экономит пространство.  Чтобы сделать  этот  тип  более  удобным  в
обращении, можно использовать операцию индексирования.
  Другое применение  определяемых  операций  преобразования  -  это
типы,  которые   предоставляют  нестандартные  представления  чисел
(арифметика по  основанию 100,  арифметика с  фиксированной точкой,
двоично-десятичное  представление   и  т.п.).   При   этом   обычно
переопределяются такие операции, как + и *.
  Функции преобразования  оказываются особенно полезными для работы
со структурами  данных, когда  чтение  (реалоизованное  посредством
операции преобразования)  тривиально, в то время как присваивание и
инициализация заметно более сложны.
  Типы istream и ostream опираются на функцию преобразования, чтобы
сделать возможными такие операторы, как

  while (cin>>x) cout<>x выше возвращает istream&. Это значение неявно
преобразуется к  значению, которое  указывает состояние  cin, а уже
это значение  может  проверяться  оператором  while  (см.  #8.4.2).
Однако определять  преобразование из  оного типа  в другой так, что
при этом теряется информация, обычно не стоит.

                             - стр 183 -

     6.3.3 Неоднозначности

  Присваивание  объекту   (или  инициализация   объекта)  класса  X
является допустимым,  если или  присваиваемое значение  является X,
или существует  единственное преобразование присваиваемого значения
в тип X.
  В некоторых случаях значение нужного типа может сконструироваться
с  помощью   нескольких  применений   конструкторов  или   операций
преобразования. Это  должно делаться  явно;  допустим  только  один
уровень неявных  преобразований, определенных пользователем. Иногда
значение нужного  типа может  быть сконструировано  более чем одним
способом. Такие случаи являются недопустимыми. Например:

  class x { /* ... */ x(int); x(char*); };
  class y { /* ... */ y(int); };
  class z { /* ... */ z(x); };

  overload f;
  x f(x);
  y f(y);

  z g(z);

  f(1);         // недопустимо: неоднозначность f(x(1)) или f(y(1))
  f(x(1));
  f(y(1));
  g("asdf");     // недопустимо: g(z(x("asdf"))) не пробуется
  g(z("asdf"));

  Определенные пользователем  преобразования рассматриваются только
в том случае, если без них вызов разрешить нельзя. Например:

  class x { /* ... */ x(int); }
  overload h(double), h(x);
  h(1);

Вызов мог  бы быть проинтерпретирован или как h(double(1)), или как
h(x(1)), и  был бы  недупустим по правилу единственности. Но превая
интерпретация использует  только стандартное  преобразование и  она
будет выбрана по правилам, приведеным в #4.6.7.
Правила  преобразования   не  являются   ни  самыми   простыми  для
реализации и документации, ни наиболее общими из тех, которые можно
было   бы    разработать.   Возьмем    требование    единственности
преобразования.  Более   общий  подход   разрешил  бы   компилятору
применять любое  преобразование, которое  он  сможет  найти;  таким
образом,   не   нужно   было   бы   рассматривать   все   возможные
преобразования перед  тем, как  объявить  выражение  допустимым.  К
сожалению, это  означало бы,  что смысл  программы зависит от того,
Предыдущая страница Следующая страница
1 ... 21 22 23 24 25 26 27  28 29 30 31 32 33 34 ... 50
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 
Комментарии (4)

Реклама