C++ 引用【笔记】

1. 引用基础与底层原理

引用本质与内存布局

cpp 复制代码
int x = 42;
int& ref = x;  // ref是x的别名,共享同一内存地址

// 底层实现:引用通常通过指针实现
// int* const ref_ptr = &x;  // 类似这样的底层代码

// 验证引用和原变量的地址相同
std::cout << &x << " == " << &ref << std::endl;  // 地址相同

// 引用必须在定义时初始化
int y = 100;
// int& invalid_ref;  // 错误:引用必须初始化
int& valid_ref = y;   // 正确

引用 vs 指针

cpp 复制代码
void compare_reference_pointer() {
    int value = 100;
    
    // 引用:自动解引用,不能重绑定
    int& ref = value;
    ref = 200;  // 直接修改value
    
    // 指针:需要显式解引用,可以重绑定
    int* ptr = &value;
    *ptr = 300;        // 需要解引用
    ptr = nullptr;     // 可以指向其他地址
    
    // 关键区别:
    // 1. 引用必须初始化,指针可以为空
    // 2. 引用不能重绑定,指针可以
    // 3. 引用自动解引用,指针需要显式解引用
    // 4. 引用更安全,指针更灵活
}

2. 左值引用与右值引用

左值引用(Lvalue Reference)

cpp 复制代码
// 常规左值引用(绑定到左值)
int x = 42;
int& lref1 = x;        // 正确:绑定到左值
// int& lref2 = 100;   // 错误:不能绑定到右值

// const左值引用(可以绑定到右值)
const int& clref1 = x;     // 正确:绑定到左值
const int& clref2 = 100;   // 正确:绑定到右值(延长生命周期)

// 函数参数中的左值引用
void process_value(int& value) {  // 需要可修改的左值
    value *= 2;
}

void process_const_ref(const int& value) {  // 接受左值和右值
    std::cout << value << std::endl;
}

int main() {
    int a = 10;
    process_value(a);      // 正确
    // process_value(20);  // 错误:不能传递右值
    
    process_const_ref(a);  // 正确
    process_const_ref(30); // 正确
}

右值引用(Rvalue Reference)与移动语义

cpp 复制代码
class String {
private:
    char* data_;
    size_t size_;
    
public:
    // 移动构造函数
    String(String&& other) noexcept  // 右值引用参数
        : data_(other.data_), size_(other.size_) {
        other.data_ = nullptr;  // 转移所有权
        other.size_ = 0;
    }
    
    // 移动赋值运算符
    String& operator=(String&& other) noexcept {
        if (this != &other) {
            delete[] data_;
            data_ = other.data_;
            size_ = other.size_;
            other.data_ = nullptr;
            other.size_ = 0;
        }
        return *this;
    }
    
    ~String() { delete[] data_; }
};

// 使用移动语义
String create_string() {
    String temp("hello");
    return temp;  // 触发移动构造(如果编译器优化)
}

String s1 = create_string();  // 移动构造
String s2 = std::move(s1);    // 显式移动

3. 万能引用与完美转发

万能引用(Universal Reference)

cpp 复制代码
template<typename T>
void universal_ref(T&& param) {  // 万能引用:T&& + 类型推导
    // param可能是左值引用或右值引用
}

// 引用折叠规则:
// T& &     -> T&
// T& &&    -> T&
// T&& &    -> T&  
// T&& &&   -> T&&

int x = 42;
const int cx = x;
const int& rx = x;

universal_ref(x);   // T = int&, param = int&
universal_ref(cx);  // T = const int&, param = const int&
universal_ref(rx);  // T = const int&, param = const int&
universal_ref(100); // T = int, param = int&&

完美转发(Perfect Forwarding)

cpp 复制代码
template<typename T>
void process(T&& arg) {
    // 错误:arg是左值(有名字的右值引用是左值)
    // some_function(arg);  // 总是调用左值版本
    
    // 正确:完美转发
    some_function(std::forward<T>(arg));
}

// std::forward的实现原理
template<typename T>
constexpr T&& forward(std::remove_reference_t<T>& arg) noexcept {
    return static_cast<T&&>(arg);
}

template<typename T>
constexpr T&& forward(std::remove_reference_t<T>&& arg) noexcept {
    return static_cast<T&&>(arg);
}

// 实际应用示例
class Widget {
public:
    template<typename... Args>
    void emplace(Args&&... args) {
        // 完美转发所有参数
        data_ = std::make_unique<Data>(std::forward<Args>(args)...);
    }
    
private:
    std::unique_ptr<Data> data_;
};

4. 引用限定成员函数

左值/右值限定成员函数

cpp 复制代码
class Buffer {
private:
    std::vector<int> data_;
    
public:
    // 左值限定版本(只能被左值对象调用)
    void modify() & {  // & 表示左值限定
        data_.push_back(42);
    }
    
    // 右值限定版本(只能被右值对象调用)
    std::vector<int> extract() && {  // && 表示右值限定
        return std::move(data_);
    }
    
    // 无限定版本(左值和右值都可以调用)
    size_t size() const {
        return data_.size();
    }
};

void use_buffer() {
    Buffer buf;
    buf.modify();           // 正确:左值调用左值限定函数
    // Buffer().modify();   // 错误:右值不能调用左值限定函数
    
    auto data = Buffer().extract();  // 正确:右值调用右值限定函数
    // buf.extract();       // 错误:左值不能调用右值限定函数
}

5. 引用生命周期延长

临时对象生命周期延长

cpp 复制代码
// 规则:const引用绑定到临时对象时,临时对象的生命周期延长到引用的生命周期

std::string create_string() {
    return "temporary string";
}

void lifetime_extension() {
    const std::string& str = create_string();  // 临时对象生命周期延长
    std::cout << str << std::endl;             // 安全使用
    
    // 非const引用不能绑定到临时对象
    // std::string& invalid_ref = create_string();  // 错误
    
    // 但可以通过const_cast绕过(危险!)
    std::string& dangerous_ref = const_cast<std::string&>(
        static_cast<const std::string&>(create_string())
    );  // 未定义行为!
}

// 生命周期延长不适用于成员访问
struct Wrapper {
    std::string value;
};

Wrapper create_wrapper() {
    return Wrapper{"hello"};
}

void member_lifetime() {
    const std::string& str = create_wrapper().value;  // 危险!
    // 临时Wrapper对象被销毁,str成为悬空引用
    // std::cout << str << std::endl;  // 未定义行为
}

6. 引用在函数式编程中的应用

引用捕获与闭包

cpp 复制代码
void functional_examples() {
    int x = 42;
    std::string name = "C++";
    
    // 值捕获
    auto lambda1 =  {
        std::cout << x << ", " << name << std::endl;
    };
    
    // 引用捕获
    auto lambda2 =  {
        x = 100;  // 修改外部变量
        name = "Modern C++";
    };
    
    // 通用引用捕获(C++14)
    auto lambda3 =  {
        std::cout << value << std::endl;
    };
    
    // 可变lambda(允许修改值捕获的变量)
    auto lambda4 =  mutable {
        x = 200;  // 修改副本
        return x;
    };
    
    lambda2();
    std::cout << "x = " << x << std::endl;  // 输出100
}

高阶函数与引用参数

cpp 复制代码
// 接受函数引用的高阶函数
template<typename Func>
void apply_to_range(int begin, int end, Func&& func) {
    for (int i = begin; i < end; ++i) {
        func(i);
    }
}

// 返回函数引用的工厂
auto create_multiplier(int factor) -> std::function<int(int)>& {
    static std::function<int(int)> multiplier = int x {
        return x * factor;
    };
    return multiplier;
}

void use_higher_order() {
    std::vector<int> results;
    
    // 使用lambda作为参数
    apply_to_range(1, 5, int x {
        results.push_back(x * x);
    });
    
    // 使用返回的函数引用
    auto& doubler = create_multiplier(2);
    std::cout << doubler(21) << std::endl;  // 输出42
}

7. 引用安全性与最佳实践

悬空引用检测与避免

cpp 复制代码
// 危险的返回引用
const std::string& dangerous_get_name() {
    std::string name = "temporary";
    return name;  // 返回局部变量的引用!
}

// 安全的替代方案
std::string safe_get_name_by_value() {
    return "safe temporary";  // 返回值
}

const std::string& safe_get_name_by_ref(const std::string& name) {
    return name;  // 返回参数引用(调用者负责生命周期)
}

// 使用optional避免悬空引用
std::optional<std::reference_wrapper<const std::string>> 
safe_get_name_optional(const std::string& name) {
    if (!name.empty()) {
        return std::cref(name);
    }
    return std::nullopt;
}

void use_safe_references() {
    // 错误示例
    // const std::string& bad_ref = dangerous_get_name();  // 悬空引用
    
    // 正确用法
    std::string value = safe_get_name_by_value();  // 值返回
    const std::string& safe_ref = safe_get_name_by_ref(value);  // 引用返回
    
    // 使用optional
    if (auto name_ref = safe_get_name_optional(value)) {
        std::cout << name_ref->get() << std::endl;
    }
}

引用包装器

cpp 复制代码
void reference_wrapper_examples() {
    int x = 42;
    std::string name = "hello";
    
    // std::reference_wrapper 可以存储引用
    std::vector<std::reference_wrapper<int>> int_refs;
    int_refs.push_back(std::ref(x));
    
    // 可以重新绑定
    int y = 100;
    int_refs[0] = std::ref(y);
    
    // 在容器中存储引用
    std::vector<std::reference_wrapper<std::string>> string_refs;
    string_refs.push_back(std::ref(name));
    
    // 修改原始对象
    string_refs[0].get() = "world";
    std::cout << name << std::endl;  // 输出"world"
    
    // 引用包装器可以拷贝,但始终引用同一对象
    auto ref1 = std::ref(x);
    auto ref2 = ref1;
    ref2.get() = 200;
    std::cout << x << std::endl;  // 输出200
}

8. 性能优化与高级技巧

引用与内联优化

cpp 复制代码
// 引用参数通常有利于内联优化
class Point {
private:
    double x_, y_;
    
public:
    // 按const引用传递,避免拷贝
    double distance_to(const Point& other) const {
        double dx = x_ - other.x_;
        double dy = y_ - other.y_;
        return std::sqrt(dx * dx + dy * dy);
    }
    
    // 按值传递小对象(可能更高效)
    Point translate(double dx, double dy) const {
        return Point{x_ + dx, y_ + dy};
    }
};

// 编译器优化示例
void optimized_calls() {
    Point p1{1.0, 2.0};
    Point p2{3.0, 4.0};
    
    // 编译器可能内联这些调用
    double dist = p1.distance_to(p2);
    Point p3 = p1.translate(1.0, 1.0);
    
    // 引用避免不必要的拷贝
    std::vector<Point> points;
    points.push_back(p1);           // 拷贝
    points.push_back(std::move(p2));// 移动(如果Point支持移动语义)
}

引用与多态

cpp 复制代码
class Shape {
public:
    virtual void draw() const = 0;
    virtual ~Shape() = default;
};

class Circle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing circle\n";
    }
};

class Square : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing square\n";
    }
};

void draw_shape(const Shape& shape) {  // 按引用传递支持多态
    shape.draw();  // 动态绑定
}

void polymorphism_example() {
    Circle circle;
    Square square;
    
    draw_shape(circle);  // 调用Circle::draw()
    draw_shape(square);  // 调用Square::draw()
    
    // 引用切片问题(不存在)
    // 引用总是绑定到实际对象类型
}

9. 现代C++引用特性

C++17 类模板参数推导中的引用

cpp 复制代码
template<typename T>
class Wrapper {
private:
    T value_;
    
public:
    Wrapper(T&& value) : value_(std::forward<T>(value)) {}
};

// C++17 推导指南
template<typename T>
Wrapper(T&&) -> Wrapper<std::decay_t<T>>;

void deduction_example() {
    int x = 42;
    
    // 自动推导类型
    Wrapper w1(x);           // Wrapper<int>
    Wrapper w2(std::move(x));// Wrapper<int>
    Wrapper w3(100);         // Wrapper<int>
    
    // 引用类型处理
    std::string str = "hello";
    Wrapper w4(str);         // Wrapper<std::string>(值语义)
    Wrapper w5(std::ref(str)); // Wrapper<std::reference_wrapper<std::string>>
}

C++20 概念与引用约束

cpp 复制代码
// 定义引用相关的概念
template<typename T>
concept LValueReference = std::is_lvalue_reference_v<T>;

template<typename T>
concept RValueReference = std::is_rvalue_reference_v<T>;

template<typename T>
concept Reference = std::is_reference_v<T>;

// 使用概念约束函数
template<typename T> requires std::copyable<T>
T process_copy(T value) {
    return value;
}

template<typename T> requires std::movable<T>
T process_move(T&& value) {
    return std::move(value);
}

// 完美转发约束
template<typename T>
concept PerfectForwardable = requires(T t) {
    requires !std::is_reference_v<T> || std::is_lvalue_reference_v<T>;
};

template<typename T> requires PerfectForwardable<T>
void forward_value(T&& value) {
    // 安全转发
    process(std::forward<T>(value));
}

10. 最佳实践总结

引用使用指南

cpp 复制代码
// ✅ 推荐做法:
// 1. 函数参数优先使用const引用
void process_object(const Object& obj);

// 2. 需要修改参数时使用非const引用
void modify_object(Object& obj);

// 3. 移动语义使用右值引用
Object create_object(Object&& source);

// 4. 模板函数使用万能引用
template<typename T>
void forward_argument(T&& arg);

// ❌ 避免做法:
// 1. 返回局部变量的引用
const std::string& bad_function();

// 2. 悬空引用
int& create_dangling_reference();

// 3. 不必要的引用拷贝
void unnecessary_copy(const std::string& str) {
    std::string local = str;  // 可能应该直接使用引用
}

// 4. 混淆引用和指针语义
void mixed_semantics(int& ref, int* ptr) {
    // 保持一致性:要么都用引用,要么都用指针
}

性能优化建议

cpp 复制代码
// 1. 小对象按值传递
void process_int(int value);        // int按值传递
void process_double(double value);  // double按值传递

// 2. 大对象按const引用传递
void process_vector(const std::vector<int>& vec);

// 3. 需要移动语义时使用右值引用
void consume_string(std::string&& str);

// 4. 避免过度使用引用包装器
// 优先考虑设计改进,而不是到处使用std::ref

// 5. 注意引用和异常安全
class Resource {
public:
    // 返回内部状态的引用可能破坏封装性
    const std::vector<int>& get_data() const& {  // 左值限定
        return data_;
    }
    
    std::vector<int> get_data() && {  // 右值限定,返回移动后的数据
        return std::move(data_);
    }
    
private:
    std::vector<int> data_;
};
相关推荐
乌日尼乐7 分钟前
【Java基础整理】封装、继承、抽象、接口和多态
java·后端
heartbeat..8 分钟前
JavaWeb 入门 - HttpServletResponse 响应对象 详解
java·网络·http·web·response
zs宝来了9 分钟前
Spring Boot启动流程源码深度解析:电商订单系统面试实战
java·spring boot·面试·源码分析·电商
智航GIS10 分钟前
9.1 多线程入门
java·开发语言·python
消失的旧时光-194318 分钟前
从 Java 接口到 Dart freezed:一文彻底理解 Dart 的数据模型设计
java·开发语言·flutter·dart
就这个丶调调22 分钟前
Java ConcurrentHashMap源码深度解析:从底层原理到性能优化
java·并发编程·源码分析·线程安全·concurrenthashmap
mall_090523 分钟前
Elasticsearch字段类型聚合排序指南
java·elasticsearch
程序猿零零漆1 小时前
Spring之旅 - 记录学习 Spring 框架的过程和经验(八)基于Spring的注解应用
java·学习·spring
大头流矢1 小时前
C++的类与对象·三部曲:初阶
开发语言·c++
indexsunny1 小时前
互联网大厂Java面试实战:从Spring Boot到微服务的逐步深入
java·数据库·spring boot·微服务·kafka·监控·安全认证