《探索C++11:现代C++语法的性能革新(上篇)》

发布于:2025-09-02 ⋅ 阅读:(14) ⋅ 点赞:(0)

前引:C++11标准标志着C++语言的一次划时代飞跃,它不仅填补了C++98/03的诸多空白,更引入了革命性特性,彻底重塑了现代软件开发的面貌。在性能与安全的双重驱动下,C++11通过智能指针(如std::unique_ptrstd::shared_ptr)终结了手动内存管理的风险,借助auto关键字和lambda表达式实现了类型推导与函数式编程的无缝融合,使C++在高性能计算和系统级开发中更具竞争力。本文将系统解析C++11的核心语法机制,揭示其如何从底层语法细节出发,引领开发者步入更安全、更高效的编程新时代!

目录

【一】左值和右值

【一】何为左值与右值

【二】左值引用与右值引用相关特性

左值引用

右值引用

【三】右值引用的效率提升

(1)传值返回

(2)赋值运算符重载

(3)移动构造

【四】右值引用的隐藏精髓

(1)自动识别为左值

(2)forward完美转发

(3)万能引用

【二】decltype类型推导

【三】nullptr与null

【四】lambda智能排序

lambda的特殊操作

(1)只能调用静态、全局性的变量、函数

(2)引用方式捕捉

(3)引用捕获所有变量

(4)混合捕捉变量


【一】左值和右值

【一】何为左值与右值

左值:左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左 值,不能给他赋值,但是可以取它的地址(可取地址的变量就是左值)

// 以下的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;

右值:右值也是一个表示数据的表达式,通常为纯右值和将亡值,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址(不能取地址的就是右值)

// 以下几个都是常见的右值
10;
x + y;
fmin(x, y);

注意:

(1)常量字符串“XXXXXXXXX”之所以为左值,是因为这个表达式返回的是首元素的地址

(2)(x+y)返回的是一个结果,所以为右值

右值引用:右值引用就是对右值的引用,给右值取别名(用两个 & 符号)

// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
【二】左值引用与右值引用相关特性
左值引用

(1)左值引用只能引用左值,不能引用右值

(2)const 左值引用可以引用/接收左值和右值,例如:

int a = 0;
int b = 0;

//const左值引用左值
const int& c = a;
//const左值引用右值
const int& d = a + b;

void Func(const int& date)
{
	cout << "const左值引用" << endl;
}

int main()
{
	int a = 0;
	int b = 0;
	//const左值引用可以接收左值 或者 右值
	Func(a);
	Func(a + b);
   
    return 0;

}

(3)左值引用和右值引用作为函数接受参数可以进行区分(二者构成函数重载),例如:

注意:用const 左值是因为const左值既可以接收左值也可以接受右值,所以是保险做法

右值引用

(1)右值引用只能引用右值,不能引用左值,例如:

move

左值可以经过move函数转化之后被右值引用,例如:C++中的std::move是一个标准库函数,用于将对象转换为右值引用。它不实际移动任何数据,仅通过强制类型转换static_cast)将左值标记为可移动的右值,从而允许调用移动构造函数或移动赋值运算符

例如没有接接收move的返回值时不会改变原来左值的数据性质:

例如接收move的返回值之后,原左值数据性质就会改变(转移数据):

【三】右值引用的效率提升

右值的生命周期一般是很短暂的(将亡值),例如一个表达式、函数返回值。那么在C++11中将这个特性利用了起来,右值引用精髓:利用swap夺舍对方,下面我们来看几个典型的改进案例!

(1)传值返回

这是一个很常见的场景:函数内有一个临时对象S,将S传值拷贝给V,过程图如下:

string Func()
{
	string S("abcde");

	return S;
}

int main()
{
	string V = Func();

	return 0;
}

后来C++11出来提出了右值/右值引用,编译器就自动优化为直接进行移动拷贝:

例如:二者地址是一样的,直接将S的内容全部转交给V,S变量出了函数就销毁了

(2)赋值运算符重载

深拷贝:开空间->拷贝数据

移动拷贝:直接swap交换数据

string operator=(const string& date)
{
	//开空间
	string tmp(date);

	//拷贝数据

	//返回
	return tmp;
}

string operator=(string&& date)
{
	//Swap交换数据
	swap(date);

	//返回
	return *this;
}
(3)移动构造

我们知道左值引用和右值引用是构成函数重载的,因此如果可以利用将亡值,那就避免了拷贝构造

精髓:利用将亡值直接交换给 *this ,避免了再次开辟空间

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

// 移动构造
Func(string&& s)
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{
	cout << "string(string&& s) -- 移动语义" << endl;
	swap(s);
}
【四】右值引用的隐藏精髓
(1)自动识别为左值

右值应该是不能修改和取地址的,下面我们看两个例子:

通过实操我们可以看出,对代表右值10的变量pc取引用和修改,是可以的,因此:

右值引用的变量会被编译器识别为左值,否则在移动构造的场景下,无法完成“夺舍”和数据修改

(2)forward完美转发

右值引用的变量会被强制识别为左值,那么对于多层函数的调用来说就失去了右值引用带来的效率

因此C++11推出了完美转发forward<数据类型>,保持数据原本的属性(左值/右值),例如:

Func(forward<int>(pc));

我们来看看效果:

(3)万能引用

万能引用就是根据参数的类型(左值/右值)自动变换,需要使用模板完成

【二】decltype类型推导

学习 decltype 之前我们需要先看两个语法:

auto:自动推导类型,用来定义变量,且变量必须初始化

typeid( ).name( ):获得变量类型,只可获得不能使用

decltype:可以根据变量\表达式推导类型+使用,且可以不初始化(比如模板参数)

【三】nullptr与null

在C中,NULL其实是#define定义的整型0,而指针可被初始化为NULL,是发生了类型转化

在C++中,为了清晰和安全的考虑,更新出了 nullptr,专门用来表示空指针

例如:

【四】lambda智能排序

现在有仿函数排序如图:

而学了lambda智能排序可以这么写(必须使用auto):

[ ](数据类型date1,数据类型date2)->bool { return 操作 ;}

注意:这里的操作就是函数内部的实现,可以写交换、赋值.......

解释:这里必须使用auto,也是有原因的,我们来看到推导的类型是特别复杂的

而在日常中,返回值类型和箭头可以不写,编译器自己推导,例如:

auto F1 = [](int x, int y) {return 0; };
lambda的特殊操作
(1)只能调用静态、全局性的变量、函数

(2)引用方式捕捉

在捕捉列表里面我们可以使用引用的方式捕捉(后可不在参数列表书写),否则是对变量的拷贝

(3)引用捕获所有变量

当捕捉列表只有一个取地址符号时,会引用收集它之前的所有变量,当然全局的本身就可以调用

(4)混合捕捉变量

混合指的是:引用捕捉+传值捕捉。此时除了 y 其它属于引用捕捉,y在函数里面是拷贝属于右值


网站公告

今日签到

点亮在社区的每一天
去签到