【Effective C++】阅读笔记2

1. 复制对象时要保证复制内容完整性

错误场景复现(没有复制基类部分)

如果一个类中包含多个成员变量或者继承了基类,那么在拷贝构造函数或者赋值运算符中,必须保证所有成员的基类部分被复制。

基类没有被复制,这样就都导致了其函数返回的是默认数值0,可能会引起逻辑错误

 

#include <iostream>class Base {
public:Base(int value = 0) : value_(value) {}int getValue() const { return value_; }private:int value_;
};class Derived : public Base {
public:Derived(int value, int extra) : Base(value), extra_(extra) {}// 错误:未复制基类部分Derived(const Derived& other) : extra_(other.extra_) {}void print() const {std::cout << "基类的值:" << getValue() << ",派生类的值:" << extra_ << std::endl;}private:int extra_;
};int main() {Derived d1(10, 20);Derived d2 = d1;  // 使用拷贝构造函数d2.print();  return 0;
}

 解决思路:显式的调用基类拷贝构造函数

 通过拷贝构造函数中,显式的调用基类的拷贝构造函数,这样就可以保证基类部分完整的复制

 

#include <iostream>class Base {
public:Base(int value = 0) : value_(value) {}Base(const Base& other) : value_(other.value_) {}  // 拷贝构造函数int getValue() const { return value_; }private:int value_;
};class Derived : public Base {
public:Derived(int value, int extra) : Base(value), extra_(extra) {}// 显式调用基类的拷贝构造函数Derived(const Derived& other) : Base(other), extra_(other.extra_) {}void print() const {std::cout << "基类的值:" << getValue() << ",派生类的值:" << extra_ << std::endl;}private:int extra_;
};int main() {Derived d1(10, 20);Derived d2 = d1;  // 使用拷贝构造函数d2.print(); return 0;
}

动态资源拷贝时,需要进行深拷贝,避免浅拷贝

一般出现在类中有指针成员的时候,一定要使用深拷贝,避免内存泄漏或者双重释放问题

#include <iostream>
#include <cstring>class String {
public:String(const char* str = "") {data_ = new char[strlen(str) + 1];strcpy(data_, str);}// 拷贝构造函数:实现深拷贝String(const String& other) {data_ = new char[strlen(other.data_) + 1];strcpy(data_, other.data_);}~String() {delete[] data_;}void print() const {std::cout << "字符串:" << data_ << std::endl;}private:char* data_;
};int main() {String s1("hhahha");String s2 = s1;  // 使用拷贝构造函数s2.print();  return 0;
}

总结与反思

  • 类中成员复制的时候要保证其完整性,在有动态资源时,使用深拷贝,避免资源泄漏问题
  • 尽量使用智能指针管理资源,管理动态资源的时候,智能指针可以简化深拷贝逻辑,同时还能够避免资源泄漏

2. RAII

RAII原理理解

核心思想,将资源的管理和生命周期绑定在一起,当对象创建的时候获取资源,对象销毁的时候自动释放,从而保证资源正常管理避免资源泄漏。

实现原理

  • RAII中,将资源封装到一个对象的构造函数和析构函数中
  • 构造函数负责在对象创建的时候获取资源
  • 析构函数负责在对象销毁的时候释放资源
  • 借助构造函数与析构函数,当对象超出作用域的时候,析构函数会自动调用从而释放资源,避免资源泄漏

RAII的好处

  • 避免内存泄漏:动态内存分配的时候如果忘记释放资源,就会导致内存泄漏
  • RAII可以保证即使发生异常,对象的资源也可以被正确的释放
  • 将资源管理逻辑封装在类的内部,也让代码更好维护

RAII简单实现

#include <iostream>class Array {
public:Array(size_t size) {data_ = new int[size];  // 在构造函数中分配资源std::cout << "分配内存" << std::endl;}~Array() {  // 在析构函数中释放资源delete[] data_;std::cout << "释放内存" << std::endl;}private:int* data_;
};void withRAII() {Array arr(10);  // 自动管理内存
}int main() {withRAII();  // 函数结束后自动调用析构函数,释放内存return 0;
}

直接使用智能指针就可以实现RAII

智能指针的特性就是自动管理内存,当对象超出作用域的时候,内存就会自动释放

#include <iostream>
#include <memory>  // 包含智能指针头文件void withSmartPointer() {std::unique_ptr<int[]> data = std::make_unique<int[]>(10);  // 自动管理内存std::cout << "分配内存" << std::endl;// 内存将在函数结束时自动释放
}int main() {withSmartPointer();std::cout << "智能指针管理内存完成" << std::endl;return 0;
}

总结与反思

  • 智能指针管理内存最方便,不用自己手动实现RAII
  • RAII可以有效的避免资源泄漏,将资源的生命周期与对象的生命周期绑定在一起

 3. 资源管理类(RAII)中小心拷贝行为

拷贝时资源泄漏和双重释放问题分析

因为资源管理类负责在对象生命周期结束的时候释放资源,所以当资源管理类拷贝的时候,就可能会出现资源泄漏和双重释放问题

  • 例如当使用默认构造函数进行拷贝的时候,此时的浅拷贝就是让其共享内存,最终释放资源的时候也就导致了双重释放错误

#include <iostream>class Resource {
public:Resource(const char* data) {data_ = new char[strlen(data) + 1];strcpy(data_, data);}~Resource() {delete[] data_;  // 释放资源}void print() const {std::cout << "数据:" << data_ << std::endl;}private:char* data_;
};int main() {Resource r1("Hello");  // 创建对象并分配资源Resource r2 = r1;       // 使用默认拷贝构造函数(浅拷贝)r2.print();  // 输出:数据:Hello// 当 r1 和 r2 被销毁时,都会尝试释放同一块内存return 0;
}

 解决方法1:直接禁用拷贝操作

简单粗暴的方法,直接禁止拷贝构造,这样就不会被多次释放情况

class Resource {
public:Resource(const char* data) {data_ = new char[strlen(data) + 1];strcpy(data_, data);}~Resource() {delete[] data_;}Resource(const Resource&) = delete;  // 禁用拷贝构造函数Resource& operator=(const Resource&) = delete;  // 禁用赋值运算符void print() const {std::cout << "数据:" << data_ << std::endl;}private:char* data_;
};int main() {Resource r1("Hello");// Resource r2 = r1;  // 编译错误:拷贝构造被禁用r1.print();return 0;
}

方法2:实现深拷贝

给构造函数和赋值运算符都实现一个的深拷贝;使用深拷贝确保每个对象都有独立的资源,避免共享资源导致双重释放问题

#include <iostream>
#include <cstring>class Resource {
public:Resource(const char* data) {data_ = new char[strlen(data) + 1];strcpy(data_, data);}// 深拷贝构造函数Resource(const Resource& other) {data_ = new char[strlen(other.data_) + 1];strcpy(data_, other.data_);std::cout << "拷贝构造函数被调用" << std::endl;}// 深拷贝赋值运算符Resource& operator=(const Resource& other) {if (this == &other) return *this;  // 检测自我赋值delete[] data_;  // 释放旧资源data_ = new char[strlen(other.data_) + 1];strcpy(data_, other.data_);std::cout << "赋值运算符被调用" << std::endl;return *this;}~Resource() {delete[] data_;  // 释放资源}void print() const {std::cout << "数据:" << data_ << std::endl;}private:char* data_;
};int main() {Resource r1("Hello");Resource r2 = r1;  // 调用拷贝构造函数r2.print();Resource r3("World");r3 = r1;  // 调用赋值运算符r3.print();return 0;
}

 方法3:智能指针

拷贝的时候使用智能指针管理内存,然后通过移动语义转移资源。实现方法就是类中动态资源使用智能指针管理,拷贝以及赋值的时候使用move

  • 移动构造:将一个对象的资源移动到当前对象,避免资源的深拷贝
  • 移动赋值:对象直接移动资源,需要判断目标对象是否已经存在,如果存在则需要先释放资源

#include <iostream>
#include <memory>
#include <cstring> class Resource {
public:// 构造函数Resource(const char* data) {data_ = std::make_unique<char[]>(strlen(data) + 1);strcpy(data_.get(), data);std::cout << "构造函数被调用" << std::endl;}// 移动构造函数Resource(Resource&& other) noexcept {data_ = std::move(other.data_);std::cout << "移动构造函数被调用" << std::endl;}// 移动赋值运算符Resource& operator=(Resource&& other) noexcept {if (this != &other) {  // 避免自我赋值data_ = std::move(other.data_);std::cout << "移动赋值运算符被调用" << std::endl;}return *this;}// 删除拷贝构造函数和拷贝赋值运算符,防止拷贝Resource(const Resource&) = delete;Resource& operator=(const Resource&) = delete;// 打印数据void print() const {std::cout << "数据:" << data_.get() << std::endl;}private:std::unique_ptr<char[]> data_;
};int main() {// 创建资源对象 r1Resource r1("Hello");r1.print();// 使用移动构造函数Resource r2 = std::move(r1);  // r1 的资源转移到 r2r2.print();// r1 现在为空,不能再使用其资源。// 使用移动赋值运算符Resource r3("World");r3 = std::move(r2);  // r2 的资源转移到 r3r3.print();// r2 也变为空return 0;
}

总结反思

  • 资源管理类中,要避免多个对象共享同一资源,防止双重释放
  • 如果类需要拷贝就一定要使用深拷贝,否则就要禁止拷贝操作
  • 优先使用移动语义和智能指针

4. 资源管理类(RAII)中提供对原始资源的访问

场景分析

 资源管理类将资源封装起来,让对象生命周期结束的时候自动释放资源,但是某些函数或者其他情况还是需要访问原始资源,例如函数需要调用原始资源中智能指针管理的原始指针。

  • 获取 std::unique_ptrstd::shared_ptr 管理的原始指针。
  • 获取文件句柄或网络连接句柄,供第三方库函数使用。

方法1:使用智能指针管理资源并提供原始指针访问

智能指针负责对动态资源管理,同时类中通过get()方法提供对原始指针的访问

  • 注意return resource_.get()中的get方法,是智能指针中提供的一个获取内部管理的原始指针

#include <iostream>
#include <memory>  // 包含智能指针头文件class ResourceManager {
public:ResourceManager(const char* data) {resource_ = std::make_unique<char[]>(strlen(data) + 1);strcpy(resource_.get(), data);}// 提供对原始指针的访问char* get() const {return resource_.get();}void print() const {std::cout << "资源内容:" << resource_.get() << std::endl;}private:std::unique_ptr<char[]> resource_;  // 使用智能指针管理资源
};int main() {ResourceManager manager("Hello, World!");manager.print();// 使用原始指针与第三方库交互char* rawPointer = manager.get();std::cout << "通过原始指针访问资源:" << rawPointer << std::endl;return 0;
}

 方法2:std::shared_ptr和weak_ptr

也是借助这两种智能指针,实现对原始资源的访问,此处回顾线程安全中智能指针的使用

#include <iostream>
#include <memory>  // 包含智能指针头文件class ResourceManager {
public:ResourceManager(const char* data) {resource_ = std::make_shared<std::string>(data);}// 提供对原始资源的访问std::string* get() const {return resource_.get();}void print() const {std::cout << "资源内容:" << *resource_ << std::endl;}private:std::shared_ptr<std::string> resource_;  // 使用 shared_ptr 管理资源
};int main() {ResourceManager manager("共享资源");manager.print();// 获取原始指针并使用std::string* rawPointer = manager.get();std::cout << "通过原始指针访问资源:" << *rawPointer << std::endl;return 0;
}

原始资源安全访问的注意点

  • 分清访问类型 
    • 如果是只读访问,那么提供只读访问接口
    • 避免外部代码直接修改资源,这样就可以保证资源管理类的封装性
  • 避免悬空指针
    • 确保资源管理类的对象原始指针在使用期间都不会被销毁
    • 要尽可能的缩短原始指针的生命周期,从而避免悬空指针问题
  • 配合shared_ptr一同使用
    • 如果资源需要在多个地方共享,可以使用 std::shared_ptr,并提供访问方法,如 get()weak_ptr

总结反思

  • 提供原始资源的安全访问接口:在资源管理类中,可以通过 get() 或类似的方法提供对原始资源的访问
  • 尽量缩短原始指针的生命周期:减少悬空指针的风险
  • 优先使用智能指针:如 unique_ptrshared_ptr,自动管理资源的生命周期,避免内存泄漏

5. 使用正确的形式释放内存

错误分析

核心就是必须是使用正确配对的方式来释放内存,否则就会导致未定义行为或者内存泄露

  • 单对象:new/delete
  • 数组:new[ ] / delete [ ] 

问题出现原因分析

  • new 和 new[ ] 实现不同,new[ ]可能会为数组元素额外的分配一些内存存储数组大小,所以其内存布局和单个对象是不同的
  • 如果使用delete释放new [ ] 分配的内存,就有可能调用所有对象的析构函数,从而导致资源泄露

错误用例分析

// 错误1int main() {int* p = new int(10);  // 分配单个 int 对象delete[] p;  // 错误:使用 delete[] 释放单对象return 0;
}// 错误2int main() {int* p = new int[5];  // 分配一个 int 数组delete p;  // 错误:使用 delete 释放数组return 0;
}

正确配对方法

#include <iostream>int main() {// 分配单个对象int* p1 = new int(10);delete p1;  // 正确:使用 delete 释放单个对象// 分配数组int* p2 = new int[5];delete[] p2;  // 正确:使用 delete[] 释放数组return 0;
}

总结反思

  • 手动管理内存的时候,一定要严格按照new 和 delete的匹配原则,避免形式错误导致的内存泄露或者没定义的行为
  • 优先选择智能指针去管理内存

6. 使用独立语句将new对象置入智能指针

问题分析

例如使用一个函数的同时使用std::shared_ptr管理对象的生命周期,当这个函数初始化的时候出现异常。那么在这种new和std::shared_ptr构造都放在一个语句中,就会导致内存泄漏问题

  • 下面代码问题关键在于,process()抛出异常之前的时候,已经new出来了内存,所以在智能指针构造的时候出现异常,内存也就会无法释放
  • 内存泄漏的发生,就是因为New创建的对象无法被智能指针管理

#include <iostream>
#include <memory>void process(std::shared_ptr<int> ptr) {throw std::runtime_error("处理时发生异常");
}int main() {try {// 错误:如果 process 抛出异常,new 分配的对象无法释放process(std::shared_ptr<int>(new int(42)));}catch (const std::exception& e) {std::cout << "捕获异常:" << e.what() << std::endl;}return 0;
}

解决方法1:使用独立的语句初始化智能指针

也就是将new对象和智能指针构造分离成两个语句,从而确保即使发生异常,内存也可以被正确的管理。

#include <iostream>
#include <memory>void process(std::shared_ptr<int> ptr) {throw std::runtime_error("处理时发生异常");
}int main() {try {// 使用独立语句分配对象并置入智能指针std::shared_ptr<int> ptr(new int(42));process(ptr);  // 如果抛出异常,ptr 会自动释放内存}catch (const std::exception& e) {std::cout << "捕获异常:" << e.what() << std::endl;}return 0;
}

解决方法2:使用make_shared或者make_unique

make_shared将对象构造和控制块创建合并成了一个操作,从而避免内存泄漏风险 

void process(std::shared_ptr<int> ptr) {throw std::runtime_error("处理时发生异常");
}int main() {try {auto ptr = std::make_shared<int>(42);  // 更优雅的方式process(ptr);  // 如果抛出异常,ptr 会自动释放内存}catch (const std::exception& e) {std::cout << "捕获异常:" << e.what() << std::endl;}return 0;
}

总结反思  

  • 使用智能指针应该尽可能的简洁安全,多使用make_shared()等函数,让代码可读性更高
  • new操作与智能指针进行分离,保证在异常的时候也可以释放资源

7. 设计易于使用的接口

不好用接口分析

设计类中接口的时候,不仅要满足基本功能需求,同时还需要防止接口误用

  • 下述代码如果忘记调用f.close(),那么文件就不会正确保存
#include <iostream>
#include <fstream>class File {
public:File(const std::string& filename) {file_.open(filename);}void write(const std::string& data) {file_ << data;}void close() {file_.close();}private:std::ofstream file_;
};int main() {File f("example.txt");f.write("Hello, World!");f.write("Another line.");// 忘记调用 f.close(),可能导致文件未正确保存return 0;
}

方法1:通过RAII思想,在析构函数的时候自动关闭文件 

#include <iostream>
#include <fstream>class File {
public:File(const std::string& filename) {file_.open(filename);}~File() {  // 在析构函数中关闭文件if (file_.is_open()) {file_.close();std::cout << "文件已关闭" << std::endl;}}void write(const std::string& data) {if (file_.is_open()) {file_ << data;}}private:std::ofstream file_;
};int main() {{File f("example.txt");f.write("Hello, World!");}  // 离开作用域时自动关闭文件return 0;
}

方法2:使用智能指针简化

通过智能指针,实现对资源的管理,避免内存泄漏

#include <iostream>
#include <memory>class Resource {
public:Resource() { std::cout << "资源被分配" << std::endl; }~Resource() { std::cout << "资源被释放" << std::endl; }void doSomething() { std::cout << "执行操作" << std::endl; }
};void useResource() {auto res = std::make_unique<Resource>();  // 使用智能指针管理资源res->doSomething();
}  // 离开作用域时,资源自动释放int main() {useResource();return 0;
}

方法3:通过编译期检查规避错误

使用delete关键字,直接禁用自己不希望存在的操作,这样就可以在编译期间直接捕获错误

class NonCopyable {
public:NonCopyable() = default;NonCopyable(const NonCopyable&) = delete;  // 禁用拷贝构造NonCopyable& operator=(const NonCopyable&) = delete;  // 禁用赋值操作
};int main() {NonCopyable obj1;// NonCopyable obj2 = obj1;  // 编译错误:拷贝构造被禁用return 0;
}

方法4:使用强类型枚举,避免枚举混用的错误

#include <iostream>enum class Color { Red, Green, Blue };
enum class TrafficLight { Red, Yellow, Green };void printColor(Color color) {switch (color) {case Color::Red: std::cout << "红色" << std::endl; break;case Color::Green: std::cout << "绿色" << std::endl; break;case Color::Blue: std::cout << "蓝色" << std::endl; break;}
}int main() {printColor(Color::Red);printColor(TrafficLight::Red);  // 编译错误:类型不匹配return 0;
}

总结反思

  • 编程前多思考,使用多种方法设计安全好用的接口

8. 设计类的时候需要考虑类型转换的安全性

问题分析

C++中允许隐式类型转换可能会引起错误,例如当类中有支持允许隐式类型转换的构造函数,编译的时候就会自动调用,但是这不一定是我们想要看到的,因为部分情况下是不需要进行类型转换的

class Fraction {
public:Fraction(int numerator, int denominator): numerator_(numerator), denominator_(denominator) {}// 单参数构造函数允许隐式类型转换Fraction(int wholeNumber) : numerator_(wholeNumber), denominator_(1) {}void print() const {std::cout << numerator_ << "/" << denominator_ << std::endl;}private:int numerator_;int denominator_;
};void printFraction(const Fraction& frac) {frac.print();
}int main() {printFraction(5); return 0;
}

方法1:将构造函数直接标记成explicit

  • explicit关键字可以防止构造函数被用作隐式类型转换的路径,也就是用户必须明确调用构造函数
#include <iostream>class Fraction {
public:explicit Fraction(int numerator, int denominator): numerator_(numerator), denominator_(denominator) {}explicit Fraction(int wholeNumber): numerator_(wholeNumber), denominator_(1) {}void print() const {std::cout << numerator_ << "/" << denominator_ << std::endl;}private:int numerator_;int denominator_;
};void printFraction(const Fraction& frac) {frac.print();
}int main() {//printFraction(5);  // 编译错误:不能进行隐式转换printFraction(Fraction(5));  // 必须显式构造对象return 0;
}

问题2:不加限制的使用重载运算符使用,可能会导致类型转换混乱

  • 类型转换成运算符会将Fraction对象隐式的转换为double
    • operator double()定义了一个类型转换运算符,其允许Fraction对象被转换成double类型
    • 具体来说将numerator_转换成double类型,然后除以denominator_
  • 隐式类型转换流程分析
    • 首先frac是一个Fraction类型,而1.5是double类型,所以编译器需要将frac转换成double,这样在可以与1.5进行相加
    • 因为类中定义了类型转换运算符operator double(),编译器会隐式调用这个运算符,将frac转换为double
    • 返回值也通过强制类型转换为了double类型
#include <iostream>class Fraction {
public:Fraction(int numerator, int denominator) : numerator_(numerator), denominator_(denominator) {}// 类型转换运算符,将 Fraction 转换为 doubleoperator double() const {return static_cast<double>(numerator_) / denominator_;}private:int numerator_;int denominator_;
};int main() {Fraction frac(3, 2);std::cout << frac + 1.5 << std::endl;  // 隐式转换为 doublereturn 0;
}

解决上述问题思路,仍然是将类型转换运算符标记为explicit

利用explicit来修饰类型转换运算符,这样当用户需要类型转换时显式调用类型转换运算符,从而避免隐式转换的弊端

#include <iostream>class Fraction {
public:Fraction(int numerator, int denominator) : numerator_(numerator), denominator_(denominator) {}// 使用 explicit 修饰类型转换运算符explicit operator double() const {return static_cast<double>(numerator_) / denominator_;}private:int numerator_;int denominator_;
};int main() {Fraction frac(3, 2);// std::cout << frac + 1.5 << std::endl;  // 编译错误:隐式转换被禁止std::cout << static_cast<double>(frac) + 1.5 << std::endl;  // 显式转换return 0;
}

总结反思

  • 设计接口的时候,要避免不必要的隐式类型转换,让接口变的安全可靠
  • 通过explicit防止编译器进行意外的类型转换

9. 常量引用转递代替值传递

避免按值传递原因分析

按值传递的时候,C++会创建一个临时对象,从而会增加性能开销。

#include <iostream>
#include <string>void printName(std::string name) {  // 按值传递,拷贝了 namestd::cout << "名字:" << name << std::endl;
}int main() {std::string myName = "Alice";printName(myName);  // 会调用拷贝构造函数return 0;
}

解决方法:通过引用传递,不会创建对象副本,从而减少内存消耗

#include <iostream>
#include <string>void printName(const std::string& name) {  // 常量引用传递std::cout << "名字:" << name << std::endl;
}int main() {std::string myName = "Alice";printName(myName);  // 不会调用拷贝构造函数return 0;
}

按值传递的特殊场景

  • 内置类型,一些较小的类型,直接使用按值传递,反而方便
  • 需要修改副本内容的时候,也就是传递给函数使用的时候,希望修改副本内容但是不影响原内容的时候

移动语义实现传递

  • std::move会将对象标记为右值,允许函数内部直接管理资源,避免拷贝
  • 使用移动语义减少内存分配和拷贝开销

#include <iostream>
#include <string>void modifyCopy(std::string name) {name += " Smith";  // 修改副本std::cout << "修改后的名字:" << name << std::endl;
}int main() {std::string myName = "Alice";modifyCopy(std::move(myName));  // 使用移动语义,避免拷贝std::cout << "原名字:" << myName << std::endl;  // 变为空字符串return 0;
}

总结反思

  • 常量引用传递可以减少不必要的拷贝
  • 使用move优化按值传递,从而进一步提高性能

 10. 必须返回对象的时候,不要试图返回器引用

问题分析

强调如果函数必须返回一个对象的时候,不要返回该对象的引用或者指针,这是因为返回对象的引用或者指针可能会导致悬空引用或者未定义行为。

返回局部对象引用的时候就会导致悬空引用

  • 首先局部变量在函数返回后就会被销毁
  • 然后如果尝试返回一个局部对象的引用或者指针,那么就会得到一个指向无效内存的引用或者指针,也就导致了未定义行为
  • 注意:在visual studio中测试可能最终打印结果是10,出现这个的原因怀疑是内存没有及时被覆盖的情况
#include <iostream>int& getLocalVariable() {int x = 10;  // 局部变量return x;  // 返回局部变量的引用(错误)
}int main() {int& ref = getLocalVariable();  // ref 指向已销毁的内存std::cout << "ref = " << ref << std::endl;  // 未定义行为return 0;
}

解决方法:通过返回对象值

  • 通过返回对象值,x的值就被拷贝并返回给调用者,避免了悬空引用的问题
  • 而且现在C++编译器也会通过特定的优化,减少拷贝的开销

#include <iostream>int getLocalVariable() {int x = 10;  // 局部变量return x;  // 返回对象值
}int main() {int value = getLocalVariable();  // 正常赋值,不会导致悬空引用std::cout << "value = " << value << std::endl;  // 输出:10return 0;
}

返回类对象的值

自己定义的类,返回对象的值是一种较为安全的选择,即使对象很大,因为编译器有优化机制,也可以避免不必要的拷贝

#include <iostream>
#include <string>class Person {
public:Person(const std::string& name) : name_(name) {}std::string getName() const {return name_;  // 按值返回字符串}private:std::string name_;
};int main() {Person p("Alice");std::string name = p.getName();  // 按值获取 namestd::cout << "名字:" << name << std::endl;  // 输出:名字:Alicereturn 0;
}

返回引用场景1:返回类成员的引用 

如果想要修改类的成员变量,那么可以返回该成员的引用

#include <iostream>class Person {
public:Person(const std::string& name) : name_(name) {}std::string& getName() {  // 返回成员变量的引用return name_;}private:std::string name_;
};int main() {Person p("Alice");p.getName() = "Bob";  // 修改成员变量std::cout << "名字:" << p.getName() << std::endl;  // 输出:名字:Bobreturn 0;
}

返回引用场景2:返回静态变量的引用

因为静态变量的生命周期是贯穿整个程序的,所以返回它的引用是十分安全

#include <iostream>int& getStaticVariable() {static int x = 42; return x;
}int main() {int& ref = getStaticVariable();ref = 10;  // 修改静态变量的值std::cout << "静态变量的值:" << getStaticVariable() << std::endl; return 0;
}

总结反思

  • 避免返回局部对象的引用或者指针,局部对象会在函数返回后销毁,返回后的引用会导致悬空引用问题
  • 优先按值返回对象,因为现代编译器有优化,可以避免不必要的拷贝
  • 返回引用的场景,返回类成员的引用和静态成员变量引用 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/752.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

线程基础知识、jmm(Java内存模型)

目录 线程基础知识 并发与并行 进程和线程 线程优先级 创建线程的方式主要有三种 休眠 作出让步 join() 方法 线程协作注意什么 理解线程状态 选择合适的协作工具 共享资源的访问控制 避免竞争条件 创建线程几种方式 线程状态&#xff0c;状态之间切换 新建&…

计算机毕业设计Spark+大模型股票推荐系统 股票预测系统 股票可视化 股票数据分析 量化交易系统 股票爬虫 股票K线图 大数据毕业设计 AI

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

MyBatis-Plus快速入门:从安装到第一个Demo

一、前言 在现代 Java 应用程序中&#xff0c;数据访问层的效率与简洁性至关重要。MyBatis-Plus 作为 MyBatis 的增强工具&#xff0c;旨在简化常见的数据操作&#xff0c;提升开发效率。它提供了丰富的功能&#xff0c;如自动生成 SQL、条件构造器和简单易用的 CRUD 操作&…

零基础Java第十二期:类和对象(三)

目录 一、static成员&#xff08;补&#xff09; 1.1. static修饰成员方法 1.2. static成员变量初始化 二、代码块 2.1. 静态代码块和实例代码块 ​三、对象的打印 一、static成员&#xff08;补&#xff09; 1.1. static修饰成员方法 public class Linear {public st…

Topaz Video AI for Mac 视频无损放大软件安装教程【保姆级,操作简单轻松上手】

Mac分享吧 文章目录 Topaz Video AI for Mac 视频无损放大软件 安装完成&#xff0c;软件打开效果一、Topaz Video AI 视频无损放大软件 Mac电脑版——v5.3.5⚠️注意事项&#xff1a;1️⃣&#xff1a;下载软件2️⃣&#xff1a;安装软件&#xff0c;将安装包从左侧拖入右侧文…

Java基础语法①Java特点和环境安装

目录 1. Java的概念和用途 1.1 Java的概念和发展史 1.2 Java的重要性 1.3 Java的特点 2. Java环境 2.1 JVM 和 JDK 2.2 Java环境安装 2.3 安装IntelliJ IDEA并使用 写在前面&#xff1a;本人已经学习了C/C方向的内容&#xff0c;大二结束找到实习回学校后还有时间&…

atest v0.0.18 提供了强大、灵活的 HTTP API Mock 功能

atest 发布 v0.0.18 atest 是致力于帮助开发者持续保持高质量 API 的开源接口工具。 你可以在命令行终端或者容器中启动&#xff1a; docker run -p 8080:8080 ghcr.io/linuxsuren/api-testing:v0.0.18 亮点 在开源之夏 2024 中 atest 增加了基于 MySQL 的测试用例历史的支持HT…

ele-table表格列表内,双击编辑部分信息(el-table组件同理)

说明 ele-table 为公司内部组件&#xff0c;重点看 【主要代码】 部分代码 ele-table表格列表内&#xff1a;双击需要编辑的区域&#xff0c;编辑部分信息 实现 双击需要编辑的数据&#xff0c;展示输入框/日期选择&#xff0c; 展示输入框&#xff1a;修改完之后&#xff0c;按…

开源的GPT-4o模型使用指南,Mini-Omni2集视觉、语音和双工能力于一体的

1. Mini-Omni2 是什么&#xff1f; Mini-Omni2 是一个开源的多模态语言模型&#xff0c;旨在模拟 GPT-4o 的视觉、语音和文本处理能力。它能够理解和生成视觉、听觉和文本内容&#xff0c;并支持实时的语音输出和灵活的双工交互。 Mini-Omni2 由清华大学和 Inspirai 的研究人…

Serverless + AI 让应用开发更简单

本文整理自 2024 云栖大会&#xff0c;阿里云智能高级技术专家&#xff0c;史明伟演讲议题《Serverless AI 让应用开发更简单》 随着云计算和人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;企业对于高效、灵活且成本效益高的解决方案的需求日益增长。本文旨在…

正式开源:从 Greenplum 到 Cloudberry 迁移工具 cbcopy 发布

Cloudberry Database 作为 Greenplum 衍生版本和首选开源替代&#xff0c;由 Greenplum 原始团队成员创建&#xff0c;与 Greenplum 保持原生兼容&#xff0c;并能实现无缝迁移&#xff0c;且具备更新的 PostgreSQL 内核和更丰富的功能。GitHub: https://github.com/cloudberry…

基于人工智能的搜索和推荐系统

互联网上的搜索历史分析和用户活动是个性化推荐的基础&#xff0c;这些推荐已成为电子商务行业和在线业务的强大营销工具。随着人工智能的使用&#xff0c;在线搜索也在改进&#xff0c;因为它会根据用户的视觉偏好提出建议&#xff0c;而不是根据每个客户的需求和偏好量身定制…

SpringBoot starter机制

1、出现的背景 在spring boot之前&#xff0c;想要在spring中集成redis&#xff0c;需要做的事情包括&#xff1a; &#xff08;1&#xff09;引入jar包&#xff1a;包括redis的jar包&#xff0c;redis依赖的jar包&#xff0c;相应的版本&#xff0c;与spring版本是否兼容等 …

在 JavaScript 中,`Array.prototype.filter` 方法用于创建一个新数组,该数组包含通过测试的所有元素

文章目录 1、概念在你的代码中的作用示例总结 2、实战3、formattedProducts4、filteredProducts 1、概念 在 JavaScript 中&#xff0c;Array.prototype.filter 方法用于创建一个新数组&#xff0c;该数组包含通过测试的所有元素。具体来说&#xff0c;filter 方法会遍历数组中…

通过源码分析类加载器里面可以加载的类

类列表 每一个ClassLoader里面的类列表&#xff0c;类的数量都是固定的。 对上一节中的dex反编译 使用DexClassLoader类动态加载插件dex   利用jadx对dex进行反编译可以看到有哪些类 源码分析 BaseDexClassLoader 从BaseDexClassLoader类加载器开始分析 在BaseDexClassLoade…

系统架构设计师(软考高级)一站式通关课程

系统架构设计师&#xff08;软考高级&#xff09; 一站式通关课程 系统架构设计师&#xff08;软考高级&#xff09;教程&#xff08;三连评论分享链接&#xff01;&#xff09; &#x1f3af; 课程亮点&#xff1a; 全面覆盖&#xff1a; 从基础概念到高级应用&#xff0c;涵盖…

C++ 优先算法——盛最多水的容器(双指针)

目录 题目&#xff1a;盛最多水的容器 1. 题目解析 2. 算法原理 3. 代码实现 题目&#xff1a;盛最多水的容器 1. 题目解析 题目截图: 如图所示&#xff1a; 水的高度一定是由较低的那条线的高度决定的&#xff1a;例1图中&#xff0c;是由7决定的&#xff0c;然后求出…

SQL-lab靶场less1-4

说明&#xff1a;部分内容来源于网络&#xff0c;如有侵权联系删除 前情提要&#xff1a;搭建sql-lab本地靶场的时候发现一些致命的报错&#xff1a; 这个程序只能在php 5.x上运行&#xff0c;在php 7及更高版本上&#xff0c;函数“mysql_query”和一些相关函数被删除&#xf…

Golang | Leetcode Golang题解之第535题TinyURL的加密与解密

题目&#xff1a; 题解&#xff1a; import "math/rand"type Codec map[int]stringfunc Constructor() Codec {return Codec{} }func (c Codec) encode(longUrl string) string {for {key : rand.Int()if c[key] "" {c[key] longUrlreturn "http:/…

使用 Elasticsearch 进行语义搜索

Elasticsearch 是一款功能强大的开源搜索引擎&#xff0c;可用于全文搜索、分析和数据可视化。传统上&#xff0c;Elasticsearch 以其执行基于关键字/词汇的搜索的能力而闻名&#xff0c;其中文档基于精确或部分关键字匹配进行匹配。然而&#xff0c;Elasticsearch 已经发展到支…