
需要云服务器等云产品来学习Linux的同学可以移步/-->腾讯云<--/-->阿里云<--/-->华为云<--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。
目录
一、std::swap和std::string::swap的区别
如果用std::swap交换两个string对象,将会发生1次构造和2次赋值,也就是三次深拷贝;而使用std::string::swap仅交换成员,代价较小。

32位环境下,string大小为28字节,64位下为40字节。
- char _buff[16];
- char* ptr;
- size_t size;
- size_t capacity;
根据内存对齐规则,32位环境下,string大小为28字节,64位下为40字节。
当string存储的字符小于16时,存储于_buff中,大于等于16,存储于ptr指向的堆空间里。
这样的目的是为了减少小块的内存碎片,也是一种以空间换时间的做法。

32位环境下,string大小为4字节,64位下为8字节。
g++下,string是通过写时拷贝实现的,内部只有一个指针,该指针指向一块堆空间,用于存储字符串,内部包含如下字段。
- struct _Rep_base
- {
- size_type _M_length;//空间总大小
- size_type _M_capacity;//字符串有效长度
- _Atomic_word _M_refcount;//引用计数
- };

g++中的string和vs中的string完全不一样,g++中拷贝构造一个对象,两个对象存储的字符地址是一模一样的。
g++中string拷贝时为了提高效率,默认进行浅拷贝,使用引用计数来保证空间不被多次析构。如果其中一个string提出修改,那么将会发生写时拷贝。(赌徒思想,就是赌用户不写,不写就赚)
- string(const char* s = "")
- {
- _size = strlen(s);//_size和_capacity均不包含'\0'
- _capacity = _size;
- _arr = new char[_size + 1];
- memcpy(_arr, s, _size + 1);
- }
构造函数用缺省值,能够满足空串的构造。
这里设计_size和_capacity均不包含'\0'。_arr的空间多new一个,用于储存'\0'。
再将形参的内存拷贝至_arr中,即可完成构造。
写法1:老老实实的根据string对象的私有变量进行拷贝构造。
- string(const string& s)
- {
- _size = s._size;//_size和_capacity均不包含'\0'
- _capacity = s._capacity;
- _arr = new char[_capacity + 1];
- memcpy(_arr, s._arr, _capacity + 1);
- }
写法2:通过构造一个临时对象,将这个临时对象的私有变量全部和*this的私有变量交换。
注意拷贝构造需要先将_arr初始化为nullptr,防止后续tmp拿到随机地址。(tmp销毁将调用析构函数,对一块随机地址的空间进行析构程序将会崩溃)
- void swap(string& s)
- {
- std::swap(_arr, s._arr);
- std::swap(_size, s._size);
- std::swap(_capacity, s._capacity);
- }
- string(const string& s)
- :_arr(nullptr)//防止交换后tmp._arr为随机值,析构出错
- {
- string tmp(s.c_str());//构造
- swap(tmp);
- }
写法1:同样的老实人写法。这种写法要防止自己给自己赋值!
- string& operator=(const string& s)
- {
- if (this != &s)//防止自己给自己赋值
- {
- _size = s._size;
- _capacity = s._capacity;
- char* tmp = new char[_capacity + 1];
- delete[] _arr;
- _arr = tmp;
- memcpy(_arr, s._arr, _capacity + 1);
- }
- return *this;
- }
写法2:通过构造临时变量tmp,完成赋值。这种写法无需担心自己给自己赋值的情况,并且_arr无需初始化为nullptr。
- void swap(string& s)
- {
- std::swap(_arr, s._arr);
- std::swap(_size, s._size);
- std::swap(_capacity, s._capacity);
- }
- string& operator=(const string& s)
- {
- string tmp(s.c_str());//构造
- swap(tmp);
- return *this;
- }
- ~string()
- {
- _size = _capacity = 0;
- delete[] _arr;
- _arr = nullptr;
- }
- //string的size()接口
- size_t size()const//右const修饰*this,这样const和非const对象均可调用
- {
- return _size;
- }
- //string的c_str()接口
- const char* c_str()const
- {
- return _arr;
- }
- //string的capacity()接口
- size_t capacity()const
- {
- return _capacity;
- }
- //string的clear()接口
- void clear()
- {
- _arr[0] = '\0';
- _size = 0;
- }
- //string的判空
- bool empty()const
- {
- return _size == 0 ? false : true;
- }
如果函数形参不发生改变的,无脑加const修饰。
只有指针和引用会有const权限问题。
- char& operator[](size_t pos)//普通对象,可读可写
- {
- assert(pos < _size);
- return _arr[pos];
- }
- const char& operator[](size_t pos)const//const对象,仅读
- {
- assert(pos < _size);
- return _arr[pos];
- }
让字符串进行下标式的访问,需要重载两个operator[]函数,正常对象去调可读可写,const对象调用只读。
- typedef char* iterator;
- iterator begin()
- {
- return _arr;
- }
- iterator end()//end指向字符串的'\0'
- {
- return _arr + _size;
- }
string的迭代器是字符指针,写完迭代器就可以用迭代器实现访问、修改了。
范围for的底层也是一个迭代器,但是范围for底层只认begin()和end(),如果和自己实现的迭代器接口名称对不上,那么范围for将无法使用。
- //sring的reserve接口, 如果预开空间小于现有空间,将不会改变容量。
- void reserve(size_t n = 0)
- {
- if (n + 1 > _capacity)
- {
- char* tmp = new char[n + 1];
- memset(tmp, '\0', n + 1);
- memcpy(tmp, _arr, _size);
- delete[] _arr;
- _arr = tmp;
- _capacity = n;
- }
- }
- //sring的resize接口
- void resize(size_t n, char c)
- {
- //判断n的大小
- if (n > _capacity)
- {
- reserve(n);
- memset(_arr + _size, c, n - _size);
- _size = n;
- }
- else
- {
- _arr[n] = '\0';
- _size = n;
- }
- }
reserve是扩容,可以用于预开空间,防止频繁的空间申请。申请一块n+1大小的空间,将该空间全部初始化'\0',再将_arr中的数据拷贝至tmp中,释放_arr,_arr指向tmp。
在resize中需要考虑_size扩容和缩容的问题。
- string& push_back(const char c)
- {
- //判断容量
- if (_size == _capacity)
- {
- size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;//防止出现空串的情况
- reserve(newCapacity);
- }
- _arr[_size++] = c;
- return *this;
- }
- string& append(const char* s)
- {
- //判断容量
- size_t len = strlen(s);
- if (_size + len > _capacity)
- {
- reserve(_size + len);
- }
- strcpy(_arr + _size, s);
- _size += len;
- return *this;
- }
- string& operator+=(const char c)
- {
- push_back(c);
- return *this;
- }
- string& operator+=(const char* s)
- {
- append(s);
- return *this;
- }
写push_back要考虑到原对象为空串的情况(即_capacity为0)。
+=可以复用push_back和append。
- string& insert(size_t pos, char c)
- {
- assert(pos < _size);
- //判断容量
- if (_size == _capacity)
- {
- reserve(_capacity + 1);
- }
- //挪动数据
- for (size_t i = _size; i > pos; --i)
- {
- _arr[i] = _arr[i - 1];
- }
- _arr[pos] = c;
- ++_size;
- return *this;
- }
- string& insert(size_t pos, const char* s)
- {
- size_t len = strlen(s);
- //判断容量
- if (len + _size > _capacity)
- {
- reserve(len + _size);
- }
- //挪动数据
- for (size_t i = _size + len; i > pos + len - 1; --i)
- {
- _arr[i] = _arr[i - len];
- }
- memcpy(_arr + pos, s, len);
- _size += len;
- return *this;
- }
- string& erase(size_t pos, size_t len = npos)
- {
- assert(pos < _size);
- //先判断删到底的情况
- if (len == npos || pos + len >= _size)
- {
- _arr[pos] = '\0';
- _size = pos;
- }
- else
- {
- memcpy(_arr + pos, _arr + pos + len, _size - pos - len);
- _size -= len;
- }
- return *this;
- }
insert接口在挪动数据时,从最后一个元素的后一个(后len个)位置开始覆盖,可以保证不出现size_t 类型越界的情况。
erase接口,需要分类讨论字符串是否删到底。
注意,这个pos是const static成员,C++语法中,只有指针和整型的const static成员是可以在类中进行初始化的。
- size_t find(const char c, size_t pos = 0)const
- {
- assert(pos < _size);
- for (size_t i = pos; i < _size; ++i)
- {
- if (_arr[i] == c)
- {
- return i;
- }
- }
- return npos;
- }
- size_t find(const char* s, size_t pos = 0)const
- {
- assert(pos < _size);
- const char* p = strstr(_arr, s);
- if (p != nullptr)
- {
- return _arr - p;
- }
- return npos;
- }
从指定位置找字符或字符串,找到了,返回第一个匹配字符/子串的下标。
- //流插入和流提取的重载时为了自定义类型的输入输出
- inline ostream& operator<<(ostream& out, const string& s)//这里访问的到私有,所以可以不用写成友元函数
- {
- for (size_t i = 0; i < s.size(); ++i)//流插入按照_size打印,c_str找到'\0'结束打印
- { //比如我在字符串中间插入一个'\0',打印结果不一样
- out << s[i];
- }
- return out;
- }
- inline istream& operator>>(istream& in, string& s)
- {
- s.clear();//用之前先清空s
- //in >> c;//流提取不会识别空格和换行
- char c = in.get();
- char buff[128] = { '\0' };//防止频繁扩容
- size_t i = 0;
- while (c != ' ' && c != '\n')
- {
- if (i == 127)
- {
- s += buff;
- i = 0;
- }
- buff[i++] = c;
- c = in.get();
- }
- if (i > 0)
- {
- buff[i] = '\0';
- s += buff;
- }
- return in;
- }
因为string提供了访问私有的接口,所以流插入和流提取可以不用重载成string类的友元函数。
对于流提取,如果频繁的尾插,会造成频繁扩容。而且C++的扩容和C语言的扩容不一样,C++使用new不能原地扩容,只能异地扩容,异地扩容就会导致新空间的开辟、数据的拷贝、旧空间释放。为了防止频繁扩容,我们可以创建一个可以存储128字节的数组,在这个数组中操作,这个数组满了就尾插至对象s中。
为什么不能用getline,而是要一个字符一个字符尾插呢?因为流提取遇到空格和'\n'会结束提取,剩余数据暂存缓冲区,如果是getline的话,遇到空格是不会停止读取的。
- #pragma once
- #define _CRT_SECURE_NO_WARNINGS 1
- #include
- #include
- using std::cout;
- using std::cin;
- using std::endl;
- using std::ostream;
- using std::istream;
- namespace jly
- {
- class string
- {
- public:
- void swap(string& s)
- {
- std::swap(_arr, s._arr);
- std::swap(_size, s._size);
- std::swap(_capacity, s._capacity);
- }
- //构造函数
- string(const char* s = "")
- {
- _size = strlen(s);//_size和_capacity均不包含'\0'
- _capacity = _size;
- _arr = new char[_size + 1];
- memcpy(_arr, s, _size + 1);
- }
- //拷贝构造
- //写法1
- //string(const string& s)
- //{
- // _size = s._size;//_size和_capacity均不包含'\0'
- // _capacity = s._capacity;
- // _arr = new char[_capacity + 1];
- // memcpy(_arr, s._arr, _capacity + 1);
- //}
- //写法2
- string(const string& s)
- :_arr(nullptr)//防止交换后tmp._arr为随机值,析构出错
- {
- string tmp(s.c_str());//构造
- swap(tmp);
- }
- //赋值运算符重载
- //写法1
- //string& operator=(const string& s)
- //{
- // if (this != &s)//防止自己给自己赋值
- // {
- // _size = s._size;
- // _capacity = s._capacity;
- // char* tmp = new char[_capacity + 1];
- // delete[] _arr;
- // _arr = tmp;
- // memcpy(_arr, s._arr, _capacity + 1);
- // }
- // return *this;
- //}
- //写法2
- string& operator=(const string& s)
- {
- string tmp(s.c_str());//构造
- swap(tmp);
- return *this;
- }
- //析构函数
- ~string()
- {
- _size = _capacity = 0;
- delete[] _arr;
- _arr = nullptr;
- }
- //string的size()接口
- size_t size()const//右const修饰*this,这样const和非const对象均可调用
- {
- return _size;
- }
- //string的c_str()接口
- const char* c_str()const
- {
- return _arr;
- }
- //string的capacity()接口
- size_t capacity()const
- {
- return _capacity;
- }
- //string的clear()接口
- void clear()
- {
- _arr[0] = '\0';
- _size = 0;
- }
- //string的判空
- bool empty()const
- {
- return _size == 0 ? false : true;
- }
- //对operator[]进行重载
- char& operator[](size_t pos)//普通对象,可读可写
- {
- assert(pos < _size);
- return _arr[pos];
- }
- const char& operator[](size_t pos)const//const对象,仅读
- {
- assert(pos < _size);
- return _arr[pos];
- }
- //迭代器
- typedef char* iterator;
- iterator begin()const
- {
- return _arr;
- }
- iterator end()const//end指向字符串的'\0'
- {
- return _arr + _size ;
- }
- //string的reserve接口,如果预开空间小于现有空间,将不会改变容量。
- void reserve(size_t n=0)
- {
- if (n + 1 > _capacity)
- {
- char* tmp = new char[n + 1];
- memset(tmp, '\0', n + 1);
- memcpy(tmp, _arr, _size);
- delete[] _arr;
- _arr = tmp;
- _capacity = n;
- }
- }
- //string的resize接口
- void resize(size_t n, char c='\0')
- {
- //判断n的大小
- if (n > _capacity)
- {
- reserve(n);
- memset(_arr + _size,c,n-_size);
- _size = n;
- }
- else
- {
- _arr[n] = '\0';
- _size = n;
- }
- }
- //插入删除查找相关接口
- string& push_back(const char c)
- {
- //判断容量
- if (_size == _capacity)
- {
- size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;//防止出现空串的情况
- reserve(newCapacity);
- }
- _arr[_size++] = c;
- return *this;
- }
- string& append(const char* s)
- {
- //判断容量
- size_t len = strlen(s);
- if (_size + len > _capacity)
- {
- reserve(_size + len);
- }
- strcpy(_arr+_size,s);
- _size += len;
- return *this;
- }
- string& operator+=(const char c)
- {
- push_back(c);
- return *this;
- }
- string& operator+=(const char* s)
- {
- append(s);
- return *this;
- }
- string& insert(size_t pos, char c)
- {
- assert(pos < _size);
- //判断容量
- if (_size == _capacity)
- {
- reserve(_capacity + 1);
- }
- //挪动数据
- for (size_t i = _size; i > pos; --i)
- {
- _arr[i] = _arr[i - 1];
- }
- _arr[pos] = c;
- ++_size;
- return *this;
- }
- string& insert(size_t pos, const char* s)
- {
- size_t len = strlen(s);
- //判断容量
- if (len + _size > _capacity)
- {
- reserve(len + _size);
- }
- //挪动数据
- for (size_t i = _size + len; i > pos + len - 1; --i)
- {
- _arr[i] = _arr[i - len];
- }
- memcpy(_arr + pos, s, len);
- _size += len;
- return *this;
- }
- string& erase(size_t pos, size_t len = npos)
- {
- assert(pos<_size);
- //先判断删到底的情况
- if (len == npos || pos + len >= _size)
- {
- _arr[pos] = '\0';
- _size = pos;
- }
- else
- {
- memcpy(_arr + pos, _arr + pos + len,_size-pos-len);
- _size -= len;
- }
- return *this;
- }
- size_t find(const char c, size_t pos = 0)const
- {
- assert(pos < _size);
- for (size_t i = pos; i < _size; ++i)
- {
- if (_arr[i] == c)
- {
- return i;
- }
- }
- return npos;
- }
- size_t find(const char* s, size_t pos = 0)const
- {
- assert(pos < _size);
- const char* p = strstr(_arr, s);
- if (p != nullptr)
- {
- return _arr - p;
- }
- return npos;
- }
- private:
- char* _arr;
- size_t _size;
- size_t _capacity;
- const static size_t npos = -1;//只有const static整型、指针成员变量可以在类中定义,其他类型不行
- };
- //流插入和流提取的重载时为了自定义类型的输入输出
- inline ostream& operator<<(ostream& out, const string& s)//这里访问得到私有,所以可以不用写成友元函数
- {
- for (size_t i = 0; i < s.size(); ++i)//流插入按照_size打印,c_str找到'\0'结束打印
- { //比如我在字符串中间插入一个'\0',打印结果不一样
- out << s[i];
- }
- return out;
- }
- inline istream& operator>>(istream& in, string& s)
- {
- s.clear();//用之前先清空s
- //in >> c;//流提取不会识别空格和换行
- char c=in.get();
- char buff[128] = { '\0' };//防止频繁扩容
- size_t i = 0;
- while (c != ' ' && c != '\n')
- {
- if (i == 127)
- {
- s += buff;
- i = 0;
- }
- buff[i++] = c;
- c = in.get();
- }
- if (i > 0)
- {
- buff[i] = '\0';
- s += buff;
- }
- return in;
- }
- //测试函数
- void test1()
- {
-
- }
- }