目录
- [一: C++11发展史](#一: C++11发展史)
- 二:列表初始化
-
- [1. 结构体初始化](#1. 结构体初始化)
- [2. 使用C++11中的思想进行初始化--------一切皆可用{}初始化](#2. 使用C++11中的思想进行初始化--------一切皆可用{}初始化)
- 三:initializer_list
- 四:右值引用和移动语义
- 五:可变参数模版
- 六:类的新功能
- 七:lambda
- 八:包装器,function和bind
一: C++11发展史
C++11是C++发展过程中的第二个版本,是C++98以来最重要的一次更新,曾用名为"C++0x"。
下面是C++版本的迭代示意图:
二:列表初始化
在C++98的版本中,一般结构体和数组可以用{}初始化,但是在C++11之后,试图一切皆可用{}初始化
1. 结构体初始化
cpp
struct Test
{
int _x;
int _y;
};
int main()
{
struct Test test = { 1,2 };
std::cout << test._x << std::endl;
std::cout << test._y << std::endl;
return 0;
}
2. 使用C++11中的思想进行初始化--------一切皆可用{}初始化
- 在用{}初始化的时候,可以省略掉=
- 内置类型也可支持{}初始化
cpp
#include<vector>
using namespace std;
struct Point
{
int _x;
int _y;
};
class Date
{
public:
//默认构造
Date(int year = 1, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
//拷贝构造
Date(const Date& d)
: _year(d._year)
, _month(d._month)
, _day(d._day)
{
cout << "Date(const Date& d)" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//自定义类型初始化
Date d1 = { 2025,9,29 };
//去掉=也可以
Date d2 { 2025,9,29 };
/////////////////////////////////////////////
//内置类型初始化
int i = 1;
int i = {1};
int i {1};
return 0;
}
三:initializer_list
- 在STL中,增加了一个initializer_list的构造。
- initializer_list支持的迭代器遍历,begin和end返回的是指针
比如,使用一个vector对象,我们可以这样用,vector v1 = {1,2,3};
cpp
int main()
{
std::initializer_list<int> mylist ;
mylist = { 1,2,3,4,5,6 };
cout << sizeof(mylist) << endl;
//支持迭代器
cout << *mylist.begin() << endl;
cout << *mylist.end() << endl;
//使用了这个构造函数
//vector (initializer_list<value_type> il,const allocator_type& alloc = allocator_type());
vector<int> v1({ 1,2,3,4,5,6 });
return 0;
}
四:右值引用和移动语义
1.左值和右值的区别:
- 相比于C++98 ,在C++11中增加了右值引用,可能这时候我们会很困惑,其实,我们之前学习的引用是左值引用。
- 但是无论是左值引用还是右值引用,都是给对象取别名。
- 对于左值和右值最明显的区分方法就是能不能取地址
- 左值是能够取地址
- 右值是能够取地址
下面是一些常见的左值与右值的例子
cpp
#include<iostream>
using namespace std;
int& getRef(int& a) { return a; }
int main() {
int x = 10;
int y = 5;
int arr[3] = { 1, 2, 3 };
int* p = &y;
// 常见左值
x = 20; // 具名变量
*p = 42; // 解引用表达式
arr[0] = 10; // 数组元素
getRef(x) = 200; // 返回引用的函数
char str[] = "hello";
str[0] = 'H'; // 字符数组元素
//常见右值
int a = 1 + 2; // 算术运算的结果(1+2 是右值)
int b = x + y; // 表达式结果
int c = 100; // 字面量 100 是右值
int d = x++; // x++ 产生右值
int e = std::move(x); // std::move(x) 是右值
return 0;
}
2.引用与声明周期
- 一般临时对象的声明周期只有一行,如果想要延长这个临时对象的生命周期,就需要右值引用。
cpp
int a = 1;
int b = 1;
int&& c = a+b;
那么问题就来了,int c = a+b;是什么?
其实a+b由系统生成临时对象,然后再拷贝给了c,如果涉及到类的拷贝构造,这样其实会增加系统的消耗。
3.左值和右值的参数匹配
- C++98中,我们实现⼀个const左值引⽤作为参数的函数,那么实参传递左值和右值都可以匹配。
- C++11以后,分别重载左值引⽤、const左值引⽤、右值引⽤作为形参的f函数,那么实参是左值会
匹配f(左值引⽤),实参是const左值会匹配f(const 左值引⽤),实参是右值会匹配f(右值引⽤).
cpp
#include <iostream>
using namespace std;
// 左值引用版本
void f(int& x) {
cout << "调用 f(int&): 左值引用重载, x=" << x << "\n";
}
// const 左值引用版本(既能接左值,也能接右值常量)
void f(const int& x) {
cout << "调用 f(const int&): const 左值引用重载, x=" << x << "\n";
}
// 右值引用版本
void f(int&& x) {
cout << "调用 f(int&&): 右值引用重载, x=" << x << "\n";
}
int main() {
int i = 1;
const int ci = 2;
f(i); // i 是一个左值 → 调用 f(int&)
f(ci); // ci 是 const 左值 → 调用 f(const int&)
f(3); // 3 是右值 → 调用 f(int&&)
// 如果没有 f(int&&),就会退化调用 f(const int&)
f(std::move(i)); // std::move(i) 把 i 转换成右值 → 调用 f(int&&)
// 注意:右值引用本身在表达式中是左值
int&& x = 1; // 1 是右值,x 是一个右值引用变量
f(x); // x 是有名字的变量 → 左值 → 调用 f(int&)
f(std::move(x)); // std::move(x) → 转换为右值 → 调用 f(int&&)
return 0;
}
注意:
- 右值本身不可以修改,但是右值引用后就可以修改。
- 右值引用本身的属性是左值
4.移动构造和移动赋值
移动构造函数是⼀种构造函数,类似拷⻉构造函数,移动构造函数要求第⼀个参数是该类类型的引⽤,但是不同的是要求这个参数是右值引⽤,如果还有其他参数,额外的参数必须有缺省值。
移动赋值是⼀个赋值运算符的重载,他跟拷⻉赋值构成函数重载,类似拷⻉赋值函数,移动赋值函数要求第⼀个参数是该类类型的引⽤,但是不同的是要求这个参数是右值引⽤。
移动构造和移动赋值的本质是资源的转移,而不是拷贝。
使用string类的部分模拟实现来说明:
cpp
//移动构造
string(string&& s)
{
//转移资源
swap(s);
}
//移动赋值
string& oprerator=(string&& s)
{
swap(s);
return *this;
}
在C++98的时候,返回的对象是这个对象的拷贝,这样会增加系统的消耗。
cpp
std::string foo() {
std::string s = "hello";
return s; // 会调用一次拷贝构造
}
但是,在C++11的时候,我们可以对这一现象进行优化,使用移动构造和移动赋值一定程度上"取代"拷贝构造和移动构造,对资源采用互换的方法,减少系统的消耗!
5.引用折叠
通俗来讲,引用的引用叫做引用折叠。但是,int& && r = i;系统会报错!typedef和模版可以使用引用折叠
通过引用折叠,我们可以引出万能引用的概念,所谓万能引用,就是右值引用的右值引用折叠成右值引用,其他均为左值引用。
cpp
template<class T>
void func1(T&& x)
{
/////
}
template<class T>
void func2(T& x)
{
/////
}
int main()
{
int a = 0;
func1<int>(a);//实例化为void func(int& x);
func1<int&>(a);//实例化为void func(int& x);
func1<int&&>(0);//实例化为void func(int&& x);
func2<int>(a);//实例化为void func(int& x);
func2<int&>(a);//实例化为void func(int& x);
func2<int&&>(a);//实例化为void func(int& x);
return 0;
}
- 如果想将左值传给右值,那么需要move()
- func1<int&&>(std::move(a));
6.完美转发
在Function(int&& t)里面,通过前面引用折叠,传左值实例化后就是左值引用函数,传右值实例化后就是右值引用函数。
此时就会引入一个问题,变量表达式全是左值属性,再次使用这个参数传入另一个函数的时候,它的属性可能发生变化,想要保持对象的属性,那么就需要完美转发。
cpp
tempale<class T>
void func1(T&& t)
{
func2(t);//当t为右值的时候,再传入fun2的时候,就会变成左值
func3(forward<T>(t));//完美转发
}
五:可变参数模版
1.基本概念
- 所谓可变参数模版,就是支持传入n个参数。
两个参数包:
- 模版参数包:表示零或者多个模版参数;
- 函数参数包:表示零或者多个函数参数;
template <class ...Args>
void func(Args ...args)
{}
template <class ...Args>
void func(Args& ...args)
{}
template <class ...Args>
void func(Args&& ...args)
{}
- 我们可以使用sizeof...来计算参数保重的个数
cpp
template<class ...Args>
void func(Args ...args)
{
//计算参数保重的个数
cout<<sizeof...(args)<<endl;
}
2.包拓展的使用
除了使用sizeof...计算包的参数个数,我们还需将包拓展,能够使用包里面的参数
使用例子:
cpp
#include<iostream>
using namespace std;
//3.终止条件
void func()
{
cout << endl;
}
//2.
template<class T, class ...Args>
void func(T&& t, Args&&... args)
{
cout << t << endl;
func(args...);
}
//1.
template<class ...Args>
void Print(Args&&... args)
{
func(args...);
}
int main()
{
Print(1, "nihao", 3);
return 0;
}

3.empalce系列接口
- C++11后面STL容器增加了empalce系列的接口,它们的接口参数均为模版可变参数,在功能上与insert和push兼容。
- 假设容器container,empalce还支持直接插入构造T对象的参数,这样的场景更加高效一些,可以直接在容器上面构造T对象。
在实践上,我们可以直接使用empalce系列接口代替oush,insert系列接口。
cpp
#include<list>
#include<string>
using namespace std;
// emplace_back总体⽽⾔是更⾼效,推荐以后使⽤emplace系列替代insert和push系列
int main()
{
list<string> lt;
// 传左值,跟push_back⼀样,⾛拷⻉构造
std::string s1("111111111111");
lt.emplace_back(s1);
// 右值,跟push_back⼀样,⾛移动构造
lt.emplace_back(move(s1));
// 直接把构造string参数包往下传,直接⽤string参数包构造string
// 这⾥达到的效果是push_back做不到的
lt.emplace_back("111111111111");
list<pair<string, int>> lt1;
// 跟push_back⼀样
// 构造pair + 拷⻉/移动构造pair到list的节点中data上
pair<string, int> kv("苹果", 1);
lt1.emplace_back(kv);
// 跟push_back⼀样
lt1.emplace_back(move(kv));
// 直接把构造pair参数包往下传,直接⽤pair参数包构造pair
// 这⾥达到的效果是push_back做不到的
lt1.emplace_back("苹果", 1);
return 0;
}
六:类的新功能
1.默认的移动构造和移动赋值
- 原来的C++类(C++98)中,有六个默认成员函数:构造,析构,拷贝,拷贝赋值重载,取地址重载,const取地址重载
- 在C++11中,新增了俩默认成员函数,移动构造函数和移动赋值函数。
- 规则:如果你没有⾃⼰实现移动构造函数,且没有实现析构函数 、拷⻉构造、拷⻉赋值重载中的任意⼀个。那么编译器会⾃动⽣成⼀个默认移动构造。默认⽣成的移动构造函数,对于内置类型成员会执⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调⽤移动构造没有实现就调⽤拷⻉构造。
2.成员变量给缺省值
- 在类的成员变量声明时候,可以给缺省值
cpp
class A {
private:
int x = 10; // 成员变量声明时给缺省值
public:
A() {} // 构造函数没有显式初始化x
};
成员变量声明时的缺省值是给初始化列表兜底用的
3.defult和delete
- defult:显式要求编译器生成默认版本
在 C++11 之前,编译器会自动帮你生成一些默认函数,比如:默认构造函数,拷贝构造函数,拷贝赋值运算符,.析构函数,但是,一旦你手动定义了某些函数(比如拷贝构造),编译器就不会再帮你生成"与之冲突"的那一类默认函数,比如移动构造或移动赋值。
假如你确实还想让编译器生成默认版本,用 = default 显式告诉编译器,要求编译器生成默认版本!
cpp
class A {
public:
A() = default; // 要求生成默认构造
A(const A&) = default; // 默认拷贝构造
A(A&&) = default; // 默认移动构造
A& operator=(const A&) = default; // 默认拷贝赋值
A& operator=(A&&) = default; // 默认移动赋值
~A() = default; // 默认析构
};
- delete:明确禁止使用某个函数
cpp
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete; // 禁止拷贝构造
NonCopyable& operator=(const NonCopyable&) = delete; // 禁止拷贝赋值
};
4.final与override
两个关键字都是 C++11 引入的面向对象增强语法,目的就是让"继承"和"虚函数"变得可控且安全
- override ------ 明确表示重写
在传统 C++(C++98)里,虚函数重写完全靠函数签名是否匹配来判断。问题是,写错一点点,就会变成"另一个新函数",而不是重写原函数。编译器也不会告诉你。
比如:
cpp
class Base {
public:
virtual void foo(int) {}
};
class Derived : public Base {
public:
void foo(double) {} // 看似重写,其实是重载,不会覆盖父类函数
};
这种错误极其隐蔽,父类指针调用 foo(10) 仍然执行父类版本。
C++11 的解决办法是引入 override:
cpp
class Derived : public Base {
public:
void foo(int) override {} // 明确告诉编译器:这是重写
};
此时如果函数签名不匹配,编译器直接报错。这就是它的作用------让意图显式化。
- final ------ 禁止继承或进一步重写
final 有两种用法:
修饰类:禁止这个类被继承
修饰虚函数:禁止这个虚函数在子类中再被重写
(1) 修饰类
cpp
class A final {
public:
void show() {}
};
class B : public A {}; // 编译错误:A 是 final 类,不能继承
(2) 修饰虚函数
cpp
class Base {
public:
virtual void foo() {}
};
class Derived : public Base {
public:
void foo() final {} // 可以重写,但禁止再往下重写
};
class SubDerived : public Derived {
public:
void foo() override {} // 编译错误:foo 在 Derived 中是 final 的
};
- 两个组合在一起使用
cpp
class Base {
public:
virtual void func() {}
};
class Derived : public Base {
public:
void func() override final {} // 明确重写,同时禁止再重写
};
七:lambda
- 基本语法和使用方法:
lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。lambda 表达式语法使⽤层⽽⾔没有类型,所以我们⼀般是⽤auto或者模板参数定义的对象去接收 lambda 对象。
lambda表达式的格式: [捕获列表] (参数列表) -> 返回类型 { 函数体 };
各部分含义:
捕获列表 []:决定 lambda 内能访问哪些外部变量,以及如何访问(按值、按引用)。
参数列表 ():函数参数。
返回类型 ->:可选,如果编译器能推断就不用写。
函数体 {}:具体执行的代码。
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {1,2,3,4,5};
// Lambda 遍历输出
for_each(v.begin(), v.end(), [](int x){
cout << x << " ";
});
cout << endl;
// Lambda 带返回值
auto square = [](int x) -> int { return x*x; };
cout << square(5) << endl; // 25
}
- 捕获 this
在类成员函数中,lambda 可以捕获 this 指针访问成员:
cpp
class A {
int value = 100;
public:
void run() {
auto f = [this]() { cout << value; };
f(); // 100
}
};
八:包装器,function和bind
- 可调用对象(Callable)
在 C++ 中,能够"像函数一样被调用"的东西都叫可调用对象(callable):
- 普通函数
cpp
int add(int a, int b) { return a+b; }
- 函数指针
cpp
int (*pf)(int,int) = add;
pf(1,2);
- 函数对象(Functors)
cpp
struct Multiply {
int operator()(int a, int b) { return a*b; }
};
Multiply mul;
mul(2,3); // 6
- Lambda 表达式
cpp
auto lambda = [](int a,int b){ return a-b; };
lambda(5,3); // 2
- std::function ------ 通用函数包装器
std::function 是 一个类型擦除的模板类,可以存储任何符合签名的可调用对象。
特点:
- 可以存储函数指针、函数对象、Lambda。
- 可以像普通函数一样调用。
cpp
#include <functional>
#include <iostream>
using namespace std;
int add(int a,int b){ return a+b; }
int main() {
std::function<int(int,int)> f1 = add; // 普通函数
std::function<int(int,int)> f2 = [](int a,int b){ return a*b; }; // lambda
cout << f1(2,3) << endl; // 5
cout << f2(2,3) << endl; // 6
}
- std::bind ------ 参数绑定与延迟调用
std::bind 可以 把函数的一部分参数提前绑定,返回一个新的可调用对象。
它的作用有点像"生成小函数版本",或者"函数局部化"。
cpp
#include <functional>
#include <iostream>
using namespace std;
int add(int a, int b, int c) { return a+b+c; }
int main() {
auto f = std::bind(add, 1, 2, std::placeholders::_1);
// bind 参数说明:
// 1,2 固定绑定,_1 是调用时传入的第一个参数
cout << f(3) << endl; // 1+2+3 = 6
}
可以和 std::function 配合:
cpp
std::function<int(int)> func = f;
cout << func(5) << endl; // 1+2+5 = 8
std::bind 的占位符 _1, _2, _3... 对应调用时提供的参数。
可以改变参数顺序,或者固定部分参数,非常灵活。