Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
билеты информатика.rtf
Скачиваний:
39
Добавлен:
02.05.2015
Размер:
6.9 Mб
Скачать

Пе­ре­груз­ка опе­ра­ции за­пя­тая

Мы по­дроб­но изу­чи­ли ис­поль­зо­ва­ние опе­ра­ции за­пя­тая, и по­ка об­на­ру­жи­ли лишь од­но по­лез­ное при­ме­не­ние. К сча­стью, язык про­грам­ми­ро­ва­ния C++ поз­во­ля­ет пе­ре­гру­зить опе­ра­цию за­пя­тая для то­го, что­бы она слу­жи­ла ка­ким угод­но це­лям. Син­так­сис пе­ре­груз­ки оче­ви­ден:

возвр_тип operator,(тип1 арг1, тип2 арг2) { ... }

Сле­ду­ет знать о том, что точ­ка сле­до­ва­ния, опре­де­лён­ная на ме­сте опе­ра­ции за­пя­тая, ис­че­за­ет при пе­ре­груз­ке этой опе­ра­ции. Этот эф­фект вы­зван тем, что за­пя­тая в слу­чае пе­ре­груз­ки пре­вра­ща­ет­ся в обыч­ный вы­зов функ­ции, а по­ря­док вы­чис­ле­ния ар­гу­мен­тов при вы­зо­ве функ­ции не опре­де­лён.

Опе­ра­ция мо­жет быть шаб­лон­ной, а ар­гу­мен­ты мо­гут быть пе­ре­да­ны по ссыл­кам. В об­щем, про­сто­ра для твор­че­ства мно­го (этот про­стор огра­ни­чен толь­ко тем, что хо­тя бы один из ар­гу­мен­тов пе­ре­опре­де­ля­е­мой опе­ра­ции дол­жен быть поль­зо­ватель­ским клас­сом).

Но и тут за­пя­тая по­ка­зы­ва­ет свой сквер­ный ха­рак­тер. Не­осто­рож­но пе­ре­опре­де­лив эту опе­ра­цию для ка­ких-ли­бо ши­ро­ко ис­поль­зуе­мых в про­грам­ме ти­пов дан­ных, мы мо­жем на­ру­шить про­грам­му в со­вер­шен­но не­ожи­дан­ных ме­стах (в том чис­ле мо­гут на­чать не­вер­но ра­бо­тать стан­дарт­ные кон­тей­не­ры).

Рас­смот­рим до­воль­но ча­стое при­ме­не­ние пе­ре­гру­жен­ной опе­ра­ции за­пя­тая — ком­пакт­ную за­пись ини­циа­ли­за­ции кон­тей­не­ров. На­при­мер, вме­сто та­ко­го ко­да:

vector<int> c;c.push_back(10); c.push_back(20); c.push_back(30); c.push_back(40);

по­яв­ля­ет­ся воз­мож­ность пи­сать та­кой код:

vector<int> c;c << 10, 20, 30, 40;

До­бить­ся опи­сан­ной функ­цио­наль­но­сти мож­но дву­мя спо­со­ба­ми:

  1. Сде­лать так, что­бы обе опе­ра­ции (за­пя­тая и <<) до­бав­ля­ли эле­мент (пра­вый ар­гу­мент) в кон­тей­нер (ле­вый ар­гу­мент), и воз­вра­ща­ли ссыл­ку на кон­тей­нер. По при­чи­не, опи­сан­ной вы­ше, это пло­хой спо­соб, так как опе­ра­ция за­пя­тая нач­нёт сра­ба­ты­вать в не­ожи­дан­ных ме­стах про­грам­мы. Да и смыс­ла не бу­дет в за­пя­той, ибо мож­но бу­дет пи­сать так: c << 10 << 20 << 30 << 40;

  2. Сде­лать так, что­бы опе­ра­ция << воз­вра­ща­ла объ­ект спе­ци­аль­но­го клас­са, и опре­де­лить за­пя­тую имен­но для это­го клас­са.

Реа­ли­зу­ем вто­рой ва­ри­ант. Пре­жде все­го, со­зда­дим шаб­лон­ный класс CommaInserter, ко­то­рый со­дер­жит ссыл­ку на кон­тей­нер. При кон­струи­ро­ва­нии объ­ект это­го клас­са бу­дет за­по­ми­нать ссыл­ку на кон­тей­нер и до­бав­лять в не­го пер­вый эле­мент. Кро­ме то­го, CommaInserter бу­дет иметь соб­ствен­ную опе­ра­цию за­пя­тая, ко­то­рая бу­дет до­бав­лять оче­ред­ной эле­мент:

template<class C> class CommaInserter{private: C &container;public:    template<class V> inline CommaInserter(C &container, V const &value):        container(container) { container.insert(container.end(), value)}    template<class V> inline CommaInserter<C> operator,(V const &value)        { container.insert(container.end(), value); return *this; }};

Те­перь до­опре­де­лим опе­ра­ции << и < для то­го, что­бы они со­зда­ва­ли эк­зем­пляр CommaInserter. Вто­рая опе­ра­ция от­ли­ча­ет­ся от пер­вой тем, что вна­ча­ле очи­ща­ет кон­тей­нер:

template<class C, class V>    inline CommaInserter<C> operator<<(C &container, V const &value){ //Начинает добавление элементов в контейнер, создавая CommaInserter    return CommaInserter<C>(container, value);}template<class C, class V>    inline CommaInserter<C> operator<(C &container, V const &value){ //Очищает контейнер и начинает добавление элементов    container.clear();    return CommaInserter<C>(container, value);}

Пред­став­лен­ные опе­ра­ции << и < для ком­пи­ля­то­ра име­ют ми­ни­маль­ный при­о­ри­тет (при­о­ри­тет не в смыс­ле по­ряд­ка вы­пол­не­ния опе­ра­ций, а в смыс­ле то­го, ка­кая имен­но реа­ли­за­ция опе­ра­ции бу­дет вы­бра­на ком­пи­ля­то­ром), так как эти опе­ра­ции шаб­лон­ные, и все ар­гу­мен­ты у них шаб­лон­ные. По­это­му не сто­ит пе­ре­жи­вать, что опе­ра­ции сра­бо­та­ют в «лиш­них» ме­стах: ес­ли уж про­грам­мист пи­шет << при­ме­ни­тель­но к ка­ко­му-ли­бо объ­ек­ту, то про­грам­мист яв­но хо­чет вы­пол­нить с объ­ек­том ка­кое-ли­бо дей­ствие (а не про­сто раз­де­лить два дей­ствия, как в слу­чае с за­пя­той).

По­иг­ра­ем­ся с на­ши­ми но­вы­ми опе­ра­ци­я­ми:

//Вектор из стандартной библиотеки (#include <vector>)vector<int> v; //Пустой векторv << 1, 2, 3;  //Добавляем элементы: [1, 2, 3]v << 2, 3, 4;  //Добавляем ещё элементы: [1, 2, 3, 2, 3, 4]v < 3, 4, 5;   //Очищаем вектор и добавляем элементы: [3, 4, 5]//Множество из стандартной библиотеки (#include <set>)set<int> s;s << 3, 2, 1, 3; //Добавляем элементы в множество. Множество всегда                 //  отсортировано, и не содержит повторений,                 //  поэтому s = {1, 2, 3}s << 2, 5; //Ещё пара элементов: s = {1, 2, 3, 5}s < 1, 2;  //Очищаем и добавляем: s = {1, 2}

Хо­чет­ся ве­рить, что пред­став­лен­ные опе­ра­ции бу­дут ра­бо­тать со все­ми кон­тей­не­ра­ми из стан­дарт­ной биб­лио­те­ки и с боль­шин­ством поль­зо­ватель