• 【C++】string类


    什么是string

    关于string的介绍,标准文档是这样说的:

    1. 字符串是表示字符序列的类
    2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
    3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)
    4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)
    5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作

    实际上string就是动态增长的char字符数组

    但string其实是一个typedef之后的类模板:如下图

    image-20220914094223124

    string实际上是一个 模板,参数是char类型

    同样,有一些少见的其他string

    wstring里面存的就是wchar_t (宽字符,应用于unicode编码)image-20220914094328097

    u16string:里面存的是char16_timage-20220914094507327

    u32string:里面存的是char32_timage-20220914094556285

    所以string其实就是basic_string类的针对char 的实例化

    string的设计以及编码问题

    这里就涉及编码的问题了。计算机中数据只有0和1,而如何表示各种文字呢,就要用到编码了。

    常见的编码表有ASCII,GBKutf-8,utf-16,utf-32等。而编码表实际就是内存中的值 和 文字符号的映射关系,例如,字符a对应内存的值就是97

    string是一个字符串,而字符串表示的是信息,所以就涉及到各种文字。我们知道ASCII码是针对于老美的,因为英美的文字只包含字母和标点符号,而一个char为8位,对于二进制可以表示256钟状态,每一个字母和标点对应一个状态对于老美绝对够用了。

    但是当计算机推广的时候,其他国家的问题就不可以只依靠ASCII来编码了,比如中文的就远比英美文字复杂得多,而且中国汉字多得多,所以一个字符根本表示不完。所以中文采用多个字符的编码。常见的是两个字符即16位(65536)个状态。所以为了支持泛型编程,把string设置为一个模板,对于不同类型的字符都可以使用!

    例如:

    char str[]="你好";
    
    • 1

    此时str的长度就是4个字符,两个字符表示一个汉字

    而每一个字符又对应一个编码

    image-20220914100502572

    -60-29组合起来映射的是 你
    -70-61组合起来映射的是 好
    //字符的编码值 对应编码表去查找
    
    • 1
    • 2
    • 3

    反过来验证:

    char hello[] = { -60,-29,-70,-61,0 };
    cout << hello << endl;
    
    • 1
    • 2

    image-20220914101309800

    如果让hello[3]的元素++

    char str[] = "你好";
    
    hello[3]++;
    cout << hello << endl;
    hello[3]++;
    cout << hello << endl;
    hello[3]++;
    cout << hello << endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    输出结果:image-20220914104046810

    发现,的周围都是hao

    关于汉字的编码,可以推得:中文编码是把同音字放在一起的

    平时我们打游戏的时候,一些出口即国粹的脏话输出机玩家发的消息就会变成***,而中国文化博大精深,很多谐音字也可以用来发挥作用,所以就利用汉字的编码分区,把同音字也放到一个国粹词库中了,一旦检测到是词库中的内容,就会和携成***这就是净网行动的原理。

    string的重要接口

    一、string的初始化

    1. string()

    string s1; // s1为"\0"
    
    • 1

    ​ 构造空string对象(调用默认构造函数),注意:为兼容C的字符产标准,即使为空也存在一个\0,表明这是字符串

    2. string(const char* s)

    string s2("hello wolrd");//s2为 "hello world"
    string s3 = "hello world"; //先构造 再拷贝构造 -> 优化:直接构造
    
    • 1
    • 2

    ​ 这是用C的常量字符串构造一个string对象

    ​ 这里因为只有const char*s这一个参数,对于单参数的构造函数,可以支持隐式类型转换,即先构造再拷贝构造,一般编译器会优化为直接构造

    3. string(const string& str)

    string s1("hello");// s1 = "hello"
    string s3(s1);//s3 = "hello"
    
    • 1
    • 2

    ​ 利用一个string对象构造一个对象(拷贝构造)

    4.string(size_t n, char c)

    string s4(5,'a'); //s4 = "aaaaa"
    
    • 1

    ​ 用 n 个字符c构造一个对象

    5.string(const char*s , size_t n)

    string s5("hello world",5);//s5 = "hello"
    
    • 1

    ​ 用 常量字符产s的前n个构造一个string对象

    6. string(const string& str, size_t pos,size_t len = npos

    string s1("hello wolrd");// s1 = "hello world"
    string s2(s1,6,3);// s2 = "wor"
    string s3(s1,6,5);// s3 = "world"
    string s4(s1,6,10);// s4 = "world" 大于后面的字符数,直接拷贝到最后
    string s4(s1,6); // s4 = "world"
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ​ 用一个string对象的pos位置开始向后len个字符来构造一个string

    ​ ① 如果len大于从pos位置到’\0’的长度,那么就拷贝到最后

    ​ ② 如果不传递len,缺省参数为 std::npos,是一个std命名空间中的全局变量,值为-1. image-20220914183429292

    因为 len为size_t类型(正数),而-1为负数,二进制是32个1,赋值给len自动被转变成无符号数,约为42亿九千万,即大约是4g,而4g这么大的字符串基本上是不存在的。所以不传递len默认拷贝到最后,有多少拷贝多少

    二、string的赋值

    1. string& operator=(const string& str)

    string s1 = "hello world";
    string s2;
    s2 = s1;//s2 = "hello world"
    
    • 1
    • 2
    • 3

    ​ 用一个string进行赋值

    2. string& operator=(const char*)

    string s1;
    s1 = "hello world";
    
    • 1
    • 2

    ​ 用常量字符串赋值

    3. string& operator=(char c)

    string s1;
    s1 = 'x';//用一个字符x给s1赋值
    
    • 1
    • 2

    三、string的长度

    string s1 = "hello world";
    cout << s1.length() << endl; //写法1
    cout << s1.size() << endl; //写法2
    
    
    • 1
    • 2
    • 3
    • 4

    注意:

    1. size_t size() const 返回size_t类型 (计算有效字符个数) —> 对于所有容器都有
    2. size_t length() const ----> 有的容器没有lengtn(对于树这样的,叫长度不合适)

    所以一般统一用 size()

    四、string元素获取

    1. char& at(size_t pos)

    0.

    顾名思义,函数返回pos位置的元素的引用

    普通对象调用char& at(size_t pos)

    const对象调用const char& at(size_t pos) const

    operator[]的区别就是,如果pos超出string的索引范围(越界),会抛异常,还可以继续执行后面的程序。operator[]是断言直接终止

    2. operaotr []

    image-20220914185447685

    两个函数构成函数重载

    普通对象调用char& operator[] (size_t pos)

    const对象调用const char& operator[] (size_t pos) const;

    string s1 = "hello world";
    
    //char& operator[] (size_t pos);
    cout << s1[0] << endl;//用[]进行访问
    s1[0] = 'x';//用[]修改原字符
    cout << s1 << endl;
    
    //const char& operator[] (size_t pos) const;
    const string s2 = "const string";
    //s2[0] = 'd';//const类型不能修改
    cout << "s2[0]:" << s2[0] << endl;
    //cout << s2[15] << endl;; 超出s2的长度,报错:assertion failed
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    利用[]遍历string

    string s3 = "string";
    const string s4 = "const string";
    //遍历s3
    for (size_t i = 0; i < s3.length(); ++i)
    {
    	s3[i]++;
    }
    cout << s3 << endl;
    
    //利用[]修改
    for (size_t i = 0; i < s3.length(); ++i)
    {
    	s3[i]++;
    }
    
    //const类型只能遍历,不能修改
    const string s4 = "const string";
    for (size_t i = 0; i < s4.size();++i)
    {
    	cout << s4[i] << " ";
    	//cout << s4.at(i) << " "; 两种效果一样
    }
    cout << endl;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3. front和back

    image-20220914222127296

    image-20220914222154329

    front是返回第一个字符

    back返回最后一个字符(不算’\0’ )

    五、迭代器

    1. 什么是迭代器

    迭代器是一个类型iterator

    每一个数据结构内部都有定义的这么一个类。如string::iterator就是string的迭代器,vector::iterator就是vector的迭代器

    迭代器是一个像指针一样的类型,可能是指针,也可能不是指针,但是用法像指针,具体细节封装了起来。

    对于string和vector其实就是原生指针,但是对于list就不是指针了,而是一个自定义类型

    我们知道可以利用[]遍历string,但是[]只针对底层利用数组实现的类型,对于其他一些比如链表、树型结构就不能利用[]遍历了。

    iterator是对于STL容器的统一遍历方法,用法都是类似的,不管是顺序表还是链式表,使用都是一样的~

    对于迭代器iterator,string中有两个常用的函数,返回类型是迭代器类型

    begin()end(),分别返回第一个位置的迭代器和最后一个位置的下一个位置的迭代器,[begin,end),左闭右开!

    image-20220915103828127

    如图所示,类似指针,可以利用迭代器去遍历string!

    image-20220915102638629

    string s("hello");
    //利用迭代器去遍历
    //定义一个迭代器类型 it
    string::iterator it = s.begin();//it为第一个位置的迭代器
    while (it != s.end())//当it迭代到最后一个位置的时候结束
    {
    	cout << *it << " "; //类似指针解引用
    	++it;
    }
    cout << endl;
    
    //const迭代器无法修改(类似const指针)
    string s1 = "hello world";
    string::const_iterator it3 = s1.begin();
    while (it3 != s1.end())
    {
    	cout << *it3 << " ";
    	it3++;
    	//(*it3)++; 不能修改
    }
    cout << endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    同理,迭代器可以进行修改,和指针类似的用法(*it)++

    但是对于string和vector类型很少用迭代器,因为底层用数组实现,[]使用起来更香,对于list、map等容器必须使用迭代器遍历

    2. 范围for底层利用迭代器

    范围for可以遍历任何容器,并具有自动迭代,自动判断结束的功能,十分智能。

    注意,范围for只能正向遍历,不能反向

    string s = "hello";
    for (auto i : s)//依次取s中的每一个元素赋值给i 
    {
    	cout << i << " ";
    }
    cout<<endl;
    
    //利用范围for修改元素
    for (auto& i : s) //依次给s中每一个元素取别名
    {
    	i++;
    	cout << i << " ";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    其实范围for的底层就是迭代器,编译器会自动把范围for替换成迭代器

    迭代器遍历的汇编:

    image-20220915112340178

    范围for的汇编

    image-20220915112300258

    所以,从汇编指令的角度可以看到,范围for就是去调用了迭代器进行遍历

    3. 反向迭代器

    类名::reverse_iterator,如string::reverse_iterator

    反向迭代器就是从后面向前迭代,利用两个函数

    image-20220915121850421

    rbegin():返回反向的第一个位置的迭代器(正向的最后一个位置)

    rend():返回反向的最后一个位置的下一个位置的迭代器(正向第一个位置的前一个位置)

    image-20220915122444904

    string s = "hello";
    
    //反向迭代器
    string::reverse_iterator r_it = s.rbegin();//必须用反向迭代器接受 rbegin()的返回结果
    while (r_it != s.rend())
    {
    	cout << *r_it << " ";
    	++r_it; //注意这里也是++ 不是--
    }
    cout << endl;
    
    //输出 o l l e h 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4. const迭代器

    const迭代器:const_iterator,如string::const_iterator

    const迭代器 – 经常发生在传参过程中(传参经常传const 对象&)

    一般利用begin()end()的返回值,当然也可以使用cbegin()cend()

    cbegin()和cend()是C++11支持的,只是为了更规范一些,

    因为begin()end()是具有重载的,根据是否为const决定是否调用哪一个重载,考虑到有些时候可能难以看出调用的是哪一个重载,才引入了cbegin()cend(),但其实没有什么必要,用begin()end()就可以了

    使用情景:

    1. 对象是const
    2. 自定义对象一般传递const 引用
    //传参是 const引用       str = "hello"
    void PrintString(const string& str)
    {
    	//这时候打印str
    	// string::iterator c_it = str.begin() -> 错误:str调用begin返回const迭代器,要用const迭代器接收
    	//因为str为const类型,所以迭代器要用const
    	string::const_iterator c_it = str.begin();//或str.cbegin() C++11支持 [然并卵]
    	while (c_it != str.end())
    	{
    		//(*c_it) = 'x';//const!不能修改
    		cout << *c_it << " ";
    		++c_it;
    	}
    	cout << endl;
    }
    
    //输出 h e l l o
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5. const反向迭代器

    类名::const_reverse_iterator

    利用函数:rbegin()rend()

    或者crbegin()crend() ->C++11推出,与cbegin(),cend()都是为了规范性,没什么使用的必要

    //str = "hello"
    void PrintString(const string& str)
    {
    	//反向打印str
    	string::const_reverse_iterator c_r_it = str.rbegin();//调用rbegin()
    	while (c_r_it != str.rend())
    	{
    		cout << *c_r_it << " ";
    		++c_r_it;
    	}
    	cout << endl;
    }
    
    //输出 o l l e h 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    6. 四种迭代器总结

    以string类为例

    迭代器函数
    string::iteratorbegin(),end()
    string::reverse_iteratorrbegin(),rend()
    string::const_iteratorbegin(),end() 或cbegin(),cend()
    string::const_reverse_iteratorrbegin(),rend()或crbegin(),crend()

    六、string插入数据

    1. push_back–尾插字符

    即在字符串的尾部插入一个字符

    //void push_back(char c)--(插入字符)
    string s = "hello";
    s.push_back('-');//s变成:"hello-"
    
    • 1
    • 2
    • 3

    2. append --尾插字符串

    //append() ----插入字符串
    string s = "hello"
    s.append("world");//s变成:"helloworld"
    
    • 1
    • 2
    • 3

    3. +=运算符的重载

    string类实现了+=的运算符重载,从而实现字符串的拼接功能

    同时,有三个函数重载:

    string& operator+= (const string & str):拼接string
    string & operator+= (const char* s):拼接C字符串
    string& operator+= (char c):拼接一个字符

    string s1("good");
    s1 += " evening ";//+= const char*
    s1 += '@';// += char
    string s2(" my friend");
    s1 += s2;//+= string对象 
    cout << s1 << endl;
    
    /************************************/
    // s1 : good evening@ myfriend
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    其实+=的底层就是调用了push_back和append

    相比C语言,C++字符串的操作是简单了很多

    1. 定义一个字符串,要么就是定义字符数组char[],但是数组的容量又是一个问题,开大开小都不合适,如果是动态开辟还要检查扩容
    2. 拼接字符串,C语言追加的strcat函数效率低并且不管空间,必须调用者保证好空间,该函数只管追加。而string的+=重载里有size记录字符串长度,并且还会自动判断扩容。

    4. append的迭代器用法

    使用迭代器可以规定append的范围

    主要用处是把某字符串的一部分追加到原字符串的后面

    string str = "hello world";
    string s = "my friend";
    str.append(s.begin(),s.end());//等价于str+=s
    //从第二个位置的迭代器开始,倒数第二个位置结束(不追加第一个字符和最后一个字符)
    str.append(++s.begin(),--s.end());//str = "hello worldy frien"
    
    • 1
    • 2
    • 3
    • 4
    • 5

    七、string的容量操作

    1. max_size

    string的最大长度,是写死的,其值为整形的最大值(32个1)没啥意义

    string s;
    string s1("hello world");
    cout << s.max_size() << endl;
    cout << s1.max_size() <<  endl;
    
    //同一环境,max_size()是一个定值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2. capacity

    获取string对象容量大小(不包括’\0’)

    string s;
    size_t sz = s.capacity();
    cout << "init capacity:" << sz << endl;
    
    //输出 15 (默认给15大小)
    // 意思就是 有15个可以存储有效字符的空间
    // '\0'是标识字符不是有效字符
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3. 扩容机制

    1. Windows Visual Stdio2019下的扩容机制

      //测试:尾插1000个x会扩容几次
      int main()
      {
          string s;
      	size_t sz = s.capacity();//初始
      	cout << "init capacity:" << sz << endl;
      	cout << "grow" << endl;
      	for (int i = 0; i < 1000; i++)
      	{
      		s.push_back('x');
              //如果发生扩容,就打印改变后的容量
      		if (sz != s.capacity())
      		{
      			sz = s.capacity();
      			cout << "capacity changed:" << sz << endl;
      		}
      	}
          return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

      结果:image-20220915163306604

      可以看到,VS下初始容量为15,并且刚开始是2倍扩容,后面约是1.5倍扩容

    2. Linux下的扩容机制,直接看运行截图:

      image-20220915164400914

      可以发现Linux下初始容量为0,并且始终是2倍扩容

    4. reserve – 容量预定

    有时候需要开一定的空间,但是如果空间比较大就需要频繁扩容

    不过reserve()这个成员函数可以提前开好空间(只改变capacity),减少频繁扩容(如果知道需要多少空间的情况下),单位是字节

        
    	/* reserve -- 对容量进行预留(如有需要可以提前开好,减少扩容)*/
    	string s1;
    	s1.reserve(1000);//提前开好1000的容量(考虑对齐VS下是1007个有效空间)Linux下1000
    	size_t sz1 = s1.capacity();
    	cout << "init capacity:" << sz1 << endl;
    	cout << "grow" << endl;
    	for (int i = 0; i < 1000; i++)
    	{
    		s1.push_back('x');
    		if (sz1 != s1.capacity())
    		{
    			sz1 = s1.capacity();
    			cout << "capacity changed:" << sz1 << endl;
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    5. resize – 容量预定+初始化

    image-20220915165447510

    resize改变的是string的长度,如果容量不够也会扩容,并且会用字符去初始化。size和capacity会同时改变

    string s;
    s.resize(100);//开100个空间,没给参数用'\0'初始化
    s.resize(100,'x');// 给参数为'x',用'x'初始化
    
    //但是size和capacity都会变成100 所以还会扩容
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意,如果字符串本身已经有内容了,那么resize只是会起到追加的作用,不会影响原来的字符串

    6. empty – 判空

    string str1 = "hello";
    string str2;
    cout<<str1.empty()<<endl; // 输出 0
    cout<<str2.empty()<<endl; // 输出 1
    
    • 1
    • 2
    • 3
    • 4

    7. clear – 清空有效字符

    string str = "welcome to my blog";
    str.clear();		// str = "\0"  即变成空字符
    
    • 1
    • 2

    八、assign(赋值)

    assign类似直接给字符串赋值,不过用的很少

    string str;
    string temp = "this is an example";
    
    //str.assign (size_t n, char c);	将n个字符c赋值给字符串str
    str.assign(10,'x');						// str = "xxxxxxxxxx"
    
    //str.assign(const string& s)		将字符串s赋值给str
    str.assign(temp);					//str = "this is an example"
    
    //str.assign(const string& s, size_t pos, size_t len)		将字符串的s从pos位置开始的len个字符赋值给字符串str
    str.assign(temp,6,2);		//str = "is"
    
    //assign (const char* s)		将字符数组s赋值给str
    str.assign("hello");			//str = "hello"
    
    //assign(const char*s,size_t n)		将字符数组的前n个字符赋值给str
    str.assign("hello",3);				//str = "hel"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    九、insert(插入)

    insert也不是很常用,简单了解几个常用的即可

    string temp = "hello world";
    
    //insert(size_t pos,const string& str)		从pos位置插入字符串str
    string s1 = "xxx";
    temp.insert(5,s1);							// str = "helloxxx world"
    
    //insert(size_t pos,const char* str)		从pos位置插入字符数组str
    temp.insert(5,"ggg");						// str = "helloggg world"
    
    //insert(iterator p,charc)					从迭代器p位置插入一个字符c
    temp.insert(str.begin(),'5');				// str = "5hello world"
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    十、erase(删除)

    string str = "welcome to my house";
    //erase (pos = 0,len = npos)		删除pos位置开始的len个字符,pos默认为0,len默认为无限大(删到结尾
    str.erase(14,5);					//str = "welcome to my "
    str.erase(7);						//str = "welcome"
    str.erase();						//str = ""; //空字符串
    
    //erase (iterator p);				//删除p位置迭代器的字符
    str.erase(str.begin()+2);			//str = "wecome to my house"
    
    //erase (iterator first, iterator last)  //删除迭代器区间[first,last)的字串
    str.erase(str.begin()+7,str.end()-5);	 //str = "welcomehouse"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    十一、replace(替换)

    替换的部分长度可以不一样,类似于先删除原部分,然后再插入

    效率很低,用的不多

    //replace (size_t pos,  size_t len,  const string& str);	源字符串从pos位置往后的len个字符用str替换
    string s1 = "hello the world";
    string s2 = "m"
    s1.replace(6,3,"great"); 			// s1 = "hello great world"
    
    //replace (size_t pos,  size_t len,  const char* s)			源字符串从pos位置往后的len个字符用C字符串s替换
    s1.replace(105"string");		   // s1 = "hello the string"
    
    //replace (size_t pos,  size_t len,  const char* s, size_t n);		原字符串从pos位置往后len个字符用C字符串s的前n个字符替换
    s1.replace(10,5,"cpp is good",3);	// s1 = "hello the cpp"
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    十二、c_str(返回C字符串)

    因为C的字符串是char*类型,不同于string

    并且有些场景是存在需要返回char*类型的,不可以返回string类型.因为C++兼容C,很多底层一些接口是用C写的,这样二者都可以使用

    比如用C的形式打开文件

    const char* c_str() const 函数的作用就是把string类型变成const char*类型来使用,底层就是返回了string封装的char*成员

    string filename("test.cpp");
    //FILE* pf = fopen(filename,"r");	//error		类型不匹配
    FILE* pf = fopen(filename.c_str(),"r"); 	//  √
    if(pf==NULL)
    {
        perror("fopen fail");
        exit(-1);
    }
    char c = fgetc(pf);
    while(c!=EOF)
    {
        cout<<c;
        c = fgetc(pf);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ★★★注意,二者打印的原理是不同的★★★

    **string是以size() 为结束标志 **

    string.c_str是以’\0’为结束标志

    string str = "hello world";
    cout << str << endl;    //调用 string的 operator << 重载
    cout << str.c_str() << endl;	// 调用const char* 的 operator << 重载
    // 上面输出是一样的
    
    str += '\0';
    str += 'welcome';
    
    cout << str <<endl;			    //输出:  hello world welcome
    cout << str.c_str() << endl;	//输出:  hello world
    
    //因为c_str以'\0'为标志就结束了!,不会打印'\0'后面的内容
    //所以,C字符串其实是有bug的,C字符串里面不能夹杂着'\0'
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    image-20220916112307579

    '\0'是不显示字符,所以打印就会显示一个空白

    十三、substr(取子串)

    image-20220916132525489

    即:返回从原字符串的pos位置处向后的len个字符的子串

    默认pos为0,len默认为整形最大值(无限大),

    string str = "hello my friend";
    string s1 = str.substr(6,2);		//s1 = "my"
    
    
    • 1
    • 2
    • 3

    十四、find(查找)

    1. find – 正向查找

    find函数如果找到返回第一个位置的下标,找不到返回string::npos

    string str = "hello world";
    //find (const string& str, size_t pos = 0) const		从pos位置处查找子串str(返回第一个位置)
    string substr = "wor";
    size_t pos1 = str.find(substr);				//pos1 = 6
    
    //find(const char* s,size_t pos = 0) const 		从pos位置查找C字符串s
    size_t pos2 = str.find("ld");				//pos2 = 9
    
    //find(char c,size_t pos = 0)const 				从pos位置查找字符c
    size_t pos3 = str.find('e');				//pos3 = 1
    
    //find(const char* s,size_t pos,size_t n)		从pos位置查找c字符串s的前n个   
    size_t pos4 = str.find("hello",0,3);		//pos4 = 0 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2. rfind – 反向查找

    rfind和find 用法相同,只是rfind是查找最后一次出现的位置

    rfind的应用:找文件名后缀(Linux下文件名需要是最后一个.后面才是后缀名)

    string filename = "test.cpp.tar.zip";		//后缀名是.zip
    size_t pos = filename.rfind('.');			//找最后一个.
    //如果找到
    if(pos != string::npos)
    {
        cout<<filename.substr(pos);//找到就打印pos后面的字串	
    }
    
    //输出:.zip
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3. substr和find的应用:url分割

    对一个网址进行url分割,url一般格式为协议://域名/资源定位符

    所以利用find()substr(),找"://“和它后面的第一个/就可以了

    string url = "https://legacy.cplusplus.com/reference/string/string/";
    size_t pos1 = url.find("://");
    if (pos1 == string::npos)
    {
    	cout << "url不合法" << endl;
    }
    string protocol = url.substr(0,pos1);//协议:第一个字符到pos1之间 共pos1个字符
    size_t pos2 = url.find('/', pos1 + 3);
    if (pos2 == string::npos)
    {
    	cout << "url不合法" << endl;
    }
    string domain = url.substr(pos1 + 3, pos2 - pos1 - 3);//域名:"://"后面的第一个字符到下一个'\'之前 pos2-pos1-3个字符       任意(pos1,pos2)之间有pos2-pos1+1个字符
    
    //剩下的到最后就是资源定位符
    string uri = url.substr(pos2+1); //pos2+1到最后即可
    cout << protocol << endl;
    cout << domain << endl;
    cout << uri << endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    image-20220916163019742

    4. find_first_of

    find是找到一个完全匹配的子串

    find_first_of是只要找到目标串中的一个字符,就返回当前的位置

    string str = "please notice that i am a sentence";
    size_t pos = str.find_first_of("aeiou");	//找出元音字母的字符,并改变为*
    //只要还能找到就继续循环						// 只要找到"aeiou"其中的一个,就返回
    while(pos!=string::npos)
    {
        str[pos]='*';
        pos = str.find_first_of("aeiou",pos+1);	//从pos的下一个位置开始继续找
    }
    cout << str << endl;
    
    //输出:pl**s* n*t*c* th*t * *m * s*nt*nc*
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    find_last_of和find_first_of类似,不同的是find_last_of是从后往前找(了解)

    十五、compare(比较)

    两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇'\0'为止。
    若是遇到‘\0’结束比较,则长的子串大于短的子串,如:“9856” > “985”。
    如果两个字符串相等,那么返回0,调用对象大于参数返回1,小于返回-1

    string str1 = "abcdef";
    string str2 = "abcefg";
    
    //s.compare(str)                     比较当前字符串s和str的大小
    cout << str1.compare(str2);                   // -1 (str2 > str1)
    
    //s.compare(pos,n,str)               比较当前字符串s从pos开始的n个字符与str的大小
    cout << str1.compare(1,5,str2);				  // 1  (b>a)
    
    //string也支持和const char*类型比较
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    十六、getline(输入string)

    我们通常使用的 cin>>可以获取字符串,但是cin>>遇到空格或者换行就会停止输入

    所以,如果要输入包含空格的一个句子,就不可以用cin>>来获取输入了

    需要用到getline():获取一行,只有遇到换行的时候才会停止输入

    image-20220916173821646

    is 中提取字符并将其存储到 str 中,直到找到分隔字符 delim(如果不给就是换行符“\n”

    如果到达文件的末尾*,*或者在输入操作期间发生其他错误,则提取也会停止。

    如果找到分隔符,则将其提取并丢弃(即不存储它,下一个输入操作将在它之后开始)

    string s;
    getline(cin,s);	//输入 i am a student
    cout<<s<<endl;	//输出:i am a student
    
    
    string str;
    getline(cin, str,'x');			//如果不输入x  就一直提示你输入
    cout << str << endl; 			//输入x之后,停止并输出
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    十七、string的转换

    //其他类型转字符串
    to_string(val);		//val转化为string对象,返回类型:string,val可以是int、double、float、long等等
    
    int a = 123456;
    string str = to_string(a);
    cout << str <<endl;			// 123456
    
    
    double dd = 12.34;
    string ddd = to_string(dd);
    cout << ddd << endl;		//12.340000  (默认小数点后6位)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    关于stoi(字符串转整形)

    int stoi(const string& str, size_t* idx= 0, int base = 10 )

    第一个参数是要转的字符串

    第二个参数是一个size_t类型数据的指针,传指针是为了改变实参,(传进去之后,函数会把同时包含数字和字符的字符串中最后一个数字的下一个位置的字符位置给它),只能是数字在前面的情况才会给你

    第二个参数一般可以不传递

    第三个参数是要转换成的进制,默认为10进制

    //字符串转其他类型
    string str = "2008,a good year";
    size_t pos = 0; 					//用于记录数字后字符的位置
    int a = stoi(str, &pos, 10);		
    cout << a << endl;					//	2008
    cout << str.substr(pos)<<endl;		//  ,a good year
    
    string str1 = "13524ddd";
    int b = stoi(str1);					//	一般使用只需传递字符串即可
    cout<<b<<endl;						//	13524
    
    
    //stod -- 字符串转double
    string s = "9999.99";
    double d = stod(s);
    cout<<d<<endl;						//9999.99
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
  • 相关阅读:
    如何通过进程启动来分析和解决EasyCVR内核端口报错问题?
    接口测试常用技能:Jmeter操作数据库
    Abdroid - 开机动画修改
    13-JavaSE基础巩固练习:ArrayList集合的练习
    FA-Phe-Gly-Gly,64967-39-1
    prompt learning
    Mysql开启binlog 和 打开gtid_mode
    flutter 打包apk
    387.字符串中第一个唯一字符
    tarfile.ReadError: not a gzip file
  • 原文地址:https://blog.csdn.net/K_04_10/article/details/126881268