赞
踩
我们常说的ASCII
编码表,实际上是早期,美国根据自己的语言和符号,制定的一个和计算机二进制编码,一一对应的表格,全名叫American Standard Code for Information Interchange。
它一共收录了128个常用字符,其中最核心的就是包括大小写英文字母在内的52个字符。但是,随着计算机的推广,渐渐有了用二进制编码表示各国符号文字的需求。
中国的汉字上万,想用一个字节(8bit)来存储显然是不够的,至少也要两个字节,能表示2^16次方个字符,但是有一些生僻字仍然无法表示,还是不够用。而且不止中国有这种需求,各个国家都需要对应的编码表来表示自己国家的符号和文字。
于是,有人就研制出统一码(Unicode
),也叫万国码,为每一种语言中的每个字符都设定了统一并且唯一的二进制编码。基于万国码,又细分出了很多不同的方案。
打个比方,像中国的文字就比较多,给一个字节的大小来表示汉字就很难表示的下。但是其他国家的文字可能没有中国那么多,它们可以用一个字节就表示出所有文字,并不需要那么大空间。
基于以上种种问题,主要产生了三类方案,叫UTF系列:UTF-8
、UTF-16
、UTF-32
。下面我们主要看UTF-8
:
首先,UTF-8
兼容ASCII
,一个字节编,格式是0开头。其次,相对常见的汉字用两个字节编,第一个字节开头是110,第二个字节开头10;生僻一点的汉字用三个字节编,格式在表格中;再生僻一点的汉字就用四个字节来编。这里我们只需要记住一点,常见的汉字都用两个字节来编。
可以发现,UTF-8
的格式并不统一,是一个变长的编码。然而有些时候,我们比较需要统一的格式,做文字工作的时候也并不需要兼容ASCII
,这时又出现了UTF-16
和UTF-32
,它们的格式就比较统一了。UTF-32
不管你每个值是多大,都用四个字节统一表示,常见的汉字和不常见的汉字都编到一起,但是比较浪费空间。UTF-16
又进行了一些折中,具体的编码方式大家可以自行查阅,这里不再介绍了。
基于上述几种不同的编码方式,C++在早期又搞出了wchar_t
的类型,它是一个变长字符串类型,一个字符就占两个字节。wstring
使用来存储wchar_t
的容器。
wchar_t ch;
cout << sizeof(ch) << endl; // 大小为2字节
后来C++11觉得wchar_t
不是很规范,就又搞出了char16_t
和char32_t
。char16_t
一个字符两个字节,char32_t
一个字符四个字节。
平常我们用UTF-8
用的最多,string
类型就是适合存储用UTF-8
编成的字符串,字符类型是char
;u16string
适合存UTF-16
编成的字符串,字符类型是char16_t
;u32string
适合存UTF-32
变成的字符串,字符类型是char32_t
。
UTF
系列是适用于全世界的编码表,但是中华文化博大精深,为了更贴合汉字,中国自己又搞出了gbk
编码。windows
很懂中国,windows
下的很多东西默认就是gbk
编码。而Linux
下更多使用的则是UTF-8
。
上面这些知识,我们在日常的学习中一般不会碰到。但是我们以后可能会做一些国际业务,就需要用到其他的编码方式。并且windows
下的一些接口也涉及char16_t
或char32_t
的字符串,在windows
编程中可能会用到。
在模拟实现的过程中,我们选择用一个自己定义的命名空间,将库中的string
和我们自己写的string
区分开来。同时,采用将成员函数写在类内的方式定义成员函数,都写在string.h
头文件中,不将声明和定义分离。测试文件命名为string_test.cpp
。
namespace LHY { class string { public: string() // 处理空字符串的情况 :_str(new char[1]{'\0'}) // 默认开一个字节空间,放`\0` ,_size(0) ,_capacity(0) {} string(const char* str) // 用常量字符串来初始化 :_size(strlen(str)) ,_capacity(_size) { _str = new char[_capacity + 1]; // 加一是要多存一个'\0' strcpy(_str, str); // strcpy会拷贝`\0` } string(const string& s) { _str = new char[s.capacity() + 1]; // capacity这个函数后面会讲,功能就是返回s的容量 strcpy(_str, s._str); _size = s._size; _capacity = s._capacity; } ~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; } const char* c_str() const // 暂时没有重载流插入,用这个函数配合默认的流插入来打印数据 { return _str; } // ... private: char* _str; size_t _size; size_t _capacity; }; }
测试:
int main()
{
// 测试构造函数
LHY::string s1("hello world");
cout << s1.c_str() << endl;
// 测试拷贝构造
LHY::string s2(s1);
cout << s2.c_str() << endl;
return 0;
}
几个注意的点:
构造函数要写两个,一个无参的,一个有参的,参数是字符类型指针。并且要注意考虑末尾的\0
。
初始化列表的执行顺序是按照声明的顺序执行的,这一点尤其要注意。例如,在写有参的构造函数时,不能将_str = new char[_capacity + 1];
这段代码写在初始化列表中,因为_str
是先声明的,如果放在初始化列表中会先执行这段代码,然而此时_capacity
还未定义,值是未知的,所以给字符串开辟的空间也是未知的。
不能直接把str
赋值_str
,涉及权限放大。
namespace LHY { class string { public: size_t size() const { return _size; } size_t capacity() const { return _capacity; } char& operator[](size_t pos) // 查看加修改 { assert(pos < _size); return _str[pos]; } const char& operator[](size_t pos) const // 仅供查看,不能修改,与const类型适配 { assert(pos < _size); return _str[pos]; } // ... private: char* _str; size_t _size; size_t _capacity; }; }
测试:
int main() { LHY::string s1("hello world"); const LHY::string s2("hello world"); // 遍历,测试重载const [] for (size_t i = 0; i < s2.size(); i++) { cout << s2[i] << " "; } cout << endl; // 遍历加修改,测试重载[] for (size_t i = 0; i < s1.size(); i++) { s1[i] = '*'; cout << s1[i] << " "; } cout << endl; return 0; }
注意:
要提供两个重载的[]
函数,一个只读,一个可写可读,要包含传const
类型的情况。而且为了使用遍历,还要提供一个size()
函数返回大小,顺便把capacity()
函数也实现了。
namespace LHY { class string { public: typedef char* iterator; typedef const char* const_iterator; iterator begin() { return _str; } const_iterator begin() const { return _str; } iterator end() { return _str + _size; // 指向'\0' } const_iterator end() const { return _str + _size; } // ... private: char* _str; size_t _size; size_t _capacity; }; }
测试:
int main() { LHY::string s1("hello world"); const LHY::string s2("hello world"); // 测试迭代器 LHY::string::iterator it = s1.begin(); while (it != s1.end()) { cout << *it << " "; ++it; } cout << endl; // 测试const迭代器 LHY::string::const_iterator cit; cit = s2.begin(); while (cit != s2.end()) { cout << *cit << " "; ++cit; } cout << endl; for (auto ch : s1) // 范围for底层完全是迭代器,并且有非常严格的规范 { cout << ch << " "; } cout << endl; // 相当于 /*it = s1.begin(); while (it != s1.end()) { auto ch = *it; cout << ch << " "; ++it; } cout << endl;*/ for (auto& ch : s1) { ch = '*'; cout << ch << " "; } cout << endl; // 相当于 /*it = s1.begin(); while (it != s1.end()) { auto& ch = *it; ch = '*'; cout << ch << " "; ++it; } cout << endl;*/ return 0; }
注意:
char*
类型,一种是const char*
类型。for
是傻瓜式地替换成迭代器,稍微改一改就编不过。比如在定义时将begin()
写成Begin()
,迭代器能跑,但是范围for
跑不了,会显示找不到begin()
。namespace LHY { class string { public: void reserve(size_t n) { if (n > _capacity) // reserve在n < _capacity的情况下不缩容也不用扩容 { char* tmp = new char[n + 1]; // 多开一个空间给'\0' strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } void push_back(char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); // 这里要用一个三目操作符,解决_capacity为0的情况 } _str[_size] = ch; ++_size; _str[_size] = '\0'; } void append(const char* str) { size_t len = strlen(str); if (_size + len > _capacity) // 这里不要有扩二倍的想法,因为可能不够 { reserve(_size + len); } strcpy(_str + _size, str); _size += len; } string& operator+=(char ch) { push_back(ch); return *this; } string& operator+=(const char* str) { append(str); return *this; } // ... private: char* _str; size_t _size; size_t _capacity; }; }
测试:
int main() { LHY::string s1 = "hello world"; s1.push_back('x'); cout << s1.c_str() << endl; s1.append("yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"); cout << s1.c_str() << endl; LHY::string s2("hello world"); s2 += 'x'; cout << s2.c_str() << endl; s2 += "yyyyyyyyyyyyyyyyyy"; cout << s2.c_str() << endl; LHY::string s3; s3 += 'x'; cout << s3.c_str() << endl; s3 += "hello world"; cout << s3.c_str() << endl; return 0; }
注意:
push_back()
和append()
时有一个关键问题,就是容量不够的时候要扩容,这时我们就要再实现一个reserve()
。reserve()
在扩容时要多new
一个字节的空间,放\0
。append()
时,容量不够,不要直接扩二倍,因为可能不够,直接扩_size + len
即可。reserve()
时不能不判断n
是否大于_capacity
,因为库中的reserve()
在n <= _capacity
时是不做处理的,要和库中保持一致。push_back()
时,如果要尾插的字符串是一个空字符串,_capacity
为0,则需要特殊处理,直接给4个字节的空间。因为如果不给的话,_capacity * 2
还是0,扩容扩了个寂寞。先看一段有问题的insert()
:
namespace LHY { class string { public: void insert(size_t pos, char ch) { assert(pos <= _size); // 等于_size就是尾插 if (_size == _capacity) { reserve(_capacity * 2); } size_t end = _size; while (end >= pos) { _str[end + 1] = _str[end]; --end; } _str[pos] = ch; _size++; } // ... private: char* _str; size_t _size; size_t _capacity; }; }
测试:
int main()
{
LHY::string s = "hello world";
s.insert(s.size(), '%');
cout << s.c_str() << endl;
s.insert(5, '%');
cout << s.c_str() << endl;
s.insert(0, '%');
cout << s.c_str() << endl;
return 0;
}
发现尾插和中间的插入都可以,但是头插崩了,这是为什么?
按照上面代码的逻辑,我们是想让end
一直--
,此时pos
等于0,当end
小于pos
时,等于-1时,在pos
位置插入数据,循环停止。但是,end
可以等于-1吗?显然是不可以的,end
的类型是size_t
无符号的整型,如果让end
等于-1,编译器会把end
理解成一个非常大的数,是无符号整型的最大值。所以end
永远无法小于pos
,死循环。
有同学可能想,将end
的类型改成int
不就行了吗?答案是还是不行。因为pos
的类型是size_t
,编译器在判断end >= pos
时,会做整型提升,让end
提升成size_t
类型。所以解决方案就只有两种,一种是直面整型提升,将pos
在比较时强转成int
:end >= (int)pos
;另一种是让end
直接指向_size + 1
,\0
的后一个位置,然后让_str[end] = _str[end - 1]
,当end
等于pos
时,循环停止。
namespace LHY { class string { public: void insert(size_t pos, char ch) { assert(pos <= _size); // 等于_size就是尾插 if (_size == _capacity) { reserve(_capacity * 2); } size_t end = _size + 1; while (end > pos) { _str[end] = _str[end - 1]; --end; } _str[pos] = ch; _size++; } // 这个重载不详细讲了,大家可以自己试着实现一下,锻炼一下自己的编码能力 void insert(size_t pos, const char* str) { assert(pos <= _size); size_t len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } // 挪数据 size_t end = _size + 1; while (end > pos) { _str[end + len - 1] = _str[end - 1]; --end; } // 插入 for (size_t i = 0; i < len; i++) { _str[pos++] = str[i]; } _size += len; } // ... private: char* _str; size_t _size; size_t _capacity; }; }
测试:
int main() { LHY::string s = "hello world"; s.insert(s.size(), '%'); cout << s.c_str() << endl; s.insert(5, '%'); cout << s.c_str() << endl; s.insert(0, '%'); cout << s.c_str() << endl; s.insert(0, "xxx"); cout << s.c_str() << endl; s.insert(s.size(), "xxxxxxxxxxxxxxxxxxxxxxxxxx"); cout << s.c_str() << endl; s.insert(5, "xx"); cout << s.c_str() << endl; return 0; }
想要模拟erase()
,首先要模拟实现npos
。
namespace LHY { class string { public: // ... private: char* _str; size_t _size; size_t _capacity; public: // npos可能会显示的调用,所以用public修饰 // const static size_t npos = -1; // 特例,只有const修饰的静态整型才可以在类内初始化 // const static double npos = 1.1; // 不支持 const static size_t npos; }; const size_t string::npos = -1; }
我们都知道,静态成员变量是不会走初始化列表的,不能在声明处直接给值。所以只能在类内声明,类外定义。但是const
修饰的静态整型是一个例外,它可以在类内定义,可以直接在声明处给值。这样的语法实际上是很不明所以的,换成const static double npos = 1;
都编不过,相当于给整型开了个特例。
namespace LHY { class string { public: void erase(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || pos + len >= _size) { _str[pos] = '\0'; _size = pos; } else { size_t begin = pos + len; while (begin <= _size) { _str[begin - len] = _str[begin]; begin++; } _size -= len; } } // ... private: char* _str; size_t _size; size_t _capacity; public: // npos可能会显示的调用,所以用public修饰 //const static size_t npos = -1; // 特例,只有const修饰的静态整型才可以在类内初始化 const static size_t npos; }; const size_t string::npos = -1; }
测试:
int main()
{
LHY::string s("hello world");
s.erase(0, 3);
cout << s.c_str() << endl;
s.erase(6, 100);
cout << s.c_str() << endl;
s.erase(1);
cout << s.c_str() << endl;
return 0;
}
namespace LHY { class string { public: bool operator<(const string& s) const { return strcmp(_str, s.c_str()) < 0; } bool operator==(const string& s) const { return strcmp(_str, s.c_str()) == 0; } bool operator<=(const string& s) const { return *this < s || *this == s; } bool operator>(const string& s) const { return !(*this <= s); } bool operator>=(const string& s) const { return !(*this < s); } // ... private: char* _str; size_t _size; size_t _capacity; public: // npos可能会显示的调用,所以用public修饰 //const static size_t npos = -1; // 特例,只有const修饰的静态整型才可以在类内初始化 const static size_t npos; }; const size_t string::npos = -1; }
测试:
int main()
{
LHY::string s1 = "zhangsan";
LHY::string s2("lisi");
cout << (s1 < s2) << endl;
cout << (s1 <= s2) << endl;
cout << (s1 > s2) << endl;
cout << (s1 == s2) << endl;
cout << (s1 >= s2) << endl;
return 0;
}
我们在重载比较运算符时,要注意复用代码,利用好之前写好的函数。
流插入和流提取不能重载为成员函数,要写在类外,在类外声明在类外定义。
看一段错误的流提取写法:
istream& operator>>(istream& in, string& s)
{
char ch;
in >> ch;
while (ch != ' ' && ch != '\n')
{
s += ch;
in >> ch;
}
return in;
}
很多同学想当然的就把流提取重载写成了这样,发现黑框框会像一个无底洞一样,一直让你输入,不会停止,陷入死循环,这是为什么?通过调试可以发现,ch
无法提取到空格或者\n
,导致死循环。
回忆一下C语言中,如何获取到空格字符。用scanf()
是不行的,因为scanf()
这个函数将空格和换行符认为是不同数据间的分隔符。要想提取到空格或者换行符,可是使用函数getcahr()
。
C++中也是同理,我们要想提取到空格,需要用到istream
类中的一个函数get()
,它的作用就类似于getchar()
,可以帮助我们提取到空格和换行符。
namespace LHY { class string { public: void clear() { _str[0] = '\0'; _size = 0; } // ... private: char* _str; size_t _size; size_t _capacity; public: // npos可能会显示的调用,所以用public修饰 //const static size_t npos = -1; // 特例,只有const修饰的静态整型才可以在类内初始化 const static size_t npos; }; const size_t string::npos = -1; ostream& operator<<(ostream& out, const string& s) { /*for (size_t i = 0; i < s.size(); i++) { out << s[i]; } return out;*/ for (auto ch : s) out << ch; return out; } istream& operator>>(istream& in, string& s) { s.clear(); // 先把原来的数据清空 char ch; ch = in.get(); while (ch != ' ' && ch != '\n') { s += ch; ch = in.get(); } return in; } }
测试:
int main()
{
LHY::string s("hello world");
cout << s << endl;
cin >> s;
cout << s << endl;
return 0;
}
注意:
string
类来说,流插入和流提取的重载不需要友元声明。因为使用重载的[]
就可以访问到要输出的成员变量。+=
这个重载,如果不清空数据,这就是一个尾插的逻辑。for
来简化语法。优化流提取的扩容:
按照上述流提取的写法,如果输入的字符串很大,s
可能会经历很多次扩容,能不能减少扩容次数,进行一些优化?看下面一段代码:
istream& operator>>(istream& in, string& s) { s.clear(); char buff[129]; // 129是个数 size_t i = 0; char ch; ch = in.get(); while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == 128) // i是下标,i等于128时指向的是buff中的第129个数据 { buff[i] = '\0'; s += buff; i = 0; } ch = in.get(); } if (i != 0) { buff[i] = '\0'; s += buff; } return in; }
上面代码的逻辑是:提取够128个有效数据,扩容一次(执行一次+=
),用if
处理一下不够128个有效数据(比如只有100个数据)的情况,和解决多余的字符(比如有效数据有200个,处理剩下的72个)。
namespace LHY { class string { public: void resize(size_t n, char ch = '\0') { if (n <= _size) { _str[n] = '\0'; _size = n; } else { reserve(n); while (_size < n) { _str[_size++] = ch; } _str[_size] = '\0'; } } // ... private: char* _str; size_t _size; size_t _capacity; public: // npos可能会显示的调用,所以用public修饰 //const static size_t npos = -1; // 特例,只有const修饰的静态整型才可以在类内初始化 const static size_t npos; }; const size_t string::npos = -1; }
测试:
int main()
{
LHY::string s("hello world");
s.resize(5);
cout << s << endl;
s.resize(7, 'x');
cout << s << endl;
return 0;
}
s1
和s2
是两个LHY::string
类型的对象,将s1
赋值给s2
,有同学可能就会考虑到容量的问题,万一s2
的容量不够,是不是要扩容?要是s2
容量太大了,用不用缩容?其实,在赋值这个地方考虑容量这些复杂的情况,就是自找麻烦,因为异地的挪动数据不可避免(除非s1
和s2
容量一样)。我们不如直接统一处理,统一将s2
先释放,然后再为s2
新开一块和s1
容量一样大的空间,再将数据一一拷贝。
namespace LHY { class string { public: string& operator=(const string& s) { if (this != &s) // 不能自己给自己赋值 { char* tmp = new char[s._capacity + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; _size = s._size; _capacity = s._capacity; } return *this; } // ... private: char* _str; size_t _size; size_t _capacity; public: // npos可能会显示的调用,所以用public修饰 //const static size_t npos = -1; // 特例,只有const修饰的静态整型才可以在类内初始化 const static size_t npos; }; const size_t string::npos = -1; }
测试:
int main()
{
LHY::string s1("hello world");
LHY::string s2;
LHY::string s3;
s3 = s2 = s1;
cout << s3 << endl;
cout << s2 << endl;
return 0;
}
注意:
this != &s
。namespace LHY { class string { public: size_t find(char ch, size_t pos = 0) // 从pos位置开始找字符ch { assert(pos < _size); for (size_t i = pos; i < _size; i++) { if (_str[i] == ch) { return i; } } return npos; // 找不到 } size_t find(const char* sub, size_t pos = 0) // 从pos位置开始找子串sub { const char* p = strstr(_str + pos, sub); // 返回子串第一次出现位置的指针,找不到就返回空指针 if (p) { return p - _str; } else { return npos; } } // ... private: char* _str; size_t _size; size_t _capacity; public: // npos可能会显示的调用,所以用public修饰 //const static size_t npos = -1; // 特例,只有const修饰的静态整型才可以在类内初始化 const static size_t npos; }; const size_t string::npos = -1; }
测试:
int main()
{
LHY::string s("hello world");
cout << s.find('l') << endl;
cout << s.find('x') << endl;
return 0;
}
namespace LHY { class string { public: string substr(size_t pos, size_t len = npos) // 从pos位置开始,取len个字符 { string s; size_t end = pos + len; if (len == npos || pos + len >= _size) // 有多少取多少 { len = _size - pos; end = _size; } s.reserve(len); // 提前开好空间 for (size_t i = pos; i < end; i++) { s += _str[i]; } return s; } // ... private: char* _str; size_t _size; size_t _capacity; public: // npos可能会显示的调用,所以用public修饰 //const static size_t npos = -1; // 特例,只有const修饰的静态整型才可以在类内初始化 const static size_t npos; }; const size_t string::npos = -1; }
测试:
int main() { LHY::string s = "https://blog.csdn.net/weixin_73870552?spm=1000.2115.3001.5343"; LHY::string sub1, sub2, sub3; size_t i1 = s.find(':'); if (i1 != string::npos) // 如果find函数找不到目标字符,就会返回npos sub1 = s.substr(0, i1); else cout << "':'no found" << endl; size_t i2 = s.find('/', i1 + 3); // 从i1 + 3的位置开始查找 if (i2 != string::npos) sub2 = s.substr(i1 + 3, i2 - (i1 + 3)); // 左闭右开,右开减左闭就是数据个数 else cout << "'/'no found" << endl; sub3 = s.substr(i2 + 1); cout << sub1 << endl; cout << sub2 << endl; cout << sub3 << endl; return 0; }
namespace LHY { class string { public: void swap(string& s) { std::swap(_str, s._str); // 直接交换指针 std::swap(_size, s._size); std::swap(_capacity, s._capacity); } // ... private: char* _str; size_t _size; size_t _capacity; public: // npos可能会显示的调用,所以用public修饰 //const static size_t npos = -1; // 特例,只有const修饰的静态整型才可以在类内初始化 const static size_t npos; }; const size_t string::npos = -1; }
测试:
int main()
{
LHY::string s1("hello world");
LHY::string s2("xxx");
s2.swap(s1);
cout << s2 << endl;
return 0;
}
有了swap
函数,我们可以在很多地方都复用这个swap
,将原来很多需要自己完成的工作,借助swap
,封装起来。
1.现代版的拷贝构造函数
namespace LHY { class string { public: // 现代版拷贝构造 string(const string& s) :_str(nullptr) ,_size(0) ,_capacity(0) { string tmp(s._str); // 构造函数 swap(tmp); // this -> swap(tmp); } // ... private: char* _str; size_t _size; size_t _capacity; }; }
我们来分析一下这个拷贝构造:我们直接利用构造函数,先构造了一个临时变量tmp
。然后利用swap
函数,将tmp
和this
指针指向的对象交换。而且不能不初化_str
,并且保险起见把_size
和_capacity
也一并初始化了,最好把它们都初始化成0。因为如果我们不写初始化,内置类型编译器默认是不做处理的,_str
就默认是随机值,我们把这个随机值交换给tmp
后,tmp
出了作用域要调析构函数,释放一个随机空间,很可能会崩。
传统的写法中我们要自己开空间,自己拷贝内容,这个现代版的拷贝构造就把这些工作全部交给了别人(swap
和构造函数)。
2.现代版的赋值
namespace LHY { class string { public: // 现代赋值 s2 = s1 string& operator=(const string& s) { if (this != &s) { string tmp(s); // 这里调用拷贝构造 swap(tmp); } return *this; } // ... private: char* _str; size_t _size; size_t _capacity; }; }
分析这个赋值:假如现在有两个LHY::string
类型对象s1
、s2
,我们让s2 = s1
。按照传统写法,我们需要自己先创建一个临时变量tmp
存储s1
字符部分的数据,然后再释放s2
的_str
原来指向的空间,把tmp
赋值给s2
,再将s1
的_size
和_capacity
给到s2
,这些工作都需要我们自己完成。
现在,我们直接调拷贝构造,让s1
中的所有数据给到tmp
,然后再交换tmp
和s2
,最终tmp
出作用域调用析构函数销毁,还不用我们自己释放s2
字符部分的数据,爽的起飞。
3.极致的现代赋值
namespace LHY { class string { public: string& operator=(string tmp) { swap(tmp); return *this; } // ... private: char* _str; size_t _size; size_t _capacity; }; }
不用判断是否自己给自己赋值,而且这种现代写法是通用的,我们只要写好了拷贝构造就可以这样来赋值,对所有的类都是可行的。
这里我们选择将声明和定义分开来写,都写在同一个命名空间中,不分文件写。
.h头文件:
#pragma once #include<iostream> #include<stdio.h> #include<assert.h> using namespace std; namespace LHY { class string { public: // 迭代器 typedef char* iterator; typedef const char* const_iterator; iterator begin() { return _str; } iterator end() { return _str + _size; } const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; } // size()和capacity() size_t size() const { return _size; } size_t capacity() const { return _capacity; } const char* c_str() const { return _str; } // 扩容 void reserve(size_t n); void resize(size_t n, char ch); // 插入删除 void push_back(char ch); void append(const char* str); void insert(size_t pos, char ch); void insert(size_t pos, const char* str); void erase(size_t pos, size_t len); // 运算符重载 char& operator[](size_t pos); const char& operator[](size_t pos) const; string& operator+=(char ch); string& operator+=(const char* str); string& operator=(const string& s); bool operator<(const string& s) const; bool operator==(const string& s) const; bool operator<=(const string& s) const; bool operator>(const string& s) const; bool operator>=(const string& s) const; // 查找 size_t find(char ch, size_t pos); size_t find(const char* sub, size_t pos); string substr(size_t pos, size_t len); string() // 处理空字符串的情况 :_str(new char[1] {'\0'}) , _size(0) , _capacity(0) {} string(const char* str) // 用常量字符串来初始化 :_size(strlen(str)) , _capacity(_size) { _str = new char[_capacity + 1]; // 加一是要多存一个'\0' strcpy(_str, str); } string(const string& s) :_str(nullptr) , _size(0) , _capacity(0) { string tmp(s._str); swap(tmp); } ~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; } void clear() { _str[0] = '\0'; _size = 0; } void swap(string& s) { std::swap(_str, s._str); // 直接交换指针 std::swap(_size, s._size); std::swap(_capacity, s._capacity); } private: char* _str; size_t _size; size_t _capacity; public: //const static size_t npos = -1; // 特例,只有const修饰的静态整型才可以在类内初始化 const static size_t npos; }; const size_t string::npos = -1; ostream& operator<<(ostream& out, const string& s) { for (auto ch : s) out << ch; return out; } istream& operator>>(istream& in, string& s) { s.clear(); char buff[129]; // 129是个数 size_t i = 0; char ch; ch = in.get(); while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == 128) // i是下标,i等于128时指向的是buff中的第129个数据 { buff[i] = '\0'; s += buff; i = 0; } ch = in.get(); } if (i != 0) { buff[i] = '\0'; s += buff; } return in; } void string::reserve(size_t n) { if (n > _capacity) // reserve在n < _capacity的情况下不缩容也不用扩容 { char* tmp = new char[n + 1]; // 多开一个空间给'\0' strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } void string::resize(size_t n, char ch = '\0') { if (n <= _size) { _str[n] = '\0'; _size = n; } else { reserve(n); while (_size < n) { _str[_size++] = ch; } _str[_size] = '\0'; } } void string::push_back(char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size] = ch; ++_size; _str[_size] = '\0'; } void string::append(const char* str) { size_t len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } strcpy(_str + _size, str); _size += len; } void string::insert(size_t pos, char ch) { assert(pos <= _size); // 等于_size就是尾插 if (_size == _capacity) { reserve(_capacity * 2); } size_t end = _size + 1; while (end > pos) { _str[end] = _str[end - 1]; --end; } _str[pos] = ch; _size++; } void string::insert(size_t pos, const char* str) { assert(pos <= _size); size_t len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } // 挪数据 size_t end = _size + 1; while (end > pos) { _str[end + len - 1] = _str[end - 1]; --end; } // 插入 for (size_t i = 0; i < len; i++) { _str[pos++] = str[i]; } _size += len; } void string::erase(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || pos + len >= _size) { _str[pos] = '\0'; _size = pos; } else { size_t begin = pos + len; while (begin <= _size) { _str[begin - len] = _str[begin]; begin++; } _size -= len; } } char& string::operator[](size_t pos) { assert(pos < _size); return _str[pos]; } const char& string::operator[](size_t pos) const { assert(pos < _size); return _str[pos]; } string& string::operator+=(char ch) { push_back(ch); return *this; } string& string::operator+=(const char* str) { append(str); return *this; } string& string::operator=(const string& s) { if (this != &s) { char* tmp = new char[s._capacity + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; _size = s._size; _capacity = s._capacity; } return *this; } bool string::operator<(const string& s) const { return strcmp(_str, s.c_str()) < 0; } bool string::operator==(const string& s) const { return strcmp(_str, s.c_str()) == 0; } bool string::operator<=(const string& s) const { return *this < s || *this == s; } bool string::operator>(const string& s) const { return !(*this <= s); } bool string::operator>=(const string& s) const { return !(*this < s); } size_t string::find(char ch, size_t pos = 0) { assert(pos < _size); for (size_t i = pos; i < _size; i++) { if (_str[i] == ch) { return i; } } return npos; // 找不到 } size_t string::find(const char* sub, size_t pos = 0) { const char* p = strstr(_str + pos, sub); // 返回子串第一次出现位置的指针,找不到就返回空指针 if (p) { return p - _str; } else { return npos; } } string string::substr(size_t pos, size_t len = npos) // 从pos位置开始,取len个字符 { string s; size_t end = pos + len; if (len == npos || pos + len >= _size) // 有多少取多少 { len = _size - pos; end = _size; } s.reserve(len); // 提前开好空间 for (size_t i = pos; i < end; i++) { s += _str[i]; } return s; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。