C++ 内存的分区模型、引用、函数补充

本文最后更新于:2022年4月22日 上午

C++ 内存的分区模型、引用、函数补充

内存的分区模型

代码区

所有编写的代码都放在代码区,存放函数体的二进制代码,由操作系统进行管理

存放 cpu 执行的机器指令

代码区是共享的,共享的是对于频繁执行的程序,只需要在程序中有一份代码即可

代码区是只读的,防止程序意外的修改了他的指令

全局区

  • 全局变量/ 通过 const修饰的全局常量

  • 通过 static 关键字修饰的静态变量

  • 字符串常量

int g_num1 = 10;
int g_num2 = 20;

int main() {

  // 局部变量
  int l_num1 = 10;
  cout << "局部变量 l_num1 地址" << (int)&l_num1 << endl << endl;

  // 可以看到内存地址是相近的
  // 全局变量
  cout << "全局变量 g_num1 地址" << (int)&g_num1 << endl;
  cout << "全局变量 g_num2 地址" << (int)&g_num2 << endl;
  
  // static 关键字修饰的静态变量

  static int l_num2 = 10;
  cout << "static 修饰的静态变量 l_num2 地址" << (int)&l_num2 << endl;

  // 字符串常量
  cout << "字符串变量变量 l_name 地址" << (int)&"hello world" << endl;


  // 全局常量
  cout << "全局常量 g_num3 地址" << (int)&g_num3 << endl;
  
  return 0
}

输出

局部变量 l_num1 地址6878980

全局变量 g_num1 地址7286784
全局变量 g_num2 地址7286788

static 修饰的静态变量 l_num2 地址7286796
字符串变量变量 l_name 地址7273596
全局常量 g_num3 地址7273264

从以上可以看出,全局区的变量存放地址是相近的

栈区

由编译器自动分配和释放,存放函数的形参列表,局部变量,当函数执行完毕时会销毁,所以不要在 函数中返回 函数内变量的内存地址

// 返回 一个 int 型的指针
int* get_a() {
  int a = 10;
  return &a;
}

int main() {

  int l_num3 = 20;
  int* l_a = get_a();
    
  // 第一次可以取到值,第二次取不到,第一次编译器会帮我们做一次保留
  cout << "函数返回的指针:" << *l_a << endl;    
  cout << "函数返回的指针:" << *l_a << endl;
  
  return 0;
}

输出

函数返回的指针:10
函数返回的指针:2062064008

堆区

由开发人员自己分配,通过 new 关键字创建的变量会存储在堆内存空间中

如果需要释放时,由开发人员手动通过 delete 关键字释放,如果没有手动释放,则会在程序运行结束时自动释放

// 返回 对应类型的指针
int* l_num4 = new int(30);
cout << "局部变量 l_num3 地址" << (int)&g_num3 << endl;

输出

局部变量 l_num3 地址7273264

new 关键字的基本使用

int* l_num6 = new int(10);

// 会一直保留,如果没有手动释放则会一直存在,知道程序关闭
cout << "通过 new 创建的变量:" << *l_num6 << endl;    
cout << "通过 new 创建的变量:" << *l_num6 << endl;  

// 释放内存
delete l_num6;


// 空指针报错
// cout << "通过 new 创建的变量:" << *l_num6 << endl; // 使用未初始化的内存

释放数组

int* arr = new int[3];
//int* arr = new int[3]{ 1,2, 3 };  // 初始化列表

cout << arr << endl;

// 释放数组的内存,需要加上 []
delete[] arr;

引用

引用就类似于给变量起一个别名,通过这个别名去对原数据进行操作时,两个变量都会发生改变,应该指向的是同一块内存地址

需要注意引用必须初始化

基本使用

// 引用的基本使用,理解为起别名
int l_num7 = 10;

// 必须初始化值,且不建议再次修改(因为是内存地址,所以会将原来的值也改变)
int& l_num8 = l_num7;
l_num8 = 20;

// 都输出 20
cout << "l_num7:" << l_num7 << endl;
cout << "l_num8:" << l_num8 << endl;

输出

l_num7:20
l_num8:20

函数的补充

引用作为函数的参数

引用作为函数参数,操作会更为简单

// 函数的引用参数
void scaleUp(int& num) {
  // 以下是 num = num * 10; 的简写,不要和指针的操作混淆
  num *= 10;
}

int main() {
  
  int l_num9 = 10;
  scaleUp(l_num9);
  cout << "l_num9:" << l_num9 << endl;
  
  return 0;
}

输出

l_num9:100

返回值是引用的注意点

不要返回局部变量的引用

// 返回 一个 int 型的引用
int b = 10;
int& get_b() {
  return b;
}

int main() {
  
  int l_num10 = get_b();
  cout << "l_num10:" << l_num10 << endl;
  cout << "l_num10:" << l_num10 << endl;
  cout << "l_num10:" << l_num10 << endl;
  cout << "l_num10:" << l_num10 << endl;
  cout << "l_num10:" << l_num10 << endl;
  cout << "l_num10:" << l_num10 << endl;
  cout << "l_num10:" << l_num10 << endl;
  cout << "l_num10:" << l_num10 << endl;
  
  return 0;
}

输出

l_num10:10
l_num10:10
l_num10:10
l_num10:10
l_num10:10
l_num10:10
l_num10:10
l_num10:10

函数的调用作为左值

如果函数返回的是一个引用,则这个函数的调用可以作为左值

// 返回 一个 int 型的引用
int b = 10;
int& get_b() {
  return b;
}

int main() {
  
  get_b() = 20;

  int l_num11 = get_b();
  cout << "l_num11:" << l_num11 << endl;
  
  return 0;
}

输出

l_num11:20

引用的本质

引用的本质就是一个指针常量,指针常量的语法糖

// 引用的本质(就类似于 使用 const 关键字修饰了的指针)
void scaleUp20(int* const num) {
  *num *= 20;
}

int main() {
  
  int l_num12 = 10;
  scaleUp20(&l_num12);
  
  cout << "l_num12:" << l_num12 << endl;
  
  return 0;
}

输入

l_num12:200

函数形参的默认值

很多语言(java,js)都有函数形参的默认值,用户没有传入的时候可以使用默认值

int sum1(int num1, int num2 = 20) {
  return num1 + num2;
}

int main() {
  
  cout << "函数的默认参数:" << sum1(10) << endl;
  
  return 0;
}

输出

函数的默认参数:30

需要注意的是,当某一个形参有默认值后,该形参后面的所有形参都必须拥有默认值,不能像 js一样使用 undefined来占位以使用形参默认值

函数的声明和函数的定义只允许一个地方出现默认值

函数的占位参数

// 占位参数,后续很有用
int sum3(int num1, int) {
  return num1;
}

函数的重载

java也有函数的重载,函数的重载需要符合以下三个条件

  • 处于同一作用域

  • 函数名称相同

  • 函数的参数个数不同 或者 参数的类型不同

需要注意的是 函数的返回值不能作为重载的条件,函数的引用参数可以形成重载 通过 const 修饰

函数的重载碰到默认参数,会出现二义性,尽量避免这种情况

基本使用

// 函数的重载
int calcTotal(int num1, int num2) {
  cout << "calcTotal 两个参数" << endl;
  return num1 + num2;
}

int calcTotal(int num1, int num2, int num3) {
  cout << "calcTotal 三个参数" << endl;
  return num1 + num2 + num3;
}

int main() {
  
  int l_num13 = 10;
  int l_num14 = 10;
  int l_num15 = 10;

  cout << "函数的重载1:" << calcTotal(l_num13, l_num14) << endl;
  cout << "函数的重载2:" << calcTotal(l_num13, l_num14, l_num15) << endl;
  cout << "函数的重载3:" << calcTotal(l_num13, l_num14, 10) << endl<< endl;
  
  return 0;
}

输出

calcTotal 两个参数
函数的重载120

calcTotal 三个参数
函数的重载230

calcTotal 三个参数
函数的重载330

函数的引用参数构成重载

// 引用参数构成 重载
int func(int* num) {
  cout << "func函数 int* num :" << *num << endl;
  return *num;
}

int func(const int* num) {
  cout << "func函数 const int* num :" << *num << endl;
  return *num;
}

int main() {
  
  const int l_num16 = 10;
  cout << "函数的重载1:" << func(&l_num13) << endl;
  cout << "函数的重载2:" << func(&l_num16) << endl;

  return 0;
}

输出

func函数 int* num :10
函数的重载110
func函数 const int* num :10
函数的重载210

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处。