• C++ 运算符重载


    个人笔记,仅供复习

    1.运算符重载的基本概念

    1.1 背景:运算符只能用于基本数据类型进行运算,不能用于对象之间为了满足对象之间可以通过运算符进行运算。

    1.2 概念:对已有的运算符(C++中预定义的运算符)赋予多重含义,使同一运算符作用于不同类型的数据时导致不同的行为

    1.3 目的:扩展C++中提供的运算符的适用范围,使之能作用于对象。

    1.4 形式:

    • 运算符重载实质是函数重载。
    • 可以重载为普通函数,也可以重载为成员函数。
    • 把含运算符的表达式转换为对运算符函数的调用
    • 把运算符的操作数转换为运算符函数的参数
    • 运算符可以被多次重载,根据实参的类型决定调用哪个运算符函数。

                返回值类型  operator 运算符 (形参表)

                {

                    ...    ...

                }

    1.5 运算符重载实例:

    #include<iostream>
    using namespace std;
    class Complex
    {
    	public:
    		double real,imag;
    		Complex(double r = 0.0,double i = 0.0):real(r),imag(i){	}
    		Complex operator - (const Complex &c);
    };
    Complex operator + (const Complex &a ,const Complex &b){
    	return Complex(a.real+b.real,a.imag+b.imag);
    }
    Complex Complex::operator -(const Complex &c){
    	return Complex(real-c.real,imag-c.imag);//返回一个临时对象
    } 
    int main()
    {
    	Complex a(4,4),b(1,1),c;
    	c = a+b;
    	cout << c.real << "," << c.imag << endl;
    	cout << (a-b).real << "," << (a-b).imag << endl;
    	return 0;
    }

    其中 c = a+b;等价于 c = operator + (a,b);

            a-b;       等价于a.operator-(b);

    注:重载为成员函数时,参数个数为运算符数目减一;

           重载为普通函数时,参数个数为运算符数目。

    2.赋值运算符的重载

    2.1 背景:有时希望赋值运算符两边的类型可以不匹配,比如,把一个int类型变量赋值给一个Complex对象,或把一个char * 类型的字符串赋值给一个字符串对象,此时就需要重载运算符“=”。

    注:赋值运算符“=”只能重载为成员函数。

    2.2 代码实例:

    #include<iostream>
    #include<cstring>
    using namespace std;
    class String{
    	private:
    		char *str;
    	public:
    		String ():str(new char[1]) {str[0] = 0;}
    		const char * c_str() {	return str;	}
    		String & operator = (const char * s);
    		~String( ) { delete [] str; }
    };
    String & String::operator =(const char*s){
    	delete [] str;
    	str = new char[strlen(s)+1];
    	strcpy(str,s);
    	return *this;
    }
    int main()
    {
    	String s;
    	s = "Good Luck," ; //等价于 s.operator=("Good Luck,");
    	cout << s.c_str() << endl;
    	// String s2 = "hello!"; //这条语句要是不注释掉就会出错
    	s = "Shenzhou 8!"; //等价于 s.operator=("Shenzhou 8!");
    	cout << s.c_str() << endl;
    	return 0;
    }

    2.3 细节处理:    S1 = "this",S2="that";

                               S1 = S2;

    • 如不定义自己的赋值运算符,那么S1=S2实际上导致 S1.str和 S2.str指向同一地方。
    • 如果S1对象消亡,析构函数将释放 S1.str指向的空间,则S2消亡时还要释放一次,不妥。
    • 另外,如果执行 S1 = "other";会导致S2.str指向的地方被delete.
    • 因此要在 class String里添加成员函数:
    	String & operator = (const String & s) {
    			delete [] str;
    			str = new char[strlen( s.str)+1];
    			strcpy( str,s.str);
    			return * this;
    		}

    另外如果令S=S,还是会出错。

    	String & operator = (const String & s) {
    			if(this == &s)
    				return *this;
    			delete [] str;
    			str = new char[strlen( s.str)+1];
    			strcpy( str,s.str);
    			return * this;
    		}

    2.4 对返回值类型的讨论:对运算符进行重载的时候,好的风格是应该尽量保留运算符原本的特性。

    考虑 a = b = c 和 (a = b) = c;

    分别等价于: a.operator = (b.operator(c)) 和 (a.operator=(b)).operator=(c).

    3.运算符重载为友元

    3.1 背景:一般情况下,将运算符重载为类的成员函数,是较好的选择。但有时,重载为成员函数不能满足使用要求,重载为普通函数,又不能访问类的私有成员,所以需要将运算符重载为友元。

    4.可变长数组类的实现

    4.1 编写一个类,使之能如下使用:

    int main() { //要编写可变长整型数组类,使之能如下使用:
    	CArray a; //开始里的数组是空的
    	for( int i = 0;i < 5;++i)
    	a.push_back(i);
    	CArray a2,a3;
    	a2 = a;
    	for( int i = 0; i < a.length(); ++i )
    	cout << a2[i] << " " ;
    	a2 = a3; //a2是空的
    	for( int i = 0; i < a2.length(); ++i ) //a2.length()返回0
    	cout << a2[i] << " ";
    	cout << endl;
    	a[3] = 100;
    	CArray a4(a);
    	for( int i = 0; i < a4.length(); ++i )
    	cout << a4[i] << " ";
    	return 0;
    }

    4.2 代码实例:

    #include<iostream>
    #include<cstring>
    using namespace std;
    class CArray{
    	private:
    		int *p;
    		int size;
    	public:
    		void push_back(int i);
    		int length(){return size;}
    		CArray():p(NULL),size(0){	}
    		~CArray(){
    			if(p)	delete [] p;
    		}
    		int & operator[](int i);
    		CArray & operator = (const CArray &);
    		CArray(const CArray &);
    };
    void CArray::push_back(int i){
    	if(p){
    		int *tmpp = new int[size+1];
    		memcpy(tmpp,p,sizeof(int)*size);
    		delete [] p;
    		p = tmpp;
    	}
    	else 
    		p = new int[1];
    	p[size++] = i;
    }
    int & CArray::operator[](int i){
    	return *(p+i);
    }
    CArray & CArray::operator = (const CArray & c){
    	if(p == c.p)	return *this;
    	if(c.p == NULL){
    		if(p)	delete [] p;
    		size = 0;
    		p = NULL;
    		return *this;
    	}
    	if(size < c.size){
    		if(p)	delete [] p;
    		p = new int[c.size];
    	}
    	size = c.size;
    	memcpy(p,c.p,sizeof(int)*size);
    	return *this;
    }
    CArray::CArray(const CArray & c){
    	if(!c.p){
    		p = NULL;
    		size = 0;
    		return;
    	} 
    	p = new int[c.size];
    	memcpy(p,c.p,sizeof(int)*c.size);
    	size = c.size;
    }
    int main() { //要编写可变长整型数组类,使之能如下使用:
    	CArray a; //开始里的数组是空的
    	for( int i = 0;i < 5;++i)
    	a.push_back(i);
    	CArray a2,a3;
    	a2 = a;
    	for( int i = 0; i < a.length(); ++i )
    	cout << a2[i] << " " ;
    	a2 = a3; //a2是空的
    	for( int i = 0; i < a2.length(); ++i ) //a2.length()返回0
    	cout << a2[i] << " ";
    	cout << endl;
    	a[3] = 100;
    	CArray a4(a);
    	for( int i = 0; i < a4.length(); ++i )
    	cout << a4[i] << " ";
    	return 0;
    }

    5.流插入运算符和流提取运算符的重载

    5.1 概念:cout << 5 << “this”;本质上的函数调用是cout.operator<<(5).operator<<(“this”);

    5.2 代码实例:假定c是Complex复数类的对象,现在希望写“cout << c;”,就能以“a+bi”的形式输出c的值,写“cin>>c;"就能从键盘接受“a+bi”形式的输入,并且使得c.real = a,c.imag = b。

    #include<iostream>
    #include<cstdlib>
    #include<string>
    using namespace std;
    class Complex{
    	double real,imag;
    	public:
    		Complex(double real = 0,double imag = 0):real(real),imag(imag){	}
    		friend ostream & operator << (ostream &o, const Complex & a);
    		friend istream & operator >> (istream &is,Complex & c);
    };
    ostream & operator << (ostream &o, const Complex & a){//这里是对ostream的引用,如果不这样写编译不通过
    	o << a.real <<"+"<<a.imag<<"i"<<endl;
    	return o;
    }
    istream & operator >> (istream &is,Complex & c){//返回一个istream的引用
    	string s;
    	is >> s; //将"a+bi"作为字符串读入, “a+bi” 中间不能有空格
    	int pos = s.find("+",0);
    	string sTmp = s.substr(0,pos); //分离出代表实部的字符串
    	c.real = atof(sTmp.c_str()); //atof库函数能将const char*指针指向的内容转换成 float
    	sTmp = s.substr(pos+1, s.length()-pos-2); //分离出代表虚部的字符串
    	c.imag = atof(sTmp.c_str());
    	return is;
    }
    int main()
    {
    	Complex a(1.2,1.3),b(1.3,1.2),c;
    	cin >> a >> b;
    	cout << a << b;
    	return 0;
    }

    注:由于istream类和ostream类都已经定义好,所以<<和>>运算符都只能重载为全局函数,为了防止全局函数无法访问私有成员,可以将其声明为友元

    6.类型转换运算符的重载

    6.1 代码实例:

    #include <iostream>
    using namespace std;
    class Complex
    {
    	double real,imag;
    	public:
    		Complex(double r=0,double i=0):real(r),imag(i) { };
    		operator double () { return real; }
    		//重载强制类型转换运算符 double
    };
    int main()
    {
    	Complex c(1.2,3.4);
    	cout << (double)c << endl; //输出 1.2
    	double n = 2 + c; //等价于 double n=2+c.operator double()
    	cout << n; //输出 3.2
    	return 0;
    }

    上面倒数第三行可以看出如果对类型转换运算符进行了重载,那么编译器会自动的对对象进行合理的强制类型转换

    7.自增自减运算符的重载

    7.1 概念:自增自减运算符有前置和后置之分,为了区分所重载的是前置运算符还是后置运算符,c++规定:

    • 前置运算符作为一元运算符重载:

            重载为成员函数:

                T & operator++();

                T & operator--();

            重载为全局函数:

                T1 & operator++(T2);

                T1 & operator—(T2);

    • 后置运算符作为二元运算符重载,多写一个没用的参数:

            重载为成员函数:

                T operator++(int);

                T operator--(int);

            重载为全局函数:

                T1 operator++(T2,int );

                T1 operator—( T2,int);

    注:在没有后置运算符重载而有前置运算符重载的情况下,在vs中,obj++也调用前置重载,而dev中obj++则出错。

    7.2 代码实例:

    #include<iostream>
    using namespace std;
    class CDemo{
    	private:
    		int n;
    	public:
    		CDemo(int i = 0):n(i) {}
    		CDemo & operator ++ ();
    		CDemo operator ++(int);
    		friend CDemo & operator --(CDemo &);
    		friend CDemo operator --(CDemo &,int); 
    		operator int () {return n;}
    };
    CDemo &CDemo::operator ++ ( ){//前置++ 
    	n++;
    	return *this;
    }//++s即为s.operator++() 
    CDemo CDemo::operator++( int k )
    { //后置 ++
    	CDemo tmp(*this); //记录修改前的对象
    	n ++;
    	return tmp; //返回修改前的对象
    } // s++即为: s.operator++(0);
    CDemo & operator -- (CDemo & a){
    	a.n--;
    	return a;
    }
    CDemo operator -- (CDemo & a,int k){
    	CDemo tmp(a);
    	a.n--;
    	return tmp; 
    }
    int main()
    {
    	CDemo d(5);
    	cout << (d++ ) << ","; //等价于 d.operator++(0);
    	cout << d << ",";
    	cout << (++d) << ","; //等价于 d.operator++();
    	cout << d << endl;
    	cout << (d-- ) << ","; //等价于 operator--(d,0);
    	cout << d << ",";
    	cout << (--d) << ","; //等价于 operator--(d);
    	cout << d << endl;
    	return 0;
    }

    其中,当自增运算符前置时,要求返回一个改变后的对象,所以要用该对象的引用,返回改变后的对象。当自增运算符后置时,要求改变当前对象的值并返回改变之前的值,所以要将该对象临时保存改变后返回临时对象

    8.注意事项:

    • C++不允许定义新的运算符 ;
    • 重载后运算符的含义应该符合日常习惯;
    • 运算符重载不改变运算符的优先级
    • 以下运算符不能被重载:“.”、“.*”、“::”、“?:”、sizeof;
    • 重载运算符()、[]、->或者赋值运算符=时,运算符重载函数必须声明为类的成员函数
  • 相关阅读:
    Kafka架构概述
    Logstash使用进阶篇
    Logstash使用快速入门
    Ubuntu环境部署Logstash实战案例
    Nginx代理Kibana并实现登录认证实战案例
    Ubuntu环境部署Kibana实战案例
    Idea打包JAR包图解
    Wormhole部署实战案例
    编译Wormhole实战篇
    Wormhole的核心概念
  • 原文地址:https://www.cnblogs.com/long98/p/10352249.html
Copyright © 2020-2023  润新知