首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C++11:现代C++的演变与提升

C++11:现代C++的演变与提升

作者头像
海棠未眠
发布2025-10-22 15:36:21
发布2025-10-22 15:36:21
1420
举报

前言

在2003年,C++标准委员会发布了一份技术勘误表(简称TC1),使得C++03取代了C++98成为C++标准的最新版本。然而,C++03(TC1)主要集中于修复C++98中的缺陷,而语言的核心部分并没有重大变化。因此,人们通常将这两个标准统称为C++98/03。

C++11,这个被称为“C++0x”的标准,经过十年的发展终于正式发布。与C++98/03相比,C++11带来了许多显著的变化和改进。它包含了约140个新特性,并修正了C++03标准中的约600个缺陷,使得C++11不仅仅是C++98/03的延续,而是一个真正意义上的新标准。

C++11的核心优势在于:

  • 语言功能的增强:新特性使得C++11在系统开发和库开发中表现更加出色。
  • 语法的简化和泛化:C++11引入了自动类型推导、范围-based for 循环、nullptr等,使得代码更加简洁和易于维护。
  • 性能和安全性提升:移动语义和右值引用减少了不必要的拷贝,提高了性能,新的线程库增强了多线程编程的安全性和效率。

在实际开发中,C++11的引入不仅丰富了语言的功能,而且极大地提升了程序员的开发效率。虽然C++11的特性非常广泛,本篇文章将重点讲解一些实际中最为实用的语法和功能,以帮助更好地掌握这一现代C++标准。

一、统一的列表初始化

1、{}初始化

在C++98版本,我们可以使用{}来对数组和结构体元素来进行统一的列表初始值设定,例如:

代码语言:javascript
复制
//在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。
struct Point
{
	int x;
	int y;
};

int main()
{
	int arr[] = { 1,2,3,4,5 };
	int arr2[5] = { 1 };
	Point p = { 1,2 };
	return 0;
}

但是在C++11版本中,其扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加

代码语言:javascript
复制
struct Point
{
	int x;
	int y;
};

int main()
{
	int x1 = 1;
	int x2{ 2 };

	int arr1[]{ 1,2,3,4,5 };
	int arr2[5]{ 1 };

	Point p{ 1,2 };

	//甚至列表初始化也能运用到new表达式中
	int* pa = new int[4] {0};
	return 0;
}

就连自定义类型创建对象时,也可以通过列表初始化的方式调用构造函数初始化。

2、std::initializer_list

std::initializer_list 是 C++11 引入的一种用于支持列表初始化的类型。它可以让你方便地用一组元素来初始化容器、对象或自定义类型。std::initializer_list 的主要用途是提供一种简便的语法,用于将一系列值传递给函数、构造函数或其他结构。

代码语言:javascript
复制
void foo(std::initializer_list<int> values)
{
    for (auto value : values)
    {
        std::cout << value << " ";
    }
}


int main()
{
    foo({ 1,2,3,4,5 });
    return 0;
}

std::initializer_list通常有以下三种运用方法:

构造函数: 允许类通过 initializer_list 来接收多个初始化参数:

代码语言:javascript
复制
class MyClass {
public:
    MyClass(std::initializer_list<int> list) 
    {
        for (auto elem : list) 
        {
            std::cout << elem << " ";
        }
        std::cout << std::endl;
    }
};

MyClass obj = {1, 2, 3, 4};


//用initializer_list 来接收一个初始化列表,并打印它的内容。

容器的初始化: C++ STL 容器(如 std::vector, std::set)也可以使用 initializer_list 来进行初始化:

代码语言:javascript
复制
std::vector<int> vec = {1, 2, 3, 4, 5};
std::set<int> s = {10, 20, 30};

函数参数: 可以通过 initializer_list 传递不定数量的参数:

代码语言:javascript
复制
void sum(std::initializer_list<int> list) 
{
    int result = 0;
    for (auto elem : list) 
    {
        result += elem;
    }
    std::cout << "Sum: " << result << std::endl;
}

sum({1, 2, 3, 4});  //输出结果为10

二、新的声明

c++11 提供了多种简化声明的方式,尤其是在使用模板时。

1、auto

在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局

部的变量默认就是自动存储类型,所以auto就没什么价值了。

C++11中废弃auto原来的用法,将 其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初 始化值的类型。

代码语言:javascript
复制
#include<map>
#include<string>
int main()
{
	int i = 10;
	auto p = &i;
	auto pf = strcpy;
	cout << typeid(p).name() << endl;
	cout << typeid(pf).name() << endl;
	map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
	//map<string, string>::iterator it = dict.begin();
	auto it = dict.begin();
	return 0;
}

2、decltype

关键字 decltype 将变量的类型声明为表达式指定的类型。

通常用于配合auto使用,快速推到变量类型:

代码语言:javascript
复制
int main()
{
	map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
	auto it = dict.begin();
	vector<decltype(it)>arr;
	return 0;
}

3、nullptr

C++把NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示

整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。

代码语言:javascript
复制
#ifndef NULL
#ifdef __cplusplus
#define NULL   0
#else
#define NULL   ((void *)0)
#endif
#endif

三、范围for循环

C++11 引入了范围 for 循环,这是一个简洁、方便的语法,用于遍历容器或数组中的元素。相比传统的 for 循环,范围 for 循环无需显式管理索引,代码更加简洁明了。

代码语言:javascript
复制
for (declaration : container) 
{
    // 循环体
}
  • declaration 是一个变量,用来表示当前循环迭代的元素,可以使用自动类型推导 auto
  • container 是一个支持迭代的容器,比如数组、std::vectorstd::mapstd::set 等。

当我们不需要改变值时就可以使用按值传递,需要改变值时就按引用传递:

代码语言:javascript
复制
int main() 
{
    int arr[] = {1, 2, 3, 4, 5};
    
    for (int x : arr) 
    {
        x *= 2;  // 修改的是副本,不会影响原数组
    }
    
    for (int x : arr) 
    {
        cout << x << " ";  // 输出依然是 1 2 3 4 5
    }

    for (int& x : arr) 
    {
        x *= 2;  // 修改的是原数组
    }
    
    for (int x : arr) 
    {
        cout << x << " ";  // 输出 2 4 6 8 10
    }
    return 0;
}

四、右值引用与移动语义

C++11 引入了右值引用(Rvalue References)和移动语义(Move Semantics),它们大大提高了程序的效率,特别是在涉及大对象或资源管理时。右值引用允许我们利用临时对象的资源,而移动语义则通过转移资源所有权来避免不必要的拷贝。

1. 左值 vs 右值

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们

之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名

左值引用就是给左值的引用,给左值取别名。右值引用就是对右值的引用,给右值取别名。在理解右值引用,左值引用之前,首先要理解 左值右值的概念。

左值(Lvalue):左值是一个表达式,它指向内存中的一个固定地址,并且可以出现在赋值操作的左侧。左值通常指的是变量的名字,它们在程序的整个运行期间都存在。左值的判断标准:可以取地址、有名字的就是左值。

代码语言:javascript
复制
// 以下的p、b、c、*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;

// 以下几个是对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;

右值(Rvalue):右值是一个临时的、不可重复使用的表达式,它不能出现在赋值操作的左侧。右值通常包括字面量、临时生成的对象以及即将被销毁的对象。右值的判断标准:不可以取地址、没有名字的就是右值。

代码语言:javascript
复制
// 以下几个都是常见的右值
10;
x + y;
fmin(x, y);

// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);

2、​​​​​​左值引用与右值引用的比较

左值引用总结:

1. 左值引用只能引用左值,不能引用右值。

2. 但是 const 左值引用既可引用左值,也可引用右值

代码语言:javascript
复制
int main()
{
    // 左值引用只能引用左值,不能引用右值。
    int a = 10;
    int& ra1 = a;   // ra为a的别名
    //int& ra2 = 10;   // 编译失败,因为10是右值
    // const左值引用既可引用左值,也可引用右值。
    const int& ra3 = 10;
    const int& ra4 = a;
    return 0;
}

右值引用总结:

1. 右值引用只能右值,不能引用左值。

2. 但是右值引用可以 move 以后的左值

代码语言:javascript
复制
int main()
{
	int&& r1 = 10;

	int a = 10;
	int&& r2 = a;//会报错,无法将左值绑定到右值引用

	// 右值引用可以引用move以后的左值
	int&& r3 = move(a);

	return 0;
}

2、移动构造与移动赋值

前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么C++11还要提出右值引

用呢?是不是化蛇添足呢?

在C++98中,当我们传递一个临时对象或表达式结果作为参数时,如果不使用引用,编译器通常会创建一个临时对象,并进行拷贝。尤其是在我们想要传递的参数是右值的情况下,只能使用传值返回。这个过程可能非常耗费性能,尤其是在处理大对象时。

而引入右值引用后,我们可以通过 移动语义直接转移临时对象的资源,而不是拷贝对象,从而减少内存分配和数据拷贝。

于是就由此衍生出来了移动构造与移动赋值。

在类与对象一章节我们学到,C++的一个类具有六大默认函数,而C++11之后,就新增了两个默认函数:移动构造函数与移动赋值函数。

如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任

意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类

型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,

如果实现了就调用移动构造,没有实现就调用拷贝构造。

如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中

的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内

置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋

值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。

如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

这两个函数的原理都是通过右值引用临时数据,用swap来交换数据,避免了不需要的深拷贝,从而显著减少了内存分配和释放的开销。对于需要频繁操作大型对象的程序,移动操作能极大地提升效率。

我们以string为例,来看一下传统赋值构造,拷贝构造与移动构造移动复制的区别。

代码语言:javascript
复制
class string
{
public:
	string(const char* s = "")//默认构造
		:size(strlen(s))
		, capacity(size)
	{
		str = new char[capacity + 1];
		strcpy(str, s);
	}
	void swap(string& s)
	{
		std::swap(str, s.str);
		std::swap(size, s.size);
		std::swap(capacity, s.capacity);
	}

	string(const string& s)//拷贝构造
		:str(nullptr)
	{
		cout << "拷贝构造" << endl;
		string tmp(s.str);
		swap(tmp);
	}

	string& operator=(const string& s)
	{
		cout << "赋值重载" << endl;
		string tmp(s);
		swap(tmp);
	}

	string(string&& s)
		:str(nullptr)
		,size(0)
		,capacity(0)
	{
		cout << "移动构造" << endl;
		swap(s);
	}

	string& operator=(string&& s)
	{
		cout << "移动赋值" << endl;
		swap(s);
		return *this;
	}

	~string()
	{

		delete[]str;
		str = nullptr;
	}

	char& operator[](size_t pos)
	{
		assert(pos < size);
		return str[pos];
	}
	void reserve(size_t n)
	{
		if (n > capacity)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, str);
			delete[] str;
			str = tmp;
			capacity = n;
		}
	}

	void push_back(char ch)
	{
		if (size >= capacity)
		{
			size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
			reserve(newcapacity);
		}
		str[size] = ch;
		++size;
		str[size] = '\0';
	}


	//string operator+=(char ch)
	string& operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}

	const char* c_str() const
	{
		return str;
	}

private:
	char* str;
	size_t size;
	size_t capacity;//不包含最后做标识的\0
};

我们这里实现了一个简单的string类

代码语言:javascript
复制
void func1(bit::string s)
{}
void func2(const bit::string& s)
{}
int main()
{
 bit::string s1("hello world");
 // func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
 func1(s1);
 func2(s1);
 // string operator+=(char ch) 传值返回存在深拷贝
 // string& operator+=(char ch) 传左值引用没有拷贝提高了效率
 s1 += '!';
 return 0;
}

先屏蔽掉两个移动构造函数我们可以发现,左值引用明显减少了我们资源不必要的拷贝,但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回, 只能传值返回。例如:test::string to_string(int value)函数中可以看到,这里只能使用传值返回, 传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

所以此时右值引用的移动构造与移动赋值就出现帮我们解决这个问题了。

移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不 用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己

所以我们将屏蔽解除后,再运行上面bit::to_string的两个调用,我们会发现,这里没有调用深拷贝的拷贝构造,而是调用了移动构造,移动构造中没有新开空间,拷贝数据,所以效率提高了 。

3、 move转换

按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能

真的需要用右值去引用左值实现移动语义。 当需要用右值引用引用一个左值时,可以通过 move

函数将左值转化为右值 。 C++11 中, std::move() 函数 位于 头文件中,该函数名字具有迷惑性,

并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义

代码语言:javascript
复制
int main()
{
 test::string s1("hello world");
 // 这里s1是左值,调用的是拷贝构造
 test::string s2(s1);
 // 这里我们把s1 move处理以后, 会被当成右值,调用移动构造
 // 但是这里要注意,一般是不要这样用的,因为我们会发现s1的
 // 资源被转移给了s3,s1被置空了。
 test::string s3(std::move(s1));
 return 0;
}

4、完美转发:forward

代码语言:javascript
复制
void Fun(int &x)
{ 
    cout << "左值引用" << endl; 
}
void Fun(const int &x) 
{ 
    cout << "const 左值引用" << endl;
}

void Fun(int &&x)
{ 
    cout << "右值引用" << endl; 
}
void Fun(const int &&x)
{ 
    cout << "const 右值引用" << endl; 
}

// 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
// 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
// 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
// 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发
template<typename T>
void PerfectForward(T&& t)
{
 Fun(t);
}
int main()
{
 PerfectForward(10);           // 右值
 int a;
 PerfectForward(a);            // 左值
 PerfectForward(std::move(a)); // 右值
 const int b = 8;
 PerfectForward(b);      // const 左值
 PerfectForward(std::move(b)); // const 右值
 return 0;
}

std::forward 完美转发在传参的过程中保留对象原生类型属性

代码语言:javascript
复制
void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{
 Fun(std::forward<T>(t));
}
int main()
{
 PerfectForward(10);           // 右值
 int a;
 PerfectForward(a);            // 左值
 PerfectForward(std::move(a)); // 右值
 const int b = 8;
 PerfectForward(b);      // const 左值
 PerfectForward(std::move(b)); // const 右值
 return 0;
}

五、lambda表达式

在C++11之前,倘若我们要排序一个自定义类型的数组,就需要手动写一个算法。随着C++算法的发展,人们开始觉得这样做实在是太复杂了,每次为了实现一个algorithm算法, 都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名, 这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

对于一个水果自定义类型来说:

代码语言:javascript
复制
struct Goods
{
 string _name;  // 名字
 double _price; // 价格
 int _evaluate; // 评价
 Goods(const char* str, double price, int evaluate)
 :_name(str)
 , _price(price)
 , _evaluate(evaluate)
 {}
};

我们可以这样写lambda:

代码语言:javascript
复制
int main()
{
 vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 
3 }, { "菠萝", 1.5, 4 } };
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
 return g1._price < g2._price; });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
 return g1._price > g2._price; });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
 return g1._evaluate < g2._evaluate; });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
 return g1._evaluate > g2._evaluate; });
}

上述代码就是使用C++11中的lambda表达式来解决,可以看出lambda表达式实际是一个匿名函

数。

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement

}

(lambda表达式各部分说明):

[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来

判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda

函数使用

(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以

连同()一起省略

mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量

性。使用该修饰符时,参数列表不可省略(即使参数为空)。

->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回

值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推

{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获

到的变量。

注意:

在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为

。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

代码语言:javascript
复制
int main()
{
    // 最简单的lambda表达式, 该lambda表达式没有任何意义
   []{}; 
    
    // 省略参数列表和返回值类型,返回值类型由编译器推导为int
    int a = 3, b = 4;
   [=]{return a + 3; }; 
    
    // 省略了返回值类型,无返回值类型
    auto fun1 = [&](int c){b = a + c; }; 
    fun1(10)
    cout<<a<<" "<<b<<endl;
    
    // 各部分都很完善的lambda函数
    auto fun2 = [=, &b](int c)->int{return b += a+ c; }; 
    cout<<fun2(10)<<endl;
    
    // 复制捕捉x
    int x = 10;
    auto add_x = [x](int a) mutable { x *= 2; return a + x; }; 
    cout << add_x(10) << endl; 
    return 0;
}

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用

[var]:表示值传递方式捕捉变量var

[=]:表示值传递方式捕获所有父作用域中的变量(包括this)

[&var]:表示引用传递捕捉变量var

[&]:表示引用传递捕捉所有父作用域中的变量(包括this)

[this]:表示值传递方式捕捉当前的this指针

注意:

a. 父作用域指包含lambda函数的语句块

b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。 比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 。[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。(比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复)

d. 在块作用域以外的lambda函数捕捉列表必须为空。

e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者

非局部变量都会导致编译报错。

f. lambda表达式之间不能相互赋值,即使看起来类型相同

六、包装器

1、function包装器

也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。

C++ 提供了多种可调用对象,例如:

  • 普通的函数(函数指针)
  • 函数对象(重载了 operator() 的类或结构体)
  • Lambda 表达式
  • 成员函数指针

不同类型的可调用对象在不同场景下可能有不同的语法和接口,这使得在通用库设计中难以统一处理。std::function 出现的主要动机之一是提供一个统一的接口来包装这些可调用对象,使得用户无需关心它们具体的类型。

同时在设计某些库(如算法库、回调机制)时,通常需要接受函数作为参数。例如,STL 中的算法如 std::sort 可以接受一个比较函数。std::function 可以作为这样的通用接口,使得程序设计更加抽象和模块化,库的使用者可以自由地传入不同类型的可调用对象,而库的实现者只需针对 std::function 进行设计。

代码语言:javascript
复制
#include <iostream>
#include <functional>

int add(int a, int b) 
{
    return a + b;
}

int main() {
    std::function<int(int, int)> func = add;//std::function<int(int)> 可以存储任何接受一个 int 并返回 int 的可调用对象
    std::cout << func(2, 3) << std::endl; // 输出 5
    return 0;
}

2、bind

std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器)接受一个可

调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而

言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M

可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺

序调整等操作。

代码语言:javascript
复制
// 使用举例
#include <functional>
int Plus(int a, int b)
{
 return a + b;
}
class Sub
{
public:
 int sub(int a, int b)
 {
 return a - b;
 }
};

int main()
{
 //表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
 std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1, 
placeholders::_2);
 //auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
 //func2的类型为 function<void(int, int, int)> 与func1类型一样
 //表示绑定函数 plus 的第一,二为: 1, 2
 auto  func2 = std::bind(Plus, 1, 2);   
 cout << func1(1, 2) << endl;
 cout << func2() << endl;
 Sub s;
 // 绑定成员函数
 std::function<int(int, int)> func3 = std::bind(&Sub::sub, s, 
 placeholders::_1, placeholders::_2);
 //参数调换顺序
 std::function<int(int, int)> func4 = std::bind(&Sub::sub, s, 
placeholders::_2, placeholders::_1);
 cout << func3(1, 2) << endl; 
 cout << func4(1, 2) << endl;
 return 0;
}

总结

C++11标准引入了众多新特性,显著增强了C++语言的功能和性能。主要更新包括:

  1. 统一的列表初始化:通过大括号{}可以统一对内置类型和用户自定义类型进行初始化,同时std::initializer_list简化了多个参数的初始化过程。
  2. 新的声明方式
    • auto:实现自动类型推导,简化代码。
    • decltype:用于获取表达式的类型,常与auto配合使用。
    • nullptr:引入了空指针nullptr,替代原先的NULL
  3. 范围for循环:简化了容器和数组的遍历过程,代码更加简洁。
  4. 右值引用与移动语义:通过右值引用和移动语义优化了内存管理,特别是处理临时对象时,避免了不必要的深拷贝操作,提升了程序的效率。
  5. Lambda表达式:引入了匿名函数,使得可以在函数内部快速定义和使用简单函数。
  6. 其他改进:包括多线程支持、std::tuplestd::function等,使得C++11不仅在语法上变得简洁,同时在性能、可维护性、并发编程等方面有了显著提升。(部分重要改动本文并未介绍,例如线程要配合linux的线程知识)
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-09-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、统一的列表初始化
    • 1、{}初始化
    • 2、std::initializer_list
  • 二、新的声明
    • 1、auto
    • 2、decltype
    • 3、nullptr
  • 三、范围for循环
  • 四、右值引用与移动语义
    • 1. 左值 vs 右值
    • 2、移动构造与移动赋值
    • 3、 move转换
    • 4、完美转发:forward
  • 五、lambda表达式
  • 六、包装器
    • 1、function包装器
    • 2、bind
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档