C++的运算符重载
使对象的运算表现得和编译器内置类型一样
template<typename T>
T sum(T a, T b)
{
return a+b;//a.operator+(b)
}
如果T是编译器的内置类型的话,编译器对a+b是可以做的。
但是如果T是我们自定义的对象类型,a和b是两个对象,对象和对象之间该怎么相加???编译器是不知道的。
这里就要使用运算符的重载了。
return a+b;//a.operator+(b)
加法运算符的重载函数。
复数类的实现(运算符重载)
这个构造函数相当于可以构造3种不同的复数对象。
#include <iostream>
using namespace std;
class CComplex
{
public:
//CComplex() CComplex(20) CComplex(30, 30)
CComplex(int r = 0, int i = 0)
:mreal(r), mimage(i) {}
//指导编译器怎么做CComplex类对象的加法操作
//CComplex operator+(const CComplex &src)
//{
//写法1:
//CComplex comp;//局部对象
//comp.mreal = this->mreal + src.mreal;
//comp.mimage = this->mimage + src.mimage;
//return comp;
//写法2:
//return CComplex(this->mreal + src.mreal,
//this->mimage + src.mimage);//对象的优化
//}
CComplex operator++(int)
{
//方法1:
//CComplex comp = *this;
//mreal += 1;
//mimage += 1;
//return comp;
//方法2:
return CComplex(mreal++, mimage++);
}
CComplex& operator++()
{
mreal += 1;
mimage += 1;
return *this;
}
void operator+=(const CComplex &src)
{
mreal += src.mreal;
mimage += src.mimage;
}
void show() { cout << "real:" << mreal << " image:" << mimage << endl; }
private:
int mreal;//实部
int mimage;//虚部
//这个全局的方法成了这个类的朋友,这个全局方法可以访问这个类的实部和虚部
friend CComplex operator+(const CComplex &lhs, const CComplex &rhs);
friend ostream& operator<<(ostream &out, const CComplex &src);
friend istream& operator>>(istream &in, CComplex &src);
};
CComplex operator+(const CComplex &lhs, const CComplex &rhs)//加法的全局运算符重载
{
return CComplex(lhs.mreal + rhs.mreal, lhs.mimage + rhs.mimage);
}
ostream& operator<<(ostream &out, const CComplex &src)//输出是从左向右运算的
{
out << "mreal:" << src.mreal << " mimage:" << src.mimage << endl;
return out;
}
istream& operator>>(istream &in, CComplex &src)
{
in >> src.mreal >> src.mimage;
return in;
}
int main()
{
CComplex comp1(10, 10);
CComplex comp2(20, 20);
//comp1.operator+(comp2) 加法运算符的重载函数
CComplex comp3 = comp1 + comp2;//左边是对象,优先调用成员方法 ,成员方法没有的话就去找全局方法
comp3.show();
CComplex comp4 = comp1 + 20;//左边是对象,优先调用成员方法,成员方法没有的话就去找全局方法
//comp1.operator+(20) int->CComplex 编译器会找有没有CComplex(int)生成1个临时对象 ,然后进行相加
comp4.show();
//编译器做对象运算的时候,会调用对象的运算符重载函数(优先调用成员方法);如果没有成员方法
//就在全局作用域找合适的运算符重载函数
// 我们在全局提供 ::operator+(30, comp1) 30和comp1都当做实参传进去
CComplex comp5 = 30 + comp1;//30在里面必须要有趋势要做类型的转换!!! 无法调用成员方法的加法运算符重载函数
//看复数类型有没有带整型参数的构造函数来生成1个临时对象让lhs引用,就生成一个实部是30,虚部是0的临时对象
comp5.show();
//CComplex operator++(int)
comp5 = comp1++;
// ++ --是单目运算符 operator++()表示前置++ operator++(int)表示后置++ 这个int只是为了区分,没有任何作用
comp1.show();
comp5.show();
//CComplex operator++()
comp5 = ++comp1;
comp1.show();
comp5.show();
//void comp1.operator+=(comp2) ::operator+=(comp1, comp2)
comp1 += comp2;
//comp1.show();//对象信息的输出
//这个输出<<运算符重载,对象不在左边,所以不可以提供成员方法 ,所以定义成全局方法
//cout ::operator<<(cout, comp1) 流对象是不断放,取东西,是变化的,不能加const
//void << endl; 因为要连续输出,不能用void作为返回值
//连续的输出,应该把cout返回:ostream& operator<<(ostream &out, const CComplex &src)
cout << comp1 << endl;
cin >> comp1 >> comp2;
cout << comp1 << comp2 << endl;
return 0;
}
C++string类的实现
这个写法的效率不好。ptmp指向new出来的内存,然后字符串拷贝,连接,当做参数传入tmp对象,tmp构造的时候又会根据传进来的指针进行判空,然后开辟内存,进行字符串拷贝,然后把这个ptmp指针delete掉,tmp是局部对象,return tmp,析构的时候又要把刚才构造时new出来的空间delete掉,2次new2次delete,太麻烦了。
我们应该这么做:
String operator+(const String& lhs, const String& rhs)
{
//char *ptmp = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1];
String tmp;
tmp._pstr = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1];
strcpy(tmp._pstr, lhs._pstr);//拷贝函数
strcat(tmp._pstr, rhs._pstr);//连接函数
//delete[]ptmp;
return tmp;
}
这样就比刚才少了1次的new和delete操作。
但是是临时对象,还是要进行大量的拷贝构造,优化的方法是调用右值引用构造。
迭代器是容器类型的嵌套类。
我们假设有一个string对象,里面有一堆字符串,这些内容我们是看不见,因为是私有的。
我们想迭代string对象底层的字符串的字符,用迭代器是怎么做到的?
容器有一个begin方法,返回的是底层首元素的迭代器。
it指向了字符串底层的首元素。
容器的end方法,返回的是最后一个元素的后继位置的迭代器。
我们只需要不断的遍历,遍历到最后一个元素,往后再走,遇到end()迭代器,就退出。
++it,就是从当前元素跳到下一个元素去遍历。不需要管底层的数据结构。我们作为使用者只需要用迭代器++,不同的数据结构的差异都封装在迭代器++的重载运算符函数里面。
对迭代器解引用,就是访问迭代器访问的底层的数据。
不同容器底层的数据结构不一样,每一种容器都有自己的迭代器,设置成嵌套类型。
迭代器用的都是前置++,因为返回的是*this,效率比较高,不用生成临时对象。
迭代器的功能:提供一种统一的方式,来透明的遍历容器,我们不需要知道底层的数据结构,都封装在迭代器的++操作里面
#include <iostream>
#include <string>
//char arr[]="jkhsdkf";
using namespace std;
//自己实现一个字符串对象
class String
{
public:
String(const char *p = nullptr)//构造函数
{
if (p != nullptr)
{
_pstr = new char[strlen(p) + 1];
strcpy(_pstr, p);
}
else
{
_pstr = new char[1];
*_pstr = '\0';
}
}
~String()//析构函数
{
delete[]_pstr;
_pstr = nullptr;
}
String(const String &str)//拷贝构造函数
{
_pstr = new char[strlen(str._pstr) + 1];
strcpy(_pstr, str._pstr);
}
String& operator=(const String &str)//赋值函数
{
if (this == &str)
return *this;
delete[]_pstr;
_pstr = new char[strlen(str._pstr) + 1];
strcpy(_pstr, str._pstr);
return *this;
}
bool operator>(const String &str)const
{
return strcmp(_pstr, str._pstr) > 0;
}
bool operator<(const String &str)const
{
return strcmp(_pstr, str._pstr) < 0;
}
bool operator==(const String &str)const
{
return strcmp(_pstr, str._pstr) == 0;
}
int length()const { return strlen(_pstr); }
const char* c_str()const { return _pstr; }
//char ch = str6[6]; 可以修改:str6[6] = '7'
char& operator[](int index) { return _pstr[index]; }
//char ch = str6[6]; 不允许修改!str6[6] = '7'
const char& operator[](int index)const { return _pstr[index]; }
//给String字符串类型提供迭代器的实现
class iterator
{
public:
iterator(char *p = nullptr) :_p(p) {}
bool operator!=(const iterator &it)
{
return _p != it._p;
}
void operator++()
{
++_p;
}
char& operator*() { return *_p; }
private:
char *_p;
};
//下面2个方法是容器的方法哦!!!
//begin返回的是容器底层首元素的迭代器的表示
iterator begin() { return iterator(_pstr); }
//end返回的是容器末尾元素后继位置的迭代器的表示
iterator end() { return iterator(_pstr + length()); }
private:
char *_pstr;
friend String operator+(const String &lhs, const String &rhs);
friend ostream& operator<<(ostream &out, const String &str);
};
String operator+(const String &lhs, const String &rhs)
{
//char *ptmp = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1];
String tmp;
tmp._pstr = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1];
strcpy(tmp._pstr, lhs._pstr);//拷贝函数
strcat(tmp._pstr, rhs._pstr);//连接函数
//delete[]ptmp;
return tmp;
}
ostream& operator<<(ostream &out, const String &str)
{
out << str._pstr;
return out;
}
int main()
{
//迭代器的功能:提供一种统一的方式,来透明的遍历容器
String str1 = "hello world!";//str1叫容器吗?叫。因为底层放了一组char类型的字符
//容器的迭代器类型
auto it = str1.begin();
for (; it != str1.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
//c++11 foreach的方式来遍历容器的内部元素的值=>底层,还是通过迭代器进行遍历的
for(char ch : str1)
{
cout << ch << " ";
}
cout << endl;
//vector allocator 提供迭代器iterator的实现
String str1;
String str2 = "aaa";//string(const char*)
String str3 = "bbb";
String str4 = str2 + str3;
String str5 = str2 + "ccc";
String str6 = "ddd" + str2;
cout << "str6:" << str6 << endl;
//bool operator>(const String &str)
if (str5 > str6)
{
cout << str5 << " > " << str6 << endl;
}
else
{
cout << str5 << " < " << str6 << endl;
}
int len = str6.length();
for (int i = 0; i < len; ++i)
{
//char& str6.operator[](i)
cout << str6[i] << " ";
}
cout << endl;
//string -> char*
char buf[1024] = { 0 };
strcpy(buf, str6.c_str());//c_str()把对象管理的字符串返回成const char*类型
cout << "buf:" << buf << endl;
return 0;
}
这2种写法等价。
auto自动根据等号右边的类型推导左边的类型,写起来比较简单方便。