2.1、继承的基本概念和派生类的定义

在开篇我想讲讲继承的使用场景

如果当新类与现有类内容非常相似,只是多出若干变量与函数,这个时候我们往往会用到继承。当遇见多个类相似的时候我们同样建议这么做。

···

继承如同字面意思一般,就是一类从另一类获取其成员变量和函数。

派生和继承是同一个概念,只是站的角度不同。比如儿子继承父业,继承是对儿子来说,而派生是对父亲来说。

语法为:

class derived-class : access-specifier base-class

三个单词分别代表派生类,继承访问修饰符,基类。

接下来进入代码环节。

class Animal
{
public:
	string  name;
	int age;
public:
	Animal(){}
	Animal(string name, int age)
	{
		this->name = name;
		this->age = age;
	}
	void printInfo()
	{
		cout << "名字为" << this->name << "年龄为" << this->age << endl;
	}
};

class Dog : public Animal
{
public:
	Dog(){}
	Dog(string name, int age, char sex)
	{
		this->name = name;
		this->age = age;
		this->sex = sex;
	}
public:
	char sex;
};

int main()
{
	Dog D1("wangcai", 3, 'f');
	D1.printInfo();
	return 0;
}

不过我们可以发现继承是需要访问修饰符的,这就说明我们可以有三种继承方式

public继承

1、基类中所有public成员在派生类中为public属性

2、基类中所有protected成员在派生类中为protected属性

3、基类中所有private成员在派生类不能使用

protected继承

1、基类中所有public成员在派生类中为protected属性

2、基类中所有protected成员在派生类中为protected属性

3、基类中所有private成员在派生类不能使用

private继承

1、基类中所有public成员在派生类中为private属性

2、基类中所有protected成员在派生类中为private属性

3、基类中所有private成员在派生类不能使用

···

因此当我们想要修改私有的变量我们需要用到函数

class Person
{
public:
	string name;
protected:
	int age;
private:
	double height;
public:
	Person(){}
	
	Person(string name, int age, double height):name(name),age(age),height(height){}
    //此处构造函数的用法相当于平替下方this->xxx
	//Person(string name, int age, double height)
	//{
	//	this->name = name;
	//	this->age = age;
	//	this->height = height;
	//}
	//通过setname函数给name成员赋值,后面同理
	void setName(string name)
	{
		this->name = name;
	}
	void setAge(int age)
	{
		this->age = age;
	}
	void setHeight(double height)
	{
		this->height = height;
	}

	//打印信息的方法
	void printInfo()
	{
		cout << "名字为" << this->name << "年龄为" << this->age << "身高为" << this->height << endl;
	}
};

class Student : public Person{};

int main()
{
    	Student s1;
	s1.setName("xm");
	s1.setAge(20);
	s1.setHeight(1.7);
	s1.printInfo();
    return 0;
}

2.2、继承中的构造

在c++中,使用继承时,子类可以继承所有父类非private的变量和函数,但不能继承构造函数

虽然子类不能继承父类构造函数,但在子类中调用父类的构造函数是没有问题的。

语法:

ChildClass::ChildClass(stirng name, int age,float score) : SuperClass(name, age) , m_score(score) {}

意为 在子类的构造函数中,调用父类的构造函数。

可通过using Base::Base把基类构造函数继承到派生类中,不需要书写多个派生类构造函数来完成基类的初始化

class Person
{
public:
	string name;
protected:
	int age;
private:
	double height;
public:
	Person(){}
	
	Person(string name, int age, double height):name(name),age(age),height(height){}

	void setName(string name)
	{
		this->name = name;
	}
	void setAge(int age)
	{
		this->age = age;
	}
	void setHeight(double height)
	{
		this->height = height;
	}

	//打印信息的方法
	void printInfo()
	{
		cout << "名字为" << this->name << "年龄为" << this->age << "身高为" << this->height << endl;
	}
};

class Student : public Person
{
public:
	using Person::Person;//using继承父类构造函数
	Student(){}
	Student (string name, int age, double height, double score):Person(name, age, height), score(score){}
	void showInfo()
	{
		cout << "名字为" << this->name << "年龄为" << this->age << "身高为" << this->height << endl;
	}
	double score;
};

int main()
{
    Student s1;
	Student s2("xm", 11, 1.5, 100);
	s2.printInfo();//从父类继承过来的printInfo,下同

    Person p1("zxx", 21, 1.8);
	p1.printInfo();
    return 0;
}

2.3、继承的构造和析构函数的调用顺序

子类继承完父类,当实例化子类时,一定会优先调用父类的构造函数。

换句话说,定义派生类构造函数时最好指明基类构造函数;若不指明,就调用基类的默认构造函数(不带参);如果没有默认的构造函数,则会编译失败

而析构函数的调用顺序与构造函数恰好相反。

class Animal
{
public:
	Animal()
	{
		cout << "call Animal" << endl;
	}
	~Animal()
	{
		cout << "break Animal" << endl;
	}
};

class Monkey : public Animal
{
public:
	Monkey()
	{
		cout << "call monkey" << endl;
	}
	~Monkey()
	{
		cout << "break monkey" << endl;
	}
};

class goldenMonkey :public Monkey
{
public:
	goldenMonkey()
	{
		cout << "call gmonkey" << endl;
	}
	~goldenMonkey()
	{
		cout << "break gmonkey" << endl;
	}
};

int main()
{
    goldenMonkey m1;
}

2.4、多继承

此章节与以上除语法外几乎无差异,在此只阐述几个需要注意的点。

语法:

class D:public A, protected B, private C {}

若现在已有一个派生类继承了多个基类,基类构造函数的调用顺序与他们在派生类构造函数中出现的顺序无关,而是和声明派生类时基类继承声明的顺序有关。

例如,上方语法中,就是先调A再B再C。

···

当多继承时,如果多个基类中有相同的成员变量或函数,那么就会存在冲突问题,若名字冲突,我们调用时就必须显式声明需要调用的成员。

class Person
{
public:
	Person()
	{
		cout << "call Person" << endl;
	}
	void printInfo()
	{
		cout << "pinfo" << endl;
	}

};
class Worker
{
public:
	Worker()
	{
		cout << "call Worker" << endl;
	}
	void printInfo()
	{
		cout << "winfo" << endl;
	}
};
class Ai :public Worker,public Person
{
public:
	Ai() :Person(),Worker()
	{
		cout <<  "call Ai" << endl;
	}
	void func()
	{
		Person::printInfo();
		Worker::printInfo();
	}
};

倘若没有将两个不同基类的pirntInfo写入func而是直接在堆中调用printInfo,那么就会出现报错。

2.5、菱形继承

c++中,在使用多继承时,若类A派生出类B和类C,类D又继承了类B和类C,那么此时情况就可以称为菱形继承。

聪明的你一定发现了,因为b和c都是继承的a,所以那么d一定会既继承b传下来的a的内容,也会继承c传下来的a的内容,这时候如果再在d中的a直接赋予a的值,那么此时就会出现定义不明确的情况。可能说起来有点抽象,我们上代码

class A
{
protected:
	int m_a;
};
class B : public A
{
protected:
	int m_b;
};
class C : public A
{
protected:
	int m_c;
};
class D : public B, public C
{
protected:
	int m_d;
public:
	void seta(int a)
	{
        //我们需要用类作用域操作符去限定是哪类
		A::m_a = a;//也就是此处this->m_a = a;的写法是会报错的,以为上面谁知道是a还是b还是c的值给到了此处的ma
	}
	void setb(int b)
	{
		this->m_b = b;
	}
	void setc(int c)
	{
		this->m_c = c;
	}
	void setd(int d)
	{
		this->m_d = d;
	}
	void print()
	{
		cout << A::m_a << endl;
	}
};

int main()
{
    D d1;
    d1.seta(100);
    d1.print();
    return 0;
}

2.6、虚继承

我们不难发现,我们使用菱形继承时,通常会发生数据冗余的问题,为了解决这一问题,c++提出了虚继承,其可以使得派生类中只保留一份间接继承的成员。

此时就不会怕出现像上面这种定义不明的情况也就是直接用this->m_a = 1;也是没有任何问题的,因为此时只保留了一份a的数据。

···

当进行普通的继承时,可以在子类直接显式调用父类的构造函数,在虚继承中,虚基类是由最终的派生类初始化的。

也就是说,最终派生类的构造函数必须要调用虚基类的构造函数。因为对于最终派生类来说,虚基类是间接基类,不是直接基类。这与普通继承不同,其派生类只能调用直接基类的构造函数,不能调用间接基类的。

此处依旧抽象,我们先上代码再说。

//虚基类A
class A
{
protected:
	int m_a;
public:
	A(int a):m_a(a){}
	void printInfo()
	{
		cout << this->m_a << endl;
	}
};
//直接派生类B
class B :virtual public A
{
protected:
	int m_b;
public:
	B(int a, int b):A(a), m_b(b){}
	void printInfo()
	{
		cout << this->m_a << "  " << this->m_b << endl;
	}
};
//直接派生类C
class C:virtual public A
{
protected:
	int m_c;
public:
	C(int a, int c):A(a), m_c(c){}
	void printInfo()
	{
		cout << this->m_a << "  " << this->m_c << endl;
	}
};
//间接派生类D
class D :public B, public C
{
protected:
	int m_d;
public:
	D(int a, int b, int c, int d):A(a),B(90, b),C(100, c), m_d(d){}
	void seta(int a)
	{
		this->m_a = a;
	}
	void setb(int b)
	{
		this->m_b = b;
	}
	void setc(int c)
	{
		this->m_c = c;
	}
	void setd(int d)
	{
		this->m_d = d;
	}
	void printa()
	{
		cout << this->m_a << endl;
	}
	void printInfo()
	{
		cout << this->m_a << "  " << this->m_b <<"   " <<this->m_c<<"   " <<this->m_d<<endl;
	}
};

int main()
{
    A a1(111);
	a1.printInfo();
	B b1(112, 222);
	b1.printInfo();
	C c1(113, 333);
	c1.printInfo();
	D d1(114,2223 ,3333,444);
	d1.printInfo();
}

当运行完代码我们可以看到 最后ma被初始化为114,这正是在D中直接调用A的构造函数的结果。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐