Следующий пост -

Перегрузка операций в С++

Рубрика: C++, Дата: 13 March, 2013, Автор:
Tags:

Здарова господа!

В данном посте мы попытаемся разобраться что такое перегрузка операций в С++. Я новичок в этой теме и поэтому после прочтения 11 главы и книги Страуструпа С++, можно сказать ничего не понял. Конечно понял кое, что но хотелось бы все по полочкам разложить и всему дать свое определение. От например какие виды операций бывают, что такое унарные операции, бинарные. Какие виды перегрузок бывают. Ну и на многие вопросы дать ответы. Все это мы разберем в данном посте. Читайте далее…..

Для начала скажем, что операции в С++ бывают бинарные и унарные.

Бинарные операции – это операции которые имеют два операнда. Например:

a+b;//бинарный плюс
a-b;//бинарный минус
a*b;//бинарный умножить
a=b;//бинарный присвоить
a+=b;//операция присваивания
a-=b;//операция присваивания
a==b;//операция сравнения
a>b;//операция сравнения
a<=b;//операция сравнения

мы видим, что во всех этих операциях по два операнда a и b.

Унарные операции – это операции которые имеют по одному операнду. Например:

a++;//инфиксный инкремент
++a;//постфиксный инкремент
--a;//постфиксный декремент
a--;//инфиксный декремент
-a;//унарный минус операция знака.

мы видим, что во всех этих операциях всего по одному операнду а.

Так с типами операций мы разобрались, теперь попробуем перезагрузкой операций. Что это такое? Чтобы это понять нужно сначала разобраться с основными типами например возьмем тип int. Напишем простой пример кода:

int a.operator=(4);//ошибка нельзя для встроенных типов данных
int b.operator=(5);//ошибка нельзя для встроенных типов данных
int c;//ошибка нельзя для встроенных типов данных
c.operator=(b.operator+(a));//ошибка нельзя для встроенных типов данных

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

Попробуем перегрузить бинарные операции для пользовательских типов, например для типа String. Перегрузим операцию ‘+’.

class String
{
        //....
        String operator+(const String &b)
	{
		cout <<"vuzov operator+"<<endl;
	}
        //....
};
//.....
int main()
{
  a+b;//тождественно a.operator+(b);
  a.operator+(b);//тождественно a+b;
  return 0;
}

в данном примере при вызове a+b это короткая форма записи вызова функции a.operator+(b); в данном случае мы должны вернуть ссылку на объект.

Так все казалось бы хорошо, но нам чтобы идти дальше нужно разобраться с ключевым словом this, что это такое в контексте класса вы можете почитать по следующей ссылке: http://www.kselax.ru/2013/03/klyuchevoe-slovo-this-v-s/

Перегрузка операций.

Теперь попробуем перегрузить самые распространенные операции операции. Рассмотрим синтаксис перегрузки. Функция operator+(), operator-(), operator=(), operator+=()  и многие другие. Нам нужно знать, что им передавать и что они должны вернуть. Вообще, то сложненький вопрос. попробуем перегрузить самые популярные операторы.

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

//peregryzka vcex operacii cozdadim klacc zamen9yuchii int (kopiyu int)
#include <iostream>
using std::cout;
using std::endl;
#include <cstdlib>
using std::exit;

class Int2
{
public:
	int val;
	int SIZE;//kolichectvo elementov v maccive
	int massVal[100];//macciv elementov cozdadim na maccive elementvov iz 100 xot9 nyjno
	//bulo cozdat6 yzel

	//konctryktor preobrazovani9 dl9 cozdani9 massivov tipa Int2
	Int2(int a, int b)// a kolichectvo elementov macciva, b znacheni9 elem po ymolchaniyu
	:SIZE(a)
	{
		for(int i=0;i<SIZE;i++)
		{
			massVal[i]=b;
		}
	}

	//konctryktor po ymolchaniyu
	Int2() :val(0)
	{
		cout <<"konctryktor po ymolchaniyu val= "<<val<<endl;
	}

	//konctryktor kopii
	Int2(const Int2& a)
	:val(a.val)
	{
		cout <<"konctryktor kopii val= "<<val<<endl;
	}

	//konctryktor preobrazovaniyu
	Int2(int a)
	:val(a)
	{
		cout <<"Konctryktor preobrazovaniyu val= "<<val<<endl;
	}

	//kak covetyet ctrayctryp v tele klacca cledyet opredel9t6 fynkcii 
	//kotorue men9yut vnytrennee predctavlenie klacca
	//naprimer += ili = , funkcii gde doljen men9tc9 pervui operand
	//vce octal6nue opredel9yutc9 vne tela klacca global6no
	//naprimer operaci9 + , opredel9etc9
	// Int operator+ (const Int& a, const Int& b);
	//voobchem kak fynkci9 ne prinadlejacha9 k klaccy
	//kak vidno operator+ porojdaet novui ob6ekt
	//eto kak bu covet ot gyry, a tak kak vam ydobno

	//=
	Int2& operator=(const Int2& a)
	{
		val=a.val;
		cout <<"operator=() val= "<<val<<endl;
		return *this;//vernyt6 ob6ekt
	}
	//+=
	Int2& operator+=(const Int2& a)
	{
		val=val+a.val;
		cout <<"operator+=() val= "<<val<<endl;
		return *this;
	}
	//-= //Int2& mu procto peredaem cculky na odin i totge ob6ekt
	     //Int2 mu vuzuvaem konctryktor kopii Int2(Int2& *this)
	Int2& operator-=(const Int2& a)
	{
		val=val-a.val;
		cout <<"operator-=() val= "<<val<<endl;
		return *this;
	}
	//*=
	Int2& operator*=(const Int2& a)
	{
		val=val*a.val;
		cout <<"operator*=() val= "<<val<<endl;
		return *this;
	}
	// /=
	Int2& operator/=(const Int2& a)
	{
		if(a.val==0)
		{
			//ecli pravil6no to cdec6 nyjno cozdat6
			//klacc dl9 icklyuchenii i
			//generirovat6 icklyucheni9
			cout <<"delenie na 0 a.val= "<<a.val<<endl;
			exit(1);//avariinoe zaverchenie
		}
		val=val/a.val;
		cout <<"operator/=() val= "<<val<<endl;
		return *this;
	}

	//peregryzka postfikcnux i prefikcnux ++ i --

	//prefixcnui ++ nyjno crazy izmenit6
	Int2& operator++()
	{
		++val;
		cout <<"prefixcnui ++ vuzov val= "<<val<<endl;
		return *this;//vernyt6 ob6ekt
	}
	//postfikcnui ++ izmenit6 i vernyt6 ne izmenennoe znachenie
	Int2 operator++(int)
	{
		cout <<"postfixcnui ++ vuzov val= "<<val<<endl;
		return val++;
	}
	//prefixnui -- crazy men9em i retur cculky
	Int2& operator--()
	{
		--val;
		cout <<"prefixnui -- vuzov val= "<<val<<endl;
		return *this;//vernyt6 ob6ekt
	}
	//postfixnui -- return kopiyu, potom men9em
	Int2 operator--(int)
	{
		cout <<"postfixnui -- vuzov val= "<<val<<endl;
		return val--;
	}

	//peregryzka indekca [] dl9 ne const elementov
	//element v [] mojet but6 lyubogo tipa v dannom clychae int
	int& operator[](int a)
	{
		cout <<"vuzov [] "<<endl;
		return massVal[a];
	}

};

//opredelenie drygix fynkcii dl9 klacca Int2 global6no

//operatoru cravneni9 ==, !=, <, >, <=, >= boolevue
//==
bool operator==(const Int2& a, const Int2& b)
{
	return a.val==b.val;
}
//!=
bool operator!=(const Int2& a, const Int2& b)
{
	return !operator==(a,b);
}
//<
bool operator<(const Int2& a, const Int2& b)
{
	return a.val<b.val;
}
//>
bool operator>(const Int2& a, const Int2& b)
{
	return !operator<(a,b);
}
//<=
bool operator<=(const Int2& a, const Int2& b)
{
	return a.val<=b.val;
}
//>=
bool operator>=(const Int2& a, const Int2& b)
{
	return a.val>=b.val;
}

//operatoru +,-,*,/
//+
Int2 operator+(const Int2& a, const Int2& b)
{
	return a.val+b.val; //vuzovetc9 conctryktor kopirovani9
}
//-
Int2 operator-(const Int2& a, const Int2& b)
{
	return a.val-b.val;
}
//*
Int2 operator*(const Int2& a, const Int2& b)
{
	return a.val*b.val;
}
// /
Int2 operator/(const Int2& a, const Int2& b)
{
	return a.val/b.val;
}

int main()
{
	//protectim konctryktora
	Int2 a;//vuzov k. po ymolchaniyu
	Int2 b(a);//vuzov k. kopii
	Int2 c(4);//vuzov k. preobrazovani9
	c=a;//vuzov c.operator=(a);

	c+=b;//vuzov c.operator+=(b);
	c+=5;//vuzov c.operator+=(Int2(5));
	c.operator+=(Int2(5));

	c-=a;//vuzov c.operator-=(a);
	c-=1;//vuzov c.operator-=(Int2(1));
	c.operator-=(Int2(4));//v c octanetc9 5

	c*=a;//vuzov c.operator*=(a);
	c.operator=(Int2(1));
	c*=2;
	c.operator*=(Int2(2));

	c/=1;//vuzov c.operator/=(Int2(1));
	a=1;//vuzov a.operator=(Int2(1));
	c/=a;//vuzov c.operator/=(a);
	a=2;//vuzov a.operator=(Int2(2));
	c/=a;//vuzov a.operator/=(a); //2
	//c/=0;//vuzov a.operator/=(Int2(0));

	if(a==1) cout <<"a ravno 1 a.val= "<<a.val<<endl;
	else cout <<"a ne ravno 1 a.val= "<<a.val<<endl;

	if(a==2) cout <<"a ravno 1 a.val= "<<a.val<<endl;
	else cout <<"a ne ravno 1 a.val= "<<a.val<<endl;

	b=2;
	if(a==b) cout <<"a ravno b"<<endl;
	else cout <<"a ne ravno b"<<endl;

	//operacii cravneni9
	//!=
	if(a!=b) cout <<"a ne ravno b"<<endl;
	else cout <<"a ravno b"<<endl;
	//<
	if(a<b) cout <<"a < b"<<endl;
	else cout <<"a ne < b"<<endl;
	a=1;
	if(a<b) cout <<"a < b"<<endl;
	else cout <<"a ne < b"<<endl;
	//>
	if(b>a) cout <<"b > a"<<endl;
	//>=
	if(b>=a) cout <<"b >= a"<<endl;
	b=a;
	if(b>=a) cout <<"b >= a"<<endl;
	if(b<=a) cout <<"b <= a"<<endl;
	b=0;
	if(b<=a) cout <<"b <= a"<<endl;

	//operacii +, -, *, /
	//+
	a+b;//operator+(a,b)
	//c=a+b;//c.operator=(operator+(a,b));
	c.operator=(operator+(a,b));
	cout <<c.val<<endl;
	//c=c+a;//c.operator=(Int2(operator+(c,a)));
	c.operator=(operator+(c,a));
	cout <<c.val<<endl;
	//c=c+a+3;//c.operator=(Int2(operator+(operator+(c,a),Int2(3))));
	c.operator=(Int2(operator+(operator+(c,a),Int2(3))));
	cout <<c.val<<endl;

	//-
	//c=c-a;//c.operator=(operator-(c,a))
	c.operator=(operator-(c,a));
	cout <<c.val<<endl;
	//*
	//c=c*2;//c.operator=(operator*(c,Int2(2)));
	c.operator=(operator*(c,Int2(2)));
	cout <<c.val<<endl;
	// /
	//c=c/2;//c.operator=(operator/(c,Int2(2)));
	c.operator=(operator/(c,2));
	cout <<c.val<<endl;

	//inkrement dekrement
	++c;
	//c++;
	c.operator++(0);
	cout <<c.val<<' '<<a.val<<endl;//7
	//b=c + a++;// b.operator=(c,Int2(a.operator++(0)))
	b.operator=(operator+(c, a.operator++(0)));
	cout <<b.val<<' '<<a.val<<endl;//8 2

	--c;
	cout <<c.val<<endl;
	c--;
	cout <<c.val<<endl;
	b=c + a--;//7

	cout <<b.val<<' '<<a.val<<endl;//7 1

	//cozdaem macciv
	Int2 k(5,4); //5 elementov znachenie 4
	int p=0;
	cout <<"p= "<<p<<endl;
	//p=k[3];//p=k.operator[](3)
	p=k.operator[](3);
	cout <<"p= "<<p<<endl;
	//k[3]=2;//k.operator[](3)=2;
	k.operator[](3)=2;
	cout <<"k[3]= "<<k[3]<<endl;
	cout <<"k[2]= "<<k[2]<<endl;
	k[2]=k[3];
	cout <<"k[2]= "<<k[2]<<endl;
	k[2]=5;
	cout <<"k[3]= "<<k[3]<<" k[2]= "<<k[2]<<endl;

	return 0;
}

Здесь я хочу внимание ваше обратить на перегрузку постфиксных и префиксных операций, в частности на вызов функций. Нарпимер Int2 operator–(int) это постфиксный вызов, потому, что функция принимает как бы int, это специально сделано для того чтобы их различать. Int2 operator–() это будет префиксный вызов.

Еще скажу немного за перегрузку индекса. Например в данном случае мы создали функцию int& operator[](int a), это функция для двух вариантов, для записи и для чтения вызывается. Еще можно для чтения константных объектов создать функцию int& operator[](int a) const; но я не создавал. Можно еще сказать, что с скобках может быть любое значение не токо инт, но и string, это позволяет создавать, классы для работы с ассоциативными массивами. Аргумент функции int& operator[](int a) целое число, это значит, что при вызове допустим b[3] вызывается b.operator[](3), а если мы б определили функцию int& operator[](string a), то при вызове b[“hel”] вызывалась функция b.operator[](“hel”); от такие от дела малята. 🙂 .

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

Все разобрал от код:

#include <iostream>
using std::cout;
using std::endl;

class Cref //cculka na s[i]
{	
public:
	char k;//cimvol

	//konctryktor po ymolchaniyu
	Cref():k(' ')
	{
		cout <<"k po ymolchaniyu"<<endl;
	}//inicializiryem k po ymolchaniyu probel6num cimvolom 

	//konctryktor preobrazovani9
	Cref(char a):k(a)
	{
		cout <<"k preobrazovani9"<<endl;
	};//inicializiryem k cimvolom a

	//konctryktor kopii
	Cref(Cref& a):k(a.k)
	{
		cout <<"k kopii"<<endl;
	};//inicializiryem k cimvolom a.k;

	//peregryzim operator= dl9 pricvaivani9 ob6ektov
	Cref& operator=(const Cref& a)
	{
		k=a.k;
		return *this;//vernyt6 ob6ekt
	}

	Cref& operator=(const char a)
	{
		k=a;
		return *this;//vernyt6 ob6ekt
	}

	//peregryzim operator char() const;
	operator char() const
	{
		cout <<"vuzov operator char()"<<endl;
		return k;
	}
};

int main()
{
	//proverka konctryktorov
	Cref a;//vuzov k po ymolchaniyu
	cout <<"a.k= "<<a.k<<endl;
	a='3';
	cout <<"a.k= "<<a.k<<endl;
	//char b=a;
	char b=a.operator char();
	cout <<"b= "<<b<<endl;

	return 0;
}

Могу сказать, что как мы видим operator char() здесь мы перегружаем и как мы видим, он вызывается неявно при присваивании элементу типа char b элемента типа Cref, это как бы средство для преобразования, оно как бы помогает объединить типы, можно было б сделать и для неявного преобразования в int. Короче неплохая штука. А перегружается как мы видем очень просто operator char() и все больше ничего не нужно, также и int перегружается operator int() и опять все, да и вызывается она вроде как без параметров, и делать в ней можно все, что угодно, возвращаем как правило тип в который мы преобразуем, для которого она вызвана неявно.

От еще перегрузка операторов cout и cin описывается можете зайти почитать если интересно: http://www.kselax.ru/2013/03/peregruzka-operatorov-cout-i-cin/

Да и все на этом я думаю закруглится мб кода нибудь добавлю перегрзку функций, перегрузку new, delete хз, поживем увидим.

rss