1. 标准库类型string
标准库类型string表示可变长字符序列,也就是我们所说的字符串。string包含在std命名空间中,且必须在使用前包含头文件。
1.2 常用接口
string作为一种新类型,相较于C中的字符数组,加之以C++中访问对象中的各种功能函数,在操作上有了许多方便。
1.2.1 *构造接口
下面使用几个例子了解string类构造对象的各种方法:
#include <iostream> #include <string> using namespace std; int main() { string s1 = "hello"; string s2 = s1; string s3("world"); string s4(s3); string s5(5,'a'); cout << s1 << endl; cout << s2 << endl; cout << s3 << endl; cout << s4 << endl; cout << s5 << endl; return 0; }
结果:
hello
hello
world
world
aaaaa
- s1和s3是等价的,都是以一个常量字符串初始化对象,创建一个副本,可以认为赋值符号右边的字符串是一个匿名对象;
- s2和s4是等价的,都是拷贝构造;
- s5是被初始化为5个连续的
a
。
【注意】
C++为了兼容C,在字符串最后会添加一个\0
,即使是空串:
void test2() { string s = ""; } int main() { test2(); return 0; }
通过调试可以看到,s
中的内容是\0
。
1.2.2 功能接口
长度or容量
void test3() { string s = "hello"; cout << s.size() << endl; cout << s.length() << endl; cout << s.capacity() << endl; } int main() { test3(); return 0; }
结果:
5
5
22
- size&&length:返回字符串有效字符长度。它们没有什么不同,只是名字上的差别,因为size的意思更有“个数”的意思,所以一般使用size;
- capacity:string类对象的总空间的大小,即容量。在这里,默认容量是22个字节(char);
其他
void test4() { string s1 = "hello"; string s2; cout << "s1 isEmpty?" << s1.empty() << endl; cout << "s2 isEmpty?" << s2.empty() << endl; s1.clear(); cout << "s1 isEmpty?" << s1.empty() << endl; cout << "s1 capacity:" << s1.capacity() << endl; s1.reserve(25); cout << "s1 capacity:" << s1.capacity() << endl; string s3 = "world"; s3.resize(8,'a'); cout << s3 << endl; } int main() { test4(); return 0; }
结果:
s1 isEmpty?0
s2 isEmpty?1
s1 isEmpty?1
s1 capacity:22
s1 capacity:31
worldaaa
- empty():是空串则返回true,否则返回false;
- clear():清空字符串(有效字符);
- reserve(size_t n):;为字符串预留空间(有效字符);
- resize(size_t n, char c):将有效字符改成n个,多余的空间用字符c填充。
【注意】
- clear()不会改变capacity;
- resize(size_t n)和resize(size_t n, char c),如果n比capacity大,会自动扩容,增大capacity;如果n比capacity小,则不会改变capacity;
- reserve()同上。
1.2.3 访问及遍历
operator[]
实际使用直接像数组一样使用[]
即可。这里仅强调是重载过的。
void test5() { string s1 = "hello"; for(int i = 0; i < s1.size(); i++) { cout << s1[i] << " "; } } int main() { test5(); return 0; }
结果:
h e l l o
[i]
作用:返回i位置的字符,const string类对象的调用。
begin()&&end()
void test5() { string s1 = "hello"; std::basic_string<char>::iterator i = s1.begin(); for(;i < s1.end(); i++) { cout << *i << " "; } }
结果:
h e l l o
begin()和end()都是迭代器,暂时把它当做指针,在STL会学习他。
- begin():获取第一个字符的迭代器;
- end():获取最后一个字符的下一个位置的迭代器。
1.2.4 修改操作
push_back(char c)、append(const char* s)、operator+=
void test6() { string s1 = "hell"; s1.push_back('o'); cout << s1 << endl; s1.append(" world"); cout << s1 << endl; s1 += "!!!"; cout << s1 << endl; } int main() { test6(); return 0; }
结果:
hello
hello world
hello world!!!
- push_back(char c):在字符串后尾插字符c;
- append(const char* s):在字符串后追加一个新的字符串s;
- operator+=:在字符串后追加字符串或字符。
【注意】
- 追加字符时,三者都可,值得注意的是append(),需要写成:append(1,c);为了方便,常使用+=。
- 为了合理利用空间,可以使用reserve提前为字符串预留空间。
c_str
void test7() { string s = "hello"; const char* c = s.c_str(); cout << c << endl; } int main() { test7(); return 0; }
结果:
hello
c_str(),返回一个指向数组的指针,该数组包含以空字符结尾的字符序列(即 C 字符串),表示字符串对象的当前值。此数组包含构成字符串对象值的相同字符序列,以及末尾的附加终止空字符 ( \0
)。
【注意】
- 此函数只能获取对应的字符串;
- 返回的字符串是被const修饰的,原因是c_str()本身被const修饰。
npos、find、rfind
void test8() { string s1 = "hello"; cout << s1.find('o') << endl; cout << s1.find('o', 5) << endl; cout << s1.find('p') << endl; cout << string::npos << endl; } int main() { test8(); return 0; }
结果:
4
18446744073709551615
18446744073709551615
18446744073709551615
- find(char c, size_t pos):从字符串下标pos位置往后开始,往后查找c字符,找到则返回下标,否则返回npos;rfind则从下标pos位置往前开始;
- npos:这是一个定义在string类内部的公有静态成员变量,它的值是-1,类型是size_t,也就是正数;按无符号整型打印-1,这是一个很大的数。
size_t:即unsigned int或unsigned long。在底层实现中,所有下标都是size_t类型,因为下标不可能为负数。为了统一,接收下标使用的临时变量尽量也使用size_t定义。
substr
void test9() { string s1 = "hello"; cout << s1.substr(1,7) << endl; } int main() { test9(); return 0; }
结果:
ello
- substr(size_t pos, size_t n):在字符串中从下标pos位置开始,往后截取含有n个字符的子串并返回。
1.2.5 string类非成员函数
operator+
void test10() { string s1 = "hello"; string s2 = s1 + " world"; cout << s2 << endl; } int main() { test10(); return 0; }
- operator+能实现简单的字符串拼接,包括字符、字符串、string对象。
【注意】
operator+的底层实现是传值返回,效率较低,在模拟实现string类时会了解。
operator>>、operator<<
void test11() { string s1; cin >> s1; cout << s1 << endl; } int main() { test11(); return 0; }
输入:
hello
输出:
hello
- 其实前面一直在使用重载的输入和输出运算符,作用就和它本身一样,将字符串读取或写入内存中。
getline()
void test12() { string s1 = "hello"; getline(cin, s1); cout << s1 << endl; } int main() { test12(); return 0; }
输入:
world
输出:
world
- getline(istream& is, string& str):获取一行字符串。从输入流istream is中提取字符并储存在字符串str中,然后返回istream类型的引用,以判断功能是否正常。
比较大小
void test13() { string s1 = "hello"; string s2 = "world"; if(s1 == s2) cout << "s1 == s2" << endl; if(s1 < s2) cout << "s1 < s2" << endl; if(s1 > s2) cout << "s1 > s2" << endl; if(s1 >= s2) cout << "s1 >= s2" << endl; if(s1 <= s2) cout << "s1 <= s2" << endl; if(s1 != s2) cout << "s1 != s2" << endl; } int main() { test13(); return 0; }
结果:
s1 < s2
s1 <= s2
s1 != s2
- 这些比较大小的操作符都是比较字符串字符的ascii值,直到不同的就返回结果。类似C语言中的strcmp函数。
2. 练习
2.1 仅仅反转字母
【思路】
和字符串反转思路类似,在限制下标的同时,在字符串的头和尾分别移动,如果遇到字母则停止,然后交换。
【注意】
- 接收下标的临时变量最好使用size_t类型;
- 在循环内进行查找操作时,用if只能移动一次。用循环必须在循环内再限制下标,否则可能会出现死循环;
- 使用了标准库内置的swap()函数和isalpha()函数。
【代码】
class Solution { public: string reverseOnlyLetters(string s) { if(s.empty()) return s; size_t begin = 0, end = s.size() - 1; while(begin < end) { while(!isalpha(s[begin]) && begin < end) begin++; while(!isalpha(s[end]) && begin < end) end--; swap(s[begin], s[end]); begin++; end--; } return s; } };
2.2 字符串中的第一个唯一字符
【思路】
类似计数排序的思路:时间换空间。新开一个数组,遍历字符串,为每个小写字母计数;然后再遍历一次字符串,如果字母出现的次数等于1,则返回下标,否则返回-1;
【注意】
- 保持一致,依然使用size_t处理下标;
- 以0-25为下标给26个小写“挖坑”的简便方法:用字母减去字母表中第一个字母a,即得到当前字母在字母表中的序号-1。
class Solution { public: int firstUniqChar(string s) { int a[26] = { 0 }; for(size_t i = 0; i < s.size(); i++) { a[s[i] - 'a']++; } for(size_t i = 0; i < s.size(); i++) { if(a[s[i] - 'a'] == 1) return i; } return -1; } };
2.3 字符串最后一个单词的长度
【思路】
非常简单,得到字符串长度,从后计数,直到遍历完或者遇到空格停止。
【注意】
C++的标准输入流对象cin
不支持输入字符串,因为空格或回车会被认为是分隔符。我们使用getline()函数代替cin>>s
。
【代码】
#include <iostream> #include <string> using namespace std; int main() { string s; getline(cin, s); size_t end = s.size() - 1; int count = 1; while (end--) { if (s[end] != ' ') { count++; } else break; } cout << count << endl; }
2.4 验证回文串
【思路】
根据题意,大写转小写,移除所有数字字符,言外之意这就是稍后要比较的前提。
思路简单,头尾同时遍历,分别遇到非字母字符就跳过,然后比较。相同则继续走,不同则返回false。
【注意】
- 大写转小写:由于ASCII表中小写字母都比对应的大写字母多32,所以只需遍历+=32即可。
- 同第一题,非字母字符迭代同样要用while,且要限制begin<end,避免出现死循环。如果最后错误有溢出,很有可能是出现了死循环。
【代码】
class Solution { public: void toSmallAlpha(string& s) { for (int i = 0; i < s.size(); i++) { if (s[i] >= 'A' && s[i] <= 'Z') s[i] += 32; } } bool isNumberOrAlpha(char ch) { return isalpha(ch) || isdigit(ch); } bool isPalindrome(string s) { if (s.empty()) return false; toSmallAlpha(s); int begin = 0, end = s.size() - 1; while (begin < end) { while (begin < end && !isNumberOrAlpha(s[begin])) begin++; while (begin < end && !isNumberOrAlpha(s[end])) end--; if (s[begin] == s[end]) { begin++; end--; } else return false; } return true; } };
2.5 字符串相加
【思路】
逆序遍历两个字符串,然后分别相加当前字符对应的数字,增加一个保存进位的临时变量tmp,用作下一次相加的对象之一。经过进位处理,得到当前数字对于的字符,并将它尾插到新字符串尾部,直到两个数字字符串都被遍历完毕。最后得到的字符串是倒序的,需要反转后再返回。
【注意】
- 迭代的条件:至少还有一个没有遍历完,也就是所有数字字符都要遍历到;
- 数字和它对应的字符相互转换的方法同字母;
- 关于对进位的处理:搭配%和/使用,逻辑较简洁,但是这样效率可能不是很高;因为两个个位数相加最大值为18,所以最多进1,tmp为0和1两种状态。但是这样做需要考虑极端情况:999+1,最后一个进位无法尾插,所以在最后一次进位时尾插tmp。
【代码】
class Solution { public: string addStrings(string num1, string num2) { int end1 = num1.size() - 1; int end2 = num2.size() - 1; string str; int tmp = 0, Num = 0, i = 0, Num1 = 0, Num2 = 0; while (end1 >= 0 || end2 >= 0) { if(end1 >= 0) Num1 = num1[end1--] - '0'; if(end2 >= 0) Num2 = num2[end2--] - '0'; tmp = tmp + Num1 + Num2; if (tmp < 10) { str.push_back(tmp + '0'); tmp = 0; } else { str.push_back((tmp - 10) + '0'); tmp = 1; if (end1 == -1 && end2 == -1) str.push_back(tmp + '0'); } Num1 = Num2 = 0; } reverse(str.begin(), str.end()); return str; } };
2.6 反转字符串 II
【思路】
从题目翻译过来的意思:每隔k个反转一次,如果剩余不够k个,就全部反转。
其实就是一个定长的区间内反转,然后移动固定距离。控制步长和步数,也就控制了控制了反转的区间。
【注意】
- 如何让一个定长区间移动固定距离:让左右端点同时移动(步数*步长)个单位长度;
- 除例子中的情况是反转长度为k的区间外,剩下的都是直接全部反转;由于第二种情况是最后一次反转,步长的累加放在第一种情况就好。
【代码】
class Solution { public: //反转函数 void reverseSubstr(string& s, int begin, int end) { while (begin < end) { swap(s[begin++], s[end--]); } } string reverseStr(string s, int k) { int num = s.size(), count = 0; int begin = 0, end = k - 1;//begin和end限制了步长 while (num >= 0) { if (num < k) { reverseSubstr(s, begin + (2 * k) * count, s.size() - 1); } else { reverseSubstr(s, begin + (2 * k) * count, end + (2 * k) * count); count++;//统计步数 } //num是剩余长度,每次用掉2k个 num -= (2 * k); } return s; } };
2.7 反转字符串中的单词 III
【思路】
正向反向遍历都可以,主要是确定反转的单词区间和控制下标不要越界。
反向遍历:用begin和end下标限制反转区间,首先将end置为最后一个元素下标,然后用循环找空格(假设有空格)的下标,空格后一个位置就是begin,以此类推。
【注意】
- 需要考虑只有一个单词的字符串,即没有空格。在找空格的下标的循环中,要限制下标不越界,既避免了死循环,也能兼顾找不到空格的情况。
【代码】
class Solution { public: void reverseSubstr(string& s, int begin, int end) { while (begin < end) { swap(s[begin++], s[end--]); } } string reverseWords(string s) { int cur = s.size() - 1; int begin = 0, end = 0; int pos = 0; while(cur >= 0) { end = cur; while(cur >= 0 && s[cur] != ' ') { cur--; } begin = cur + 1; reverseSubstr(s, begin, end); cur--; } return s; } };
2.8 字符串相乘
【思路】
根据题意,是要返回一个新字符串,
【代码】
class Solution { public: string multiply(string num1, string num2) { int len1 = num1.size(), len2 = num2.size(); int len = len1 + len2; //一定要初始化新串的值,否则后面会出现随机值 string ret(len, '0'); for(int i = len1 - 1; i >= 0; i--) { for(int j = len2 - 1; j >= 0; j--) { //tmp是当前位之和 int tmp = (num1[i] - '0') * (num2[j] - '0') + (ret[i + j + 1] - '0'); //当前的最后一位,得 ret[i + j + 1] = tmp % 10 + '0'; ret[i + j] += tmp / 10; } } for(int i = 0; i < len; i++) { if(ret[i] != '0') return ret.substr(i); } return "0"; } };
2.9 找出字符串中第一个只出现一次的字符
【思路】
遍历字符串,使用内置类string的接口:find_first_of(char ch)和find_last_of(char ch),前者是找到ch第一次出现的位置,后者是找到ch最后一次出现的位置,找不到则返回npos(-1)。如果只出现一次,那么这两个接口的返回值都是相同的。另一种情况就是不存在只出现一次的字符,则在遍历完毕前最后一次打印-1。
【代码】
#include<iostream> #include<string> using namespace std; int main(){ string s; getline(cin, s); for(int i = 0; i < s.size(); i++) { if(s.find_first_of(s[i]) == s.find_last_of(s[i])) { cout << s[i] << endl; break; } if(i == s.size() - 1) cout << "-1" << endl; } return 0; }
3. 模拟实现内置类string
了解接口的底层实现,能熟练且深刻地掌握string类各种常用接口的用法。而模拟实现某个类或接口,也是面试时常见的问题。
当然,内置类string有一百多个接口,在这里只实现最常用的,大部分接口都是被重载的。
为了区分内置类,取名为Mystring
,除此之外,也可以在自定义命名空间内使用string作为自定义字符串类,以区分内置string类。
3.1 成员变量
string底层是一个能自动扩容的字符数组,所以需要有一个变量记录容量和字符的个数。除此之外,还有最重要的字符数组,它是被new出来的。
- 容量:size_t _capacity;
- 数量:size_t _size;
- 字符数组:char* str;
- npos:static const size_t npos = -1;
许多容器都包含npos,它表示“找不到”的情况的返回值。既然如此,npos是公有静态成员变量。
private: size_t _size; size_t _capacity; char* _str; public: const static size_t npos = -1;
3.2 *构造函数&&析构函数
- 构造函数:由于底层是一个动态数组所以使用new动态分配内存,之所以不使用malloc,是为了方便;
- 析构函数:相对地,使用delete释放new分配的内存,并将容量和数量计数器置零,指针置空。
浅拷贝出现的问题就是它们指向同一个空间,所以需要在构造函数中开一个新空间(可以使用初始化列表)。
//构造函数 Mystring(const char* str = "") { _size = strlen(str); _capacity = _size; _str = new(char[_capacity + 1]); strcpy(_str, str); } //析构函数 ~Mystring() { delete[] _str; _size = 0; _str = nullptr; _capacity = 0; }
【注意】
这里的构造函数给str以缺省值""
,是为了符合内置string默认空串为\0
的做法。除此之外,申请内存的大小比_capacity
多1,就是为了保存这个\0
。
值得注意的是,_size即个数,不包含末尾的\0
。
构造函数中的初始化列表不是必要的,而且它可能会出现初始化顺序的问题。
【另辟蹊径】
向系统申请内存,然后将字符拷贝到内存中,这样做是符合常理且传统的,有的人提出了新的方法:直接使用传入的字符串,交换它们成员变量的值,以达到构造Mystring对象的目的。
由于稍后还要使用这里的思路,所以将交换功能封装为一个函数。
void swap(Mystring& tmp) { std::swap(tmp._capacity, _capacity); std::swap(tmp._size, _size); std::swap(tmp._str, _str); } Mystring(const Mystring& str) { string tmp(str._str); swap(tmp); }
3.3 operator[]
- operator[]:重载[]操作符,是为了让使用Mystring能像使用数组一样自然。
【注意】
- 接收下标的类型最好为size_t,下面将不再赘述;
- 返回值为引用类型的原因:a. 为了像数组一样读和写;b. 减少传值拷贝在性能上的消耗;
- 需要判断下标是否越界,由于pos是size_t类型,只需要判断其小于_size即可;
- 运算符重载,还需要有被const修饰的版本,目的是配合用户需要,返回值只能读不能写,下面将不再赘述。
//返回引用是为了读写 char& operator[](size_t pos) { assert(pos < _size); return _str[pos]; } //加const是只读 const char& operator[](size_t pos) const { assert(pos < _size); return _str[pos]; }
3.4 迭代器
迭代器在初学STL会学习到,在这里只需要把它当做指针即可。
【注意】
区间是左闭右开。
//迭代器(begin、end) //可读可写 iterator begin() { return _str; } iterator end() { return _str + _size; } //只读 const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; }
3.5 size()、capacity()
- 获取元素个数和容量。
【注意】
个数和容量均不包括\0
。
size_t size() { return _size; } size_t capacity() { return _capacity; }
3.6 =、+=、+
- +=、+:相当于尾插;
- =:赋值,相当于拷贝构造。
【注意】
- 返回值都是引用类型;
- 深浅拷贝的解释在下面。
//默认=,浅拷贝 // Mystring& operator=(const Mystring& s) // { // if (this != &s) // { // char* pStr = new char[strlen(s._str) + 1]; // strcpy(pStr, s._str); // delete[] _str; // _str = pStr; // } // return *this; // } //新=,深拷贝 Mystring& operator=(Mystring& s) { swap(s); return *this; } Mystring& operator+=(const char* s) { append(s); return *this; } Mystring& operator+=(char* s) { append(s); return *this; } Mystring& operator+(const char* s) { append(s); return *this; } Mystring& operator+(char* s) { append(s); return *this; }
3.7 *拷贝构造【深浅拷贝】
- 对于内置类型:深浅拷贝都是直接拷贝;
- 对于自定义类型:浅拷贝只拷贝它们的地址,深拷贝则完全复制一份,地址不同。
浅拷贝出现的问题就是它们指向同一个空间,所以需要在构造函数中开一个新空间(可以使用初始化列表)。
使用默认的=,实现赋值也是粗暴的浅拷贝,首先会出现内存泄漏,原来的地址被覆盖了,其次还是刚才的问题。如果想直接拷贝值,这样做看起来简单,实际上反而麻烦,因为两个空间的大小会影响太小或太大。
- 传统写法:先释放原空间,然后让它再指向新空间,记得
\0
,然后拷贝,最后更新容量。要允许自身赋值,通过判断地址。 - 新写法:要是不想改变原有数据,创建新的临时对象,然后使用swap构造新对象。
3.8 比较运算符重载
- 各种比较运算符重载,只需要实现其中的两个,剩下的复用即可。但在这里我是直接使用strcmp()。
- 其中比较的是第一个不相同的字符的ASCII值。
bool operator>=(const Mystring& s) const { return !(strcmp((*this)._str, s._str) < 0); } bool operator<=(const Mystring& s) const { return !(strcmp((*this)._str, s._str) > 0); } bool operator==(const Mystring& s) const { return strcmp((*this)._str, s._str) == 0; } bool operator>(const Mystring& s) const { return strcmp((*this)._str, s._str) > 0; } bool operator<(const Mystring& s) const { return strcmp((*this)._str, s._str) < 0; }
3.9 reserve()
这个接口在上面已经提到过。它的功能就是管理容量,传入的参数就是要保存的容量。
- 增容:如果要保存的容量比本身的容量还大,那就是扩容,思路同构造函数;
- 减容:由于内置string类判断字符串终止的标志是
\0
,只需将它放在最后一个位置,然后更新_size。
//更改容量 void reserve(size_t n) { if (n > _capacity)//扩容 { char* tmp = new(char[n + 1]); strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; //元素个数未变 } else//减容,相当于删除 { _str[n] = '\0'; _size = n; } }
3.10 insert()、push_back()、append()
它们分别是:插入符号或字符串,尾插符号,尾接字符串。(实际上通过查阅官方文档可得知:它们的功能远不如此,为了实现方便,所以只实现简单的几个)。
insert():
- 尾插字符:
- 首先要判断容量,不够要扩容,这里使用了常见的三目运算符;
- 其次就是挪动数据,由于size_t的特殊性,-1是一个很大的正数,所以在循环中不能让下标变成-1,否则会越界;
- 然后再插入,最后返回。
- 尾插字符串:
- 思路同上。
**push_back():**复用insert()尾插。
**append():**同上。
【注意】
- 插入后需要更新_size。
- 为什么不使用strcat(),因为效率低。
Mystring& insert(size_t pos, const char ch) { assert(pos <= _size); //扩容 if (_capacity == _size) reserve(_capacity == 0 ? 4 : _capacity * 2); //要挪动的数据个数,再加上\0 /*int num = _size - pos + 1; int tail = _size; while (num--) { _str[tail + 1] = _str[tail]; tail--; }*/ size_t tail = _size + 1; while (tail > pos) { _str[tail] = _str[tail - 1]; tail--; } _str[pos] = ch; _size++; return *this; } Mystring& insert(size_t pos, const char* s) { assert(pos <= _size); size_t len = strlen(s); if (_size + len > _capacity) { reserve(_size + len); } // 挪动数据 size_t end = _size + len; while (end >= pos + len) { _str[end] = _str[end - len]; --end; } strncpy(_str + pos, s, len); _size += len; return *this; } void push_back(char ch) { insert(_size, ch); } void append(const char* s) { insert(_size, s); }
3.11 clear()、erase()
- clear():清空Mystring对象,思路同reserve();
- erase():清空pos位置往后的内容,思路同上。
void clear() { _str[0] = '\0'; _size = 0; _capacity = 0; } void erase(size_t pos) { assert(pos >= 0); _str[pos] = '\0'; _size = pos; }
3.12 operator<<、operator>>
在类和对象中,我们学习了流输入和流输出运算符,简单地说,<<就是将流中的数据提取到显示器(可以是其他设备);>>就是将输入的数据插入到流中,在这里可以认为“流”是内存的一部分,它由各种数据组成。
人们经常使用cin和cout搭配流插入和流提取运算符输入,之所以能支持众多类型,是因为它被重载了。
- operator<<:流提取。遍历字符串,将每个元素重载到ostream对象中,然后一个一个地返回;
- operator>>:流插入。不同于流提取,需要将一定量的数据存入“缓冲区”(buffer),然后将缓冲区的数据插入到流中。通常情况下,在重载中定义一个自定义大小的数组作为缓冲区。注意回车和
\0
是作为字符串结束的标志。
缓冲区:CPU的速度非常快,寄存器更甚,而磁盘的速度相去甚远,数据流是“穿过”它们的,但是由于速度不匹配,这样会造成输入和输出中的一个很快,一个很慢,这样就会出现不必要的等待时间,缓冲区因此而生。
【注意】
- 一般情况重载运算符都在类的内部,或作为友元函数(友元的目的是访问类的成员变量)。但作为友元函数,使用它们就没那么自然,需要
对象名.<<
这样的格式,为了符合日常的习惯,将其写在类的外部,只要能访问类的成员变量即可。 - 可能出现的问题:链接失败。类的声明的代码都写在.h头文件中,可以尝试将>>和<<的重载的实现写在.cpp文件中。原因是它被编译了两次,使用
#program once
或上面的方法也许可以解决。
ostream& operator<<(ostream& os, Mystring& s) { for (size_t i = 0; i < s.size(); i++) { os << s[i]; } return os; } istream& operator>>(istream& is, Mystring& s) { s.clear(); const int N = 32; char ch = is.get(); //自定义缓冲区 char buffer[N]; size_t i = 0; while (ch != ' ' && ch != '\n') { //达到缓冲区容量,最后加上\0 if (i == N - 1) { buffer[i] = '\0'; //连接 s += buffer; i = 0; } //迭代 buffer[i++] = ch; ch = is.get(); } //连接 buffer[i] = '\0'; s += buffer; return is; }
string还有许多其他内置类型转换,例如to_string......