【C++】STL —— String类不会怎么办? 看文档(万字详解)(上)

简介: 【C++】STL —— String类不会怎么办? 看文档(万字详解)(上)

一. 编码科普


为什么string不能针对char来写?因为编码不同。char只能表示256个字符。

所以这时候要用模板。


string管理的是一个char*的字符串。

u16string:一个字符是两个字节

u32string:一个字符是四个字节

wstring:叫做宽字符,一个字符占两个字节


🐋ASCII码


ASCII码表。是美国设计的。

ASCII码表是:计算机当中存的值,和字符的映射

但是只有256个字符的表示。用char表示


🐋Unicode


也叫做万国码。


Unicode是针对全世界的语言而设计的一种编码。


常见的有utf-8 utf-16 utf-32


🐋gbk


gbk是叫做国标码。是针对中文创建的一个编码。其中还涉及台湾的繁体字。


计算机上不止有英文,还要有中文,日文等语言。但是ASCII码表不足以表示


二. String类


string类实际上是basic_string这个类模板的实例化 ——


0a2653c851af460fa595bd959398a8f1.png


其底层实现大概如下:


template<class T>
class basic_string
{
  // ...
private:
  T* _str; //动态申请的
    size_t _size;
    size_t _capacity;
  // ...
};


我们发现字符串类型不仅仅只有字符串,为什么还会有类模板呢?这就涉及到不同编码规则问题


在ascii编码表(老美写的)中,将值和符号建立映射关系,1byte空间可以表示256个英文字符;再说unicode,是为了表示全世界文字的编码表,其中的utf-16方案,所有字符,无论中英还是啥,都是两字节表示。我们认识到的,字符可不简单的是char,还可以是wchar宽字符等等


🔥下面介绍string类常用的接口 ,要熟练掌握,其余的用时查阅即可。在使用string类时,需要包含头文件#include<string>以及展开命名空间using namespace std;


三.构造 & 析构 & 赋值重载


⚡构造函数


0a2653c851af460fa595bd959398a8f1.png

展示:——

2d65d23f6d4748949b924e4057485923.png

这里我们看见是一个空串,但是为了更好的兼容c,它后面是给了一个\0的,我在原始视图里发现


4cebaac233b3433da32a72337a77fc60.png


说明符合c字符串的规范


其余的接口简单演示,主要为了演示如何查阅文档


⚡ 功能:从pos开始取对象的一部分(len)。


substring (3) string (const string& str, size_t pos, size_t len = npos);


其中len给了缺省值npos,npos是string类的一个静态成员变量,值为-1,换算成补码就是全1,赋值给了size_t,也就是整型的最大值4,294,967,295。那如果不传参数的话,基本上是有多少取多少。


0a2653c851af460fa595bd959398a8f1.png2d65d23f6d4748949b924e4057485923.png


注:string类对象支持直接用cin和cout进行输入和输出,因为重载了流插入>>和流提取<<操作符(后面说)


⚡取字符串前n个(用的非常少)


from sequence (5) string (const char* s, size_t n);


⚡ 一键初始化


fill (6) string (size_t n, char c);


0a2653c851af460fa595bd959398a8f1.png


⚡析构函数(不重要)


析构我们不管,对象出了作用域,自动调用的


⚡赋值重载


2d65d23f6d4748949b924e4057485923.png

string s1;
string s2 = "hello world";//构造+拷贝构造-》优化——直接构造
s1 = s2;
s1 = "xxxxx";
s1 = 'y';


如果现在让我们取实现逆置一个字符串,我们又不能拿的到str(私有),何况我们都不知道底层是_str 还是str_ ,不清楚底层实现,这就要引出[]


四.operator[ ]


重载了[],使得string类可以像数组一样访问字符。不同的是,数组访问本质是解引用,而这里是调用函数。


它提供了两个版本 ——


0a2653c851af460fa595bd959398a8f1.png


其大概的底层实现如下:


char& operator[](size_t pos)//传引用返回,别名
{
   assert(pos<_size);
   return _str[pos];
}


operator[]是传引用返回,返回的是别名,这使得它可读可写。


这里不是为了减少拷贝,而是为了做输出型参数,支持修改返回值


2d65d23f6d4748949b924e4057485923.png


#include<iostream>
#include<string>
using namespace std;
int main()
{
  string s("more than words");
  // 1.可读
  //for (size_t i = 0; i < s.length(); i++)  二者都是求长度,string比较早,且不适合一些树的结构
  for (size_t i = 0; i < s.size(); i++)
  {
  //等价于cout << s.operator[](i) << " " <<; 
  cout << s[i] << ' ' ;
  }
  cout << endl;
  // 2.可写
  for (size_t i = 0; i < s.size(); i++)
  {
  s[i] += 1;
  }
  cout << s << endl;
  return 0;
}


对于const修饰的就不能修改了


const string s2("hello world");
for (size_t i = 0; i < s1.size(); ++i)
{
  s2[i]++;//报错了
}


下面两个函数功能一致,(at的存在有历史原因)只不过二者检查越界的方式不同,推荐使用[] ——


0a2653c851af460fa595bd959398a8f1.png


五、Capacity 容量操作



2d65d23f6d4748949b924e4057485923.png

⚡size vs length


➰字符串中有效字符长度,即不包含最后作为结尾标识符的\0


0a2653c851af460fa595bd959398a8f1.png


两者底层实现完全一致(length的存在是历史原因),但强烈推荐使用size. 这是为了和后序各种容器接口保持一致(二叉树你不能用length吧)


capacity

➰ 容量:能存多少个有效字符(注意\0无效字符不算),要记得string类的底层是顺序表结构,初始值是15


2d65d23f6d4748949b924e4057485923.png


⚡resize vs reverse

➰reserve 和 resize 都是改变容量,申请至少n个字符的空间(字符串涉及对齐问题,后续详谈) ,但有所不同 ——


🤞1. resize - 开空间,并可以对空间初始化


4cebaac233b3433da32a72337a77fc60.png

翻译知道


如果是将元素个数减少,会把多出size的字符抹去,这不挺resize的吗(狗头)

如果是将元素个数增多,void resize (size_t n);,用\0来填充多出的元素空间,void resize (size_t n, char c);用字符c来填充多出的元素空间

注:resize在改变元素个数时,如果是将元素个数增多,可能会改变容量的大小;如果是将元素个数size减少,容量不变

void test_string14()
{
  string s1("JDG 总冠军");
  s1.resize(5);//size缩小成5,capacity不变
  string s2("JDG 总冠军");
  s2.resize(100);//填充'\0',size——>100,capacity->111(自动扩容)
  string s3("JDG 总冠军");
  s3.resize(100,'~');//填充'\0',size——>100,capacity->111(自动扩容)
}


➰ 2. reserve - 开空间。在已知需要多少空间时,调用reserve,可以避免频繁增容的消耗


0a2653c851af460fa595bd959398a8f1.png


为字符串预留空间,改变容量。当然了不会改变有效元素个数size

给reserve的参数n小于string的容量时,是无效请求,并不会改变容量大小

#include<string>
using namespace std;
int main()
{
  string s1;
  s1.reserve(100); // size - 0,capcacity->111
  string s2("more than words");
  s2.reserve(5);   // capacity和size仍为15
  return 0;
}


⚡clear

➰ 清空有效字符,容量不变


2d65d23f6d4748949b924e4057485923.png


⚡empty

➰ 检测字符串是否为空串


4cebaac233b3433da32a72337a77fc60.png


六、iterator 迭代器


第二种遍历的方法:迭代器,对于string类,无论正着还是倒着走,[下标]的方法都足够好用,为什么还要有迭代器?

🤞🤞事实上,迭代器是一种通用的遍历方式,且用法类似,所有容器都可以使用迭代器这种方式去访问修改,而list、map/set不支持[下标]遍历。结论是,对于string类,我们得会用迭代器,但是我们更喜欢用[下标]


🌈正向迭代器


正向迭代器提供了两个函数——


0a2653c851af460fa595bd959398a8f1.png2d65d23f6d4748949b924e4057485923.png


🌈迭代器 iterator是像指针一样的类型,不确定是不是(薛定谔的猫),但它的用法像指针一样, 其区间[ }左闭右开


#include<iostream>
#include<string>
using namespace std;
int main()
{
  string s("more than words");
  // 1.可读
  string::iterator it = s.begin();
  while (it != s.end())
  {
  cout << *it << " ";
  it++;
  }
  cout << endl;
  // 2.可写
  it = s.begin();
  while (it != s.end())
  {
  *it += 1;
  it++;
  }
  cout << s <<endl;
  return 0;
}

0a2653c851af460fa595bd959398a8f1.png

iterator依然提供了两个版本,第二个是const变量

关于遍历的时候!=能不能改成<= ,可以但是没必要,因为在string的物理空间是连续的,其他容器list等不一定连续。


🌈反向迭代器


也提供了两个成员函数


0a2653c851af460fa595bd959398a8f1.png2d65d23f6d4748949b924e4057485923.png


void test_string5()
{
  string s("hello");
  string::reverse_iterator rit = s.rbegin();
  //auto rit = s.rbegin();//auto 可以自动推导类型
  while (rit != s.rend())
  {
  cout << *rit << " ";
  rit++;
  }
  cout << endl;
}

0a2653c851af460fa595bd959398a8f1.png


🌈const迭代器


所谓的const迭代器就是针对const的版本嘛


普通迭代器可读可写,相当于string类模板中,类型为T*

而const迭代器不可写。这是因为是const成员函数,const修饰this指针指向的内容(相当于string类模板中,类型为const T*)

const迭代器也分正向迭代器和反向迭代器,且就是给const对象用的。这是因为const对象才能调用这里的const成员函数,返回const迭代器,不可写。


0a2653c851af460fa595bd959398a8f1.png


使用情况如下:


void test_string6()
{
  string s("hello");
  // const正向迭代器 - 可读不可写
  string::const_iterator it = s.begin();
  while (it != s.end())
  {
  cout << *it << " ";
  it++;
  }
  cout << endl;
  // const反向迭代器 - 可读不可写
  string::const_reverse_iterator rit = s.rbegin();
  while (rit != s.rend())
  {
  cout << *rit << " ";
  rit++;
  }
  cout << endl;
}


传参进func中,s是const对象,自动调用第二个接口,返回的是const_iterator,要用const迭代器类型接收,且不能修改


2d65d23f6d4748949b924e4057485923.png


C++11为了区分const迭代器和普通迭代器还提供了以下接口,不然调用时容易混淆


4cebaac233b3433da32a72337a77fc60.png


🌈范围for遍历


范围for是C++11提供的语法糖🍬,实际上底层编译器会替换成迭代器(反汇编里可以看出) 只能正向遍历


🍬依次取s中的每个字符,赋值给ch


自动迭代

自动判断结束

#include<iostream>
#include<string>
using namespace std;
int main()
{
  string s("more than words");
  for (auto& ch : s)
  {
  cout << ch << " ";
  }
  cout << endl;
  for (auto& ch : s)
  {
  ch += 1;
  }
  cout << s << endl;
  return 0;
}


ps:


若要修改,auto要加上&。因为*it会依次赋值给ch,ch是*it的拷贝,*it改变不影响ch,所以要加上&


相关文章
|
1月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
262 5
|
5月前
|
对象存储 C++ 容器
c++的string一键介绍
这篇文章旨在帮助读者回忆如何使用string,并提醒注意事项。它不是一篇详细的功能介绍,而是一篇润色文章。先展示重载函数,如果该函数一笔不可带过,就先展示英文原档(附带翻译),最后展示代码实现与举例可以直接去看英文文档,也可以看本篇文章,但是更建议去看英文原档。那么废话少说直接开始进行挨个介绍。
119 3
|
5月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
158 0
|
5月前
|
存储 编译器 C语言
关于string的‘\0‘与string,vector构造特点,反迭代器与迭代器类等的讨论
你真的了解string的'\0'么?你知道创建一个string a("abcddddddddddddddddddddddddd", 16);这样的string对象要创建多少个对象么?你知道string与vector进行扩容时进行了怎么的操作么?你知道怎么求Vector 最大 最小值 索引 位置么?
142 0
|
5月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
249 0
|
7月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
288 12
|
9月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
8月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
173 16
|
9月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
8月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
下一篇
oss云网关配置