【C++初阶】string的介绍和使用

前言

我们在学习 C/C++ 时接触过基本的数据类型,如 intchardouble 等,但这些类型并不适合处理“字符串”这种在实际编程中非常常见的数据,比如姓名、书名、电话号码等。

在 C 语言中,我们通常使用字符数组(char[])来表示字符串。但这种方式不仅需要手动管理内存,还在进行插入、删除、拼接等操作时非常繁琐,容易出错。比如添加字符时可能需要手动扩容,插入时还要挪动数组内容,非常不便。

为了简化这些操作,C++ 提供了一个更高层的封装类型——std::string。它基于字符数组实现,封装了字符串处理所需的各种操作函数,如获取长度、拼接、查找、替换等,大大提高了开发效率和代码的可读性。

掌握 string 的基本使用是学习 C++ 的重要内容之一,本文将介绍 string 的常用接口及其用法。

一、string的基本介绍

string 字符串是一种更加高级的封装,string 字符串中包含了大量的方法。

在C++中,字符串直接作为一种类型,也就是string类型,使用string类型创建的对象就是C++的字符串

注意:

  1. string不属于STL容器,string产生的比STL早
  2. string就是串,本质就是字符数组
  3. string类在C++标准库中,使用要包含头文件#include<string>

string类是一个模版,原模版叫basic_string :

template < class charT, 
           class traits = char_traits<charT>, // basic_string::traits_type 
           class Alloc = allocator<charT> // basic_string::allocator_type 
         > class basic_string;

模版是可以给缺省参数的,但是我们一般是用string类,是一个模版,只不过被typedef了,是char数组:

typedef basic_string<char> string;

除了有string外,C11还新增了u16string 和 u32string,也是字符数组,

  • u16string:每个字符是16位的char,占两个字节

  • u32string:每个字符是4个字节

为什么要定义成basic-string?

因为 C++ 是一门泛型编程语言,basic_string 是一个模板类,目的是为了让字符串类可以支持不同类型的字符(如 char、wchar_t、char16_t、char32_t),从而实现更强的通用性和复用性。

二、string的常见操作(上)

string的文档

2.1 创建字符串

img

string的常见的构造函数

constructor函数名称功能说明
string();构造空的string类对象,即空字符串
string(const char* s);用C风格的字符串来构造string类对象
string(const char* s, n);string类对象中包含C风格的字符串的前n个字符
string(size_t n, char c) ;string类对象中包含n个字符c
string(const string& s, size_t pos, size_t len = npos);拷贝已经存在的string对象的从pos位置开始的len个字符给要初始化的string对象。如果最后一个参数不传或者传的参数大于str剩下的长度,就拷贝到str的末尾
string(const string& s) ;用已经存在的string对象拷贝给要初始化的string对象

例子:

#include <iostream>
#include <string>

int main()
{
	std::string s1; // 空字符串
	std::string s2("hello world"); // 末尾不含 \0
	std::string s3("hello world", 5); // 字符串的前5个字符
	std::string s4(10, 'x'); // 10个字符x
	std::string s5(s2, 2, 6); // s2字符串从第2个位置开始往后的6个字符
	std::string s6(s2, 2); // s2字符串从第2个位置开始一直到结束
	std::string s7(s2); // 拷贝s2字符串来初始化
	
	std::cout << s1 << std::endl;
	std::cout << s2 << std::endl;
	std::cout << s3 << std::endl;
	std::cout << s4 << std::endl;
	std::cout << s5 << std::endl;
	std::cout << s6 << std::endl;
	std::cout << s7 << std::endl;

	return 0;
}

img

注意:

string s2 = "hello world"表示创建一个字符串s2,它的内容是“hello world”,

但是,string中字符串不再以\0作为结束标志了

img

补充 string::npos:

img

npos是string里的静态成员变量,所以可以做缺省参数

size_t 在 32 位下可以理解为 unsigned int ,赋值给 size_t 的变量 -1 也就是全1,即整型的最大值

2.2 string字符串的输入

cin的方式

cin在读取字符串的时候,如果遇到空格就不会继续读

#include <iostream> 
#include <string>

int main()
{
    std::string s;
    //输入
    std::cin >> s;
    //输出
    std::cout << s << std::endl; 
    
    return 0;
}

img


getline的方式
getline是C++标准库中的一个函数,用于从输入流中读取一行文本,并将其存储为字符串

img

补充:

istream是输入流类型,cin是istream类型的标准输入流对象

ostream是输出流类型,cout是ostream类型的标准输出流对象

getline函数是输入流中读取一行文本信息,所以如果是在标准输入流(键盘)中读取数据,就可以传cin给第一个参数

getline函数有两种不同的形式,分别对应着字符串的结束方式:

  • 第一种getline函数以换行符\n作为字符串的结束标志

    这种方式getline一直读到\n才停止(不包含 \n),然后将读到的文本存储到str中

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    int main()
    {
        string str;
        getline(cin, str); // cin 表示从输入流中读取信息,str 是存放读取到的信息的字符串
        cout << str << endl;
        
        return 0;
    }
    

    img

  • 第二种getline函数允许用户自定义结束标志

    从输入流中读取文本,直到遇到用户指定的结束标志字符为止(不包括标志字符)

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    int main()
    {
        string s;
        getline(cin, s, 'r'); // 以字符 r 为结束标志,
        cout << s << endl;
     
        return 0;
    }
    

    img

2.3 size() 和 capacity

补充:在C++中,关于字符串的操作函数都是包含在string中的,所以需要调用这些函数时,通常要用运算符.

size()函数用于获取字符串的长度

#include <iostream> 
#include <string>

using namespace std;

int main()
{
	string s1;
	string s2 = "hello";
	string s3 = "hello world";
	string s4 = "abc123     !#";
	cout << s1.size() << endl;
	cout << s2.size() << endl;
	cout << s3.size() << endl;
	cout << s4.size() << endl;
	
	return 0;
}

img

capacity()求字符传串能存多少有效字符

clear()把数据清空,但是一般不清空间。clear就是把size变为0,capacity没变

#include <iostream> 
#include <string>
using namespace std;

int main()
{
	string s1("hello world");
	cout << s1.capacity() << endl;
	
    //clear把数据清空,但是一般不清空间
	s1.clear();
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	return 0;
}

img

2.4 迭代器遍历字符串

迭代器是容器通用的一种遍历方式,迭代器的作用类似于指针或者数组下标,通过迭代器就可以逐个去找到它的元素。不过,访问迭代器指向的值的时候,需要解引用

string可以认为是存放字符的容器

c++中的string提供了多种迭代器,用于遍历和操作字符串中的内容

普通迭代器

  • begin():返回指向字符串第一个字符的迭代器,需要一个迭代器的变量来接收
  • end():返回指向字符串最后一个字符的下一个位置的迭代器
  • string中begin()end()返回的迭代器的类型是string::iterator
#include <iostream> 
#include <string>

using namespace std;

int main()
{
	string s = "abcdef";
	string::iterator it1 = s.begin();
	string::iterator it2 = s.end();
	//可以打印迭代器指向的那个字符
	cout << *it1 << endl; // 解引用操作 
	it1++;
	cout << *it1 << endl;
	
	//可以比较大小 
	if(it1 < it2)
		cout << "<" << endl;
	else
		cout << ">=" << endl;
    
    //计算之间有多少个元素
    cout << it2 - it1 << endl;
	
	return 0;
}

img

我们可以定义一个iterator的对象,接收begin()返回第一个字符的位置,当该对象不等于该字符串有效数据的下一个位置时,我们就得到该字符*it1(有点像我们学过的指针解引用),然后++it1就可以到下一个位置

#include <iostream> 
#include <string>

using namespace std;

int main()
{
	string s = "abcdef";
	//遍历字符串,打印
	//for(auto it1 = s.begin(); it1 != s.end(); it1++) // 可以写auto 
	for(string::iterator it1 = s.begin(); it1 < s.end(); it1++)
	{
		cout << *it1;	
	} 
	cout << endl;
        
     //还可以倒着打印
     for(string::iterator it1 = s.end()-1; it1 >= s.begin(); --it1)
     {
             cout << *it1;	
     } 
     cout << endl;

	return 0;
}

反向迭代器

  • rbegin():返回指向字符串最后一个字符的迭代器,需要一个迭代器的变量来接收
  • rend():返回指向字符串第一个字符的前一个位置的迭代器
#include <iostream>
#include <string>
using namespace std;


int main()
{
	string s1("abcdef");
	//rbegin指向最后一个位置的前一个位置
	string::reverse_iterator rit = s1.rbegin();
	//rend指向第一个元素的前一个位置
	while (rit != s1.rend())
	{
		cout << *rit << " ";
		++rit; // 注意这里是++不是--
	}
	cout << endl;

	return 0;
}

img

img

const 迭代器

img

const对象要使用const迭代器

const对象调用const修饰的begin,返回的是const_iterator

#include <iostream> 
#include <string>

using namespace std;

int main()
{
    const string s2(s1);
    string::const_iterator it1 = s2.begin(); // 返回的是const_iterator
    while (it1 != s2.end())
    {
        cout << *it1 << " ";
        ++it1;
    }
    cout << endl;
    
    return 0;
}

const 反向迭代器

img

#include <iostream> 
#include <string> 
using namespace std;

int main()
{
	string s1("hello world");
	//string::const_reverse_iterator rit1 = s2.rbegin();
    auto rit1 = s2.rbegin();
	while (rit1 != s2.rend())
	{
		cout << *rit1 << " ";
         ++rit1;
	}
	cout << endl;

	return 0;
}

为什么要使用迭代器?

我们一般喜欢使用下标加方括号的形式,但是下标加方括号的这种方式只适用于string和vector,

因为string和vector底层是连续的物理空间,才能重载[]。

迭代器是所有容器通用的方式,因为像链表这种,通过重载[]来遍历,效率低

#include <iostream> 
#include <list>

using namespace std;

int main()
{
	list lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	list::iterator it = lt.begin();
	while (it != lt.end())//注意,此处不能用小于,因为空间不是连续的
	{
		cout << *it << " ";
		++it;
	}

	return 0;
}

注意:

  1. 迭代器的底层是指针,但是它不等于指针,是运算符重载

  2. 遍历的时候,循环条件最好写 s.begin() != s.end()

    因为其它容器不像string和vector物理空间是连续的

2.5 获取某个字符

operator[]

img

string重载了一个运算符operator[],可以访问pos位置的字符,如果越界了会报错。

string::operator重载了两个成员函数,一个是普通的重载,一个是const修饰的重载,
所以它是既可读又可写的接口

  • 如果是const对象调用,那么就不可以修改
  • 如果是普通对象,可以用它来打印字符串,也可以修改字符串

例子:

#include <iostream> 
#include <string>

int main()
{
	std::string s1("hello world");
	std::cout << s1 << std::endl;
	s1[0] = 'x'; // 等价于 s2.operator[](0) = 'x';
	std::cout << s1 << std::endl;

	return 0;
}

img

底层相当于是这样的:

#include <iostream> 
#include <string>
#include <cassert>

class string
{
public:
	char& operator[](size_t pos)
	{
		assert(pos < _size);
		return _str[pos];
	}
private:
	char* _str;
	size_t _size;
	size_t _capacity;
};

at也是返回pos位置的字符

at 和 operator[]的区别:at失败后会抛异常,需要捕获;而operator[]是通过断言处理的

#include <iostream> 
#include <string>

using namespace std;
int main()
{
	try
	{
		string s1("hello world");
        //s1[20]; //程序直接终止,弹窗

		s1.at(20);
	}
	
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

img

front 和 back

front 返回第一个字符,back 返回最后一个字符

细节:front 和 back 返回的是 pos 位置字符的引用,也就是可以修改

#include <iostream> 
#include <string>

using namespace std;

int main()
{
	string s1("hello world");
	cout << s1.front() << endl;
	cout << s1.back() << endl;

	return 0;
}

img

2.6 尾插和尾删

push_back尾插

作用:在字符串尾部插入一个字符

底层:空间不够就扩容

#include <iostream> 
#include <string>
using namespace std;

int main()
{
	string s1("hello");
	s1.push_back(',');
	s1.push_back('w');
	cout << s1 << endl;

	return 0;
}

img

pop_back()尾删

pop_back()用于删除字符串中尾部的一个字符。

#include <iostream> 
#include <string>

using namespace std;

int main()
{
	string s = "hello";
	s.pop_back();
	cout << s << endl;
	cout << s.size() << endl;
	
	return 0;
}

img
当一个字符串是空字符串的时候,不能再调用pop_back,这是一种标准未定义的行为

2.7operator+=和operator+

+=会将原字符串改变

#include <iostream> 
#include <string>
using namespace std;

int main()
{
    string s3("hello");
	s3 += ',';
	s3 += "world";
	cout << s3 << endl;
    return 0;
}

img

+不会改变原字符串

string s1 = "hello ";
cout << s1 + "world" << endl;
cout << s1 << endl;

img

字符串可以头部拼接

 string s2 = "hello";
 s2 = "world" + s2; // worldhello
 cout << s2 << endl;

img

2.8 find()查找字符/子串

find()函数用于查找字符串中指定子串/字符,并返回子串/字符在字符串中第一次出现的位置

img

查找字符串或字符,默认从0位置开始查找

返回值:

  • 若找到。返回子串/字符在字符串中第一次出现的起始下标位置

  • 若未找到。返回一个整数值nposnpos的类型是size_t,值是-1,转为无符号整型就是一个非常大的数字。通常判断find()函数的返回值是否等于npos就能知道是否查找到子串或字符。

各个版本的find接口介绍+示例代码

  1. size_t find (const string& str, size_t pos = 0) const;

    含义:查找string类型的字符串str,默认是从头开始查找,pos可以指定位置开始``

    #include <iostream> 
    #include <string>
    using namespace std;
    
    int main()
    {
    	string s = "hello world hello everyone";
    	string str = "llo";
    	//默认从头开始查找 
    	size_t n = s.find(str);
    	cout << n << endl;
    	 
    	 //可以指定要查找的位置 
    	n = s.find(str, n + 1);
    	cout << n << endl;
    	return 0;
    }
    

    img

  2. size_t find (const char* s, size_t pos = 0) const;
    含义:查找C风格的字符串s,默认是从头开始查找,pos可以指定位置开始

    string s = "hello world hello everyone";
    size_t n = s.find("llo");
    cout << n << endl;
    
  3. size_t find (const char* s, size_t pos, size_t n) const;
    含义:在字符串的pos这个位置开始查找C风格的字符串s中的前n个字符

    #include <iostream>
    #include <string>
    using namespace std;
    
    int main()
    {
    	string s = "hello world hello everyone";
    	//从字符串s下标为0处开始,找word的前3个字符 
    	size_t n = s.find("word", 0, 3); // 6
    	cout << n << endl;
    	
    	n = s.find("everyday", n + 1, 5);
    	cout << n << endl;
    	  
    	return 0;
    }
    

    img

  4. size_t find (char c, size_t pos = 0) const;
    含义:查找字符c,默认是从头开始,pos可以指定位置开始

    #include <iostream>
    #include <string>
    using namespace std;
    
    int main()
    {
    	string s = "hello world hello everyone";
    	//查找字符o 
    	int n = s.find('o');
    	cout << n << endl;
    	
    	n = s.find('o', n + 1);
    	cout << n << endl; 
    	  
    	return 0;
    }
    
    

    img

string还有一个接口 rfind,rfind 的使用和find几乎完全一样,只不过 rfind 是倒着找

使用场景:

比如说要求 取出文件名的后缀

文件名后缀一般是 .xxx 但是还有一种可能就是这个文件被压缩了,

比如test.cpp.zip,此时得到的后缀.cpp.zip显然不是真实的后缀。

string s3("test.cpp.zip");
size_t pos = s3.rfind('.'); // 默认是从后往前找

2.9 substr()截取子串

substr的作用就是 截取字符串中指定位置指定长度的子串(也就是截取一个字符串中的某个子串)

img

substr():不传参数,就从下标为0的位置开始截取,直到结尾,得到的是整个字符串

substr(pos):从指定下标pos位置开始截取子串,直到结尾

substr(pos,len):从指定下标pos位置开始截取长度为len的子串

例子:

#include <iostream> 
#include <string>
using namespace std;

int main()
{
	string s = "hello world hello everyone";
	string s1 = s.substr(); // 得到整个字符串 
	cout << s1 << endl;
	
	//从下标为7的位置开始截取字符串,[7, 末尾]
	string s2 = s.substr(7);
	cout << s2 << endl;
	
	//从下标为7的位置开始截取6个字符 
	string s3 = s.substr(7, 6);
	cout << s3 << endl;
	
	return 0;
}

img

substr()和find()经常配合使用,find()负责查找到位置,substr()从这个位置开始向后获得字符串

#include <iostream>
#include <string>

using namespace std;

int main()
{
	//substr()通常和find()配合使用 
	string s = "hello world hello everyone";
	//          0123456
	size_t n =  s.find("world");
	string s1 = s.substr(n, 5);
	cout << s1 << endl;
	
	return 0;
}

img

三、和string相关的函数

3.1 stoi/stol

stoi是将字符串转换成int类型的值

stol是将字符串转换成long int类型的值

img

参数说明:

str:要转换的字符串

idx:是一个输出型参数,也就是这个通过这个参数会带回一个值。

    idx是一个指针,需要在外边创建一个size_t类型的值,传递它的

    地址给idx,这个参数将带回str中无法正确匹配数字的第一个字符的位置(下标)

base:被解析的字符串中数字的进制值,

  • 2、8、10、16表示将2或8或10或16进制转换成10进制

  • 如果传递的是0,会根据字符串的内容的信息自动推导进制

    比如:字符串中有0x就认为是16进制,0开头会被认为是8进制

    最终会转换成10进制

例子:

#include <iostream> 
#include <string>
using namespace std;

int main()
{
	string s = "11x22";
    //                     01234
	size_t pos = 0; 
	int r = stoi(s, &pos); // 默认认为s中的数字是10进制的
	cout << r << endl; 
	cout << "pos=" << pos << endl;
	

	return 0;

}

img

如果不想要返回的下标,可以传NULL

#include <iostream> 
#include <string>
using namespace std;

int main()
{
	string s = "11x22";
	//        2^1 + 2^0 = 3
	size_t pos = 0; 
	int r = stoi(s, &pos, 2); // 认为s中的数字是2进制的
	cout << r << endl; // 3
	cout << "pos=" << pos << endl;
	
	return 0;
}

img

3.2 stod/stof

stod将字符串转换成double类型的值

stof是将字符串转换成float类型的值

img

img

#include <iostream> 
#include <string>
using namespace std;

int main()
{
	string s = "3.14abc234";
	size_t pos;
	double d = stod(s, &pos);
        //double d = stod(s, 0);
        //double d = stod(s, NULL);
	cout << d << endl;
	cout << "pos = " << pos << endl; // 4
	
	return 0;
}

img

3.3 to_string

to_string函数可以将数字转换成字符串

img

#include <iostream> 
#include <string>
using namespace std;

int main()
{
	//string s = to_string(3.14);
//	string s = to_string(110);
//	cout << s << endl;

	string pi = "pi is " + to_string(3.14);
	cout << pi << endl;
	
	return 0;
}

img

四、string的常见操作(下)

4.1 insert任意位置插入

我们之前学过push_back即在字符串的末尾插入字符,那如果我们想在字符串的中间插入

字符呢?我们可以使用成员函数insertimg

在这里,我们先学3种:

  • 在pos前面插入一个string字符串
#include <iostream> 
#include <string> 

using namespace std;

int main()
{
	string s = "abcdef";
	string str = "xxx";
	
	cout << s << endl;
	// 在3这个下标前面插入str中的字符串 
	s.insert(3, str);
	cout << s << endl;
	
	return 0;
}

img

  • pos位置前面插入一个C风格的字符串
string s = "abcdef";
string str = "xxx";

cout << s << endl;
// 在3这个下标前面插入字符串"xxx" 
s.insert(3, "xxx");
cout << s << endl;

img

  • pos位置前面插入n个字符c
string s = "abcdef";
string str = "xxx";

cout << s << endl;
// 在3这个下标前面插入1个字符'x'
s.insert(3, 1, 'x'); 
cout << s << endl;

img

4.2 reserve预留空间

如果我们知道要开多大的空间,那么可以使用reserve

如果给200的话会开一个比200大的一段空间

因为每次插入数据可能会有扩容,而扩容需要拷贝旧空间里的数据,

开辟新空间,释放旧空间,有消耗

#include <iostream> 
#include <string> 
using namespace std;

int main()
{
	string s1;
    // 提前开空间,避免扩容
	s1.reserve(200);

	size_t old = s1.capacity();
	cout << "capacity:" << old << endl;

	return 0;
}

img

4.3 reverse反向/翻转

img

注意:传的参数是迭代器

#include <iostream> 
#include <string> 

using namespace std;

int main()
{
	string s1("hello world");
	reverse(s1.begin() + 6, s1.end());

	cout << s1 << endl;

	return 0;
}

4.4 resize调整大小

resize调整大小有三种情况:

  • resize 小于 当前 size :删除数据
  • resize 大于 当前 size 小于 capacity :超出当前 size 的部分默认补 \0,但是如果显示传了字符,那么就用显示传的字符来初始化
  • resize 大于 capacity :比[resize 大于 当前 size 小于 capacity]多了一个操作:会扩容
#include <iostream> 
#include <string> 

using namespace std;

int main()
{
	string s1("1234567890");
	cout << s1 << endl;
	cout << s1.size() << endl; // 10
	cout << s1.capacity() << endl << endl; // 15

	//resize 比当前 size 小 --- 本质就是删除
	s1.resize(5);
	cout << s1 << endl; 
	cout << s1.size() << endl; // 5
	cout << s1.capacity() << endl << endl; // 15

	//resize 比当前 size 大,比 capacity 小 ----- 本质就是插入
	s1.resize(12);
	cout << s1 << endl;
	cout << s1.size() << endl; // 12
	cout << s1.capacity() << endl << endl; // 15

	//resize 比当前 capacity 大 ----- 本质就是插入
	s1.resize(20);
	cout << s1 << endl;
	cout << s1.size() << endl; // 20
	cout << s1.capacity() << endl << endl; // 31

	return 0;
}

img

4.5 erase删除

img

接口1(最常用):从pos位置开始,删除len个字符。删除后,后面的字符要往前挪

例子1:

#include<iostream>
#include <string>

using namespace std;

int main()
{
	string s1("hello world");
	s1.insert(5, "xxxx");
	cout << s1 << endl;

	s1.erase(5, 5); // 从第5个位置开始,删除5个字符
	cout << s1 << endl;

	//头删
	s1.erase(0, 1);
	cout << s1 << endl;

	//使用迭代器头删
	s1.erase(s1.begin());
	cout << s1 << endl;

	return 0;
}

请添加图片描述

例子2:

#include <iostream>
#include <string>
using namespace std;

int main()
{
	//substr()通常和find()配合使用 
	string s = "hello world hello everyone";
	//          0123456
	size_t n =  s.find("world");
	string s1 = s.substr(n, 5);
	cout << s1 << endl;
	
	return 0;
}

img

4.6 c_str 取 char* 类型的 str

img

获取string底层(char*类型)的字符串,主要是为了保持和C语言兼容

string s1("hello world");
cout << s1 << endl; // 重载string的流插入
cout << s1.c_str() << endl; // 返回string底层的字符串,是内置类型的

使用场景:

我们现在在写一个C++的程序,但是我们必不可少地需要调用一些C语言的库,

(C++不是纯的面向对象,它还有面向过程)

假设我们现在需要打开一个文件进行读,且我们现在只学了C的库。

fopen要求第一个参数必须是const char*类型,但是我们的文件名是string类型的,那么现在我们就可以使用 c_str()

string s2("Test.cpp");
FILE* fout = fopen(s1.c_str(), "r"); // fopen的第一个参数必须是const char*
char ch = fgetc(fout);
while (ch != EOF)
{
    cout << ch;
    ch = fgetc(fout);
}

4.7 replace替换

请添加图片描述
replace只有平替时效率最高,其它时候需要挪动数据,效率低

#include <iostream>
#include <string>

using namespace std;

int main()
{
	string s1("hello world hello bit");
	cout << s1 << endl;
	//s1.replace(5, 1, "%%");//第5个位置开始的1个字符替换成%%
	//cout << s1 << endl;

	//将字符串中所有空格都替换成%%
	//先通过find找到要替换的字符的下标,然后通过下标替换
	size_t i = s1.find(' ');//查找字符串或字符,默认从0位置开始查找
	while (i != string::npos)
	{
		s1.replace(i, 1, "%%");//将i位置开始的1个字符替换成%%
		//替换完后再找下一个字符
		i = s1.find(' ');
	}
	cout << s1 << endl;

	return 0;
}

下面这种方法同样能实现,空间换时间

#include <iostream>
#include <string>

using namespace std;

int main()
{
	string s1("hello world hello bit");
	cout << s1 << endl;
	
	string s2;
	for (auto ch : s1)
	{
	if (ch != ' ')
	s2 += ch;
	else
	s2 += "%%";
	}
	cout << s2 << endl;
	s1.swap(s2);

	return 0;
}

请添加图片描述

五、补充:auto和范围for

5.1 auto自动推导类型

#include <iostream>

int main()
{
 	int i = 0;
	int j = i;
	//auto会自动推导类型
	auto z = i;    // int
	auto x = 1.1; // double
	auto p = &i;  // int*

    //auto不能推导出引用
	int& r1 = i;
	auto r2 = r1; // int,r1是i的别名,r1本质上还是int类型
	auto& r3 = r1;// int&
    //auto r4; // 报错
    
    return 0;
}

对于一些涉及到比较长的类型(比如map的迭代器),我们就可以使用auto

#include <iostream>

int main()
{
 	std::map<std::string, std::string> dict;
	//std::map<std::string, std::string>::iterator dit = dict.begin();
	auto dit = dict.begin();

	return 0;
}

总结:auto作用就是简化代码,替代写起来长的类型

C++20开始支持auto做函数形参

void func(auto x)
{}

C++11开始支持auto做返回值

auto func()
{
	int x = 0;
	return x;
}

5.2 范围for 遍历容器

范围for基本介绍

  • 自动取容器的数据赋值给左边的值(赋值是一种拷贝)

  • 自动++,自动判断结束。

范围for是用来遍历容器的(也就是数据结构),底层是迭代器,只有容器才支持迭代器

#include <iostream>
#include <string>
using namespace std;

int main()
{
    string s1("hello world");
	for (auto ch : s1) // 范围for经常和auto配套在一起使用
	{
		cout << ch << " ";
	}
	cout << endl;
    
    return 0}

img


使用范围for修改

先看一个例子:

#include <iostream> 
#include <string>
using namespace std;

int main()
{
	string s1("hello world");
	for (auto ch : s1)
	{
		ch++; // 修改
		cout << ch << " ";
	}
	cout << endl;

	for (auto ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
    
    return 0;
}

img

我们发现在范围for里修改后,在里面确实打印出来的是修改后的值,但是出了范围for再打印就是原来的值了。

原因是,范围for是自动取容器里的数据赋值给左边的值,赋值是一种拷贝,所以我们在范围for里修改后,不会影响外面的值

要是想修改里面的值,因为auto不能自动推导出引用,所以我们需要使用auto&

#include <iostream> 
#include <string>
using namespace std;

int main()
{
	string s1("hello world");
	for (auto ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	
	//范围for c++11
	for (auto& ch : s1)
	{
		ch++;
		cout << ch << " ";
	}
	cout << endl;

	for (auto ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
    return 0;
}

总结范围for有两种情况要用引用:

  1. 要修改数据
  2. 容器里面存的是一些比较大的对象,减少拷贝
  3. 如果是一些比较大的对象使用引用但是不想修改,可以使用const auto&

范围for只适用于容器和数组

  1. 数组可以使用范围for

    因为范围for底层是迭代器,迭代器的行为跟指针类似

  2. 容器才能支持范围for

    因为容器才支持迭代器,而范围for的底层就是迭代器

#include <iostream>
using namespace std;

int main()
{
	int a[] = { 1, 2, 3, 4, 5, 6 };
	for (auto e : a)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值