前言
我们在学习 C/C++ 时接触过基本的数据类型,如 int
、char
、double
等,但这些类型并不适合处理“字符串”这种在实际编程中非常常见的数据,比如姓名、书名、电话号码等。
在 C 语言中,我们通常使用字符数组(char[]
)来表示字符串。但这种方式不仅需要手动管理内存,还在进行插入、删除、拼接等操作时非常繁琐,容易出错。比如添加字符时可能需要手动扩容,插入时还要挪动数组内容,非常不便。
为了简化这些操作,C++ 提供了一个更高层的封装类型——std::string
。它基于字符数组实现,封装了字符串处理所需的各种操作函数,如获取长度、拼接、查找、替换等,大大提高了开发效率和代码的可读性。
掌握 string
的基本使用是学习 C++ 的重要内容之一,本文将介绍 string
的常用接口及其用法。
一、string的基本介绍
string 字符串是一种更加高级的封装,string 字符串中包含了大量的方法。
在C++中,字符串直接作为一种类型,也就是string类型,使用string类型创建的对象就是C++的字符串
注意:
- string不属于STL容器,string产生的比STL早
- string就是串,本质就是字符数组
- 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的常见操作(上)
2.1 创建字符串
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;
}
注意:
string s2 = "hello world"表示创建一个字符串s2,它的内容是“hello world”,
但是,string中字符串不再以\0
作为结束标志了
补充 string::npos:
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;
}
getline的方式
getline是C++标准库中的一个函数,用于从输入流中读取一行文本,并将其存储为字符串
补充:
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; }
-
第二种getline函数允许用户自定义结束标志
从输入流中读取文本,直到遇到用户指定的结束标志字符为止(不包括标志字符)
#include <iostream> #include <string> using namespace std; int main() { string s; getline(cin, s, 'r'); // 以字符 r 为结束标志, cout << s << endl; return 0; }
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;
}
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;
}
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;
}
我们可以定义一个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; }
const 迭代器
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 反向迭代器
#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;
}
注意:
-
迭代器的底层是指针,但是它不等于指针,是运算符重载
-
遍历的时候,循环条件最好写 s.begin() != s.end()
因为其它容器不像string和vector物理空间是连续的
2.5 获取某个字符
operator[]
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;
}
底层相当于是这样的:
#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;
}
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;
}
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;
}
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;
}
当一个字符串是空字符串的时候,不能再调用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;
}
+不会改变原字符串
string s1 = "hello ";
cout << s1 + "world" << endl;
cout << s1 << endl;
字符串可以头部拼接
string s2 = "hello";
s2 = "world" + s2; // worldhello
cout << s2 << endl;
2.8 find()查找字符/子串
find()函数用于查找字符串中指定子串/字符,并返回子串/字符在字符串中第一次出现的位置
查找字符串或字符,默认从0位置开始查找
返回值:
-
若找到。返回子串/字符在字符串中第一次出现的起始下标位置
-
若未找到。返回一个整数值
npos
。npos
的类型是size_t,值是-1,转为无符号整型就是一个非常大的数字。通常判断find()函数的返回值是否等于npos就能知道是否查找到子串或字符。
各个版本的find接口介绍+示例代码
-
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; }
-
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;
-
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; }
-
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; }
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的作用就是 截取字符串中指定位置指定长度的子串(也就是截取一个字符串中的某个子串)
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;
}
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; }
三、和string相关的函数
3.1 stoi/stol
stoi
是将字符串转换成int
类型的值
stol
是将字符串转换成long int
类型的值
参数说明:
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;
}
如果不想要返回的下标,可以传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;
}
3.2 stod/stof
stod将字符串转换成double类型的值
stof是将字符串转换成float类型的值
#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;
}
3.3 to_string
to_string函数可以将数字转换成字符串
#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;
}
四、string的常见操作(下)
4.1 insert任意位置插入
我们之前学过push_back即在字符串的末尾插入字符,那如果我们想在字符串的中间插入
字符呢?我们可以使用成员函数insert
在这里,我们先学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;
}
- pos位置前面插入一个C风格的字符串
string s = "abcdef";
string str = "xxx";
cout << s << endl;
// 在3这个下标前面插入字符串"xxx"
s.insert(3, "xxx");
cout << s << endl;
- pos位置前面插入n个字符c
string s = "abcdef";
string str = "xxx";
cout << s << endl;
// 在3这个下标前面插入1个字符'x'
s.insert(3, 1, 'x');
cout << s << endl;
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;
}
4.3 reverse反向/翻转
注意:传的参数是迭代器
#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;
}
4.5 erase删除
接口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;
}
4.6 c_str 取 char* 类型的 str
获取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;
}
使用范围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;
}
我们发现在范围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有两种情况要用引用:
- 要修改数据
- 容器里面存的是一些比较大的对象,减少拷贝
- 如果是一些比较大的对象使用引用但是不想修改,可以使用
const auto&
范围for只适用于容器和数组
-
数组可以使用范围for
因为范围for底层是迭代器,迭代器的行为跟指针类似
-
容器才能支持范围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;
}