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

news2024/11/16 20:55:59

目录

前言

一、统一的列表初始化

1、{}初始化

2、std::initializer_list

二、新的声明 

1、auto

2、decltype

3、nullptr

三、范围for循环

四、右值引用与移动语义

1. 左值 vs 右值

2、移动构造与移动赋值

3、 move转换

 4、完美转发:forward

五、lambda表达式

六、包装器

1、function包装器

2、bind

总结


前言

在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版本,我们可以使用{}来对数组和结构体元素来进行统一的列表初始值设定,例如:

//在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版本中,其扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加

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 的主要用途是提供一种简便的语法,用于将一系列值传递给函数、构造函数或其他结构。

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 来接收多个初始化参数:

    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 来进行初始化:

    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::set<int> s = {10, 20, 30};
  • 函数参数: 可以通过 initializer_list 传递不定数量的参数:

    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原来的用法,将 其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初 始化值的类型。
#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使用,快速推到变量类型:
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,用于表示空指针。
#ifndef NULL
#ifdef __cplusplus
#define NULL   0
#else
#define NULL   ((void *)0)
#endif
#endif

三、范围for循环

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

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

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

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

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

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

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

左值引用总结:
1. 左值引用只能引用左值,不能引用右值。
2. 但是 const 左值引用既可引用左值,也可引用右值
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 以后的左值
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为例,来看一下传统赋值构造,拷贝构造与移动构造移动复制的区别。
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类

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() 函数 位于 头文件中,该函数名字具有迷惑性,
并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义

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

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 完美转发在传参的过程中保留对象原生类型属性
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表达式。

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

struct Goods
{
 string _name;  // 名字
 double _price; // 价格
 int _evaluate; // 评价
 Goods(const char* str, double price, int evaluate)
 :_name(str)
 , _price(price)
 , _evaluate(evaluate)
 {}
};

我们可以这样写lambda:

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函数不能做任何事情。
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 进行设计。

#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函数还可以实现参数顺
序调整等操作。
// 使用举例
#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的线程知识)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2171149.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

调和级数枚举+前缀和,CF 731F - Video Cards

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 731F - Video Cards 二、解题报告 1、思路分析 题目提示的很明显要用调和…

复杂类型 el-form 表单的校验

背景描述 表单结构 form 表单结构 活动名称 - 必填&#xff0c;prop“name”活动类型 - 必填&#xff0c;prop“actType”活动人数 - 必填&#xff0c;prop“actUserAccount”签到记录 - 必填&#xff0c;prop“actList” 对表单做必填校验 rulesOld: {name: [{ required: t…

【15%】100小时机器学习——什么是机器学习

前言 虽然已经好久没有更新了&#xff0c;但笔者最近一直都在努力学习哦。 前面三三两两根据GitHub上的项目写了一些实验操作&#xff0c;但是总觉得这样是不行的。碎片化的学习只能是建立在已知的基础上进行熟练&#xff0c;不能作为打基础的主力方法&#xff0c;最关键的是&a…

物联网系统中声音拾取音频方案_咪头

01 物联网系统中为什么要使用咪头 物联网系统中使用咪头&#xff08;麦克风或传声器&#xff09;的原因主要可以归结为以下几个方面&#xff1a; 声音信号的拾取与转换 基本功能&#xff1a;咪头是一种将声音转换为电信号的装置。在物联网系统中&#xff0c;咪头负责捕捉周围…

一键降重:芝士AI如何简化论文查重过程?

大家写论文时“旁征博引”是常规操作&#xff0c;所以重复率就成了投稿前的“噩梦”。自己降重&#xff0c;发现怎么改写都无法下降重复率&#xff0c;可能一天改下来下降3%&#xff0c;让人抓狂。 但今天开始&#xff0c;你不用再苦恼啦&#xff0c;更不用自己抓耳挠腮一整天…

商汤SenseNova 5.5大模型的应用实践

SenseNova 5.5如何重塑金融、医疗与自动驾驶的未来 ©作者|wy 来源|神州问学 一、引言 人工智能&#xff08;AI&#xff09;作为引领未来发展的重要力量&#xff0c;正以前所未有的速度改变着我们的生活和工作方式。每年一度的世界人工智能大会&#xff08;WAIC&#xf…

高密度EEG人脑成像:技术与应用

摘要 EEG是一种非侵入性的人脑神经活动测量技术。随着数字技术的进步&#xff0c;EEG分析已从定性分析幅值和频率调制发展到全面分析记录信号的复杂时空特征。EEG能够在亚秒级的时间范围内测量神经过程&#xff0c;但其空间分辨率较低&#xff0c;这使得难以准确可靠地定位EEG…

【Gitee自动化测试5】Gitee免费版的所有按钮

一、首页 就红框里有用 1. 仓库&#xff08;Repository&#xff09; 功能&#xff1a;仓库是一个项目的存储空间&#xff0c;用于保存源代码、文档、配置文件等与项目相关的内容。每个仓库通常会有一个版本控制系统&#xff08;如 Git&#xff09;来跟踪代码的变更历史。用…

Xinstall助力广告主实现精准投放,提升App广告效果!

随着移动互联网的快速发展&#xff0c;App广告投放已成为品牌推广的重要手段。然而&#xff0c;广告投放的效果如何&#xff0c;是否达到了预期的目标&#xff0c;这些问题一直困扰着广告主。今天&#xff0c;我们就来聊聊App广告投放数据统计的痛点&#xff0c;以及Xinstall如…

从体质入手:气虚痰湿人群的健康攻略

“气虚”与“痰湿”是中医体质学九大体质中常见的两种易胖体质&#xff0c;可以说大多数肥胖人群都有这两种体质的身影。比如气虚质的特征是容易疲劳&#xff0c;乏力&#xff0c;出汗&#xff0c;抵抗力差&#xff0c;声弱&#xff0c;气短&#xff0c;面部苍白或萎黄等&#…

车辆目标检测、工程车辆检测算法、工程车辆类型检测

工程车辆检测算法主要用于智能交通系统、建筑工地管理、矿山开采、物流运输等领域&#xff0c;通过图像识别技术来检测和识别视频或图像中的工程车辆。这种技术可以帮助管理者实时监控工程车辆的活动&#xff0c;确保施工安全、交通流量管理和资源调度的效率。以下是关于工程车…

cups-browsed远程代码执行漏洞安全风险通告

今日&#xff0c;亚信安全CERT监控到安全社区研究人员发布安全通告&#xff0c;披露了cups-browsed 远程代码执行漏洞(CVE-2024-47176)。由于cups-browsed 服务在处理网络打印任务时&#xff0c;会绑定到 UDP 端口 631 上的 INADDR_ANY 地址&#xff0c;从而信任来自任何来源的…

uniapp框架中实现文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间

前言 uni-file-picker是uniapp中的一个文件选择器组件,用于选择本地文件并返回选择的文件路径或文件信息。该组件支持选择单个文件或多个文件,可以设置文件的类型、大小限制,并且可以进行文件预览。 提示:以下是本篇文章正文内容,下面案例可供参考 uni-file-picker组件具…

Linux高级IO之poll与epoll

文章目录 poll使用 epoll系统调用 epoll的工作原理红黑树队列 epoll的工作模式水平触发边缘触发 Reactor设计模式工作原理epoll Reactor设计模式的简单示例 poll和epoll都是多路转接的调用&#xff0c;但是epoll实在过于优秀了&#xff0c;一般也都是用epoll的&#xff0c;除此…

mysql索引结构操作(主键/唯一键/普通索引的创建/查询/删除),复合索引介绍(索引覆盖,索引最左匹配原则)

目录 索引操作 创建索引 主键索引 介绍 在创建表时设置主键 创建表后添加主键 唯一键索引 介绍 在创建表时设置唯一键 创建表后添加唯一键 普通索引 在创建表时指定某列为索引 创建表后添加普通索引 自主命名索引 索引创建原则 哪些列适合创建索引 不适合作为…

猫头虎带你解决:error Error: certificate has expired

&#x1f42f;猫头虎带你解决&#xff1a;error Error: certificate has expired &#x1f4a5; 今天有粉丝问猫哥&#xff1a;“&#x1f42f;猫头虎&#xff0c;我在 Node.js 项目中使用 Yarn 安装包时遇到了一个错误&#xff1a;Error: certificate has expired。你能帮忙解…

【机器学习(十二)】机器学习回归案例之二手汽车价格预测—XGBoost回归算法—Sentosa_DSML社区版

文章目录 一、算法和背景介绍二、Python代码和Sentosa_DSML社区版算法实现对比(一) 数据读入与统计分析(二) 数据处理(三) 特征选择与相关性分析(四) 样本分区与模型训练(五) 模型评估和模型可视化 三、总结 一、算法和背景介绍 关于XGBoost的算法原理&#xff0c;已经进行了介…

[C++]栈队列改成模板类

栈、队列都更改成模板类 栈 .hpp #ifndef MY_STACK_H #define MY_STACK_H #include <iostream>using namespace std;template<typename T> class my_stack { private:T *base;//动态栈指针int top;//栈顶元素int size;//栈大小 public:my_stack();//无参构造my_…

宠物空气净化器有必要买吗?希喂、霍尼韦尔和352哪款更推荐?

国庆假终于要来了&#xff0c;对于我这个上班族而言&#xff0c;除了春节假期最期待的就是这个国庆假&#xff0c;毕竟假期这么长&#xff0c;家里还有一只小猫咪&#xff0c;一直都没时间陪它&#xff0c;终于给我找到时间带它会老家玩一趟了。 我跟我妈说的时候&#xff0c;…

Apache OFBiz SSRF漏洞CVE-2024-45507分析

Apache OFBiz介绍 Apache OFBiz 是一个功能丰富的开源电子商务平台&#xff0c;包含完整的商业解决方案&#xff0c;适用于多种行业。它提供了一套全面的服务&#xff0c;包括客户关系管理&#xff08;CRM&#xff09;、企业资源规划&#xff08;ERP&#xff09;、订单管理、产…