C++ 变量、引用、指针、智能指针 详细对比讲解
✅ 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 调试技巧:
- 在变量/指针上右键 → 添加监视
- 使用 诊断工具 查看内存泄漏
- 开启 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;
}
代码核心要点总结
-
std::unique_ptr- 独占所有权,不能拷贝,只能移动(
std::move)。 - 用于
Team类内部管理动态数组(std::unique_ptr<T[]>)。
- 独占所有权,不能拷贝,只能移动(
-
std::make_unique- 推荐写法,异常安全,比
new更好。
- 推荐写法,异常安全,比
-
std::shared_ptr- 共享所有权,通过引用计数管理。
-
std::weak_ptr- 不增加引用计数,用于打破循环引用和观察者模式。
-
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 - 理解
constvsconstexpr的区别是面试常考点
需要我继续补充以下内容吗?
consteval/constinit(C++20)- 模板类型推导细节(
T、T&、T&&) - 完整综合练习项目
更多推荐
所有评论(0)