CPP核心内容
内存分区模型
C++程序在执行时,将内存大方向划分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
| 区域 | 存储内容 | 生命周期 | 访问速度 |
|---|---|---|---|
| 栈区 | 局部变量、函数参数、返回地址 | 函数结束时自动释放 | 最快 |
| 堆区 | new/malloc动态分配的对象 | 手动管理或程序结束回收 | 较慢 |
| 全局区 | 全局/静态变量、常量、虚函数表 | 程序启动到结束 | 中 |
| 代码区 | 机器指令、只读常量(字符串字面量) | 程序运行期间 | 只读 |
代码区(Text Segment)
用途:存储编译后的二进制机器指令(函数体代码)
特性:
只读属性(防止程序意外修改指令)
在程序运行前完成分配
共享内存机制(多个实例共享同一份代码) 全局区(Global/Static Segment)
用途:存放全局变量、静态变量(static)、常量
细分区域:
.data 段:已初始化的全局/静态变量
.bss 段:未初始化的全局/静态变量(默认置零)
常量区:字符串常量(如 "Hello")、const 修饰的全局常量
生命周期:程序启动时分配,结束时释放 栈区(Stack)
用途:存储函数参数、局部变量、函数返回地址
特性:
自动管理(编译器分配/回收)
大小固定(Windows默认约1MB,Linux默认约8MB)
后进先出(LIFO)结构
快速访问(通过寄存器直接操作) 堆区(Heap)
用途:存储动态分配内存(通过 new/malloc 申请)
特性:
手动管理(需显式使用 delete/free 释放)
空间远大于栈区(受系统可用内存限制)
访问速度慢于栈区(通过指针间接访问)
可能产生内存碎片
内存四区意义:不同区域存放的数据,赋予不同的生命周期,提供更大的灵活编程
典型对比场景
int global_var; // 全局区
void func() {
static int static_var; // 全局区
int local_var; // 栈区
int* heap_var = new int(10); // 堆区
const char* str = "Literal"; // "Literal"在全局区,指针在栈区
}
⚠️ 注意事项:
- 栈溢出:递归深度过大时可能触发
Stack Overflow - 内存泄漏:堆区未正确释放内存会导致资源耗尽
- C++11后推荐使用智能指针(如
unique_ptr)管理堆内存
地址分配展示:
using namespace std;
// 全局变量
int g_a = 10;
int g_b = 20;
// 全局常量
const int g_c_a = 10;
const int g_c_b = 20;
int main()
{
// 局部变量地址
int a = 10;
int b = 20;
// 常量
const int c_a = 10;
const int c_b = 20;
string str = "hello world";
// 静态变量
static int s_a = 10;
static int s_b = 20;
cout << "局部变量a的地址为:" << "\t" << (long long)&a << endl;// 98882747140
cout << "局部变量b的地址为:" << "\t" << (long long) &b << endl;// 98882747172
cout << "局部常量c_a的地址为:" << "\t" << (long long)&c_a << endl;// 98882747204
cout << "局部常量c_b的地址为:" << "\t" << (long long)&c_b << endl;// 98882747236
cout << "字符串常量str的地址为:" << "\t" << (long long)&str << endl;// 98882747272
cout << "全局常量g_c_a的地址为:" << "\t" << (long long)&g_c_a << endl;// 140699421043728
cout << "全局常量g_c_b的地址为:" << "\t" << (long long)&g_c_b << endl;// 140699421043732
cout << "全局变量g_a的地址为:" << "\t" << (long long) &g_a << endl;// 140699421061120
cout << "全局变量g_b的地址为:" << "\t" << (long long) &g_b << endl;// 140699421061124
cout << "静态变量d_a的地址为:" << "\t" << (long long) &s_a << endl;// 140699421061128
cout << "静态变量d_b的地址为:" << "\t" << (long long) &s_b << endl;// 140699421061132
// 以上输出都是举例,实际最终运行结果不同
// 不过可以得出结论:
// 局部变量、局部常量、字符串常量的地址都是相近的
// 全局变量、全局常量、静态变量地址都是相近的
return 0;
}
记忆技巧:
- 局部地址相近
- 全局地址相近
程序运行前
在程序编译后,生成了.exe可执行程序,未执行该程序前分为两个区域:
代码区:
存放CPU执行的机器指令
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码是只读,使其只读的原因是防止程序意外地修改了它的指令
全局区:
全局变量和静态变量存放在此
全局区还包含了常量区,字符串常量和其他常量也存放在此
该区域的数据在程序结束后由操作系统释放
程序运行后
栈区
栈区:
- 由编译器自动分配释放,存放函数的参数值,局部变量等
- 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
示例:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int* func()
{
int a = 10;
return &a;
}
int* another_function() {
int a = 10;
return &a;
};
int main() {
int* p = func();
cout << *p << endl;// 10
std::vector<int> v(1000);// 故意占用栈空间
cout << *p << endl;// 32759
return 0;
}
堆区
堆区:
- 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
- 在C++中主要利用new在堆区开辟内存
示例:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int* func() {
int* a = new int(10);
return a;
}
int main() {
int* p = func();
cout << *p << endl;// 10
std::vector<int> v(1000);// 故意占用栈空间
cout << *p << endl;// 10不受影响
return 0;
}
new操作符
C++中利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
语法:new 数据类型
示例:
基础用法:
#include <iostream>
#include <string>
using namespace std;
int main() {
// 在堆区创建整型数据
// new返回是该数据类型的指针
int* p1 = new int(10);
cout << *p1 << endl;
delete p1;
// cout << *p1 << endl; // 野指针访问
// new数组
int* p2 = new int[10]; // [10] 表示数组长度
for (int i = 0; i < 10; i++)
{
p2[i] = i;
cout << p2[i] << " "; // 0 1 2 3 4 5 6 7 8 9
}
// 释放堆区数组
delete[] p2;
return 0;
}
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int* func_int() {
int* a = new int(10);// new返回的是该数据类型的指针
return a;
}
bool* func_bool() {
bool* a = new bool(true);// new返回的是该数据类型的指针
return a;
}
string* func_string() {
string* a = new string("hello"); // new返回的是该数据类型的指针
return a;
}
float* func_float() {
float* a = new float(3.14);// new返回的是该数据类型的指针
return a;
}
int main() {
int* int_p = func_int();
bool* bool_p = func_bool();
string* string_p = func_string();
float* float_p = func_float();
std::vector<int> v(1000);// 故意占用栈空间
cout << *int_p << endl;// 10不受影响
cout << *bool_p << endl;
cout << *string_p << endl;
cout << *float_p << endl;
return 0;
}
引用
作用:给变量起别名(实际上就是让多个变量指向同一个内存地址)
语法:数据类型& 别名 = 原名
区别:取址符为数据类型 变量名 = &变量名
示例:
int main() {
int a = 10;
int &b = a;// 编译器生成:int* const b = &a;
cout << "a:" << a <<endl;// 10
cout << "b:" << b <<endl;// 10同一内存地址的数据
}
引用变量
b在底层是通过常量指针(int* const)实现的,存储的是变量a的地址任何对
b的操作(如b = 20)会被编译为*b = 20(自动完成指针解引用)
引用注意事项
- 引用必须初始化
- 引用在初始化后,不可以改变
示例:
#include <iostream>
#include <string>
using namespace std;
int main() {
int a = 10;
int b = 20;
// int &c; // 报错,引用变量必须初始化
int &c = a;
c = b; // 这是赋值操作,不是更改引用
// &c = b; // 报错,不可以改变引用变量的引用对象
cout << "a: " << a << endl;// 20
cout << "b: " << b << endl;// 20
cout << "c: " << c << endl;// 20
return 0;
}
引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
示例:
#include <iostream>
#include <string>
using namespace std;
// 交换函数
// 值传递方式
void swap(int a,int b) {
int tmp = a;
a = b;
b = tmp;
};
// 地址传递方式
void swap_byPointer(int* a, int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
// 引用传递方式
void swap_byRef(int& a, int& b) {// 相当于给实参起了个别名叫a
int tmp = a;
a = b;
b = tmp;
}
int main() {
int a = 10;
int b = 20;
swap(a,b);
cout << "a = " << a << endl; // 10
cout << "b = " << b << endl; // 20
int c = 10;
int d = 20;
swap_byPointer(&c,&d);
cout << "c = " << c << endl; // 20
cout << "d = " << d << endl; // 10
int e = 10;
int f = 20;
swap_byRef(e,f);
cout << "e = " << e << endl; // 20
cout << "f = " << f << endl; // 10
return 0;
}
通过引用参数产生的效果同按地址传递是一样的
引用做函数返回值
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
示例:
#include <iostream>
#include <string>
using namespace std;
// test01和test02没有区别
int& test01() {
int a = 10;
int &b = a;
return b;
}
int& test02() {
int a = 10;
return a;
}
// test03,将局部变量变为静态变量
int& test03() {
static int a = 10;
return a;
}
int main() {
int &a_01 = test01(); // 相当于给函数内的变量a起了个别名
int &a_02 = test02();
int &a_03 = test03();
vector<int> v(1000); // 故意占用栈空间
cout << a_01 << endl; // 悬垂引用
cout << a_02 << endl; // 悬垂引用
cout << a_03 << endl; // 10
// 引用函数的返回值可以作为左值
test03() = 10000;
cout << a_03 << endl;
return 0;
}
当函数声明为返回
int&时,任何返回的int变量都会隐式转换为引用(即使未显式使用&)。其他类型同理当函数的返回值为引用类型时,是可以作为变量的左值的(也就是对函数内被引用的值重新赋值)
引用的本质
之前已经提到过引用本质,这一章会详细说明
本质:引用的本质在C++内部实现是一个指针常量
示例:
#include <iostream>
#include <string>
using namespace std;
void func(int& ref) {
ref = 100; // ref是引用,转换为*ref = 100
}
int main() {
int a = 100;
// 自动转换为int* const ref = &a; 指针常量是指针指向不可改,也说明了为什么引用不可更改
int& ref = a;
ref = 20; // 内部发现ref是引用,自动帮我们转换为:*ref = 20;
cout << "a:" << a << endl; // 20
cout << "ref:" << ref << endl; // 20
func(ref);
return 0;
}
C++推荐使用引用技术,因为语法方便,引用本质是指针常量,但所有的指针操作编译器都会帮助程序员完成
常量引用
作用:常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参
示例:
#include <iostream>
#include <string>
using namespace std;
void func(const int& ref) {
//ref += 100; // 不可修改
cout << ref << endl;
}
int main() {
//int& ref = 10; // 无法将临时值(字面量 10)直接绑定到非常量左值引用 int&
const int& ref = 10; // 常量引用可绑定到右值,非常量引用不能
cout << ref << endl; // 10
func(100);
return 0;
}
需要注意,常量引用是可以直接赋值(字面量
100、表达式结果、临时对象)的,非常量引用则会报错
函数进阶
函数默认参数
在C++中,函数的形参列表中的形参是可以有默认值的
语法:返回值类型 函数名 (参数 = 默认值) {}
示例:
#include <iostream>
#include <string>
using namespace std;
// 1. 如果某个位置参数有默认值,那从这个位置开始,后面的参数都必须有默认值
// 2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
int func_01(int a, int b = 10, int c = 20) {
return a + b + c;
}
int func_02(int a = 10, int b = 10);
int func_02(int a, int b) {
return a + b;
}
int main() {
int getFunc01 = func_01(1);
cout << getFunc01 << endl; // 31
getFunc01 = func_01(1, 2); // 原默认值b = 10,现在b = 2
cout << getFunc01 << endl; // 23
int getFunc02 = func_02();
cout << getFunc02 << endl; // 20
return 0;
}
函数占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名 (数据类型) {}
示例:
#include <iostream>
#include <string>
using namespace std;
// 占位参数
void func(int a, int) {
cout << "Hello,Im Func;" << endl;
}
int main() {
// func(10); // 报错
// func(10, "Hello"); // 错误
func(10, 10);
return 0;
}
函数重载
在基础篇中已经介绍了基本的函数重载使用,这一章将详细说明
作用:函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同,或者个数不同或者顺序不同
注意:函数的返回值不可以作为函数重载的条件
示例:
#include <iostream>
#include <string>
using namespace std;
// 同一个作用域
void func(int a, int b) {
cout << a << " ";
cout << b <<endl;
}
void func(int a, int b, int c) {
cout << a << " ";
cout << b << " ";
cout << c << endl;
}
void func(int a, string b) {
cout << a << " ";
cout << b << endl;
}
void func(string a, int b) {
cout << a << " ";
cout << b << endl;
}
//int func(int a, int b) {} // 错误
int main() {
func(1, 2); // 第一个函数
func(1, 2, 3); // 第二个函数
func(1, "hello"); // 第三个函数
func("hello", 1); // 第四个函数
return 0;
}
函数重载注意事项
- 引用作为重载条件
- 函数重载碰到函数默认参数
示例1:
#include <iostream>
#include <string>
using namespace std;
void func(int& a) {
cout << "func(int& a)函数调用了" << endl;
}
void func(const int& a) {
cout << "func(const int& a)函数调用了" << endl;
}
int main() {
int a = 10;
func(a); // func(int& a)函数调用了
func(10); // func(const int& a)函数调用了
const int b = 10;
func(b); // func(const int& a)函数调用了
return 0;
}
示例2:
#include <iostream>
#include <string>
using namespace std;
// 函数重载避免有默认值
void func(int a) {
cout << "func(int a)函数调用了" << endl;
}
void func(int a, int b = 10) {
cout << "func(int a, int b = 10)函数调oked" << endl;
}
int main() {
func(10); // 报错
return 0;
}