C++新特性

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;
      }
      
      

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值