<C++>6 内存和引用
本文最后更新于:2022年6月19日 晚上
6 内存和引用
6.1 内存分区模型
不同区域存放的数据被赋予不同的生命周期,提高编程灵活性
C++ 程序执行时,将内存大致分为 4 个区域:
- 代码区:存放函数体的二进制代码,由操作系统进行管理
- 全局区:存放全局变量、静态变量以及常量
- 栈区:由编译器自动分配释放。存放函数的参数值、局部变量等
- 堆区:由程序员分配和释放。若程序员不释放,程序结束时会由操作系统回收
6.1.1 程序运行前
程序编译后,生产了 exe 可执行程序。未执行该程序前分为 2 个区域:
-
代码区:
存放 CPU 执行的机器指令
代码区是 共享 的。对于频繁被执行的程序,只要在代码区有一份代码即可
代码区是 只读 的,以防止程序意外修改其指令
-
全局区:
全局变量 和 静态变量 存放在这里
全局区还包含 常量区,字符串常量 和其他 常量 也存放在这里
(局部变量、局部常量 不放在这里)
该区域的数据在程序结束后由系统操作释放
6.1.2 程序运行后
-
栈区:
存放函数的参数值、局部变量等。
由编译器自动分配释放。**因此,应该避免把局部变量地址作为返回值。**若如此做,编译器仅一次会保留指针指向的值。
int* met1() { int a = 10; //一个局部变量 return &a; //把局部变量地址作为返回值 } int main() { int* p = met1(); //接收上面的局部变量地址 cout << *p << endl; //仅一次,编译器保留的局部变量值 cout << *p << endl; //此时会输出错误的值 return 0; }
-
堆区:
由程序员分配和释放。若程序员不释放,程序结束时会由操作系统回收
在 C++ 中主要利用 new 在栈区开辟内存空间
int* met2() { int* a = new int(10); //开辟了一个内存空间 return a; //把内存空间地址作为返回值 } int main() { int* p = met2(); //接收上面的内存空间地址 cout << *p << endl; cout << *p << endl; //正常输出 return 0; }
6.1.3 new 操作符
在 C++ 中主要利用 new 在栈区开辟内存空间
堆区由程序员手动分配和释放空间
-
分配空间:
new 数据类型
-
释放空间:
delete 指针
int* p = new int(24); //[1]
cout << *p << endl; //这里,输出 24
delete p; //[2]
/* cout << *p; */ //这里就不能再访问该内存空间了
int* p2 = new int[10]; //[3]
for (int i = 0; i < 10; i++){ //赋值
p2[i] = i;
}
delete[] p2; //[4]
- new 返回一个该数据类型的指针
- delete 释放该数据内存
- 为一个长度 10 的数组开辟了空间。这个场合数组中的元素也都在堆区内
- delete[] 释放该数组内存。需要加
[ ]
中括号以表示释放多个内存
6.2 引用
引用:给变量起别名
6.2.1 引用的基本使用
语法:数据类型& 别名 = 原名
string keQing = "霓霆快雨";
string& waifu = keQing; // 为 keQing 起了别名 waifu
waifu = "玉衡星";
cout << keQing; // 玉衡星
上面的 waifu 就是引用,其类型是 string&。
引用声明后,系统不会为其分配空间,而是让其指向被引用的地址。
6.2.2 引用注意事项
-
引用必须初始化
/* int& temp; */ // 不可以,不可以哟
引用的对象也必须完成初始化。
-
引用初始化后即不能改变
int S_H_Ark = 101; int S_H_Dark = 101; int& card = S_H_Ark; /* int& card = S_H_Dark; */ // 快住手,这根本不是引用 card = S_H_Dark; // 这里是赋值操作
-
不能声明地址的引用
int n = 10; /* int& nn = &n; */ // 这,这不对吧
不能声明宏常量的引用,也不能声明一个表达式的引用(除非表达式的结果是一个变量的引用)
6.2.3 引用做函数参数
传入参数时,利用引用方法让形参修饰实参。这样,可以简化指针修改实参。
-
值传递:
void met1 (int n1) { n1++; }
值传递的场合,形参是另一个局部变量。其值改变不影响原本的变量。
-
地址传递:
void met2 (int* n2) { (*n2)++; }
地址传递的场合,形参即原变量的指针。其值改变会影响原本的变量。
-
引用传递:
void met3 (int& n3) { n3++; }
引用传递的场合,形参即原变量的引用。其值改变会影响原本的变量。
引用传递的效果和地址传递相同,但语法更简单
6.2.4 引用做返回值
-
不要返回局部变量的引用
—— 同 [6.1.2 栈区]
int& met4() { int n = 100; return n; //[1] } int main() { int& ref = met4(); cout << ref << endl; //[2] cout << ref << endl; //[3] }
- 返回了局部变量(的引用)
- 编译器保留一次
- 此时变量已经被释放,结果会出错
-
如果函数的返回值是引用,则该函数调用可以作为左值
int& met5(int a) { static int n = a; //[1] return n; } int main() { int& ref = met5(1); cout << ref << endl; //[2] met5(20) = 100; //[3] cout << ref << ',' << met5(4); //[4] }
-
静态变量,储存在全局区。全局区数据在系统结束后被释放
—— 全局区 见 [6.1.1 全局区]
-
此时输出 1
-
met5()
返回值是引用,所以可以作为左值met5()
相当于是ref
变量的原名,ref
是别名所以此处,
ref
变量变为 100经过测试,虽然
met5()
方法的参数改变了,但值是同一个,即 100—— 原因 见 …… 就往下看呗 [6.2.5 引用的本质]
-
输出:100,100
-
6.2.5 引用的本质
引用的本质在 C++ 内部实现是一个指针常量
—— 指针常量 见 [4.1.3.2 指针常量]
/*—> —> —> —> —> —> —> —> —> 去伪存真 —> —> —> —> —> —> —> —> —>*/
//
void met3 (int& n3) { // void met3 (int* const n3) {
n3++; // (*n3)++;
} // }
//
int& met5(int a) { // int* met5(int a) {
static int n = a; // static int n = a;
return n; // int* const met5 = &n;
// /* [1] */
// return met5;
} // }
//
int main() { // int main() {
int a = 0; // int a = 0;
met3(a); // met3(&a);
int& ref = met5(a); // int* ref = met5(a);
cout << ref << endl; // cout << *ref << endl;
met5(20) = 100; // *met5 = 100;
cout << ref << met5(4); // cout << *ref << *met5;
} // }
- 此处解释了 [6.2.4.3] 出现的参数变化的问题:指针常量的指向是不变的
6.2.6 常量引用
常量引用主要用来修饰形参,防止误操作
/* int& n1 = 10; */ //引用必须指向合法的内存空间,而常量在常量区
const int& n = 10; // int temp = 10;
// const int& n = temp;
/* n = 20 */ //此时,n 不可修改
修饰形参:
void println(const int& n) {
/* n = 100; */ //此时,n 不可修改
cout << n << endl;
}