1.标准库中的string类
简单概述:
1.string是表示字符串的字符串类
2.该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作
3.string在底层实际是:basic_string模板类的别名,typedef basic_string<cahr, char_traits, allcoator> string
4.注意:这个类独立于所使用的编码类处理字节:如果用来处理多字节或者变成字节(如UTF-8)的序列,这个类的所有成员以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作
5.在使用string类时,必须包含头文件及using namespace std;
string常用接口
1.string类对象的常见构造
2.string类对象的容量操作
注意:
1.size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()
2.clear()只是将string中有效字符清空,不改变底层空间大小
3.resize(size_t n)与resize(size_t n, char n)都是将字符串有效字符个数改变到n个,不同的是当字符个数增多时:前者用0来填充多出的元素空间,后者用字符c来填充多出的元素空间,注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减小,底层空间总大小不变
4.reserve:为string预留空间,不改变有效元素个数当reserve的参数小于string的底层空间总大小时,reserve不会改变容量大小
string类对象的访问操作
string类对象的修改操作
注意:
1.在string尾部追加字符时,s.push_back(c)/s.append(1,c)/s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅仅可以连接单个字符,而且可以连接字符串
2.对string操作时,如果能够大概预估放多少字符,可以先通过reserve把空间预留好
2.string类的模拟实现
#include <iostream>
#include <assert.h>
using namespace std;
class String{
public:
typedef char* iterator;
typedef const char* const_iterator;
//构造函数
String(char *str = ""){
assert(str != nullptr);
//_size:有效字符数量,不包含'\0'
_size = strlen(str);
_str = new char[_size + 1];
strcpy(_str, str);//while(*str1++ = *str2++);
_capacity = _size;
}
//拷贝构造
String(const String& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
String tmp(s._str);
Swap(tmp);
}
//交换函数
void Swap(String& s) {
swap(_str, s._str);
swap(_size, s._size);
swap(_capacity, s._capacity);
}
//赋值运算符重载
String& operator=(String s){
Swap(s);
return *this;
}
//[]形式访问字符,可读可改
char& operator[](size_t pos) {
assert(pos < _size);
return _str[pos];
}
//[]只读,不可改
const char& operator[](size_t pos) const {
assert(pos < _size);
return _str[pos];
}
//迭代器的使用
//返回begin位置,可读可写
iterator begin() {
return _str;
}
//返回end的位置,可读可写
iterator end() {
return _str + _size;
}
//返回begin位置,只读
const_iterator begin()const {
return _str;
}
//返回end位置,只读
const_iterator end()const {
return _str + _size;
}
//析构函数
~String(){
if (_str){
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
}
char* c_str(){
return _str;
}
//在尾部插入
void PushBack(char c) {
//检查空间是否足够
if (_size == _capacity){
//初始化容量设置为15
size_t newC = (_capacity == 0 ? 15 : 2 * _capacity);
Reserve(newC);
}
_str[_size++] = c;
_str[_size] = '\0';
}
//扩容
void Reserve(size_t n) {
if (n > _capacity){
char *tmp = new char[n + 1];
strcpy(tmp, _str);
delete _str;
//让指针指向新空间
_str = tmp;
//更新容量
_capacity = n;
}
}
//尾删
void PopBack(){
if (_size > 0){
_str[_size--] = '\0';
}
}
//设置有效字符的大小
void Resize(size_t n, char c = '\0') {
if (n > _capacity){
Reserve(n);
}
if (n > _size){
memset(_str + _size, c, n - _size);
}
_size = n;
_str[_size] = '\0';
}
//从指定位置删除元素,可以指定删除长度
void Erase(size_t pos, size_t len) {
assert(pos < _size);
if (pos + len >= _size){
_size = pos;
_str[_size] = '\0';
}
else{
//从pos+len位置开始挪动数据,每个数据向前挪动len位置
for (int i = pos + len; i <= _size; i++){
_str[pos++] = _str[i];
}
_size -= len;
}
}
//追加
void Append(const char *str) {
size_t sz = strlen(str);
if (_size + sz > _capacity){
Reserve(_size + sz);
}
//从'\0'位置开始插入字符串str
strcpy(_str + _size, str);
_size += sz;
}
//插入一个字符
void Insertc(size_t pos, char c) {
//检查容量:是否增容
//元素挪动
//更新size,补'\0'
assert(pos <= _size);
if (_size == _capacity){
size_t newCapacity = (_capacity == 0 ? 15 : _capacity * 2);
Reserve(newCapacity);
}
size_t end = _size;
while (end > pos){
_str[end] = _str[end - 1];
end--;
}
_size += 1;
_str[pos] = c;
_str[_size] = '\0';
}
//插入一个字符串
void Insertstr(size_t pos, const char *str){
//检查容量:是否增容
//挪动元素
//更新size,补'\0'
assert(pos <= _size);
size_t len = strlen(str);
//不能使用给_capacity*2的方法,如果2倍之后还是还是小于_size+len就会出错
if (_size + len > _capacity){
Reserve(_size + len);
}
size_t end = _size + len;
while (end > pos + len - 1){
_str[end] = _str[end - len];
end--;
}
//插入字符串,不能使用strcpy(),strcpy(_str+pos, str)
//会把str中的'\0'一起拷贝过去,导致有效内容被覆盖
//strncpy可以,指定拷贝内容的大小strncpy(_str+pos, str, len)
while (*str){
_str[pos++] = *str++;
}
_size += len;
}
//查找一个字符
size_t find(char c) {
for (int i = 0; i < _size; i++){
if (_str[i] == c){
return i;
}
}
return npos;
}
//查找一个字符串
size_t find(const char *str, size_t pos = 0){
const char *posPtr = subStrPos(_str + pos, str);
if (posPtr){
return posPtr - _str;
}
return npos;
}
String& operator+=(char c){
PushBack(c);
return *this;
}
String& operator+=(const char* str) {
Append(str);
return *this;
}
String& operator+=(const String& s) {
*this += s._str;
return *this;
}
private:
char *_str;
size_t _size;
size_t _capacity;
public:
//静态成员在类外初始化
static const size_t npos;
};
const size_t String::npos = -1;
String operator+(const String& s1, const String& s2){
String tmp(s1);
return tmp + s2;
}
String operator+(const String& s1, const char *str){
String tmp(s1);
return tmp + str;
}
String operator+(const String& s1, char c) {
String tmp(s1);
return tmp + c;
}
const char* subStrPos(const char *dest, const char *src) {
const char *dt = dest;
const char *sc = src;
while (*dt){
//如果第一个字符相等,匹配剩余字符
if (*dt == *sc){
const char *mathdest = dt + 1;
const char *mathsrc = sc + 1;
//匹配剩余字符
while (*mathsrc && *mathdest){
//如果当前带匹配字符不相等,停止匹配
if (*mathdest != *mathsrc)
break;
//继续向后匹配
mathdest++;
mathsrc++;
}
if (*mathsrc == '\0'){
//匹配成功,返回当前子串在目标字符串中的首元素出现的位置
return dt;
}
}
dt++;
}
return nullptr;
}
浅拷贝:编译器只是将对象中的值拷贝过来。
注意:如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道资源已经被释放,以为还有效,所以当继续对资源进行操作时,就会发生访问违规。
深拷贝:如果一个类中涉及到资源管理,其拷贝构造函数、赋值运算符重载函数以及析构函数必须显示给出。
写是拷贝:是在浅拷贝的基础上添加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数,在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象是资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。