C++ 11
自动类型推导
在定义变量时可以不指明具体的类型,而是让编译器自己去推导,这让代码的编写更加方便。
auto 类型推导
语法如下,name 是变量的名字,value 是变量的初始值。
auto name = value;
注意:auto 仅仅是一个占位符,在编译器期间它会被真正的类型所替代。或者说,C++ 中的变量必须是有明确类型的,只是这个类型是由编译器自己推导出来的。
以下是一些例子:
#include<iostream>
using namespace std;
int main()
{
// 一些基本的用法
auto number1 = 1;
auto number2 = 22.2;
auto number3 = &number1;
auto number4 = number1, number5 = 2; // auto被推断为int, number5只能是int
auto str = "12345";
// auto 除了可以独立使用,还可以和某些具体类型混合使用.
int x = 0;
auto* y = &x; // auto被推导为int, y为int*类型
auto& z = x; // auto被推导为int, z为int&类型
auto w = z; // auto被推导为int, w为int类型(注意auto会把引用抛弃,直接推导出它的原始类型)
// auto 和 const 的结合:
int a = 0;
const auto b = a; // auto 被推导为 int, b 为 const int
auto c = b; // auto 被推导为 int(const 属性被抛弃), c为int
const auto& d = a; // auto 被推导为 int,d 为 const int& 类型
auto& e = d; // auto 被推导为 const int 类型, e 为 const int& 类型
// 最后我们来简单总结一下 auto 与 const 结合的用法:
// 当类型不为引用时,auto 的推导结果将不保留表达式的 const 属性;
// 当类型为引用时,auto 的推导结果将保留表达式的 const 属性。
return 0;
}
使用限制:
- 使用 auto 的时候必须对变量进行初始化。
- auto 不能在函数的参数中使用。
- auto 不能作用于类的非静态成员变量(也就是没有 static 关键字修饰的成员变量)中。
- auto 关键字不能定义数组,比如下面的例子就是错误的:
char str1[] = "12345";
auto str2[] = str1; //str1为数组,所以不能使用auto
- auto 不能作用于模板参数。
template <typename T>
class A{
};
int main()
{
A<int> C1;
A<auto> C2 = C1; // 错误
return 0;
}
decltype类型推导
decltype 是“declare type”的缩写,译为“声明类型”。
既然已经有了 auto 关键字,为什么还需要 decltype 关键字呢?因为 auto 并不适用于所有的自动类型推导场景,在某些特殊情况下 auto 用起来非常不方便,甚至压根无法使用,所以 decltype 关键字也被引入到 C++11 中。
语法如下,expression为要计算的表达式,name 是变量的名字,value 是变量的初始值。
decltype(expression) name = value;
decltype 根据 expression 表达式推导出变量的类型,跟=
右边的 value 没有关系。
auto 要求变量必须初始化,而 decltype 不要求。这很容易理解,auto 是根据变量的初始值来推导出变量类型的,如果不初始化,变量的类型也就无法推导了。decltype 可以写成下面的形式:
decltype(expression) name;
expression 就是一个普通的表达式,它可以是任意复杂的形式,但是我们必须要保证 expression 的结果是有类型的,不能是 void。
使用 decltype(exp) 获取类型时,编译器将根据以下三条规则得出结果:
-
如果 exp 是一个不被括号()包围的表达式,或者是一个类成员访问表达式,或者是一个单独的变量,那么 decltype(exp) 的类型就和 exp 一致,这是最普遍最常见的情况。
-
如果 exp 是函数调用,那么 decltype(exp) 的类型就和函数返回值的类型一致。
-
如果 exp 是一个左值,或者被括号()包围,那么 decltype(exp) 的类型就是 exp 的引用;假设 exp 的类型为 T,那么 decltype(exp) 的类型就是 T&。
#include<iostream>
using namespace std;
class Student {
public:
static int total;
string name;
int age;
};
//函数声明
int& f1(int, char) { static int a = 0; return a; }
int&& f2() { return 0; }
int f3(double) { return 1; }
const int& f4(int, int, int) { static int a = 0; return a; }
const int&& f5() { return 0; }
int main()
{
{
// 简单用法
int a = 0;
decltype(a) b = 1; // b 被推导成了 int
decltype(1.2) c = 1.7; // c 被推导成了 double
decltype(c + 10) d; // d 被推导成了 double
}
{
// 规则一示例
// 对于一般的表达式,decltype 的推导结果就和这个表达式的类型一致。
int n = 0;
const int& refn = n;
Student stu;
decltype(n) m2 = n; // 被推导为 int 类型
decltype(refn) m3 = n; // 被推导为 const int& 类型
decltype(Student::total) m4 = 0; // 被推导为 int 类型
decltype(stu.name) m5 = "Lee"; // 被推导为 string 类型
}
{
// 规则二示例
// 需要注意的是,exp 中调用函数时需要带上括号和参数,但这仅仅是形式,并不会真的去执行函数代码。
int n = 0;
decltype(f1(100, 'A')) v1 = n; // 被推导为 int&
decltype(f2()) v2 = 0; // 被推导为 int&&
decltype(f3(10.5)) v3 = 0; // 被推导为 int
decltype(f4(1, 2, 3)) v4 = n; // 被推导为 const int &
decltype(f5()) v5 = 0; // 被推导为 const int &&
}
{
// 规则三示例
// 这里我们需要重点说一下左值和右值:
// 左值是指那些在表达式执行结束后依然存在的数据,也就是持久性的数据;
// 右值是指那些在表达式执行结束后不再存在的数据,也就是临时性的数据。
// 有一种很简单的方法来区分左值和右值,对表达式取地址,如果编译器不报错就为左值,否则为右值。
// 带有括号的表达式
Student stu;
decltype(stu.age) t1 = 0; // stu.age 为类的成员访问表达式,符合推导规则一,t1 的类型为 int
decltype((stu.age)) t2 = t1; // stu.age 带有括号,符合推导规则三,t2 的类型为 const int&。
//加法表达式
int n = 0, m = 0;
decltype(n + m) p = 0; // n + m 得到一个右值,符合推导规则一,所以推导结果为 int
decltype(n = n + m) q = p; // n = n + m 得到一个左值,符号推导规则三,所以推导结果为 int&
}
return 0;
}
string
原生字符串
原生字符串(Raw String)指不进行转义“所见即所得”的字符串。从C++ 11开始开始支持原生字符串。
C++的语法格式如下:
(1)字符串前加R前缀;
(2)字符串首尾加上小括号;
以下是一个例子:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string path = R"(D:\workspace\cpp\my.cpp)";
cout << path << endl;
return 0;
}
字符串和其他类型的转换
C++ 11在string头文件中增加了一系列和其他基础类型的转换函数,使用时十分方便。
#include <iostream>
#include <string>
using namespace std;
int main()
{
{
// 字符串转换为其他类型
string str = "1";
//字符串转int
int a = stoi(str);
// 字符串转long
long b = stol(str);
// 字符串转long long
long long c = stoll(str);
// 字符串转float
float d = stol(str);
// 字符串转double
double e = stod(str);
// 字符串转unsigned long
unsigned long f = stoul(str);
// 字符串转unsigned long long
unsigned long long g = stoull(str);
// 字符串转long double
long double h = stold(str);
}
{
// 其他类型转换为字符串
string str;
// Int转string
str = to_string(1);
// float转string
str = to_string(1.0f);
// double转sting
str = to_string(1.0);
// to_string一共有9个重载,这里就不继续取例了
// string to_string(int val);
// string to_string(long val);
// string to_string(long long val);
// string to_string(unsigned val);
// string to_string(unsigned long val);
// string to_string(unsigned long long val);
// string to_string(float val);
// string to_string(double val);
// string to_string(long double val);
}
return 0;
}
for循环
C++ 11 新增了一种for循环方式:
for(declaration: expression)
{
//循环体
}
-
declaration表示是要定义一个变量,这个变量的类型要和即将被遍历的序列中的元素类型一致,当然也可使用auto或者decltypedecltype来进行定义,让编译器自动做类型的自动推导。
-
expression表示遍历的序列,可以是数组、字符串、容器甚至可以是一个大括号初始化的序列。
-
冒号后面的expression表达式只会被执行一次。
以下是实际例子:
#include<iostream>
#include<vector>
#include<map>
#include<set>
using namespace std;
int main()
{
// 遍历整型数组
int Numbers[] = { 1,2,3,4,5,6,7,8,9 };
for (int number : Numbers)
cout << number << " ";
cout << endl;
// 遍历字符串
// 注意:C++标准中的for循环遍历字符串的时候,不仅仅遍历到最后一个字符,而且还会遍历位于该字符串末尾的'\0'
char Text[] = "abcdefg";
for (decltype ('a') ch : Text)
cout << ch;
// 可以看见|前面有个空格
cout << "|" << endl;
// 遍历大括号初始化的列表
for (auto number : { 1,2,3,4,5 })
cout << number << " ";
cout << endl;
// 遍历容器
vector<int> vNumbers = { 1,2,3,4,5,6,7,8,9 };
for (auto number : vNumbers)
cout << number << " ";
cout << endl;
// 如果需要修改容器中的值,变量需要定义为引用类型
for (auto& number : vNumbers)
++number;
// 一般我们为了更快的遍历容器,避免复制开销,将其申明为常引用类型
for (const auto& number : vNumbers)
cout << number << " ";
cout << endl;
// 遍历map
map<string, int> MapInfo =
{
{"1", 1},
{"2", 2},
{"3", 3},
};
for (const auto& value : MapInfo)
{
// 注意这里的value推导出来是std::pair类型
cout << value.first << ":" << value.second << endl;
}
// 注意点:
// 1、要注意容器本身的约束
// 这里value没有被声明为const类型,但是由于set类型的值是只读的,所会报错。
// 类似的 std::map 的遍历中,基于范围的 for 循环中的 std::pair 引用,是不能够修改 first 的。
// set<int> SetInfo = { 1, 2, 3 };
// for (auto& value : SetInfo)
// value++;
// 2、禁止对容器大小做出改变,增加和减少元素都不行,因为遍历一开始遍历的范围已经确定了。
// vector<int> arr = { 1, 2, 3, 4, 5 };
// for (const auto& value : arr)
// {
// std::cout << value << std::endl;
// // 扩大容器
// arr.push_back(0);
// }
cout << endl;
return 0;
}
array
array容器是 C++ 11 标准中新增的序列容器,它就是在 C++ 普通数组的基础上进行了一次封装,使我们能更容易的操作数组。
// array
#include <array>
void ArrayTest()
{
// 初始大小并赋值为0
cout << "初始大小并赋值为0: ";
array<int, 10> arr = { 0 };
for (int i = 0; i < arr.size(); i++)
// at() 返回容器在n处的引用,有错误检测
cout << arr.at(i);
cout << endl;
// 赋值
for (int i = 0; i < arr.size(); i++)
arr[i] = i;
// 使用迭代器正向输出
cout << "正向输出: ";
for (array<int, 10>::iterator it = arr.begin(); it != arr.end(); it++)
cout << *it;
cout << endl;
// 使用迭代器逆向输出
cout << "逆向输出: ";
for (array<int, 10>::reverse_iterator it = arr.rbegin(); it != arr.rend(); it++)
cout << *it;
cout << endl;
// 第一个数据
cout << "front() 第一个数据: " << arr.front() << endl;
// 最后一个数据
cout << "back() 最后一个数据: " << arr.back() << endl;
// 所占字节
cout << "sizeof(arr) 所占字节: " << sizeof(arr) << endl;
// 返回容器当前元素的数量
cout << "size() 返回容器当前元素的数量: " << arr.size() << endl;
// 返回容器可容纳的最大数量
cout << "max_size() 返回容器可容纳的最大数量: " << arr.max_size() << endl;
// 判断容器是否为空
cout << "empty() 判断容器是否为空: " << arr.empty() << endl;
// 返回指向首个元素的指针
int* p = arr.data();
cout << "data() 返回指向首个元素的指针: " << p << endl;
// 全部填充为8
cout << "fill() 全部填充: ";
arr.fill(8);
for (int i = 0; i < arr.size(); i++)
cout << arr[i];
cout << endl;
// 交换两个Array中的数据
cout << "swap() 交换两个Array中的数据: " << endl;
array<int, 10> arr2 = { 3 };
arr.swap(arr2);
for (auto it = arr.begin(); it != arr.end(); it++)
cout << *it;
cout << endl;
for (auto it = arr2.begin(); it != arr2.end(); it++)
cout << *it;
cout << endl;
cout << endl;
// 多维数组
// 5 * 4 的数组
cout << "多维数组: " << endl;
array<array<int, 4>, 5> arr3 = {};
arr3.fill({ 1, 2, 3, 40 });
for (int i = 0; i < arr3.size(); i++)
{
for (int j = 0; j < arr3[i].size(); j++)
{
cout << arr3[i][j];
}
cout << endl;
}
cout << endl;
}
SmartPointer
C++11增加了智能指针来解决C++指针容易内存泄漏和容易成为野指针的问题,其本质是对普通指针进行了一次封装,记录其使用个数,当使用计数为0时,自动进行释放。
C++11提供3种智能指针:shared_ptr(共享指针), unique_ptr(独占指针), weak_ptr(弱指针)
使用智能指针需要避免以下错误:
- 不要将原始指针赋值给shared_ptr
shared_ptr<int> p = new int(); // 编译错误
- 不要将一个原始指针初始化多个shared_ptr,这点很好理解共享嘛就只能有一个。
int* p = new int(); shared_ptr<int> p1(p); shared_ptr<int> p2(p); // 逻辑错误,可能导致程序崩溃
- 不要在函数实参中创建shared_ptr,由于C++的函数参数计算顺序在不同的编译器(不同的默认调用惯例)下,可能不一样,一般从右到左,也可能从左到右,因而可能的过程是先new int,然后调用g()。如果恰好g()发送异常,而shared_ptr 尚未创建,那么int内存就泄漏了。
void f(shared_ptr<int> p, int a); int g(); f(shared_ptr<int>(new int), g()); // 有缺陷 // 正确写法 shared_ptr<int> p(new int()); f(p, g());
-
避免循环引用。循环引用会导致内存泄漏。这样的原因是:循环引用会导致2个shared_ptr引用计数无法归0,从而导致shared_ptr所指向对象无法正常释放。
class A; class B; class A { std::shared_ptr<B> bptr; ~A() { cout << "A is delete!" << endl; } }; class B { std::shared_ptr<A> aptr; ~B() { cout << "B is delete!" << endl; } }; void test() { shared_ptr<A> ap(new A); shared_ptr<B> bp(new B); ap->bptr = bp; bp->aptr = ap; // A和B对象应该都被删除,然而实际情况是都不会被删除:没有调用析构函数 }
-
通过shared_from_this()返回this指针。不要将this指针作为shared_ptr返回出来,因为this本质是一个裸指针。因此,直接传this指针可能导致重复析构。
// 将this作为shared_ptr返回,从而导致重复析构的错误示例 struct A { shared_ptr<A> GetSelf() { return shared_ptr<A>(this); // 不要这样做,可能导致重复析构 } ~A() { cout << "~A()" << endl; } }; shared_ptr<A> p1(new A); shared_ptr<A> p2 = p1->GetSelf(); // A的对象将被析构2次,从而导致程序崩溃
本例中,用同一个指针(this)构造了2个智能指针p1, p2(两者无任何关联),离开作用域后,this会被构造的2个智能指针各自析构1次,从而导致重复析构的错误。
正确返回this的shared_ptr做法:让目标类通过派生std::enable_shared_from_this类,然后使用base class的成员函数shared_from_this来返回this的shared_ptr。
struct A : public enable_shared_from_this<A> { shared_ptr<A> GetSelf() { return shared_from_this(); } ~A() { cout << "~A()" << endl; } }; shared_ptr<A> p1(new A); shared_ptr<A> p2 = p1->GetSelf(); // OK cout << p2.use_count() << endl; // 打印2,注意这里会引起指向A的raw pointer对应的shared_ptr的引用计数+1
void SmartPointerTest()
{
// 共享指针shared_ptr
{
cout << "共享指针shared_ptr" << endl;
// 构造方法一,传参构造
shared_ptr<int> pShared(new int(1));
// 构造方法二,使用工厂函数初始化(推荐,会有更好的性能)
pShared = make_shared<int>(3);
// 构造方法三,先创建空指针,再用reset方法构造
shared_ptr<int> pShared3;
pShared3.reset(new int(3));
// 构造并指定lambda表达式的删除器
shared_ptr<int> pShared5(new int(3), [](int* p) { delete p; });
// 错误的智能指针创建方法
// 编译错误,不允许直将原始指针赋值给智能指针
// shared_ptr<int> pShared6 = new int(1);
// 指针数组(C++ 17)
shared_ptr<int[]> pArrShared(new int[10]());
// 可以和普通指针一样判断其是否为NULL
if (NULL == pShared)
return;
// 可以和普通指针一样使用*号取值
cout << "*pShared is " << *pShared << endl;
// 可以转换为普通指针
// get获取原始指针并不会引起引用计数变化。
int* p = pShared.get();
cout << "*p is " << *p << endl;
// 输出引用计数
cout << "use_count is " << pShared.use_count() << endl;
// 构造方法四,复制构造
// 两个指针指向同一块内存
shared_ptr<int> pShared2 = pShared;
// 再次输出引用计数
cout << "use_count is " << pShared.use_count() << endl;
// 释放一个引用,导致引用计数-1;
pShared2.reset();
// 再次输出引用计数
cout << "use_count is " << pShared.use_count() << endl;
{
shared_ptr<int> pShared4 = pShared;
// 输出引用计数
cout << "use_count is " << pShared.use_count() << endl;
} // pShared4超出作用域,自动释放
// 再次输出引用计数
cout << "use_count is " << pShared.use_count() << endl;
}
cout << endl;
// 弱指针weak_ptr
{
cout << "弱指针weak_ptr" << endl;
shared_ptr<int> pShared = make_shared<int>(6);
weak_ptr<int> pWeak(pShared);
// 再次输出引用计数
cout << "use_count is " << pShared.use_count() << endl;
// 通过lock() 获取所监视的shared_ptr
shared_ptr<int> pShared2 = pWeak.lock();
// 再次输出引用计数
cout << "use_count is " << pShared.use_count() << endl;
pShared.reset();
pShared2.reset();
if (pWeak.expired())
// 通过expired() 判断所观测的资源释放已经被释放。
cout << "weak ptr is null !" << endl;
}
// 独占指针unique_ptr
{
cout << "独占指针unique_ptr" << endl;
unique_ptr<int> pUnique(new int());
// 错误,unique_ptr不允许复制
// unique_ptr<int> pUnique2 = pUnique;
// 移动指针
// 移动后,原来的unique_ptr不再拥有原来指针的所有权了,所有权移动给了新unique_ptr。
unique_ptr<int> pUnique2 = move(pUnique);
// C++ 14创建智能指针
unique_ptr<int> pUnique3 = make_unique<int>(10);
// 自定义unique_ptr删除器
unique_ptr<int, MyDeleter> pUnique4(new int(1));
cout << *pUnique4 << endl;
// ptr指向元素个数为5的int数组
unique_ptr<int[]> pArrUnique(new int[5]());
// unique_ptr<int> pArrUnique5(new int(1), [](int* p) { delete p; }); // 错误:为unique_ptr指定删除器时,需要确定删除器的类型
unique_ptr<int, void(*)(int*)> pArrUnique6(new int(1), [](int* p) { delete p; }); // OK
// unique_ptr<int, void(*)(int*)> pArrUnique7(new int(1), [&](int* p) { delete p; }); // 错误:lambda无法转换为函数指针,因为lambda没有捕获变量时,可以直接转换为函数指针,而捕获变量后,无法转换。
// 如果希望unique_ptr删除器支持已捕获变量的lambda,可以将模板实参由函数类型修改为std::function类型(可调用对象):
unique_ptr<int, function<void(int*)>> pArrUnique8(new int(1), [&](int* p) { delete p; });
}
}
Lambda表达式
Lambda这个词是从数学上来的,含义是简化某个数学函数的书写,对应到C++中也是一样的,也是简化C++函数的书写。
最简单的应用莫过于在sort函数中了,如下列例子将arr从大到小排序。
#include <iostream>
#include <vector>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main()
{
vector<int> arr = { 1, 3, 6, 2, 8, 5 };
sort(arr.begin(), arr.end(), [](int left, int right) { return left > right; });
for (int i = 0; i < arr.size(); i++)
cout << arr[i] << ends;
return 0;
}
这里给出Lambda表达式的一般写法:
[捕获列表](参数列表) mutable throw() -> 返回类型 { 函数体 }
捕获列表的方括号不可省略.
(参数列表)可连括号一起省略.
mutable修饰符,默认情况下Lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空).
throw()异常说明一般省略不写.
-> 返回类型 一般情况下都省略不写.
简化记忆:
[捕获列表](参数列表) { 函数体 }
(参数列表)可连括号一起省略.
- 捕获列表
- 捕获列表就是捕获外部的参数给Lambda表达式使用,可以指定以值传递的方式或引用传递的方式。
-
#include <iostream> #include <vector> #include <string> #include <algorithm> using namespace std; class A { public: A() { // [this]表示值传递方式捕捉当前的this指针 auto f6 = [this] { this->Print(); }; f6(); } void Print() { cout << "this is A." << endl; } }; int main() { // []表示不捕获任何变量 auto f1 = [] { cout << "Hi!" << endl; }; f1(); // [variable]表示值传递方式捕获变量variable int num = 1; auto f2 = [num] { cout << num << endl; }; f2(); // [=]表示值传递方式捕获所有父作用域的变量(包括this) string book = "English Book"; int BookNum = 10; auto f3 = [=] { cout << book << " " << BookNum << endl; }; f3(); // [&variable]表示引用传递捕捉变量variable int age = 18; auto f4 = [&age] { age = 20; }; f4(); cout << age << endl; // [&]表示引用传递方式捕捉所有父作用域的变量(包括this) string name = "Bob"; auto f5 = [&] { name = "Harry"; }; f5(); cout << name << endl; // [this]表示值传递方式捕捉当前的this指针 A test; // 拷贝与引用混合 int a = 1; int b = 2; int c = 3; auto f7 = [a, &b] { b = a; }; // a按照值专递, b按照引用传递 f7(); cout << a << " " << b << " " << c << endl; auto f8 = [=, &b, &c] { c = b = a; }; // b、c按照引用传递,其余变量按照值传递 f8(); cout << a << " " << b << " " << c << endl; auto f9 = [&, b, c] { a = b + c; }; // b、c按照值传递,其余变量按照引用传递 f9(); cout << a << " " << b << " " << c << endl; // 默认情况下Lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空)。 a = 2; auto f10 = [a]() mutable { cout << ++a << endl; }; f10(); cout << a << endl; // 异常处理 auto f11 = [](int para) throw() { if (para < 0) throw 0; cout << para << endl; }; int d = 3; f11(d); // 会引发异常 // d = -2; // f11(d); // 一般来说,我们不必写返回值,编译器会自动推导 auto f12 = []() -> int { return 1; }; int result = f12(); cout << result << endl; return 0; }
正则表达式
- 正则表达式就是一套表示规则的式子,专门用来处理各种复杂的字符串操作。regex是C++正则表达式库,于C++11加入,与之对应的还有一个针对wchar_t类型的特化为std::wregex。
- 常用符号的意义如下:
-
符号 意义 ^ 匹配行的开头 $ 匹配行的结尾 . 匹配任意单个字符 […] 匹配[]中的任意一个字符 (…) 设定分组 \ 转义字符 \d 匹配数字[0-9] \D \d 取反 \w 匹配字母[a-z],数字,下划线 \W \w 取反 \s 匹配空格 \S \s 取反 + 前面的元素重复1次或多次 * 前面的元素重复任意次 ? 前面的元素重复0次或1次 {n} 前面的元素重复n次 {n,} 前面的元素重复至少n次 {n,m} 前面的元素重复至少n次,至多m次 | 逻辑或 #include <iostream> #include <vector> #include <string> #include <algorithm> #include <regex> using namespace std; int main() { bool ret = false; cout << boolalpha; // 单纯判断是否匹配 { ret = regex_match("<html>value</html>", regex("<.*>.*</.*>")); cout << ret << endl; // 注意这里使用了原生字符串R"()",否则需要对\进行转义,既需要写成regex("\\D+\\d+\\D+") ret = regex_match("abcefg1235zzz", regex(R"([a-z]+\d+[a-z]+)")); cout << ret << endl; } cout << "--------------------------------------------" << endl; // 判断是否匹配,并获取匹配结果 { // 通过cmatch类型获取结果 cmatch match; // 每个需要获取的结果用()号进行分组 ret = regex_match("abcefg1235zzz", match, regex(R"([a-z]+(\d+)([a-z]+))")); if (ret) { // match[0]保存着匹配结果的所有字符 // str(),length(),position(),有默认参数0,代表第一个子匹配项 // 返回第一个子匹配项的字符串 cout << match.str() << endl; // 返回第一个子匹配项的长度 cout << match.length() << endl; // 返回第一个子匹配项的起始偏移量 cout << match.position() << endl; // 遍历处理每一个子匹配项 for (int i = 0; i < match.size(); i++) { // 返回子匹配项的字符串 cout << match.str(i) << endl; // 返回子匹配项的长度 cout << match.length(i) << endl; // 返回子组的起始偏移量 cout << match.position(i) << endl; // 返回子匹配项的字符串 string strChildMatch = match[i]; cout << strChildMatch << endl; } // 也可以使用迭代器来进行遍历 for (auto it = match.begin(); it != match.end(); it++) { cout << *it << endl; } } } cout << "--------------------------------------------" << endl; // 搜索 // "搜索"与"匹配"非常相像,不同之处在于"搜索"只要字符串中有目标出现就会返回,而非完全"匹配"。 { cmatch match; bool ret = regex_search("abcefg1235zzz", match, regex(R"(\d+)")); if (ret) { for (auto& element : match) { cout << element << endl; } // 对于"搜索",在匹配结果中可以分别通过prefix和suffix来获取前缀和后缀,前缀即是匹配内容前面的内容,后缀则是匹配内容后面的内容。 cout << match.prefix() << endl; cout << match.suffix() << endl; } cout << "循环搜索:" << endl; // 当待搜索的字符串中有多个目标时,需要循环搜索,将其一个个的找出来 // 注意这里使用的是smatch,不是cmatch smatch m; string str = "abcefg1235zzz324iei"; auto it = str.cbegin(); auto itEnd = str.cend(); while (regex_search(it, itEnd, m, regex(R"(\d+)"))) { cout << m.str() << endl; it = m.suffix().first; } } cout << "--------------------------------------------" << endl; // 替换 { // 全文替换 string str = "he...ll....o"; str = regex_replace(str, regex(R"(\.)"), ""); cout << str << endl; // 分组替换 string strName = "001-Harry,002-Bob"; strName = regex_replace(strName, regex(R"((\d+)-(\w+))"), "no = $1 name = $2"); cout << strName << endl; } cout << "--------------------------------------------" << endl; // 分词 { // 以逗号为分割符来切割这些内容 string str = "123,456,789"; regex reg(","); // 最后一个参数,这个参数可以指定一系列整数值,用来表示你感兴趣的内容,此处的-1表示对于匹配的正则表达式之前的子序列感兴趣; // 而若指定0,则表示对于匹配的正则表达式感兴趣,这里就会得到“,";还可对正则表达式进行分组,之后便能输入任意数字对应指定的分组 sregex_token_iterator token(str.begin(), str.end(), reg, -1); sregex_token_iterator end; while (token != end) { cout << token->str() << endl; token++; } } return 0; }