c++的三大特性:封装、继承、多态。
继承的目的:提高代码重用、提高开发效率。
B继承于A类,A派生B,A为基类,B为派生类。
继承得到父类的数据和方法,自己新添一定的数据和方法。
父类是共性,子类是个性。
class 父类{};
class 子类 :继承方式 父类名
{
//新增子类的数据。
};
继承三种方式:私有、保护、公共。
private/protected/public(推荐)
这三种继承方式相同点是:三种继承方式都不可以访问父类的私有数据。
不同点是:公共继承,不改变父类其他数据和方法的类型,比如父类是公共的子类也是公共的,父类是保护的子类也是保护的。
保护继承:无论父类是何种类型的数据和方法,继承后都是保护的。
私有继承:无论·父类是何种类型的数据和方法,继承后都是私有的。
注意:这样的变化是编译器自己完成的,不需要人为进行操作。
#include<iostream>
using namespace std;
class Base
{
private:
int a;
protected:
int b;
public:
int c;
Base(){
}
Base( int a,int b,int c):a(a),b(b),c(c){}
};
class Son: public Base
{
public:
void func()
{
cout<<b<<c<<endl;
}
Son(int a,int b,int c):a(a),b(b),c(c)
{
}
};
/*
Base::Base( int a, int b, int c)
{
this->a=a;
this->b=b;
this->c=c;
}*/
int main()
{
Base ob1( 1,2,3);
cout<<ob1.c<<endl;
Son ob(1,2,3);
ob.func();
return 0;
}
子类构造析构的顺序:
构造顺序: 父类->成员->子类。
析构顺序: 子类->成员->父类。
#include<iostream>
using namespace std ;
class Base
{
public:
Base()
{
cout<<"父类构造"<<endl;
}
~Base()
{
cout<<"父类析构"<<endl;
}
};
class Other
{
public:
Other()
{
cout<<" Other构造"<<endl;
}
~Other()
{
cout<<" Other析构"<<endl;
}
};
class Son : public Base
{
public:
Other ob;
public:
Son()
{
cout<<" Son构造"<<endl;
}
~Son()
{
cout<<" Son析构"<<endl;
}
};
int main()
{
Son ob;
return 0;
}
按照这个构造的顺序,我们为了使父类构造和成员构造,在此之前发生。
子类想调用·父类的有参构造和成员的有参构造,必须用到初始化列表。
值得注意的是如果我们想调用父类的1参构造我们需要用父类的类名称,而调用成员的有参构造我们用写成员名。
#include<iostream>
using namespace std ;
class Base
{
private:
int a;
public:
Base()
{
cout<<"父类构造"<<endl;
}
Base( int a)
{
cout<<"父类有参构造"<<endl;
this->a=a;
}
~Base()
{
cout<<"父类析构"<<endl;
}
};
class Other
{
private:
int b;
public:
Other()
{
cout<<" Other构造"<<endl;
}
Other(int b)
{
this->b=b;
cout<<"Other有参构造"<<endl;
}
~Other()
{
cout<<" Other析构"<<endl;
}
};
class Son : public Base
{
public:
Other ob;
int c;
public:
Son()
{
cout<<" Son构造"<<endl;
}
Son( int a,int b,int c):Base(a),ob(b)//初始化列表
{
this->c=c;
cout<<" Son有参构造"<<endl;
}
~Son()
{
cout<<" Son析构"<<endl;
}
};
int main()
{
Son ob(1,2,3);
return 0;
}
2,在继承的过程中,子类和父类同名现象是很普遍的,也是我们在继承的过程中需要注意和解决的问题。
同名成员,最简单最安全的方式是加作用域。
访问父类,加父类的做用域,访问子类不用。
子类会优先访问自己的成员。
#include<iostream>
using namespace std;
class Base
{
public:
int a;
public:
Base()
{
// cout<<"父类构造"<<endl;
}
Base( int a)
{
// cout<<"父类有参构造"<<endl;
this->a=a;
}
~Base()
{
// cout<<"父类析构"<<endl;
}
};
class Son : public Base
{
public:
int a ;
Son(int x,int y):Base(x)
{
a=y;
}
};
int main()
{
Son ob(1,20);
cout<<ob.a<<endl;
cout<<ob.Base::a<<endl;
return 0;
}
从上面的结果我们看出当我们需要访问父类的成员时,我们需要加作用域。
成员方法也是这样的。
当我们子类重载了函数名,子类不能访问父类相同函数名的方法,即使他们参数不一样。
这时我们需要加入作用域。
总的来说继承过程中的同名处理,就是加作用域。
3,多继承
我们可以从一个类继承,亦可以同时的从多个类继承,这就是多继承。但是多继承备受争议,从多个类的继承会导致函数变量等同名造成的歧义。
这时我们最简单的方法还是加作用域。
3.2 菱形继承
菱形继承:有公共祖先的继承叫菱形继承。
最底层的子类 数据会包含多份(公共祖先的数据)
举一个例子,动物是父类,之下有两个子类 羊和驼,他们又同时有一个相同的子类,羊驼。
这时羊驼有两份祖先动物的数据,这就是菱形继承。当我们访问这个数据时,会产生二义性,这个时候我们一需要加作用域。
3.3虚继承
虚继承是解决菱形继承的多份数据的问题,虚继承就是在继承中加 virtual 修饰
class 子类 : virtual 继承方式 父类
{
};
还是上面的例子,如果羊和驼都是虚继承动物,羊驼继承羊和驼,这时我们只会得到一份数据不会产生二义性。
虚继承会产生一个虚基类指针,指向虚基类表,表里记录了这个数据的偏移量对应的是空间数据起始地址偏移。
在最有羊驼继承的过程中,羊驼会继承两个虚基类指针,而两个指针的数据偏移量是不一样的但指向的都是一份数据。
注意;虚继承是为了解决公共祖先引发的多义性,但是对于非公共祖先产生的多义性无法解决。
多继承在工程开发中是弊大于利的,对于代码维护也是非常困难的,在设计方法上多继承可以用单继承来代替。
感谢阅读!!!