精华内容
下载资源
问答
  • 在派生类中可以通过基类名
    2021-11-10 15:18:35

    除了可以将派生类对象赋值给基类对象(对象变量之间的赋值),还可以将派生类指针赋值给基类指针(对象指针之间的赋值)。我们先来看一个多继承的例子,继承关系为:

    #include <iostream>
    
    
    using namespace std;
    
    //基类
    
    class A{
    
    
    
    public:
        A(int a);
        int m_a;
    public:
        void display();
    
    };
    
    A::A(int a):m_a(a) {}
    
    void A::display() {
        cout<<"Class A:m_a="<<m_a<<endl;
    }
    
    
    //派生类
    
    class B:public A{
    
    public:
        B(int a1,int b);
    
    public:
        void display();
    
    protected:
        int m_b;
    };
    
    B::B(int a, int b):A(a),m_b(b) {}
    
    void B::display() {
    
        cout<<"Class B:m_a="<<m_a<<", m_b"<<m_b<<endl;
    
    }
    
    class C{
    public:
        C(int c);
    
    public:
        void display();
    
    protected:
        int m_c;
    
    };
    
    C::C(int c):m_c(c){}
    
    void C::display() {
    
        cout<<"Class C: m_c="<<m_c<<endl;
    
    }
    
    
    class D:public B,public C{
    
    public:
        D(int a,int b,int c,int d);
    
    public:
        void display();
    
    private:
        int m_d;
    
    };
    
    D::D(int a, int b, int c, int d) :B(a,b),C(c),m_d(d){}
    
    void D::display() {
    
        cout<<"Class D: m_a="<<m_a<<", m_b="<<m_b<<", m_c="<<m_c<<", m_d="<<m_d<<endl;
    }
    
    
    
    int main(){
        A *pa =new A(1);
        B *pb =new B(2,20);
        C *pc =new C(3);
        D *pd =new D(4,40,400,4000);
    
        pa=pd;
        pa ->display();
    
        pb=pd;
        pb ->display();
    
        pc=pd;
        pd ->display();
    
        cout<<"-----------------------"<<endl;
        cout<<"pa="<<pa<<endl;
        cout<<"pb="<<pb<<endl;
        cout<<"pc="<<pc<<endl;
        cout<<"pd="<<pd<<endl;
    
    
        return 0;
    
    }
    
    Class A:m_a=4
    Class B:m_a=4, m_b40
    Class D: m_a=4, m_b=40, m_c=400, m_d=4000
    -----------------------
    pa=0x55a33ea0ced0
    pb=0x55a33ea0ced0
    pc=0x55a33ea0ced8
    pd=0x55a33ea0ced0
    

    本例中定义了多个对象指针,并尝试将派生类指针赋值给基类指针。与对象变量之间的赋值不同的是,对象指针之间的赋值并没有拷贝对象的成员,也没有修改对象本身的数据,仅仅是改变了指针的指向。

    • 通过基类指针访问派生类的成员
      请读者先关注第 68 行代码,我们将派生类指针 pd 赋值给了基类指针 pa,从运行结果可以看出,调用 display() 函数时虽然使用了派生类的成员变量,但是 display() 函数本身却是基类的。也就是说,将派生类指针赋值给基类指针时,通过基类指针只能使用派生类的成员变量,但不能使用派生类的成员函数,这看起来有点不伦不类,究竟是为什么呢?第 71、74 行代码也是类似的情况。

    pa 本来是基类 A 的指针,现在指向了派生类 D 的对象,这使得隐式指针 this 发生了变化,也指向了 D 类的对象,所以最终在 display() 内部使用的是 D 类对象的成员变量,相信这一点不难理解。

    编译器虽然通过指针的指向来访问成员变量,但是却不通过指针的指向来访问成员函数:编译器通过指针的类型来访问成员函数。对于 pa,它的类型是 A,不管它指向哪个对象,使用的都是 A 类的成员函数,具体原因已在《C++函数编译原理和成员函数的实现》中做了详细讲解。

    概括起来说就是:编译器通过指针来访问成员变量,指针指向哪个对象就使用哪个对象的数据;编译器通过指针的类型来访问成员函数,指针属于哪个类的类型就使用哪个类的函数。

    1. 赋值后值不一致的情况
      本例中我们将最终派生类的指针 pd 分别赋值给了基类指针 pa、pb、pc,按理说它们的值应该相等,都指向同一块内存,但是运行结果却有力地反驳了这种推论,只有 pa、pb、pd 三个指针的值相等,pc 的值比它们都大。也就是说,执行pc = pd;语句后,pc 和 pd 的值并不相等。

    将派生类引用赋值给基类引用

    引用在本质上是通过指针的方式实现的,这一点已在《引用在本质上是什么,它和指针到底有什么区别》中进行了讲解,既然基类的指针可以指向派生类的对象,那么我们就有理由推断:基类的引用也可以指向派生类的对象,并且它的表现和指针是类似的。

    修改上例中 main() 函数内部的代码,用引用取代指针:

    int main(){
        D d(4, 40, 400, 4000);
       
        A &ra = d;
        B &rb = d;
        C &rc = d;
       
        ra.display();
        rb.display();
        rc.display();
        return 0;
    }
    
    Class A: m_a=4
    Class B: m_a=4, m_b=40
    Class C: m_c=400
    

    ra、rb、rc 是基类的引用,它们都引用了派生类对象 d,并调用了 display() 函数,从运行结果可以发现,虽然使用了派生类对象的成员变量,但是却没有使用派生类的成员函数,这和指针的表现是一样的。

    引用和指针的表现之所以如此类似,是因为引用和指针并没有本质上的区别,引用仅仅是对指针进行了简单封装,读者可以猛击《引用在本质上是什么,它和指针到底有什么区别》一文深入了解。

    最后需要注意的是,向上转型后通过基类的对象、指针、引用只能访问从基类继承过去的成员(包括成员变量和成员函数),不能访问派生类新增的成员。

    更多相关内容
  • 基类派生类对象之间有赋值兼容关系,由于派生类中包含从基类继承的成员,因此可以派生类的值赋给基类对象,用到基类对象的时候可以用其子类对象代替。 具体表现以下几个方面: 派生类对象可以基类对象赋值...
  • 以下简单示例演示了在基类中声明可从派生类引发的事件的标准方法。此模式广泛应用于 .NET Framework 类库中的 Windows 窗体类。 创建可用作其他类的基类的类时,应考虑如下事实:事件是特殊类型的委托,只可以从...
  • 只有公用继承能较好地保留基类的特征,它保留了除构造函数和析构函数以外的基类所有成员,基类的公用或保护成员的访问权限在派生类中全部都按原样保留下来了,在派生类可以调用基类的公用成员函数访问基类的私有...
  • 今天小编就为大家分享一篇关于C++基类指针和派生类指针之间的转换方法讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
  • 今天重温C++的知识,当看到虚基类这点的时候,那时候也...(1)、obj 是一个虚拟继承的派生类的对象 (2)、mem是从虚拟基类派生下来的成员 (3)、p是基类类型的指针 当这种情况下,p->mem会比obj.mem多了两个中间层
  • 《Visual C++2012入门经典(第6版)》实例,在派生类的构造函数调用基类的构造函数
  • 基类派生类

    2021-08-12 09:20:45
    C++11标准,如果我们需要默认的行为,可以通过在参数列表后面写上 = default 来要求编译器生成构造函数。其中,= default 既可以和声明一起出现在类的内部,也可以作为定义出现在类的外部。和其他函数一样,如果...
    Quote() = default;
    
    因为该构造函数不接受任何实参,所以它是一个默认构造函数。在C++11标准中,如果我们需要默认的行为,可以通过在参数列表后面写上 = default 来要求编译器生成构造函数。其中,= default 既可以和声明一起出现在类的内部,也可以作为定义出现在类的外部。和其他函数一样,如果= default 在类的内部,则默认构造函数是内联的;如果它在类的外部,则该成员默认情况下不是内联的。

    之所以有效,是因为对内置类型的数据成员提供了初始值。



    定义基类

    class Quote1 {
    public:
    	Quote1() = default;
    	Quote1(const string& book, double sales_price) :bookNo(book), price(sales_price) {}
    	string isbn()const { return bookNo; }
    	// 返回给定数量的书籍销售总额
    	// 派生类负责改写并使用不同的则扣计算算法
    	virtual double net_price(size_t n)const {
    		return n * price;
    	}
    	virtual ~Quote1() = default;// 对析构函数进行动态绑定
    private:
    	string bookNo;				// 书籍的 ISBN 编号
    protected:
    	double price = 0.0;			// 普通状态下不打折的价格
    };
    

    基类通常都应该定义一个虚构函数,即使该函数不执行任何实际操作也是如此。

    成员函数与继承

    派生类可以继承其基类的成员,然而遇到与类型相关的操作时,派生类必须对其重新定义。即,派生类需要对这些操作提供自己的新定义以覆盖(voerride)从基类继承而来的旧定义。
      在C++语言中,基类必须将它的两种成员函数分开来:一种是基类希望其派生类进行覆盖的函数;另一种是基类希望派生类直接继承而不要改变的函数。对于前者,基类通常将其定义为虚函数(virtual)。当使用指针或引用调用虚函数时,该调用将被动态绑定。
      任何构造函数之外的非静态函数都可以是虚函数。关键字virtual只能出现在类内部的声明语句之前而不能用于类外部的函数定义。
      成员函数如果没被声明为虚函数,则其解析过程发生在编译时而非运行时。对于isbn成员来说这正是我们希望看到的结果。isbn函数的执行与派生类的细节无关,不管作用于Quote对象还是Bulk_quote对象,isbn函数的行为都一样。在继承层次关系中只有一个isbn函数,因此也就不存在调用isbn()时到底执行哪个版本的疑问。

    访问控制与继承

    派生类可以继承定义在基类中的成员,但是派生类的成员函数不一定有权访问从基类继承而来的成员。和其他使用基类的代码一样,派生类能访问共有成员,而不能访问私有成员。不过在某些时候基类中还有这样一类成员,基类希望它的派生类有权访问该成员,同时禁止其他用户访问。用受保护的(protected)访问运算符说明这样的成员。
      Quote类希望它的派生类定义各自的net_price函数,因此派生类需要访问Quote的price成员。此时我们将price定义成受保护的。

    定义派生类

    派生类必须通过使用类派生列表(class derivation list)明确指出它是从哪些基类继承来的。
      派生类必须将其继承而来的成员函数中需要覆盖的那些重新声明。

    class Bulk_quote1 :public Quote1 {
    public:
    	Bulk_quote1() = default;
    	Bulk_quote1(const string&, double, size_t, double);
    	// 覆盖基类的函数版本以实现基于大量购买的则扣政策
    	double net_price(size_t)const override;
    private:
    	size_t min_qty = 0;			// 适用折扣政策的最低购买量
    	double discount = 0.0;		// 以小数表示的折扣额
    };
    

    如果一个派生是公有的,则基类的公有成员也是派生类接口的组成部分。此外,能将公有派生类型的对象绑定到基类的引用或指针上。
      因为在派生列表中使用了public,所以Bulk_quote的接口隐式地包含isbn函数,同时在任何需要Quote的引用或指针的地方我们都能使用Bulk_quote的对象。

    派生类中的虚函数

    派生类不总是覆盖它继承的虚函数。如果派生类没有覆盖其基类中的某个虚函数,则该虚函数的行为类似于其他的普通成员,派生类会直接继承其在基类中的版本。
      派生类可以在它覆盖的函数前使用virtual关键字,但不是非得这么做。C++11标准允许派生类显式地注明它使用某个成员覆盖了它继承的虚函数,具体做法是添加一个关键字override。

    派生类对象及派生类向基类的类型转换

    一个派生类对象包含多个组成部分:一个含有派生列自己定义的(非静态)成员的子对象,以及一个与该派生类继承的基类对应的子对象,如果有多个基类,那么这样的子对象也有多个。因此,一个Bulk_quote对象将包含四个数据元素:它从Quote继承而来的bookNo和price数据成员,以及Bulk_quote自己定义的min_qty和discount成员。

    在一个对象中,继承自基类的部分和派生类自定义的部分不一定是连续存储的。

    因为在派生类对象中含有与其基类对应的组成部分,所以我们能把派生类的对象当成基类对象来使用,而且我们也能将基类的指针或引用绑定到派生类对象中的基类部分上。

    Quote item;		// 基类对象
    Buik_quote bulk;	// 派生类对象
    Quote *p = &item;	// p指向Quote对象
    p = &bulk;			// p指向bulk的Quote部分
    Quote &r = bulk;	// r绑定到bulk的Quote部分
    

    这种转换通常称为派生类到基类(derived-to-base)的转换。和其他类型转换一样,编译器会隐式地执行派生类到基类地转换。

    在派生类对象中含有与其基类对应的组成部分,这一事实是继承的关键所在。

    派生类构造函数

    尽管在派生类对象中含有从基类继承而来的成员,但是派生类并不能直接初始化这些成员。和其他创建了基类对象的代码一样,派生类也必须使用基类的构造函数来初始化它的基类部分。

    每个类控制它自己的成员初始化过程。

    Bulk_quote1::Bulk_quote1(const string& book, double p, size_t qty, double disc) :Quote1(book, p), min_qty(qty), discount(disc) {}
    

    该函数将它的前两个参数(分别表示ISBN和价格)传递给Quote的构造函数,由Quote的构造函数负责初始化Bulk_quote的基类部分。当(空的)Quote构造函数体结束后,构建的对象的基类部分也就完成初始化了。接下来初始化由派生类直接定义的min_qty成员和discount成员。最后运行Bulk_quote构造函数的(空的)函数体。
      除非特别指出,否则派生类对象的基类部分会像数据成员一样执行默认初始化。

    首先初始化基类的部分,然后按照声明的顺序依次初始化派生类的成员。

    派生类使用基类的对象

    派生类可以直接访问基类的公有成员和受保护的成员:

    // 如果达到了购买书籍的某个最低限量值,就可以享受折扣价格了
    double Bulk_quote1::net_price(size_t cnt)const {
    	if (cnt >= min_qty)
    		return cnt * (1 - discount) * price;
    	else
    		return cnt * price;
    }
    

    派生类的作用域嵌套在基类的作用域之内。

    关键概念:遵循基类的接口
      必须明确一点:每个类负责定义各自的接口。要想与类的对象交互必须使用该类的接口,即使这个对象是派生类的基类部分也是如此。
      因此,派生类对象不能直接初始化基类的成员。尽管从语法上来说我们可以在派生类构造函数体内给它的公有或受保护的基类成员赋值,但是最好不要这样做。

    继承与静态成员

    如果基类定义了一个静态成员,则在整个继承体系中只存在该成员的唯一定义。不论从基类中派生出多少个派生类,对于每个静态成员来说都只存在唯一的实例。
      静态成员遵循通用的访问控制规则,如果基类中的成员是private的,则派生类无权访问它。假设某静态成员是可访问的,则我们即可以用过基类使用它也能通过派生类使用它。

    派生类的声明

    派生类的声明与其他类的差别不大,声明中包含类名但是不包含它的派生列表。
      一条声明语句的目的是令程序知晓某个名字的存在以及该名字表示一个什么样的实体。派生列表以及与定义有关的其他细节必须与类的主体一起出现。

    被用作基类的类

    如果我们想将某个类用作基类,则该类必须已经定义而非仅仅声明。
    这一规定的原因显而易见:派生类中包含并且可以使用它从基类继承而来的成员,为了使用这些成员,派生类当然要知道它们是什么。因此该规定还有一层隐含的意思,即一个类不能派生它本身。

    防止继承的发生

    在类名后跟一个关键字final:

    class NoDerived final { /* */ }	// NoDerived不能作为基类
    

    类型转换与继承

      可以将基类的指针或引用绑定到派生类对象上有一层及其重要的含义:当使用基类的引用(或指针)时,实际上我们并不清楚该引用(或指针)所绑定对象的真实类型。

    静态类型与动态类型

      当我们使用存在继承关系的类型时,必须将一个变量或其他表达式的静态类型(static type)与该表达式表示对象的动态类型(dynamic type)区分开来。表达式的静态类型在编译时总是已知的,它是变量声明时的类型或表达式生成的类型;动态类型则是变量或表达式表示的内存中的对象的类型。动态类型直到运行时才可知。
      如果表达式既不是引用也不是指针,则它的动态类型永远与静态类型一致,无论如何都不能改变该变量对应的对象的类型。

    基类的指针或引用的静态类型可以与其动态类型不一致。

    不存在从基类向派生类的隐式类型转换

      之所以存在派生类向基类的类型转换是因为每个派生类对象都包含一个基类部分,而基类的引用或则指针可以绑定到该基类部分上。一个基类的对象既可以以独立的形式存在,也可以作为派生类对象的一部分存在。如果基类对象不是派生类对象的一部分,则它只含有基类定义的成员,而不含有派生类定义的成员。
      因为一个基类的对象可能是派生类对象的一部分,也可能不是,所以不存在从基类向派生类的自动类型转换。
      即使一个基类指针或引用绑定在一个派生类对象上,也不能指向从基类向派生类的转换。

    Bulk_quote bulk;
    Quote *itemP = &bulk;		// 正确:动态类型是Bulk_quote
    Bulk_quote *bulkP = itemP;	// 错误:不能将基类转换成派生类
    

      编译器在编译时无法确定某个特定的转换在运行时是否安全,这是因为编译器只能通过检查指针或引用的静态类型来推断该转换是否合法。
    如果在基类中含有一个或多个虚函数,我们可以使用dynamic_cast请求一个类型转换,该转换的安全检查将在运行时执行。

    Bulk_quote* bulkP = dynamic_cast<Bulk_quote*>(itemP);	// 正确
    

    同样,如果我们已知某个基类向派生类的转换是安全的,则可以使用static_cast来强制覆盖掉编译器的检查工作。

    在对象之间不存在类型转换

      派生类向基类的自动类型转换只对指针或引用类型有效,在派生类类型和基类类型之间不存在这样的转换。
      当初始化或赋值一个类类型的对象时,实际上是在调用某个函数。当执行初始化时,调用构造函数;当执行赋值操作时,调用赋值运算符。这些成员通常都包含一个参数,该参数的类型时类类型的const版本的引用。
      因为这些成员接受引用作为参数,所以派生类向基类的转换允许我们给基类的拷贝、移动操作传递一个派生类的对象。这些操作不是虚函数。当我们给基类的构造函数传递一个派生类对象时,实际运行的构造函数时基类中定义的那个,显然该构造函数只能处理基类自己的成员。

    Bulk_quote bulk;
    Quote item(bulk);		// 使用Quote::Quote(const Quote&)构造函数
    item = bulk;			// 调用Quote::operator=(const Quote&)
    

    在上述过程中会忽略Bulk_quote部分,所以可以说bulk的Bulk_quote部分被切掉(sliced down)了。

    当我们用一个派生类对象为一个基类对象初始化或赋值时,只有该派生类对象中的基类部分会被拷贝,移动或赋值,它的派生类部分将被忽略掉。

    展开全文
  • 基类指针指向派生类对象、派生类指针指向基类对象 以下代码运行后的输出结果是() #include using namespace std; class A { public: void virtual print() { cout << "A" << endl; } }; class ...

    基类指针指向派生类对象、派生类指针指向基类对象

    以下代码运行后的输出结果是()

    #include using namespace std;
    class A
      {
    public:
      void virtual print()
        {
          cout << "A" << endl;
        }
    };
     
    class B : public A
    {
    public:
        void virtual print()
        {
            cout << "B" << endl;
        }
    };
    int main()
    {
        A* pA = new A();
        pA->print();
        B* pB = (B*)pA;
        pB->print();
        delete pA, pB;
        pA = new B();
        pA->print();
        pB = (B*)pA;
        pB->print();
    }
    using namespace std;
    class A{
        public:void virtual print(){
            cout << "A" << endl;}
        };
    class B : public A{
        public:void virtual print(){
            cout << "B" << endl;}
        };
        int main(){
            A* pA = new A();//基类指针指向基类对象
            pA->print();
            B* pB = (B*)pA;//派生类指针指向基类对象
            pB->print();
            delete pA, pB;
            pA = new B();//基类指针指向派生类对象
            pA->print();
            pB = (B*)pA;//派生类指针指向派生类对象
            pB->print();
    }
    

    在这里插入图片描述

    解答:

    1. A* pA = new A()//基类指针指向基类对象,毫无疑问调用的是A类的print
      输出A

    2. B* pB = (B*)pA;//派生类指针指向基类对象,这里疑问会比较大。首先是为什么这里不会报错,为什么派生类指针指向基类对象可以成立?理论上指针的可访问范围一定大于对象的大小,会指向一些未知区域导致运行出错,但是要注意的是,这个题目里面B类不存在新增数据成员,所以不会出错。还有就是由于是基类对象,还没有发生虚函数掩盖
      输出A

    3. pA = new B();//基类指针指向派生类对象。基类指针之所以可以访问派生类对象是因为指针的可访问范围一定小于对象的大小,所以做完切割即可,即切割一些派生类中存在而基类中不存在的成员即可。此时派生类对象的vptr指向的虚函数表中,派生类虚函数已经把基类同名虚函数掩盖掉了,所以指向的肯定是派生类虚函数
      输出B

    4. pB = (B*)pA;//派生类指针指向派生类对象。注意此时pA指向的是派生类对象
      输出B

    总结:(在分析完运行不出错的前提下)看指向的对象是啥类型,而不是看指针的类型

    这题涉及到的知识点

    虚函数预备常识

    需要知道一些常识,一个类所有的函数都是再code代码区中唯一的存放一份。而数据成员则是每个对象存储一份,并按照声明顺序依次存放。
    类A中有了虚函数就会再类的数据成员的最前面添加一个vfptr指针(void** vfptr),这个指针用来指向一个vtable表(一个函数指针数组)(一个类只有一个该表),该表存储着当前类的所有 虚函数 的地址。这样vfptr就成为了一个类似成员变量的存在。访问虚函数的时候通过vfptr间址找到vtable表,再间址进而找到要调用的函数。这样就在一定程度上摆脱了类型制约。

    示例:

    (People类是基类,student类是People类的派生类, Senior类是Student派生类)
    在这里插入图片描述
    (在虚函数表中,基类的虚函数在 vtable 中的索引(下标)是固定的,不会随着继承层次的增加而改变,派生类新增的虚函数放在 vtable 的最后。如果派生类有同名的虚函数遮蔽(覆盖)了基类的虚函数,那么将使用派生类的虚函数替换基类的虚函数,这样具有遮蔽关系的虚函数在 vtable 中只会出现一次。)

    只要vptr的值不同,那么访问函数成员的时候使用的vtable表就不同,就可能访问到不同类的函数成员。B类对象中的vptr指向B类自己的vtable。
    当B类继承A类的时候,因为A中有虚函数,编译器就自动的给B类添加vfprt指针和vtable表。也可以理解为B类继承来了A类中的那个vptr指针成员
    当A类指针指向B类对象时,发生假切割。要知道这个过程只是切掉A类中没有的那些成员。(即当People类指针指向Student类对象时,切割掉m_score这个People类中没有的成员)
    由于vptr是从A类中继承来的,所以这个量仍将保留。而对于vptr的值则不会改变,仍然指向B类的vtable表。所以访问F1函数的时候是通过B类的vtable表去寻址的,自然就是使用子类的函数(拿图中的情况举例,子类的Student::display()函数已经覆盖了People::display()函数,此时A类指针访问虚函数display()时也是访问到子类的Student::display()函数)。

    当B类的指针指向A类的对象时(当B类存在新增数据成员时可能出错),同理。

    而对于普通函数则受类型的制约,(因为没有vptr指针)使用哪个类的指针调用函数,那么所调用的就是那个累的函数。
    总而言之,普通函数通过对象或指针的类型来找所调用的函数,而虚函数是通过一个指针来找到所要调用的函数的。

    示例

    #include <iostream.h>
    
    class A
    {
    public:
    virtual void F1()
    {
    cout<<"A1"<<endl;
    }
    void F2()
    {
    cout<<"A2"<<endl;
    }
    
    };
    class B :public A
    {
    public:
    void F1()
    {
    cout<<"B1"<<endl;
    }
    void F2()
    {
    cout<<"B2"<<endl;
    }
    
    };
    void main(){
    
    A *pa;
    B *pb;
    B TB;
    A TA;
    
    pa = &TB;//基类指针指向派生类对象
    pa->F1();
    pa->F2();
    
    pb =(B *) &TA;//派生类指针指向基类对象
    
    pb->F1();
    pb->F2();
    
    }
    输出:
    B1
    A2
    A1
    B2
    

    基类指针指向派生类对象:如果基类声明的不是虚函数就调用基类的,如果是虚函数并在派生类中实现,就调用派生类的函数;
    派生类指针指向基类对象:如果基类声明的是虚函数就调用基类的,如果不是虚函数并在派生类中实现,就调用派生类的函数.

    理由见上

    参考

    【1】C++ 派生类指针指向基类对象

    展开全文
  • 通过基类指针调用派生类中的方法动态绑定静态绑定作用域掩盖dynamic_cast<>动态转型的作用区分接口继承和实现继承 动态绑定 动态类型指“目前所指对象的类型”; 动态绑定是指执行期间(非编译期)判断所...

    C++中通过基类指针调用派生类中定义的方法&&C++继承中的名称遮掩

    动态绑定

    动态类型指“目前所指对象的类型”;
    动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

    #include <iostream>
    using namespace std;
    class BaseA
    {
    public:
        virtual void display(){
            cout << "BaseA::display()" << endl;
        }
        virtual void display(int a){
            cout<<"BaseA::display(a)" <<endl;
        }
    };
    class DerivedA : public BaseA
    {
    public:
       virtual void display(){
            cout << "DerivedA::display()" << endl;
        }
    };
    
    int main()
    {
        DerivedA t;
        
        BaseA* pt1=&t;
        pt1->display();
        
        DerivedA* pt2=&t;
        pt2->display();
        //pt2->display(2);
        //错误,DerivedA中的函数display()在“名称遮掩规则”下遮掩了BasedA中的函数display(int a)
        system("pause");
        return 0;
    }
    

    结果:
    在这里插入图片描述
    virtual函数是动态绑定,动态绑定时通过指针调用的是该指针指向的对象中的成员函数。

    virtual函数是动态绑定,而成员函数缺省参数却是静态绑定

    静态绑定

    静态类型指在程序声明时所采取的类型,
    静态绑定是指在程序编译过程中,把函数(方法或者过程)调用与响应调用所需的代码结合的过程称之为静态绑定。

    #include <iostream>
    using namespace std;
    class BaseA
    {
    public:
        void display(){
            cout << "BaseA::display()" << endl;
        }
        void display(int a){
            cout<<"BaseA::display(a)" <<endl;
        }
    };
    class DerivedA : public BaseA
    {
    public:
       void display(){
            cout << "DerivedA::display()" << endl;
        }
    };
    
    int main()
    {
        DerivedA t;
        
        BaseA* pt1=&t;
        pt1->display();//BasedA::display()
        
        DerivedA* pt2=&t;
        pt2->display();//DerivedA::display()
        //pt2->display(2);
        //错误,DerivedA中的函数display()在“名称遮掩规则”下遮掩了BasedA中的函数display(int a)
        system("pause");
        return 0;
    }
    

    结果:
    在这里插入图片描述
    这个示例只是为了说明静态绑定时,通过指针调用的是该指针声明时的类型对应的类对象中的成员函数。

    但是必须强调一下:不要在派生类中重新定义基类的非虚函数

    从面向对象的角度来说,公有继承是一种Is-a的关系。其表明每一个派生类对象都可以被当做基类对象来处理。基类的每一个接口和成员变量派生类也有。如果重定义了非虚函数,会导致派生类对象将不再是基类对象(当通过指向派生类对象的指针访问时)

    很少情况下,需要重定义非虚函数。一个特例是为了解决私有继承中的名称遮掩问题。在私有继承中,基类的公有函数在派生类中都是私有,如果派生类想要继承基类的某个接口,可以使用所谓的转交函数(forwarding function)。即定义一个public的与基类中那个接口同名的函数,这个函数一般是inline,函数体实现部分仅调用基类的对应函数即可。

    关于C++继承中的名称遮掩问题,下面细讲一下:

    C++继承中的名称遮掩

    示例:

    #include <iostream>
    using namespace std;
    
    class BaseA
    {
    public:
    	void display() {
    		cout << "BaseA::display()" << endl;
    	}
    	void display(int a) {
    		cout << "BaseA::display(a)" << endl;
    	}
    };
    class DerivedA : public BaseA
    {
    public:
    	void display() {
    		cout << "DerivedA::display()" << endl;
    	}
    };
    
    int main()
    {
    	DerivedA t;
    
    	BaseA* pt1 = &t;
    	pt1->display();//BasedA::display()
    	pt1->display(2);//静态绑定,所以还能通过基类指针去调用派生类中的display(int a)函数
    
    	DerivedA* pt2 = &t;
    	pt2->display();//DerivedA::display()
    	//pt2->display(2);
    	//错误,DerivedA中的函数display()在“名称遮掩规则”下遮掩了BasedA中的函数display(int a)
    	system("pause");
    	return 0;
    }
    

    结果:
    在这里插入图片描述

    上面的代码展示了,DerivedA类“没有能够成功继承”BaseA中的display(int a)函数。打上引号的原因是,我们使用基类指针指向派生类时,又能够成功调用。

    从名称查找的观点出发,BaseA::display(int a)被DerivedA::display()函数遮蔽了。

    编译器拒绝这样的继承是为了防止在程序库或应用框架内建立新的derived class 时附带地从疏远的base classes 继承重载函数。

    如果你想从基类BasedA中继承重载了的display()和display(int a)函数,同时又想在派生类DerivedA中重新定义display()函数时不遮掩display(int a)函数名,

    可以通过在派生类DerivedA中用using声明基类BasedA中的函数名:

    #include <iostream>
    using namespace std;
    
    class BaseA
    {
    public:
    	void display() {
    		cout << "BaseA::display()" << endl;
    	}
    	void display(int a) {
    		cout << "BaseA::display(a)" << endl;
    	}
    };
    class DerivedA : public BaseA
    {
    public:
    	using BaseA::display;//让BaseA class内名为dislay的所有东西在DerivedA中都可见(并且public)
    
    	void display() {
    		cout << "DerivedA::display()" << endl;
    	}
    };
    
    int main()
    {
    	DerivedA t;
    
    	BaseA* pt1 = &t;
    	pt1->display();//BasedA::display()
    	pt1->display(2);//静态绑定,所以还能通过基类指针去调用派生类中的display(int a)函数
    
    	DerivedA* pt2 = &t;
    	pt2->display();//DerivedA::display()
    	pt2->display(2);//正确, DerivedA类中使用using BaseA::display,避免了名称遮掩
    	system("pause");
    	return 0;
    }
    

    结果:
    在这里插入图片描述
    总结:derived classes 内的名称会遮掩base classes 内的名称;如果继承base class并加上重载函数,而又希望在derive中重新定义或覆写其中一部分,那必须手动使用using 声明那些覆写的函数,否则某些希望继承的名称会被遮掩。

    但有时候,比如在private继承时(表示的是一种根据基类实现派生类的关系,is-implemented-in-terms-of),基类的公有函数在派生类中都是私有。如果派生类不想继承base class的所有函数接口。而只想要继承基类的某一个接口,可以使用所谓的转交函数(forwarding function)

    以上面的例子为例,DerviedA只想继承BaseA::display()函数,

    #include <iostream>
    using namespace std;
    class BaseA
    {
    public:
    	virtual void display() {
    		cout << "BaseA::display()" << endl;
    	}
    	virtual void display(int a) {
    		cout << "BaseA::display(a)" << endl;
    	}
    };
    class DerivedA : private BaseA
    {
    public:
    	//using BaseA::display;//让BaseA class内名为dislay的所有东西在DerivedA中都可见(并且public)
    
    	virtual void display() {
    		BaseA::display();
    	}
    };
    
    int main()
    {
    	DerivedA t;
    	t.display();
    	//t.display(2);//错误
    	system("pause");
    	return 0;
    }
    

    结果:
    在这里插入图片描述

    dynamic_cast<>动态转型的作用

    dynamic_cast:将指向base class objects的pointers或references转型为指向derived(或sibling base)clasee objects的pointers或references,

    #include <iostream>
    using namespace std;
    class BaseA {
    public:
    	virtual void display() = 0;
    };
    
    class DerivedA : public BaseA {
    public:
    	virtual void display() {
    		cout << "DerivedA::display()" << endl;
    	}
    	void displaymore() {
    		cout << "DerivedA::displaymore()" << endl;
    	}
    };
    
    int main() {
    	BaseA *pt = new DerivedA;
    	pt->display();
    	//pt->displaymore();//error: ‘class BaseA’ has no member named ‘display()’
    	dynamic_cast<DerivedA*>(pt)->displaymore();
    	system("pause");
    	return 0;
    }
    

    结果:
    在这里插入图片描述

    区分接口继承和实现继承

    在设计类的继承关系时,

    • 有时候你希望derived classes只继承成员函数的接口(即声明);
    • 有时候你希望derived classes同时继承成员函数的接口和实现,但又希望能够覆写它们所继承的实现;
    • 有时候你希望derived classes同时继承成员函数的接口和实现,并且不允许覆写任何东西;
    1. 声明一个pure virtual 函数的目的是为了让derived classes只继承成员函数的接口

    2. 声明impure virtual函数(非纯虚函数)是为了让derived classes同时继承成员函数的接口和缺省实现

    3. 声明non-virtual 函数的目的是为了让derived classes同时继承成员函数的接口和一份强制性实现;

    展开全文
  • C++派生类中如何初始化基类对象

    千次阅读 2018-08-26 16:41:42
    关于派生类中如何初始化基类对象,我派生类对于构造函数不都是先构造基类对象,然后构造子类对象,但是如果我们成员初始化列表先初始化派生类的私有成员,函数内去调用基类的构造函数,能编译通过吗?...
  • 定义一个基类Base,有两个公共成员函数fun1()和fun2(),如果私有派生出Derived,如何通过Derived的对象调用基类的函数fun1()和fun2()? 源代码 #include "stdafx.h" #include<iostream> using namespace ...
  • C#在派生类中调用基类成员

    千次阅读 2019-10-04 21:57:35
    C#的派生类中,我们可以使用base关键字调用基类中的公有或者受保护成员。这些成员只能是构造函数、实例方法或者实例属性。 base关键字调用基类成员的语法格式如下: base . identifier或 base[expression-list]...
  • 的成员可以分为三种类型,分别为public成员、...2、对象访问:在派生类外部,通过派生类的对象对从基类继承来的成员的访问。 public继承 此时基类的public成员,protected成员,private成员对于派生类来说变
  • 当类的继承方式为私有继承时,基类的public成员和protected成员被继承后成为派生类的private成员,派生类的其它成员可以直接访问它们,但是类的外部通过派生类的对象无法访问。基类的private成员私有派生类中是...
  • ① 公有派生时,基类中的所有公有成员在派生类中也都是公有成员,基类中的所有保护成员在派生类中仍然是保护成员。 ② 保护派生时,基类中的所有公有成员和保护成员在派生类中是保护成员。 ③ 私有派生时,基类中...
  • 基类成员被派生类继承以后访问权限保持不更多相关问题商业洽谈中可以用你的热情来感染对方。()商业计划书中,战略规划应该分阶段和分年份提出()。商代有学者认为,( )与金(青铜)、玉一样,都是专门的礼用材料...
  • 我们明确初始化一个派生类对象时,会先调用基类的构造函数,然后再调用派生类的构造函数;回收资源的时候,先调用派生类的析构函数,再调用基类的析构函数。 我们初始化一个派生类的对象时,如何去初始化一个...
  • 定义一个基类Base,有两个公共成员函数fun1()和fun2(),如果公有派生出Derived类,Derived类重载了基类的成员函数fun1(),没有重载基类的成员函数fun2(),如何在派生类的函数调用基类的成员函数fn1(),fn2()?...
  • 【C++】C++继承和派生类、虚基类

    万次阅读 多人点赞 2018-06-07 18:46:17
    派生则是继承的直接产物,它通过继承已有的一个或多个来产生一个新的通过派生可以创建一种类族。 继承 基本概念 定义一个A时,若它使用了一个已定义B的部分或全部成员,则称A继承了...
  • 实例 class base ...因为dynamic_cast向下转换时,会检查该基类指针是否是指向其派生类对象的,否则返回NULL; 说白了,就是检查待转换的指针或引用是否是指向要转换的类型的。      
  • 本篇主要参考《C++ Primer 第5版》,为什么派生类能向基类进行类型转换。...因为派生类对象含有基类的组成部分,所以可以基类类型的指针指向派生类对象基类部分,安全地访问基类部分的成员
  • 派生类基类继承的过程可以分为三个步骤:吸收基类成员、修改基类成员和添加新成员。 吸收基类成员就是代码复用的过程,修改基类成员和添加新成员实现的是对原有代码的扩展,而代码的复用和扩展是继承与派生的主要...
  • 派生类基类的了解

    2019-01-08 20:13:25
    看了c#的教程,也上网络上搜索基类派生类的概念,一直没有一个很明确的定义.弄得偶一直很迷糊,现在搜索到c++的教程里倒是有比较明确的说明,特意贴这里,帮助自己理解. 继承机制提供了无限重复利用程序资源的一种...
  • C++继承,很容易遇到一个问题,那就是将派生类指针赋值给基类指针(向上转型)的情况,下面我们就来举例分析: 举一个多继承的例子: #include <iostream> using namespace std; //基类A class A { ...
  • 在派生类中实现与基类方法相同的方法,返回值和特征标都一样,在派生类中就相当于覆盖基类的该方法。 #include &lt;iostream&gt; using namespace std; class Fish{ private: bool isFreshWaterFish; ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 190,601
精华内容 76,240
热门标签
关键字:

在派生类中可以通过基类名