找回密码
 注册

QQ登录

只需一步,快速开始

查看: 919|回复: 0

大工20春《c&c 语言程序设计》十八

[复制链接]
发表于 2021-2-25 11:45:12 | 显示全部楼层 |阅读模式
    C/C++语言程序设计辅导资料十八
一、主题:继承与多态二、学习时间:2020年7月27日-8月2日“不忘初心、牢记使命”主题理论学习:
“一带一路”是促进共同发展、实现共同繁荣的合作共赢之路,是增进理解信任、加强全方位交流的和平友谊之路。中国政府倡议,秉持和平合作、开放包容、互学互鉴、互利共赢的理念,全方位推进务实合作,打造政治互信、经济融合、文化包容的利益共同体、命运共同体和责任共同体。
摘选自《推动共建丝绸之路经济带和21世纪海上丝绸之路的愿景与行动》三、知识脉络:
第13章 类与对象
13.1 继承的概念与派生类的定义
13.2 派生类的构成与访问控制
13.3 派生类的构造函数与析构函数
13.4 继承中的同名成员访问
13.5 类的转化与类的关系
13.6 多态的概念与虚函数
13.7 纯虚函数与抽象类四、学习要求:
理解继承,掌握继承与派生、单继承、多继承的概念
掌握C++语言派生类的定义
掌握C++继承的三种方式
掌握派生类构成有三个步骤:接收基类成员、改造基类成员、添加新的成员
掌握公有继承的特点和访问特性
掌握保护继承的特点和访问特性
掌握私有继承的特点和访问特性
掌握protected访问权限
掌握三种继承方式的异同
掌握派生类构造函数的定义方式
掌握组合单继承的派生类构造函数的定义方式
掌握多重继承派生类构造函数
掌握派生类构造函数的执行顺序
掌握派生类的析构函数及其执行顺序
理解同名成员造成的二义性问题
理解基类成员与派生类成员同名
掌握类名限定符
理解多重继承的成员同名
掌握虚基类的实现
掌握基类与派生类间的赋值兼容关系
掌握类与类的三种关系
理解多态现象
理解静态多态与动态多态
掌握虚函数
掌握虚析构函数
掌握纯虚函数与抽象类五、重点难点:
继承的三种方式
三种继承方式的异同
派生类
虚函数
  
六、主要内容:
1.继承
继承的概念
继承(inheritance)是一种联结类与类的层次模型。有了继承,类与类之间不再是彼此孤立的,一些特殊的类可以自动地拥有一些一般性的属性与行为,而这些属性与行为并不是重新定义的,而是通过继承的关系得来的。
继承与派生
面向对象语言中的继承就是以已经存在的类为基础构建一个新的类:已存在的类称为基类(base class)或父类(father class),新建立的类称为派生类(derived class)或子类(son class)。
多重继承
一个派生类有两个或者多个基类,这种继承称为多重继承或多继承。
在类族中,直接参与派生出其他类的基类被称为直接基类,基类的基类甚至更高层的基类,称作间接基类。
C++中的继承具有传递性,具有非对称性,不能够循环继承。
派生类的定义
单继承派生类
class 派生类名 :  [继承方式]  基类名
{
派生类成员声明;
};
“继承方式”包括public(公有继承),private(私有继承)和protected(保护继承)。
继承方式是可选的,如果不显式声明继承方式,那么默认是private(私有继承)。
多重继承派生类
class 派生类名: [继承方式] 基类名1, [继承方式] 基类名2,…, [继承方式] 基类名n
{
派生类成员声明;
};2.派生类的构成与访问控制
派生类的构成
派生类与基类的成员表现出相同和不同,派生类构成有三个步骤:接收基类成员、改造基类成员、添加新的成员

继承中的访问控制
C++的继承方式有:公有继承、私有继承、保护继承
继承方式决定了基类成员在派生类中的访问权限,这种访问来自两个方面:1.派生类中的新增函数成员访问从基类继承来的成员 2.在派生类外部(非类族内的成员)通过派生类的对象访问从基类继承的成员
公有继承

私有继承

保护继承

三种继承方式的对比
私有继承:基类的可被继承的成员都成了其直接派生类的私有成员,无法再进一步派生,实际上私有继承相当于终止了基类成员的继续派生。
保护继承:基类的可被继承的成员都成了直接派生类的保护成员,保护继承恰恰保证了最上层基类的成员依然能被继承树中的次级子类所继承。
私有继承和保护继承:改变了基类成员继承后的访问属性,但对于其直接子类来说,这两种方式实际上是相同的。
一般情况下私有继承和保护继承的设计比较少见。一般采用不会改变基类成员访问权限的公有继承。3.派生类的构造函数与析构函数
派生类构造函数的定义方式
派生类名(参数总表):基类名(基类构造函数参数表)
{
     派生类成员初始化;
}
例如
Student(int number, char name[],char sex)erson(name,sex)
{……}
组合单继承的构造函数举例
class Person {  
protected:                                        
        char name[10];                  // 姓名
        char sex;                       // 性别
public:
        // 基类构造函数
        Person(char name[],char sex):sex(sex){
                cout<<"基类构造函数运行"<<endl;
                strcpy(this->name,name);
        }
};
class School {
private:
        char name[10];                            // 校名
        char city[10];                             // 所在城市
public:
        School(char name[],char city[]){             // 构造函数
                strcpy(this->name,name);
                        strcpy(this->city,city);
        }
        void print(){                             // 输出学校信息
                cout<<"school name:"<<name<<endl;
                cout<<"city:"<<city<<endl;     }
};
class Student : public Person{
private:                           
    int number;                                      // 学号                                     
    School school;                        // 学校(内嵌对象)
public:                              
    Student(int number,char name[],char sex,char s_name[],char city[])
        erson(name,sex),school(s_name,city),number(number){ }
    void print(){                     
       cout<<"Student ID:"<< number<<endl;
       cout<<"name:"<< name<<endl;
       cout<<"sex:"<< sex<<endl;
       school.print();                // 调用内嵌对象的公有函数
     }
};
//主函数
int main(){
        Student s(1001,"Li Ming",'M',"DLUT","dalian");
        s.print();
        return 0;
}
程序运行结果如下:
Student ID:1001
namei Ming
sex:M
school nameLUT
city:dalian

多重继承派生类构造函数
派生类名(参数总表):基类1名(基类1构造函数参数表),基类2名(基类2构造函数参数表),……,基类n名(基类n构造函数参数表)
{派生类新增数据成员初始化;}
派生类构造函数的执行顺序
“先祖先,再客人,后自己”
1. 调用基类的构造函数,如有多个基类,则按照它们被继承的顺序依次调用。
2. 调用内嵌对象的构造函数,如果有多个,则按照它们在类的数据成员声明中的先后顺序依次调用。
3. 执行派生类的构造函数体中的内容。
如果派生类的构造函数没有显示声明其基类和其内嵌对象的构造方式,那么系统按照“默认”方式对它们进行初始化,也就是调用它们的默认构造函数,如果基类或者内嵌类不具有这样的构造函数,那么就会出现编译错误。
派生类的析构函数
在析构的的时候,派生类的析构函数依次执行了自身的析构函数、内嵌对象的析构函数和基类的析构函数,其执行的顺序与其构造函数执行的顺序正好严格相反。4.继承中的同名成员访问
基类成员与派生类成员同名
class Person{                                // 基类
protected:                                        
        char name[10];                           // 姓名
        char sex;                                // 性别
        int id;                                  // 身份证号   
};
class Student : public Person{        // 派生类
private:           int id;                    // 学号       
             char school[10];              // 学校
public:           void test(){              //测试函数   
                             id=123;      //究竟访问哪一个?   
                     }     };

派生类定义了与基类相同的成员,此时基类的同名成员在派生类内不可见,也就是派生类成员隐藏了同名的基类成员,这种现象称为继承时的同名成员隐藏规则。
基类成员与派生类成员同名,可以通过类名限定符“::”来解决。其语法为: 类名 : : 成员
class Student : public Person{        // 派生类
private:           int id;                         // 学号       
             ……
public:           void test(){              //测试函数   
                               id=123;        // 访问派生类成员
                      Person::id =456; // 访问基类成员   
                     }
};
继承中的同名成员访问
多重继承时不同基类成员同名也可以用类名限定符“::”来解决。

多个基类继承来的成员有同名现象,可以使用类名限定符“::”来进行区分。但是,有个前提:这些基类不是从一个共同的基类派生来的。如果不具备这个前提条件,在访问同名成员时会出现问题。
class Person
  {   protected:        int id; };    // 身份号码
class Student : public Person
{  protected:    int id; };            // 学号
class Teacher : public Person
{  protected:   int id; };             // 职工号
class Assistant: public Student,public Teacher{
public:   void test(){                     // 测试
                  Student::id  = 1001;          // 正确:访问Student类的id
                  Teacher::id = 101;            // 正确:访问Teacher类的id
                 Person::id   =10001;     // 错误!为什么?
               }
};

内存中实际上存在两个基类Person的对象,一个是由Student这一派生分支产生,另一个是由Teacher的派生分支产生的。
Person::id   =10001;  // 二义性错误
无法传递给系统足够的信息,系统无法知道该语句到底要访问哪一个id。
虚基类
C++语言允许程序中只建立公共基类的一个副本,将直接基类的共同基类设置为虚基类(virtual base class),这时从不同路径继承过来的该类成员在内存中只拥有一个副本。
class 派生类名:virtual [继承方式] 基类名
class Person{   protected:        int id; };
class Teacher: virtual {   protected:        int id; };
class Student: virtual {   protected:        int id; };
class  Assistant : public Teacher , public Student {
public:   
      void test(){                    
      Student::id  = 1001;          // 正确:访问Student类的id
      Teacher::id = 101;            // 正确:访问Teacher类的id
      Person::id   =10001;        // 正确:内存只有一个Person对象
    }
};
5.类的转化与类的关系
基类与派生类的转化
三种继承方式中,只有公有继承使派生类完整地保留了基类的所有特征,因此,公有继承时,一个派生类的对象可用于基类对象适用的地方,需要基类对象的任何地方都可以使用派生类对象替代。
假设存在以下的继承关系:
class A{
public:
        int i;
};
class B :public A{
public :
        int j;
};
1. 派生类对象可以直接赋值给基类对象。
                A  a;    // 基类对象
                B  b;    // 派生类对象
                a = b;  // 直接赋值
注意,赋值后a的数据类型依然是A,因此不要企图使用a去访问派生类的成员,因此下面的语句是错误的:
               a.j = 3;     //  错误,A中不具有j成员
2. 派生类对象可以初始化基类的引用。
A & c = b;   //  c是基类的引用,使用派生类对象b初始化c
函数的形式参数如果是一个基类的引用,在实际调用该函数的时候,可以传递一个派生类对象来代替基类对象。
void function(A & c)          // 形式参数是基类A的引用
{  cout<<c.i<<endl;  }       // 输出A的数据成员i
实际调用该函数时使用如下的方式:
function(b);                 //实际参数是派生类B的对象
3. 派生类对象的地址可以赋值给基类的指针。
A * p = & b ;   //基类指针p指向派生类对象b
p->i = 5 ;        //使用基类指针对派生类对象的成员进行访问
或者
A *p = new B();
p->i = 5 ;
类与类的关系
C++的程序是由类组成的。类和类之间不是彼此孤立的,就像现实世界中的万事万物一样,相互之间存在各种各样的联系。类与类的关系可分为三种:

1. 类的组合(has-a关系)
class Engine{                              // 发动机类
public:void work();      };                // 发动机运转
class Wheel{                              // 车轮类
public:    void roll(); };                    // 车轮转动
class Automobile{                         // 汽车类
private:   Engine * engine;              // 拥有一个发动机
               Wheel* wheel;         // 拥有四个车轮
public:    void move(){                   // 汽车行进
                   engine->work();           // 使用成员对象
           for(int i=0;i<4;i++) wheel[i]->roll();   // 使用成员对象
       }
};
2. 类的继承(is-a关系)
class Vehicle{                        // 交通工具类
protected:
        double weight;                   // 重量
           float speed;              // 速度
public:
        void run();                     // 运行
};
class Automobile:public Vehicle{   // 汽车类:就是交工工具
private:  int  load;                // 载客数
public:
    void move(){run();            // 可以直接调用基类成员
}   };
3. 类的使用(usa-a关系)
class Person{
public:
    void buy(Automobile & auto){
            auto.move();                    // Person类使用汽车类
        }   
};
void main(){
    Person person;
        Automobile auto;
    person.buy(auto);
}
继承、组合和使用是面向对象程序设计中常采用的三种类关系。
类与类的“继承关系”是最紧密的关系,而“使用关系”是相对比较松散的关系。在实际的程序设计中,究竟使用哪一种关系,要看具体的实际需要,灵活使用。6.多态的概念与虚函数
理解多态
多态性(Polymorphism)是指同样的消息被不同类型的对象接收时产生不同行为的现象。
学校的第一节上课铃声响过之后:
教师要走上讲台准备上课
学生在座位上做好上课准备
教务人员开始一天的事务性工作
实验室的工作人员开始准备设备
消息都是 “铃声”,不同消息接收者产生不同行为。
静态多态
静态多态是指在程序编译时系统就能够确定要调用的是哪个函数,因此这种多态也被称为编译时多态。通过函数的重载来实现。
动态多态
动态多态性是指程序在编译时并不能确定要调用的函数,直到运行时系统才能动态地确定操作所针对的具体对象,它又被称为运行时多态。
动态多态是通过虚函数(virtual function)来实现的。
虚函数
C++中的虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或者基类引用来访问这个同名函数。虚函数成员声明的语法为:virtual 函数类型 函数名(参数列表);
注意以下两点:
1. virtual只能使用在类定义的函数原型声明中,不能在成员函数实现的时候使用,也不能用来限定类外的普通函数
2. virtual具有继承性,在派生类覆盖基类虚成员函数时,既可以使用virtual,也可以不用virtual来限定,二者没有差别,默认派生类中的重写函数是具有virtual的。
虚函数来说,基类与派生类同名函数的参数列表是完全相同的,唯一不同的就是函数所属的类不同,也就是调用函数的对象不同。
虚析构函数
当基类的析构函数为虚函数时,无论指针指的是同一类族的哪一个类对象,对象撤销时,系统会采用动态关联,调用相应的析构函数,完成该对象的清理工作。
习惯把析构函数声明为虚函数,即使基类并不需要析构函数,以确保撤销动态存储空间时能够得到正确的处理。
构造函数是不能声明为虚函数的。7.纯虚函数与抽象类
例:二维图形派生关系

比如现在有一个test函数用来测试平面图形的信息,如下:
void test( TwoDimensionalShape & t ){
    t.print();               // 输出平面图形信息
        cout<<"面积为"<<t.getArea()<<endl;     // 输出平面图形面积
}
virtual double getArea()
   { return 0;}
函数的返回值设定为0,实际上这个函数的返回值是无法确定的。二维图形本身就是一个抽象的概念,其面积也是无法求的,将面积的值设定为0,其实是一种不得已的做法。
C++语言为这种无法具体化的抽象函数提供了一种声明的方式:
                      virtual double getArea() = 0;
此函数没有函数体,只给出函数的原形,并在后面加上“=0”,这样就把getArea函数声明为一个纯虚函数(pure virtual function)。纯虚函数的一般形式是:        
                   virtual 函数类型 函数名(参数列表)= 0;注意,纯虚函数是没有函数体的,与下面的函数具有本质上
     的区别: virtual void getArea(){ }

本章小结
1. 继承实现了代码复用,使派生类与基类相似,多态实现了不同的派生类彼此之间又有不同,派生类与基类的不同。
2. 基类中的某些功能本身并没有具体的作用,一般把这种函数设计为纯虚函数,它的存在是为了提供给多个派生类一个统一的访问接口,具体功能是在派生类中根据实际对象的要求而实现的。
3. 利用继承和多态技术,可以使程序代码更加简洁可以实现不同类型的对象针对同一指令产生不同的响应。七、习题:
1.下面关于C++中类的继承与派生的说法错误的是(   )。
A.基类的protected成员在公有派生类的成员函数中可以直接使用
B.基类的protected成员在私有派生类的成员函数中可以直接使用
C.私有派生时,基类的所有成员访问权限在派生类中保持不变
D.继承可以分为单一继承与多重继承
答案:C2.派生类继承基类时,如果省略继承方式,则默认为(   )继承。
A.公有
B.保护
C.私有
答案:C3.当一个派生类公有继承一个基类时,基类中的所有公有成员成为派生类的(  )。
A.public成员
B.private成员
C.protected 成员
D.友元
答案:A4.关于多重继承二义性的描述,错误的是(   )。
A.派生类的多个基类中存在同名成员时,派生类对这个成员访问可能出现二义性
B.一个派生类是从具有共同的间接基类的两个直接基类派生来的,派生类对该公共基类的访问可能出现二义性
C.解决二义性最常用的方法是作用域运算符对成员进行限定
D.派生类和它的基类中出现同名函数时,将可能出现二义性
答案:D5.在如下继承层次下,当实例化有派生类对象时,调用构造函数的顺序为(   )。
class Base{…};
class Base1: virtual Base{…};
class Base2: virtual Base{…};
class Derived : public Base2, public Base1{…};
A.Base(), Base1(), Base(), Base2(), Drived()
B.Base(), Base2(), Base(), Base1(), Drived()
C.Base(), Base1(), Base2(), Drived()
D.Base(), Base2(), Base1(), Drived()
答案:D6.派生类的对象对它的基类成员中(   )是可以访问的。
A.公有继承的公有成员
B.公有继承的私有成员
C.公有继承的保护成员
D.私有继承的公有成员
答案:A7.派生类采用(  )方式可以使基类中的保护数据成员成为自己的私有数据成员。
A.私有继承
B.保护继承
C.公有继承
D.私有、保护、公有均可
答案:A8.在如下继承层次下,有关访问权限描述不正确的为(  )。
class A{
protected:  int x;
public :    void  set_X( int i){ x = i;} ;
           int  get_X( ){ return x; } ;  
};
class B : public A
{    int y;  };
class C: protected B{…};
A.B类中可以直接访问父类A中的数据成员x;
B.B类对象不可以直接访问其数据成员y;
C.C 类对象可以调用get_X()访问父类数据成员x;
答案:C9.C++中所谓多态性是指()。
A.不同的对象调用不同名称的函数
B.不同的对象调用相同名称的函数
C.一个对象调用不同名称的函数
D.一个对象调用不同名称的对象
答案:B10.有关多态性说法不正确的是()。
A.C++语言的多态性分为编译时的多态性和运行时的多态性
B.编译时的多态性可通过函数重载实现
C.运行时的多态性可通过函数重载实现
D.实现运行时多态性的机制称为动态多态性
答案:C11.以下()是正确的纯虚函数定义。
A.virtual void tt()=0
B.void tt(int)=0
C.virtual void tt(int)
D.virtual void tt(int){}
答案:A12.C++类体系中,能被派生类继承的是(  )。
A.构造函数
B.虚函数
C.析构函数
D.友元函数
答案:B南开答案可以联系QQ 761296021

QQ|手机版|小黑屋|网站地图|无忧答案网 ( 冀ICP备18010495号-1 )

GMT+8, 2024-5-4 03:32

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表