作用域(Scope)和生命周期(Lifetime)是 C++ 中的两个重要概念。作用域决定了变量在程序中的可见性,而生命周期决定了变量在内存中存在的时间。
作用域的基本概念
全局作用域
#include <iostream>
// 全局变量 - 全局作用域
int globalVar = 100;
const int globalConst = 200;
static int staticGlobal = 300; // 文件作用域
// 全局函数
void globalFunction() {
std::cout << "全局函数被调用" << std::endl;
}
// 命名空间作用域
namespace MyNamespace {
int namespaceVar = 400;
void namespaceFunction() {
std::cout << "命名空间函数被调用" << std::endl;
}
}
int main() {
std::cout << "=== 全局作用域演示 ===" << std::endl;
// 访问全局变量
std::cout << "globalVar: " << globalVar << std::endl;
std::cout << "globalConst: " << globalConst << std::endl;
std::cout << "staticGlobal: " << staticGlobal << std::endl;
// 调用全局函数
globalFunction();
// 访问命名空间中的变量和函数
std::cout << "MyNamespace::namespaceVar: " << MyNamespace::namespaceVar << std::endl;
MyNamespace::namespaceFunction();
// 修改全局变量
globalVar = 150;
std::cout << "修改后的 globalVar: " << globalVar << std::endl;
return 0;
}
局部作用域
#include <iostream>
int globalVar = 10; // 全局变量
void demonstrateLocalScope() {
int localVar = 20; // 函数局部变量
std::cout << "=== 局部作用域演示 ===" << std::endl;
std::cout << "函数内 localVar: " << localVar << std::endl;
std::cout << "函数内 globalVar: " << globalVar << std::endl;
// 块作用域
{
int blockVar = 30; // 块局部变量
int localVar = 40; // 隐藏外层的 localVar
std::cout << "\n块内 blockVar: " << blockVar << std::endl;
std::cout << "块内 localVar: " << localVar << std::endl; // 40,不是20
std::cout << "块内 globalVar: " << globalVar << std::endl;
// 嵌套块
{
int nestedVar = 50;
int blockVar = 60; // 隐藏外层的 blockVar
std::cout << "\n嵌套块内 nestedVar: " << nestedVar << std::endl;
std::cout << "嵌套块内 blockVar: " << blockVar << std::endl; // 60,不是30
}
// nestedVar 在这里不可访问
std::cout << "\n回到外层块 blockVar: " << blockVar << std::endl; // 30
}
// blockVar 在这里不可访问
std::cout << "\n回到函数 localVar: " << localVar << std::endl; // 20
}
void demonstrateForLoopScope() {
std::cout << "\n=== for 循环作用域 ===" << std::endl;
// C++98 风格
int i;
for (i = 0; i < 3; ++i) {
std::cout << "C++98 风格 i: " << i << std::endl;
}
std::cout << "循环后 i: " << i << std::endl; // i 仍然可访问
// C++11 风格
for (int j = 0; j < 3; ++j) {
std::cout << "C++11 风格 j: " << j << std::endl;
}
// j 在这里不可访问
// 范围 for 循环
int arr[] = {1, 2, 3};
for (int element : arr) {
std::cout << "范围 for element: " << element << std::endl;
}
// element 在这里不可访问
}
int main() {
demonstrateLocalScope();
demonstrateForLoopScope();
return 0;
}
作用域解析
#include <iostream>
int value = 100; // 全局变量
namespace Outer {
int value = 200;
namespace Inner {
int value = 300;
void demonstrateScopeResolution() {
int value = 400; // 局部变量
std::cout << "=== 作用域解析演示 ===" << std::endl;
std::cout << "局部 value: " << value << std::endl; // 400
std::cout << "Inner::value: " << Inner::value << std::endl; // 300
std::cout << "Outer::value: " << Outer::value << std::endl; // 200
std::cout << "全局 value: " << ::value << std::endl; // 100
// 使用 using 声明
{
using Outer::value; // 引入 Outer::value
std::cout << "\nusing Outer::value 后:" << std::endl;
std::cout << "value: " << value << std::endl; // 200
std::cout << "::value: " << ::value << std::endl; // 100
}
// 使用 using 指令
{
using namespace Outer;
std::cout << "\nusing namespace Outer 后:" << std::endl;
std::cout << "value: " << value << std::endl; // 400 (局部变量优先)
// std::cout << "Outer::value: " << Outer::value << std::endl; // 仍然可以显式访问
}
}
}
}
class ScopeDemo {
static int classVar;
int memberVar;
public:
ScopeDemo(int val) : memberVar(val) {}
void memberFunction() {
int localVar = 500;
std::cout << "\n=== 类作用域演示 ===" << std::endl;
std::cout << "局部变量 localVar: " << localVar << std::endl;
std::cout << "成员变量 memberVar: " << memberVar << std::endl;
std::cout << "静态成员变量 classVar: " << classVar << std::endl;
std::cout << "全局变量 value: " << ::value << std::endl;
}
static void staticFunction() {
// 静态函数只能访问静态成员
std::cout << "静态函数中的 classVar: " << classVar << std::endl;
// std::cout << memberVar; // 错误!不能访问非静态成员
}
};
int ScopeDemo::classVar = 600; // 静态成员定义
int main() {
Outer::Inner::demonstrateScopeResolution();
ScopeDemo demo(700);
demo.memberFunction();
ScopeDemo::staticFunction();
return 0;
}
C++ 对象生命周期详解
生命周期的基本概念
在C++中,对象的生命周期决定了对象何时被创建和销毁。主要有四种存储期:
- 自动存储期(Automatic Storage Duration):栈上的局部变量
- 静态存储期(Static Storage Duration):全局变量、静态变量
- 动态存储期(Dynamic Storage Duration):堆上分配的对象
- 线程存储期(Thread Storage Duration):thread_local变量
1. 自动存储期
自动存储期对象在进入作用域时构造,离开作用域时析构,遵循RAII原则。
#include <iostream>
class LifetimeDemo {
public:
int value;
LifetimeDemo(int v) : value(v) {
std::cout << "LifetimeDemo 构造: " << value << std::endl;
}
~LifetimeDemo() {
std::cout << "LifetimeDemo 析构: " << value << std::endl;
}
LifetimeDemo(const LifetimeDemo& other) : value(other.value) {
std::cout << "LifetimeDemo 拷贝构造: " << value << std::endl;
}
};
void demonstrateAutomaticLifetime() {
std::cout << "=== 自动存储期演示 ===" << std::endl;
std::cout << "进入函数" << std::endl;
LifetimeDemo obj1(1); // 自动变量
{
std::cout << "进入内层块" << std::endl;
LifetimeDemo obj2(2); // 块作用域自动变量
{
std::cout << "进入最内层块" << std::endl;
LifetimeDemo obj3(3);
std::cout << "离开最内层块" << std::endl;
} // obj3 在这里析构
std::cout << "离开内层块" << std::endl;
} // obj2 在这里析构
std::cout << "离开函数" << std::endl;
} // obj1 在这里析构
void demonstrateTemporaryLifetime() {
std::cout << "\n=== 临时对象生命周期 ===" << std::endl;
// 临时对象的生命周期
std::cout << "创建临时对象:" << std::endl;
LifetimeDemo temp = LifetimeDemo(10); // 可能的优化:直接构造
// 表达式中的临时对象
std::cout << "\n表达式中的临时对象:" << std::endl;
int result = LifetimeDemo(20).value + LifetimeDemo(30).value;
std::cout << "表达式结果: " << result << std::endl;
// 引用延长临时对象生命周期
std::cout << "\n引用延长生命周期:" << std::endl;
const LifetimeDemo& ref = LifetimeDemo(40);
std::cout << "引用的值: " << ref.value << std::endl;
std::cout << "函数结束前" << std::endl;
} // ref 引用的临时对象在这里析构
// 返回值优化演示
LifetimeDemo createObject(int value) {
std::cout << "在函数中创建对象" << std::endl;
return LifetimeDemo(value); // RVO优化
}
2. 静态存储期
静态存储期对象在程序启动时创建,程序结束时销毁。包括全局变量、静态局部变量、静态成员变量。
#include <iostream>
class StaticDemo {
public:
int value;
StaticDemo(int v) : value(v) {
std::cout << "StaticDemo 构造: " << value << std::endl;
}
~StaticDemo() {
std::cout << "StaticDemo 析构: " << value << std::endl;
}
};
// 全局对象 - 静态存储期
StaticDemo globalObj(100);
void demonstrateStaticLifetime() {
std::cout << "=== 静态存储期演示 ===" << std::endl;
// 局部静态变量 - 第一次调用时初始化
static StaticDemo localStatic(200);
static int counter = 0;
++counter;
std::cout << "函数调用次数: " << counter << std::endl;
std::cout << "局部静态对象值: " << localStatic.value << std::endl;
// 修改静态变量
localStatic.value += 10;
}
class ClassWithStatic {
public:
static StaticDemo staticMember;
static int staticValue;
static void accessStaticMember() {
std::cout << "静态成员值: " << staticMember.value << std::endl;
}
};
// 静态成员定义 - 必须在类外定义
StaticDemo ClassWithStatic::staticMember(300);
int ClassWithStatic::staticValue = 42;
// 单例模式 - 利用函数静态变量的延迟初始化
StaticDemo& getSingleton() {
static StaticDemo instance(400); // 第一次调用时初始化,线程安全(C++11)
return instance;
}
// 静态初始化顺序问题演示
StaticDemo earlyGlobal(50); // 可能在其他全局对象之前初始化
3. 动态存储期
动态存储期对象在堆上分配,需要显式管理其生命周期。现代C++推荐使用智能指针。
#include <iostream>
#include <memory>
#include <vector>
class DynamicDemo {
public:
int value;
DynamicDemo(int v) : value(v) {
std::cout << "DynamicDemo 构造: " << value << std::endl;
}
~DynamicDemo() {
std::cout << "DynamicDemo 析构: " << value << std::endl;
}
// 移动构造函数
DynamicDemo(DynamicDemo&& other) noexcept : value(other.value) {
std::cout << "DynamicDemo 移动构造: " << value << std::endl;
other.value = 0;
}
};
void demonstrateDynamicLifetime() {
std::cout << "=== 动态存储期演示 ===" << std::endl;
// 1. 原始指针 - 手动管理(不推荐)
std::cout << "原始指针管理:" << std::endl;
DynamicDemo* rawPtr = new DynamicDemo(500);
std::cout << "使用动态对象: " << rawPtr->value << std::endl;
delete rawPtr; // 手动删除,容易忘记或出错
rawPtr = nullptr;
// 2. unique_ptr - 独占所有权
std::cout << "\nunique_ptr 管理:" << std::endl;
{
std::unique_ptr<DynamicDemo> uniquePtr = std::make_unique<DynamicDemo>(600);
std::cout << "使用 unique_ptr: " << uniquePtr->value << std::endl;
// 转移所有权
std::unique_ptr<DynamicDemo> anotherPtr = std::move(uniquePtr);
if (!uniquePtr) {
std::cout << "原 unique_ptr 已无效" << std::endl;
}
std::cout << "新 unique_ptr 值: " << anotherPtr->value << std::endl;
} // anotherPtr 超出作用域,自动删除对象
// 3. shared_ptr - 引用计数管理
std::cout << "\nshared_ptr 管理:" << std::endl;
{
std::shared_ptr<DynamicDemo> sharedPtr1 = std::make_shared<DynamicDemo>(700);
std::cout << "引用计数: " << sharedPtr1.use_count() << std::endl;
{
std::shared_ptr<DynamicDemo> sharedPtr2 = sharedPtr1; // 共享所有权
std::cout << "共享后引用计数: " << sharedPtr1.use_count() << std::endl;
std::cout << "两个指针指向同一对象: " << (sharedPtr1.get() == sharedPtr2.get()) << std::endl;
} // sharedPtr2 析构,引用计数减1
std::cout << "内层作用域结束后引用计数: " << sharedPtr1.use_count() << std::endl;
} // sharedPtr1 析构,引用计数为0,对象被删除
// 4. weak_ptr - 打破循环引用
std::cout << "\nweak_ptr 打破循环引用:" << std::endl;
{
std::shared_ptr<DynamicDemo> shared = std::make_shared<DynamicDemo>(800);
std::weak_ptr<DynamicDemo> weak = shared;
std::cout << "shared 引用计数: " << shared.use_count() << std::endl;
std::cout << "weak 是否过期: " << weak.expired() << std::endl;
if (auto locked = weak.lock()) { // 尝试获取 shared_ptr
std::cout << "通过 weak_ptr 访问对象: " << locked->value << std::endl;
}
shared.reset(); // 释放 shared_ptr
std::cout << "shared.reset() 后 weak 是否过期: " << weak.expired() << std::endl;
}
// 5. 数组的动态分配
std::cout << "\n动态数组管理:" << std::endl;
{
// 使用 unique_ptr 管理数组
std::unique_ptr<DynamicDemo[]> arrayPtr(new DynamicDemo[3]{901, 902, 903});
for (int i = 0; i < 3; ++i) {
std::cout << "数组元素[" << i << "]: " << arrayPtr[i].value << std::endl;
}
} // 数组自动释放
// 6. 自定义删除器
std::cout << "\n自定义删除器:" << std::endl;
{
auto customDeleter = [](DynamicDemo* p) {
std::cout << "使用自定义删除器删除对象: " << p->value << std::endl;
delete p;
};
std::unique_ptr<DynamicDemo, decltype(customDeleter)>
customPtr(new DynamicDemo(999), customDeleter);
}
}
// 内存泄漏演示(仅用于教学,实际代码中避免)
void memoryLeakDemo() {
std::cout << "\n=== 内存泄漏演示 ===" << std::endl;
// 错误:忘记释放内存
DynamicDemo* leaked = new DynamicDemo(1000);
// 没有 delete leaked; - 这会导致内存泄漏
std::cout << "对象被创建但永远不会被删除(内存泄漏)" << std::endl;
}
4. 线程存储期
线程存储期对象在线程开始时创建,线程结束时销毁。
#include <iostream>
#include <thread>
#include <vector>
class ThreadDemo {
public:
int value;
ThreadDemo(int v) : value(v) {
std::cout << "ThreadDemo 构造: " << value
<< " (线程ID: " << std::this_thread::get_id() << ")" << std::endl;
}
~ThreadDemo() {
std::cout << "ThreadDemo 析构: " << value
<< " (线程ID: " << std::this_thread::get_id() << ")" << std::endl;
}
};
// thread_local 变量
thread_local ThreadDemo tlsObj(2000);
void threadFunction(int id) {
std::cout << "\n线程 " << id << " 开始执行" << std::endl;
// 访问 thread_local 变量
tlsObj.value += id;
std::cout << "线程 " << id << " 中的 tlsObj 值: " << tlsObj.value << std::endl;
// 局部 thread_local 变量
thread_local ThreadDemo localTLS(3000 + id);
localTLS.value += 100;
std::cout << "线程 " << id << " 中的局部 tlsObj 值: " << localTLS.value << std::endl;
std::cout << "线程 " << id << " 结束执行" << std::endl;
}
void demonstrateThreadLifetime() {
std::cout << "=== 线程存储期演示 ===" << std::endl;
std::vector<std::thread> threads;
// 创建多个线程
for (int i = 1; i <= 3; ++i) {
threads.emplace_back(threadFunction, i);
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
std::cout << "\n所有线程执行完毕" << std::endl;
}
5. 主函数和测试
// 完整的测试主函数
int main() {
std::cout << "===== C++ 对象生命周期演示开始 =====" << std::endl;
// 1. 自动存储期
demonstrateAutomaticLifetime();
demonstrateTemporaryLifetime();
// RVO演示
std::cout << "\n=== 返回值优化演示 ===" << std::endl;
auto obj = createObject(50);
std::cout << "返回的对象值: " << obj.value << std::endl;
// 2. 静态存储期
std::cout << "\nmain 开始执行" << std::endl;
// 多次调用函数,观察静态变量行为
for (int i = 0; i < 3; ++i) {
std::cout << "\n第 " << (i + 1) << " 次调用:" << std::endl;
demonstrateStaticLifetime();
}
// 访问类静态成员
std::cout << "\n访问类静态成员:" << std::endl;
ClassWithStatic::accessStaticMember();
// 访问单例
std::cout << "\n访问单例:" << std::endl;
StaticDemo& singleton1 = getSingleton();
StaticDemo& singleton2 = getSingleton();
std::cout << "单例地址相同: " << (&singleton1 == &singleton2) << std::endl;
// 3. 动态存储期
demonstrateDynamicLifetime();
// 4. 线程存储期
demonstrateThreadLifetime();
// 5. 内存泄漏演示(教学用)
memoryLeakDemo();
std::cout << "\n===== main 函数结束 =====" << std::endl;
return 0;
}
// 程序结束时,静态对象按构造的逆序析构
关键要点总结
生命周期管理最佳实践
- 优先使用自动存储期:栈上对象自动管理,性能好,异常安全
- 避免原始指针:使用智能指针管理动态内存
- 理解移动语义:减少不必要的拷贝,提高性能
- 注意静态初始化顺序:全局静态对象的初始化顺序不确定
- 使用RAII原则:资源获取即初始化,确保资源正确释放
常见陷阱
- 悬空指针:指向已销毁对象的指针
- 内存泄漏:动态分配的对象未正确释放
- 静态初始化顺序问题:全局对象依赖关系导致的问题
- 循环引用:shared_ptr 循环引用导致内存泄漏
性能考虑
- 自动存储期 > 静态存储期 > 动态存储期(一般情况)
- 栈分配比堆分配快
- 智能指针有轻微性能开销,但安全性收益巨大