首页 > 编程语言 >C++继承基础

C++继承基础

时间:2024-09-07 19:24:51浏览次数:5  
标签:函数 继承 基类 基础 C++ 析构 派生类 拷贝 构造函数

虚函数默认实参

C++默认实参是靠编译器实现的,因此默认实参不参与动态绑定,默认实参有静态类型决定。

访问控制

每个类分别控制自己的成员初始化过程,还分别控制其成员对于派生类来说是否可访问,友元不继承

成员:

  • protected:派生类可见、自己、friend可见
  • public:所有可见
  • private:自己、friend可见

继承:

public protected private
公有继承 public protected 不可见
私有继承 private private 不可见
保护继承 protected protected 不可见

继承中的作用域

  • 每个类定义自己的作用域
  • 当存在继承关系时,派生类的作用域嵌套在基类的作用域之内。
  • 名字查找首先发生在派生类的作用域内,若无法解析则编译器将继续在外层的基类作用域中寻找该名字的定义。

与其他作用域一样,派生类也能重用定义在其直接基类或间接基类中的名字,此时定义在内层作用域的名字将隐藏定义在外层作用域的名字。

函数调用的解析过程:假设调用p->mem()

  • 首先确定p的静态类型。
  • p的静态类型对应的类中查找mem。如果找不到,则以此在直接基类中不断查找直至到达继承链的顶端。如果依然找不到则编译器报错。
  • 若找到了mem,就进行常规的类型检查以确认本次调用是否合法。
  • 若调用合法,则编译器将根据mem是否是虚函数而产生不同的代码:
    • 如果mem是虚函数且通过指针或者引用进行的调用,则编译器产生的代码将在运行时确定到底运行该虚函数的哪个版本,依据对象的动态类型。
    • 否则,进行常规的函数调用。

构造函数与拷贝控制

虚析构函数

在C++中,析构函数应设为虚函数的主要原因是为了确保正确的资源释放,特别是在使用多态时。当基类的析构函数为虚函数时,通过基类指针删除派生类对象时,能够正确调用派生类的析构函数,确保派生类对象的资源被正确释放。

如果基类的析构函数不是虚函数,通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数。这样,派生类的资源就可能无法正确释放,导致资源泄漏或其他问题。

#include <iostream>

class Base {
public:
    ~Base() {
        std::cout << "Base destructor called" << std::endl;
    }
};

class Derived : public Base {
public:
    ~Derived() {
        std::cout << "Derived destructor called" << std::endl;
    }
};

int main() {
    Base* ptr = new Derived();
    delete ptr;  // 只调用了Base的析构函数,没有调用Derived的析构函数
    return 0;
}

如果基类的析构函数不是虚函数,则delete一个指向派生类的基类指针将产生未定义的行为。

虚析构函数将阻止合成移动操作,即使通过=default的形式使用合成的版本。

合成的拷贝构造函数

  • 合成的拷贝构造函数首先会调用基类的拷贝构造函数,以拷贝派生类对象的基类子对象。这个调用会遵循基类中定义的拷贝构造函数的逻辑。
  • 对于派生类中定义的成员变量,合成的拷贝构造函数会按照成员的定义顺序逐个进行拷贝。这种拷贝是成员级的浅拷贝,即简单地将一个对象的成员值复制到另一个对象的对应成员中。

删除的拷贝控制

  • 如果基类中的默认构造函数、拷贝构造函数、拷贝赋值运算符、析构函数、移动构造函数、移动赋值运算符是被删除的的或者不可访问,则派生类中对应的成员将是被删除的。原因是编译器不能使用基类成员来执行派生类对象基类部分的构造、赋值、移动或者销毁。
  • 默认构造、拷贝构造、移动构造涉及临时对象的析构,因此当基类有一个不可访问或者删掉的析构函数时,则派生类中合成的默认构造函数

派生类的拷贝控制成员

派生类的拷贝、移动成员在拷贝和移动自由成员的同时,也需要拷贝和移动基类部分的成员。当派生类定义了拷贝或移动操作时,该操作负责拷贝或移动包括基类部分成员在内的整个对象。

#include <iostream>
#include <string>

// 基类
class Base {
public:
    std::string name;
    Base(const std::string& n) : name(n) {}
    
    // 拷贝构造函数
    Base(const Base& other) : name(other.name) {
        std::cout << "Base 拷贝构造函数\n";
    }

    // 移动构造函数
    Base(Base&& other) noexcept : name(std::move(other.name)) {
        std::cout << "Base 移动构造函数\n";
    }
};

// 派生类
class Derived : public Base {
public:
    int value;
    
    Derived(const std::string& n, int v) : Base(n), value(v) {}

    // 拷贝构造函数
    Derived(const Derived& other) : Base(other), value(other.value) {
        std::cout << "Derived 拷贝构造函数\n";
    }

    // 移动构造函数
    Derived(Derived&& other) noexcept : Base(std::move(other)), value(other.value) {
        std::cout << "Derived 移动构造函数\n";
    }
};

int main() {
    Derived d1("Example", 42);
    Derived d2 = d1;  // 调用拷贝构造函数
    Derived d3 = std::move(d1);  // 调用移动构造函数
}

派生类的析构函数

派生类的析构函数只负责清理派生类自己分配的资源,而基类的资源清理是通过隐式调用基类的析构函数来完成的。

  • 当派生类的对象被销毁时,系统会自动调用派生类的析构函数。
  • 派生类的析构函数执行完后,系统会自动调用基类的析构函数来清理基类部分的成员。
#include <iostream>

// 基类
class Base {
public:
    Base() { std::cout << "Base 构造函数\n"; }
    ~Base() { std::cout << "Base 析构函数\n"; }
};

// 派生类
class Derived : public Base {
public:
    Derived() { std::cout << "Derived 构造函数\n"; }
    ~Derived() { std::cout << "Derived 析构函数\n"; }
};

int main() {
    Derived d;
}

继承构造函数

class Derived : public Base{
public:
    using Base::Base; 
}

通常using声明语句只是令某个名字在当前作用域中可见,而当作用域构造函数是将令编译器产生代码:

Derived(params): Base(args){}

标签:函数,继承,基类,基础,C++,析构,派生类,拷贝,构造函数
From: https://www.cnblogs.com/sfbslover/p/18402048

相关文章

  • 【C++算法全真练习题】迷宫问题
    目录题目描述思路AC解答题目描述‌题目描述‌:‌给定一个二维迷宫,‌其中 0 表示可以走的路,‌1 表示障碍物。‌起点坐标为 (0,0),‌终点坐标为 (m-1,n-1),‌其中 m 和 n 分别是迷宫的行数和列数。‌你需要使用广度优先搜索(‌BFS)‌找到从起点到终点的一条路径......
  • 【网络安全】服务基础第二阶段——第三节:Linux系统管理基础----Linux用户与组管理
    目录一、用户与组管理命令1.1用户分类与UID范围1.2用户管理命令1.2.1useradd1.2.2groupadd1.2.3usermod1.2.4userdel1.3组管理命令1.3.1groupdel1.3.2查看密码文件/etc/shadow1.3.4passwd1.4Linux密码暴力破解二、权限管理2.1文件与目录权限2.2目......
  • 【网络安全】服务基础第二阶段——第二节:Linux系统管理基础----Linux统计,高阶命令
    目录一、Linux高阶命令1.1管道符的基本原理1.2重定向1.2.1输出重定向1.2.2输入重定向1.2.3wc命令基本用法1.3别名1.3.1which命令基本语法1.3.2alias命令基本语法1.4压缩归档tar1.4.1第一种:gzip压缩1.4.2第二种:bzip压缩1.5tar命令二、VIM编辑器使用2......
  • C++常见异常汇总(二): undefined reference to
    文章目录1、undefinedreferencetoA2、undefinedreferenceto`vtable2.1模版函数定义方案1:定义与实现均一起定义在头文件中2.2模版函数定义方案2:定义的同一个文件中,显示声明具体类型3、multipledefinitionof1、undefinedreferencetoA检查所有main相......
  • C++复习day06
    一、内存管理1.课件上关于内存分配的题目intglobalVar=1;staticintstaticGlobalVar=1;voidTest(){staticintstaticVar=1;intlocalVar=1;intnum1[10]={1,2,3,4};charchar2[]="abcd";constchar*pChar3="abcd";int*ptr1=(in......
  • js逆向基础14异步编程3
    上节课遗留.finally.finally()方法不管Promise对象最后的状态如何都会执行.finally()方法的回调函数不接收任何的参数,也就是你在.finally()函数中是没法知道Promise最终的状态是resolved还是rejected的它最终返回的默认会是一个上一次的Promise对象值,不过抛出的是一个异常......
  • 信息学奥赛初赛天天练-85-NOIP2014普及组-基础题4-链表、随机存取、顺序存取、二分查
    信息学奥赛初赛天天练-85-NOIP2014普及组-基础题4-链表、随机存取、顺序存取、二分查找、二分比较、循环结构、图领奖PDF文档公众号回复关键字:202409071NOIP2014普及组基础题49下列选项中不属于图像格式的是()AJPEG格式BTXT格式CGIF格式DPNG格式1......
  • windows C++-并行编程-转换使用异常处理的 OpenMP 循环以使用并发运行时
    此示例演示如何将执行异常处理的OpenMP并行for循环转换为使用并发运行时异常处理机制。在OpenMP中,在并行区域中引发的异常必须由同一线程在同一区域中捕获和处理。未处理的异常处理程序会捕获逃离并行区域的异常,默认情况下会终止进程。在并发运行时中,在传递给任务组(例......
  • c++元对象实现
    c++元对象实现在C++中,元对象技术通常指的是运行时检查类型信息和对象信息的能力。C++11标准引入了typetraits和reflection的概念,允许我们在编译时获取和使用类型信息。下面是一个简单的C++类,使用了C++11的typetraits和C++17的std::any来实现元对象:  #include<iostrea......
  • C++ 调用 C# - AOT 方案
    一些C#AOT编译的笔记,整体感觉:简单很方便,但限制也很多,适用于比较单一的功能点。跨语言调用C#代码的新方式-DllExport-InCerry-博客园在.NET8下,直接添加<PublishAot>true</PublishAot>就可以支持了,需要注意一些限制,这里比较相关的是,不能使用Newtonsoft.Json做序列......