精华内容
下载资源
问答
  • 在基类定义的protected
    2021-11-03 21:16:54

    定义基类和派生类

    我们首先完成基类和派生类的定义:

    我们首先定义了一个名为Quote的类作为基类表示按原价销售的书籍,Bulk_quote的类作为派生类表示可以打折销售的书籍。这些类包含两个函数:isbn()表示书籍的ISBN编号,netPrice()表示实际出售的价格。

    class Quote {
    public:
        Quote() = default;
        Quote(const string &s, double d):_isbn(s), _price(d){}
        virtual double netPrice(int n) const {
            return _price * n;
        }
    private:
        string _isbn = "x";
    protected:
        double _price = 0.0;
    };
    class BulkQuote : public Quote { //公共继承了Quote
    public:
        BulkQuote() = default;
        BulkQuote(const string &s, double d, int b, double dis):
    		Quote(s, d), _minBuy(b), _discount(dis){}
        double netPrice(int n) const override {
            if (n > _minBuy) {
                return n * _discount * _price;
            }
            return n * _price;
        }
    private:
        int _minBuy;         //最低购买数量
        double _discount;    //小数表示的折扣额
    };
    

    1.定义基类

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

    ①基类的两种成员函数:

    基类希望派生类进行覆盖的函数(虚函数):

    • 当我使用指针或引用调用虚函数时,该调用会被动态绑定:根据引用或指针所绑定的对象类型,调用可能执行基类的版本,也可能执行某个派生类的版本。
    • 虚函数是在成员函数声明语句之前加上关键字virtual使得该函数执行动态绑定。virtual只能出现在类内部的声明语句之前,而不能用于类外部的函数定义。
    • 虚函数解析过程发生在运行时。

    基类希望派生类直接继承的函数:

    • 静态成员函数解析发生在编译时。

    ②访问控制与继承

    派生类可以继承定义在基类中的成员,但是不一定有权访问从基类继承而来的成员。派生类可以访问基类的公有和保护成员而不能访问基类的私有成员

    2.定义派生类

    ①派生类中的虚函数

    派生类经常覆盖它继承的虚函数,但并非必须覆盖,如果没有覆盖其继承基类的虚函数,则类似其他普通成员,派生类会直接继承在基类的版本。

    派生类用关键字override显式地定义覆盖了地虚函数。

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

    一个派生类包含多个组成部分:

    • 派生类自己定义的成员的子对象。
    • 与派生类继承的基类对应的子对象。

    如:BulkQuote包含自己定义的子对象_minBuy、_discount和从基类Quote继承的子对象_isbn和_price。

    因为派生类对象中含有与其基类对应的组成部分,所以我们能把派生类的对象当成基类对象来使用,也能将基类的指针或引用绑定到派生类对象中的基类部分上。这种转换称为派生类到基类的类型转换。编译器会隐式的执行派生类到基类的转换。

    ③派生类构造函数

    派生类从基类继承了基类的构造函数,派生类对象不能直接初始化基类的成员,而是应该通过基类的接口(基类的构造函数)初始化从基类继承而来的成员

    如:BulkQuote的构造函数

    BulkQuote(const string &s, double d, int b, double dis):
    	Quote(s, d), _minBuy(b), _discount(dis){}
    

    将其前两个参数传递给Quote的构造函数,由Quote初始化BulkQuote的基类部分,接下来初始化由派生类直接定义的成员。

    ④派生类的声明

    派生类的声明与其他类差别不大,声明中包含类名,但是不包含它的派生列表

    class BulkQuote : public Quote; //错误:声明不能包含派生列表
    class BulkQuote; //正确:声明派生类的正确方式
    

    声明语句的目的是令程序知晓某个名字的存在以及该名字表示一个什么样的实体,如一个类、一个函数或一个变量等。派生列表以及预定义有关的其他细节必须与类的定义一起出现。

    ⑤被用作基类的类

    如果我们向将某个类用作基类,则该类必须已经定义而非仅仅声明:

    class Quote;   //声明但未定义
    class BulkQuote : public Quote {...} //错误:Quote必须已经定义
    

    ⑥防止继承的发生

    有时我们会定义这样一种类,我们不希望其他类继承它,或者不想考虑他是否适合做一个基类。只要我们在这种类名后面加上一个final:

    class NoDerived final {...} //NoDerived不能用作基类
    
    
    更多相关内容
  • 基类protected成员

    千次阅读 2014-07-27 18:23:27
    2014-07-27 星期日 18:10:56 ...1、基类protected成员 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

    2014-07-27 星期日 18:10:56

    重温下c++ primer,随意记录。

    1、基类protected成员
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    #include <iostream>
    #include <iterator>
    #include <vector>
    #include <algorithm>
    #include <utility>
    #include <string>
     
    using  namespace  std;
     
    /*
    如果没有继承,
    类只有两种用户:
    1:类本身的成员
    2:类的用户(instance或有效的ptr&ref)
     
    有继承
    3:derive:public/protected/private均可
     
    derive可以访问base protected,不可以访问base private
     
    希望禁止derive访问的成员应该设为 private
    希望提供derive实现所需方法或数据的成员应设为 protected。
     
    所以
    base提供给derive的接口应该是
    protected 成员和 public 成员的组合。
     
    */
    class  base
    {
    public :
         base():m_base(3){}
         virtual  ~base(){}
         
    protected :
         int  m_base;
    };
     
    class  derive: private  base
    {
    public :
         derive():m_derive(4){}
    public :
         void  access( const  base& refb,  const  derive& refd)
         {
             /*
             此外,protected 还有另一重要性质:
             派生类只能通过派生类对象访问其基类的 protected 成员,
             派生类对其基类类型对象的 protected 成员没有特殊访问权限。
             */
             //cout << "refb.m_base  : "<< refb.m_base<< endl;
     
             /*
             15.2.2. protected 成员
             可以认为 protected 访问标号是 private 和 public 的混合:
             .像 private 成员一样,protected 成员不能被类的用户访问。
             .像 public  成员一样,protected 成员可被该类的派生类访问。
             */     
             cout <<  "refd.m_base  : " << refd.m_base<< endl;
             cout <<  "refd.m_derive: " << refd.m_derive<< endl;
         }
    protected :
         int  m_derive;
    };
     
    int  main ( int  argc, char  *argv[])
    {
    #if 1
         base b;
         base *pb =  new  base();
         //cout << "m_base  : "<< b.m_base<< endl;
     
         derive d;
         d.access(b, d);
    #endif
         return  0;
    }



    展开全文
  • C# 里面,接口的定义只需要类里面存在和接口声明相同的方法或属性就可以,而存在的方法或属性是子类定义的还是基类里面定义的都无所谓。也就是在基类里面写好了方法,但不继承接口,等子类继承接口的时候就不...

    在 C# 里面,接口的定义只需要类里面存在和接口声明相同的方法或属性就可以,而存在的方法或属性是在子类定义的还是基类里面定义的都无所谓。也就是在基类里面写好了方法,但不继承接口,等子类继承接口的时候就不需要子类实现方法。通过这样的方法可以在基类里面添加一些辅助方法,而这些方法默认不给子类开启,除非子类继承了接口

    在基类定义的方法,如果在子类继承的接口里面声明的方法刚好和基类定义的相同,那么子类可以不需要再次定义。在基类可以判断当前自己的类型是否继承了接口

    这个方法用在基类里面期望在特定的代码里面给子类暴露一些内部的方法,但是又不能让子类随意调用。或者预设了一些方法,这些方法存在性能或其他的问题,只有子类声明自己需要这个功能的时候才被调用

    内部方法隐藏

    在基类里面有一些内部的方法,默认不让子类使用的,但只有某些特殊的子类的情况才能被使用。同时这些内部方法还可能被外部的其他方法调用,不一定是子类

    按照以上的约定就可以写出如下代码

        public interface ILindexiDb
        {
            string GetPrivateString();
        }
    
        public class BassClass
        {
            public string GetPrivateString()
            {
                if (!(this is ILindexiDb))
                {
                    throw new NotSupportedException("这个方法除非是林德熙逗比才能使用,其他人不能使用");
                }
    
                return "林德熙是逗比";
            }
        }
    

    从代码可以看到,除非这个类继承了 ILindexiDb 不然 GetPrivateString 方法将会异常,因为我期望这个方法只有我才能调用

    那么假如我有一个 Foo 的接口里面就需要使用到对应的方法,而默认业务想要返回的方法是基类定义的上面的方法

        public interface IFoo
        {
            void GetString();
        }
    
    

    不明真相的小伙伴可能会如何写?

        public class Foo : BassClass, IFoo
        {
            public void GetString()
            {
                Console.WriteLine(GetPrivateString());
            }
        }
    

    上面代码是小伙伴会直接写出的代码,然而这段代码没有运行的时候就会炸,原因是在基类判断当前这个类没有继承林德熙是逗比这个接口

    在我将基类放在一个程序集编译为dll之后,小伙伴没有方法直接看到源代码,他在调用 GetPrivateString 方法的时候抛出了方法没有支持异常,于是小伙伴会认为这个方法还没实现,她就不会想要去使用这个方法

    而林德熙逗比将会写出下面代码用来访问内部的方法

        public class Foo2 : BassClass, ILindexiDb, IFoo
        {
            public void GetString()
            {
                Console.WriteLine(GetPrivateString());
            }
        }
    

    因为注入的都是 IFoo 接口,小伙伴都不知道实际实现的 Foo2 里面做了什么,如果不信,请在 VisualStudio 里面用上代码,然后调试一下。此时很难知道 Foo2 里面做了什么魔法让调用 GetPrivateString 方法不会异常,因为在调试的时候是很难知道当前这个类继承了哪些接口,而一般调试的时候都不会注意到某个类继承的接口会影响到具体调用的方法

    我写了很多调试的方法,请看dotnet 代码调试方法

    引用子类方法

    按照这个约定,其实还可以让基类在满足一定条件的时候才能执行,这个条件需要子类定义。这个方法是上面说到的内部方法隐藏的升级,用起来将会更复杂

    在基类里面判断当前类是否继承了某个接口,如果继承了,那么将会可以调用接口的方法。这个方法一般用在某个接口的声明要用到很多基类的信息才能实现,或某个接口本身不适合在外部的其他类继承,只能由子类继承

    例如我定义了接口,用来判断林德熙是不是逗比的接口,而这个接口的方法实现需要依赖于基类的内容才能判断,但基类的内容是私有的,从业务上也不应该让外部方法知道

        public interface ILindexiConverter
        {
            bool CheckLindexiIsDoubi();
        }
    

    以下是基类的定义

        public class BassClass
        {
            protected string Name { get; } = "林德熙逗比";
    
            public string GetPrivateString()
            {
                // 忽略代码
            }
        }
    

    基类里面定义的 Name 属性是不能给外部类知道的,要不大家都知道我逗比,只有子类才能知道

    此时基类需要依赖 ILindexiConverter 的判断才能决定是否需要做业务

            public string GetPrivateString()
            {
                // 忽略代码
    
                    if (!lindexiConverter.CheckLindexiIsDoubi())
                    {
                        throw new NotSupportedException("这个方法除非是林德熙逗比才能使用,其他人不能使用");
                    }
    
            }
    

    然而 ILindexiConverter 接口除非是子类,其他类是无法按照预期做到的,于是在基类里面可以这样写

                if (this is ILindexiConverter lindexiConverter)
                {
                    if (!lindexiConverter.CheckLindexiIsDoubi())
                    {
                        throw new NotSupportedException("这个方法除非是林德熙逗比才能使用,其他人不能使用");
                    }
                }
    

    判断当前这个类是否有继承 ILindexiConverter 接口,如果有就调用方法,按照这个方式写小伙伴就更加不能简单调用这个方法

    而作为逗比我将会写出下面的代码,用于调用

        public class Foo2 : BassClass, ILindexiDb, IFoo, ILindexiConverter
        {
            public void GetString()
            {
                Console.WriteLine(GetPrivateString());
            }
    
            /// <inheritdoc />
            public bool CheckLindexiIsDoubi()
            {
                return base.Name == "林德熙逗比";
            }
        }
    

    我在 Foo2 里面的 CheckLindexiIsDoubi 用到基类的属性判断,同时基类因为子类继承了预期接口而能使用

    这就是通过在基类里面定义好方法和定义好代码逻辑,只要子类继承接口才能实现

    我搭建了自己的博客 https://blog.lindexi.com/ 欢迎大家访问,里面有很多新的博客。只有在我看到博客写成熟之后才会放在csdn或博客园,但是一旦发布了就不再更新

    如果在博客看到有任何不懂的,欢迎交流,我搭建了 dotnet 职业技术学院 欢迎大家加入

    知识共享许可协议
    本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接:http://blog.csdn.net/lindexi_gd ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系

    展开全文
  • C++ 定义基类和派生类

    千次阅读 2021-05-22 20:18:37
    定义基类和派生类 定义基类 class Quote { public: Quote() = default; Quote(const string &book, double sales_price): bookNo(book), price(sales_price) {} string isbn() const { return bookNo; } /...

    定义基类和派生类

    定义基类

    class Quote
    {
        public:
            Quote() = default;
            Quote(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 ~Quote() = default;
        private:
            string bookNo; // 书籍的ISBN编号
        protected:
            double price = 0.0; // 代表普通状态下不打折的价格
    };
    

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

    成员函数与继承

    派生类可以继承其基类的成员,然而当遇到如net_price这样与类型相关的操作时,派生类必须对其重新定义(覆盖 override)。

    基类希望派生类进行覆盖的成员函数,一般被定义为虚函数(virtual)。除了构造函数,任何非静态函数都可以是虚函数。

    关键字virtual只能出现在类内部的声明语句之前而不能用于类外部的函数定义。如果基类把一个函数声明成虚函数,则该函数的派生类中隐式地也是虚函数

    访问控制与继承

    • public:public表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用
    • private:private表示私有,私有的意思就是除了class自己之外,任何人都不可以直接使用,即便是子女,朋友,都不可以使用。
    • protected:protected对于子女、朋友来说,就是public的,可以自由使用,没有任何限制,而对于其他的外部class,protected就变成private。
    读写权限当前类派生类外部类
    public
    protected×
    private××
    friend××

    定义派生类

    class Bulk_quote : public Quote
    {
        public:
            Bulk_quote() = default;
            Bulk_quote(const string&, double, std::size_t, double);
            // 覆盖基类的函数版本以实现基于大量购买地折扣政策
            double net_price(size_t) const override;
        private:
            // 实用折扣政策地最低购买量
            size_t min_qty = 0;
            // 以小数表示的折扣额
            double discount = 0.0;
    };
    
    • 派生类声明基类的虚函数的新版本时,必须加上关键字override
    • 基类的虚析构函数不需要覆盖
    • 注意:直接编译上述代码是会报错:
      C:\MinGW\bin\mingw32-g++.exe -g E:\C++\opp.cpp -o E:\C++\opp.exe c:/mingw/bin/../lib/gcc/mingw32/9.2.0/../../../../mingw32/bin/ld.exe: C:\Users\12435\AppData\Local\Temp\cc34XfTn.o: in functionZN10Bulk_quoteD1Ev’:
      E:/C++/opp.cpp:31: undefined reference to vtable for Bulk_quote' c:/mingw/bin/../lib/gcc/mingw32/9.2.0/../../../../mingw32/bin/ld.exe: C:\Users\12435\AppData\Local\Temp\cc34XfTn.o: in functionZN10Bulk_quoteC1Ev’:
      E:/C++/opp.cpp:27: undefined reference to vtable for Bulk_quote' collect2.exe: error: ld returned 1 exit status
      \
    • 原因:父类中的虚函数,在子类中需要重新实现它们。对于在上述代码中的net_price()函数,在子类中仅仅声明了,但是没有定义,因此我们需要进一步定义函数:
    double Bulk_quote::net_price(size_t n) const{
        return 0.0;
    };
    

    派生类中的虚函数

    为了所设计的应用适应新版本的功能,派生类经常(但不总是)覆盖基类中已定义的函数。一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须与被它覆盖的基类函数完全一致,而且虚函数的返回类型也必须与基类函数匹配

    基类中的虚函数在派生类中隐含地也是一个虚函数

    必须要清晰的概念:虚函数只能借助于指针或者引用来达到多态的效果。

    finaloverride说明符

    值得一提的是,派生类如果定义了一个函数与基类中虚函数的名字相同但是形参列表不同,这仍然是合法的行为。编译器将认为新定义的这个函数与基类中原有的函数是相互独立的(不是覆盖)。

    为了避免上述规则造成编程的混乱,我们可以适应override关键字标记派生类中的虚函数。

    class B
    {
    public:
    	virtual void f1(int) const;
    	virtual void f2();
    	void f3();
    }
    
    class D1 : public B
    {
    public:
    	//True
    	void f1(int) const override; 
    	//False,函数不匹配,编译报错
    	void f2(int) override;
    	//False, 函数不匹配,编译报错
    	void f3() override;
    	//False, 函数不匹配,编译报错
    	void f4() override;
    }
    

    如果不希望某个虚函数被覆盖,我们可以将其设定为final

    class D2 : public B
    {
    public:
    	// 从B继承f2()和f3(),覆盖f1(int)
    	void f1(int) const final;
    }
    class D3 : public D2
    {
    public:
    	void f2(); // 正确,覆盖从间接基类B继承而来的f2
    	void f1(int) const; // 错误,D2不允许f1被覆盖
    }
    

    虚函数与默认实参

    如果某次函数调用使用了默认实参,则该实参值由本次调用的静态类型决定。如果我们通过基类的引用或指针调用函数,则使用基类中定义的默认实参,即使实际允许的是派生类中的函数版本也是如此。

    如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致。

    回避虚函数的机制

    在某些情况下,我们希望对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个版本。

    强行调用基类中定义的函数版本而不管BaseP的动态类型到底是什么
    double undiscounted = baseP->Quote::net_price(42);
    

    通常情况下,只有成员函数(或友元)中的代码才需要使用作用域运算符来回避虚函数的机制。

    当一个类有子类时,该类的析构函数必须是虚函数,否则会有资源释放不完全的情况

    参考:C++杂谈 为什么需要虚析构函数

    #include<iostream>
    using namespace std;
    
    class IDelegate
    {
    public:
    	IDelegate(){}
    	~IDelegate()	//非虚析构函数
    	{
    		std::cout << "~IDelegate()!" << std::endl;
    	}
    };
    
    class CStaticDelegate : public IDelegate
    {
    public:
    	CStaticDelegate()  {}
    	~CStaticDelegate()
    	{
    		std::cout << "~CStaticDelegate()!" << std::endl;
    	}
    };
    
    template<class T>
    class CMethodDelegate : public IDelegate
    {
    public:
    	CMethodDelegate()  {}
    	~CMethodDelegate()
    	{
    		std::cout << "~CMethodDelegate()!" << std::endl;
    	}
    private:
        T m;
    };
    
    int main()
    {
    	IDelegate* demo1 = new CStaticDelegate();
    	注意这里是用父类的指针指向子类
    	IDelegate* demo2 = new CMethodDelegate<int>();
    	delete demo1;
    	delete demo2;
    }
    
    
    

    注意这里是用父类的指针指向子类
    结果:

    ~IDelegate()!
    ~IDelegate()!
    

    这里可以看到,对象销毁时只调用了父类的析构函数。如果这时子类的析构函数中有关于内存释放的操作,将会造成内存泄露。所以需要给父类的析构函数加上virtual

    class IDelegate
    {
    public:
    	IDelegate(){}
    	virtual ~IDelegate()	//非虚析构函数
    	{
    		std::cout << "~IDelegate()!" << std::endl;
    	}
    };
    

    结果:

    ~CStaticDelegate()!
    ~IDelegate()!      
    ~CMethodDelegate()!
    ~IDelegate()!
    

    内存清理干净了,舒服~~

    子类不需要声明函数覆盖基类的虚析构函数

    子类不是必须覆盖基类的虚函数

    被覆盖的情况:

    #include<iostream>
    using namespace std;
    
    class A
    {
    public:
    	A(){}
        virtual void print_hello();
    	virtual ~A()	//非虚析构函数
    	{
    		// std::cout << "~A()!" << std::endl;
    	}
    private:
        int a = 10;
    };
    
    void A::print_hello()
    {
        cout<<"a = "<<a<<endl;
    }
    
    class B : public A
    {
    public:
    	B()  {}
        void print_hello() override;
    	~B()
    	{
    		// std::cout << "~B()!" << std::endl;
    	}
    private:
        int b = 20;
    };
    
    void B::print_hello()
    {
        cout<<"b = "<<b<<endl;
    }
    
    int main()
    {
    	A* demo1 = new A();
    	B* demo2 = new B();
    
        demo1->print_hello();
        demo2->print_hello();
    
    	delete demo1;
    	delete demo2;
    }
    
    

    结果:

    a = 10
    b = 20
    

    不覆盖:

    class B : public A
    {
    public:
    	B()  {}
        // void print_hello() override;
    	~B()
    	{
    		// std::cout << "~B()!" << std::endl;
    	}
    private:
        int b = 20;
    };
    
    // void B::print_hello()
    // {
    //     cout<<"b = "<<b<<endl;
    // }
    

    结果:

    a = 10
    a = 10
    

    如果print_hello()不是虚函数呢?能不能被覆盖?或者说被重载?(答案是可以的)

    #include<iostream>
    using namespace std;
    
    class A
    {
    public:
    	A(){}
        void print_hello();
    	virtual ~A()	//非虚析构函数
    	{
    		// std::cout << "~A()!" << std::endl;
    	}
    private:
        int a = 10;
    };
    
    void A::print_hello()
    {
        cout<<"a = "<<a<<endl;
    }
    
    class B : public A
    {
    public:
    	B()  {}
        void print_hello(int para);
    	~B()
    	{
    		// std::cout << "~B()!" << std::endl;
    	}
    private:
        int b = 20;
    };
    
    void B::print_hello(int para)
    {
        cout<<"para = "<<para<<", ";
        cout<<"b = "<<b<<endl;
    }
    
    int main()
    {
    	A* demo1 = new A();
    	B* demo2 = new B();
    
        demo1->print_hello();
        demo2->print_hello(30);
    
    	delete demo1;
    	delete demo2;
    }
    
    

    结果:

    a = 10
    para = 30, b = 20
    

    抽象基类

    纯虚函数

    首先:强调一个概念

    • 定义一个函数为虚函数,不代表函数为不被实现的函数。

    • 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。

    • 定义一个函数为纯虚函数,才代表函数没有被实现。

    定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。

    一般虚函数清晰的范例:

    #include<iostream>
    #include<memory>
    using namespace std;
    
    class A
    {
    public:
    	A(){}
        virtual void print_hello();
    	 ~A()	//非虚析构函数
    	{
    		std::cout << "~A()!" << std::endl;
    	}
    private:
        int a = 10;
    };
    
    void A::print_hello()
    {
        cout<<"a = "<<a<<endl;
    }
    
    class B : public A
    {
    public:
    	B()  {}
        void print_hello();
    	~B()
    	{
    		std::cout << "~B()!" << std::endl;
    	}
    private:
        int b = 20;
    };
    
    void B::print_hello()
    {
        cout<<"b = "<<b<<endl;
    }
    
    int main()
    {
    	// 基类指针 -> 基类对象
        shared_ptr<A> demo1 = make_shared<A>(A());
        // 子类指针 -> 子类对象
        shared_ptr<B> demo2 = make_shared<B>(B());
        // 基类指针 -> 子类对象
        shared_ptr<A> demo3 = make_shared<B>(B());
    
        // 绑定到基类的函数
        demo1->print_hello();
        // 绑定到子类的函数
        demo2->print_hello();
        // 绑定到子类的函数
        demo3->print_hello();
        // 强制使用基类版本的函数
        demo3->A::print_hello();
    }
    
    

    结果:

    ~A()!
    ~B()!
    ~A()!
    ~B()!
    ~A()!
    a = 10
    b = 20
    b = 20
    a = 10
    ~B()!
    ~A()!
    ~B()!
    ~A()!
    ~A()!
    

    虚函数只能借助于指针或者引用来达到多态的效果。

    纯虚函数是在基类中声明的虚函数,它在基类中没有定义(仅声明,没定义),但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加 =0

    virtual void funtion1()=0
    

    引入纯虚函数的意义

    • 为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
    • 在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

    为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

    声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。

    纯虚函数最显著的特征是:它们必须在继承类中重新声明函数(不要后面的=0,否则该派生类也不能实例化),而且它们在抽象类中往往没有定义。

    定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。

    纯虚函数的意义,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的默认实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。

    容器与继承

    因为C++不允许在容器中保存不同类型的元素,因此通常必须采取间接存储的方式。

    class A
    {
    public:
    	A(){}
        virtual void print_hello();
    	 ~A()	//非虚析构函数
    	{
    		std::cout << "~A()!" << std::endl;
    	}
    private:
        int a = 10;
    };
    
    void A::print_hello()
    {
        cout<<"a = "<<a<<endl;
    }
    
    class B : public A
    {
    public:
    	B()  {}
        void print_hello();
    	~B()
    	{
    		std::cout << "~B()!" << std::endl;
    	}
    private:
        int b = 20;
    };
    
    void B::print_hello()
    {
        cout<<"b = "<<b<<endl;
    }
    
    int main()
    {
        vector<A> Avec;
        Avec.push_back(A());
        // 合法,但是只能把对象的基类部分拷贝给Avec
        Avec.push_back(B());
        Avec.back().print_hello();
    }
    

    结果:

    ~A()!
    ~A()!
    ~B()!
    ~A()!
    a = 10
    ~A()!
    ~A()!
    

    在容器中放置(智能)指针而非对象

    当我们希望在容器中存放具有继承关系的对象时,我们实际上存放的通常是基类的指针(更好的选择是智能指针)。这些指针所指对象的动态类型可能是基类类型,也可能是派生类类型:

    vector<shared_ptr<A>> Avec;
    Avec.push_back(make_shared<A>(A()));
    Avec.push_back(make_shared<B>(B()));
    Avec.back()->print_hello();
    

    结果:

    ~A()!
    ~B()!
    ~A()!
    b = 20
    ~A()!
    ~B()!
    ~A()!
    
    展开全文
  • c++定义基类和派生类

    2022-05-05 18:42:38
    2、基类通过其成员函数的声明语句(不得用于类外部的函数定义)前加上virtual使得该函数执行动态绑定 3、任何构造函数出外的非静态函数都可以是虚函数 4、如果在基类中把一个函数声明称虚函数,那么该函数派生...
  • 基类的public和protected成员的访问属性派生类中保持不变,但基类的private成员不可直接访问; 派生类中的成员函数可以直接访问基类中的public和protected成员,但是不能直接访问基类的private成员; 通过派生类...
  • 下面描述中,错误的是() 基类定义的public成员公有继承的派生类中可见,也能类外被访问 ...基类定义的public和protected成员...基类定义protected成员在protected继承的派生类中可见,也能类外被访问
  • C++ 抽象基类

    千次阅读 2021-04-29 00:20:38
    抽象基类: abstract base class,简称ABC,只定义接口,而不涉及实现,主要用于定义派生类的通用接口。抽象基类必 须包含至少一个纯虚方法。且包含纯虚函数的抽象基类,不能用来创建对象,只能用作基类。 纯虚方法...
  • 定义基类和派生类

    千次阅读 2019-11-05 08:35:41
    数据抽象是一种依赖于接口和...二是使用这些彼此相似的类编写程序时,我们可以一定程度上忽略掉它们的区别。 定义鸡肋: #include<bits/stdc++.h> using namespace std; class Quote{ public: Quote()=...
  • C++学习 十五、类继承(1)基类,派生类前言类继承类的关系与继承基类, 派生类基类派生类构造函数,析构函数文件位置访问权限protected 前言 本篇开始学习C++类的继承。 类继承 C++的类继承用于描述一种is的关系。...
  • C++中继承机制的繁复和微妙让作为初学者的我感觉头痛,下面对基类中public/private/protected成员的访问方式做一个总结。例子来自Primer C++ 中的书店购书的管理系统。 基类: class Item_base { public: Item_base...
  • 基类的基本概念

    2022-07-17 20:52:36
    C++提供了**虚基类**使得继承共同间接基类时只保留一份成员。
  • C++ 虚基类

    千次阅读 多人点赞 2021-05-13 12:07:25
    C++ 虚基类. 虚基类如何解决多重继承的问题.
  • C++学习之第十天-继承

    2021-11-07 22:43:03
    基类protected成员派生类中仍然是protected B.基类protected成员public派生类中仍然是protected的 C.基类protected成员private派生类中是private的 D.基类protected成员不能被派生类的对象访问 ...
  • C++ 虚基类定义、功能、规定

    千次阅读 2017-09-30 11:02:11
    虚继承和虚基类定义是非常的简单的,同时也是非常容易判断一个继承是否是虚继承的,虽然这两个概念的定义是非常的简单明确的,但是C++语言中虚继承作为一个比较生僻的但是又是绝对必要的组成部份而存在着,并且...
  • 经查阅资料 个人理解是这样的:只有派生类中的作用域中(譬如派生类中的函数)才可以通过派生类对象访问基类protected成员,派生类中也不可以直接访问基类对象的protected成员。 下面给出一个例子:
  • 当一个子类从父类继承时,父类的所有成员成为子类的成员,此时对父类成员的访问...(3)父类的protected成员成为子类的protected成员,只允许子类成员访问; 2.如果子类从父类继承时使用的继承限定符是protected,那么
  • C++的抽象基类

    2021-12-22 11:28:37
    很多情况下,定义那些不能实例化出对象的类是很有用的,例如一个抽象基类Shape的纯虚函数Draw()它并不知道自己要画一个什么形状,具体画什么圆啊,矩形啊等等都由派生类决定。因此依据这个可以将类分为抽象类...
  • 1、定义Controller基类 2、日期处理工具类 3、sql操作工具类 4、引入分页插件依赖 5、返回表格分页数据对象 6、表格数据处理 1、定义Controller基类 package com.ruoyi.framework.web.controller; /** * ...
  • (1)C++中,通过基类的指针或引用调用虚函数时发生动态绑定,既可以指向基类对象也可以指向派生类对象 这一事实是发生动态绑定的关键。用引用或指针调用的虚函数运行时确定,被调用的函数是引用或指针所指对象...
  • protected类型成员派生类定义时可以访问,派生类声明的对象不可以访问,即类体外不可以访问。 protected成员可以被基类的所有派生类使用,这一性质可以沿继承树无限向下传播。 也就是说: 基类中的...
  • 继承是C++中实现多态的重要方法,通过继承机制,可利用已有的...C++支持public,protected和private三种继承方式,请明确知道3者的区别和可见性。除非必要请不要实现多继承。多继承的强制类型转换会导致对象切片问题。
  • 【C++】C++继承和派生类、虚基类

    万次阅读 多人点赞 2018-06-07 18:46:17
    从已有的对象类型出发建立一种新的对象类型,使它部分或全部继承原对象的特点和功能,这是面向对象设计方法中的基本特性之一。...在定义一个类A时,若它使用了一个已定义类B的部分或全部成员,则称类A继承了类...
  • 实验6.3 定义一个车(vehicle)基类(虚基类

    万次阅读 多人点赞 2020-06-08 17:46:28
    定义一个车(vehicle)基类,具有MaxSpeed、Weight等成员变量,Run、Stop等成员函数,由此派生出自行车(bicycle)类、汽车(motorcar)类。自行车(bicycle)类有高度(Height)等属性,汽车(motorcar)类有座位数...
  • C++:继承

    2020-11-23 22:45:21
    文章目录基本概念访问控制和继承私有继承保护继承公有继承可访问性成员属性为protected的优点构造、析构顺序构造函数、析构函数继承问题二义性和支配规则虚基类赋值兼容与切片现象 基本概念 继承(inheritance)...
  • 基类内存布局:原来的基础上,把虚基类移到最后面,原来基类位置补一个虚基类指针vbptr 0、8是偏移量 菱形继承 class A { public: A(int data) :ma(data) { cout << "A()" << endl; } ~A() { ...
  • protected访问修饰符详解

    千次阅读 2021-03-18 20:53:35
    若子类与基类同一包中,那么子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法。 解释: 一个引用类,和被引用类一个包的时候,protected成员是可见的(不管...
  • C++ 继承 基类、派生类、访问权限

    千次阅读 2019-07-25 17:47:08
    “子类”和“父类”通常放一起称呼,“基类”和“派生类”通常放一起称呼。 派生类除了拥有基类的成员,还可以定义自己的新成员,以增强类的功能。 使用继承的场景: 创建的新类只是比原有类多出若干成员变量...
  • C++里,继承有三中方式,public、protected、private三中继承方式,我们需要记住这3种继承的可见性问题,会有两个可见性的问题, 一是子类与父类的可见性, 二是对象的可见性(具体更多是指对象与父类的成员可见...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 104,496
精华内容 41,798
热门标签
关键字:

在基类定义的protected