✅ C++ 变量、引用、指针、智能指针 详细对比讲解(Visual Studio 版)

以下内容使用 C++17 标准,在 Visual Studio 2022 中可直接编译运行。


1. 变量(Variable)

定义:变量是内存中一块具有名称的存储空间,用于存放数据。

#include <iostream>
using namespace std;

int main() {
    // 基本变量
    int a = 10;                    // 自动类型(栈上分配)
    const int b = 20;              // 常量,初始化后不可修改
    constexpr int c = 30;          // 编译期常量(C++11)

    // 现代 C++ 类型推导
    auto x = 42;                   // int
    auto y = 3.14;                 // double
    auto str = "Hello";            // const char*

    cout << "a = " << a << endl;
    return 0;
}

特点

  • 默认在栈上分配,函数结束自动释放。
  • const 保护只读,constexpr 编译期求值(性能更高)。

2. 引用(Reference)

定义:引用是变量的别名,必须初始化,一旦绑定不可更改。

void swap(int& x, int& y) {    // 引用传参(高效,无拷贝)
    int temp = x;
    x = y;
    y = temp;
}

int main() {
    int a = 10;
    int b = 20;
    
    cout << "交换前: a=" << a << ", b=" << b << endl;
    swap(a, b);
    cout << "交换后: a=" << a << ", b=" << b << endl;

    // 常量引用(推荐用于只读参数)
    const string& s = "Hello World";   // 绑定临时对象

    // 右值引用(移动语义核心)
    string str1 = "Hello";
    string str2 = std::move(str1);     // str1 变为合法但未指定状态

    return 0;
}

引用 vs 值传递

  • 值传递:拷贝一份数据,修改不影响原变量。
  • 引用传递:直接操作原变量,高效(尤其大对象)。

3. 指针(Raw Pointer)

定义:指针存储的是内存地址

int main() {
    int value = 100;
    int* p = &value;           // p 指向 value 的地址

    cout << "value = " << value << endl;
    cout << "p 的地址 = " << p << endl;
    cout << "*p 的值 = " << *p << endl;   // 解引用

    *p = 999;                  // 通过指针修改原变量
    cout << "修改后 value = " << value << endl;

    // 数组与指针
    int arr[5] = {1, 2, 3, 4, 5};
    int* ptr = arr;            // 数组名就是首元素地址

    for (int i = 0; i < 5; i++) {
        cout << *(ptr + i) << " ";   // 指针算术
    }
    cout << endl;

    // 动态内存分配(容易出错!)
    int* dynamic = new int(42);
    cout << "*dynamic = " << *dynamic << endl;
    delete dynamic;            // 必须手动释放!!!

    // 空指针
    int* nullPtr = nullptr;
    if (nullPtr == nullptr) {
        cout << "空指针\n";
    }

    return 0;
}

Raw Pointer 问题

  • 容易内存泄漏(忘记 delete
  • 野指针、悬挂指针
  • 双重释放
  • 异常安全差

4. 智能指针(Smart Pointer)—— 现代 C++ 推荐

智能指针基于 RAII 思想,自动管理内存。

(1)std::unique_ptr(独占所有权)
#include <memory>

int main() {
    // 基本使用
    std::unique_ptr<int> up1(new int(100));
    std::unique_ptr<int> up2 = std::make_unique<int>(200);  // 推荐写法

    cout << "*up1 = " << *up1 << endl;

    // 不能拷贝,只能移动
    // auto up3 = up1;              // 错误!
    auto up3 = std::move(up1);      // 所有权转移

    if (!up1) {
        cout << "up1 已失效\n";
    }

    // 自定义删除器(可选)
    auto deleter = [](int* p) {
        cout << "自定义删除: " << *p << endl;
        delete p;
    };
    std::unique_ptr<int, decltype(deleter)> up4(new int(999), deleter);

    return 0;   // 自动释放内存
}
(2)std::shared_ptr(共享所有权)
int main() {
    auto sp1 = std::make_shared<int>(42);
    {
        auto sp2 = sp1;           // 引用计数 +1
        auto sp3 = sp2;           // 引用计数 +1
        
        cout << "sp1 use_count = " << sp1.use_count() << endl;  // 3
    } // sp2、sp3 析构,引用计数 -2

    cout << "sp1 use_count = " << sp1.use_count() << endl;  // 1

    return 0;   // sp1 析构时释放内存
}
(3)std::weak_ptr(弱引用,不影响计数)
int main() {
    auto sp = std::make_shared<int>(100);
    std::weak_ptr<int> wp = sp;

    cout << "sp use_count = " << sp.use_count() << endl;   // 1

    if (auto locked = wp.lock()) {   // 尝试提升为 shared_ptr
        cout << "weak_ptr 提升成功,值 = " << *locked << endl;
    }

    sp.reset();   // 释放 shared_ptr

    if (wp.expired()) {
        cout << "weak_ptr 指向的对象已销毁\n";
    }

    return 0;
}

完整对比总结

特性 变量 引用 原始指针 unique_ptr shared_ptr
所有权 - 别名 手动管理 独占 共享
内存自动释放
可拷贝 不可 不可(只能移动)
性能 最高 很高 中(引用计数)
线程安全 - - 部分安全
推荐场景 普通变量 函数参数/返回值 底层代码 独占资源 共享对象

Visual Studio 调试技巧

  1. 在变量/指针上右键 → 添加监视
  2. 使用 诊断工具 查看内存泄漏
  3. 开启 Address Sanitizer(项目属性 → C/C++ → Sanitizers)

推荐现代 C++ 写法

  • 优先使用 std::make_unique / std::make_shared
  • 能用引用就不用指针
  • 能用 unique_ptr 就不用 shared_ptr

✅ 完整示例:使用智能指针管理动态数组和对象的类

以下是一个现代 C++ 的完整示例,展示如何在类中合理使用智能指针管理动态资源。

示例功能

  • Person 类(普通对象)
  • Team 类:使用 std::unique_ptr 管理动态对象数组
  • 使用 std::shared_ptr 实现共享所有权(多人可引用同一个 Person
  • 使用 std::weak_ptr 避免循环引用
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <algorithm>

class Person {
public:
    Person(const std::string& name, int age) 
        : name_(name), age_(age) {
        std::cout << "[Person] 构造: " << name << "\n";
    }

    ~Person() {
        std::cout << "[Person] 析构: " << name_ << "\n";
    }

    void introduce() const {
        std::cout << "我是 " << name_ << ", " << age_ << "岁。\n";
    }

    void setAge(int age) { age_ = age; }
    std::string getName() const { return name_; }

private:
    std::string name_;
    int age_;
};

// ======================== 使用 unique_ptr 管理动态数组 ========================
class Team {
public:
    // 使用 unique_ptr 管理动态 Person 对象数组
    explicit Team(size_t capacity) 
        : members_(std::make_unique<std::unique_ptr<Person>[]>(capacity)),
          capacity_(capacity), size_(0) {}

    // 添加成员(转移所有权)
    void addMember(std::unique_ptr<Person> person) {
        if (size_ >= capacity_) {
            std::cout << "队伍已满,无法添加!\n";
            return;
        }
        members_[size_] = std::move(person);
        ++size_;
    }

    // 通过索引访问(返回引用)
    Person& getMember(size_t index) {
        if (index >= size_) throw std::out_of_range("索引越界");
        return *members_[index];
    }

    const Person& getMember(size_t index) const {
        if (index >= size_) throw std::out_of_range("索引越界");
        return *members_[index];
    }

    size_t getSize() const { return size_; }

    void showAll() const {
        std::cout << "\n=== 队伍成员 (" << size_ << "/" << capacity_ << ") ===\n";
        for (size_t i = 0; i < size_; ++i) {
            std::cout << "[" << i << "] ";
            members_[i]->introduce();
        }
    }

private:
    std::unique_ptr<std::unique_ptr<Person>[]> members_;  // unique_ptr 管理动态数组
    size_t capacity_ = 0;
    size_t size_ = 0;
};

// ======================== 使用 shared_ptr + weak_ptr 示例 ========================
class Project {
public:
    std::string name;

    Project(const std::string& n) : name(n) {
        std::cout << "[Project] 创建项目: " << name << "\n";
    }

    ~Project() {
        std::cout << "[Project] 项目销毁: " << name << "\n";
    }

    void addMember(std::shared_ptr<Person> p) {
        members_.push_back(std::move(p));
    }

    void addWeakObserver(std::weak_ptr<Person> observer) {
        observers_.push_back(observer);
    }

    void showMembers() const {
        std::cout << "\n项目 [" << name << "] 成员列表:\n";
        for (const auto& p : members_) {
            if (auto sp = p.lock()) {  // weak_ptr 提升
                sp->introduce();
            }
        }
    }

private:
    std::vector<std::weak_ptr<Person>> members_;     // 使用 weak_ptr 避免循环引用
    std::vector<std::weak_ptr<Person>> observers_;
};

int main() {
    std::cout << "=== 智能指针管理动态对象示例 ===\n\n";

    // 1. 使用 unique_ptr 管理单个对象
    auto leader = std::make_unique<Person>("张领导", 45);

    // 2. 使用 Team 类(内部用 unique_ptr 管理动态数组)
    Team devTeam(5);
    devTeam.addMember(std::make_unique<Person>("李工程师", 28));
    devTeam.addMember(std::make_unique<Person>("王程序员", 26));
    devTeam.addMember(std::move(leader));        // 转移所有权

    devTeam.showAll();

    // 修改成员信息
    devTeam.getMember(0).setAge(29);
    std::cout << "\n修改后:\n";
    devTeam.getMember(0).introduce();

    // 3. shared_ptr + weak_ptr 示例
    std::cout << "\n=== shared_ptr 与 weak_ptr 示例 ===\n";
    auto p1 = std::make_shared<Person>("赵小明", 24);
    auto p2 = std::make_shared<Person>("钱小红", 23);

    {
        Project proj("AI 开发项目");
        proj.addMember(p1);           // 共享所有权
        proj.addMember(p2);

        auto weakP1 = std::weak_ptr<Person>(p1);   // 弱引用
        proj.addWeakObserver(weakP1);

        proj.showMembers();
    } // Project 析构,但 Person 不会立即析构(因为 p1、p2 还在)

    std::cout << "\nProject 已销毁,但 Person 仍然存活:\n";
    p1->introduce();
    p2->introduce();

    // p1 和 p2 离开作用域后,Person 才会被自动销毁
    std::cout << "\nmain 函数结束,所有智能指针管理的资源将自动释放。\n";

    return 0;
}

代码核心要点总结

  1. std::unique_ptr

    • 独占所有权,不能拷贝,只能移动(std::move)。
    • 用于 Team 类内部管理动态数组(std::unique_ptr<T[]>)。
  2. std::make_unique

    • 推荐写法,异常安全,比 new 更好。
  3. std::shared_ptr

    • 共享所有权,通过引用计数管理。
  4. std::weak_ptr

    • 不增加引用计数,用于打破循环引用和观察者模式。
  5. RAII 思想

    • 对象析构时智能指针自动释放内存,无需手动 delete

编译运行(Visual Studio 2022)

  • 新建 控制台应用 → C++17 或更高标准
  • 直接复制运行即可

输出中你会清晰看到构造和析构顺序,证明智能指针自动管理了内存。


想继续扩展? 我可以再给你以下版本:

  • 小字符串优化 (SSO)String 类 + 智能指针
  • 使用 shared_ptr 实现观察者模式完整版
  • 线程安全的智能指针使用示例

✅ C++ const、constexpr、auto、decltype 与类型推导 详细讲解

以下内容基于 C++17 / C++20 标准,在 Visual Studio 2022 中可直接编译运行。


1. const —— 常量与只读保护

作用:保证变量或对象在初始化后不可被修改,提供语义上的只读保护

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

int main() {
    const int MAX = 100;           // 常量,必须初始化
    // MAX = 200;                  // 错误!不能修改

    const double PI = 3.1415926;

    // const 指针与指针 const
    int value = 10;
    const int* p1 = &value;        // 指向常量的指针(不能通过 p1 修改值)
    int* const p2 = &value;        // 常量指针(指针本身不能指向其他变量)
    const int* const p3 = &value;  // 两者都是常量

    *p1 = 20;   // 错误!
    p2 = nullptr; // 错误!

    // const 修饰对象
    const vector<int> vec = {1, 2, 3};
    // vec.push_back(4);        // 错误!const 对象不能调用非 const 成员函数

    return 0;
}

const 成员函数(非常重要):

class Player {
public:
    void setLevel(int lvl) { level_ = lvl; }           // 非 const
    int getLevel() const { return level_; }            // const 成员函数
private:
    int level_ = 1;
};

2. constexpr —— 编译期常量(C++11 起增强)

作用:要求在编译期就能计算出结果,带来更高的性能更强的类型安全

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

constexpr double getPi() {
    return 3.141592653589793;
}

int main() {
    constexpr int SIZE = 1024;                 // 编译期常量
    constexpr int ARR[5] = {1, 2, 3, 4, 5};   // constexpr 数组

    constexpr int result = square(8);          // 编译期计算
    // int arr[result];                        // 可以用于数组大小

    int runtimeValue = 10;
    // constexpr int bad = square(runtimeValue); // 错误!参数必须是编译期常量

    cout << "square(8) = " << result << endl;
    return 0;
}

C++20 进一步增强constexpr 函数可以做更多事情(如 new/delete、std::vector 等)。


3. auto —— 类型自动推导

作用:让编译器根据初始化值自动推导类型,极大提升代码可读性。

int main() {
    auto i = 42;                    // int
    auto d = 3.14;                  // double
    auto str = "Hello";             // const char*
    auto v = std::vector<int>{1,2,3}; // std::vector<int>

    auto lambda = [](int x) { return x * 2; };

    // 常用场景:迭代器
    std::vector<int> nums = {10, 20, 30};
    for (auto it = nums.begin(); it != nums.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;

    // 推荐:const auto&
    const auto& ref = nums;         // 引用 + const
    auto&& universal = 100;         // 万能引用( forwarding reference)

    return 0;
}

auto 推导规则

  • auto x = expr; → 按值拷贝,忽略顶层 const 和引用
  • auto& x = expr; → 左值引用
  • auto&& x = expr; → 万能引用(最强大)

4. decltype —— 类型声明(声明符)

作用:获取表达式的类型,常用于模板和泛型编程。

int main() {
    int x = 10;
    const int& rx = x;

    decltype(x) a = 5;           // int
    decltype(rx) b = x;          // const int&
    decltype(x + 1) c = 20;      // int(表达式结果类型)

    // 常见用法:函数返回类型
    auto func = [](auto x, auto y) -> decltype(x + y) {
        return x + y;
    };

    cout << func(3, 4.5) << endl;   // double

    return 0;
}

decltype(auto)(C++14)—— 完美转发类型:

template<typename F, typename... Args>
decltype(auto) invoke(F&& f, Args&&... args) {
    return std::forward<F>(f)(std::forward<Args>(args)...);  // 完美保持返回值类别
}

5. 类型推导综合示例(推荐练习)

#include <iostream>
#include <vector>
#include <string>

template<typename Container>
void printContainer(const Container& cont) {
    for (const auto& item : cont) {           // auto 推导
        std::cout << item << " ";
    }
    std::cout << std::endl;
}

constexpr int factorial(int n) {
    return (n <= 1) ? 1 : n * factorial(n - 1);
}

int main() {
    constexpr int fact5 = factorial(5);       // 编译期 120

    std::vector<std::string> names = {"Alice", "Bob", "Charlie"};

    const auto& firstName = names[0];         // const std::string&

    decltype(names.size()) count = names.size();  // size_t

    std::cout << "5! = " << fact5 << std::endl;
    printContainer(names);

    // 复杂类型推导示例
    auto lambda = [fact5](auto x) constexpr {
        return x * fact5;
    };

    std::cout << "lambda(10) = " << lambda(10) << std::endl;

    return 0;
}

总结对比表

关键字 作用 编译期求值 可修改 主要场景
const 只读保护 函数参数、成员函数、变量
constexpr 编译期常量/函数 数组大小、模板、性能优化
auto 类型自动推导 - - 变量声明、迭代器、lambda
decltype 获取表达式类型 - - 模板、返回值类型、泛型编程
decltype(auto) 完美类型转发 - - 转发函数、包装器

学习建议

  • 日常开发中优先使用 auto(提高可读性)
  • 常量尽量用 constexpr 而不是 const
  • 在模板和库代码中大量使用 decltype
  • 理解 const vs constexpr 的区别是面试常考点

需要我继续补充以下内容吗?

  • consteval / constinit(C++20)
  • 模板类型推导细节(TT&T&&
  • 完整综合练习项目

更多推荐