一元运算符重载
例如 " ! "
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_size | vector的最大容量 |
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