c++智能指针

当在C++中处理动态内存分配时,使用智能指针是一种良好的实践,可以帮助避免内存泄漏和悬空指针等问题。C++11引入了三种主要的智能指针:std::unique_ptr、std::shared_ptr和std::weak_ptr。下面是一个简单的代码演示,展示如何使用std::unique_ptr和std::shared_ptr:

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass(int val) : value(val) {
        std::cout << "Constructor called. Value: " << value << std::endl;
    }

    ~MyClass() {
        std::cout << "Destructor called. Value: " << value << std::endl;
    }

    void setValue(int val) {
        value = val;
    }

    int getValue() const {
        return value;
    }

private:
    int value;
};

int main() {
    // 使用 std::unique_ptr
    std::unique_ptr<MyClass> uniquePtr(new MyClass(42));
    uniquePtr->setValue(100);
    std::cout << "Value using unique_ptr: " << uniquePtr->getValue() << std::endl;

    // 使用 std::shared_ptr
    std::shared_ptr<MyClass> sharedPtr = std::make_shared<MyClass>(10);
    sharedPtr->setValue(200);
    std::cout << "Value using shared_ptr: " << sharedPtr->getValue() << std::endl;

    // shared_ptr 的引用计数
    std::cout << "shared_ptr use count: " << sharedPtr.use_count() << std::endl;

    // shared_ptr 的复制
    std::shared_ptr<MyClass> sharedPtr2 = sharedPtr;
    std::cout << "shared_ptr use count after copy: " << sharedPtr.use_count() << std::endl;

    std::weak_ptr<MyClass> weakPtr = sharedPtr;
    if (auto shared = weakPtr.lock()) {
        std::cout << "Value using weak_ptr: " << shared->getValue() << std::endl;
    } else {
        std::cout << "Object no longer exists" << std::endl;
    }

    sharedPtr.reset();

    if (auto shared = weakPtr.lock()) {
        std::cout << "Value using weak_ptr after shared_ptr reset: " << shared->getValue() << std::endl;
    } else {
        std::cout << "Object no longer exists" << std::endl;
    }

    return 0;
    return 0;
}

在这个示例中,我们创建了一个简单的类MyClass,然后演示了如何使用std::unique_ptr和std::shared_ptr来管理动态分配的对象。std::unique_ptr表示独占所有权,而std::shared_ptr允许多个指针共享对象所有权。在使用std::shared_ptr时,我们还展示了引用计数和复制操作的效果。

记得在实际项目中使用智能指针时,要注意避免循环引用,确保正确管理内存资源。

学习C++的最佳方式之一是通过实际项目来深入了解语言的各个方面。以下是一些优秀的C++学习项目,这些项目涵盖了不同的主题和难度级别,可以帮助您快速学习和提高C++编程技能:

  1. C++ Primer Exercises:
    • 项目描述:《C++ Primer》是一本经典的C++教材,书中包含了许多练习题,通过完成这些练习可以加深对C++的理解。
    • 链接:C++ Primer
  2. C++ Programming Challenges:
    • 项目描述:挑战性编程练习可以帮助您提高解决问题的能力和编程技巧。网站如LeetCode、HackerRank和Project Euler提供了大量的C++编程挑战。
    • 链接:LeetCode, HackerRank, Project Euler
  3. C++ Game Development:
    • 项目描述:使用C++开发游戏是一个有趣且具有挑战性的学习项目。可以尝试使用游戏引擎如Unity或Unreal Engine,也可以从头开始使用C++编写游戏。
    • 链接:Unreal Engine, Unity
  4. C++ Data Structures and Algorithms:
    • 项目描述:实现常见的数据结构(如链表、栈、队列、树等)和算法(如排序、搜索、图算法等)是学习C++的重要一步。尝试实现这些数据结构和算法并进行测试。
    • 链接:GeeksforGeeks, Coursera Algorithms Specialization
  5. C++ GUI Applications:
    • 项目描述:使用C++和GUI库(如Qt、wxWidgets等)开发桌面应用程序是一个很好的学习项目。尝试创建简单的图形界面应用程序并添加交互性。
    • 链接:Qt, wxWidgets
  6. Open Source Contributions:
    • 项目描述:参与开源项目是一个很好的学习方式,可以学习他人的代码风格和最佳实践,同时也可以为开源社区做出贡献。
    • 链接:GitHub, GitLab

class Derived : public Base {
cpp 继承里面的public是什么意思

在C++中,继承是一种面向对象编程的重要概念,允许一个类(称为子类或派生类)继承另一个类(称为基类或父类)的属性和行为。在继承中,关键字public、protected和private用于指定派生类继承基类成员的访问权限。

在你提供的代码片段中,class Derived : public Base 表示派生类 Derived 从基类 Base 公开继承(public inheritance)。这意味着:

  1. Public继承:
    • 公开继承(public inheritance)意味着基类的公有成员在派生类中保持为公有成员,保护成员在派生类中保持为保护成员,私有成员在派生类中不可直接访问。
    • 派生类的对象可以被视为基类的对象,可以通过指向派生类对象的基类指针或引用来访问基类的成员。
  2. Private继承:
    • 私有继承(private inheritance)意味着基类的公有和保护成员在派生类中变为私有成员,无法直接访问。
    • 派生类对象不能被视为基类对象,只能通过派生类的成员函数来访问基类的成员。
  3. Protected继承:
    • 保护继承(protected inheritance)意味着基类的公有和保护成员在派生类中变为保护成员,只能在派生类内部或其派生类中访问。
    • 派生类对象不能被视为基类对象,只能通过派生类的成员函数来访问基类的成员。

在大多数情况下,公有继承是最常见和推荐的继承方式,因为它符合面向对象编程的“is-a”关系,即派生类是基类的一种类型。这种继承方式使得代码更具可读性和可维护性。

数据类型的对齐方式

在C++中,数据类型的对齐方式是编译器根据平台架构和优化考虑来决定如何在内存中排列数据。数据类型的对齐方式可以影响内存访问的效率和数据存储的紧凑性。以下是一些常见的数据类型对齐规则:

  1. 默认对齐:
    • 在C++中,数据类型的默认对齐方式通常是按照其自身大小进行对齐。例如,char通常对齐到1字节,int对齐到4字节(32位系统)或8字节(64位系统)等。
  2. 结构体对齐:
    • 结构体的对齐方式是按照结构体中最大成员的大小进行对齐。这样做是为了保证结构体的每个成员都能够被正确对齐,以提高访问效率。
    • 结构体的大小通常是其成员大小的倍数,但也受到编译器的优化和对齐规则的影响。
  3. 指定对齐方式:
    • 可以使用#pragma pack(n)指令或attribute((packed))属性来指定结构体的对齐方式,将其设置为n字节对齐。这样可以控制结构体在内存中的紧凑性,但可能会影响访问效率。
  4. 对齐补齐:
    • 为了满足对齐要求,编译器可能会在结构体成员之间插入填充字节(padding),使得结构体的起始地址和每个成员的地址都符合对齐要求。
    • 对齐补齐可以确保数据的访问效率,但可能会增加内存的占用。
  5. 数据类型对齐的优化:
    • 在一些特定情况下,编译器可能会根据平台和编译选项进行数据类型对齐的优化,以提高程序的性能和效率。

对齐 具体演示

假设我们有以下结构体定义:

#include <iostream>

struct MyStruct {
    char a;
    int b;
    double c;
};

int main() {
    std::cout << "Size of MyStruct: " << sizeof(MyStruct) << " bytes" << std::endl;
    return 0;
}

在这个示例中,MyStruct 结构体包含一个 char 类型的成员 a,一个 int 类型的成员 b,和一个 double 类型的成员 c。

当我们运行这段代码时,我们会得到结构体 MyStruct 的大小。这个大小取决于结构体成员的大小和对齐方式。我们可以通过以下方式观察结构体成员在内存中的布局情况:

  1. char a:char 类型通常占用1字节。
  2. int b:在大多数系统上,int 类型通常占用4字节。
  3. double c:double 类型通常占用8字节。

根据默认对齐规则,结构体的对齐方式通常是按照最大成员的大小进行对齐。在这个例子中,double 类型的 c 成员是最大的,因此结构体 MyStruct 的大小可能会是 1(char) + 3(padding) + 4(int) + 8(double) = 16 字节。

对齐有什么好处坏处

数据类型对齐在计算机编程中是一个重要的概念,它既有好处也有坏处。下面是数据类型对齐的好处和坏处:

好处:

  1. 访问效率提高:数据类型对齐可以提高内存访问的效率。当数据按照合适的边界对齐时,处理器可以更快地访问内存中的数据,因为它们可以直接从内存中读取对齐的数据,而无需额外的处理。
  2. 硬件要求:许多计算机体系结构要求数据按照特定的边界对齐,否则可能会导致性能下降甚至程序崩溃。
  3. 平台兼容性:正确对齐数据类型可以增加代码在不同平台上的可移植性,避免因为对齐问题导致程序在不同平台上表现不一致的情况。
  4. 内存空间利用:对齐数据类型可以减少内存碎片,提高内存空间的利用率。

坏处:

  1. 内存浪费:对齐数据类型可能会导致内存浪费。为了满足对齐要求,编译器可能会在数据类型之间插入填充字节,增加结构体或类的大小。
  2. 结构体大小增加:由于对齐要求,结构体或类的大小可能会比成员变量的总和更大,导致额外的内存消耗。
  3. 复杂性:手动控制数据类型的对齐可能会增加代码的复杂性,尤其是在需要处理不同平台的情况下。

在C++开发中,将代码分为头文件(.h或.hpp)和源文件(.cpp)是一种常见的组织代码的方式。这种分离有助于提高代码的可读性、可维护性和可重用性。以下是一些关于头文件和源文件的最佳实践:

头文件(.h或.hpp文件):

  1. 声明类、结构体和函数:头文件通常包含类、结构体和函数的声明,以便其他文件可以使用这些声明而无需关注实现细节。
  2. 包含必要的头文件:确保头文件中包含所需的其他头文件,以便使用其中定义的类型和函数。
  3. 避免实现细节:尽量避免在头文件中包含实现细节,如变量定义或函数实现。这些应该放在源文件中。
  4. 避免全局变量:尽量避免在头文件中定义全局变量,因为这可能导致重复定义错误。
  5. 使用头文件保护宏:使用头文件保护宏(ifndef、define、endif)防止头文件被多次包含。

源文件(.cpp文件):

  1. 包含对应的头文件:确保源文件包含与之相关的头文件,以便使用其中声明的类、结构体和函数。
  2. 实现函数和方法:在源文件中实现头文件中声明的函数和方法。
  3. 避免全局变量:尽量避免在源文件中定义全局变量,除非有必要。
  4. 避免重复包含:避免在源文件中重复包含头文件,以减少编译时间。
  5. 注释:为了提高代码的可读性,源文件中应包含必要的注释,解释代码的作用和实现细节。

其他建议:

  1. 单一职责原则:每个头文件应该有明确的职责,不要把太多的功能放在一个头文件中。
  2. 模块化设计:将代码分成模块,每个模块有对应的头文件和源文件,以便于组织和维护代码。
  3. 命名规范:遵循良好的命名规范,使代码易于理解和维护。
  4. 代码重用:通过头文件和源文件的组织,可以更容易地重用代码,提高开发效率。

当在C++中处理动态内存分配时,使用智能指针是一种良好的实践,可以帮助避免内存泄漏和悬空指针等问题。C++11引入了三种主要的智能指针:std::unique_ptr、std::shared_ptr和std::weak_ptr。下面是一个简单的代码演示,展示如何使用std::unique_ptr和std::shared_ptr:

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass(int val) : value(val) {
        std::cout << "Constructor called. Value: " << value << std::endl;
    }

    ~MyClass() {
        std::cout << "Destructor called. Value: " << value << std::endl;
    }

    void setValue(int val) {
        value = val;
    }

    int getValue() const {
        return value;
    }

private:
    int value;
};

int main() {
    // 使用 std::unique_ptr
    std::unique_ptr<MyClass> uniquePtr(new MyClass(42));
    uniquePtr->setValue(100);
    std::cout << "Value using unique_ptr: " << uniquePtr->getValue() << std::endl;

    // 使用 std::shared_ptr
    std::shared_ptr<MyClass> sharedPtr = std::make_shared<MyClass>(10);
    sharedPtr->setValue(200);
    std::cout << "Value using shared_ptr: " << sharedPtr->getValue() << std::endl;

    // shared_ptr 的引用计数
    std::cout << "shared_ptr use count: " << sharedPtr.use_count() << std::endl;

    // shared_ptr 的复制
    std::shared_ptr<MyClass> sharedPtr2 = sharedPtr;
    std::cout << "shared_ptr use count after copy: " << sharedPtr.use_count() << std::endl;

    return 0;
}

在这个示例中,我们创建了一个简单的类MyClass,然后演示了如何使用std::unique_ptr和std::shared_ptr来管理动态分配的对象。std::unique_ptr表示独占所有权,而std::shared_ptr允许多个指针共享对象所有权。在使用std::shared_ptr时,我们还展示了引用计数和复制操作的效果。

记得在实际项目中使用智能指针时,要注意避免循环引用,确保正确管理内存资源。弱指针(std::weak_ptr)是C++11标准库中的一种智能指针,通常与std::shared_ptr一起使用,用于解决std::shared_ptr可能导致的循环引用问题。弱指针允许你观察std::shared_ptr所管理的对象,但不拥有对象的所有权。这样可以避免循环引用,因为弱指针不会增加对象的引用计数。

为什么需要弱指针?

当两个对象相互引用,且它们都使用std::shared_ptr来管理对方的内存时,会形成循环引用。这会导致对象永远无法被销毁,因为它们的引用计数永远不会变为零。弱指针的出现就是为了解决这个问题,允许观察共享对象而不会增加引用计数,从而打破循环引用。

使用弱指针的注意事项:

  1. 弱指针不拥有对象的所有权,它只是观察std::shared_ptr所管理的对象。
  2. 使用弱指针之前,需要检查弱指针指向的对象是否仍然存在,可以通过lock()方法将弱指针转换为std::shared_ptr,如果对象存在则返回一个有效的std::shared_ptr,否则返回一个空指针。
  3. 弱指针不会增加对象的引用计数,因此对象可能在任何时候被销毁,需要小心处理潜在的悬空指针问题。
  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值