C++学习之运算符重载

  • 运算符重载的基本概念

为什么要重载运算符?

C++中预定义的运算符只能用于基本数据类型的计算,但有时我们会希望对象也能通过运算符进行运算,(比如求两个复数对象的和)这样会使代码更简洁,容易理解。

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

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

同一个运算符,对不同类型的操作数,所发生的行为不同。

例如:

complex_a + complex_b  生成新的复数对象

5 +4  = 9

运算符重载的实质是函数的重载

可以重载为普通函数,也可以重载为成员函数

把含运算符的表达式转换成对运算符函数的调用

把运算符的操作数转换成运算符函数的参数

运算符被多次重载时,根据实参的类型决定调用哪个运算符函数。

运算符重载的形式:

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

{

······

}

运算符重载的示例:

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;        //等价于c = operator+(a, b)
    cout<<c.real<<","<<c.imag<<endl;
    cout<<(a-b).real<<","<<(a-b).imag<<endl;    //a-b等价于a.operator-(b)
    return 0;
}

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

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

输出结果为:

5,5

3,3

  • 赋值运算符‘=’的重载

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

赋值运算符只能重载为成员函数

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)
{         //重载“=”以使得 obj = “hello”能够成立
        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;
}
  • 运算符重载为友元函数

一般只需将运算符重载为类的成员函数便是较好的选择,但有时,重载为成员函数不能满足使用要求,重载为普通函数,又不能访问类的私有成员,所以才将运算符重载为友元

引出代码:

class Complex
{
    double real,imag;
public:
    Complex( double r, double i):real(r),imag(i){ };
    Complex operator+( double r );
};

Complex Complex::operator+( double r )
{ //能解释 c+5
    return Complex(real + r,imag);
}

经过上述重载后,能够解释c+5,但是不能解释5+c

所以改动重载函数为:

Complex Complex::operator+(double r, const Complex & c)
{
    return Complex(c.real+r, c.imag);
}        

上述函数就可以解释5+c的问题了,但是普通函数是不能访问私有成员的,需要将运算符+重载为友元。

class Complex
{
    double real,imag;
public:
    Complex( double r, double i):real(r),imag(i){ };
    Complex operator+( double r );
    friend Complex operator + (double r,const Complex & c);
};
  • 可变长整型数组(运算符重载的实例)

#include <iostream>
#include <string>
#define _SCL_SECURE_NO_WARNINGS
using namespace std;

class CArray {
	int size;//	数组元素的个数
	int *ptr;//指向动态分配的数组
public:
	CArray(int s = 0);//s代表数组元素的个数
	CArray(CArray & a);
	~CArray();
	void push_back(int v);//用于在数组尾部添加一个元素
	CArray &operator=(const CArray & a);//用于数组对象间的赋值
	int length() { return size; }//返回数组元素的个数
	int & operator[](int i)//返回值为int是不行的,不支持a[i]=4.
	{
		return ptr[i];  //用于支持根据下标访问数组元素,如n=a[i]和a[i]=4这样的语句。
	}
};

CArray::CArray(int s) :size(s) {
	if (s == 0) {
		ptr = NULL;
	}
	else {
		ptr = new int[s];
	}
}

CArray::CArray(CArray & a) {
	if (!a.ptr) {
		ptr = NULL;
		size = 0;
		return;
	}
	ptr = new int[a.size];
	memcpy(ptr, a.ptr, sizeof(int)*a.size);
	size = a.size;
}

CArray::~CArray() {
	if (ptr)
		delete[] ptr;
}
CArray & CArray::operator=(const CArray & a) {
	//赋值号重载的作用是使“=”左边的对象里存放的数组,大小和内容都和右边的对象一样
	if (ptr == a.ptr) { //防止a = a这样的赋值导致出错
		return *this;
	}
	if (a.ptr == NULL) {   //a里面的数组是空的
		if (ptr) {
			delete[] ptr;
		}
		ptr = NULL;
		size = 0;
		return *this;
	}
	if (size < a.size) {//如果原有空间够大,就不用分配新的空间
		if (ptr) {
			delete[] ptr;
		}
		ptr = new int[a.size];
	}

	memcpy(ptr, a.ptr, sizeof(int)*a.size);
	size = a.size;
	return *this;
}

void CArray::push_back(int v) {
	if (ptr) {
		int *temptr = new int[size + 1];//重新分配空间
		memcpy(temptr, ptr, sizeof(int)*size);//拷贝原数组的内容
		delete[] ptr;
		ptr = temptr;
	}
	else//数组本来是空的
		ptr = new int[1];
	ptr[size++] = v;		//加入新的数组元素
}

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;
}
  • 流插入运算符和 流提取运算符的重载

问题的引入:

cout << 5 << “this”; 为什么能够成立?

cout是什么? “<<” 为什么能用在 cout上?

cout 是在 iostream 中定义的,ostream 类 的对象。

“<<” 能用在cout 上是因为,在iostream 里对 “<<” 进行了重载

怎么重载才能使得 cout << 5 << “this” ; 成立?

重载的形式:

ostream & ostream::operator<<(int n)
{
    …… //输出n的代码
    return * this;
}


ostream & ostream::operator<<(const char * s )
{
    …… //输出s的代码
    return * this;
}

问题:假定下面程序输出为5hello,该补写些什么?

class CStudent{
public: 
    int nAge;
};

int main(){
    CStudent s ;
    s.nAge = 5;
    cout << s <<"hello";
    return 0;
}

可以分析得到上面的程序缺少对流插入运算符的重载,

即:

ostream & operator<<( ostream & o,const CStudent & s){
    o << s.nAge ;
    return o;
}

例题:

假定c是Complex复数类的对象,现在希望 写“cout >c;”,就能从键 盘接受“a+bi”形式的输入,并且使得 c.real = a,c.imag = b。

int main() {
    Complex c;
    int n;
    cin >> c >> n;
    cout << c << "," << n;
    return 0;
}

//使得程序的输出结果是
//      13.2+133i 87   (输入)
//      13.2+133i, 87

显然需要我们补充对<<和>>运算符的重载。

完整代码如下:

#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;

class Complex {
    double real,imag;
public:
    Complex( double r=0, double i=0):real(r),imag(i){ };
    friend ostream & operator<<( ostream & os,
    const Complex & c);
    friend istream & operator>>( istream & is,Complex & c);
};

ostream & operator<<( ostream & os,const Complex & c)
{
    os << c.real << "+" << c.imag << "i"; //以"a+bi"的形式输出
    return os;
}

istream & operator>>( istream & is,Complex & c)
{
    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 c;
    int n;
    cin >> c >> n;
    cout << c << "," << n;
    return 0;
}

选择题:

重载“<<”用于将自定义的对象通过cout输出时, 以下说法哪个是正确的?

A) 可以将"<<"重载为 ostream 类的成员函数, 返回值类型是 ostream &

B) 可以将"<<"重载为全局函数,第一个参数以 及返回值,类型都是 ostream

C) 可以将"<<"重载为全局函数,第一个参数以 及返回值,类型都是 ostream &

D) 可以将"<<"重载为 ostream 类的成员函数, 返回值类型是 ostream

正确答案:C。

  • 类型转换运算符的重载

例如:

#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
}

上面代码中的double就是属于类型转换运算符的重载,可以使得类的对象显示或隐式的发生转换。

  • 自增,自减运算符的重载

自增运算符++、自减运算符--有前置/后置之分,为了区分所重载的是前 置运算符还是后置运算符,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则会编译出错。

例题:

下面的代码,该如何编写CDemo类?

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;
}


//输出结果为
5,6,7,7
7,6,5,5

正确的代码补充为:

class CDemo {
private :
    int n;
public:
    CDemo(int i=0):n(i) { }
    CDemo & operator++(); //用于前置形式
    CDemo operator++( int ); //用于后置形式
    operator int ( ) { return n; }        //这里是一个强制类型转换的运算符被重载
    friend CDemo & operator--(CDemo & );
    friend CDemo operator--(CDemo & ,int);
};

    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 & d)
{//前置--
    d.n--;
    return d;
} //--s即为: operator--(s);

    CDemo operator--(CDemo & d,int)
{//后置--
    CDemo tmp(d);
    d.n --;
    return tmp;
} //s--即为: operator--(s, 0);
  • 运算符重载的注意事项

1. C++不允许定义新的运算符 ;

2. 重载后运算符的含义应该符合日常习惯;  complex_a + complex_b  word_a > word_b  date_b = date_a + n ‘’

3. 运算符重载不改变运算符的优先级;

4. 以下运算符不能被重载:“.”、“.*”、“::”、“?:”、sizeof;

5. 重载运算符()、[]、->或者赋值运算符=时,运算符重载函数必须声明为 类的成员函数

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值