运算符重载(进阶学习)

一元运算符重载

例如 " ! "

class Calculator{
public:
    bool operator!() const;
};

class Calculator{
    friend bool operator!(const Calculator &);
};

二元运算符重载

例如 " < "

class Calculator{
public:
    bool operator<(const Calculator &) const;
};

class Calculator{
    friend bool operator<(const Calculator &,const Calculator &);
};

vector

在进一步讲解之前,我们先介绍一个非常著名的容器:vector

头文件
#include<vector> 
using std::vector;
创建
vector<int>a(7);//创建包含7个元素,每个元素为int类型的vector对象
  • 默认情况下,vector对象的所有元素被初始化为0
  • vector可以存储任何类型的元素:<类型名>
  • 可以使用一个对象初始化另一个对象,例:
    vector< int > A( B); //copy constructor
访问
  • 使用[ ]访问vector容器中的每个元素
  • 注意:使用下标不能向容器添加元素
    下标只能修改已经存在的元素或者访问已经存在的元素
vector对象作为函数参数
  • 传递vector对象的引用或指针
常用内置函数
函数用途
push_back在数组最后添加一个数据
pop_back删除数组最后一个数据
at获取编号位置的数据
begin获取数组头的指针
end获取数组最后一个单元+1的指针
front获取数组头的引用
back获取数组最后一个单元的引用
max_sizevector的最大容量
capacity当前vector对象分配的空间
size当前使用数据的占用空间
resize改变当前使用数据的占用空间,如果比当前的使用空间大则填充默认值
reserve改变当前vecotr对象所分配空间大小
erase删除指针指向的数据项
clear清空当前vector对象
rbegin返回将vector对象反转后的开始指针返回(初始vecor对象的end-1)
rend返回将vector对象反转后的结束指针返回(初始vector对象的begin-1)
empty判断vector对象是否为空
swap与另一个vector对象交换数据

实例讲解

下面C_T要用很大的篇幅,结合程序实例,努力解释在重载运算符时常见的一些重难点

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

class Array{
	friend ostream &operator<<(ostream &output,const Array &A) {
	    for (int i=0;i<A.size;i++)
			output<<A.ptr[i]<<" ";
		output<<endl;
	    return output;
	}
	friend istream &operator>>(istream &input,Array &A) {
		for (int i=0;i<A.size;i++)
		    input>>A.ptr[i];
		return input;
	}

public:
	Array(int arraySize=10) {
		size=(arraySize>0 ? arraySize:10);
		ptr=new int[size];
		memset(ptr,0,arraySize*4);
	}
	Array(const Array &A):size(A.size){    //拷贝构造函数
		ptr=new int[size];
		for (int i=0;i<size;i++) ptr[i]=A.ptr[i];
	}
	~Array() {delete []ptr;}
	int getSize() const {return size;}

	//const return avoids:(x=y)=z
	const Array &operator=(const Array &A) {   //赋值运算符
		if (&A==this) return *this;    //避免自我赋值
		if (size!=A.size) {
			delete []ptr;
			size=A.size;
			ptr=new int[size];
		}
		for (int i=0;i<size;i++) ptr[i]=A.ptr[i];
		return *this;   //enables x=y=z
	}
     
	bool operator==(const Array &A) {
		if (size!=A.size) return 0;
		for (int i=0;i<size;i++)
			if (ptr[i]!=A.ptr[i]) return 0;
		return 1;
	}
	bool operator!=(const Array &A) {return !((*this)==A);}

	int &operator[](int x) {       //返回引用的非常成员函数
		if (x<0||x>=size) {
			cerr<<"\nError:Subscript "<<x<<" out of range\n";
			exit(1);
		}
		return ptr[x];
	}
	int operator[](int x) const {   //返回值的常成员函数
		if (x<0||x>=size) {
			cerr<<"\nError:Subscript "<<x<<" out of range\n";
			exit(1);
		}
		return ptr[x];
	}

private:
	int size;
	int *ptr;
};

int main() {
	Array A(7);
	Array B;
	cout<<"A:\n"<<A;
	cout<<"\nEnter 17 integers:\n";
	cin>>A>>B;
	cout<<"B:\n"<<B;
	
	const Array D=B;   //定义常量对象
	cout<<"D[0] is "<<D[0]<<endl;
	if (A!=B)
		cout<<"A is not equal to B.\n";
	Array C(A);
	A=B;
	if (A==B)
		cout<<"A is equal to B.\n";
	cout<<"\nA[5] is "<<A[5]<<endl;

	A[5]=1000;
	cout<<"A:\n"<<A;
	cout<<"\nAttempt to assign 1000 to A[15].\n";
	A[15]=1000;

	system("pause");
	return 0;
}

我们从主程序开始看:

  • 首先,我们需要有默认实参的构造函数(比较简单,相信大家都get it successfully)

  • 对于输出流运算符 " << " 和输入流运算符 " >> " 的重载在上一篇blog中已经讲解过了

  • 拷贝构造函数
    要注意该类中存在一个指针数据成员,需要用new申请

  • const Array D=B;
    这个定义要命啊,这是个什么玩意?一个常量对象!
    但凡是带上关键字const的定义,都会有些奇技淫巧

    • const对象只能调用常成员函数(const成员函数)
    • const成员函数不能修改本对象(的数据成员),但是可以修改非本对象的数据成员
    • const成员函数不能调用本对象的其他non-const成员函数,但是可以调用非本对象的non-const成员函数
  • cout<<"D[0] is "<<D[0]<<endl;
    这个非常非常重要!!这里我们需要重载 " [ ] "
    要格外注意,此处该运算符作用于一个const对象D,所以我们必须提供一个常成员函数const成员函数不能调用本对象的其他non-const成员函数务必牢记
    由于这里我们只要求访问D[0]的值,所以重载运算符时返回int类型即可

int operator[](int x) const {   //返回值的常成员函数
	if (x<0||x>=size) {
		cerr<<"\nError:Subscript "<<x<<" out of range\n";
		exit(1);
	}
	return ptr[x];
}
  • 对于 " == " 和 " != " 的重载(比较简单,相信大家都get it successfully)
  • A=B;
    这个也非常非常重要!!这是一个重载的赋值运算
    返回const Array &类型:常量对象的引用
    返回常量const类型,是因为我们要避免赋值运算返回的值作为表达式的左值
    (const类型的变量不支持修改,所以不能作为表达式的左值)
    返回引用&,是因为我们不希望出现值拷贝(避免调用拷贝构造函数),返回的就是赋值得到的对象本身
    参数是引用类型,不需要调用拷贝构造函数
    在具体实现中,首先就是要注意:避免自我赋值
    如果对象中的数组大小不同,需要先delete原先申请的内存,重新new新的内存
//const return avoids:(x=y)=z
const Array &operator=(const Array &A) {   //赋值运算符
	if (&A==this) return *this;    //避免自我赋值
	if (size!=A.size) {
		delete []ptr;
		size=A.size;
		ptr=new int[size];
	}
	for (int i=0;i<size;i++) ptr[i]=A.ptr[i];
	return *this;   //enables x=y=z
}
  • A[5]=1000;
    这个也是巨大挑战!!重载一个返回引用的 " [ ] "
    返回引用,是因为我们在主程序中出现了一个赋值运算,需要改变原先对象的值
int &operator[](int x) {       //返回引用的非常成员函数
	if (x<0||x>=size) {
		cerr<<"\nError:Subscript "<<x<<" out of range\n";
		exit(1);
	}
	return ptr[x];
}

实例引申

运算符重载在什么情况下返回引用类型,什么情况下返回值类型?
返回引用类型:
  • 返回值还要继续被处理,例如:+=运算符: a += b += c
  • 为了提高效率,例如:Time类的set函数级联调用
返回值类型
  • 返回值用过就丢弃,例如:+运算符: a = (b + c) * d

注意:返回值如果是临时对象或局部对象,则不能返回引用类型,只能是值类型

临时对象

临时对象是无法用肉眼观察到的,用完就析构。
① 类型转换:void f(Two) {cout << "Function f called." << endl;} f(one); //Two的临时对象
② 无名临时对象:Integer t = Interger(10);
③ 函数返回对象:cout<<String(5,15);


强制类型转换

强制类型转换重载

  • 非同类抽象数据类型之间
  • 抽象数据类型和基本数据类型之间

举个简单的栗子:

A::operator int() const{...};

A obj;
static_cast<int>(obj);

注意:

  • 不能写返回类型,默认为运算符代表的类型
  • 必须是非static的成员函数
class A {
public:
    A(int =5);
    A(const A &);
    ~A();
    int getSize() const;
    operator int() const;
    
    int *ptr;
    int size;
};

A::operator int() const {
    int tmp=0;
    for (int i=size-1;i>=0;i--)
        tmp=tmp*10+ptr[i];
    return tmp;
}

int main() 
{
    A obj;
    cout<<static_cast<int>(obj)<<endl;
    return 0;
}

在这里插入图片描述


转换构造函数

转换构造函数是单实参的构造函数,用于将其他类型的对象(包括基本数据类型)转换为当前类的对象
目的:使编译器执行自动类型转化

class One {
public:
    One() {cout<<"One Constructor called.\n";}
    ~One() {cout<<"One Destructor called.\n";}
};

class Two {
public:
    //转换构造函数,参数为其他数据类型
    Two(const One &o) {cout<<"Conversion Constructor called.\n";}
    ~Two() {cout<<"Two Destructor called.\n";}
};

void f(Two) {cout<<"Function f called.\n";}   //Two是one的临时对象,函数结束就析构

int main()
{
    One one;
    f(one);    //隐性调用转换构造函数
    cout<<"Check whether Two has been destructed.\n";
    return 0;
}

// Output
One Constructor called.
Conversion Constructor called.
Function f called.
Two Destructor called.
Check whether Two has been destructed.
One Destructor called.

观察力敏锐的朋友应该已经发现了,转换构造函数和普通的构造函数形式上非常相像
但是转换构造函数是隐式调用,普通的构造函数必须显式调用
为了避免这两个长得很像的函数在程序执行时出现意想不到的混用,我们可以在普通的构造函数前增加explicit关键字,声明其只能被显式调用


String类型实现实例

//String.h
#ifndef STRING_H
#define STRING_H

#include<iostream>
#include<iomanip>
using namespace std;

class String{
	friend ostream &operator<<(ostream &output,const String &s) {
		output<<s.sPtr;
	    return output;
    }
    friend istream &operator>>(istream &input,String &s) {
	    char tmp[100];
	    input>>setw(100)>>tmp;
	    s=tmp;
	    return input;
    }
public:
	String(const char* ="");
	String(const String &);
	~String();

	const String &operator=(const String &);
	const String &operator+=(const String &);

	bool operator!() const;
	bool operator==(const String &);
	bool operator!=(const String &);
	bool operator<(const String &);
	bool operator>(const String &);
	bool operator<=(const String &);
	bool operator>=(const String &);
	
	char &operator[](int);
	char operator[](int) const;

	String operator()(int,int =0) const;
	int getLength() const;

private:
	int length;
	char *sPtr;

	void setString(const char*);
};

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

String::String(const char* s) :length((s!=0) ? strlen(s):0) {
	cout<<"Conversion (and default) constructor:"<<s<<endl;
	setString(s);
}

String::String(const String &s) :length(s.length) {
	cout<<"Copy constructor:"<<s.sPtr<<endl;
	setString(s.sPtr);
}

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

const String &String::operator=(const String &s) {
	cout<<"operator= called\n";
	if (&s!=this)   //避免自我赋值
	{
		delete []sPtr;
		length=s.length;
		setString(s.sPtr);
	}
	else
		cout<<"Attempted assignment of a String to itself.\n";
	return *this;
}
	
const String &String::operator+=(const String &s) {
	int len=length+s.length;
	char *tmp=new char[len+1];
	strcpy(tmp,sPtr);
	strcpy(tmp+length,s.sPtr);

	delete []sPtr;
	sPtr=new char[len+1];              // sPtr=tmp;
	strcpy(sPtr,tmp);                  // length=len;
	length=len;                        //
	delete []tmp;                      //
	return *this;
}

bool String::operator!() const {return length==0;}

bool String::operator==(const String &s) {return strcmp(sPtr,s.sPtr)==0;}

bool String::operator!=(const String &s) {return !((*this)==s);}
	
bool String::operator<(const String &s) {return strcmp(sPtr,s.sPtr)<0;}
	
bool String::operator>(const String &s) {return strcmp(sPtr,s.sPtr)>0;}
	
bool String::operator<=(const String &s) {return ((*this)<s) || ((*this)==s);}
	
bool String::operator>=(const String &s) {return ((*this)>s) || ((*this)==s);}
	
char &String::operator[](int x) {
	if (x<0||x>=length) {
		cerr<<"\nError:Subscript "<<x<<" out of range\n";
		exit(1);
	}
	return sPtr[x];
}
	
char String::operator[](int x) const {
	if (x<0||x>=length) {
		cerr<<"\nError:Subscript "<<x<<" out of range\n";
		exit(1);
	}
	return sPtr[x];
}

String String::operator()(int index,int subLength) const {
	if (index<0||subLength<0||index>=length) return "";
	int len;
	if ((index+subLength>length) || (subLength==0))
		len=length-index;
	else len=subLength;

	char *tmp=new char[len+1];
	strncpy(tmp,&sPtr[index],len);
    tmp[len]='\0';

	String tmpString(tmp);      //
	delete []tmp;               //
	return tmpString;
}

int String::getLength() const {
	return length;
}

void String::setString(const char* s) {
	sPtr=new char[length+1];
	if (s!=0) strcpy(sPtr,s);
	else sPtr[0]='\0';
}
//Test.cpp
#include<iostream>
#include"String.h"
using namespace std;

int main()
{
	String s1("happy ");
	String s2("birthday ");
	String s3;

	cout<<boolalpha<<"\nThe results of comparing s1 and s2:"
		<<"\ns1==s2 yields "<<(s1==s2)
		<<"\ns1!=s2 yields "<<(s1!=s2)
		<<"\ns1>s2 yields "<<(s1>s2)
		<<"\ns1<s2 yields "<<(s1<s2)
		<<"\ns1>=s2 yields "<<(s1>=s2)
		<<"\ns1<=s2 yields "<<(s1<=s2)<<endl;

	if (!s3) {
		s3=s1;
		cout<<"s3 is \""<<s3<<"\"\n";
	}

	s1+=s2;
	cout<<"\ns1 += s2 yields "<<s1;
	cout<<"\n\ns1 += \"to you\" yields"<<endl;
	s1+="to you";
	cout<<"s1="<<s1<<"\n\n";
	cout<<"The substring of s1 starting at\n"
		<<"location 0 for 14 characters,s1(0,14),is:\n"
		<<s1(0,14)<<"\n\n";

	cout<<"THe substring of s1 starting at\n"
		<<"s1(15),is:\n"
		<<s1(15)<<"\n\n";

	String *s4Ptr=new String(s1);
	cout<<"\n*s4Ptr = "<<*s4Ptr;
	cout<<"\nassigning *s4Ptr to *s4Ptr\n";
	*s4Ptr=*s4Ptr;
	cout<<"*s4Ptr = "<<*s4Ptr<<endl;
	
	delete s4Ptr;
	
	s1[0]='H';
	s1[6]='B';
	cout<<endl<<s1<<endl;

	system("pause");
	return 0;
}
//Output
Conversion (and default) constructor:happy
Conversion (and default) constructor:birthday
Conversion (and default) constructor:

The results of comparing s1 and s2:
s1==s2 yields false
s1!=s2 yields true
s1>s2 yields true
s1<s2 yields false
s1>=s2 yields true
s1<=s2 yields false
operator= called
s3 is "happy "

s1 += s2 yields happy birthday

s1 += "to you" yields
Conversion (and default) constructor:to you
s1=happy birthday to you

Conversion (and default) constructor:happy birthday
Copy constructor:happy birthday
The substring of s1 starting at
location 0 for 14 characters,s1(0,14),is:
happy birthday

Conversion (and default) constructor:to you
Copy constructor:to you
THe substring of s1 starting at
s1(15),is:
to you

Copy constructor:happy birthday to you

*s4Ptr = happy birthday to you
assigning *s4Ptr to *s4Ptr
operator= called
Attempted assignment of a String to itself.
*s4Ptr = happy birthday to you

Happy Birthday to you

我们这里选择重点讲解前置/后置自增的重载
前置/后置自减就是一个道理啦

前置自增 ++num

前置自增:先自增再取值

重载前置自增允许有两种形式

成员函数:
num.operator++()
Data &operator++();

全局函数:
operator++(num)
Data &operator++(Data &);

需要提醒大家的是,在重载前置自增的时候,返回的是引用
我们在哪里还建议大家重载结束后返回引用?
没错,就是对<<>>的重载:

ostream &operator<<(ostream &,const Data &);
istream &operator>>(istream &,Data &);

之所以选择返回引用,很大的一个原因是为了之后能够级联使用该运算符:

cin>>x>>y;
cout<<u<<v;
++(++num);

当然,函数也好,重载运算符也好,选择返回引用还是返回值,主要的依据还是函数的功能和程序的需求
在这里不得不啰嗦一句,有些函数返回的是有const修饰的引用,这个时候就要格外注意:该函数返回的对象不能作为表达式的左值
更详细的const和&介绍可以去之前的blog里考古~
文章结尾介绍const和&的基本含义
实例讲解助力实战
可见const和&的运用,一直以来都是编程中的一个难点


后置自增 num++

后置自增:先取值再自增

重载前置自增允许有两种形式

成员函数:
num.operator++(integer)
const Data operator++(int);

全局函数:
operator++(num,integer)
const Data operator++(Data &,int);

可以看到,对于后置自增的重载,函数的参数有所增加,也没有选择返回引用,返回值还有const关键字修饰
这就说明后置自增返回的只是一个临时对象(拷贝值)

临时对象
临时对象是无法用肉眼观察到的,用完就析构。
① 类型转换:void f(Two) {cout << "Function f called." << endl;} f(one); //Two的临时对象
② 无名临时对象:Integer t = Interger(10);
③ 函数返回对象:cout<<String(5,15);

随之而来一个问题:

num++只能作为表达式的右值(我们不能对后置自增返回的值进一步操作)
++num既可以作为表达式的左值也可以作为表达式的右值

(a++)++;    //CE
++(a++);    //CE
a++ = 1;    //CE

(++a)++;    //OK
++(++a);    //OK
++a = 1;    //OK

重载测试

#include<iostream>
using namespace std;

class Test{
	friend ostream &operator<<(ostream &output,const Test &A) {
		output<<A.value;
		return output;
	}
public:
	Test():value(0) {};
	Test &operator++() {
		value++;
		return (*this);
	}
	const Test operator++(int) {
		Test tmp=(*this);
		++(*this);
		return tmp;
	}
private:
	int value;
};

int main() {
	Test num;
	cout<<num++<<endl;
	cout<<++num<<endl;
	system("pause");
	return 0;
}
//Output
0
2
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值