<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]
  1. new 返回一个该数据类型的指针
  2. delete 释放该数据内存
  3. 为一个长度 10 的数组开辟了空间。这个场合数组中的元素也都在堆区内
  4. 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]
    }
    1. 返回了局部变量(的引用)
    2. 编译器保留一次
    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]
    }
    1. 静态变量,储存在全局区。全局区数据在系统结束后被释放

      —— 全局区 见 [6.1.1 全局区]

    2. 此时输出 1

    3. met5() 返回值是引用,所以可以作为左值

      met5() 相当于是 ref 变量的原名,ref 是别名

      所以此处,ref 变量变为 100

      经过测试,虽然 met5() 方法的参数改变了,但值是同一个,即 100

      —— 原因 见 …… 就往下看呗 [6.2.5 引用的本质]

    4. 输出: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;
}									//	}
  1. 此处解释了 [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;
}

<C++>6 内存和引用
https://i-melody.github.io/2022/04/02/C++/入门阶段/6 内存和引用/
作者
Melody
发布于
2022年4月2日
许可协议