C++继承与派生

发布于:2025-04-19 ⋅ 阅读:(46) ⋅ 点赞:(0)

继承和派生

现实写照:父亲“派生”出儿子,儿子“继承”自父亲,派生和派生,本质是相同的,只是从不同的角度来描述。

继承和派生在UML中的表示

注意是“空心三角箭头”,从子类【派生的类】指向父类【被继承的类】,父类,也称为“基类”。除了“构造函数”和“析构函数”,父类的所有成员函数,以及数据成员,都会被子类继承!

派生和继承的实现

//Father.h
#pragma once
#include <string>
using namespace std;

class Father{
public:
    Father(const char*name, int age);
    ~Father();
    string getName();
    int getAge();
    string description();
private:
    int age;
    string name;
};
//Father.cpp
#include "Father.h"
#include <sstream>
#include <iostream>
Father::Father(const char*name, int age){
    cout << __FUNCTION__ << endl;
    this->name = name;
    this->age = age;
}

Father::~Father()
{

}

string Father::getName() {
    return name;
}

int Father::getAge() {
    return age;
}

string Father::description() {
    stringstream ret;
    ret << "name:" << name << " age:" << age;
    return ret.str();
}
//Son.h
#pragma once
#include "Father.h"
class Son : public Father {
public:
    Son(const char *name, int age, const char *game);
    ~Son();
    string getGame();
    string description();
    private:
    string game;
};
//Son.cpp
#include "Son.h"
#include <iostream>
#include <sstream>
// 创建Son对象时, 会调用构造函数!
// 会先调用父类的构造函数, 用来初始化从父类继承的数据 
// 再调用自己的构造函数, 用来初始化自己定义的数据
Son::Son(const char *name, int age, const char *game) : Father(name, age) {
    cout << __FUNCTION__ << endl;
    // 没有体现父类的构造函数, 那就会自动调用父类的默认构造函数!!!
    this->game = game;
}
Son::~Son() {

}

string Son::getGame() {
    return game;
}

string Son::description() {
    stringstream ret;
    // 子类的成员函数中, 不能访问从父类继承的private成员
    ret << "name:" << getName() << " age:" << getAge()<< " game:" << game;
    return ret.str();
}
//main.cpp
#include <iostream>
#include "Father.h"
#include "Son.h"
int main(void) {
    Father wjl("王健林", 68);
    Son wsc("王思聪", 32, "电竞");
    cout << wjl.description() << endl;
    // 子类对象调用方法时, 先在自己定义的方法中去寻找, 如果有, 就调用自己定义的方法
    // 如果找不到, 就到父类的方法中去找, 如果有, 就调用父类的这个同名方法
    // 如果还是找不到, 就是发生错误!
    cout << wsc.description() << endl;
    system("pause");
    return 0;
}

子类, 一般会添加自己的数据成员/成员函数,或者, 重新定义从父类继承的方法!!! 子类对象就会调用自己重新定义的方法, 不会调用父类的同名方法。

说明:成员函数,不占用对象的内存空间,但是也被子类继承了!!!

protected(保护)访问权限

为什么要使用protected访问权限?

子类的成员函数中,不能直接访问父类的private成员,虽然这些成员已经被继承下来了,但是却不能访问。只有通过父类的public函数来间接访问,不是很方便。比如,刚才Demo中Father类中的name和age成员。

解决方案:把name和age定义为protected访问访问权限。

效果:Son类的成员函数中,可以直接访问它的父类的protected成员。但是在外部,别人又不能直接通过Son对象来访问这些成员。 一个类, 如果希望, 它的成员, 可以被自己的子类(派生类)直接访问,  但是, 又不想被外部访问那么就可以把这些成员, 定义为 protected访问权限!!!

访问权限总结:public     外部可以直接访问,可以通过对象来访问这个成员

private            外部不可以访问,自己的成员函数内, 可以访问

protected            protectedprivate非常相似

private的唯一区别:protecte: 子类的成员函数中可以直接访问;private:  子类的成员函数中不可以访问。

派生和继承的各种方式

public(公有)继承 [使用最频繁],父类中定义的成员(数据成员和函数成员)被继承后,访问权限不变。public-->public、potected-->protected、private-->private

private(私有)继承父类中定义的成员(数据成员和函数成员)被继承后,访问权限都变成private。private-->private、protected-->private、private-->private      

protected(保护)继承 public-->protected、protected-->protected、private-->private

小结:public 继承全不变、private继承全变私、protected继承只把public降级为protected

什么时候使用继承和派生

准备实现多个类,但是这些类在现实世界中有某种特殊关系(比如:类别与子类别的关系)。例如:人 、女人、男人。如果完全独立的实现这3个类,将有很多重复代码,而且不利于以后的维护。

准备构建一个类,但是这个类与已经开发好的某个类非常相似,而且在现实世界中具有某种特殊关系(比如:类别与子类别的关系)。如果全部重新写这个新类,效率较低,因为有很多东西已经在这个已有的类中实现了。

对多个已经实现的类(这些类有某种特殊关系),进行重构。一般在前两种情况使用,第3种(重构)是不得而为之。

子类对父类成员的访问权限:父与子亲密有间

无论通过什么方式(public、protected、private)继承,在子类内部均可访问父类中的public、protected成员,private成员不可访问(如果想要子类能够访问,就定义为protected),继承方式只影响外界通过子类对父类成员的访问权限。

子类的构造函数

调用父类的哪个构造函数

class Son : public Father {
public:
    // 在子类的构造函数中,显式调用父类的构造函数
    Son(const char *name, int age, const char *game):Father(name, age) {
        this->game = game;
    }
    // 没有显式的调用父类的构造函数,那么会自动调用父类的默认构造函数
    Son(const char *name, const char *game){
        this->game = game;
    }
    ......
};

子类和父类的构造函数的调用顺序

当创建子类对象时, 构造函数的调用顺序:静态数据成员的构造函数->父类的构造函数->非静态的数据成员的构造函数->自己的构造函数。

注意: 无论创建几个对象, 该类的静态成员只构建一次, 所以静态成员的构造函数只调用1次。

#include <iostream>
#include <Windows.h>
using namespace std;
class M {
public:
    M(){
        cout << __FUNCTION__ << endl;
    }
};

class N {
public:
    N() {
        cout << __FUNCTION__ << endl;
    }
};

class A {
public:
    A() {
        cout << __FUNCTION__ << endl;
    }
};

class B : public A {
public:
    B() {
        cout << __FUNCTION__ << endl;
    }
private:
    M m1;
    M m2;
    static N ms;
};

N B::ms;  //静态成员
int main(void) {
    B b;
    system("pause");
}

执行:

N::N      静态数据成员的构造函数

A::A      父类的构造函数

M::M     非静态数据成员的构造函数

M::M     非静态数据成员的构造函数

B::B      自己的构造函数

子类的析构函数

子类的析构函数的调用顺序,和子类的构造函数的调用顺序相反!!!记住,相反即可。

#include <iostream>

#include <Windows.h>

using namespace std;

class M {

public:

M() {

cout << __FUNCTION__ << endl;

}

~M() {

cout << __FUNCTION__ << endl;

}

};

class N {

public:

N() {

cout << __FUNCTION__ << endl;

}

~N() {

cout << __FUNCTION__ << endl;

}

};

class A {

public:

A() {

cout << __FUNCTION__ << endl;

}

~A() {

cout << __FUNCTION__ << endl;

}

};

class B : public A {

public:

B() {

cout << __FUNCTION__ << endl;

}

~B() {

cout << __FUNCTION__ << endl;

}

private:

M m1;

M m2;

static N ms;

};

N B::ms;  //静态成员

int main(void) {

{

B b;

cout << endl;

}

system("pause");

}

执行:

N::N

A::A

M::M

M::M

B::B

B::~B

M::~M

M::~M

A::~A

静态对象在程序终止时被销毁,所以:静态成员的析构函数,在程序结束前,是不会被调用的!

子类型关系

什么是子类型

公有继承时,派生类的对象可以作为基类的对象处理,派生类是基类的子类型。

B类就是A类的子类型。

Demo.

#include <iostream>

using namespace std;

class A {

public:

A() {}

~A() {}

void kill() { cout << "A kill." << endl; }

};

class B : public A {

public:

B(){}

~B(){}

void kill() { cout << "B kill." << endl; }

};

void test(A a) {

a.kill();  //调用的是A类对象的kill方法

}

int main(void) {

A a;

B b;

test(a);

test(b);

system("pause");

return 0;

}

子类型关系具有单向传递性。C类是B类的子类型 、B类是A类的子类型

子类型的作用:

在需要父类对象的任何地方, 可以使用”公有派生”的子类的对象来替代,从而可以使用相同的函数统一处理基类对象和公有派生类对象。即:形参为基类对象时,实参可以是派生类对象

demo

#include <iostream>

#include <sstream>

using namespace std;

class Father {

public:

void play() {

cout << "KTV唱歌!" << endl;

}

};

class Son : public Father {

public:

void play() {

cout << "今晚吃鸡!" << endl;

}

};

void party(Father *f1, Father *f2) {

f1->play();

f2->play();

}

int main(void) {

Father yangKang;

Son yangGuo;

party(&yangKang, &yangGuo);

system("pause");

return 0;

}

执行:KTV唱歌!        KTV唱歌!

注意:如果把Son改为protected继承,或private继承,就会导致编译失败!

子类型的应用

  1. 基类(父类)的指针,可以指向这个类的公有派生类(子类型)对象。

Son yangGuo;

Father * f = &yangGuo;

  1. 公有派生类(子类型)的对象可以初始化基类的引用

Son yangGuo;

Father &f2 = yangGuo;

  1. 公有派生类的对象可以赋值给基类的对象

Son yangGuo;

Father f1 = yangGuo;

注意:以上的应用,反过来就会编译失败!

多重继承

多继承/多重继承:一个派生类可以有两个或多个基类(父类)。多重继承在中小型项目中较少使用,在Java、C#等语言中直接取消多继承, 以避免复杂性.

多重继承的用法

将多个基类用逗号隔开。

实例:例如已声明了类A、类B和类C,那么可以这样来声明派生类D:

class D: public A, private B, protected C{

    //类D自己新增加的成员

};

D 是多继承形式的派生类,D 有3个父类(基类),它以公有的方式继承 A 类,以私有的方式继承 B 类,以保护的方式继承 C 类。D 根据不同的继承方式获取 A、B、C 中的成员.

多继承的构造函数

多继承形式下的构造函数和单继承形式基本相同。以上面的 A、B、C、D 类为例,D 类构造函数的写法为:

D(形参列表): A(实参列表), B(实参列表), C(实参列表){

    //其他操作

}

Father.h

#pragma once

#include <string>

class Father

{

public:

Father(const char *lastName="无姓", const char *firstName="无名");

~Father();

void playBasketball(); //打篮球

protected:

std::string lastName; //

std::string firstName; //

};

Father.cpp

#include "Father.h"

#include <iostream>

Father::Father(const char *lastName, const char *firstName)

{

this->lastName = lastName;

this->firstName = firstName;

}

Father::~Father()

{

}

void Father::playBasketball() {

std::cout << "呦呦, 我要三步上篮了!" << std::endl;

}

Mother.h

#pragma once

#include <string>

class Mother

{

public:

Mother(const char * food,

const char *lastName = "无姓", const char *firstName = "无名");

~Mother();

void dance();

private:

std::string lastName; //

std::string firstName; //

std::string food; //喜欢的食物

};

Mother.cpp

#include "Mother.h"

#include <iostream>

Mother::Mother(const char *food,

const char *lastName, const char *firstName)

{

this->food = food;

this->lastName = lastName;

this->firstName = firstName;

}

Mother::~Mother()

{

}

void Mother::dance()

{

std::cout << "一起跳舞吧, 一二三四, 二二三四..." << std::endl;

}

Son.h

#pragma once

#include "Father.h"

#include "Mother.h"

class Son : public Father, public Mother {

public:

Son(const char *lastName, const char *firstName,

const char *food,

const char *game);

~Son();

void playGame();

private:

std::string  game;

};

Son.cpp

#include "Son.h"

#include <iostream>

Son::Son(

const char *lastName, const char *firstName,

const char *food,

const char *game)

:Father(lastName, firstName), Mother(food)

{

this->game = game;

}

Son::~Son()

{

}

void Son::playGame()

{

std::cout << "一起玩" << game << "..." << std::endl;

}

main.cpp

#include <Windows.h>

#include "Son.h"

int main(void) {

Son wsc("川菜", "", "思聪", "电竞");

wsc.playBasketball();

wsc.dance();

wsc.playGame();

system("pause");

return 0;

}

多继承的构造函数的调用顺序:基类构造函数的调用顺序和和它们在派生类构造函数中出现的顺序无关,而是和声明派生类时基类出现的顺序相同。

多重继承的弊端: 二义性

Demo.

Father.h

#pragma once

#include <string>

class Father

{

public:

Father(const char *lastName="无姓", const char *firstName="无名");

~Father();

void playBasketball(); //打篮球

void dance(); //跳舞

protected:

std::string lastName; //

std::string firstName; //

};

Father.cpp

#include "Father.h"

#include <iostream>

Father::Father(const char *lastName, const char *firstName)

{

this->lastName = lastName;

this->firstName = firstName;

}

Father::~Father()

{

}

void Father::playBasketball() {

std::cout << "呦呦, 我要三步上篮了!" << std::endl;

}

void Father::dance() {

std::cout << "嘿嘿, 我要跳霹雳舞!" << std::endl;

}

Mother.h

不变

#pragma once

#include <string>

class Mother

{

public:

Mother(const char * food,

const char *lastName = "无姓", const char *firstName = "无名");

~Mother();

void dance();

private:

std::string lastName; //

std::string firstName; //

std::string food; //喜欢的食物

};

Mother.cpp

不变

#include "Mother.h"

#include <iostream>

Mother::Mother(const char *food,

const char *lastName, const char *firstName)

{

this->food = food;

this->lastName = lastName;

this->firstName = firstName;

}

Mother::~Mother()

{

}

void Mother::dance()

{

std::cout << "一起跳恰恰舞吧, 一二三四, 二二三四..." << std::endl;

}

Son.h

#pragma once

#include "Father.h"

#include "Mother.h"

class Son : public Father, public Mother {

public:

Son(const char *lastName, const char *firstName,

const char *food,

const char *game);

~Son();

void playGame();

void dance();

private:

std::string  game;

};

Son.cpp

#include "Son.h"

#include <iostream>

Son::Son(

const char *lastName, const char *firstName,

const char *food,

const char *game)

:Father(lastName, firstName), Mother(food)

{

this->game = game;

}

Son::~Son()

{

}

void Son::playGame()

{

std::cout << "一起玩" << game << "..." << std::endl;

}

void Son::dance() {

Father::dance();

Mother::dance();

std::cout << "霍霍, 我们来跳街舞吧! " << std::endl;

}

main.cpp

#include <Windows.h>

#include "Son.h"

int main(void) {

Son wsc("川菜", "", "思聪", "电竞");

wsc.playBasketball();

// 解决多重继承的二义性的方法1:

// 使用 "类名::" 进行指定, 指定调用从哪个基类继承的方法!

    wsc.Father::dance();

wsc.Mother::dance();

// 解决多重继承的二义性的方法2:

// 在子类中重新实现这个同名方法, 并在这个方法内部, 使用基类名进行限定,

// 来调用对应的基类方法

wsc.dance();

wsc.playGame();

system("pause");

return 0;

}

虚拟的祖先:虚基类

多重继承在菱形继承中的重大缺点

Demo

#include <iostream>

#include <string>

#include <Windows.h>

using namespace std;

// 电话类

class Tel {

public:

Tel() {

this->number = "未知";

}

protected:

string number; //电话号码;

};

// 座机类

class FixedLine : public Tel {

};

// 手机类

class MobilePhone :public Tel {

};

// 无线座机

class WirelessTel :public FixedLine, public MobilePhone {

public:

void setNumber(const char *number) {

//this->number = number;   //错误, 指定不明确

this->FixedLine::number = number;  //this可以省略

}

string getNumber() {

//return MobilePhone::number;

return MobilePhone::number;

}

};

int main(void) {

WirelessTel phone;

phone.setNumber("13243879166");

cout << phone.getNumber() << endl;  //打印未知

system("pause");

return 0;

}

检查:添加命令行选项:
/d1 reportSingleClassLayoutWirelessTel

解决方案

使用虚基类和虚继承.

Demo.

#include <iostream>

#include <string>

#include <Windows.h>

using namespace std;

// 电话类

class Tel {   //虚基类

public:

Tel() {

this->number = "未知";

}

protected:

string number; //电话号码;

};

// 座机类

class FixedLine : virtual public Tel {  //虚继承

};

// 手机类

class MobilePhone : virtual public Tel { //虚继承

};

// 无线座机

class WirelessTel :public FixedLine, public MobilePhone {

public:

void setNumber(const char *number) {

this->number = number;  //直接访问number

}

string getNumber() {

return this->number;   //直接访问number

}

};

int main(void) {

WirelessTel phone;

phone.setNumber("13243879166");

cout << phone.getNumber() << endl;

system("pause");

return 0;

}

这个被共享的基类(Tel)就称为虚基类(Virtual Base Class)


网站公告

今日签到

点亮在社区的每一天
去签到