C++11新特性详解
C++11新特性详解
C++11作为C++语言的一个重要版本,引入了许多新特性,这些特性极大地提高了代码的可读性和效率。本文将详细介绍C++11中的列表初始化、左值与右值、类型推导、lambda表达式、函数对象及绑定、Args参数包和异常处理等内容,帮助开发者更好地理解和使用这些新特性。
列表初始化
在C++11中,列表初始化是一种新的初始化方式,可以用于各种类型的变量和容器。其基本用法是在变量名后面加上初始化列表进行初始化。
class T1
{
public:
Test(int x, int y) : _x(x), _y(y) {}
~Test() {}
private:
int _x;
int _y;
};
struct T2
{
int x;
int y;
};
int main()
{
vector<int> a1({ 1, 2, 3 }); // 构造
// 等号可有可无
vector<int> a2{ 1, 2, 3 };
vector<int> a3 = { 1, 2, 3 };
// 内置类型也可以使用列表初始化
int a4 = { 1 };
int a5{ 1 };
// 自定义类型根据其构造函数使用即可
// 这里实际上是:多参数的隐式类型转换
T1 a6({ 1, 2 });
T1 a7{ 1, 2 };
T1 a8 = { 1, 2 };
T2 a9 = { 2, 2 };
return 0;
}
左值与右值
- 左值:可以取地址,可以对其赋值(可以出现在赋值号的左边)。
- 右值:不能取地址,不可以出现在赋值号的左边。
右值又有纯右值和将亡值两种,纯右值指内置类型的,而将亡值是自定义类型的。主要表现为返回值、匿名对象和临时对象。
左值引用和右值引用
- 左值引用:相当于左值的别名,可以减少拷贝,对于函数传引用也便于修改变量。
- 右值引用:相当于右值的别名,不可修改,可以节省资源。
- 左值引用不能给右值取别名(但const左值引用可以)。
- 右值引用不能给左值取别名(但可以给move以后的左值取别名)。
注:右值引用本身也是左值。move(左值)
的结果为右值,数据不变。
移动构造和移动赋值
这是基于右值完成的,右值的特点是很快会被系统回收内存空间的值。这时可能就存在一个右值马上要被回收,但是恰好有对象想要这个值,此时利用移动赋值/构造将这个右值的数据给该对象,就节省了一次回收和创造的开销。
秉承着反正你也要被回收了,还不如拿来吧你的原则,移动构造/赋值就诞生了。
类型推导
- decltype 关键字:用于推导表达式的类型。
- auto 关键字:允许编译器自动推导变量的类型。
用法:
decltype(变量名)
推导成与该变量名一样的类型。auto
直接使用,会自动推导成赋值的类型。(但必须初始化)
int main()
{
int x = 1;
int y = 1;
decltype(x) a = x + y;
cout << typeid(a).name() << endl;
cout << "a:" << a << endl;
auto b = x + y;
cout << typeid(b).name() << endl;
cout << "b:" << b << endl;
return 0;
}
不建议滥用,可能会增加维护难度,例如查看返回值类型困难的问题。
auto A()
{
return 1;
}
auto B()
{
return A();
}
auto C()
{
return B();
}
int main()
{
auto a = C();
return 0;
}
lambda
lambda是一种定义匿名函数对象的方法。
- 语法:
[捕捉列表] (参数列表) ->返回类型{ 函数体 }
以add函数为例:
int main()
{
auto add = [](int x, int y)->int {return x + y; };
cout << add(2, 3) << endl;
return 0;
}
捕捉列表
捕获列表:用于指定 Lambda表达式可以访问的外部变量。
捕获分为按值捕获和引用捕获两大类,引用捕获的变量可以修改,但按值捕获的不行。其中又分为全部捕获和指定地去捕获。
int main()
{
int x = 10;
int y = 10;
// 值捕获
auto add1 = [x](int y)->int {return x + y; };
cout << "add1(3) = " << add1(3) << endl;
cout << " x = " << x << endl;
//-------------------------------------------------
// 引用捕获
auto add2 = [&x](int y)->int {
x = 20;
return x + y; };
cout << "add2(3) = " << add2(3) << endl;
cout << " x = " << x << endl;
//-------------------------------------------------
// 全(引用)捕获
auto add3 = [&]()->int {return x + y; };
cout << "add3() = " << add3() << endl;
cout << " x = " << x << endl;
//-------------------------------------------------
// 全(按值)捕获
auto add4 = [=]()->int {return x + y; };
cout << "add4() = " << add4() << endl;
cout << " x = " << x << endl;
return 0;
}
除此之外还能混用,例如:
int main()
{
int x = 10;
int y = 10;
// x进行引用捕获,其余使用按值捕获
auto add1 = [=,&x]()->int {return x + y; };
return 0;
}
函数对象及绑定
bind函数
头文件:<functional>
使用方法:bind(函数名,...) ...
为要绑定的参数
// 打印名字,年龄,编号
void Printf(string name, int age, int number)
{
cout << name << " " << age << " " << number << endl;
}
int main()
{
auto P1 = ::bind(Printf, "张三", std::placeholders::_1, std::placeholders::_2);
// 第一个参数绑定了“张三”所以不用再传入
P1(20, 5);
return 0;
}
同理可以有:
int main()
{
// 全绑定
auto P2 = ::bind(Printf, "张三", 20, 5);
P2();
// age绑定20,_1占位符对应name,_2占位符对应number
auto P3 = ::bind(Printf, std::placeholders::_1, 20, std::placeholders::_2);
P3("张三", 5);
// number绑定5,_1占位符对应name,_2占位符对应age
auto P4 = ::bind(Printf, std::placeholders::_1, std::placeholders::_2, 5);
P4("张三", 20);
return 0;
}
绑定类的成员函数时要注意,类的成员函数是要传入this指针的。所以想对成员函数进行绑定要绑定一个对象。
class D
{
public:
D(int y, int m, int d)
: _year(y), _month(m), _day(d)
{}
void PrintfDate()
{
cout << _year << " : " << _month << " : " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
D t(2025, 3, 12);
auto P1 = ::bind(&D::PrintfDate, t);
auto P2 = ::bind(&D::PrintfDate, &t);
P1();
P2();
return 0;
}
包装器
类模板 std::function
是通用多态函数封装器。std::function
的实例能存储、复制及调用任何可调用 (Callable) 目标——函数、 lambda 表达式、 bind 表达式或其他函数对象,还有指向成员函数指针和指向数据成员指针。
使用方法:function<返回值(参数类型列表)> 变量名 = 函数指针
// 打印名字,年龄,编号
void Printf(string name, int age, int number)
{
cout << name << " " << age << " " << number << endl;
}
int main()
{
function<void(int, int)> P1 = ::bind(Printf, "张三", std::placeholders::_1,
std::placeholders::_2);
function<void(string, int, int)> P2 = Printf;
function<void(string, int, int)> P3 = [](string name, int age, int number)->void{cout
<< name << " " << age << " " << number << endl;};
P1(20, 5);
P2("张三", 20, 5);
P3("张三", 20, 5);
return 0;
}
Args参数包
C++可变参数是指函数的参数个数是可变的,可以在函数定义时不确定参数的个数,需要在函数体内通过特定的语法来处理这些参数。
#include <iostream>
// 递归终止条件
void print() {}
// 使用参数包实现可变参数打印函数
template <typename T, typename... Args>
// 每次执行时都把参数包第一个参数拿出来给first。
//(准确的说是除了参数保外有多少个形参就应该取出多少个参数)
void print(T first, Args... args) {
std::cout << first << std::endl;
print(args...);
}
// 也可以直接写...代表参数包
// template <typename T>
// void print(T first, ...) {
// std::cout << first << std::endl;
// print(args...);
// }
int main() {
print(1, "hello", 3.14, "world");
return 0;
}
抛异常
在C++中,异常处理的关键字包括:
throw
:当程序在运行过程中发生异常时,可以使用throw关键字抛出一个异常对象。异常可以是任意的类型,通常是一个派生自std::exception的异常类对象。try
:使用try块来包裹可能引发异常的代码块。当异常被抛出时,程序会寻找与之匹配的catch块进行处理。catch
:在try块之后可以跟随一个或多个catch块,用来捕获并处理特定类型的异常。每个catch块可以处理不同类型的异常,也可以使用省略号(...)来捕获任意类型的异常。
void divide(int a, int b) {
if (b == 0) {
throw std::runtime_error("除以了0");
}
std::cout << "结果: " << a / b << std::endl;
}
int main() {
try {
divide(10, 2);
divide(8, 0); // 会引发异常
}
catch (const std::exception& e) {
std::cerr << "捕获到异常: " << e.what() << std::endl;
}
catch (...) {
std::cout << "未知异常" << std::endl;
}
return 0;
}
如果是在嵌套的函数抛出异常在自己那层异常没有被捕获,就会层层返回直到异常被捕获。