程序的内存分配

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int global_data = 1;
static int global_static_data = 1;

int* stack_data()
{
int d = 2;
return &d;
}

int * heap_data()
{
int* e = new int(6); // 程序员手动申请内存,分配在堆区
return e;
}

int static_data()
{
static int sti = 1;
return sti;
}

int main() {
// int* stack_ptr = stack_data();
// cout << "局部变量(栈区)访问: " << *stack_ptr << endl; // 错误,因为栈区内存被释放了

int* heap_ptr = heap_data();
cout << "局部变量(堆区)访问: " << *heap_ptr << endl; // 正确

int static_local_data = static_data();
int local_data = 8;
cout << "static_local_data: " << (uintptr_t)&static_local_data << endl;
cout << "static_global_data: " <<(uintptr_t) &global_static_data << endl;
cout << "global_data: " << (uintptr_t)&global_data << endl;
cout << "local_data: " << (uintptr_t)&local_data << endl;
return 0;
}

局部变量(堆区)访问: 6
static_local_data: 140724477297976
static_global_data: 94198146936852
global_data: 94198146936848
local_data: 140724477297980

在C++中,内存通常被划分为几个不同的区域,每个区域都有其特定的用途。以下是C++中常见的内存区域及其特点:

  1. 栈区(Stack)
    特点
    ◦ 栈区用于存储局部变量、函数参数、函数调用的返回地址等。
    ◦ 内存分配和释放由编译器自动管理,遵循“后进先出”(LIFO)的原则。
    ◦ 栈区的内存分配速度快,但大小有限,通常较小(几MB)。
    ◦ 当函数调用结束时,栈上的局部变量会自动被销毁。
    示例

    void foo() {
    int x = 10; // x 分配在栈上
    } // x 在函数结束时自动销毁
  2. 堆区(Heap)
    特点
    ◦ 堆区用于动态内存分配,程序员可以手动控制内存的分配和释放。
    ◦ 堆区的内存分配速度较慢,但大小通常较大(受系统内存限制)。
    ◦ 内存的分配和释放需要通过 newdelete(或 mallocfree)来手动管理。
    ◦ 如果不手动释放内存,可能会导致内存泄漏。
    示例

    int* p = new int(10); // 在堆上分配一个整数
    delete p; // 手动释放内存
  3. 全局区(Global/Static Area)
    特点
    ◦ 全局区用于存储全局变量和静态变量(包括静态局部变量和静态类成员变量)。
    ◦ 全局变量和静态变量的生命周期贯穿整个程序的运行时间。
    ◦ 全局区在程序启动时分配内存,在程序结束时释放内存。
    ◦ 全局区通常分为两个部分:data 段(已初始化的全局/静态变量)和 bss 段(未初始化的全局/静态变量)。
    示例

    int globalVar = 10; // 全局变量,分配在全局区
    void foo() {
    static int staticVar = 20; // 静态局部变量,分配在全局区
    }
  4. 代码区(Text/Code Segment)
    特点
    ◦ 代码区用于存储程序的二进制代码(即编译后的机器指令)。
    ◦ 代码区通常是只读的,防止程序在运行时意外修改指令。
    ◦ 代码区的大小在编译时确定,且在程序运行期间不会改变。
    示例

    void bar() {
    // 函数的代码存储在代码区
    }
  5. 常量区(Constant Area)
    特点
    ◦ 常量区用于存储字符串常量和全局常量。
    ◦ 常量区通常是只读的,防止程序在运行时修改常量。
    ◦ 常量区的内存分配在程序启动时完成,在程序结束时释放。
    示例

    const char* str = "Hello, World!"; // 字符串常量存储在常量区

总结:
栈区:用于局部变量和函数调用,自动管理,速度快,大小有限。
堆区:用于动态内存分配,手动管理,速度慢,大小较大。
全局区:用于全局变量和静态变量,生命周期贯穿整个程序。
代码区:用于存储程序的二进制代码,只读。
常量区:用于存储字符串常量和全局常量,只读。

类和对象

链式编程

链式编程是一种编程风格,它允许你将多个函数调用链接在一起,形成一个连续的表达式。这种风格可以使代码更加简洁和易读。

需要注意的是,为了实现链式编程,函数需要返回一个引用(&)而不是值传递(即新建一个对象,将*this复制到新对象,然后返回)。

例如,下面是一个简单的链式编程示例:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class circle
{
private:
static void info1()
{
cout << "private visit!" << endl;
}

public:
double r;
static constexpr double pi = 3.1415926;
circle(double _r) : r(_r) {}; // 构造函数的简化写法

~circle()
{
cout << "byebye" << endl;
}

double calc()
{
return this->pi * r;
}

static void info()
{
cout << "Im circle!" << endl;
info1();
}

circle &addCircle(circle to_add) // 这边得返回引用,而不是返回新的对象(值返回)
{
this->r += to_add.r;
return *this;
}
};

int main()
{

circle mycicle(10);
circle newcircle(10);
cout << mycicle.calc() << endl;

mycicle.info();
circle ans = mycicle.addCircle(newcircle).addCircle(newcircle).addCircle(newcircle); // 支持链式成员函数
cout << ans.r << " " << mycicle.r << endl; // ans.r和mycicle.r是相等的,因为addCircle返回的是引用。如果不返回引用,则不一定相等
return 0;
}

如果返回的是新建对象,那么每次调用都会新建一个对象,导致内存浪费。当然,如果业务正好需要新建对象,那么返回新建对象也是可以的。

circle addCircle(circle to_add) // 返回对象会导致链式编程出现意料之外的结果
cout << ans.r << " " << mycicle.r << endl; // 输出为40 20

circle& addCircle(circle to_add) // 返回对象会导致链式编程出现意料之外的结果
cout << ans.r << " " << mycicle.r << endl; // 输出为40 40

空类是否占用内存?

在C++中,空类的大小为1字节。这是因为空类没有成员变量,但是为了保持内存地址的唯一性,编译器会为空类分配一个字节的空间。这个字节被称为“空字节”,它并不存储任何有用的数据,只是为了防止两个空类的内存地址相同。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class Empty
{
};

int main()
{
Empty e;
cout << sizeof(e) << endl;// 空结构体也占1个字节(占位),为了保持内存空间地址不重复
return 0;
}