当前位置: 首页 > news >正文

【C++学习】核心编程之内存分区模型、引用和函数提高(黑马学习笔记)

目录

一、内存分区模型

1.1程序运行前

1.2程序运行后

1.3new操作符

二、引用

2.1基本使用

2.2引用做函数参数

2.3引用做函数返回值

2.4引用的本质

2.5常量引用

 三、函数提高

3.1函数默认参数

3.2函数占位参数

3.3函数重载

系列文章:


一、内存分区模型

C++程序在执行时,将内存大方向划分为4个区域

  • 代码区:存放函数体的二进制代码,由操作系统进行管理的

  • 全局区:存放全局变量静态变量以及常量

  • 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等

  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

内存四区意义:

不同区域存放的数据,赋予不同的生命周期, 给我们更大的灵活编程

1.1程序运行前

​ 在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域

​ 代码区:

​ 存放 CPU 执行的机器指令

​ 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可【共享特性】

​ 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令【只读特性】

​ 全局区:

全局变量静态变量存放在此.

​ 全局区还包含了常量区, 字符串常量和其他常量也存放在此.

​ 该区域的数据在程序结束后由操作系统释放【内存管理】

1. 全局变量:在所有函数外部定义的变量。

可以在程序的任何地方访问,具有全局作用域;程序开始时分配内存,程序结束时释放。

全局变量在整个程序的执行过程中保持其值,可以在多个地方被修改。

2. 静态变量:使用static关键字定义的变量,可以是局部的也可以是全局的。

局部静态变量:在函数内部定义,生命周期与全局变量相同,但作用域限于定义它的函数。

全局静态变量:在所有函数外部定义,并且只能在定义它的文件中访问。

程序开始时分配内存,程序结束时释放,保持其值直到下次调用。

3. 常量:使用const关键字定义的不可修改的变量。常量可以是全局的、局部的,也可以是静态的。

#include <iostream>
using namespace std;// 全局变量
int g_a = 10; // 全局变量在整个程序中都可以访问
int g_b = 10; const int c_g_a = 10; // 全局常量值不可更改
const int c_g_b = 10;int main() 
{// 局部变量int a = 10; // 局部变量 a,仅在 main 函数内有效int b = 10; // 打印局部变量的地址cout << "局部变量a地址为: " << (int)&a << endl; cout << "局部变量b地址为: " << (int)&b << endl; // 打印全局变量的地址cout << "全局变量g_a地址为: " << (int)&g_a << endl; cout << "全局变量g_b地址为: " << (int)&g_b << endl; // 静态变量// 静态变量值在函数调用之间保持不变static int s_a = 10; static int s_b = 10; // 打印静态变量的地址cout << "静态变量s_a地址为: " << (int)&s_a << endl; cout << "静态变量s_b地址为: " << (int)&s_b << endl; // 打印字符串常量的地址cout << "字符串常量地址为: " << (int)&"hello world" << endl; cout << "字符串常量地址为: " << (int)&"hello world1" << endl; // 打印全局常量的地址cout << "全局常量c_g_a地址为: " << (int)&c_g_a << endl; cout << "全局常量c_g_b地址为: " << (int)&c_g_b << endl;// 局部常量const int c_l_a = 10; // 局部常量 c_l_a,仅在 main 函数内有效,值不可更改const int c_l_b = 10; // 打印局部常量的地址cout << "局部常量c_l_a地址为: " << (int)&c_l_a << endl; cout << "局部常量c_l_b地址为: " << (int)&c_l_b << endl; system("pause"); return 0; 
}

 运行结果:

全局变量存放在全局区(数据区),地址相邻(g_a 和 g_b 地址差为 4,因它们都是 int 类型)。

局部变量和局部常量在栈区。

全局变量、静态变量和全局常量在全局区

1.2程序运行后

 栈区:

​ 由编译器自动分配释放, 存放函数的参数值,局部变量

注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

#include <iostream>
using namespace std;// 函数返回局部变量的地址
int* func() {int a = 10; // 局部变量 a,存储在栈区return &a;  // 返回局部变量 a 的地址
}int main() {int* p = func(); // p 接收 func() 的返回值(局部变量 a 的地址)// 这里尝试解引用 p,访问局部变量 a 的值cout << *p << endl; cout << *p << endl; system("pause"); return 0;
}

 在 C++ 中,返回局部变量的地址是非常危险的做法,会导致程序的稳定性和安全性问题。

return &a; 返回局部变量的地址。这是一个常见的错误,因为当 func 返回时,a 的内存会被释放,指针 p 将指向一个无效的地址

vs2022运行出来两次都是10是vs2022编译器做了优化,故和老师的视频运行结果不一样

​ 堆区:

​ 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收

​ 在C++中主要利用new在堆区开辟内存

#include <iostream>
using namespace std;// 函数返回一个动态分配的整数指针
int* func() 
{//利用new关键字,将数据开辟到堆区//指针本质是局部变量,放在栈上,指针保存的数据放在堆区int* a = new int(10); // 动态分配内存,初始化为 10return a; // 返回指向动态分配内存的指针,存放地址
}int main() 
{int* p = func(); // p 接收 func() 的返回值,即动态分配的整数指针// 输出指针 p 指向的值cout << *p << endl; // 输出 10cout << *p << endl; // 再次输出 10delete p; // 释放动态分配的内存,以避免内存泄漏system("pause");return 0;
}

指针本质是局部变量,放在栈上,指针保存的数据放在堆区

指针的性质

int* a; 是一个指针变量,它本身是在栈区分配的。它在 func 函数调用期间存在,函数返回后,指针本身的内存会被释放。

指针指向的内存

指针 a 保存的是指向堆区动态分配内存的地址。int* a = new int(10); 创建了一个存储 10 的整型变量,这个整型变量位于堆区。尽管指针变量 a 存储在栈区,但它指向的数据(10)在堆区,直到显式释放(使用 delete)为止

new关键字可以创建一个 堆区数据,返回创建的数据的地址

1.3new操作符

C++中利用new操作符在堆区开辟数据

​ 堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete

语法: new 数据类型

#include <iostream>
using namespace std;int main() {// 在堆区动态分配一个整型变量int* p = new int(10); // 使用 new 分配内存并初始化为 10// 输出指针 p 指向的值cout << *p << endl; // 输出 10// 释放动态分配的内存delete p; // 在堆区动态分配一个整型数组int* arr = new int[5]; // 使用 new[] 开辟一个包含 5 个整数的数组// 初始化数组for (int i = 0; i < 5; i++) {arr[i] = i * 2; // 将数组初始化为 0, 2, 4, 6, 8}// 输出数组中的值for (int i = 0; i < 5; i++) {cout << arr[i] << " "; // 输出 0 2 4 6 8}cout << endl;delete[] arr; // 使用 delete[] 释放数组的内存return 0; 
}

 利用new创建的数据,会返回该数据对应的类型的指针

动态分配单个变量

int* p = new int(10); 在堆上分配一个整型变量并初始化为 10p 存储了这个变量的地址。

动态分配数组

int* arr = new int[5]; 在堆上分配一个整型数组,数组包含 5 个整数。

释放内存

使用 delete 释放单个变量的内存,使用 delete[] 释放数组的内存。


二、引用

2.1基本使用

作用: 给变量起别名

语法: 数据类型 &别名 = 原名

#include <iostream>
using namespace std;int main() {int a = 10;        // 定义整型变量 a,并初始化为 10int& b = a;       // 定义引用 b,引用变量 acout << "a = " << a << endl; cout << "b = " << b << endl; b = 100;          // 修改引用 b 的值cout << "a = " << a << endl; cout << "b = " << b << endl; system("pause"); return 0;       
}

引用的作用ba 实际上指向同一内存地址,对 b 的修改会影响到 a。因此,当你通过 b 修改值时,a 的值也会改变。

运行结果:修改 b100 后,a 也变为 100,因为 b 是对 a 的引用

	int a = 10;int b = 20;//int &c; //错误,引用必须初始化int &c = a; //一旦初始化后,就不可以更改c = b; //这是赋值操作,不是更改引用

引用注意事项

  • 引用必须初始化
  • 引用在初始化后,不可以改变

2.2引用做函数参数

函数传参时,可以利用引用的技术让形参修饰实参

#include <iostream>
using namespace std;// 1. 值传递
void mySwap01(int a, int b) {int temp = a; // 将 a 的值保存在 temp 中a = b;        // 将 b 的值赋给 ab = temp;     // 将 temp 的值赋给 b
}// 2. 地址传递
void mySwap02(int* a, int* b) {int temp = *a; // 通过指针获取 a 指向的值*a = *b;       // 将 b 指向的值赋给 a 指向的地址*b = temp;     // 将 temp 的值赋给 b 指向的地址
}// 3. 引用传递
void mySwap03(int& a, int& b) {int temp = a; // 将 a 的值保存在 temp 中a = b;        // 将 b 的值赋给 ab = temp;     // 将 temp 的值赋给 b
}int main() {int a = 10;  int b = 20;  mySwap01(a, b); // 调用值传递交换函数cout << "a:" << a << " b:" << b << endl; mySwap02(&a, &b); // 调用地址传递交换函数cout << "a:" << a << " b:" << b << endl; mySwap03(a, b); // 调用引用传递交换函数cout << "a:" << a << " b:" << b << endl; system("pause"); return 0;       
}
  • 值传递适合不需要修改原始数据的情况。
  • 地址传递适合需要在函数内部修改数据的情况,但要小心指针的使用。
  • 引用传递提供了一种简洁的方法来实现类似于指针的功能,同时提高了代码的可读性

值传递、引用传递、指针传递【函数的参数传递】详解-CSDN博客

2.3引用做函数返回值

注意:不要返回局部变量引用

当一个函数返回一个引用时,它就可以作为左值使用

#include <iostream>
using namespace std;// 返回局部变量引用
int& test01() {int a = 10; // 局部变量,存放在栈区return a;   // 返回局部变量的引用(函数返回值类型int&)【错误用法】
}// 返回静态变量引用
int& test02() {static int a = 20; // 静态变量,放在全局区,生命周期贯穿程序运行return a;         // 返回静态变量的引用【正确用法】
}int main() {// 不能返回局部变量的引用int& ref = test01(); // 尝试返回局部变量的引用,会导致未定义行为cout << "ref = " << ref << endl; cout << "ref = " << ref << endl; // 如果函数做左值,那么必须返回引用int& ref2 = test02(); // 返回静态变量的引用cout << "ref2 = " << ref2 << endl; // 输出 ref2 的值,结果为 20cout << "ref2 = " << ref2 << endl; //函数调用做左值test02() = 1000; // 通过引用修改静态变量 a 的值cout << "ref2 = " << ref2 << endl; // 输出 ref2 的值,结果为 1000cout << "ref2 = " << ref2 << endl; system("pause"); return 0; 
}

2.4引用的本质

引用的本质在c++内部实现是一个指针常量.

 

#include <iostream>
using namespace std;// 函数接受一个引用参数
void func(int& ref) {ref = 100; // ref 是引用,实际效果是 *ref = 100;
}int main() {int a = 10; // 声明一个引用 ref,指向 a//自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改int& ref = a; ref = 20; // 修改 ref 的值,实际上是修改 a 的值,*ref = 20;cout << "a: " << a << endl; // 输出 a: 20cout << "ref: " << ref << endl; // 输出 ref: 20func(a); // 调用函数,传入 a 的引用cout << "a after func: " << a << endl; // 输出 a : 100cout << "ref after func: " << ref << endl; // 输出 ref : 100return 0;
}

C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了 

2.5常量引用

主要用来修饰形参,防止误操作(在函数形参列表中,加const修饰形参,防止形参改变实参)

#include <iostream>
using namespace std;// 常量引用参数
void showValue(const int& v) {// v += 10; // 不能修改常量引用的值cout << v << endl; // 输出引用的值
}int main() {// int& ref = 10; // 错误:引用必须指向一个合法的内存空间// 加入 const 使其合法,编译器优化生成临时变量int temp = 10; const int& ref = temp;const int& ref = 10; // 创建一个常量引用,指向字面量 10// ref = 100; // 错误:常量引用不能被修改cout << ref << endl; // 结果为 10int a = 10;showValue(a); // 调用函数,传入 a 的常量引用system("pause");return 0;
}

C++中的引用——引用详解_c++工程引用-CSDN博客


 三、函数提高

3.1函数默认参数

在C++中,函数的形参列表中的形参是可以有默认值的。

语法: 返回值类型 函数名 (参数= 默认值){}

#include <iostream>
using namespace std;// 带有默认参数的函数
int func(int a, int b = 10, int c = 10) {return a + b + c; 
}// 函数声明,带有默认参数【 如果函数声明有默认值,函数实现的时候就不能有默认参数】
// 注意:如果某个参数有默认值,那么从这个参数往后必须都要有默认值
int func2(int a = 10, int b = 10); // 函数声明
int func2(int a, int b) { // 函数实现return a + b; 
}int main() {cout << "ret = " << func(20, 20) << endl; // 输出:ret = 50cout << "ret = " << func(100) << endl;   // 输出:ret = 120system("pause");return 0;
}
  • 如果某个位置的参数有默认值,则从该参数往后,所有参数都必须有默认值

  • 如果我们自己传入参数,就用自己的数据,没有的话就用默认值

  • 在函数的声明和定义中,不能同时包含默认参数。如果函数声明中已有默认参数,则在实现时不能再次声明它们

3.2函数占位参数

C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置

语法: 返回值类型 函数名 (数据类型){}

#include <iostream>
using namespace std;// 定义一个函数,其中有一个占位参数
void func(int a, int) {cout << "this is func" << endl; // 打印信息
}int main() {// 调用函数,传入两个参数func(10, 10); // 占位参数必须填补system("pause");return 0;
}

占位参数定义

void func(int a, int) 定义了一个函数 func,其中第二个参数是占位参数。这个参数虽然定义了,但在函数体内没有被使用

函数调用

main 中调用 func(10, 10); 时,两个参数都必须提供,即使第二个参数没有被使用。C++ 的语法要求你必须为每个参数提供一个值

3.3函数重载

函数名可以相同,提高复用性

函数重载满足条件:

  • 同一个作用域下

  • 函数名称相同

  • 函数参数类型不同 或者 个数不同 或者 顺序不同

函数的返回值不可以作为函数重载的条件

注意事项

  • 引用作为重载条件
  • 函数重载碰到函数默认参数
#include <iostream>
using namespace std;// 函数重载示例:引用作为重载条件
void func(int &a) {cout << "func (int &a) 调用 " << endl; void func(const int &a) {cout << "func (const int &a) 调用 " << endl; 
}// 函数重载示例:处理默认参数
void func2(int a, int b = 10) {cout << "func2(int a, int b = 10) 调用" << endl; 
}void func2(int a) {cout << "func2(int a) 调用" << endl; 
}int main() {int a = 10;// 调用带有引用的函数func(a); // 调用 func(int &a)func(10); // 调用 func(const int &a)// 以下调用会导致歧义,因为存在默认参数// func2(10); // 碰到默认参数产生歧义,需要避免system("pause");return 0;
}

系列文章:

基于基于哔哩哔哩黑马程序员的学习笔记

在本博文前:

C++学习笔记(基于哔哩哔哩黑马程序员)之基础编程(上)

C++学习笔记(基于哔哩哔哩黑马程序员)之基础编程(下)

C++学习(黑马程序员)之通讯录管理系统(附完整代码)【代码实战】

视频链接:黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难_哔哩哔哩_bilibili


http://www.mrgr.cn/news/48006.html

相关文章:

  • JDK高频面试题(包重点)
  • Structured-Streaming初识
  • 企业架构学习笔记-数字化转型
  • 【数据科学导论】第四章·特征工程与探索性分析
  • ubuntu20.04安装ros noetic版本记录
  • 【深度学习】Java DL4J基于 CNN 构建车辆识别与跟踪模型
  • 简单解析由于找不到xinput1_3.dll,无法继续执行代码的详细解决方法
  • 图的深度优先遍历的非递归算法
  • 服务端测试开发必备的技能:Mock测试!
  • 半周期检查-下降沿发上升沿采
  • AI语音助手在线版本
  • 数据结构与算法(八)循环链表
  • onnx代码解读
  • 摩托车一键启动智能钥匙提高了便捷性和安全性
  • 多元线性回归:机器学习中的经典模型探讨
  • HttpPost 类(构建 HTTP POST 请求)
  • 基于Springboot+Vue的网上订餐系统(含源码数据库)
  • 光伏“地图导航”:光照、政策、电价一目了然
  • 【p2p、分布式,区块链笔记 UPNP】: Libupnp test_init.c 02 初始化SDK --- UpnpInitPreamble
  • 如何创建一个node.js项目并配置
  • 【Lua学习】数值number和数学库math
  • MacOS 同时配置github、gitee和gitlab密钥
  • 信息安全数学基础(26)二次互反律
  • DevOps
  • 【Spring】@Autowired注解自动装配的过程
  • 2025届计算机保研经验贴(末九→浙江大学软件学院)