目录
一.命名空间的封装与交换函数模板
1.命名空间的封装与类的定义
2.交换函数模板
二.string类的四个重要默认成员函数
1.构造函数的类外定义:
2.析构函数在类外的定义
3.拷贝构造函数在类外的定义
4.赋值运算符重载在类外的定义
5.关于两个string对象的内容交换问题
三.string类的迭代器
四.string类的[]成员重载
一.命名空间的封装与交换函数模板
1.命名空间的封装与类的定义
模拟实现的string类最好用一个命名空间封装起来避免发生命名冲突。
命名空间定义:
namespace mystring { //..... }
类的定义(为了方便整理和维护成员方法的声明和定义分离):
(本期不含增删查改的接口)
namespace mystring { class string { public: typedef char * iterator; //宏定义string类的迭代器 typedef const char* const_iterator; string(const char* nstr); //类中四个重要的默认成员函数 string(const string& nstr); ~string(); string& operator=(string nstr); void copyswap(string &nstr); //复用交换函数的接口 iterator begin(); //string类用于获取迭代器的接口 iterator end(); const_iterator begin()const; const_iterator end()const; const char * C_str()const; //用于返回C类型字符串的函数 char & operator[](int pos); //用于返回pos下标字符的引用的[]重载 const char & operator[](int pos) const; size_t size() const; //获取有效字符个数size和容量的接口 size_t capacity() const; private: char * _str; size_t _size; size_t _capacity; }; }
2.交换函数模板
template <typename T> void myswap (T& e1 , T&e2) { T tem = e1; e1 = e2; e2 =tem; }
该模板可以根据传入的实参类型生成用于交换对应类型数据的函数实例。
二.string类的四个重要默认成员函数
1.构造函数的类外定义:
类外声明方法时注意表明方法所在命名空间和类域
mystring::string::string(const char* nstr = "") :_size(strlen(nstr)) ,_capacity (_size) { _str = new char[_capacity+1]; strcpy(_str,nstr); }
- 设计缺省参数是为了保证类的构造函数可以被无参调用,(形参缺省值可用于构造空字符串)(会完成'\0'的拷贝)(注意参数缺省值只能写在函数的声明和定义其中一个之中)
- strlen函数中有关于空指针的断言,所以构造函数中无须进行空指针断言
- +1是为了保存'\0'(不计入容量和有效字符),同时保证_str不为空指针(对象只要创建出来就一定要维护一块堆空间,防止析构的时候程序崩溃)
2.析构函数在类外的定义
mystring::string::~string() { if(_str) { delete[] _str; 释放堆区空间 _str=nullptr; } _size=0; _capacity=0; }
3.拷贝构造函数在类外的定义
mystring::string::string(const string& nstr) :_size(nstr._size) ,_capacity(nstr._capacity) { _str = new char[_capacity+1]; strcpy(_str,nstr._str); }
这是一种非复用的写法,现代STL中比较喜欢使用复用的写法,拷贝构造函数可以通过复用构造函数来实现,从而使代码更加简洁:
现在类中定义一个成员交换函数(该函数通过复用swap函数实现):
//template <typename T> //void myswap (T& e1 , T&e2) //{ // T tem = e1; // e1 = e2; // e2 =tem; //} void mystring::string::copyswap(string & nstr) { myswap(_str,nstr._str); myswap(_size,nstr._size); myswap(_capacity,nstr._capacity); }
调用该成员函数可以将两个string对象的各个成员变量进行交换
复用构造函数实现拷贝构造函数:
mystring::string::string(const string& nstr) //复用构造函数完成拷贝构造 :_str (nullptr) //防止tem中_str作为野指针被释放 ,_size(0) ,_capacity(0) { string tem(nstr._str); //拷贝构造拷贝的是不含'\0'的有效字符 this->copyswap(tem); //为了方便阅读加上this指针 }
该实现方法的思想是通过形参对象中的字符串构造tem临时对象,再将tem对象中的内容通过交换函数置换到被构造的对象中去。
tem在函数体执行完后会自动调用自身的析构函数并销毁。
4.赋值运算符重载在类外的定义
string& mystring::string::operator=(const string& nstr) { if(this != &nstr) //判断重载操作数是否为同一个对象 { char * tem = new char[nstr._capacity+1]; strcpy(tem,nstr._str); delete[] _str; //释放原来的堆区空间 _str = tem; _size = nstr._size; _capacity = nstr._capacity; } return *this; }
- 注意不能直接用_str去接收新申请的堆区空间地址(因为new可能会失败)
- 注意返回对象本身的引用*this(使=重载可以满足连等赋值)
赋值运算符重载也可以通过复用拷贝构造来实现:
string& mystring::string::operator=(string nstr) { copyswap(nstr); return (*this); }
- 复用拷贝构造实现赋值运算符重载的思路:
5.关于两个string对象的内容交换问题
- STL的标准库中string类中有成员交换函数swap(相当于本篇模拟实现中的copyswap成员函数),使用成员函数完成两个string对象的内容交换效率远高于直接使用全局swap函数(相当于本篇模拟实现中的myswap函数)完成两个string对象的内容交换
- 直接调用全局交换函数交换两个string对象会额外调用对象的拷贝构造函数和赋值运算符重载从而增加性能消耗(new申请空间的性能消耗是很大的)
比如:
using mystring::string; using mystring::myswap; int main() { string a("hello"); string b("world"); myswap(a,b); 交换方式一(直接调用全局交换函数) a.copyswap(b); 交换方式二(调用成员交换函数) return 0; }
两种对象交换的方式差异图解:
三.string类的迭代器
string类的迭代器可以typedef为字符指针
mystring::string::iterator mystring::string:: begin() { return _str; } mystring::string::iterator mystring::string::end() { return _str+_size; } mystring::string::const_iterator mystring::string::begin()const { return _str; } mystring::string::const_iterator mystring::string::end()const { return _str+_size; }
四.string类的[]成员重载
string类的[]成员重载用于返回字符串中指定下标的字符的引用
char & mystring::string::operator[](int pos) { assert(pos<_size); return _str[pos]; } const char & mystring::string::operator[](int pos) const { assert(pos<_size); return _str[pos]; }
- const修饰的重载成员是供const修饰的string对象调用的
完整代码:
#include<iostream> #include<cstring> #include <assert.h> //只要不涉及寻址,一定要注意编译器的顺序编译机制 namespace mystring { class string { public: typedef char * iterator; typedef const char* const_iterator; string(const char* nstr); //类中四个重要的默认成员函数 string(const string& nstr); ~string(); string& operator=(string nstr); void copyswap(string &nstr); //复用交换函数的接口 iterator begin(); //string类用于获取迭代器的接口 iterator end(); const_iterator begin()const; const_iterator end()const; const char * C_str()const; //用于返回C类型字符串的函数 char & operator[](int pos); //用于返回pos下标字符的引用的[]重载 const char & operator[](int pos) const; size_t size() const; //获取有效字符个数size和容量的接口 size_t capacity() const; private: char * _str; size_t _size; size_t _capacity; }; template <typename T> void myswap (T& e1 , T&e2) { T tem = e1; e1 = e2; e2 =tem; } void mystring::string::copyswap(string & nstr) { myswap(_str,nstr._str); myswap(_size,nstr._size); myswap(_capacity,nstr._capacity); } const char * mystring::string::C_str() const { return _str; } mystring::string::string(const char* nstr = "") //缺省值用于构造空字符串(会完成'\0'的拷贝)(注意缺省参数只能写在声明和定义其中一个之中) :_size(strlen(nstr)) //strlen函数中有关于空指针的断言,所以构造函数中无须进行空指针判断 ,_capacity (_size) { _str = new char[_capacity+1]; // +1是为了保存'\0'(不计入容量和有效字符),同时保证_str不为空指针(对象只要创建出来就一定维护一块堆空间) strcpy(_str,nstr); } // mystring::string::string(const string& nstr) //非复用式写法 // :_size(nstr._size) // ,_capacity(nstr._capacity) // { // _str = new char[_capacity+1]; // +1是为了保存'\0'(不计入容量和有效字符),同时保证_str不为空指针 // strcpy(_str,nstr._str); // } mystring::string::string(const string& nstr) //复用构造函数完成拷贝构造 :_str (nullptr) //防止tem中_str作为野指针被释放 ,_size(0) ,_capacity(0) { string tem(nstr._str); //拷贝的是不含'\0'的有效字符 this->copyswap(tem); //为了方便阅读加上this指针 } // string& mystring::string::operator=(const string& nstr) // { // if(this != &nstr) //判断重载操作数是否为同一个对象 // { // char * tem = new char[nstr._capacity+1]; // +1是为了保存'\0'(不计入容量和有效字符) // strcpy(tem,nstr._str); // delete[] _str; // _str = tem; // _size = nstr._size; // _capacity = nstr._capacity; // } // return *this; // } string& mystring::string::operator=(string nstr) //与直接交换对象作对比 { copyswap(nstr); return (*this); } mystring::string::~string() { if(_str) { delete[] _str; _str=nullptr; } _size=0; _capacity=0; } mystring::string::iterator mystring::string:: begin() { return _str; } mystring::string::iterator mystring::string::end() { return _str+_size; } mystring::string::const_iterator mystring::string::begin()const { return _str; } mystring::string::const_iterator mystring::string::end()const { return _str+_size; } char & mystring::string::operator[](int pos) { assert(pos<_size); return _str[pos]; } const char & mystring::string::operator[](int pos) const { assert(pos<_size); return _str[pos]; } size_t mystring::string::size() const { return _size; } size_t mystring::string::capacity() const { return _size; }