【C++要笑着学】编码的由来 | basic_string模板类 | string类的常用接口讲解 | 学会查文档(二)

简介: 好久不见!前段时间比较忙,更新频率有所减缓。好在现在快忙完了,我又有时间更文咯,还希望大伙能多多支持!我将会呈现出更多高质量的博客给大家!

Ⅲ.  sting类对象的容量操作

函数名称 功能说明
size     (重点) 返回字符串有效字符长度
length 返回字符串有效字符长度
capacity 返回空间总大小
empty  (重点) 检测字符串是否为空串,是返回true,否则返回 flase
clear    (重点)

清空有效字符

reserve(重点) 为字符串预留空间
resize   (重点) 将有效字符的个数改成n个,多出的空间用字符c填充

我们先讲解其中的一部分:


0x00  让我康康字符串有多长的 size() 和 length()


💬 先看代码:


int main(void)
{
  string s1;
  cin >> s1;
  cout << "你输入了:" << s1 << endl;
  return 0;
}
int main(void)
{
  string s1;
  cin >> s1;
  cout << "你输入了:" << s1 << endl;
  cout << s1.size() << endl;
  cout << s1.length() << endl;
  return 0;
}

56e536eb20b0748616200388a54b3867_69f352b77feb4d35b29373911f4b24a2.png

71ee70397b8f7700fb74adc315f51a4a_81f21db2674f41aaac0012fb968c9eb6.png

📌 注意事项:


①  size() 和 length() 的计算不包含 \0。


    解释:它不包含最后作为结尾标识符的 \0,告诉你的是有效的字符长度。


②  size() 和 length() 的功能都是返回字符串有效长度,功能上没有区别。


    解释: 这是一个 "历史包袱" 问题。


    因为 string 比 STL 出现的还要早一些,所以有了STL容器之后就习惯地给出了 size() 。


    而 length() 是代替传统的C字符串,所以针对C中的 strlen ,给出相应的函数 length() 。


    C++中 string 成员函数 length() 等同于 size() ,功能没有任何区别。


    不信可以看 C++标准库中的 string 中 size() 和 length() 的源代码:


size_type   __CLR_OR_THIS_CALL   length()   const
{   //   return   length   of   sequence
    return   (_Mysize);
}
size_type   __CLR_OR_THIS_CALL   size()   const
{   //   return   length   of   sequence
    return   (_Mysize);
}

0x01  返回字符串最大长度的 max_size

返回字符串可以达到的最大长度。


💬 我们来试试:


int main(void)
{
  string s1;
  cout << s1.max_size() << endl;
  return 0;
}

a928d98397430e3e13eeebee23057a92_4b236247c5af4aafbc8a0baf637bfe48.png

 long long int 最大值  


🧀 小芝士:C++string ax_size():返回字符串最大长度 (4294967294)  2^32=4294967296,其中有两位系统做什么了?


-1被占用为不存在的位置string::npos,因此不能用于长度(2^32-1)。字符串最后一字节保留给\0,因此只能再少一字节(2^32-2)。


0x02  返回空间总大小的 capacity()

容量,代表能存多少个有效字符。


💬 代码演示:


int main(void)
{
  string s1("abcdef");
  cout << s1.capacity << endl;
  return 0;
}

5eab86d119f4032cf2da4f794b24e779_ac78e828905740a7adaf7c4e1fbbee22.png

0x03  清空有效字符的 clear()

📚 clear:清空有效数据


int main(void)
{
  string s1("abcdef");
  cout << "清空前: " << s1 << endl;
  s1.clear();
  cout << "清空后: " << s1 << endl;
  return 0;
}

6f1728026c854a1672d0aaf4fb768012_60a12df60a8f4d0cad4917d5d862b520.png


值得注意的是,clear 只是把数据清了,但是容量还在:


int main(void)
{
  string s1("abcdef");
  cout << "清空前: " << s1 << endl;
  cout << s1.capacity() << endl;
  s1.clear();
  cout << "清空后: " << s1 << endl;
  cout << s1.capacity() << endl;
  return 0;
}

1f9d6e8d3b54f0b037c7bef8cc648b55_14be94d9089445f881893fc2c63bfb3a.png


0x04  开完空间就跑路的 reserve() 和开完空间还给你初始化的 resize()

先说 reserve,这个单词的拼写和 reverse 有点像,但是注意分别!


(reserve:保留    reverse:逆置)


📚 reserve 会改变容量


.reserve(size_t res_arg=0)
为string预留空间,不改变有效元素个数,当reserve的参数小于
string的底层空间总大小时,reserve不会改变容量大小。
s.reserve(1000);  // 请求申请至少能存储1000个数据的容量


我们再来说说 resize。


reserve 开空间,影响容量。而 resize 是开空间,并对这些空间给一个初始值,进行初始化。


(它们的区别类似于 malloc 和 calloc)


举个形象地例子 ——


" reserve 和 resize 都是卖房子的(开空间的),reserve 只是把房子卖给你(开空间),而 resize 是一条龙服务,房子卖给你(开空间)之后还帮你搞装修(初始化),你还可以指定装修风格(初始化内容),如果不指定会按默认的简约风格装修(不指定默认用 \0 初始化)"


💬 看代码:


void TestPushBack() {
  string s1;
  s1.reserve(100);
  string s2;
  s2.resize(100);
}

600f8e81d9e14c39411666528443561f_4dc65feabd1c44d5a8792ca93e412a6f.png

💬 指定 resize 初始化字符:


string s2;
s2.resize(100, 'x');   // 给值,指定初始化字符

8cb08e4876302c1e5d21b3ccbdb97db1_68e24ce3d3f64b43a0f1d8118f5b61bd.png


💬 如果 resize 的大小比你当前的数据还要小:


string s5("hello world");  
s4.resize(5);

4136b97fb464610e7ba46034a2976f3d_5c2affd7ac184d84ba8db7b146b34764.png


resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字 符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的 元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大 小,如果是将元素个数减少,底层空间总大小不变


Ⅳ.  string类对象的访问及遍历操作


函数名称 功能说明
operator[] 返回pos位置的字符,const string类对象调用
begin + end begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
rbegin + rend rbegin获取一个字符的反向迭代器 + rend获取最后一个字符下一个位置的迭代器
范围 for C++11支持更简洁的范围 for 新遍历方式


0x00  访问字符串字符的 operator[] 和 at()

如果访问这个字符串的每一个字符:


int main(void)
{
  string s1("hello world");
  for (size_t i = 0; i < s1.size(); i++) {
  cout << s1[i] << " ";
  }
  cout << endl;
  return 0;
}

这里可以和数组一样使用 for 循环遍历,然后通过 s1[i] 来获取每个字符。


我们熟知的数组,可以用 "方括号" 直接解引用,即数组的第 i 个位置解引用。


这里很像,只是在 string 这里它作为函数调用,这就是 operator[]  ,并且等价于:


cout << s1[i] << " ";        
cout << s1.operator[](i) << " ";   // 取字符串第i个位置的字符的引用

💬 我们不仅能读,还可以写。比如我们给每个字符+1:


int main(void)
{
  string s1("hello world");
  for (size_t i = 0; i < s1.size(); i++) {
  s1[i] += 1;  // 对s1每个位置的字符都+1
  }
  cout << s1 << endl;
  return 0;
}

2fb7559dfe247cb9b912f56d654fa1a1_8d180dd691ba4034bb55d23c780b36e8.png


🤔 思考:operator[] 可以写的原因是什么?

f324dbb7f74ffa0b29a811efd30fbeae_2453fbfde89e4fad8a8d48f859e4e021.png



❓ string 把这里设计成引用的目的是什么?operator[] 的底层是这样设计的:


char& operator[] (size_t pos)
{
    return _str[pos];
}

d3194f9824de6b77713377243b05acf2_ad9947a9e794427887221460a712b37f.png


这里的 & 不是为了减少拷贝而使用的,因为就算拷贝一个 char 代价也不大。


这里 operator[] 使用引用返回,是为了能够支持修改返回的变量!


顺便一提,除了 operator[] 还有一个 at() ,at() 也是早期支持的一个接口。


💬 它和 operator[] 用处是一样的,at() 是像函数一样去使用的:


int main(void)
{
  string s1("hello world");
  for (size_t i = 0; i < s1.size(); i++) {
  s1.at(i) += 1;
  }
  cout << s1 << endl;
  return 0;
}


7dfdf5d6dcb096caf46586d4654b2204_a5aa625226a7478390e9b2d3bca77e41.png

at() 和 operator[] 的区别 —— 它们检查越界的方式不一样。


operator[] 是使用断言处理的,断言是一种激进的处理手段。


char& operator[] (size_t pos)
{
    assert(pos < _size);
    return _str[pos];
}

而 at() 是比较温柔的处理方式,如果 pos >= size 就 throw 一个异常对象出去。

5d1be1e55f0808db924439df16b9e880_b2d1ab0a230c4d40bc1fdb606f88306b.png

但是一般情况还是 operator[] 用的比较多一些。


0x01 初识迭代器

📚 迭代器是 STL 六大组件之一,是用来访问和修改容器的。


💬 用一下试试:


int main(void)
{
  string s1("hello");
    // 迭代器
  string::iterator it = s1.begin();
  while (it != s1.end()) {
  cout << *it << " ";
  it++;
  }
  cout << endl;
  return 0;
}

7c5ca70c6564e7beace180b8418faa7c_9598eaefabeb4873a02f36752e5595e3.png


注意,这是本教程第一次提到迭代器,后续我会详细讲解。


如果你是第一次接触 "迭代器的概念",不妨可以先把迭代器想象成 "像指针一样的类型"。

287f5f5f26b0e12b46d2247c962599cb_465185bca99d4296976f0e01590b1790.png

(可以试着想象遍历的过程)


❓ 迭代器遍历的意义是什么呢?


对于 string,无论是正着遍历,倒着遍历,下标 + [] 都足够好用,为什么还要迭代器呢?


当然,对于 string,下标和 [] 确实足够好用,我们在学习C语言的时候就先入为主地使用了,


确实可以不用迭代器。但是如果是其他容器(数据结构)呢?


比如 list、map / set  不支持 下标 + [] 遍历,迭代器就排上用场了,这就是迭代器存在的意义。


迭代器是通用的遍历方式。


🔺 总结: 对于 string,你得会用迭代器,但是一般我们还是喜欢用 下标 + [] 遍历。


迭代器有很多,此外还有反向迭代器、const 迭代器……


这些都可以通过看文档去了解和学习。对于迭代器我们下一章还会详细讲解,


(我们今天主要说的还是 string,所以这里要讲究点到为止)


0x02  甜甜的语法糖 —— 范围for

还有一种遍历字符串的方式,范围 for。


这个我们在讲 auto 关键字的时候讲过了,它是一个用起来是很甜的语法糖。


💬 用起来非常爽,不用加加也不用解引用:

int main(void)
{
  string s1("hello");
  for (auto e : s1) {
  cout << e << " ";
  }
  cout << endl;
  return 0;
}

be69297a208c7b6b57025dbdecbf9ba1_7dd4748e77504c18a235373bf681c391.png


如此完美,当然不仅仅是能读,还能写 ——


int main(void)
{
  string s1("hello");
  for (auto& e : s1) {    // 引用修改
  e += 1;
  }
  cout << s1 << endl;
  return 0;
}

dd09d4461cb846676ab634fddbfe3833_e425d2a6c51948b9b8e2997ff687bc4c.png


这里加引用是为了可以修改。不仅如此,遇到数据大的情况下也会有加 & 的时候(减少拷贝)。


其实,你不用 auto 也可以用返回 for。


for (char& e : s1) {
  e += 1;
}
cout << s1 << endl;

我们习惯用 auto ,是因为 C++11 改版之后的 auto 可以自动推导类型,


让这个语法糖更特么香了!复习猛戳 👉【C++要笑着学】范围for    (位于Ⅲ处 0x00 ~ 0x02)


再说一嘴:这个范围 for 虽然看起来和听上去都很


又是自动迭代又是自动判断结束的,


但其实它底层也就是编译器在编译后把这段代码替换成了迭代器而已。


(下一章讲 string 模拟实现的时候会顺便带大家演示一下范围for的实现原理)

相关文章
|
1月前
|
存储 算法 C++
C++ STL 初探:打开标准模板库的大门
C++ STL 初探:打开标准模板库的大门
100 10
|
1月前
|
编译器 程序员 C++
【C++打怪之路Lv7】-- 模板初阶
【C++打怪之路Lv7】-- 模板初阶
18 1
|
1月前
|
Linux 编译器 C语言
Linux c/c++之多文档编译
这篇文章介绍了在Linux操作系统下使用gcc编译器进行C/C++多文件编译的方法和步骤。
40 0
Linux c/c++之多文档编译
|
1月前
|
C++
【C++】实现日期类相关接口(三)
【C++】实现日期类相关接口
|
1月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
44 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
1月前
|
算法 编译器 C++
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
80 2
|
1月前
|
存储 编译器 C++
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
39 2
|
1月前
|
存储 算法 编译器
【C++】初识C++模板与STL
【C++】初识C++模板与STL
|
1月前
|
C++
【C++】实现日期类相关接口(二)
【C++】实现日期类相关接口
|
1月前
|
C++
【C++】实现日期类相关接口(一)
【C++】实现日期类相关接口