一、下标运算符的重载
1.概念
如果一个类表示容器,那么要重载下标运算符[],下标运算符必须是成员函数。下表访问运算符通常要有一个const版本和一个非const版本。如果不定义const版本,那么const对象将无法调用下表运算符。非const版本返回对应位置的引用,const版本返回常量对象的引用
2.示例
通过实现一个string类来熟悉下标运算符的重载
string类的整体结构如下
#include <utility>
#include <iostream>
#include <cstring>
using namespace std;
class mystring
{
friend istream &operator>>(istream &is, mystring &data);
friend ostream &operator<<(ostream &os, const mystring &data);
public:
mystring();
mystring(const char *str);
mystring(const mystring &rval);
mystring &operator=(const mystring &rval);
char &operator[](size_t n);
const char &operator[](size_t n) const;
size_t size() const;
const char *c_str() const;
void swap(mystring &t);
~mystring();
private:
char *data_;
};
istream &operator>>(istream &is, mystring &data);
ostream &operator<<(ostream &os, const mystring &data);
具体实现如下和测试
#define _GLIBCXX_DEBUG
#include "mystring.h"
mystring::mystring():
data_(new char[1])
{
cout<<"mystring()"<<endl;
data_[0]='\0';
}
mystring::mystring(const char *str):
data_(new char[strlen(str)+1])
{
cout<<"mystring(const char *str)"<<endl;
strcpy(data_, str);
}
mystring::mystring(const mystring &rval):
data_(new char[rval.size()+1])
{
cout<<"mystring(const mystring &t)"<<endl;
strcpy(data_, rval.c_str());
}
mystring & mystring::operator=(const mystring &rval)
{
cout<<"operator=(const mystring &rval)"<<endl;
mystring t(rval);
this->swap(t);
return *this;
}
char & mystring::operator[](size_t n)
{
cout<<"operator[](size_t n)"<<endl;
return data_[n];
}
const char & mystring::operator[](size_t n) const
{
cout<<"operator[](size_t n) const"<<endl;
return data_[n];
}
size_t mystring::size() const
{
cout<<__func__<<endl;
return strlen(data_);
}
const char *mystring::c_str() const
{
cout<<__func__<<endl;
return data_;
}
void mystring::swap(mystring &t)
{
cout<<__func__<<endl;
std::swap(this->data_, t.data_);
}
mystring::~mystring()
{
cout<<"~mystring()"<<endl;
delete data_[];
}
istream &operator>>(istream &is, mystring &data)
{
cout<<__func__<<endl;
is>>data.data_;
return is;
}
ostream &operator<<(ostream &os, const mystring &data)
{
cout<<__func__<<endl;
os<<data.data_;
return os;
}
int main(int argc, char const *argv[])
{
mystring t1("1234"), t2("5678");
mystring t3=t1;
cout<<t2<<t3<<endl;
cout<<t2.size()<<endl;
t1.swap(t2);
cout<<t1<<t2<<endl;
mystring t;
t=t2;
cout<<t<<endl;
const mystring t4=t;
cout<<t[2]<<t4[1]<<endl;
return 0;
}
上述代码中,mystring类实现了两个版本的operator[]
char & mystring::operator[](size_t n)
{
cout<<"operator[](size_t n)"<<endl;
return data_[n];
}
const char & mystring::operator[](size_t n) const
{
cout<<"operator[](size_t n) const"<<endl;
return data_[n];
}
都返回的是对应位置的元素的引用。表面看起来,这两个函数的函数名相同,形参列表也相同,好像是违背了函数重载的规则,但是实际上这两个函数的形参列表是不同的
因为operator[]是成员函数,,形参列表中都隐式的存在一个指向创建对象的this指针,非const版本的operator[]的this的类型是mystring *const this,所以,非常量的mystring对象可以调用非常量版本的operator[]
而const版本的operator[]的this的类型是const mystring *const this,所以不仅得永远指向创建对象,而且还要求指向的创建对象不能更改,所以只有const mystring的对象可以调用const版本的operator[]
关于const函数,见博客https://blog.csdn.net/Master_Cui/article/details/106885048
此外,mystring关于operator=的实现使用了swap,而不是将delete原来的对象,然后再重新创建对象
void mystring::swap(mystring &t)
{
cout<<__func__<<endl;
std::swap(this->data_, t.data_);
}
mystring & mystring::operator=(const mystring &rval)
{
cout<<"operator=(const mystring &rval)"<<endl;
mystring t(rval);
this->swap(t);
return *this;
}
类不用自定义swap成员,但是如果一个类含有动态分配内存的操作,那么swap可能就是一个优化的手段。上述代码通过自定义swap,只是改变了两个指针的指向对象,而指向的对象并不需要重新分配内存。
减少了动态分配内存和销毁指向对象的次数
参考
https://github.com/chenshuo/recipes/blob/master/string/StringTrivial.h
欢迎大家评论交流,作者水平有限,如有错误,欢迎指出