阶段性总结(3)——继承和多态

本文介绍了C++中的继承机制,包括基类与派生类的概念、继承的访问特性以及构造与析构函数的调用顺序。还探讨了多态性,包括静态和动态多态、虚函数和纯虚函数的应用,以及向上和向下类型转换的安全性。通过实例展示了如何利用继承和多态性来创建和操作类对象。
摘要由CSDN通过智能技术生成

--------------继承---------------
C++中通过继承机制,可以利用已有的数据类型来定义新的数据类型。所定义的新的数据类型不仅拥有新定义的成员,而且还同时拥有旧的成员。这使得维护和创建一个应用程序变得比较容易。
基类&派生类
一个类可以派生自一个或多个基类。定义一个派生类,我们使用一个类派生列表来指定基类。形式如下:
class derived-class: access-specifier base-class
例子:
class animal{

public:
animal() {
std::cout << “animal init” << std::endl;
}
~animal() {
std::cout << “~animal()” << std::endl;
}

public:
void eat() {
std::cout << “animal eat” << std::endl;
}
void sleep() {
std::cout << “animal sleep” << std::endl;
}
};

class Dog : public animal
{
private:

public:
Dog(){
std::cout << “Dog init” << std::endl;
}
~Dog(){
std::cout << “~Dog()” << std::endl;
}
};

int main(int argc, char* argv[])
{
Dog dog;
dog.eat();

printf(“Hello World!\n”);
return 0;
}
在这里插入图片描述
注意:
1.在继承中,private类型不能被继承

公有继承,基类中的public在派生类中还是public类型,protected类型还是protected类型。

保护继承,基类中的public,protected在派生类中均为protected类型。

私有继承,基类中的public,protected均为private类型。

2、当定义一个类对象时,首先从顶层依次调用基类的构造函数,最后调用自身的构造函数。

析构函数的调用顺序与构造函数正好相反,首先调用自身的析构函数,然后依次调用基类的析构函数。
3.子类与父类中函数如果有重名,如果子类中隐藏了父类的方法,那么父类中同名的方法(重载方法)都将被隐藏。

4、在多继承中,当多个基类中有相同的函数名时,需要明确指明调用的是哪一个基类。

5、在多继承中,如果cbird类和cfish类均是canimal的子类,那么当从cbird和cfish中派生出cwaterbird时,将存在两个canimal的拷贝,虚继承将使得子类中只存在一个基类。

6、类型转换是指将某一个类型的对象转换为另一个类型的对象。在应用程序中,对象的类型转换主要是指子类和父类之间的转换。因此这种转换主要有两种形式,向上类型转换和向下类型转换。向上类型转换是指从子类到父类的转换,这种转换是安全的,因为子类具有父类中被外界能够访问的方法。
在这里插入图片描述

向下类型转换是从父类到子类的转换,这种类型类型转换是不安全的。因为通常在子类中提供了父类不具有的方法,将父类转换为子类后,试图调用这些实际不存在的方法,必然会出现错误。

向上类型转换是比较安全的,编译器允许直接将子类对象赋值给基类对象。

如下代码:
#include
#include
#include
using namespace std;
class cemployee
{
protected:
int m_nid;
string m_szname;
string m_szdepart;
public:
cemployee()
{
m_szname=“han”;

	cout << "employee 构造函数被调用 " << endl;
}

virtual void outputname()
{
cout << “基函数员工姓名:” << m_szname << endl;
}
};

class coperator:public cemployee
{
private:
string m_szpassword;
public:
coperator()
{
m_szpassword = “123”;
cout << “coperator 构造函数被调用” << endl;
}

void outputname()
{
	cout << "派生类员工姓名:" << m_szname << endl;
}

};
int main()
{

coperator * poperator = new coperator();
cemployee *pemp = poperator;
pemp->outputname();

// delete poperator();
pemp = NULL;

return 0;

}
cemployee *pemp = poperator;语句直接将子类对象指针直接赋值给基类指针,这是完全合法的。语句pemp->outputname();将调用outputname()方法,如果outputname()是虚函数则调用子类的,否则执行基类的outputname()方法。

向下类型转换。是一种不安全的类型转换,因此编译器不允许直接通过赋值的方式将基类对象赋值给子类的对象。

如下主函数,编译器无法通过
int main()
{

cemployee * pemp = new cemployee();
coperator *poperator = pemp;
poperator->outputname();

// delete poperator();
// pemp = NULL;

return 0;

}
如果非要实现,可以采用强制转换。改为如下:
int main()
{

cemployee * pemp = new cemployee();
coperator *poperator = (coperator*)pemp;
poperator->outputname();

// delete poperator();
// pemp = NULL;

return 0;

}
上述转换是非常不安全的,如果我们使用poperator 对象调用coperator类特有的方法,例如login()方法。

还有一种可以利用dynamic_cast进行动态转换。利用它可以实现两个比较安全的转换。如果待转换的对象类型与目标类型不相同,则转换将会失败。可以通过转换的结构来判断是否成功。

主函数:
int main()
{

cemployee * pemp = new coperator();
coperator *poperator = dynamic_cast<coperator*>(pemp);
if(poperator != NULL)
{
	cout << "转换成功" << endl;
}
else
	cout << "转换失败" << endl;
delete pemp;
poperator = NULL;}

dynamic_cast有一个缺陷,就是待转换的源对象必须有具有虚拟方法表,即源对象中必须有虚拟方法。可以将析构函数声明为虚析构函数就可以轻松解决这个缺陷。。
----- 例子-----
A类为基类,B类继承A类,B具有A类的属性和方法,同时可以定义新的属性和方法,称之为继承。
#include
#include

class Animal
{
public:
std::string mouth;

void eat();
void sleep();
void drool();

};
class Pig : public Animal
{
public:
void climb();
};

class Turtle : public Animal
{
public :
void swim();
};
void Animal::eat()
{
std::cout << “我是Animal,我可以吃饭”<<std::endl;
}
void Animal::sleep()
{
std::cout << “我是Animal,我可以睡觉”<<std::endl;
}
void Pig::climb()
{
std::cout <<“我是一个小猪,我可以上树”<<std::endl;
}
void Turtle::swim()
{
std::cout <<“我是一个乌龟,我可以游泳”<<std::endl;
}
int main()
{
Turtle tur;
tur.sleep();
tur.swim();

Pig pig;
pig.eat();
pig.climb();

return 0;

}
多继承:
一个派生类可以由多个基类继承而来;
例子:
class A{
public:
A();
~A();
}
class B{
public:
B();
~B();
}

class C : public A ,public B
{
public:
C();
~C();
}
静态成员与继承:
在基类中静态成员,都会继承给派生类;所有的派生类都会共享静态成员变量,某一处修改静态变量的值,就会就会修改派生类和基类中的值。
静态成员的访问属性跟继承方式有关,与普通成员相同;
#include

class animal{

public:
animal() {
std::cout << “animal init” << std::endl;
}
~animal() {
std::cout << “~animal()” << std::endl;
}
protected:
void test(void){
std::cout << “animal :protected test " << std::endl;
}
public:
void eat() {
std::cout << “animal eat” << std::endl;
}
void sleep() {
std::cout << “animal sleep” << std::endl;
}
public:
static int a ;
void ShowA()
{
std::cout << a << std::endl;
}
static void ts(void)
{
std::cout << " ts”<< std::endl;
}
};

int animal::a = 100;
class Dog : public animal
{
private:

public:
Dog(){
std::cout << “Dog init” << std::endl;
}
~Dog(){
std::cout << “~Dog()” << std::endl;
}
public:
void Dog_test()
{
test();
}
void SetA(int val)
{
a = val;
}

};
class Cat : public animal
{
private:

public:
Cat();
~Cat();
};

Cat::Cat()
{
}

Cat::~Cat()
{
}

int main(int argc, char* argv[])
{
Dog dog;
Cat cat;
dog.eat();
cat.a = 10;
dog.ShowA();
Dog::ts();
printf(“Hello World!\n”);
return 0;
}
转换与继承:
基类与派生类之间,可以进行转换。
派生类 -> 基类

–可以通过指针、引用进行转换;
–不能进行对象转换;
基类 -> 派生类

不能直接进行转换。
只能进行强制转换基类指针。
#include

class Item_base{
public:
   Item_base() {}
   ~Item_base() {} 
   int base;
};

class Bulk_item : public Item_base
{
public:
   Bulk_item() {}
   ~Bulk_item() {} 
   int bulk;
};
int main(int argc, char* argv[])
{
    std::cout << "hello c++ "<< std::endl;

    Item_base item1;
    Bulk_item item2;

    Bulk_item *p = static_cast<Bulk_item *>(&item1);//强制转换基类指针
    Item_base *item =  &item2; 
   
    return 0;
}

二:

***------------多态------------***

多态分为两类:
静态多态:
:函数重载和运算符的重载属于静态多态,复用函数名;
动态多态
动态多态:派生类和虚函数实现运行时的多态;
区别
1、静态多态的函数地址属于早绑定,编译阶段确定函数地址;
2、动态多态的函数地址晚绑定,运行阶段确定函数地址;
我们下面讨论的都是动态多态。
多态满足的条件
1、有继承关系;
2、子类重写父类中的虚函数;
重写 :函数返回值类型 函数名 参数列表 完全一致称为重写
当一个成员函数被声明为虚函数后,其派生类中的同名函数都会自动生成虚函数。因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,但是习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰
注意 与重载的区别
多态使用
父类指针或引用指向子类对象

代码:

class animal
{
public:
//函数前面加上virtual关键字,就变成虚函数,那么编译器在编译的时候就不能确定函数调用了
//speak函数就是虚函数
virtual void speak()
{
cout << “动物在说话” << endl;
}
};
//多态满足的条件:
//1、有继承关系;
//2、子类重写父类中的虚函数;
//多态使用
//父类指针或引用指向子类对象
class cat : public animal
{
public :
void speak()
{
cout << “小猫在说话” << endl;
}
};
class dog : public animal
{
public:
void speak()
{
cout << “小狗在说话” << endl;
}
};
//这里用父类引用调用子类对象
void dospeak(animal &animal)
{
animal.speak();
}
void test01()
{
cat miao;
dospeak(miao);
}
int main()
{
test01();
system(“pause”);
return 0;
}
补充:
–虚函数
第一:
多态可以分为静态的多态和动态多态,静态多态即所谓的函数重载,在编译阶段即可确定是使用哪个函数。动态多态是通过虚函数来实现的,并基于类的继承来表现。

在使用过程中应注意以下几点:

1、虚函数使用过程中,需要用父类指针指向子类,如下例子中Animal *Ani = new Dog;

2、在子类中的同名虚函数是可加关键字virtual也可不加,但是为方便代码阅读,建议是进行添加

3、当子类中的构造函数中存在new对象或者空间时,为避免内存的泄露,需要将父类中的析构函数定义为虚函数。这是因为析构函数定义为虚函数后,在main函数中调用delete Ani进行析构的时候,会自动调用子类的析构函数,由于调用子类的析构函数会自动析构父类;而析构父类是无法自动析构子类的。

4、普通的全局函数、静态成员函数和构造函数都是不能够指定为虚函数的。对于inline修饰的内联函数,当用virtual对其进行修饰时,inline是失效的。

5、父类即使不做任何的定义,单纯是一个空的构造函数和空的析构函数,在对父类进行实例化为对象的时候也是占用1个内存空间的,即对该父类对象进行sizeof衡量时,输出结果为1,。该1个单元的内存空间是用以标定该对象的存在的,当该父类中有其他如int成员时,就不需要该标定空间的。同时,当父类中定义了虚函数时,则实例化后会由于存在一个虚函数表指针,使得该父类的对象的内存空间为4(指针嘛,空间占用是4个单元)。而从该父类继承的所有子类都是会存在一个虚函数表指针的。

定义一个动物(animal)类,要求含有虚函数eat和move,并定义构造函数和虚析构函数
定义一个狗(Dog)类,要求共有继承动物类,定义构造函数和虚析构函数,并实现自己的eat和move函数
使用父类对象实例化子类,调用子类成员函数。

代码:

#include
#include <stdlib.h>
#include
using namespace std;

/**

  • 定义动物类:Animal
  • 成员函数:eat()、move()
    */
    class Animal
    {
    public:
    // 构造函数
    Animal(){cout << “Animal” << endl;}
    // 析构函数
    virtual ~Animal(){cout << “~Animal” << endl;}
    // 成员函数eat()
    virtual void eat(){cout << “Animal – eat” << endl;}
    // 成员函数move()
    virtual void move(){cout << “Animal – move” << endl;}
    };

/**

  • 定义狗类:Dog
  • 此类公有继承动物类
  • 成员函数:父类中的成员函数
    */
    class Dog : public Animal
    {
    public:
    // 构造函数
    Dog(){cout << “Dog” << endl;}
    // 析构函数
    virtual ~Dog(){cout << “~Dog” << endl;}
    // 成员函数eat()
    virtual void eat(){cout << “Dog – eat” << endl;}
    // 成员函数move()
    virtual void move(){cout << “Dog – move” << endl;}
    };

int main(void)
{
// 通过父类对象实例化狗类
Animal *Ani = new Dog;
// 调用成员函数
Ani->eat();
Ani->move();
// 释放内存
delete Ani;

return 0;

}
第二、

纯虚函数:

当虚函数并没有做任何定义时,该虚函数称为纯虚函数,含有纯虚函数的类,称为抽象类。所以,子类也可能是抽象类。注意一点是,抽象类是无法进行实例化对象的!

代码:

#include
#include <stdlib.h>
#include
using namespace std;

/**

  • 定义动物类:Animal
  • 虚函数:eat()
  • 纯虚函数:move()
  • 数据成员:m_strName
    */
    class Animal
    {
    public:
    // 默认构造函数
    Animal(){};
    // 含参构造函数
    Animal(string name){m_strName = name; cout << “Animal” << endl;}
    // 虚析构函数
    virtual ~Animal(){cout << “~Animal” << endl;}
    // 虚成员函数
    virtual void eat(){cout << “Animal–” << m_strName << “-- eat” << endl;}
    // 纯虚函数
    virtual void move()=0;
    public:
    // 数据成员
    string m_strName;
    };

/**

  • 定义狗类:Dog
  • 公有继承动物类
  • 虚成员函数:eat()、move()
    */
    class Dog : public Animal
    {
    public:
    // 默认构造函数
    Dog(){};
    // 含参构造函数
    Dog(string name){m_strName = name; cout << “Dog” << endl;}
    // 虚析构函数
    virtual ~Dog(){cout << “~Dog” << endl;}
    // 虚成员函数eat()
    virtual void eat(){cout << “Dog–” << m_strName << " – eat" << endl;}
    // 虚成员函数move()
    virtual void move(){cout << “Dog–” << m_strName << " – move" << endl;}
    public:
    // 数据成员
    string m_strName;
    };

int main(void)
{
// 通过动物类实例化狗类
Animal *Ani=new Dog(“dog007”);
// 调用成员函数
Ani->eat();
Ani->move();
// 释放内存
delete Ani;
Ani=NULL;

return 0;

}

第三、

接口类:在类中仅含有纯虚函数的类。这点包含两个信息,其一是只有成员函数,其二是该成员函数都是虚函数。接口类更多是作为一种协议。

接口的使用例子:

#include
#include <stdlib.h>
#include
using namespace std;

/**

  • 定义射击类:CanShut
  • 定义纯虚函数:aim、reload
    */
    class CanShut//接口类,只含有纯虚函数
    {
    public:
    virtual void aim() =0;
    virtual void reload() =0;//纯虚函数
    };

/**

  • 定义枪类:Gun
  • 公有继承射击类
  • 实现成员函数:aim、reload
    */
    class Gun : public CanShut
    {
    public:
    virtual void aim()
    {
    cout << “Gun – aim” << endl;
    }
    virtual void reload()
    {
    cout << “Gun – reload” << endl;
    }
    };

/**

  • 定义含参函数射击:hunting
  • 调用参数的aim与reload函数
    */
    void hunting(CanShut *s)
    {
    s->aim();
    s->reload();
    }

int main(void)
{
// 实例化枪对象
CanShut *g1=new Gun;
// 调用含参函数hunting,将对象枪传入函数中
hunting(g1);
// 释放内存
delete g1;
g1=NULL;

return 0;

}
父类子类指针函数调用注意事项:
1,如果以一个基础类指针指向一个衍生类对象(派生类对象),那么经由该指针只能访问基础类定义的函数(静态联翩)
2,如果以一个衍生类指针指向一个基础类对象,必须先做强制转型动作(explicit cast),这种做法很危险,也不符合生活习惯,在程序设计上也会给程序员带来困扰。(一般不会这么去定义)
3,如果基础类和衍生类定义了相同名称的成员函数,那么通过对象指针调用成员函数时,到底调用那个函数要根据指针的原型来确定,而不是根据指针实际指向的对象类型确定。

虚函数就是为了对“如果你以一个基础类指针指向一个衍生类对象,那么通过该指针,你只能访问基础类定义的成员函数”这条规则反其道而行之的设计。
如果你预期衍生类由可能重新定义一个成员函数,那么你就把它定义成虚拟函数( virtual )。
polymorphism就是让处理基础类别对象的程序代码能够通透的继续适当地处理衍生类对象。
纯虚拟函数:
virtual void myfunc ( ) =0;
纯虚拟函数不许定义其具体动作,它的存在只是为了在衍生类钟被重新定义。只要是拥有纯虚拟函数的类,就是抽象类,它们是不能够被实例化的(只能被继承)。如果一个继承类没有改写父类中的纯虚函数,那么他也是抽象类,也不能被实例化。
抽象类不能被实例化,不过我们可以拥有指向抽象类的指针,以便于操纵各个衍生类。
虚拟函数衍生下去仍然是虚拟函数,而且还可以省略掉关键字“virtual”。

课程感言:
-----不知不觉,这门课程到了结束的时候,不禁感言,时间是过的真快,感觉并没有汲取到什么知识,就到了最后。
-----12周的时间学习这门课程,从刚开始的懵懵懂懂到现在多少了解了一些,学到了一些。怎么说呢,还是只会些皮毛。人算不如天算,由于疫情,一直呆在家里,再加上没带电脑回来,这门课程着实是折磨死我了,再加上动手能力本来就差,这老师布置的作业就快要我的命了。接下来还有考试,不能再迷迷糊糊丶浑浑噩噩的过日子了,复习得提上日程。
-----接触的老师很负责,教授的知识也很全面,能遇上这样的老师,我感觉挺幸运的,就算挂科了,也是学了一些东西了,挂了只能说明自己学艺不精。
------和老师学到的比较重要的除了知识,还有我觉得更值得我铭记的就是做事不要拖拉。我这人从高中养成拖拉这个毛病的,什么事都拖到最后一天,甚至根本不去做,我明明知道这样做是不对的,但就是管不住自己。太好玩了,所以说,有一些强制对我是好事。尽管没有彻底做到一点也不拖拉,但是至少是向好的方面发展。我想这尽管身份是大学生,也更应该学习的吧。
--------对于c++这门课程,我感觉是有点难的,通过做作业我发现,也是因为在家的原因,自律达不到,就是不如在学校里,除了学习就是学习,反思自己,课上不认真,课下不复习,慢慢的就落下了,归根到底就是自己管不住自己。看到身边的同学们样样精通,说心里不着急那肯定是假的,现在还一直纠结什么时候开学,又分散了一波精力,有时候感觉事真多,一天又一天,着实受够了。
------慢慢的我真正接触计算机的时间有一年了,c,c++基础课程也将我慢慢带进了计算机这个大世界,大学是一个新征程,地基得慢慢打牢,转眼就要大三,面临着考研的压力,基础的东西现在就要学好。
------其实挺害怕c++老师的,大气不敢喘一下的,跟高中有点像了,这久违的感觉,主要是这作业对我的挑战性太大了。不管怎么说,学好了是自己的,认认真真,努努力力的才是现在的我应该做的。
------认真复习,不会就问,争取搞通搞精。着实不想体验挂科的经历。

1.声明一个动物基Animal,私有整型成员变量年龄age,请定义一个派生Dog,在其成员函数SetAge(int n)中直接给age赋值,测试下看是否会出问题?如何解决? 2.设计一个单基继承层次程序,用Person派生出Student,增加属性学号index和年级level。Person中至少有姓名name、年龄age等数据成员,以及构造函数、输出函数等,其余成员函数根据需要添加。在主函数中进行测试。 3.定义一个学生Student和教师Teacher,学生有姓名name、学号index等数据成员,教师有姓名name、工作证号workID、职称title、课程course、周学时hoursPerWeek等数据成员。再定义一个助教TeachingAssistant,多继承于学生和教师,该可以使用学生的全部数据成员,以及教师的课程和周学时数据成员。要求:每个提供自定义的构造函数和析构函数,并通过同名函数ShowInfo来显示全部数据成员的值。在主函数中进行测试。 4.声明一个Person,包含姓名name和年龄age等私有数据成员以及相关的成员函数;由它派生出领导Leader,包含职务position和部门department私有数据成员以及相关的成员函数;再由Person派生出工程师Engineer,包含职务position和专业speciality私有数据成员以及相关的成员函数;再由Leader和Engineer派生出主任工程师Chairman。在主函数中测试各对象初始化和信息输出,查看是否会出问题?如何解决?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值