首页 > 编程语言 >C++(继承)

C++(继承)

时间:2024-10-11 19:23:02浏览次数:12  
标签:name 继承 cout Father C++ 基类 public 构造函数

1. 继承

1.1 基础使用

继承就是在一个已经存在的类的基础上新建立一个类,新创建的类拥有之前类的特性。继承是面向对象的三大特性之一,体现了代码复用的思想。

  • 已经存在的类被称为“基类 Base Class”或“父类”
  • 新创建的类被称为“派生类”或“子类Sub Class”

下面是一个最简单的单一继承:

#include <iostream>

using namespace std;

/**
 * @brief The Father class 基类
 */
class Father
{
public:
    string first_name = "欧阳";

    void work()
    {
        cout << "我是一个厨师" << endl;
    }
};

/**
 * @brief The Son class 派生类
 */
class Son:public Father
{

};


int main()
{
    Son s;
    cout << s.first_name << endl;
    s.work();

    return 0;
}

上面的继承在实际开发中没有任何意义,因为派生类对基类没有做出任何改变。通常派生类都会在基类的基础上做出一些代码修改或增加。

#include <iostream>

using namespace std;

/**
 * @brief The Father class 基类
 */
class Father
{
public:
    string first_name = "欧阳";

    void work()
    {
        cout << "我是一个厨师" << endl;
    }
};

/**
 * @brief The Son class 派生类
 */
class Son:public Father
{
public:
    void play() // 增加的代码
    {
        cout << "打游戏" << endl;
    }

    // 【函数隐藏】与基类的work函数签名相同
    void work()
    {
        cout << "我要做码农" << endl;
    }
};


int main()
{
    Son s;
    cout << s.first_name << endl;
    s.work();
    // 也可以调用被隐藏的基类函数
    s.Father::work();
    s.play();

    return 0;
}

基类和派生类是相对的,一个类既可以作为基类又可以作为派生类,取决于两个类之间的关系。派生类是基类的具象化,基类是派生类的抽象化。

对于基类的私有成员,派生类可以继承,但是无法直接访问,访问需要通过基类提供的接口。

#include <iostream>

using namespace std;


class Father
{
private:
    string first_name = "欧阳";

public:
    string get_first_name() const
    {
        return first_name;
    }

    void set_first_name(string fn)
    {
        first_name = fn;
    }
};


class Son:public Father
{
public:
    Son()
    {
//        cout << first_name << endl; 错误
    }
};

class Test
{

};


int main()
{
    Son s;
//    cout << s.first_name << endl; 错误
    Test t;
    Father f;

    cout << sizeof(f) << endl; // 4
    cout << sizeof(s) << " " << sizeof(t) << endl; // 4 1

    // 通过基类接口可以访问继承的基类私有成员
    s.set_first_name("司马");
    cout << s.get_first_name() << endl; // 司马

    return 0;
}

1.2 构造函数

派生类的构造函数必须直接或间接调用基类的任意一个构造函数。如果程序员不手动在派生类的构造函数中调用基类的构造函数,编译器会尝试调用基类的无参构造函数。

#include <iostream>

using namespace std;


class Father
{
public:
    Father()
    {
        cout << "基类的构造函数" << endl;
    }

};


class Son:public Father
{
public:
    // 编译器增加以下代码
    Son():Father() // 透传构造:在派生类的构造函数中调用基类的构造函数
    {

    }
};

int main()
{
    Son s;

    return 0;
}

编译器无法处理所有情况,例如基类没有无参构造函数。

#include <iostream>

using namespace std;


class Father
{
private:
    string name;
public:
    Father(string name)
    {
        this->name = name;
        cout << "基类的构造函数" << endl;
    }

    void set_name(string name)
    {
        this->name = name;
    }

    string get_name() const
    {
        return name;
    }
};


class Son:public Father
{
public:
    // 编译器只会自动调用基类的无参构造
    // 基类现在没有无参构造
    // 报错!
};

int main()
{
    Son s;

    return 0;
}

上面的情况必须程序员手动在派生类的构造函数中直接或间接调用基类的构造函数:

  • 透传构造
  • 委托构造
  • 继承构造(C++11)

注:构造函数和析构函数是不能被继承的,继承构造仅仅是一种用法的名称,并没有继承构造函数。

1.2.1 透传构造

透传构造属于在派生类的构造函数中直接调用基类的构造函数。

#include <iostream>

using namespace std;


class Father
{
private:
    string name;
    int age;

public:
    Father(string name)
    {
        this->name = name;
        age = 1;
    }

    Father(string name,int age)
        :name(name),age(age){}

    void show()
    {
        cout << name << " " << age << endl;
    }
};


class Son:public Father
{
public:
    Son():Father("佚名") // 通过透传构造调用基类的一参构造函数
    {

    }

    Son(string name,int age):
        Father(name,age) // 通过透传构造调用基类的二参构造函数
    {}
};

int main()
{
    Son s;
    s.show();
    Son s1("李白",29);
    s1.show();

    return 0;
}

1.2.2 委托构造

委托构造本身可以脱离继承使用,指的是在某个类中构造函数A可以调用构造函数B。在继承中就可以让构造函数A调用构造函数B,构造函数B透传调用基类的构造函数,这样构造函数A就间接调用了基类的构造函数。

#include <iostream>

using namespace std;


class Father
{
private:
    string name;
    int age;

public:
    Father(string name)
    {
        this->name = name;
        age = 1;
    }

    Father(string name,int age)
        :name(name),age(age){}

    void show()
    {
        cout << name << " " << age << endl;
    }
};


class Son:public Father
{
public:
    // 构造函数A委托调用构造函数B(Son的二参构造函数)
    Son():Son("佚名",2)
    {

    }

    // 构造函数B
    Son(string name,int age):
        Father(name,age) // 通过透传构造调用基类的二参构造函数
    {}
};

int main()
{
    Son s;
    s.show();
    Son s1("李白",29);
    s1.show();

    return 0;
}

委托构造要避免委托闭环。

#include <iostream>

using namespace std;


class Father
{
private:
    string name;
    int age;

public:
    Father(string name)
    {
        this->name = name;
        age = 1;
    }

    Father(string name,int age)
        :name(name),age(age){}

    void show()
    {
        cout << name << " " << age << endl;
    }
};


class Son:public Father
{
public:
    // 构造函数A委托调用构造函数B(Son的二参构造函数)
    Son():Son("佚名",2)
    {

    }

    // 构造函数B委托调用构造函数A
    Son(string name,int age):
        Son()
    {}
};

int main()
{
    Son s;
    s.show();
    Son s1("李白",29);
    s1.show();

    return 0;
}

在上面代码中,程序会卡在第46行,因为构造函数执行不完,互相委托形成闭环。

1.2.3 继承构造(熟悉)

继承构造是C++11的新特性,并不是表示能继承构造函数,而是一种简便的写法,可以一句话实现一种透传构造。

在派生类中使用下面的语句,可以让派生类生成n(n为基类的构造函数数量)个构造函数,同时这n个构造函数参数与基类的n个构造函数相同,每个派生类的构造函数都透传参数相同的基类构造函数。

#include <iostream>

using namespace std;


class Father
{
private:
    string name;
    int age;

public:
    Father(string name)
    {
        this->name = name;
        age = 1;
    }

    Father(string name,int age)
        :name(name),age(age){}

    void show()
    {
        cout << name << " " << age << endl;
    }
};


class Son:public Father
{
public:
    // 自动创建2个构造函数
    // 这两个构造函数的参数:
    // (1) string
    // (2) string int
    // 前者透传Father(string name)
    // 后者透传Father(string name,int age)
    using Father::Father;
};

int main()
{
    Son s("王维");
    s.show();
    Son s1("李白",29);
    s1.show();

    return 0;
}

1.3 对象的创建与销毁流程

#include <iostream>

using namespace std;

/**
 * @brief The Value class
 * 作为其他类的变量使用
 *
 */
class Value
{
private:
    string name;

public:
    Value(string name):name(name)
    {
        cout << name << "创建了" << endl;
    }

    ~Value()
    {
        cout << name << "销毁了" << endl;
    }
};

class Father
{
public:
    Value value = Value("Father类的成员变量");
    static Value s_value;

    Father()
    {
        cout << "Father类的构造函数" << endl;
    }

    ~Father()
    {
        cout << "Father类的析构函数" << endl;
    }
};

Value Father::s_value = Value("Father类的静态成员变量");

class Son:public Father
{
public:
    Value value = Value("Son类的成员变量");
    static Value s_value;

    Son()
    {
        cout << "Son类的构造函数" << endl;
    }

    ~Son()
    {
        cout << "Son类的析构函数" << endl;
    }
};

Value Son::s_value = Value("Son类的静态成员变量");

int main()
{
    cout << "主函数开始执行" << endl;
    Son* s = new Son;
    delete s;
    cout << "主函数结束执行" << endl;
    return 0;
}

在上面的结果中可以得到如下规律:

1. 创建与销毁过程是对称的。

2. 静态的周期贯穿整个程序。

3. 创建过程中,先执行基类;销毁过程中,后执行基类。因为派生类依赖于基类。

虽然推荐理解记忆,但是也可以直接记忆,根据自己的学习习惯自行选择。

1.4 多重继承

1.4.1 基础使用(掌握)

C++支持多继承,即一个派生类可以有多个基类。

#include <iostream>

using namespace std;

class Sofa
{
public:
    void sit()
    {
        cout << "能坐着" << endl;
    }
};

class Bed
{
public:
    void lay()
    {
        cout << "能躺着" << endl;
    }
};

/**
 * @brief The SofaBed class
 * 多重继承
 */
class SofaBed:public Sofa,public Bed
{

};


int main()
{
    SofaBed sb;
    sb.lay();
    sb.sit();

    return 0;
}

1.4.2 二义性

1.4.2.1 基类拥有同名成员

当多重继承的两个基类拥有同名成员时,编译器会无法区分,因此出现二义性问题。

解决方法:在二义性的成员前使用 类名:: 修饰。

#include <iostream>

using namespace std;

class Sofa
{
public:
    void sit()
    {
        cout << "能坐着" << endl;
    }

    void position()
    {
        cout << "放在客厅" << endl;
    }
};

class Bed
{
public:
    void lay()
    {
        cout << "能躺着" << endl;
    }

    void position()
    {
        cout << "放在卧室" << endl;
    }
};

/**
 * @brief The SofaBed class
 * 多重继承
 */
class SofaBed:public Sofa,public Bed
{

};


int main()
{
    SofaBed sb;
    sb.lay();
    sb.sit();
//    sb.position(); 错误:二义性
    // 区分二义性
    sb.Sofa::position();
    sb.Bed::position();

    return 0;
}

1.4.2.2 菱形继承(钻石继承)

如果一个类A有两个派生类B和C,类D同时继承B和C,此时就出现了菱形继承,当对象D调用A的成员时,会产生二义性。

#include <iostream>

using namespace std;

class Furniture
{
public:
    void func()
    {
        cout << "我是家具" << endl;
    }
};

class Sofa:public Furniture{};

class Bed:public Furniture{};

class SofaBed:public Sofa,public Bed{};


int main()
{
    SofaBed sb;
//    sb.func(); 错误:二义性

    return 0;
}

解决方法1:使用类名::进行区分。

解决方法2:虚继承。

#include <iostream>

using namespace std;

class Furniture
{
public:
    void func()
    {
        cout << "我是家具" << endl;
    }
};

class Sofa:virtual public Furniture{};

class Bed:virtual public Furniture{};

class SofaBed:public Sofa,public Bed{};


int main()
{
    SofaBed sb;
    sb.func();
    sb.Bed::func();
    sb.Sofa::func();

    return 0;
}

当使用虚继承时,Furniture类内部会生成一张虚基类表(程序运行时加载进内存),内部存储Furniture的成员的调用地址,每个Sofa和Bed类对象内部都会有一个隐藏成员指向虚基类表。

SofaBed对象同时继承了Sofa和Bed类的虚基类表指针,调用func()函数时查表比对,防止二义性的出现。

虚继承的本质是查表,因此会降低调用效率。

#include <iostream>

using namespace std;

class Furniture
{
public:
    void func()
    {
        cout << "我是家具" << endl;
    }
};

class Sofa:virtual public Furniture{};

class Bed:virtual public Furniture{};

class SofaBed:public Sofa,public Bed{};


int main()
{
    Furniture f;
    cout << sizeof(f) << endl; // 1 占位
    Sofa s;
    cout << sizeof(s) << endl; // 4 虚基类表指针
    Bed b;
    cout << sizeof(b) << endl; // 4 虚基类表指针

    SofaBed sb;
    cout << sizeof(sb) << endl; // 8 两个虚基类表指针
    sb.func();
    sb.Bed::func();
    sb.Sofa::func();

    return 0;
}

1.5 权限

1.5.1 权限修饰符

本类中

派生类中

全局(例如主函数中)

private 私有(默认)

X

X

protected 保护

X

public 公有

#include <iostream>

using namespace std;

class Father
{
protected:
    string s1 = "Father的保护权限";

public:
    string s2 = "Father的公有权限";

    void func()
    {
        cout << s1 << endl;
    }
};

class Son:public Father
{
public:
    void test()
    {
        cout << s1 << endl;
        cout << s2 << endl;
    }
};


int main()
{
    Son s;
    s.test();
    Father f;
    f.func();
//    cout << f.s1 << endl; 错误

    return 0;
}

1.5.2 不同权限的继承

三种权限修饰符可以修饰继承:

  • 公有继承
  • 保护继承
  • 私有继承
1.5.2.1 公有继承

使用的最多的一种继承,之前的继承都是公有继承。在公有继承中,基类的所有成员均可以被派生类继承,但是基类的私有成员无法被派生类直接访问,基类的保护成员和公有成员继承到派生类中作为派生类的保护成员和公有成员(权限不变)。

#include <iostream>

using namespace std;

class Father
{
private:
    string s0 = "Father的私有权限";

protected:
    string s1 = "Father的保护权限";

public:
    string s2 = "Father的公有权限";
};

class Son:public Father
{
public:
    Son()
    {
//        cout << s0 << endl; 错误
        cout << s1 << endl;
        cout << s2 << endl;
    }
};

class Grandson:public Son
{
public:
    Grandson()
    {
        cout << s1 << endl;
    }
};


int main()
{
    Son s;
    cout << sizeof(s) << endl; // 12
//    cout << s.s1 << endl; 错误
    cout << s.s2 << endl;
    Grandson gs;

    return 0;
}
1.5.2.2 保护继承(掌握)

在保护继承中,基类的所有成员均可以被派生类继承,但是基类的私有成员无法被派生类直接访问,基类的保护成员和公有成员继承到派生类中作为派生类的保护成员。

#include <iostream>

using namespace std;

class Father
{
private:
    string s0 = "Father的私有权限";

protected:
    string s1 = "Father的保护权限";

public:
    string s2 = "Father的公有权限";
};

class Son:protected Father
{
public:
    Son()
    {
//        cout << s0 << endl; 错误
        cout << s1 << endl;
        cout << s2 << endl;
    }
};

class Grandson:public Son
{
public:
    Grandson()
    {
        cout << s1 << endl;
        cout << s2 << endl;
    }
};


int main()
{
    Son s;
    cout << sizeof(s) << endl; // 12
//    cout << s.s1 << endl; 错误
//    cout << s.s2 << endl; 错误
    Grandson gs;

    return 0;
}
1.5.2.3 私有继承(掌握)

在私有继承中,基类的所有成员均可以被派生类继承,但是基类的私有成员无法被派生类直接访问,基类的保护成员和公有成员继承到派生类中作为派生类的私有成员。

#include <iostream>

using namespace std;

class Father
{
private:
    string s0 = "Father的私有权限";

protected:
    string s1 = "Father的保护权限";

public:
    string s2 = "Father的公有权限";
};

class Son:private Father
{
public:
    Son()
    {
//        cout << s0 << endl; 错误
        cout << s1 << endl;
        cout << s2 << endl;
    }
};

class Grandson:public Son
{
public:
    Grandson()
    {
//        cout << s1 << endl; 错误
//        cout << s2 << endl; 错误
    }
};


int main()
{
    Son s;
    cout << sizeof(s) << endl; // 12
//    cout << s.s1 << endl; 错误
//    cout << s.s2 << endl; 错误
    Grandson gs;

    return 0;
}

标签:name,继承,cout,Father,C++,基类,public,构造函数
From: https://blog.csdn.net/2301_77143270/article/details/142831049

相关文章

  • 【C++】二叉搜索树+变身 = 红黑树
    ......
  • 实验一,现代C++编程初体验
    一、实验目的 体验C++的标准库,算法库用法。数据表示,分支循环,函数和标准库等,编程解决简单基础问题。二、实验准备 第二章C++语言简单设计第三章函数第九章函数模板 三、实验内容 1.实验任务1代码:1#include<iostream>2#include<string>3#include<vector>......
  • ROS1,用C++实现获取激光雷达数据,并使用gazebo测试
    实现步骤构建一个新的软件包,包名叫做lidar_pkg。cdcatkin_ws/src/catkin_create_pkglidar_pkgroscpprospysensor_msgs输入code,打开vscode在软件包中新建一个节点,节点名叫做lidar_node。在节点中,向ROS大管家NodeHandle申请订阅话题/scan,并设置回调函数为......
  • C++ 读写锁 shared_mutex
    C++17新增了std::shared_mutex,通过shared_mutex可以实现读写锁的功能, 参考网址:https://zh.cppreference.com/w/cpp/thread/shared_mutexshared_mutex可以同时支持多个线程对共享资源同时读,但是只支持一个线程对同一共享资源进行写操作。shared_mutex支持共享锁和独......
  • Dev C++ 安装与使用
    本帖子针对C/C++入门的学生。用该编译器可便于初学者入门C/C++。一、安装1、下载DevC++    百度搜索DevC++的官网点击Download等待下载2、安装点击安装这里并没有发现有支持中文的语言选项(可能是本人在语言选择的时候漏看了,见谅),选择English即可。......
  • C++删除字符串中的所有空格与换行(任意字符)
    删除字符串中的所有空格与换行使用头文件中的remove函数,注意:std::remove不会改变容器的大小,它只是将元素移动到容器的末尾。因此,我们需要调用erase来实际从字符串中删除这些元素。使用std::remove算法,它重排元素,使得要删除的元素(在这里是空格和换行符)被放在序列的末尾,......
  • C++ 算法学习——1.8 倍增与ST表
    在C++中,"倍增"(也称为"指数增长"或"指数级别增长")是一种算法优化技术,它通常用于解决一些需要频繁查询某个区间内的信息的问题,例如在处理动态规划、搜索等算法中。倍增思想的主要目的是通过预处理和存储一些中间结果,以加速后续的查询操作。具体来说,倍增思想通常包括以下步骤:......
  • C++ 算法学习——1.8 单调队列算法
    单调队列(MonotonicQueue)是一种特殊类型的队列,通常用于解决一些数组或序列相关的问题。和单调栈类似,单调队列也具有一些特定的性质,在解决一些问题时非常有用。以下是关于单调队列的一些重要点:定义:单调队列是一种数据结构,队列中的元素满足单调递增或单调递减的性质。应用:单......
  • Chromium 前端form表单提交过程分析c++
    一、本文以一个简单的HTML表单,包含两个文本输入框和一个提交按钮:<formaction="demo_form.php">Firstname:<inputtype="text"name="fname"><br>Lastname:<inputtype="text"name="lname"><br><i......
  • 实验1 现代C++编程初体验
    实验任务1代码1//现代C++标准库、算法库体验2//本例用到以下内容:3//1.字符串string,动态数组容器类vector、迭代器4//2.算法库:反转元素次序、旋转元素5//3.函数模板、const引用作为形参67#include<iostream>8#include<string>9......