C++内存布局以及常用关键字
C++的内存空间
代码存储区域:常量区、代码区、静态区(全局区)、堆区、栈区
栈区向下增长,堆区向上增长。栈由系统管理,没有内存碎片,每个元素之间都是连续的,大小比较小,8k,可以修改系统参数,堆区存储动态开辟的变量。
还有一个内核空间,但是它不与用户直接交互(内核区)。
常用关键字
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
申明一个常量,在编译期间可以进行类型检查,确保其值在程序运行期间不能被修改。
它还可以应用于指针、函数形参和成员函数等不同场景。
基本用法示例
定义常量:
const int a = 10; // 定义一个整型常量 a = 20; // 错误,尝试修改常量的值
在编译阶段,如果试图修改
a
的值,会报错:
“assignment of read-only variable ‘a’”指针中的
const
:const
在指针上下文中非常灵活,具体含义取决于其位置(需要注意理解哦:指针指向的值不可变:
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; // 错误
函数参数中的
const
:传值参数: 如果函数参数是按值传递,
const
修饰没有意义,因为传入的值本身就是副本,修改不会影响原始变量。传引用参数: 使用
const
修饰引用,可以保护原始数据:cpp复制代码void print(const std::string& str) { // str 是只读的 std::cout << str << std::endl; }
好处:
- 避免拷贝,提高性能。
- 防止函数内部修改传入的数据。
成员函数中的
const
: 当一个成员函数后面添加const
修饰符时,表示该函数不会修改对象的任何成员变量:class MyClass { private: int value; public: int getValue() const { return value; // 只读操作 } };
如果尝试在
const
成员函数中修改任何非mutable
的成员变量,编译会报错。const
和数组: 声明只读的数组:const int arr[] = {1, 2, 3}; // 数组的内容是只读的 arr[0] = 10; // 错误
mutable
mutable为可变的,易变的跟C++中的const是反义词。被mutable修饰的变量(mutable智能用于修饰类的非静态数据成员),将永远处于可变的状态, 即使在一个const函数中
mutable
的用法示例
- 普通类的
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
对象是const
,logCount
仍然可以在logMessage
函数中被修改。
- 在
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
管理变量作用域和生命周期。 - 优先使用
inline
和constexpr
替代宏定义。 - 用
const
提升代码的安全性和优化性能。 - 谨慎使用
mutable
,仅在必要时放宽const
限制。 - 用
typedef
或现代using
提高类型定义的灵活性。
- 用
村上春树说:“跑步时我什么都不想,只是奔跑。跑步时我只感受跑步本身。”
编程时亦如此,专注写好每段代码,感受语言的精妙与力量。