知识点列举
- 使用初始化列表的好处
- 拷贝构造的形参为&与值的区别
- 深拷贝与浅拷贝
- 使用&的好处
- 形参使用const引用的好处
使用初始化列表的好处
- 类成员中存在常量,如const int a,只能用初始化不能复制
- 类成员中存在引用,同样只能使用初始化不能赋值。类成员中存在引用,同样只能使用初始化不能赋值。
- 提高效率:对类类型来说,用初始化列表少了一次调用默认构造函数的过程,
//模拟实现stringclass String
{public://构造函数String(const char* str =“”):_size(strlen(str)), _str(new char[_size + 1]),_capacity( _size){strcpy(_str, str);}String(char* str, size_t size):_size(size),_capacity(size),_str(_str = new char[size + 1]){strncpy(_str, str, size);}
拷贝构造的形参为&与值的区别
如果拷贝构造函数的形参用值,由于类中没有这个对象,就要调用拷贝构造函数来创建,以此类推,会造成无限递归循环
//拷贝构造函数String(const String& s):_str(new char[s._capacity + 1]), _capacity(s._capacity), _size(s._size){strcpy(_str, s._str);}
深拷贝与浅拷贝
- 浅拷贝:也称位拷贝,编译器只是直接将指针的值全部拷贝过来,从而造成对个对象使用同一块空间,当一个对象将这块内存释放掉之后,另一些对象不知道该块空间已经还给了系统,以为还有效,依旧会对这块空间进行释放,在对这段内存进行释放操作的时候,一个内存空间被释放多次,发生了访问违规,导致程序崩溃。
- 深拷贝:为了解决浅拷贝的问题,深拷贝则不是直接将指针的值拷贝,是为指针_str开辟与str相同大小的内存空间,然后将str中的资源拷贝到_str中,这样虽然_str与str中的资源一样,但是使用的是两块独立的空间,所以delete时只释放这个对象自己的内存,不会出现访问违规。
//赋值运算符String operator=(const String& s){if (this != &s){char* pStr = new char[s._capacity + 1];strcpy(pStr, _str);delete[] _str;_str = pStr;_capacity = s._capacity;_size = s._size;}return *this;}
形参使用&的好处
形参是对象的引用,是通过传地址的方法传递参数的,对函数形参的改变就是对实参的改变,如果函数的形参是对象,则是通过传值的方法传递参数的,函数体内对形参的修改无法传到函数体外。
运算符重载部分
//运算符重载部分bool operator<(const String& s){return !strcmp(_str, s._str);}bool operator<=(const String& s){if (strcmp(_str, s._str) == 1)return npos;return 1;}bool operator>(const String& s){return strcmp(_str, s._str);}bool operator>=(const String& s){if (strcmp(_str, s._str) != 1)return npos;return 1;}bool operator==(const String& s){return (strcmp(_str, s._str) == 0);}bool operator!=(const String& s){return (strcmp(_str, s._str) != 0);}friend ostream& operator<<(ostream& _cout, const String& s){_cout << s._str;return _cout;}friend istream& operator>>(istream& _cin, String& s){_cin >> s._str;return _cin;}
形参使用const的好处
- 当实参的类型比较大时,复制开销很大,引用会“避免复制”。
- “避免修改实参”,当使用引用时,如果调用者希望只使用实参并不修改实参,则const可以避免使用该引用修改实参。
string中部分接口的模拟实现
//尾插void PushBack(char c){//CheckCapacity();_str[_size++] = c;_str[_size] = '\0';}//追加字符串void Append(const char* str) {//判断对象中的剩余空间是否能够放下,放不下就开辟新空间int len = strlen(str);if (len > _capacity - _size)Reserve(2 * _capacity + len);strcat(_str, str);}char& operator[](size_t index){return _str[index];}const char& operator[](size_t index)const //at与[]唯一不同的方式是前者越界会抛出异常,后者越界会崩溃,或者给随机值{return _str[index];}void Reserve(size_t newCapacity){if (_capacity <= newCapacity && _capacity != newCapacity){char* pStr = new char[newCapacity + 1];strcpy(pStr, _str);delete[] _str;_str = pStr;_capacity = newCapacity;}}void ReSize(size_t newSize, char c){if (newSize > _size){/*自己实现memset函数size_t size = _size;for (size_t i = 0; i < newSize; ++i){pStr[size++] = c;}delete[] _str;_str = pStr;*/Reserve(newSize);memset(_str + _size, c, newSize - _size);_str[newSize] = '\0';_capacity = newSize;}else if (newSize < _size){/*char* pStr = new char[newSize + 1];for (size_t i = 0; i < newSize; ++i){pStr[i] = _str[i];}delete[] _str;_str = pStr;*/memset(_str + newSize, '\0', 1);}_size = newSize;}int Size()const{return _size;}bool Empty()const{return (0 == _size);}int Find(char c, size_t pos = 0){for (size_t i = pos; i < _size; ++i){if (c == _str[i])return i;}return npos;}int rFind(char c){for (size_t i = _size - 1; i > 0; --i){if (c == _str[i])return i;}return npos;}//返回c格式的字符串const char* C_str()const //返回c格式的字符串{return _str;}void Swap(String& s){swap(_str, s._str);swap(_size, s._size);swap(_capacity, s._capacity);}//从_str中返回从pos开始的size个字符的子字符串String StrSub(size_t pos, size_t size) {//统计pos之后的所有字符数量int len = strlen(_str + pos); //如果size超过了字符串实际长度,将size设置为pos之后的所有字符数量if (len < size) size = len;//将_str中从pos位置开始的字符串传递size个回去return String(_str + pos, size); }
//析构函数~String(){if (_str){delete[] _str;_str = nullptr;_capacity = 0;_size = 0;}}private:char* _str;size_t _capacity; //容量size_t _size; //有效字符的个数const static int npos;
};const int String::npos = -1;