在 C++ 中,变量的默认值取决于其存储类别、声明位置和类型。理解这些默认值规则对于编写安全可靠的代码至关重要。以下是各种类型数据声明时的默认值规则:
一、基本数据类型的默认值
1. 全局作用域(静态存储期)
int globalInt; // 0
double globalDouble; // 0.0
char globalChar; // '\0' (ASCII 0)
bool globalBool; // false
2. 局部变量(自动存储期)
void func() {
int localInt; // 未初始化(随机值)
double localDouble; // 未初始化(随机值)
char localChar; // 未初始化(随机值)
bool localBool; // 未初始化(随机值)
}
3. 静态变量(静态存储期)
void func() {
static int staticInt; // 0
static double staticDouble; // 0.0
static char staticChar; // '\0'
static bool staticBool; // false
}
二、指针类型的默认值
1. 原始指针(Raw Pointer)
int* globalPtr; // nullptr(全局)
void* globalVoidPtr; // nullptr(全局)
void func() {
int* localPtr; // 未初始化(野指针)
int* staticPtr; // nullptr(函数内静态)
static int* staticLocalPtr; // nullptr
}
2. 智能指针(C++11 起)
#include <memory>
std::unique_ptr<int> globalUnique; // 空指针(nullptr)
std::shared_ptr<int> globalShared; // 空指针(nullptr)
std::weak_ptr<int> globalWeak; // 空指针(nullptr)
void func() {
std::unique_ptr<int> localUnique; // 空指针
std::shared_ptr<int> localShared; // 空指针
std::weak_ptr<int> localWeak; // 空指针
}
三、对象类型的默认值
1. 内置类型对象
std::string globalStr; // 空字符串 ""
std::vector<int> globalVec;// 空向量
void func() {
std::string localStr; // 空字符串 ""
std::vector<int> localVec; // 空向量
}
2. 自定义类对象
取决于类定义:
- 如果有用户定义的默认构造函数,则调用它
- 否则进行默认初始化(基本类型成员不初始化)
class MyClass {
public:
int a; // 默认不初始化
double b; // 默认不初始化
};
class SafeClass {
public:
int x = 0; // C++11 成员初始化
std::string s;
SafeClass() : y(0) {} // 初始化列表
private:
int y;
};
MyClass globalObj; // a 和 b 初始化为 0(全局)
SafeClass globalSafe; // x=0, s="", y=0
void func() {
MyClass localObj; // a 和 b 未初始化
SafeClass localSafe; // x=0, s="", y=0
}
四、数组类型的默认值
1. 基本类型数组
int globalArray[5]; // 所有元素为 0
char globalCharArr[10]; // 所有元素为 '\0'
void func() {
int localArray[5]; // 所有元素未初始化
static int staticArray[5]; // 所有元素为 0
}
2. 对象数组
std::string globalStrArr[3]; // 三个空字符串
MyClass globalObjArr[2]; // MyClass 对象,成员值取决于位置
void func() {
std::string localStrArr[2]; // 两个空字符串
MyClass localObjArr[3]; // 成员未初始化
}
五、特殊类型的默认值
1. 引用类型
int globalInt = 5;
int& globalRef = globalInt; // 必须初始化
void func() {
int localInt = 10;
int& localRef = localInt; // 必须初始化
// int& invalidRef; // 错误:引用必须初始化
}
2. 常量类型
const int globalConst; // 错误:常量必须初始化(全局)
constexpr int globalExpr = 42; // 必须初始化
void func() {
const int localConst; // 错误:常量必须初始化
constexpr int localExpr = 10; // 必须初始化
}
3. 枚举类型
enum Color { RED, GREEN, BLUE };
Color globalColor; // 0 (RED)
void func() {
Color localColor; // 未初始化(随机值)
}
六、默认值规则总结表
类型 | 全局/命名空间作用域 | 静态局部变量 | 自动局部变量 | 类成员变量 |
---|---|---|---|---|
基本类型 | 零初始化 | 零初始化 | 未初始化 | 取决于类定义 |
原始指针 | nullptr | nullptr | 未初始化 | 未初始化 |
unique_ptr | 空 | 空 | 空 | 空 |
shared_ptr | 空 | 空 | 空 | 空 |
weak_ptr | 空 | 空 | 空 | 空 |
STL 容器 | 空 | 空 | 空 | 空 |
自定义类 | 调用默认构造函数 | 调用默认构造函数 | 调用默认构造函数 | 取决于类定义 |
数组(基本类型) | 所有元素零初始化 | 所有元素零初始化 | 所有元素未初始化 | 取决于类定义 |
数组(对象类型) | 调用默认构造函数 | 调用默认构造函数 | 调用默认构造函数 | 取决于类定义 |
引用 | 必须初始化 | 必须初始化 | 必须初始化 | 必须初始化 |
常量 | 必须初始化 | 必须初始化 | 必须初始化 | 必须初始化 |
七、最佳实践与安全建议
1. 始终显式初始化变量
// 良好的初始化实践
int value = 0;
double sum{};
std::string name = "Unknown";
auto ptr = std::make_unique<MyClass>();
int* rawPtr = nullptr;
2. 使用 C++11 统一初始化语法
int x{}; // 值初始化 (0)
double d{}; // 0.0
int arr[5]{}; // 全为 0
MyClass obj{}; // 值初始化
3. 类设计原则
class SafeObject {
public:
// 成员初始化列表
SafeObject() : count(0), data(nullptr), name("default") {}
// C++11 类内成员初始化
int value = 0;
std::string id = "ID000";
double ratio{1.0};
private:
int count;
char* data;
std::string name;
};
4. 智能指针使用准则
void processResource() {
// 优先使用 make_unique/make_shared
auto unique = std::make_unique<Resource>();
auto shared = std::make_shared<Resource>();
// 传递智能指针
useResource(std::move(unique)); // 转移所有权
shareResource(shared); // 共享所有权
// 观察对象而不拥有
Resource* observer = shared.get();
}
5. 避免未初始化风险的技巧
// 编译器警告选项
// GCC/clang: -Wall -Wextra -Werror
// MSVC: /W4 /WX
// 静态分析工具
// clang-tidy: check cppcoreguidelines-init-variables
// 运行时检测
#ifdef _DEBUG
#define SAFE_INIT(var, init) var = init
#else
#define SAFE_INIT(var, init)
#endif
int main() {
int potentiallyUninitialized;
SAFE_INIT(potentiallyUninitialized, 0);
// ...
}
八、不同类型初始化的性能影响
初始化方式 | 性能特点 | 适用场景 |
---|---|---|
零初始化 | 低开销 | 基本类型全局/静态变量 |
默认初始化 | 最低开销 | POD 类型局部变量 |
值初始化 | 中等开销 | 安全初始化所有类型 |
构造函数初始化 | 取决于构造函数复杂度 | 类对象 |
智能指针创建 | 动态分配开销(shared_ptr更高) | 动态资源管理 |
总结
- 全局/静态变量:基本类型零初始化,对象调用默认构造函数
- 局部自动变量:基本类型未初始化(危险!),对象调用默认构造函数
- 智能指针:总是初始化为空状态(nullptr)
- 原始指针:全局为 nullptr,局部未初始化(高危野指针)
- STL 容器:总是初始化为空状态
- 类对象:依赖构造函数,成员变量默认不初始化(除非指定)
- 引用和常量:必须显式初始化
安全编程的关键原则:
- 永远不要假设未显式初始化的变量有确定值
- 对局部基本类型变量始终显式初始化
- 优先使用智能指针而非原始指针
- 在类设计中使用成员初始化和初始化列表
- 开启编译器警告并使用静态分析工具
通过遵循这些规则和实践,可以避免未定义行为,提高代码的健壮性和安全性。