精华内容
下载资源
问答
  • c++继承
    2021-10-11 17:17:04

    c++ 继承构造函数

    class TestA {
    public:
        TestA(string i): a(i) {}
        ~TestA() {}
        string geta() {
            return a;
        }
        virtual string getb() = 0;
    private:
        string a;
    };
    
    class TestB: public TestA{
    public:
        TestB(string i):TestA(i),b(i) {}
        ~TestB() {}
        virtual string getb() override {
            return b;
        }
    private:
        string b;
    };
    
    int main() {   
        string i("haha");
        TestA* testb = new TestB(i);
    
        cout << "A a:" << testb->geta() << endl;
        cout << "B b:" << testb->getb() << endl;
        return 0;
    }
    
    

    如上代码,TestB是TestA的派生类,TestB这里需要使用基类的构造函数来初始化基类的成员变量。相当于是构造函数的传递,所以在TestB中的构造函数里显示声明了TestA。
    这样的话我们初始化TestB的成员变量时又同时初始化了TestA的成员变量。从这个意义上讲,这样的构造函数设计也是非常合理的。

    但是,有的时候基类TestA可能有好多个构造函数。如果TestA有大量的构造函数,TestB只有一些成员函数,对于派生类而言,其构造等同于构造基类,这个时候我们就要写很多透传的构造函数。

    比如:

    class TestA {
    public:
        TestA(string i): a1(i) {}
        TestA(int i) : a2(i) {}
        TestA(double i) : a3(i) {}
        ~TestA() {}
        string geta1() {
            return a1;
        }
        virtual string getb1() = 0;
    private:
        string a1;
        int a2;
        double a3;
    };
    
    class TestB: public TestA{
    public:
        TestB(string i):TestA(i),b1(i) {}
        TestB(int i) :TestA(i), b2(i) {}
        TestB(double i) :TestA(i), b3(i) {}
        ~TestB() {}
        virtual string getb1() override {
            return b1;
        }
        virtual void testb();
    private:
        string b1;
        int b2;
        double b3;
    };
    

    这里没有在一个构造里初始化全部值只是举个例子,可以看到这里TestB继承TestA实际上只是添加了一个接口testb,如果在构造TestB的时候想要拥有TestA的所有构造方法的话,就必须一个个写,肯定不是很方便。

    c++11里提供一个规则,派生类可以通过使用using声明来声明继承基类的构造函数。这样的话就可以如下代码来代替上面的代码:

    class TestA {
    public:
        TestA(string i): a1(i) {}
        TestA(int i) : a2(i) {}
        TestA(double i) : a3(i) {}
        ~TestA() {}
        string geta1() {
            return a1;
        }
        virtual string getb1() = 0;
    private:
        string a1;
        int a2;
        double a3;
    };
    
    class TestB: public TestA{
    public:
        // 继承构造函数
    	using TestA::TestA; 
    	// ....
        virtual void testb();
    private:
        string b1;
        int b2;
        double b3;
    };
    

    这里通过using TestA::TestA的声明,把基类中的构造函数全部继承到派生类B中。
    但是继承构造函数 只会初始化基类中的成员变量,对于派生类的成员变量,不会操作。
    可以通过使用初始化表达式来解决这个问题,例如:

    class TestB: public TestA{
    public:
        // 继承构造函数
    	using TestA::TestA; 
    	// ....
        virtual void testb();
    private:
        string b1{"b1"};
        int b2{0};
        ...
    };
    

    这里另外有一点需要注意的事,如果基类构造函数参数有默认值,默认值会导致基类产生多个构造函数的版本。这样都会被派生类继承。所以在使用有参数默认值的构造函数的基类,必须小心。

    另外私有的构造函数,不会被继承构造。

    更多相关内容
  • 08_31_C++ 继承的二义性

    2022-07-01 09:24:07
    08_31_C++ 继承的二义性08_31_C++ 继承的二义性08_31_C++ 继承的二义性08_31_C++ 继承的二义性08_31_C++ 继承的二义性08_31_C++ 继承的二义性08_31_C++ 继承的二义性08_31_C++ 继承的二义性08_31_C++ 继承的二义性08...
  • c++继承描述

    2019-01-05 16:24:58
    c++继承描述
  • C++继承和派生

    2019-01-17 16:15:55
    信息科学技术学院《程序设计实习》,C++程序员入门级教程
  • c++继承分类

    2016-04-28 14:55:09
    在Qt中编写的c++继承分类的视频
  • 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++继承(基本篇)

    千次阅读 多人点赞 2022-03-02 08:16:38
    1.继承的基本概念及其定义 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象...

    目录

    1.继承的基本概念及其定义

    1.1继承的基本概念

    1.2继承的定义

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

    3.继承中的作用域

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

    4.1基类派生类构造函数析构函数的调用顺序

    5.继承与友元

    6.继承中的静态成员变量

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

     8.继承的总结和反思


    1.继承的基本概念及其定义

    1.1继承的基本概念

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

    #include<iostream>
    using namespace std;
    class Person
    {
    public:
    	void Print()
    	{
    		cout << "name:" << _name << endl;
    		cout << "age:" << _age << endl;
    	}
    protected:
    	string _name = "peter"; // 姓名
    	int _age = 18; // 年龄
    };
    // 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了Student和
    //Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可以看到变量的复用。
    //调用Print可以看到成员函数的复用。
    class Student : public Person
    {
    protected:
    	
    		int _stuid; // 学号
    };
    class Teacher : public Person
    {
    protected:
    	int _jobid; // 工号
    };
    int main()
    {
    	Student s;
    	Teacher t;
    	s.Print();
    	t.Print();
    	return 0;
    }

    1.2继承的定义

    #include<iostream>
    #include<string>
    using namespace std;
    class Person {
    public:
    	Person(const string& name="Peter", int age=18)
    		:_name(name)
    		,_age(age)
    	{}
    	
    
    	string _name;
    	int _age;
    };
         子类 继承方式  父类
    class Men :public Person {
    public:
    	int id;
    };
    int main() {
    	return 0;
    }

    在这里Men被称为派生类,也叫做子类,Person叫做父类也叫做基类。在class Men:public Person中public表示的是继承方式 

    1.2.2继承的几种方式:

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

    总结:

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

    2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能
    访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
    3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类
    的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
    4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的
    写出继承方式。

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

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

    1.派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
    2.基类对象不能赋值给派生类对象
    3.基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 来进行识别后进行安全转换。

     下面我们来看一个例子:

    #include<iostream>
    #include<string>
    using namespace std;
    class Person {
    public:
    	Person(const string& name="Peter", int age=18,string sex="男")
    		:_name(name)
    		,_age(age)
            ,sex(sex)
    	{}
    	
    protected:
    	string _name;
    	int _age;
       string sex;
    
    };
    class Men :public Person {
    public:
    	int id;
    };
    int main() {
    	Men M;
    	return 0;
    }

    对应代码验证:

    #include<iostream>
    #include<string>
    using namespace std;
    class Person {
    public:
    	Person(const string& name="Peter", int age=18,string sex="男")
    		:_name(name)
    		,_age(age)
    		,sex(sex)
    	{}
    	
    protected:
    	string _name;
    	int _age;
    	string sex;
    };
    class Men :public Person {
    public:
    	int id;
    };
    int main() {
    	Men M;
    	//子类对象可以父类的指针
    	Person* p = &M;
    	//子类对象可以赋值给父类的引用
    	Person& t = M;
    	//子类对象可以赋值给父类对象,反过来不行
    	Person tmp = M;
    	// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
    	Person* parent = new Person("李四",25, "男");
    	Men* sub = (Men*)parent;
    	return 0;
    }

     3.继承中的作用域

    1. 在继承体系中基类和派生类都有独立的作用域
    2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定
    义。(在子类成员函数中,可以使用 基类::基类成员 显示访问
    3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏
    4. 注意在实际中在继承体系里面最好不要定义同名的成员。

    5.特别要注意的是如果父类中和子类中出现了同名函数这叫做隐藏或者重定义不是函数重载,函数重载的前提是要在同一作用域。

     示例1:

    #include<iostream>
    #include<string>
    using namespace std;
    // Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
    class Person
    {
    protected:
    	string _name = "小李子"; // 姓名
    	int _num = 111; // 身份证号
    };
    class Student : public Person
    {
    public:
    	void Print()
    	{//如果没有指明作用域会优先访问子类中的同名变量或者函数
    		cout << " 姓名:" << _name << endl;
    		cout << " 身份证号:" << Person::_num << endl;
    		cout << " 学号:" << _num << endl;
    	}
    protected:
    	int _num = 999; // 学号
    };
    void Test()
    {
    	Student s1;
    	s1.Print();
    };
    int main() {
    	Test();
    	return 0;
    }

    示例2:

    #include<iostream>
    #include<string>
    using namespace std;
    // B中的fun和A中的fun不是构成重载,因为不是在同一作用域
    // B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
    class A
    {
    public:
    	void fun()
    	{
    		cout << "func()" << endl;
    	}
    };
    class B : public A
    {
    public:
    	void fun(int i)
    	{
    		A::fun();
    		cout << "func(int i)->" << i << endl;
    	}
    };
    void Test()
    {
    	B b;
    	b.fun(10);
    };
    int main() {
    	Test();
    	return 0;
    }

     如果我们想要让父类中的同名函数在子类中可见。c++11中让父类中的同名函数在子类中可见通过using关键字,说白了就是让父类中的同名函数在子类中以重载的方式使用。

    示例:

    #include<iostream>
    #include<string>
    using namespace std;
    // B中的fun和A中的fun不是构成重载,因为不是在同一作用域
    // B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
    class A
    {
    public:
    	void fun()
    	{
    		cout << "func()" << endl;
    	}
    };
    class B : public A
    {
    public:
    	//c++11中让父类中的同名函数在子类中可见通过using关键字,
    	// 说白了就是让父类中的同名函数在子类中以重载的方式使用
    	using A::fun;
    	void fun(int i)
    	{
    		
    		cout << "func(int i)->" << i << endl;
    	}
    };
    void Test()
    {
    	B b;
    	b.fun();
    };
    int main() {
    	Test();
    	return 0;
    }

     总结:

    成员函数被重载的特征:

    1.在同一个作用域内。

    2.函数名字相同。

    3.参数不同。

    4.virtual可有可无

    覆盖是指派生类函数覆盖基类的同名函数

    1.作用域不同

    2.函数名字相同

    3.参数相同

    4.基类函数必须有virtual关键字

    隐藏是指派生类的函数屏蔽了基类中与之同名的函数,规则如下

    1.如果派生类的函数名和基类中的函数名相同但是参数个数不同,此时不管有无virutal,基类函数都会被隐藏

    2.如果派生类的函数名和基类中的函数名相同但是参数个数也相同,但是基类函数无virtual,此时基类函数也会被隐藏。

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

    6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个
    成员函数是如何生成的呢?
    1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函
    数,则必须在派生类构造函数的初始化列表阶段显示调用。

    2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
    3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
    4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类
    对象先清理派生类成员再清理基类成员的顺序
    5. 派生类对象初始化先调用基类构造再调派生类构造
    6. 派生类对象析构清理先调用派生类析构再调基类的析构。

    4.1基类派生类构造函数析构函数的调用顺序

    在继承中 如果子类继承了父类在定义子类对象时:

    1.先调用父类的构造函数在调用子类的构造函数。

    2.析构时则与之相反先调用子类的构造函数在调用父类的析构函数

    3.拷贝构造函数与operator=与构造函数类似

    4.如果基类中写了构造函数并且需要在基类中调用构造函数可以在子类中的初始化参数列表中显示调用构造函数

    例:

    
    #include<iostream>
    #include<string>
    using namespace std;
    
    class Person{
    	public:
    	Person(const char* name = "peter")
    		: _name(name)
    	{
    		cout << "Person()" << endl;
    	}
    	Person(const Person&p)
    		:_name(p._name)
    	{
    		cout << "Person(const Person& p)" << endl;
    		
    	}
    	Person& operator=(const Person& p)
    	{
    		cout << "Person operator=(const Person& p)" << endl;
    		if (this != &p)
    			_name = p._name;
    		return *this;
    	}
    	~Person()
    	{
    		cout << "~Person()" << endl;
    	}
    protected:
    	string _name; // 姓名
    };
    
    class Student : public Person
    {
    public:
    	Student(const char* name, int num)
    		: Person(name)
    		, _num(num)
    	{
    		cout << "Student()" << endl;
    	}
    	Student(const Student& s)
    		: Person(s)
    		, _num(s._num)
    	{
    		cout << "Student(const Student& s)" << endl;
    	}
    	Student& operator = (const Student& s)
    	{
    		cout << "Student& operator= (const Student& s)" << endl;
    		if (this != &s)
    		{
    			Person::operator =(s);
    			_num = s._num;
    		}
    		return *this;
    	}
    
    	~Student()
    	{
    		cout << "~Student()" << endl;
    	}
    protected:
    	int _num; //学号
    };
    void Test()
    {
    	Student s1("jack", 18);
    	Student s2(s1);
    	Student s3("rose", 17);
    	s1 = s3;
    }
    int main() {
    	Test();
    	return 0;
    }

    面试题:如何定义一个不可以被继承的类:

    答:将该类的构造函数或者析构函数设置为私有即可。 

    5.继承与友元

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

    示例:

    class Student;
    class Person
    {
    public:
    friend void Display(const Person& p, const Student& s);
    protected:
    string _name; // 姓名
    };
    class Student : public Person
    {
    protected:
    int _stuNum; // 学号
    };
    void Display(const Person& p, const Student& s)
    {
    cout << p._name << endl;
    cout << s._stuNum << endl;
    }
    int main()
    {
    Person p;
    Student s;
    Display(p, s);
    }

    在上面这个例子中Display这个函数是基类的友元函数但是他不是子类的友元函数,就像我们的爸爸有他自己的朋友但是爸爸的朋友不一定是我们的朋友。所以父类的友元函数不能访问子类的友元函数。

    6.继承中的静态成员变量

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

    示例:

    class Person
    {
    public :
    Person () {++ _count ;}
    protected :
    string _name ; // 姓名
    public :
    static int _count; // 统计人的个数。
    };
    int Person :: _count = 0;
    class Student : public Person
    {
    protected :
    int _stuNum ; // 学号
    };
    class Graduate : public Student
    {
    protected :
    string _seminarCourse ; // 研究科目
    };
    void TestPerson()
    {
    Student s1 ;
    Student s2 ;
    Student s3 ;
    Graduate s4 ;
    cout <<" 人数 :"<< Person ::_count << endl;
    Student ::_count = 0;
    cout <<" 人数 :"<< Person ::_count << endl;
    }

    运行结果:

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

    我们首先来看一下单继承:

    单继承:一个子类只有一个直接父类时称这个继承关系为单继承

     多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

    菱形继承:菱形继承是多继承的一种特殊情况

    菱形继承的问题:从上面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题.

    我们来看一个例子:

    #include<iostream>
    #include<string>
    using namespace std;
    
    class A {
    public:
    	int m_age;
    };
    class B :public A {
    };
    class C :public A {
    
    };
    class D :public B, public C {
    
    };
    void Test() {
    	D d;
    }
    int main() {
    	
    	return 0;
    }

     在上面这个代码中D继承了两份m_age分别来自他的基类B和C。

    此时如果我们使用d访问m_age则会出现二义性,如图所示:

    #include<iostream>
    #include<string>
    using namespace std;
    
    class A {
    public:
    	int m_age;
    };
    class B :public A {
    };
    class C :public A {
    
    };
    class D :public B, public C {
    
    };
    void Test() {
    	D d;
    	cout << d.m_age << endl;
    }
    int main() {
    	
    	return 0;
    }

     如果我们此时要访问需要指明作用域,指明m_age是那个基类的。那如何证明呢?下面博主介绍一种方式:

    首先我们点击项目属性:

     然后找到c/c++中的命令行

     找到命令行:

    在其他选项中输入 

    /d1 reportAllClassLayout 是查看所有类的布局

    /d1 reportSingleClassLayoutXX  其中"XX"是填入你想查看的类的名字

    3. 配置之后,保存。然后重新编译该项目,在[输出]中可看到下图

     我们可以看到DD类中继承了两份m_age,这完全是重复的,并且访问m_age时还需要指明作用域才可以访问。

    为了解决这个问题c++中引入了虚拟继承来解决菱形继承的二义性和数据冗余的问题。

     

    #include<iostream>
    #include<string>
    using namespace std;
    
    class Person
    {
    public:
    	string _name; // 姓名
    };
    class Student : virtual public Person
    {
    protected:
    	int _num; //学号
    };
    class Teacher : virtual public Person
    {
    protected:
    	int _id; // 职工编号
    };
    class Assistant : public Student, public Teacher
    {
    protected:
    	string _majorCourse; // 主修课程
    };
    void Test()
    {
    	Assistant a;
    	a._name = "peter";
    }
    int main() {
    	Test();
    	return 0;
    }

    注意:虚拟继承不要在其他地方去使用。

    下面我们来探究一下菱形继承中虚拟继承的原理:为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系。

    #include<iostream>
    #include<string>
    using namespace std;
    
    class A
    {
    public:
    	int _a;
    };
    // class B : public A
    class B : virtual public A
    {
    public:
    	int _b;
    };
    // class C : public A
    class C : virtual public A
    {
    public:
    	int _c;
    };
    class DD : public B, public C
    {
    public:
    	int _d;
    };
    int main()
    {
    	DD d;
    	d.B::_a = 1;
    	d.C::_a = 2;
    	d._b = 3;
    	d._c = 4;
    	d._d = 5;
    	return 0;
    }

    同样的我们按照上面的操作在输出列表中查看对象模型

    下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下面,这个A
    同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向的一张表。这两个指
    针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。

      

     

     8.继承的总结和反思

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

    3. 继承和组合

    1.public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
    组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
    优先使用对象组合,而不是类继承 。
    继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用
    (white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。
    继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关
    系很强,耦合度高。
    2.对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对
    象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),
    因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系,
    耦合度低。优先使用对象组合有助于你保持每个类被封装。
    实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适
    合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就
    用组合。

    #include<iostream>
    #include<string>
    using namespace std;
    // Car和BMW Car和Benz构成is-a的关系
    class Car {
    protected:
    	string _colour = "白色"; // 颜色
    	string _num = "陕ABIT00"; // 车牌号
    };
    class BMW : public Car {
    public:
    	void Drive() { cout << "好开-操控" << endl; }
    };
    class Benz : public Car {
    public:
    	void Drive() { cout << "好坐-舒适" << endl; }
    };
    // Tire和Car构成has-a的关系
    class Tire {
    protected:
    	string _brand = "Michelin"; // 品牌
    	size_t _size = 17; // 尺寸
    };
    class Car {
    protected:
    	string _colour = "白色"; // 颜色
    	string _num = "陕ABIT00"; // 车牌号
    	Tire _t; // 轮胎
    };
    int main()
    {
    	
    	return 0;
    }

    最后:如果觉得对您有帮助的话劳烦您动动您的小手在下方点个赞,或者发现错误请在评论区留言。

    展开全文
  • C++继承与派生

    千次阅读 2022-04-04 14:45:37
    1.继承与派生:保持已有类的特征构造新类的过程为继承,在已有类的基础上新增特性而产生新类的过程称为派生 2.继承目的:实现代码重用;派生目的:实现源程序的改造 3.声明:class 派生类:继承方式 基类 4.派生类:...

    一.类的继承与派生:
    1.继承与派生:保持已有类的特征构造新类的过程为继承,在已有类的基础上新增特性而产生新类的过程称为派生
    2.继承目的:实现代码重用;派生目的:实现源程序的改造
    3.声明:class 派生类:继承方式 基类
    4.派生类:
    吸收基类成员:派生类包含了基类中除了构造析构函数以外的所有成员,构造析构函数不会被继承
    改造基类成员:虚函数重写(实际是虚函数表的覆盖)以及同名函数
    添加新的成员:加入新的构造析构函数以及其他成员

    二.类成员的访问:public--->protected--->private注意基类中私有成员不能被访问
    基类中的私有成员派生类无法访问
    继承方式为public时权限不变,但是私有成员也会被继承?
    继承方式为private何protected(体现类的继承特征)时,权限与继承方式一致


    三.类型兼容规则:一个公有派生类的对象在使用上可以被当成基类的对象,反之不行
    1.派生类的对象可以赋值给基类对象
    2.派生类的对象可以初始化基类的引用
    3.指向基类的指针可以指向派生类
    通过基类的对象名与指针只能使用从基类继承的成员

    #include<iostream>
    #include <string>
    using namespace std;
    class Base
    {
    public:
    	void showBase()
    	{
    		cout << "base成员函数的调用" << endl;
    	}
    };
    class Son :public Base
    {
    public:
    	void showSon()
    	{
    		cout << "Son成员函数的调用" << endl;
    	}
    };
    int main()
    {
    	Son s1;
    	Base b1 = s1;//派生类对象赋值给基类对象
    	Base& b2 = s1;//派生类对象初始化基类的引用
    	Base* b3 = &s1;//派生类地址初始化基类指针/基类指针指向派生类
    	b1.showBase();//通过基类的对象名和指针只能调用基类成员
    	b2.showBase();
    	b3->showBase();
    	return 0;
    }
    //运行结果:
    //base成员函数的调用
    //base成员函数的调用
    //base成员函数的调用
    

     


    四.单继承与多继承
    1.单继承:一个基类一个派生类
    2.多继承:多基类一派生 class 派生类:继承方式1 基类1, 继承方式2 基类2
    3.多重派生:一个基类多个派生类
    4.多层派生:派生类为基类继续派生


    五.派生类的构造与析构

    1.基类的构造析构函数不会被继承,派生类需要写自己的构造析构函数
    2.派生类的构造函数需要给基类构造函数传递参数,注意基类成员用基类名在初始化列表进行初始化!!!

    #include<iostream>
    #include <string>
    using namespace std;
    class Base
    {
    public:
    	int m_A;
    	Base(int a):m_A(a){}//如果有默认构造,派生类构造时可以不传参
    	void showBase()
    	{
    		cout << "m_A=" << m_A << endl;
    	}
    };
    class Son :public Base
    {
    public:
    	int m_B;
    	Son(int a,int b):Base(a),m_B(b){}//注意基类用基类名在初始化列表进行初始化,多个基类类推
    	void showSon()
    	{
    		cout << "m_B="<<m_B << endl;
    	}
    };
    int main()
    {
    	Son s(10, 20);
    	s.showBase();
    	s.showSon();
    	return 0;
    }
    //运行结果:
    //m_A = 10
    //m_B = 20


    六.类成员的标识与访问

    1.当派生类与基类成员同名时,优先调用派生类成员,基类同名成员被屏蔽(包括重载)
    2.通过作用域来访问

    #include<iostream>
    #include <string>
    using namespace std;
    class Base
    {
    public:
    	int m_A;
    	Base(int a):m_A(a){}
    	void show()
    	{
    		cout << "基类m_A=" << m_A << endl;
    	}
    	void show(int a)
    	{
    		cout << "基类重载" << endl;
    	}
    };
    class Son :public Base
    {
    public:
    	int m_A;
    	Son(int a1,int a2):Base(a1),m_A(a2){}
    	void show()
    	{
    		cout << "派生类m_B="<<m_A << endl;
    	}
    };
    int main()
    {
    	Son s(10, 20);
    	s.show();//默认派生类,屏蔽了基类同名成员,包括重载
    	s.Base::show();//作用域访问基类
    	s.Base::show(10);//作用域访问基类重载类型
    	return 0;
    }
    //运行结果:
    //派生类m_B = 20
    //基类m_A = 10
    //基类重载
    

    3.菱形继承引发的二义性问题:B1,B2继承A,C继承B1和B2,解决方法:同名屏蔽或虚函数

    A类称为虚基类,在继承前加virtual,vbptr虚基类指针virtual base pointer指向虚基数表

    #include <iostream>
    using namespace std;
    class BaseA
    {
    public:
    	int m_A;
    	BaseA(int a):m_A(a){}
    };
    class BaseB1 :virtual public BaseA//虚继承
    {
    public:
    	int m_B1;
    	BaseB1(int a,int b1):BaseA(a),m_B1(b1){}
    };
    class BaseB2 :virtual public BaseA//虚继承
    {
    public:
    	int m_B2;
    	BaseB2(int a,int b2):BaseA(a),m_B2(b2){}
    };
    class SonC :public BaseB1, public BaseB2
    {
    public:
    	int m_C;
    	SonC(int a,int b1,int b2,int c):BaseA(a),BaseB1(a,b1),BaseB2(a,b2),m_C(c){}
    };
    int main()
    {
    	SonC c(30,20,20,30);//B1,B2也可以初始化虚基类,但是C直接初始化优先级更高,调用了一次
    	cout << c.m_A << endl;
    	return 0;
    }
    //运行结果:30
    /*
    class BaseB1    size(12):
    		+---
     0      | {vbptr}
     4      | m_B1
    		+---
    		+--- (virtual base BaseA)
     8      | m_A
    		+---
    
    BaseB1::$vbtable@:
     0      | 0
     1      | 8 (BaseB1d(BaseB1+0)BaseA)
    vbi:       class  offset o.vbptr  o.vbte fVtorDisp
    		   BaseA       8       0       4 0
    */
    
    /*
    class SonC      size(24):
    		+---
     0      | +--- (base class BaseB1)
     0      | | {vbptr}
     4      | | m_B1
    		| +---
     8      | +--- (base class BaseB2)
     8      | | {vbptr}
    12      | | m_B2
    		| +---
    16      | m_C
    		+---
    		+--- (virtual base BaseA)
    20      | m_A
    		+---
    
    SonC::$vbtable@BaseB1@:
     0      | 0
     1      | 20 (SonCd(BaseB1+0)BaseA)
    
    SonC::$vbtable@BaseB2@:
     0      | 0
     1      | 12 (SonCd(BaseB2+0)BaseA)
    vbi:       class  offset o.vbptr  o.vbte fVtorDisp
    		   BaseA      20       0       4 0
    */
    

    建立对象所指定的类称为最(远)派生类
    虚基类的成员由最派生类调用虚基类的构造函数进行初始化,在初始化列表调用构造函数初始化。

    虚函数与纯虚函数在多态进行总结

     

     

    展开全文
  • c++继承的视频

    2016-04-28 14:46:49
    在Qt中编写的c++继承的视频
  • C++ 继承 (inheritance)

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

    千次阅读 多人点赞 2021-05-09 16:34:16
    1.1、概念1.2、定义1.2.1、继承关系和访问限定符1.2.2、继承基类成员访问方式的变化注意:二、基类和派生类对象赋值转换三、继承中的作用域四、派生类的默认成员函数五、继承与友元六、继承与静态成员七、菱形继承八...
  • C++继承和重载

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

    千次阅读 多人点赞 2021-10-01 22:08:39
    菱形继承及菱形虚拟继承八.C++编译器如何通过虚继承解决数据冗余和二义性 一.继承的概念和定义 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行...
  • C++继承与多态性

    2017-04-25 15:51:39
    学习C++类的继承与多态详细全面易于理解的资料,从百度文库免费资源中获取。
  • C++继承(详细)

    千次阅读 2022-04-01 13:27:06
    了解什么是继承继承的作用,继承与友元、静态成员变量的关系。了解派生类的默认成员函数做什么,什么时候需要我们自己写。了解什么是菱形继承,什么是虚拟继承,以及虚拟继承的原理
  • 在本文中我们通过实例代码给大家讲解下C++继承和派生相关知识点,需要的朋友们学习下。
  • C++继承、成员访问权限
  • C++ 继承(2): 多重继承, 多继承, 虚继承(virtual)

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

    2014-12-29 10:28:49
    入门基础实例 有关继承和派生的应用小程序 希望对大家有帮助
  • C++继承子类和父类修改情况

    千次阅读 2022-03-20 20:09:09
    继承可看作指针指向相同的作用域,但实例化对象后,各自的某个成员变量不相干。 #include<iostream> using namespace std; class Base { public: Base(int i=2) { x = i; } int x=10; }; class C :...
  • C++继承中的同名成员变量处理方法

    千次阅读 2020-09-24 23:34:40
    2、子类依然从父类继承同名成员 3、在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员, 显式地使用类名限定符) 4、同名成员存储在内存中的不同位置 #include <iostream> using ...
  • C++继承与多态性实验报告
  • C++ 高级程序语言设计 面向对象的程序设计 面向对象编程 C++继承与多态性
  • C++ 继承、多态实验代码
  • C++运算符重载和继承

    2019-01-17 14:07:59
    C++编程思想的第十四章,代码,本人亲测通过后才上传的. 技术是不断锤炼出来的,每天进步一点点,最后终将成就你所有
  • C++继承课件

    2013-05-01 17:42:33
    基类,子类,继承机制讲解,内含实例代码,汽车跑车之间的继承关系
  • C++继承类的构造函数

    千次阅读 2019-02-28 16:28:45
    C++继承类的构造函数 ** 一.内部赋值的构造函数 二.用父类指针初始化子类对象的构造函数 class student { public: int num; int age; student() //默认构造函数 { num = 10086; age = 18; } //...
  • C++继承和多态(虚函数、纯虚函数、虚继承)

    千次阅读 多人点赞 2019-03-05 11:32:40
    C++继承和多态(虚函数、纯虚函数、虚继承) 一:继承 继承的概念:为了代码的复用,保留基类的原始结构,并添加派生类的新成员。 继承的本质:代码复用 我们用下图解释下: 那么我们这里就可以提出几...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 389,673
精华内容 155,869
关键字:

c++继承

c++ 订阅
友情链接: NAT-dranahission.zip