C++ 运算符重载:成员、非成员函数重载

一、运算符重载

1、背景

  • C++中标准运算符(如+、—、*、/、<、>等)的操作对象只能是基本数据类型。但对于用户自定义类型(例如类),也需要类似的运算操作。这时就必须在C++中重新定义这些运算符,使它能够用于特定类型执行特定的操作。
  • 运算符重载,本质上是函数重载,属于静态多态

2、运算符函数重载的两种形式

1、成员函数重载

1、定义格式
  • 成员函数声明的格式:
函数类型 operator 运算符(参数表);
  • 成员函数定义的格式:
函数类型 类名::operator 运算符(参数表)
 {
 	 函数体;
  }

2、非成员函数重载(友元)

1、定义格式
  • 友元函数声明的格式:
friend 函数类型 operator 运算符(参数表);
  • 友元函数定义的格式:
 函数类型 operator 运算符(参数表)
{
  	函数体;
 }

3、重载原则

  • (1) 除了类属关系运算符"."、成员指针运算符".*"、作用域运算符"::"、sizeof运算符和三目运算符"?:"以外,C++中的所有运算符都可以重载。
  • (2) 重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符。
  • (3) 运算符重载实质上是函数重载,因此编译程序对运算符重载的选择,遵循函数重载的选择原则。
  • (4) 重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构。
  • (5) 运算符重载不能改变该运算符用于内部类型对象的含义。它只能和用户自定义类型的对象一起使用,或者用于用户自定义类型的对象和内部类型的对象混合使用时。
  • (6) 运算时,有数和对象的混合运算时,必须使用友元
  • (7) =,(),[],->不能以友元方式重载
  • (8) 二元运算符中,第一个操作数为非对象时,必须使用友元函数。如输入输出运算符<<和>>
    为什么输出运算符重载不能是一个成员函数?而非得声明为友元?
    原因如下:
 返回值 operator运算符(参数列表){}

重载运算符时,函数声明在类内和类外是有区别的,比方说 + - * / 等需要2个操作数的运算符,当声明在类的外部时,则参数列表为2个参数,第一个参数为运算符左边的操作数,而第二个参数为操作符右边的操作数:如下

classType operator+(classType& left, classType& right);

而当函数声明在类的内部时,即为类的成员函数时,

classType operator+(classType& right );

第一个操作数就是调用该操作符的对象的引用第二个操作数是传进来的参数,所以,如果要重载<<运算符,一般写法是这样的

ostream& operator<<(ostream& os, const classType& obj);

第一个参数是该运算符的第一个操作数,然而,却不是类对象
所以当该类运算符重载时写在类的内部时,又为了访问类内除public外的其它变量或函数,
则应当声明为友元函数:

friend ostream& operator<<(ostream& os, const classType& obj);

所以,为什么有些运算符的重载要声明为友元函数,是类似的。

运算符重载原则表:

运算符建议使用方式
一元运算符成员函数
= ( ) [ ] ->必须是成员函数
+= -= /= *= ^= &= != %= >>= <<=成员函数
所有其它二元运算符, 例如: –,+,*,/友元函数
<< >>友元函数

4、参数和返回值

  • 参数:
    当参数不会被改变,一般按const引用来传递(若是使用成员函数重载,函数也为const).

  • 返回数值:

  1. 如果返回值可能出现在=号左边, 则只能作为左值, 返回非const引用。
  2. 如果返回值只能出现在=号右边, 则只需作为右值, 返回const型引用或者const型值。
  3. 如果返回值既可能出现在=号左边或者右边, 则其返回值须作为左值, 返回非const引用。

5、成员函数重载

定义格式

<函数类型> operator <运算符>(<参数表>)
    {
     <函数体>
    }

1、双目运算符重载

1、定义

双目运算符(或称二元运算符)是C++中最常用的运算符。双目运算符有两个操作数,通常在运算符的左右两侧,

+,-,*,/,%,<,>,>=,<=,==,!=,<<,>>,&,^,|,&&,||,=

如3+5,a=b,i<10等

注意:
双目运算符重载为类的成员函数时,函数只显式说明一个参数,该形参是运算符的右操作数,因为成员函数用this指针隐式地访问了类的一个对象,它充当了运算符函数最左边的操作数。

2、调用格式
<对象名>.operator <运算符>(<参数>)  //obj1.operator<运算符>(OBJ obj2)
等价于
<对象名><运算符><参数>   //obj1<运算符>obj2
例如  a+b  等价于   a.operator+(b)

2、单目运算符重载

1、定义

单目运算符是指运算所需变量为一个的运算符,即在运算当中只有一个操作数,又叫一元运算符。

逻辑非运算符:!、按位取反运算符:~、自增自减运算符:++, - - 等

2、调用格式
  • 前置单目运算符重载为类的成员函数时,不需要显式说明参数,即函数没有形参。因为成员函数用this指针隐式地访问了类的一个对象,它充当了运算符函数最左边的操作数。
<运算符> obj    
相当于
obj.operator<运算符>();

例如:
++a 相当于 a.operator++();
  • 后置单目运算符重载为类的成员函数时,函数要带有一个整型形参。
obj <运算符>  
相当于
obj.operator<运算符>(int);

例如:
a++ 相当于 a.operator++(int);

6、非成员函数(友元)重载

1、定义

通常我们都将其声明为友元函数,因为大多数时候重载运算符要访问类的私有数据,如果不声明为类的友元函数,而是通过在此函数中调用类的公有函数来访问私有数据会降低性能。所以一般都会设置为类的友元函数,这样我们就可以在此非成员函数中访问类中的数据了。

2、定义形式

声明的格式:

friend 函数类型 operator 运算符(参数表);

定义的格式:

 friend 函数类型 operator 运算符(参数表){
  函数体;
  }
3、调用格式
  1. 双目运算符 B重载后,
obj1 运算符 obj2   等同于   operator   运算符(obj1,obj2 )
  1. 前置单目运算符 B重载后,
 运算符 obj   等同于    operator 运算符(obj )
  1. 后置单目运算符 ++和–重载后,
 obj 运算符  等同于   operator 运算符(obj,0 )

举例

//前置++运算符重载
friend 函数类型 & operator++(类类型 &);
//后置++运算符重载
friend 函数类型 & operator++(类类型 &,int);
4、重载原则:
  • 函数的形参代表依自左至右次序排列的各操作数。
  • 参数个数=原操作数个数(后置++、–除外)
  • 至少应该有一个自定义类型的参数。
  • 后置单目运算符 ++和–的重载函数,形参列表中要增加一个int,但不必写形参名。
  • 如果在运算符的重载函数中需要操作某类对象的私有成员,可以将此函数声明为该类的友元。

7、 +和 -运算符的重载实例

//https://blog.csdn.net/zhuzhaoming1994/article/details/80371779
class Point  
{  
private:  
	int x; 
public:  
	Point(int x1)
	{  	x=x1;}  
	Point(Point& p)   
	{  	x=p.x;}
	const Point operator+(const Point& p);//使用成员函数重载加号运算符
	friend const Point operator-(const Point& p1,const Point& p2);//使用友元函数重载减号运算符
};  
 
const Point Point::operator+(const Point& p)
{
	return Point(x+p.x);
}
 
const Point operator-(const Point& p1,const Point& p2)
{
	return Point(p1.x-p2.x);
}

结果:

Point a(1);  
Point b(2);
a+b;  //正确,调用成员函数
a-b;  //正确,调用友元函数
a+1;  //正确,先调用类型转换函数,把1变成类类型,之后调用成员函数
a-1;  //正确,先调用类型转换函数,把1变成类类型,之后调用友元函数
1+a;  //错误,调用成员函数时,第一个操作数必须是对象,因为第一个操作数还有调用成员函数的功能
1-a;  //正确,先类型转换 后调用友元函数

8、++和–运算符的重载实例

//https://blog.csdn.net/zhuzhaoming1994/article/details/80371779
class Point  
{  
private:  
	int x; 
public:  
	Point(int x1)
	{  	x=x1;}  
	Point& operator++();//成员函数定义自增
	
	/*函数返回临时变量,不能出现在等号的左边(临时变量不能做左值),函数的结果只能做右值,则要返回一个const类型的值*/
	const Point operator++(int x); //后缀可以返回一个const类型的值,这里引入一个虚参数int x,x可以是任意整数。
	friend Point operator--(Point& p);//友元函数定义--
	friend const Point operator--(Point& p,int x);//后缀可以返回一个const类型的值
};  
 
Point Point::operator++()//++obj
{
	x++;
	return *this;
}
const Point Point::operator++(int x)//obj++
{
	Point temp = *this;
	this->x++;
	return temp;
}
Point operator--(Point& p)//--obj
{
	p.x--;
	return p;
         //前缀形式(--obj)重载的时候没有虚参,通过引用返回*this 或 自身引用,也就是返回变化之后的数值
}
const Point operator--(Point& p,int x)//obj--
{
	Point temp = p;
	p.x--;
	return temp;
         // 后缀形式obj--重载的时候有一个int类型的虚参, 返回原状态的拷贝
}
Point a(1);
Point b(2);
a++;//隐式调用成员函数operator++(0),后缀表达式
++a;//隐式调用成员函数operator++(),前缀表达式
b--;//隐式调用友元函数operator--(0),后缀表达式
--b;//隐式调用友元函数operator--(),前缀表达式
cout<<a.operator ++(2);//显式调用成员函数operator ++(2),后缀表达式
cout<<a.operator ++();//显式调用成员函数operator ++(),前缀表达式
cout<<operator --(b,2);//显式调用友元函数operator --(2),后缀表达式
cout<<operator --(b);//显式调用友元函数operator --(),前缀表达式 

9、重载输入输出操作符<< >>实例

class Point  
{  
private:  
	int x; 
public:  
	Point(int x1)
	{  	x=x1;} 
	friend ostream& operator<<(ostream& cout,const Point& p);//使用友元函数重载<<输出运算符
	friend istream& operator>>(istream& cin,Point& p);//使用友元函数重载>>输出运算符
};  
ostream& operator<<(ostream& cout,const Point& p)
{
	cout<<p.x<<endl;
	return cout;
}
istream& operator>>(istream& cin,Point& p)
{
	cin>>p.x;
	return cin;
}
  • 语法:
    重载方式:只能使用友元函数重载 且 使用三个引用&

  • 函数名:

    输出流: operator<<(参数表)
    输入流:operator>>(参数表)
    
  • 参数表:两个参数均用引用&

    输出流: 必须是两个参数:对输出流ostream& 和 对象
                     第一个操作数cout,定义在文件iostream中,是标准类类型ostream的对象的引用。
                     如:ostream& cout,const Point& p
    输入流:必须是两个参数:对输入流ostream& 和 对象
                    第一个操作数是cin,定义在文件iostream,实际上是标准类类型istream的对象的引用
                    如:instream& cin,const Point& p
    
  • 函数调用:

    输出流: 显式调用:cout<<对象
                     隐式调用: operator<<(cout,对象)
    输入流:显式调用:cin>>对象
                     隐式调用: operator>>(cin,对象)
    

返回类型:返回类型固定 + 使用返回函数引用(满足连续输出)

   输出流: 返回ostream&
                    如:ostream& operator<<(ostream& cout,const Point& p)
   输入流:返回:istream&
                    如:istream& operator>>(istream& cin,Point& p)

10、类型转换运算符重载

类型转换函数的作用是将一个类的对象转换成另一类型的数据。(C++中没有返回类型的函数有3个,构造函数、析构函数、类型转换函数。)

  • 1、必须是成员函数,不能是友元函数 ,因为转换的主体是本类的对象。不能作为友元函数或普通函数。
  • 2、没有参数(操作数是什么?)
  • 3、不能指定返回类型(返回值的类型是由函数名中指定的类型名来确定)
  • 4、函数原型:
operator 类型名( )
    {
        实现转换的语句
    }

举例:
类型转换运算符,只要你把XXX对象隐式或者显式转换为T对象时,它都会被自动调用。

//https://www.cnblogs.com/renyuan/p/6555172.html
#include<iostream>  
using namespace std;  
//类型转换运算符重载,只要你把XXX对象隐式或者显式转换为T对象时,它自动被调用  
  
template<class T>  
class Transfer  
{  
public:  
    Transfer(int arg):i(arg){}  
    operator T() const  
    {  
        return i;  
    }  
private:  
    int i;  
};  
  
int main()  
{  
    Transfer<double> t(3);  
    //double d =static_cast<double>(t);//显示转换  
    double d = t;//隐式转换  
    cout<<d;  
    getchar();  
    return 0;  
}

11、类成员访问运算符 -> 重载

类成员访问运算符( -> )可以被重载,它被定义用于为一个类赋予"指针"行为。运算符 -> 必须是一个成员函数。如果使用了 -> 运算符,返回类型必须是指针或者是类的对象。

#include<iostream>

using namespace std;
class DBHelper//动态分配内存更加方便
{
public:
	DBHelper()
	{
		cout << " DBHelper()...." << endl;
	}
	~DBHelper()
	{
		cout << " ~DBHelper()" << endl;
		//Close();
	}
	void Open()
	{
		cout << " Open()...." << endl;
	}
	void Query()
	{
		cout << " Query()...." << endl;
	}
	void Close()
	{
		cout << " Close()...." << endl;
	}
 };

class DB
{
public:
	DB()
	{
		db_ = new DBHelper;//利用类对象确定性析构的特性
	}
	~DB()
	{
		delete db_;//利用类对象确定性析构的特性,将所包装的对象销毁掉
	}
	DBHelper* operator->()//重载指针运算符,返回的是DBHelper对象
	{
		return db_;
	}

private:
	DBHelper* db_;
};
int main()
{
	DB db;
	db->Open();
	db->Query();
	db->Close();
	return 0;
}

12、string类实例(重载综合说明)

String.h

#ifndef _STRING_H_
#define _STRING_H_
#include<iostream>

using namespace std;

class String
{
public:
	String(const char* str="");
	String(const String& other);

	String& operator=(const String& other);
	String& operator=(const char* str);
	bool operator!() const;

	char& operator[](unsigned int index);
	const char& operator[](unsigned int index) const;

	friend String operator+(const String& s1, const String& s2);
	//String operator+(const String& s1, const String& s2);
	String& operator+=(const String& other);

	friend ostream& operator<<(ostream& os, const String& str);
	friend istream& operator>>(istream& is,  String& str);
	~String(void);

	void Display() const;

private:
	String& Assign(const char* str);
	char* AllocAndCpy(const char* str);
	char* str_;
};

#endif // _STRING_H_

String.cpp

#pragma warning(disable:4996)
#include "String.h"
#include <string.h>
//#include <iostream>
//using namespace std;

String::String(const char* str)
{
	str_ = AllocAndCpy(str);
}

String::String(const String& other)
{
	str_ = AllocAndCpy(other.str_);
}

String& String::operator=(const String& other)
{
	if (this == &other)
		return *this;

	/*delete[] str_;
	str_ = AllocAndCpy(other.str_);
	return *this;*/
	return Assign(other.str_);
}

String& String::operator=(const char* str)
{
	/*delete[] str_;
	str_ = AllocAndCpy(str);
	return *this;*/
	return Assign(str);
}
String& String::Assign(const char* str)
{
	delete[] str_;
	str_ = AllocAndCpy(str);
	return *this;
}
char& String::operator[](unsigned int index)
{
	return const_cast<char&>(static_cast<const String&>(*this)[index]);
	//return str_[index];
}

const char& String::operator[](unsigned int index) const
{
	return str_[index];
}
bool String::operator!() const
{
	return strlen(str_) != 0;
}



char* String::AllocAndCpy(const char* str)
{
	int len = strlen(str) + 1;
	char* newstr = new char[len];
	memset(newstr, 0, len);
	strcpy(newstr, str);

	return newstr;
}

String operator+(const String& s1, const String& s2)
{
	//int len = strlen(s1.str_) + strlen(s2.str_) + 1;
	//char* newchar = new char[len];
	//memset(newchar, 0, len);
	//strcpy(newchar, s1.str_);
	//strcat(newchar, s2.str_);

	//String tmp(newchar);//return String(newchar);内部构造函数分配空间,newchar没办法销毁
	//delete newchar;
	//return tmp;
	String str = s1;
	str += s2;
	return str;
}


String& String::operator+=(const String& other)
{
	int len = strlen(str_) + strlen(other.str_) + 1;
	char* newchar = new char[len];
	memset(newchar, 0, len);
	strcpy(newchar, str_);
	strcat(newchar, other.str_);

	delete str_;
	str_ = newchar;
	return *this;
}

//cout 类型就是ostream,第一个参数是ostream,不是对象自身,所以选择友元方式重载
//返回值还是ostream&,保证接下来的对象还能够被继续输出,
//例如 cout << str << endl;//cout << str返回值还是ostream,所以可以继续输出<< endl
ostream& operator<<(ostream& os, const String& str)
{
	os << str.str_;
	return os;
}

istream& operator >> (istream& is, String& str)
{
	char tmp[1024];
	cin >> tmp;
	str = tmp;
	return is;
}

void String::Display() const
{
	cout << str_ << endl;
}

String::~String()
{
	delete[] str_;
}

main.cpp

#include "String.h"
//#include<iostream>
//
//using namespace std;

int main()
{
	String s1("abcdefg");
	//s1.Display();

	char ch = s1[2];
	//cout << ch << endl;
	s1[2] = 'A';
	//s1.Display();

	const String s2("abcdefgh");
	//s2[2] = 'C';
	//s2.Display();

	String s3 = "xxx";//String(const char* str="");前面不能加explicit,需要隐式转化
	String s4 = "yyy";
	String s5 = s3 + s4;
	s5.Display();
	//如果以函数方式重载,不允许,因为成员函数隐含的一个参数是对象自身,不能够以"aaa"任意字符串开头
	String s6 = "aaa" + s3;//不予许"bbb" + "aaa".因为String operator+(const String& s1, const String& s2)。
	s6.Display();

	s3 += s4;
	s3.Display();

	cout << s3 << endl;

	String s7;
	cin >> s7;
	cout << s7 << endl;
	return 0;
}

参考

1、https://blog.csdn.net/zgl_dm/article/details/1767201
2、https://blog.csdn.net/insistGoGo/article/details/6626952
3、https://blog.csdn.net/zhuzhaoming1994/article/details/80371779
4、https://www.cnblogs.com/xiaokang01/p/9166745.html
5、https://www.cnblogs.com/renyuan/p/6555172.html

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值