

**前引:**C++11标准标志着C++语言的一次划时代飞跃,它不仅填补了C++98/03的诸多空白,更引入了革命性特性,彻底重塑了现代软件开发的面貌。在性能与安全的双重驱动下,C++11通过智能指针(如
std::unique_ptr
和std::shared_ptr
)终结了手动内存管理的风险,借助auto
关键字和lambda表达式实现了类型推导与函数式编程的无缝融合,使C++在高性能计算和系统级开发中更具竞争力。本文将系统解析C++11的核心语法机制,揭示其如何从底层语法细节出发,引领开发者步入更安全、更高效的编程新时代!
目录
【一】左值和右值
【一】何为左值与右值
**左值:**左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左 值,不能给他赋值,但是可以取它的地址(可取地址的变量就是左值)
cpp// 以下的p、b、c、*p都是左值 int* p = new int(0); int b = 1; const int c = 2;
**左值引用:**左值引用就是给左值的引用,给左值取别名(用一个 & 符号)
cpp// 以下几个是对上面左值的左值引用 int*& rp = p; int& rb = b; const int& rc = c; int& pvalue = *p;
**右值:**右值也是一个表示数据的表达式,通常为纯右值和将亡值 ,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址(不能取地址的就是右值)
cpp// 以下几个都是常见的右值 10; x + y; fmin(x, y);
注意:
(1)常量字符串"XXXXXXXXX"之所以为左值,是因为这个表达式返回的是首元素的地址
(2)(x+y)返回的是一个结果,所以为右值
**右值引用:**右值引用就是对右值的引用,给右值取别名(用两个 & 符号)
cpp// 以下几个都是对右值的右值引用 int&& rr1 = 10; double&& rr2 = x + y; double&& rr3 = fmin(x, y);
【二】左值引用与右值引用相关特性
左值引用
(1)左值引用只能引用左值,不能引用右值
(2)const 左值引用可以引用/接收左值和右值,例如:
cppint a = 0; int b = 0; //const左值引用左值 const int& c = a; //const左值引用右值 const int& d = a + b;
cppvoid 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,过程图如下:
cpp
string Func()
{
string S("abcde");
return S;
}
int main()
{
string V = Func();
return 0;
}

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

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

(2)赋值运算符重载
深拷贝:开空间->拷贝数据
移动拷贝:直接swap交换数据
cpp
string operator=(const string& date)
{
//开空间
string tmp(date);
//拷贝数据
//返回
return tmp;
}
string operator=(string&& date)
{
//Swap交换数据
swap(date);
//返回
return *this;
}
(3)移动构造
我们知道左值引用和右值引用是构成函数重载的,因此如果可以利用将亡值,那就避免了拷贝构造
**精髓:**利用将亡值直接交换给 *this ,避免了再次开辟空间
cpp
// 拷贝构造
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<数据类型>,保持数据原本的属性(左值/右值),例如:
cppFunc(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,也是有原因的,我们来看到推导的类型是特别复杂的
而在日常中,返回值类型和箭头可以不写,编译器自己推导,例如:
cppauto F1 = [](int x, int y) {return 0; };
lambda的特殊操作
(1)只能调用静态、全局性的变量、函数
(2)引用方式捕捉
在捕捉列表里面我们可以使用引用的方式捕捉(后可不在参数列表书写),否则是对变量的拷贝
(3)引用捕获所有变量
当捕捉列表只有一个取地址符号时,会引用收集它之前的所有变量,当然全局的本身就可以调用
(4)混合捕捉变量
混合指的是:引用捕捉+传值捕捉。此时除了 y 其它属于引用捕捉,y在函数里面是拷贝属于右值