N

Norlcayn的博客

A text-focused Halo theme

  • 首页
  • 文章
  • 默认分类
  • 关于
主页 CPP核心内容
文章

CPP核心内容

发表于 2025-06-28 更新于 昨天
作者 Administrator
38~49 分钟 阅读

内存分区模型

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;
}
CPP
自学系列 编程语言 CPP核心
许可协议:  CC BY 4.0
分享

相关文章

6月 28, 2025

CPP错误案例

数组倒序 将一组数组倒序排序,如{1,2,3,4,5}=>{5,4,3,2,1} 错误写法: #include <iostream> using namespace std; int main() { // 数组倒序 int arr_02[] = { 1,2,3,4,5 }; length =

6月 28, 2025

CPP基础案例

通讯录管理系统 系统需求 通讯录是一个可以记录亲人、好友信息的工具 本案例主要利用C++来实现基础的通讯录管理系统 系统中需要实现的主要功能如下: 添加联系人:向通讯录中添加新人,信息包括姓名、性别、年龄、联系电话、家庭住址,最多记录1000人 显示联系人:显示通讯录中所有联系人信息 删除联系人:按

6月 28, 2025

CPP文件操作

C++文件操作 关键字:ifstream、ofstream、ios:: 程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放 通过文件可以将数据持久化 C++中对文件操作需要包含头文件 文件类型分为两种: 文本文件 - 文件以文本的ASCII码形式存储在计算机中 二进制文件 - 文件以文本

下一篇

CPP面向对象

上一篇

CPP基础内容

最近更新

  • Hello Halo
  • 状态机
  • 数据表设计规范
  • 缓存优化
  • 事务失效

热门标签

Halo 异常处理 Redisson 并行 Vue 并发 递归 请求限流 SpringAMQP 类加载器

目录

©2026 Norlcayn的博客. 保留部分权利。

使用 Halo 主题 Chirpy