Part1:C++11 (Abandoned)

1. const修饰符

const(ant),望文生义就是"常值"的意思。在C++中,我们使用 const 修饰符用来表示修饰后的数值或字符串是不可改动的,如:

const int i = 20;//int const i = 20;一个道理
const char ch = 'a';
const char* s = "hello world";
const int* p = 100;//指针p指向的数字100是常值,但是指针可以变
int* const p = 100;//定义了一个常指针指向数值为100的区域,这里的数值是可以改变的
const int* const p = 100;//定义了一个指向常量100的常指针

const修饰的变量和常量是不一样的。常量常常会放到.text段或.rodata段,但是const修饰的变量不一定放在.rodata段。如果变量是局部变量,它的生命周期会随着函数的返回、栈帧的销毁一同消逝。但是如果const修饰的是全局变量,则一般会将这个全局变量放在.rodata段中。

我们在下面的例子中进一步学习。我们定义了全局变量和许多局部变量。其中 const_global 会放到.rodata段中,global 会放在bss/data段中。在函数中,字符串"hello world"是一个常字符串,会被放到.rodata段中,其余函数内用const修饰局部变量的生命周期都会随着栈帧的摧毁而消逝。

const int const_global = 0;
int global = 20;

int constFunc(const char** d,const int** e) {
	const char* s = "hello world";
	const int i = 5;
	*d = s;
	*e = &i;
	return 0;
}
int main() {
	int x = 30;
	int y = 40;
	const char* a;
	const int* b;
	constFunc(&a, &b);
	return 0;
}

2. auto关键字(C++11)

auto 是C++11新加入的关键字,用于自动推导变量的类型。它可以让编译器根据初始化表达式自动确定变量的类型,从而简化代码编写和提高代码的可读性。但是要注意,在使用 auto 时要清楚编译器会给 auto 什么类型。我们可以使用 boost库 来判断变量的具体类型。

在使用auto时,我们要注意以下几点:

  1. auto只能推断出类型,而引用不是类型,所以auto无法推断出引用,要引用只能自己加引用符号。代码演示如下:
int i = 100;
auto i2 = i;//i2为 int 类型
auto& refI = i;//refI 为 int& 类型
/*
我们看到,只有自己加入引用类型后 refI 才能变成 int&。
*/
  1. auto关键字在推断引用的类型时,会直接将引用替换为引用指向的对象。引用不是对象,任何使用引用的地方都可以直接替换成引用指向的对象。
int i = 100;
int& refI = i;
auto i2 = refI;//相当于 auto i2 = i;  i2 类型为 int
auto& i3 = refI;//相当于auto& i3 = i;  i3 类型为 int&
  1. auto关键字在推断带const关键字的类型时,若没有引用符号,则会忽略const的修饰。而保留指向对象的const,典型的就是指针。
const int i = 100;
auto i2 = i;//i2 为 int 类型

int j = 100;
const int* const p = &j;//定义了一个指向常量 int 的常指针p
auto p2 = p;//p2的类型为const int*,是指向常量 int 的指针。

static int k = 100;
auto k2 = k;//k2 为 int 类型

请留意:在这行代码中const int* const p = &j;//定义了一个指向常量 int 的常指针pj 并没有被设置为常量。p 是一个指向 const int 的常指针,但这并不改变 j 的本质。只是说不能通过 p 来修改 j 的值。
4. 在auto关键字推断类型时,如果带引用符号则会保留值类型。

const int i = 100;
auto& i2 = i;//i2 为 const int 类型

int j = 100;
const int *const p = &j;//定义了一个指向常量 int 的常指针p
auto& p2 = p;//p2的类型为const int* const&。

static int k = 100;
auto& k2 = k;//k2 为 int& 类型
  1. 如果在auto前面加上const,就会永远有const的含义。
int i = 100;
const auto i2 = i;//i2 为 const int 类型

3. 全局变量和静态变量

全局变量和静态变量都是存放在.data/.bss段中的,这些变量在编译过程中就已经被赋予地址,在执行时不会再次调用。我们用代码做简单的演示。

int global_1 = 100;//.data
int global;//.bss

void test(){
	static int i;//.bss,在程序启动时自动初始化为0
	static int i2 = 0;//.data,且这行代码不会再进程运行时执行
	++i;
}

int main(){
	test();
	test();
}

4. 左值,右值,左值引用, 右值引用

C++的表达式一般有两部分对象组成,如int i = 100;。在表达式中,左值(lvalue) 就是能够取地址的那部分(有地址属性,表达式结束后依然存在的持久对象),而不能够取地址的我们称之为右值(rvalue)(表达式结束后就不再存在的对象)。在上面的表达式中,变量对象i就是左值,10这样的字面量(字符字面量除外)对象就是右值。

左值来源于C语言的说法,即可以放在等号左边的就叫左值,左值也可以放在等号右边。但右值只能放到赋值操作符的右边,这是因为右值没有持久的存储位置(临时对象),所以不能作为赋值操作的目标。

4.1 引用的分类

  1. 普通左值引用:一个对象的别名,只能绑定左值,无法绑定右值。
int i = 100;
int& ref = i;//lvalue reference, no mistake

const int i2 = 100;
int& ref2 = i2;//错误:非常量引用不能绑定到常量
const int& ref2 = i2;//正确
ref2 = 200;//如果绑定后就会违反常量的不可变原则

int& ref3 = 100;//错误:非常量引用不能绑定到右值
  1. const左值引用:可以对常量起别名,可以绑定左值和右值。
const int i =100;
const int& ref = i;//正确

const int& ref = 100;//正确
  1. 右值引用:右值引用只能绑定到右值。右值引用的主要用途是实现移动语义和优化性能。
int i = 100;
int&& rref = 200;//正确
int&& rref2 = (i+1);//正确
int&& rref3 = i++;//正确,i++是一个右值表达式,因为它返回 i 的旧值(临时对象)

int&& rref4 = i;//错误,i是右值
int&& rref5 = ++i;//错误,++i是左值,因为它返回的是 i 的引用类型
  1. 万能引用:属于模板的部分:万能引用、引用折叠、完美转发。在学习模板时再学习。

5. move函数,临时对象

在上节课中,我们看到,在使用右值引用时不能绑定左值。在C++11中,我们可以通过move函数来将左值转换为右值引用。

5.1 move函数

  1. 右值看重对象的值而不考虑地址,move函数可以对一个左值使用,使操作系统不再在意其地址属性,将其完全视作一个右值看待。
  2. 当我们使用了move函数后,操作对象就失去了其地址属性。因此,我们有义务保证之后避免使用该变量的地址属性,也就是不再使用该变量,因为再次使用该变量不可避免地会使用到变量的地址属性。在后面移动语义时会体现move函数意义。
#include<utility>
int i = 100;
int&& rref = i;//错误,因为 i 是左值,不能绑定到右值引用

int&& rref2 = std::move(i);//正确,std::move 将 i 转换成右值引用
int&& rref3 = srd::move(++i);//正确,++i 是左值,std::move 将 ++i 转换成右值引用

5.2 临时对象

临时对象是程序执行时生成的中间对象,所有的临时对象都是右值对象,因为临时对象产生后很快就会被销毁。常见的临时对象有:

  1. 函数返回值:当函数返回一个对象时,会创建一个临时对象来存储返回值,如:
int get20(){
	return 20;//返回一个临时对象
}
int&& i = get20();
  1. 类型转换:当需要进行类型转换时,编译器会创建临时对象。如:
double d = 3.14;
int i = static_cast<int>(d);//临时对象存储转换后的值,然后赋给 i 。
  1. 表达式中的中间结果:在复杂表达式中,临时对象用于存储中间结果。
int a = 5, b = 10;
int result = (a + b) * 2; // 临时对象存储 a + b 的结果   

6. 可调用对象

如果一个对象可以使用调用运算符"()",()里面可以放参数。那么这个对象就是可调用对象。可调用对象的分类有:

Lambda表达式:也称为匿名函数,是一种需要一个函数但不想命名它的情况下使用的简便方法。基本语法是:[capture list] (parameter list) -> return_type { function body }

  • 捕获列表 []:指定Lambda表达式可以访问的外部变量,可以按值(=)或按引用(&)捕获。
    1. [ ]表示不捕获任何变量。
    2. [=]表示按值捕获所有变量。
    3. [&]表示按照引用捕获所有变量。
    4. [=,& i]除了 i 按引用传递,其他所有变量按值转递。
    5. [&, i]表示除了 i 按值传递,其他变量按引用传递。
    6. 也可以捕获单独变量,如[i],[&i]。
  • 参数列表(parameter list):与普通函数的参数列表类似,可以为空。
  • 返回类型(return type):可以省略,由编译器推导,也可以显式指定。
  • 函数体(function body):Lambda表达式的具体逻辑。