精华内容
下载资源
问答
  • 【C++】C++继承和派生、虚基类

    千次阅读 多人点赞 2018-06-07 18:46:17
    派生则是继承的直接产物,它通过继承已有的一个或多个类来产生一个新的类,通过派生可以创建一种类族。 继承 基本概念 在定义一个类A时,若它使用了一个已定义类B的部分或全部成员,则称类A继承了类...

    从已有的对象类型出发建立一种新的对象类型,使它部分或全部继承原对象的特点和功能,这是面向对象设计方法中的基本特性之一。继承不仅简化了程序设计方法,显著提高了软件的重用性,而且还使得软件更加容易维护。派生则是继承的直接产物,它通过继承已有的一个或多个类来产生一个新的类,通过派生可以创建一种类族。

     

    继承

    基本概念

    在定义一个类A时,若它使用了一个已定义类B的部分或全部成员,则称类A继承了类B,并称类B为基类或父类,称类A为派生类或子类。一个派生类又可以作为另一个类的基类,一个基类可以派生出若干个派生类,这样就构成类树。

    在C++中有两种继承:单一继承和多重继承。当一个派生类仅由一个基类派生时,称为单一继承;而当一个类由两个或更多个基类派生时,称为多重继承。

    C++中派生类从父类继承特性时,可在派生类中扩展它们,或者对其做些限制,也可以改变或删除某一特性,还可对某些特性不作任何修改。所有这些变化可归结为两种基本的面向对象技术:

    • 特性约束,即对父类的特性加以限制或删除;
    • 特性扩展,即增加父类的特性。

    单一继承

    从一个基类派生一个类的一般格式为:

    class 派生类类名: <Access> 基类类名{
        private:
            ...
        public:
            ...
        protected:
            ...
    };

    其中:Access可有可无,用于规定基类中的成员在派生类中的访问权限,它可以是关键字private、public、protected三者之一。当Access省略的时候,对于类,系统默认约定为private;而对于结构体而言,系统默认约定为public。花括号中的部分是在派生类中新增加的成员数据或成员函数,这部分也可为空。

    当Access为public时,称派生类为公有派生;当Access为private时,称派生类为私有派生;当Access为protected时,称派生类为保护派生。派生时指定不同的访问权限,直接影响到基类中的成员在派生类中的访问权限。

    公有派生

    公有派生时,基类中所有成员在公有派生类中保持各个成员的访问权限。具体地说:

    • 基类中public成员,在派生类中保持public,在派生类中或派生类外部可以直接使用;
    • 基类中private成员,属于基类私有,在派生类中或派生类外部都不能直接使用;
    • 基类中protected成员,可在公有派生类中直接使用,但在派生类外不可直接访问。

    例如:

    #include <iostream>
    using namespace std;
    
    class A {
    	private:
    		int x;
    	protected:
    		int y;
    	public:
    		int z;
    		A(int a, int b, int c) { x = a; y = b; z = c; }
    		void Setx(int a) { x = a; }
    		void Sety(int a) { y = a; }
    		int Getx() { return x; }
    		int Gety() { return y; }
    		void ShowA() {
    			cout << x << '\t' << y << '\t' << z << endl;
    		}
    };
    
    class B : public A {
    	private:
    		int Length, Width;
    	public:
    		B(int a, int b, int c, int d, int e) :A(a, b, c) {                //A
    			Length = d; Width = e;
    		}
    		void Show() {
    			cout << Length << '\t' << Width << endl;
    			cout << Getx() << '\t' << y << '\t' << z << endl;         //B
    		}
    };
    
    int main()
    {
    	B b1(1, 2, 3, 4, 5);
    	b1.ShowA();
    	b1.Show();
    	cout << b1.Getx() << '\t' << b1.Gety() << '\t' << b1.z << endl;            //C
    
    	system("pause");
    	return 0;
    }

    解释:A行是在派生类的构造函数中调用基类的构造函数,写法与对象成员比较类似。只不过对象成员是类中有其他类的对象作为成员,而此处为继承;对象成员使用的是对象名,继承为类名。具体的讲解在下面会介绍到。

    主要看一下B行和C行。B行是在公有派生类中查看基类的方法,除了private不能直接查看之外,public和protected都能;C行是在公有派生类外查看基类的方法,仅有public的能够直接查看之外,private和protected都不能。

    私有派生

    对于私有派生类而言,其:

    • 基类中public成员和protected成员在派生类中均变成私有的,在派生类中仍可使用这些成员,而派生类之外均不能;
    • 基类中private成员,在派生类中和派生类外都不可直接使用。

    例子:

    #include <iostream>
    using namespace std;
    
    class A {
    	private:
    		int x;
    	protected:
    		int y;
    	public:
    		int z;
    		A(int a, int b, int c) { x = a; y = b; z = c; }
    		void Setx(int a) { x = a; }
    		void Sety(int a) { y = a; }
    		int Getx() { return x; }
    		int Gety() { return y; }
    		void ShowA() {
    			cout << x << '\t' << y << '\t' << z << endl;
    		}
    };
    
    class B : private A {
    	private:
    		int Length, Width;
    	public:
    		B(int a, int b, int c, int d, int e) :A(a, b, c) {                //A
    			Length = d; Width = e;
    		}
    		void Show() {
    			cout << Length << '\t' << Width << endl;
    			cout << Getx() << '\t' << y << '\t' << z << endl;         //B
    		}
    };
    
    int main()
    {
    	B b1(1, 2, 3, 4, 5);
    	b1.ShowA();                                                                //D
    	b1.Show();
    	cout << b1.Getx() << '\t' << b1.Gety() << '\t' << b1.z << endl;            //C
    
    	system("pause");
    	return 0;
    }

    首先:这段程序运行出错!

    B行没有问题,基类中的private无法访问,而public和protected都能直接访问。而C行和D行在派生类的外部,都不能直接调用基类的任何方法或成员,出错。

    综上所述,在派生类中,继承基类的访问权限可以概括为:

    公有或私有派生
    派生方式基类访问权限派生类中访问权限派生类外访问权限
    publicpublicpublic可访问
    publicprotectedprotected不可访问
    publicprivate不可访问不可访问
    private

    public

    private不可访问
    privateprotectedprivate不可访问
    privateprivate不可访问不可访问

    抽象类与保护的成员函数

    若定义了一个类,该类只能用做基类来派生出新的类,而不能用作定义对象,该类称为抽象类。当对某些特殊的对象要进行很好地封装时,需要定义抽象类。

    当把一个类的构造函数和析构函数的访问权限定义为保护的时,这种类为抽象类。

    原因:当某个类的构造函数或析构函数的访问权限定义为保护时,在类的外面(如:主函数等)由于无法调用该类的构造函数和析构函数,因此无法产生该类的对象或者撤销对象。而当使用该类作为基类产生派生类时,在派生类中是可以调用其基类的构造函数和析构函数的,因为基类中的保护成员在派生类中一样可以使用。

    但:如果将一个类的构造函数和析构函数的访问权限定义为私有的时候,通常这种类是没有任何实际意义的,既不能产生对象,也不能用来产生派生类。

    多重继承

    用多个基类来派生一个类时,其一般格式为:

    class 派生类类名: <Access> 基类类名1, <Access> 基类类名2,..., <Access> 基类类名n {
        private:
            ...
        public:
            ...
        protected:
            ...
    };

    其中:Access是以限定该基类中的成员在派生类中的访问权限,其规则与单一继承的用法类似。从上述格式可以看出,很容易将单一继承推广到多重继承。

     

    初始化基类成员

    在基类中定义了基类的构造函数,并且在派生类中也定义了派生类的构造函数,那么在产生派生类的对象时,一方面系统要调用派生类的构造函数来初始化在派生类中新增的成员数据,另一方面系统也要调用基类的构造函数来初始化派生类中的基类成员。

    这种基类的构造函数是由派生类的构造函数来确定的。为了初始化基类成员,派生类的构造函数的一般格式为:

    派生类类名(派生类参数列表): 基类类名1(基类1参数列表),  基类类名2(基类2参数列表),..., 基类类名n(基类n参数列表) {
        ...
    }

    需要注意的是:“派生类参数列表”是带有类型说明的形参表,而“基类参数列表”是不带有类型说明的实参表。其中,“基类参数列表”可以是表达式,可以是“派生类参数列表”下的形参,也可以是各种常量。

    如果派生类中包含对象成员,则在派生类的构造函数的初始化成员列表不仅要列举要调用的基类的构造函数,而且要列举调用的对象成员的构造函数。

    这里的顺序问题:

    • 如果某派生类有好几个基类,那么各个基类构造函数的调用顺序:在类继承中说明的顺序,与它们在构造函数的初始化成员列表的先后顺序无关;
    • 派生类构造函数、基类构造函数的调用顺序:当说明派生类对象时,系统首先调用个基类的构造函数,对基类成员进行初始化,然后执行派生类的构造函数。若某一个基类仍是派生类,则这种调用基类的构造函数的过程递进下去。当撤销派生类的对象时,析构函数的调用顺序正好与构造函数的顺序相反;
    • 对象成员的构造函数、基类构造函数的调用顺序:先调用基类的构造函数,再调用对象成员的构造函数,最后执行派生类的构造函数。有多个对象成员的条件下,调用这些对象成员的构造函数的顺序取决于它们在派生类中说明的顺序。

    重点:在派生类的构造函数的初始化成员列表中,对对象成员的初始化必须使用对象名;而对基类成员的初始化,使用的是对应基类的构造函数名。比如:

    Der(int a, int b): Base1(a), Base2(20), b1(200), b2(a+b){
        ...
    }

    来一条练习题:

    #include <iostream>
    using namespace std;
    
    class Base1 {
    	private:
    		int x;
    	public:
    		Base1(int a) {
    			x = a;
    			cout << x <<" 调用基类1的构造函数!" << endl;
    		}
    		~Base1() {
    			cout << "调用基类1的析构函数!" << endl;
    		}
    };
    
    class Base2 {
    private:
    	int y;
    public:
    	Base2(int a) {
    		y = a;
    		cout << y <<" 调用基类2的构造函数!" << endl;
    	}
    	~Base2() {
    		cout << "调用基类2的析构函数!" << endl;
    	}
    };
    
    class Der : public Base2, public Base1 {                                             //A
    	private:
    		int z;
    		Base1 b1;                                                            //B
    		Base2 b2;                                                            //C
    	public:
    		Der(int a, int b) :Base1(a), Base2(20), b2(200), b1(a + b) {         //D
    			z = b;
    			cout << "调用派生类的构造函数!" << endl;
    		}
    		~Der() {
    			cout << "调用派生类的析构函数!" << endl;
    		}
    };
    
    int main()
    {
    	Der d(100, 200);
    
    	system("pause");
    	return 0;
    }

    这段程序的运行结果为:

    20 调用基类2的构造函数!
    100 调用基类1的构造函数!
    300 调用基类1的构造函数!
    200 调用基类2的构造函数!
    调用派生类的构造函数!
    调用派生类的析构函数!
    调用基类2的析构函数!
    调用基类1的析构函数!
    调用基类1的析构函数!
    调用基类2的析构函数!
    请按任意键继续. . .

    解析:派生类构造函数的顺序,首先基类构造函数,再对象成员构造函数。基类构造函数顺序看A行,对象成员构造函数顺序看B、C行,而不是看初始化成员列表D行。同时注意一下,在D行,基类构造函数用类名,对象成员构造函数用对象名。

     

    冲突、支配规则和赋值兼容性

    冲突

    若一个公有的派生类是由两个或多个基类派生,当基类中成员的访问权限为public,且不同基类中的成员具有相同的名字时,出现了重名的情况。这是在派生类使用到基类中的同名成员时,出现了不唯一性,这种情况称为冲突。

    例如:

    class A {
    	public:
    		int x;
    		void Show() {
    			cout << x << endl;
    		}
    };
    
    class B {
    	public:
    		int x;
    		void Show() {
    			cout << x << endl;
    		}
    };
    
    class C :public A, public B {
    	public:
    		void Setx(int a) {
    			x = a;                    //A出错
    		}
    };

    此时,在A行编译器无法确定要访问的时基类A中的x,还是基类B中的x,即出现编译出错。

    解决这种冲突的办法有三种:

    • 使得各个基类中的成员名各不相同,但并不是最优解决方法;
    • 在各个基类中,把成员数据的访问权限说明为私有的,并在相应的基类中提供公有的成员函数来对这些成员数据进行操作,但这只适合于成员数据,不适合成员函数;
    • 使用作用域运算符“::”来限定所访问的是属于哪一个基类的。

    作用域运算符的一般格式为:

    类名:: 成员名

    其中:类名可以是任一基类或派生类的类名,成员名只能是成员数据名或成员函数名。

    当把派生类作为基类,又派生出新的派生类时,这种限定作用域的运算符不能嵌套使用,也就是说,如下形式的使用方式是不允许的:

    类名1:: 类名2:: ...:: 成员名

    例如:

    class A {
    	public:
    		int x;
    		void Show() {
    			cout << x << endl;
    		}
    };
    
    class B {
    	public:
    		int x;
    		void Show() {
    			cout << x << endl;
    		}
    };
    
    class C :public A, public B {
    	public:
    		void Setx(int a) {
    			A::x = a;                    //作用域运算符
    		}
    };

    支配规则

    在C++中,允许派生类中新增加的成员名与其基类的成员名相同,这种同名并不产生冲突。当没有使用作用域运算符时,则派生类中定义的成员名优先于基类中的成员名,这种优先关系称为支配规则。

    有点类似于“局部优先”的意思。

    例如:

    #include <iostream>
    using namespace std;
    
    class A {
    	public:
    		int x;
    		void Showx() {
    			cout << x << endl;
    		}
    };
    
    class B {
    	public:
    		int x;
    		void Showx() {
    			cout << x << endl;
    		}
    		int y;
    		void Showy() {
    			cout << y << endl;
    		}
    };
    
    class C :public A, public B {
    	public:
    		int y;
    		void Showy() {
    			cout << y << endl;
    		}
    };
    
    int main()
    {
    	C c1;
    	c1.A::x = 100;                        //冲突,基类A的x
    	c1.B::x = 100;                        //冲突,基类B的x
    	c1.y = 200;                           //支配,派生类中的y
    	c1.B::y = 100;                        //支配,基类B的y
    
    	system("pause");
    	return 0;
    }

    基类和对象成员

    任一基类在派生类中只能继承一次,否则就会造成成员名的冲突。比如:

    class A {
        public:
            float x;
    };
    
    class B :public A, public A{                //发生冲突
        ...
    };

    此时在派生类B中包含两个继承来的成员x,会在使用的时候产生冲突。为了避免这样的冲突,C++规定同一基类只能继承一次。

    若在类B中,确实要有两个类A的成员,则可用类A的两个对象作为类B的成员。例如:

    class B {
        A a1, a2;
    };

    把一个类作为派生类的基类或把一个类的对象作为一个类的成员,从程序的执行效果上看是相同的,但在使用上是有区别的:在派生类中可直接使用基类的成员(访问权限允许的话),但要使用对象成员的成员时,必须在对象名后面加上成员运算符“.”和成员名。

    赋值兼容规则

    在同类型的对象之间可以相互赋值,那么派生类的对象与其基类的对象之间能否相互赋值呢?这里就要讨论赋值兼容规则。

    简答的说:对于公有派生类来说,可以将派生类的对象赋给其基类的对象,反之是不允许的。

    例如:

    #include <iostream>
    using namespace std;
    
    class A {
    	public:
    		int x;
    };
    
    class B {
    	public:
    		int y;
    };
    
    class C :public A, public B {
    	public:
    		int y;
    };
    
    int main()
    {
    	C c1, c2, c3;
    	A a1, *pa1;
    	B b1, *pb1;
    
    	system("pause");
    	return 0;
    }

    赋值兼容与限制可归结为以下四点:

    • 派生类的对象可以赋值给基类的对象,系统是将派生类对象中从对应基类中继承来的成员赋给基类对象。例如:
    a1 = c1;                //将c1中从a1继承下来的对应成员(x)赋值给a1的对应成员
    • 不能将基类的对象赋值给派生类对象。例如:
    c2 = a1;                //错误
    • 可以将一个派生类对象的地址赋给基类的指针变量。例如:
    pa1 = &c2;
    pb1 = &c3;
    • 派生类对象可以初始化基类的引用。例如:
    B &rb = c1;

    注意:在后两者的情况下,使用基类的指针或引用时,只能访问从相应基类中继承来的成员,而不允许访问其他基类的成员或在派生类中增加的成员。

     

    虚基类

    在C++中,假定已定义了一个公共的基类A,类B由类A公有派生,类C也由类A公有派生,而类D是由类B和类C共同公有派生。显然在类D中包含类A的两个拷贝(实例)。这种同一个公共的基类在派生类中产生多个拷贝不仅多占用了存储空间,而且可能会造成多个拷贝中数据不一致。

    例如:

    #include <iostream>
    using namespace std;
    
    class A {
    	public:
    		int x;
    		A(int a = 0){
    			x = a;
    		}
    };
    
    class B : public A {
    	public:
    		int y;
    		B(int a = 0, int b=0):A(b) {
    			y = a;
    		}
    		void PB() {
    			cout << x << ' ' << y << endl;
    		}
    };
    
    class C : public A {
    	public:
    		int z;
    		C(int a = 0, int b = 0) :A(b) {
    			z = a;
    		}
    		void PC() {
    			cout << x << ' ' << z << endl;
    		}
    };
    
    class D :public B, public C {
    	public:
    		int m;
    		D(int a, int b, int d, int e, int f) :B(a, b), C(d, e) {
    			m = f;
    		}
    		void Print() {
    			PB();
    			PC();
    			cout << m << endl;
    		}
    };
    
    int main()
    {
    	D d1(100, 200, 300, 400, 500);
    	d1.Print();
    
    	system("pause");
    	return 0;
    }

    这段程序的运行结果为:

    200 100
    400 300
    500
    请按任意键继续. . .

    从结果可以看出,在类D中包含公共基类A的两个不同的拷贝(实例)。

    但如果将类D改成如下:

    class D :public B, public C {
    	public:
    		int m;
    		D(int a, int b, int d, int e, int f) :B(a, b), C(d, e) {
    			m = f;
    		}
    		void Print() {
    			cout << x << ' ' << y << endl;                //出错
    			cout << x << ' ' << z << endl;                //出错
    			cout << m << endl;
    		}
    };

    此时编译器无法确定成员x时从B类继承过来的,还是C类继承过来的,产生了冲突。可使用作用域运算符“::”来限定成员是属于类B或类C。也就是将程序改为:

    class D :public B, public C {
    	public:
    		int m;
    		D(int a, int b, int d, int e, int f) :B(a, b), C(d, e) {
    			m = f;
    		}
    		void Print() {
    			cout << B::x << ' ' << y << endl;
    			cout << C::x << ' ' << z << endl;
    			cout << m << endl;
    		}
    };

    需要注意的是,这里使用的是B::x和C::x,而不是B::A::x和C::A::x。也就是和上文冲突的区别:

    冲突是派生类继承的两个基类中含有相同名称的成员数据,而此处是派生类继承的两个基类同时又继承同一个基类。

    上面都是派生类中包含同一基类的两个拷贝,这样极有可能造成使用上的冲突。那么,在多重派生的过程中,若欲使公共的基类在派生类中只有一个拷贝,怎么来实现呢?

    可以将这种基类说明为虚基类。在派生类的定义中,只要在其基类的类名前加上关键字virtual,就可以将基类说明为虚基类。其一般格式为:

    class 派生类类名:virtual <Access> 基类类名{
        ...
    }
    
    class 派生类类名:<Access> virtual 基类类名{
        ...
    }

    其中:关键字virtual可放在访问权限之前,也可以放在访问权限之后,而且该关键字只对紧随其后的基类名起作用。

    例如:

    #include <iostream>
    using namespace std;
    
    class A {
    	public:
    		int x;
    		A(int a = 0){                                        //A
    			x = a;
    		}
    };
    
    class B : virtual public A {                                        //B
    	public:
    		int y;
    		B(int a = 0, int b=0):A(b) {
    			y = a;
    		}
    		void PB() {
    			cout << x << ' ' << y << endl;
    		}
    };
    
    class C : public virtual A {                                        //C
    	public:
    		int z;
    		C(int a = 0, int b = 0) :A(b) {
    			z = a;
    		}
    		void PC() {
    			cout << x << ' ' << z << endl;
    		}
    };
    
    class D :public B, public C {
    	public:
    		int m;
    		D(int a, int b, int d, int e, int f) :B(a, b), C(d, e) {        //D
    			m = f;
    		}
    		void Print() {
    			PB();
    			PC();
    			cout << m << endl;
    		}
    };
    
    int main()
    {
    	D d1(100, 200, 300, 400, 500);                                            //E
    	d1.Print();
    	d1.x = 600;                                                               //F
    	d1.Print();
    
    	system("pause");
    	return 0;
    }

    这段程序的运行结果为:

    0 100
    0 300
    500
    600 100
    600 300
    500
    请按任意键继续. . .

    首先,从这段程序的输出可以看出:在派生类D中的对象d1中仅仅只有基类A的一个拷贝(原因:当F行改变了基类中成员x的值之后,接下来基类B和C的输出结果同时都改变了)。这是因为在B行和C行都定义为了虚基类。

    其次,还可以看出一个好玩的现象:x的初值为0。

    当没有定义成虚基类的时候,在类D中包含公共基类A的两个不同的拷贝(实例),所以在E行初始化的时候,两份拷贝一份为200,一份为400。一旦变成虚基类仅有一个拷贝的时候,保留哪一个?还是怎么处理这个问题呢?

    解释:调用虚基类的构造函数的方法与调用一般基类的构造函数的方法是不同的。由虚基类经过一次或多次派生出来的派生类,在其每一个派生类的构造函数的成员初始化列表中,都必须给出对虚基类的构造函数的调用;如果未列出,则调用虚基类的缺省的构造函数。在这种情况下,在虚基类的定义中必须要有缺省的构造函数。

    可能还会有点理解不了,下面就结合实例分析一下:

    在程序的D行,类D的构造函数尽管调用了其基类B和基类C的构造函数,但由于虚基类A在D中只有一个拷贝,所以编译器也无法确定应该由B类的构造函数还是C类的构造函数来调用类A的构造函数。在这种情况下,编译器约定,在执行类B和类C的构造函数时都不调用虚基类A的构造函数,而是在类D的构造函数中直接调用虚基类A的构造函数。但由于类D的构造函数中并没有调用,所以就调用虚基类A的缺省构造函数(也就是A行)。所以x的初值为0。

    若将A类改成:

    class A {
    	public:
    		int x;
    		A(int a){
    			x = a;
    		}
    };

    重新编译时,会出现错误。原因是:D行未给出虚基类A的构造函数,基类A有没有缺省的构造函数。

    若将D类改成:

    class D :public B, public C {
    	public:
    		int m;
    		D(int a, int b, int d, int e, int f) :B(a, b), C(d, e), A(1000) {
    			m = f;
    		}
    		void Print() {
    			PB();
    			PC();
    			cout << m << endl;
    		}
    };

    那么,此时就不会调用基类A的缺省构造函数,而是直接调用基类A的构造函数来进行1000的赋值。

    最后再次强调:用虚基类进行多重派生时,若虚基类没有缺省的构造函数,则在派生的每一个派生类的构造函数的初始化成员列表中,必须有对虚基类构造函数的调用!

     

    展开全文
  • C++类继承

    千次阅读 2018-10-21 22:18:34
    文章目录C++类继承1.is-a继承1.1公有继承1.2私有继承1.3保护继承2.has-a关系3.多态公有继承3.1特性:3.2虚函数实现原理4.纯虚函数和抽象基类5.多继承、多重继承和虚拟继承6.模板 1.is-a继承 1.1公有继承 特性: //...

    C++类继承

    1.is-a继承

    1.1公有继承

    特性:

    //保持基类的访问权限不变,基类数据成员只能通过基类的方法访问
    
    //派生类需要自己的构造函数,创建对象时,需要使用成员初始化列表,将基类成员一起初始化
    
    //派生类可以重载基类成员函数,也可以添加额外的方法和数据成员
    
    //创建派生类对象时,基类对象将被先行创建,即先调用基类的构造函数
    
    //如果不调用基类构造函数,直接创建派生类对象时,将使用默认的基类构造函数
    
    //实例
    class Person{
    private:
        char m_name[10];
        char m_sex[10];
    public:
        Person(const char *name="yang",const char *sex="male"){
    		strcpy(m_name,name);
            strcpy(m_sex,sex);
            cout<<"我是基类构造函数,我被优先调用了\n";
        }
        void show() const{
    		cout<<m_name;
    		cout<<"\n";
    		cout<<m_sex;
            cout<<"\n";
    	}
    };
    class Student:public Person{
    private:
        char m_xuehao[10];
    public:
        Student(const char *name="xu",const char *sex="male",const char *xuehao="1510370221")
            :Person(name,sex){//使用成员初始化列表,如果省略,则默认调用Person()构造函数
    			strcpy(m_xuehao,xuehao);
            }
        void display() const{
            show();
            cout<<"\n";
            cout<<m_xuehao;
        }
    };  //注意赋值一个常量字符串给一个char *时,必须加上const,由于字符串为一个常量,不能被修改,会发出警告!!!!
    int main(){
    	Person p;
    	p.show();
        
        Student s;
        s.display();
    }//打印:  
    	/*  我是基类构造函数,我被优先调用了
    		yang
        	male
        	我是基类构造函数,我被优先调用了
    		xu
    		male
    		1510370221
    		*/
    
    //此外如果构造函数new一块内存,还需要重定义新的复制构造函数
    
    //派生类指针不能指向和引用基类,即不能讲基类放大; 但是可以使用基类指针指向和引用派生类,可以放窄
    Person p;
    Student *s=p;//error
    
    Student s;
    Person *p=s;//允许
    
    //派生类对象可以赋给基类对象,不能反转 
    Student s;
    Person p=s;//语义:Student is a person!but,person not a student.
    

    1.2私有继承

    用的少,不是重点…

    1.3保护继承

    主要用于继承中派生类可以直接访问基类的protected成员

    2.has-a关系

    //方法一:在类使用其他类对象
    class Student{
    private:
        string m_name;//使用string类对象
    public:
        Student(const string name){ ... }
        
    }
    
    //方法二:使用私有继承
    class Student:private string{//私有继承string对象
    private:
        char m_name[10];
        
    public:
        Student(const char* name="yang")
            :string(name){ ... }
    }
    

    3.多态公有继承

    3.1特性:

    • 能够在派生类中重定义基类的方法,注意不是重载,是重定义,原型不一定相同,函数体不同,将隐藏基类方法;;;重载是原型不同,函数体必须相同;;;如果基类虚函数被重载,则需在派生类重定义所有版本

    • 基类使用虚方法,在基类和派生类中不同实现

    • 编译器对非虚方法使用静态联编,是哪个数据类型调用哪个方法;使用虚方法,将动态联编,根据指针和引用对象来调用相应的方法

    • 静态联编:执行效率更高,如果类不用做基类,就不使用动态联编!!只有预期哪些将被重定义的方法声明为虚的!

    //实例
    class Person{
    private:
        char m_name[10];
        char m_sex[10];
    public:
        Person(const char *name="yang",const char *sex="male"){
    		strcpy(m_name,name);
            strcpy(m_sex,sex);
            cout<<"我是基类构造函数,我被优先调用了\n";
        }
        virtual ~Person(){}//基类使用虚析构方法,保证释放派生对象时,按照正确的顺序调用析构函数
        
       	virtual void show() const{//基类使用虚方法,方便程序根据对象就能调用相应的方法
    		cout<<m_name;
    		cout<<"\n";
    		cout<<m_sex;
            cout<<"\n";
    	}
    };
    class Student:public Person{
    private:
        char m_xuehao[10];
    public:
        Student(const char *name="xu",const char *sex="male",const char *xuehao="1510370221")
            :Person(name,sex){
    			strcpy(m_xuehao,xuehao);
            }
       virtual void display() const{//派生类加上virtual,方便识别,可以省略,最好加上
            Person::show();//显式调用基类的同名方法,作用解析符不能省略,否则将无限递归
            cout<<"\n";
            cout<<m_xuehao;
        }
    }; 
    
    int main(){
    	Student s;
        Person *p=&s;
        p->show();//由于show声明为virtual,采用动态联编根据对象调用Student::show()
    }
    

    3.2虚函数实现原理

    //使用一个指向虚函数表的指针vtbl
    
    //派生类与基类共用一个指向虚函数表的指针,如果虚函数没有被重新定义,则此虚函数地
    址不变,如    
    果改变或者添加了新的虚函数,将保存为一个新的地址!!
    
    //无论虚函数有多少个,类与实例对象都只保存一个vtbl,只是指向的虚函数表的大小不同而已!!
    
    //可见使用虚函数,将增加以下时间和空间成本
    	/*
    		1.每个对象额外增加一个vtbl内存
    		2.每个类将创建一个虚拟地址表数组
    		3.每次函数调用,需要查找地址
    	*/
    //注意几点:构造函数不能为虚,析构函数必须为虚!否则只会释放基类的new部分内存,造成内存泄漏;
    友元函数不能为虚!
    Person *p=new Student;
    ...
    delete p;//假设Student对象new一个内存,此时只会调用Person的析构函数,
    但是Student内存没有释放,而使用虚析构函数,则直接调用Student的析构函数,从而释放全部的内存!!    
    

    注:以上代码为了方便,直接定义与实现一起写了,这样会导致方法均是内联函数,非常浪费内存,所以还是将方法实现放在定义之外!!!

    4.纯虚函数和抽象基类

    //纯虚函数指在基类中没有实现代码,专门为了派生类继承重定义使用!!
    virtual void show() const=0;//纯虚函数
    
    //含有纯虚函数的类都可以叫做抽象类,专门用来被继承,重定义实现的,不能被创建实例!
    
    //好处:方便将多个派生类使用基类指针数组进行统一管理
    

    5.多继承、多重继承和虚拟继承

    //多继承,比如班长多继承Leader,Student,
    class Monitor:public Leader,public Student{
        ...
    }
    
    //多重继承,由于Leader,Student又继承与Person,导致Monitor存在Person的两份拷贝,非常浪费内存,完全没必要
    class Monitor:public Leader,public Student{
        ...
    }
    
    //使用虚拟继承
    class Leader:virtual public Person{
        ...
    }
    class Student:virtual public Person{
        ...
    }
    class Monitor:public Leader,public Student{
        ...
    }//使用虚拟继承从而解决了拷贝多基类的问题,只会保留一份拷贝
    

    6.模板类

    +补坑…

    展开全文
  • C++继承

    千次阅读 多人点赞 2020-06-06 19:45:20
    C++继承 1.继承的概念及定义 1.1继承的概念 继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有特性的基础上进行扩展,增加功能,这样产生新的,称派生。继承呈现了面向对象...

    C++继承

    1.继承的概念及定义

    1.1继承的概念

    继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程,以前我们接触的复用都是函数复用,继承是类设计层次的复用。

    class Person
    {
    public:
    	void Print()
    	{
    		cout << _name << endl;
    		cout << _age << endl;
    	}
    protected:
    	string _name = "jack";
    	int _age = 20;
    };
    
    
    class Student : public Person
    {
    protected:
    	int _stuid;
    };
    
    int main()
    {
    	Student s;
    	s.Print();
    	return 0;
    }

    继承后父类的Person的成员都会变成子类的一部分,这里体现出了Student复用了Person的成员,调用Print可以看到成员函数的复用。

    1.2继承定义
    1.2.1定义格式
    //     派生类   继承方式  基类
    class Student : public Person
    {
    protected:
    	int _stuid;
    };
    1.2.2继承关系的访问限定符

    继承方式:public继承、protected继承、private继承。

    访问限定符:public访问、protected访问、private访问。

    1.2.3继承基类成员访问方式的变化

    在这里插入图片描述

    1.2.4总结

    a.基类private成员在派生类中无论以什么方式继承都是不可见的,这里的不可见是指基类的私有成员还是被继承到了派生类的对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。

    b.基类private成员在派生类中是不能被访问,如果基类成员不行在类外直接被访问,但需要在派生类中能访问,就定义为protected,可以看出保护成员限定符因继承才出现的。

    c.实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见的。基类的其他成员在子类的访问方式==Min(成员在基类的访问限定符,继承方式)。

    d.使用关键字class是默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

    e.在实际运用中一般使用都是public继承,几乎很少使用protected/private继承,也不提倡使用protected/private继承,因为protected/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

    2.基类和派生类对象赋值转换

    派生类对象可以赋值给基类的对象/基类的指针/基类的拷贝,这里有个形象的说法叫做切片或者切割,意寓把派生类中父类那部分切出来赋值过去。

    基类对象不能给派生类对象。

    基类的指针可以通过强制类型转换赋值给派生类的指针,但是必须是基类的指针是指向派生类对象时才是安全的。

    int main()
    {
    	Student s;
    	Person p1 = s;
    	Person* p2 = &s;
    	Person& p3 = s;
    
        	Student* s1 = (Student*)p2;
    	return 0;
    }

    3.继承中的作用域

    a.在继承体系中基类和派生类都有独立的作用域。

    b.子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。在子类成员函数中,可以使用基类::基类成员显示访问。

    c.需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

    d.注意在实际中在继承体系里面最好不要定义同名的成员。

    class Person
    {
    protected:
    	string _name = "jack";
    	int _age = 20;
    };
    
    
    class Student : public Person
    {
    public:
    	void Print()
    	{
    		cout << _name << endl;
    		cout << _age << endl;
    	}
    protected:
    	int _age = 21;
    };
    
    int main()
    {
    	Student s;
    	s.Print();
    	return 0;
    }

    Student和Person中的_age构成隐藏关系,这样代码虽然可以运行,但是非常容易混淆。

    class A
    {
    public:
    	void func()
    	{
    		cout << "func()" << endl;
    	}
    };
    class B : public A
    {
    public:
    	void func(int i)
    	{
    		cout << "func(int)" << endl;
    	}
    };
    
    //B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
    int main()
    {
    	B b;
    	b.func(1);
    	return 0;
    }

    4.派生类的默认成员函数

    a.派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员,如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。

    b.派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。

    c.派生类的operator=必须要调用基类的operator=完成基类的复制。

    d.派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员,因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。

    e.派生类对象初始化先调用基类构造再调派生类构造。

    f.派生类对象析构清理先调用派生类析构再调用基类析构。

    class Person
    {
    public:
    	Person(const char* name = "jack")
    		:_name(name)
    	{}
    	Person(const Person& p)
    		:_name(p._name)
    	{}
    	Person& operator=(const Person& p)
    	{
    		if (this != &p)
    		{
    			_name = p._name;
    		}
    		return *this;
    	}
    	~Person()
    	{
    		_name = "";
    	}
    protected:
    	string _name;
    };
    
    class Student : public Person
    {
    public:
    	Student(const char* name = "jack", int id = 111)
    		:Person(name)
    		, _id(id)
    	{}
    	Student(const Student& s)
    		:Person(s)
    		, _id(s._id)
    	{}
    	Student& operator=(const Student& s)
    	{
    		if (this != &s)
    		{
    			Person::operator=(s);
    			_id = s._id;
    		}
    		return *this;
    	}
    	~Student()
    	{
    		_id = 0;
    	}
    protected:
    	int _id;
    };

    5.继承与友元

    友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。

    class Person
    {
    public:
    	friend void func();
    protected:
    	string _name;
    };
    
    class Student : public Person
    {
    public:
    	friend void func();
    protected:
    	int _id;
    };
    
    void func()
    {
    	cout << "func()" << endl;
    }

    6.继承与静态成员

    基类定义的static静态成员,则整个继承体系里面只有一个这样的成员,无论派生出多少个子类,都只有一个static成员实例。

    class Person
    {
    public:
    	static int _num;
    protected:
    	string _name;
    };
    int Person::_num = 0;
    
    class Student : public Person
    {
    protected:
    	int _id;
    };
    
    int main()
    {
    	Student s;
    	Student::_num = 10;
    	cout << Person::_num << endl;
    }

    7.复杂的菱形继承及菱形虚拟继承

    单继承:一个子类只有一个直接父类时称这个继承关系为单继承。
    在这里插入图片描述

    多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
    在这里插入图片描述

    菱形继承:菱形继承是多继承的一种特殊情况。
    在这里插入图片描述

    菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在D的对象中A成员会有两份。

    class A
    {
    protected:
    	int _a;
    };
    
    class B : public A
    {
    protected:
    	int _b;
    };
    
    class C : public A
    {
    protected:
    	int _c;
    };
    
    class D : public B, public C
    {
    protected:
    	int _D;
    };
    
    
    int main()
    {
    	D d;
        //这样会有二义性无法明确知道访问的是哪一个
    	//d._a = 10;
    
        //需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
    	d.B::_a = 10;
    	d.C::_a = 20;
    
    	return 0;
    }

    虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在B和C的继承A时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。

    class A
    {
    public:
    	int _a;
    };
    
    class B : virtual public A
    {
    public:
    	int _b;
    };
    
    class C : virtual public A
    {
    public:
    	int _c;
    };
    
    class D : public B, public C
    {
    public:
    	int _d;
    };
    
    int main()
    {
    	D d;
    	d._a = 10;
    	return 0;
    }

    它的原理是,在D对象中将A放到的了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。

    8.继承的总结和反思

    1.很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。多继承可以认为是C++的缺陷之一。

    2.继承和组合

    //继承
    //public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。白箱复用。
    class A
    {
    public:
    	int _a;
    };
    
    class B : public A
    {
    public:
    	int _b;
    };
    
    //组合
    //组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。黑箱复用。
    class C
    {
    public:
    	int _c;
    };
    
    class D
    {
    public:
    	int _d;
    	C _c;
    };

    实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。如果类之间的关系既可以用继承还可以用组合,就用组合。

    展开全文
  • C++判断一个类是否继承自另一个类

    千次阅读 2020-09-03 16:14:33
    派生继承基类的接口,所以我们经常会把一基类派生出的多派生当作同一类别。在某些函数或者中,我们可能希望只支持某一类别的输入,即只支持某个类型或者这类型的派生,此时就需要判断输入类型是否...

    前言

    派生类会继承基类的接口,所以我们经常会把一个基类派生出的多个派生类当作同一个类别。在某些函数或者类中,我们可能希望只支持某一类别的输入,即只支持某个类型或者这个类型的派生类,此时就需要判断输入类型是否继承自目标类型。

    考虑这样一种情形:线条(Line)由一系列点构成,而“点”可以有多种类型:圆点(Dot)、星点(Star)…,另外还有一种类型“平面”(Plane)则不是一种“点”,它们定义如下:

    class Point { };
    class Dot : public Point { };
    class Star : public Point { };
    class Plane { };
    
    template <typename Pt>
    class Line { };
    

    显然,线条可以由圆点或星点构成,但不能由平面构成,我们希望 Line 的模板参数 Pt 是一种 Point,如何在代码中进行判断呢?

    std::is_base_of

    C++11 提供了std::is_base_of(头文件<type_traits>)用于判断两个类型的继承关系,参考cppreference,使用方法为:

    std::is_base_of<A, B>::value; // get true if B is derived from A
    

    所以上述问题中,Line可以这么定义:

    template <typename Pt>
    class Line {
        static_assert(std::is_base_of<Point, Pt>::value,
            "template params MUST be Point!");
    };
    

    如果用户使用了错误的类型作为模板参数,比如 Line<Plane> line;,则会在编译时报错。

    基类带模板的情况

    点的坐标通常可以是多种类型的(int、float、double),下面我们给Point加上模板参数Scalar来支持不同类型数据:

    template <typename Scalar>
    class Point { };
    template <typename Scalar>
    class Dot : public Point<Scalar> { };
    template <typename Scalar>
    class Star : public Point<Scalar> { };
    template <typename Scalar>
    class Plane { };
    

    这时候你会发现std::is_base_of就不那么好用了,它会要求你提供PointPt的模板参数(即各自的Scalar),而且它会认为Scalar不同的两种 Point 没有继承关系!这往往并不是我们所希望的:

    std::is_base_of<Point<float>, Dot<float>>::value; // true
    std::is_base_of<Point<float>, Dot<double>>::value; // false
    


    针对这种情况,我在stackoverflow@Jarod42的回答中找到了如下方法:

    // case1: T* can cast to C*
    template <template <typename...> class C, typename...Ts>
    std::true_type is_base_of_template_impl(const C<Ts...>*); 
    // case2: T* cannot cast to C*
    template <template <typename...> class C>
    std::false_type is_base_of_template_impl(...);
    
    template <template <typename...> class C, typename T>
    using is_base_of_template = decltype(is_base_of_template_impl<C>(std::declval<T*>()));
    

    其中std::declval<T*>()是在不构造T*指针的情况下获得一个T*指针的引用类型,仅用于类型推断。这个方法利用的是派生类指针能够隐式转换为基类指针,基类C的模板参数则被参数包Ts...捕获,因此支持任意数量的模板参数,而其缺陷是仅适用于 public 继承(protected 或者 private 继承的类指针无法转化为基类指针)。使用方法与std::is_base_of类似:

    is_base_of_template<Point, Dot<float>>::value; // true
    is_base_of_template<Point, Dot<double>>::value; // true
    is_base_of_template<Point, Plane<double>>::value; // false
    


    stackoverflow同一问题下@jenka 的回答提供了适用于 protected 继承的解决方法(其代码好像有点问题,@Anton Dyachenko的补充后才正常)。这个方法能用,但原理我目前还没看明白,先贴出代码吧:

    template <template <typename...> class BaseTemplate, typename Derived, typename TCheck = void>
    struct test_base_template;
    
    template <template <typename...> class BaseTemplate, typename Derived>
    using is_base_of_template = typename test_base_template<BaseTemplate, Derived>::is_base;
    
    //Derive - is a class. Let inherit from Derive, so it can cast to its protected parents
    template <template <typename...> class BaseTemplate, typename Derived>
    struct test_base_template<BaseTemplate, Derived, std::enable_if_t<std::is_class_v<Derived>>> : Derived {
        template <typename... T>
        static constexpr std::true_type test(BaseTemplate<T...>*);
        static constexpr std::false_type test(...);
        using is_base = decltype(test((Derived*)nullptr));
    };
    
    //Derive - is not a class, so it is always false_type
    template <template <typename...> class BaseTemplate, typename Derived>
    struct test_base_template<BaseTemplate, Derived, std::enable_if_t<!std::is_class_v<Derived>>> {
        using is_base = std::false_type;
    };
    
    展开全文
  • C++ 继承类练习

    千次阅读 2018-11-01 17:10:40
    定义一基类Account,数据成员包含string变量userName用于保存账户主人姓名,函数成员包括默认构造函数、带参构造函数用于初始化数据成员和输出姓名的成员函PrintName()。从Account派生出CreditAccount,增加...
  • #include <iostream> using namespace std;...//只需要在声明继承的多个类之间加上逗号来分隔 class B1 { public: void output(); }; class B2 { public: void output(); }; void B1::output() { ...
  • C++继承和重载

    千次阅读 多人点赞 2019-04-12 19:49:37
    C++继承和重载 c++集成 继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行时间的效果。 当创建一个类时,您不需要重新编写新的数据成员和...
  • C++的多继承

    万次阅读 多人点赞 2015-07-19 23:07:43
    在写这一主题的文章之前,在网上找到一篇很非常好的文章...类C会同时拥有类A和类B的特性(属性和方法,也就是两个类的所有成员)。这种方式很简单这里就不多说,主要讲下面这种方式。2. 复杂版本 同样的,这个结构中
  • java中一个类不能直接继承两个类比如说这样: class A extends B,C 不能这样写,因为java不支持多继承, 但是你可以像下面这样实现继承多个类 class A extends B class C extends A 这样C就同时继承了B和A两个类在...
  • C++继承详解

    千次阅读 多人点赞 2020-05-08 00:55:09
    继承方式限定了基类成员在派生中的访问权限,包括 public(公有的)、private(私有的)和 protected(受保护的)。此项是可选项,如果不写,默认为 private(成员变量和成员函数默认也是 private)。 现在我们...
  • c++类的不同继承方式

    千次阅读 2017-12-23 15:53:50
    c++类对一个继承而来的成员的访问权限受两个因素影响:   (1) 该成员在基类中的访问权限说明符   (2) 派生继承方式  需要注意,上述是站在一个派生的使用者(包括继承该派生的新派生)而言的。所站...
  • C++ 继承(2): 多重继承, 多继承, 虚继承(virtual)

    千次阅读 多人点赞 2019-03-20 22:59:49
    C++远征之继承篇 视频教程 笔记 方便自己查阅和复习,温故而知新。 接着C++ 继承(1): 继承方式(public, protected, private), 继承中的特殊关系(隐藏 , is-a) 继续做笔记 目录 4 多继承与多重继承 4.1 多重...
  • C++ 继承 (inheritance)

    千次阅读 多人点赞 2021-05-10 13:00:04
    C++ 继承. 继承 & 的概念. 继承的重要性以及如何实现.
  • C++继承之菱形继承

    千次阅读 2020-02-20 15:46:42
    在学习菱形继承之前,我们先了解两个概念:单继承、多继承。 单继承 一个子类只有一个直接父类时称这个继承关系为单继承。 多继承 一个子类有两个或以上直接父类时称这个继承关系为多继承。 菱形继承 菱形继承是多...
  • C++继承

    千次阅读 2018-06-27 10:08:14
    C++继承 1 公有继承 2 私有继承 3 保护继承 ...一从已有那里获得其已有特性,这种现象称为继承。新叫做派生或子类,原来的已有叫做基类或父类。值得注意的是派生和基类永远是相...
  • #设计一个圆类Circle和一个桌子类Table,另设计一个圆桌类Roundtable,它是前面两个类的派生类,要求编写测试程序给出输出一个圆桌的高度、面积和颜色等数据。 #include<iostream> #include<cstring> #...
  • 在面向对象技术下中成员的可见性以及访问控制程度总是让程序员发懵。特别是初学者。下面将对其做一总结。 1.横向来看,比较简单。中成员的访问控制程度有三种:public,private,proteced。 public:完全向外部...
  • C++类继承关系

    万次阅读 2009-02-15 03:17:00
    C++类继承关系问题 在C++继承主要有三种关系:public、protected和private。这三种继承关系中public继承是最为常用的一种继承关系,代表了接口继承含义,而他们分别具体代表了什么含义呢? 1. public 从语义...
  • java中一个类不能直接继承两个类比如说这样: class A extends B,C 不能这样写,因为java不支持多继承, 但是你可以像下面这样实现继承多个类 class A extends B class C extends A 这样C就同时继承了B和A两个类
  • 一般函数同名:当某个函数func()在基类和派生中都有定义时,派生中的函数func()将修改从基类继承来的函数func(),如果非要从派生中访问基类的函数func(),有种方法:一、定义基类指针,让基类指针...
  • C++继承模型

    千次阅读 2014-05-24 08:55:25
    C++继承模型中,一派生对象表现出来的东西,是其自己的成员加上其基类成员的总和。但这些成员如何摆放,标准并未强制规定。一般而言,先摆放基类成员,内存向高地址增长。 下面从四部分讨论C++继承模型: ...
  • C++继承和派生笔记

    千次阅读 2019-10-24 12:52:32
    基类成员继承给派生后,其访问权限不变,但基类成员的私有成员在派生中不可以直接访问 私有继承 基类中的所有成员都以私有成员身份出现在派生中,基类的私有成员在派生中不可直接访问 保护成员和保护继承 从...
  • C/C++类继承

    万次阅读 多人点赞 2019-01-24 17:15:26
    继承 class Shape{ }; class Circle : public Shape{ }; 语法:class B : public A{} B继承于A,A是父类,B是派生(子类) 当 B继承于A时,自动将父类中的所有public 成员继承,无法继承private的 ...
  • 理解C++类继承

    千次阅读 2014-12-05 13:08:23
    本文讲述的是面向对象语言中类的继承的理解,文中以C++语言作为例子。...C++中允许单继承,也允许多继承(一个类可以有多个基类) 类继承用一个冒号来表示,注意不要与作用域运算符的两个冒号相混淆。
  • c++派生继承

    万次阅读 多人点赞 2017-12-23 20:59:07
    基类的构造函数和析构函数不能被继承,在派生中,如果对派生新增的成员进行初始化,需要加入派生的构造函数 当派生创建对象时,首先执行基类的构造函数,随后执行派生的构造函数;当撤销派生对象时,先...
  • C++什么不能被继承

    千次阅读 2020-08-17 21:55:52
    C++什么不能被继承 什么不能被继承,首先思考派生...这种方式都能阻止派生继承(因为自身无法构造函数),第三种,使用C++新特性final。 一、将自身的构造函数与析构函数放在private作用域 当我们声明一
  • C++两个类的头文件互相包含问题

    千次阅读 2014-08-13 21:12:54
    抽象概括下,就是A类定义在A.h中,A类中有个B*成员;B类定义在B.h,B类同样有A*成员 ...的话,应该是不行的,经vc6一个程序验证,貌似会出现,类视图里找不到某一个类    这里如果在A.c
  • C++继承和包含

    千次阅读 2016-03-20 21:08:58
    在面向对象中,有两个很重要的东西,继承和包含。更多的时候,我们不会去分析两种方式究竟哪种更好,能够达到自己想要的目的即可。但是存在即道理,还是要仔细分析一下。继承:Class Animal{}; Class Cat : public ...
  • c++继承总结

    千次阅读 2012-04-28 17:00:33
    public继承,是一种D是B的一种,但B不一定都是D。 在这种继承关系下,通常意义上的理解,会在程序中出现乱流。例如,企鹅是一种鸟,又鸟派生出的企鹅,却不能飞。 因此,在鸟中派生出会飞的鸟,再派生出企鹅会更好...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 235,906
精华内容 94,362
关键字:

c++继承两个类

c++ 订阅