C++内存布局以及常用关键字


C++内存布局以及常用关键字

C++的内存空间

代码存储区域:常量区、代码区、静态区(全局区)、堆区、栈区

栈区向下增长,堆区向上增长。栈由系统管理,没有内存碎片,每个元素之间都是连续的,大小比较小,8k,可以修改系统参数,堆区存储动态开辟的变量。

还有一个内核空间,但是它不与用户直接交互(内核区)。

C++的内存空间

常用关键字

static

修饰局部变量:局部变量的存储区域改变、变为静态区,生命周期改为程序结束才销毁。

修饰全局变量:全局变量只能在本文件中访问,不能在其它文件中访问, extern 外部声明也不可以。

修饰成员变量:静态成员变量不属于实体的类对象,要在类外初始化

修饰成员函数:静态函数属于类不属于类对象 需要通过类作用域调用 函数无this指针(静态成员函数仅能访问静态的数据成员,不能访问非静态的数据成员,也不能访问非静态的成员函数)

#define

带参数的宏定义可以减少函数调用的开销,在运行时只是简单的展开。

#include <iostream>
#define SQUARE(x) ((x) * (x))

int main() {
    int a = 5;
    std::cout << "Square of a: " << SQUARE(a) << std::endl;
    std::cout << "Square of a+1: " << SQUARE(a + 1) << std::endl;
    return 0;
}

代码会被展开为:

std::cout << "Square of a: " << ((a) * (a)) << std::endl;
std::cout << "Square of a+1: " << ((a + 1) * (a + 1)) << std::endl;

同样逻辑使用函数实现:

inline int square(int x) {
    return x * x;
}

虽然 inline 函数也可能避免函数调用开销,但其行为是在编译阶段由编译器决定;而宏定义是在预处理阶段直接展开。因此,宏展开比函数调用更加简单直接。

const

申明一个常量,在编译期间可以进行类型检查,确保其值在程序运行期间不能被修改。

它还可以应用于指针、函数形参和成员函数等不同场景。

基本用法示例

  1. 定义常量

    const int a = 10; // 定义一个整型常量
    a = 20;           // 错误,尝试修改常量的值

    在编译阶段,如果试图修改 a 的值,会报错:
    “assignment of read-only variable ‘a’”

  2. 指针中的 constconst 在指针上下文中非常灵活,具体含义取决于其位置(需要注意理解哦:

    • 指针指向的值不可变

      const int* ptr = &a; // 指针指向的内容是只读的
      *ptr = 20;           // 错误
      ptr = &b;            // 正确,可以修改指针本身
    • 指针本身不可变

      int* const ptr = &a; // 指针本身是常量,不能指向其他地址
      *ptr = 20;           // 正确
      ptr = &b;            // 错误
    • 值和指针均不可变

      const int* const ptr = &a; // 值和指针都不可更改
      *ptr = 20;                // 错误
      ptr = &b;                 // 错误
  3. 函数参数中的 const

    • 传值参数: 如果函数参数是按值传递,const 修饰没有意义,因为传入的值本身就是副本,修改不会影响原始变量。

    • 传引用参数: 使用 const 修饰引用,可以保护原始数据:

      cpp复制代码void print(const std::string& str) {
          // str 是只读的
          std::cout << str << std::endl;
      }

      好处:

      • 避免拷贝,提高性能。
      • 防止函数内部修改传入的数据。
  4. 成员函数中的 const: 当一个成员函数后面添加 const 修饰符时,表示该函数不会修改对象的任何成员变量

    class MyClass {
    private:
        int value;
    public:
        int getValue() const {
            return value; // 只读操作
        }
    };

    如果尝试在 const 成员函数中修改任何非 mutable 的成员变量,编译会报错。

  5. const 和数组: 声明只读的数组:

    const int arr[] = {1, 2, 3}; // 数组的内容是只读的
    arr[0] = 10;                 // 错误

mutable

mutable为可变的,易变的跟C++中的const是反义词。被mutable修饰的变量(mutable智能用于修饰类的非静态数据成员),将永远处于可变的状态, 即使在一个const函数中

mutable 的用法示例

  1. 普通类的 mutable 成员
#include <iostream>
#include <string>

class Logger {
private:
    mutable int logCount;  // 用于统计日志记录次数,可变
public:
    Logger() : logCount(0) {}
    
    void logMessage(const std::string& message) const {
        ++logCount;  // 即使在 const 函数中,也允许修改
        std::cout << "Log: " << message << " (Log #" << logCount << ")\n";
    }
};

int main() {
    const Logger logger;  // 声明一个 const对象
    logger.logMessage("Program started");  // 依然可以修改logCount
    logger.logMessage("Processing data");
    return 0;
}

输出

Log: Program started (Log #1)
Log: Processing data (Log #2)

在上面的例子中:

  • 类的 logCount 成员变量是用 mutable 修饰的。
  • 即使 Logger 对象是 constlogCount 仍然可以在 logMessage 函数中被修改。

  1. const 对象中的 mutable 使用
#include <iostream>

class MyClass {
private:
    mutable int counter;  // 可变成员
public:
    MyClass() : counter(0) {}

    void increment() const {
        ++counter;  // 修改可变成员
    }

    int getCounter() const {
        return counter;
    }
};

int main() {
    const MyClass obj;  // 声明 const 对象
    obj.increment();    // 修改 mutable 成员
    obj.increment();
    std::cout << "Counter: " << obj.getCounter() << std::endl;
    return 0;
}

输出

Counter: 2

使用 mutable 时应明确需求,通常用于辅助性的、不影响对象逻辑状态的数据成员。例如:

  • 缓存计算值。
  • 日志或调试计数器。

const_cast 的对比

  • mutable 是在类设计阶段明确指定的“可变性”。
  • const_cast 是临时移除 const 限制,常用于特殊需求。

typedef

用途: 1、为名称复杂的变量创建别名; 2、创建与平台无关的变量,当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改。 另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健(虽然用宏有时也可以完成以上的用途)。 比如:size_t的介绍

// 在vs中,short 2个字节,int 4个,long也是4个,long long 8个
typedef short int16_t;
typedef int int32_t;
typedef long long int64_t;
// 在Linux下,short 2个,int 4 个,long 8个,long long 也是8个
typedef short int16_t;
typedef int int32_t;
typedef long int64_t;

所以在程序源码中,只使用这些在头文件中声明的别名。 参考:typedef用法详解

总结与建议

  • 内存管理:明确堆和栈的用途,动态内存需及时释放,避免内存泄漏。
  • 关键字使用
    • static 管理变量作用域和生命周期。
    • 优先使用 inlineconstexpr 替代宏定义。
    • const 提升代码的安全性和优化性能。
    • 谨慎使用 mutable,仅在必要时放宽 const 限制。
    • typedef 或现代 using 提高类型定义的灵活性。

村上春树说:“跑步时我什么都不想,只是奔跑。跑步时我只感受跑步本身。”

编程时亦如此,专注写好每段代码,感受语言的精妙与力量。


文章作者: AllenMirac
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 AllenMirac !
  目录