C++赋值与函数调用:新手避坑指南与最佳实践

1次阅读
没有评论

共计 2255 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

核心概念

基本类型赋值 vs 类对象赋值

基本类型的赋值操作非常简单,就是直接复制值:

C++ 赋值与函数调用:新手避坑指南与最佳实践

int a = 5;
int b = a; // 基本类型赋值,直接复制值 

而类对象的赋值会调用拷贝构造函数(如果定义了的话):

class MyClass {
public:
    int* data;
    MyClass(int size) {data = new int[size]; }
    ~MyClass() { delete[] data; }
    // 拷贝构造函数
    MyClass(const MyClass& other) {data = new int[/*size*/];
        std::copy(other.data, other.data + /*size*/, data);
    }
};

MyClass obj1(10);
MyClass obj2 = obj1; // 调用拷贝构造函数 

函数参数传递方式

  1. 值传递(Pass by value):

    void func(int param) {/* 操作 param */}

  2. 引用传递(Pass by reference):

    void func(int& param) {/* 操作原变量 */}

  3. 指针传递(Pass by pointer):

    void func(int* param) {/* 通过指针操作 */}

痛点分析

浅拷贝问题

class ShallowCopy {
public:
    int* data;
    ShallowCopy(int val) {data = new int(val); }
    ~ShallowCopy() { delete data;}
    // 没有自定义拷贝构造函数
};

ShallowCopy a(5);
ShallowCopy b = a; // 浅拷贝
// 析构时会导致同一内存被释放两次 

临时对象生命周期

void process(const std::string& str) {/*...*/}

process("temporary"); // 临时对象的生命周期只到函数结束 

返回局部变量引用

int& badFunc() {
    int local = 10;
    return local; // 非常危险!}

解决方案

Rule of Three/Five

class SafeClass {
public:
    int* data;
    size_t size;

    // 构造函数
    SafeClass(size_t s) : size(s), data(new int[s]) {}

    // 1. 析构函数
    ~SafeClass() { delete[] data; }

    // 2. 拷贝构造函数
    SafeClass(const SafeClass& other) : size(other.size), data(new int[other.size]) {std::copy(other.data, other.data + size, data);
    }

    // 3. 拷贝赋值运算符
    SafeClass& operator=(const SafeClass& other) {if (this != &other) {delete[] data;
            size = other.size;
            data = new int[size];
            std::copy(other.data, other.data + size, data);
        }
        return *this;
    }

    // 4. 移动构造函数(C++11)SafeClass(SafeClass&& other) noexcept : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
    }

    // 5. 移动赋值运算符(C++11)SafeClass& operator=(SafeClass&& other) noexcept {if (this != &other) {delete[] data;
            data = other.data;
            size = other.size;
            other.data = nullptr;
            other.size = 0;
        }
        return *this;
    }
};

参数传递决策树

  1. 小对象(int, double 等)→ 值传递
  2. 只读大对象 → const &
  3. 需要修改的原对象 → &
  4. 可移动对象 → &&

避坑指南

explicit 关键字

class MyString {
public:
    explicit MyString(int size) {/*...*/} // 阻止隐式转换
};

// MyString s = 10; // 错误,不能隐式转换
MyString s(10);    // 正确,显式调用 

NRVO 优化

命名返回值优化(Named Return Value Optimization)允许编译器优化掉多余的拷贝操作:

std::vector<int> createVector() {std::vector<int> result(1000); // 可能直接在调用处构造
    return result; // NRVO 可能发生
}

自测题

  1. 以下哪种情况会导致内存问题?

    class A {
    public:
        int* data;
        A() { data = new int(10); }
        ~A() { delete data;}
    };
    
    A a;
    A b = a;

  2. 如何修改以下函数以避免潜在问题?

    std::string& getString() {
        std::string local = "hello";
        return local;
    }

  3. 哪种参数传递方式最适合大型只读对象?


答案:
1. 会导致重复释放,因为没有定义拷贝构造函数
2. 改为返回值而非引用:std::string getString()
3. const &

总结

C++ 的赋值和函数调用看似简单,但隐藏着许多陷阱。理解拷贝语义、引用和指针的区别,以及现代 C ++ 的移动语义,可以避免大多数常见问题。记住 Rule of Three/Five,合理选择参数传递方式,你的代码会安全高效很多。

正文完
 0
评论(没有评论)