C++ part one
C++ part one
织基本特性
程序的执行过程
- 内核虚拟内存——内核使用
- 栈区——程序运行时用于存放局部变量,可向下延展空间
- 内存库的内存映射
- 堆区——程序运行时用于分配malloc和new申请的区域
- 可读写区——用于存放全局变量和静态变量
- 只读区——存放程序和常量等
- 未使用
- 静态变量区
- 静态变量:一些局部作用,寿命很长的变量
- 全局变量:字如其名,全局作用
new 关键字
- 在分配单个对象的内存时:
- 当对象是普通变量时,可以分配对应的内存
- 当对象是类对象时,会调用构造函数,如果没有对应的构造函数,就会报错。
- 在分配数组对象内存时:
- 对于普通变量:可以使用“()”将所有对象全部初始化为0。
- 对于类对象,有没有“()”都一样,均使用默认构造函数,如果没有默认构造函数就会报错。
1
2
3
4
5
6
7
8int* a = new int(100); //分配为普通变量,同时初始化
delete a;//记得释放空间
std::string* hello = new std::string("你好,世界");
delete hello
int* b = new int[5](); //全部初始化为零
delte[] b;
命名空间
- 命名空间用来解决大型项目的命名重复问题
- C++最后都要转化为C来执行程序,在namespace A中定义的Test类,其实全名是A::Test。
- 头文件中不能使用using关键字,极易造成头文件污染
const关键字
const是让编译期将变量视为常量,用const修饰的变量和真正的常量有本质的区别。
真正的常量存储在常量区或代码区,比如“abcdefg”这个字符串就存储在常量区,而“3”,“100”这些数字就存储在代码区中,这些都是真正的常量,无法用任何方式修改。
const修饰的变量仍然存储在堆区或栈区中,从内存分布的角度讲,和普通变量没有区别。const修饰的变量并非不可更改的,C++本身就提供了mutable关键字,用来修改const修饰的变量,从汇编的角度讲,const修饰的变量也是可以修改的。
常量指针与指针常量
- 常量指针:指针指向的内容是常量,常量不能改变
- 指针常量:指针本身就是常量,不能再指向其他地址
1
2
3
4
5
6
7
8
9
10
11const char *p = "abcdef"; //指针常量,此处指针指向的字符串abcdef无法改变
int a = 8;
int* const p = &a;
*p = 9; // 正确
int b = 7;
p = &b; // 错误,此处指针指向的地址无法改变
int a = 8;
const int * const p = &a;
//指针指向的内容和指针都无法改变
auto关键字
auto只能推断出类型:引用不是类型,引用只是别名,所以auto无法推断出引用,要使用引用只能自己加引用符号。
auto关键字在推断引用的类型时:会直接将引用替换为引用指向的对象。其实引用一直是这样的,引用不是对象,任何使用引用的地方都可以直接替换成引用指向的对象。
auto关键字在推断类型时,如果没有引用符号,会忽略值类型的const修饰,而保留修饰指向对象的const。
auto关键字在推断类型时,如果有了引用符号,那么值类型的const和修饰指向对象的const都会保留。
- 示例三和四的原因。在传递值时,修改这个值并不会对原有的值造成影响。而传递引用时,修改这个值会直接对原有的值造成影响.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//示例一
int a = 10;
auto i = a; //auto推导出int
auto& i = a;//auto推导出int&
//示例二
int a = 10;
int& b = a;
auto c = b; //相当于auto c =a;
//示例三
const int* const a = 10;
auto b = &a; //变量b的类型为int* const
//示例四
const int* const a = 10;
auto& b = &a; //变量b的类型为const int* const &
静态变量,指针与引用
- 变量的存储位置有三种,分别是静态变量区,栈区,堆区
- 静态变量区在编译时就已经确定地址,存储全局变量与静态变量
- 指针都是存储在栈上或堆上,不管在栈上还是堆上,都一定有一个地址
- &a代表的a这个变量的地址,a代表的a对应地址存储的值,* a对应指针指向的内存地址中存放的值
左值,右值
左值、右值的纯右值、亡值、右值
- 左值(lvalue, left value),顾名思义就是赋值符号左边的值。准确来说, 左值是表达式(不一定是赋值表达式)后依然存在的持久对象。(注意:字符串字面量为左值,如"hello")
- 右值(rvalue, right
value),右边的值,是指表达式结束后就不再存在的临时对象(或引用)。而
C++11
中为了引入强大的右值引用,将右值的概念进行了进一步的划分,分为:纯右值、将亡值。
- 纯右值(prvalue, pure rvalue),纯粹的右值,要么是纯粹的字面量,例如 10 , true ; 要么是临时对象,例如 -1 , 1+2 ,a++的结果。非引用返回的临时变量、运算表达式产生的临时变量、Lambda 表达式都属于纯右值。
- 亡值(xvalue, expiring value),是 C++11 为了引入右值引用而提出的概念(因此在传统 C++ 中,只有右值这一个概念),也就是即将被销毁、却能够被移动(move)的值。右值引用和左值引用
- 顾名思义,右值引用可以引用一切右值(包括纯右值和亡值): T && ,其中 T 为非左值引用类型(若 T 为左值引用类型,则结果仍是左值引用)。 右值引用的声明让这个临时对象的生命周期得以延长,只要右值引用生命期还未结束,那么这个临时对象的生命期也不会结束。
左值:拥有地址属性的对象就叫左值
右值:没有地址属性的对象就叫右值
C++11 提供了 std::move 这个方法将左值参数无条件的转换为右值, 有了它我们就能够方便的获得一个右值引用(匿名右值引用)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void reference(std::string& str) {
std::cout << "左值" << std::endl;
}
void reference(std::string&& str) {
std::cout << "右值" << std::endl;
}
int main(){
std::string lv0 = "string,"; // lv0 是一个左值
std::string& lv1 = lv0; // lv1 是左值引用
// std::string&& r1 = lv0; // 非法, 右值引用不能引用左值
std::string&& rv1 = std::move(lv0); // 合法, std::move可以将左值转换为右值
const std::string& lv2 = lv0 + lv0; // 合法, 常量左值引用能够延长临时变量的生命周期
// lv2 += "Test"; // 非法, 常量引用无法被修改
std::string&& rv2 = lv0 + lv2; // 合法, 右值引用延长临时对象生命周期
rv2 += "Test"; // 合法, 非常量引用能够修改临时变量
reference(lv1); // lv1是左值引用,为左值
reference(rv2); // rv2是一个具名右值引用,为左值,见完美转发
reference(std::move(lv0)); // 匿名右值引用,为右值
return 0;
}
lambda 表达式
- lambda表达式就是匿名函数,普通的函数在使用前需要找个地方将这个函数定义,于是C++提供了lambda表达式,需要函数时直接在需要的地方写一个lambda表达式,省去了定义函数的过程,增加开发效率。
- lambda表达式的格式:最少是“[] {}”,完整的格式为“[] () ->ret {}”。
- lambda各个组件介绍
- []代表捕获列表:表示lambda表达式可以访问前文的哪些变量。 (1)[]表示不捕获任何变量。 (2)[=]:表示按值捕获所有变量。 (3)[&]:表示按照引用捕获所有变量。=,&也可以混合使用 (4)[=, &i]:表示变量i用引用传递,除i的所有变量用值传递。 (5)[&, i]:表示变量i用值传递,除i的所有变量用引用传递。 当然,也可以捕获单独的变量 (6)[i]:表示以值传递的形式捕获i (7)[&i]:表示以引用传递的方式捕获i
- ()代表lambda表达式的参数,函数有参数,lambda自然也有。
- ->ret表示指定lambda的返回值,如果不指定,lambda表达式也会推断出一个返回值的。
- {}就是函数体了,和普通函数的函数体功能完全相同。