前言
在自然界中普遍存在着继承,很多东西都存在继承性。
继承机制是面向对象程序设计使代码可以重复利用的重要手段,它允许在保持原有状态的情 况进行拓展,增加功能。
一、派生类的定义
一个B类继承于A类,或者称类A派生类B,所以,类A称为基类(父类),那么类B就称为子类(派生类)。
那么派生类中的成员包括两部分:
1.从父类继承过来的成员
2.拓展的成员
从父类继承过来的体现了其共性,而新增的成员体现其个性。
其定义为
class 派生类名 :继承方式 父类名{
//派生类新增的数据和成员函数
}
class Person{
public://父类的成员函数
void set(int age,string name){
_age=age;
_name=name;}
void Print(){
cout<<"age: "<<_age<<endl;
cout<<"name:"<<_name<<endl; }
protected:
int _age;
string _name;
};
class Student:public Person{//继承,Student继承person,且继承方式为public
public://派生类自己拓展的成员函数
Student(int stdID){
_stdID=stdID;}
protected://派生类自己拓展的属性
int _stdID;
};
继承相当于将父类中的代码复制到子类中。来验证一下
class Teacher:public Person{
};
cout<<"Person大小:"<<sizeof(Person)<<endl;
cout<<"Teacher大小:"<<sizeof(Teacher)<<endl;
cout<<"Student大小:"<<sizeof(Student)<<endl;
可以大胆的猜测一下,类Teacher,类Student和类Person的大小。
Person大小:16
Teacher大小:16
Student大小:24
类Teacher和类Person果然是相同的,而Student和Person不相同的原因是类Student中加入新的属性,而值得注意的是,加入的int型数据比Person类多8个字节,但是int类型应该占4个字节,这是因为字节对齐。前面也说过,函数放在代码区,数据放在栈上
二、访问控制符
三种继承方式(访问控制符):
public:共有继承
private:私有继承
protected:保护继承
class A{
public:
int x;
void set(){y=100;z=100;}
protected:
int y;
private:
int z;//在父类里面能访问,在父类外不能访问
// 在子类类里类外都不能访问
};
创建一个父类A,这里着重说一下protected和private在类里的区别
protected修饰的在子类里面可以访问
private修饰的在子类里不可访问
class B:public A{
void set(){
x=100;
y=100;//protected修饰的可以访问
//z=100;//[Error] 'int A::z' is private
//用private来修饰,子类不能访问
}
};
二.继承关系
public 继承 保持父类访问控制符关系不变
protected 继承 父类的public在子类中表现为protected
private 继承 父类中的public和protected在子类表现为privata
class A{
public:
int x;
void set(){y=100;z=100;}
protected:
int y;
private:
int z;
};
public继承:
class B:public A{
void set(){
x=100;y=100;
//z=100;//[Error] 'int A::z' is private
//用private来修饰,子类不能类里类外都不能访问
}
};
protected:
class C:protected A{//用protected 来继承的话将类里public属性改成protected
};
用protected继承后,在类里只能是protected和private作用域,无论是protected还是private在类外都不能访问。所以protected继承后,类外均不可以访问
private:
class D:private A{用private来继承的话将类里public属性改成private
};
用private方式继承后,在类只能是private,private域不能在外访问。同样的protected相同的,private继承后在类外是不能访问的。
那么,protected和private有什么区别呢
如果我们在对protected继承的C进行派生
class C:protected A{//用protected 来继承的话将类里public属性改成protected
// 继承后的作用域
// protected:
// x;
// protected:
// y;
// private:
// z;
};
class E:public C{
//再继承后的作用域
// protected:
// x;
// protected:
// y;
void set(){//对x,y进行赋值操作
x=100;
y=100;
}
};
对其进行再次继承后类E里面x,y只能在类里访问。
但是如果对private继承后的类进行再次继承
class D:private A{
// private:
// x;
// private:
// y;
// private:
// z;
};
class F:public D{
// private:
// x;
// private:
// y;
// void set(){
// x=100;[Error] 'int A::x' is inaccessible
// y=100;在之前的继承继承中,D中所有属性被修改成了private
// }
};
这个时候如果再对x,y进行赋值,报错。
所以继承关系用public,protected和private就看后面的几次继承。
三.析构函数和构造函数
构造时:先对基类进行构造,然后对子类进行构造
析构时:先对子类进行析构,再对父类进行析构
#include<iostream>
using namespace std;
class A{
public:
A(){cout<<"A()"<<endl;}
~A(){cout<<"~A()"<<endl;}
};
class B:public A{
public:
B(){cout<<"B()"<<endl;}
~B(){cout<<"~B()"<<endl;}
};
int main(){
B b;
return 0;
}
A()
B()
~B()
~A()
二.构造函数的巧用
初始化一个对象的时候不免要对其属性进行赋值,
对父类中的public属性,赋值的方法比较多,在类外就可以赋值。
父类中protected属性,无法在类外进行赋值,可以在类里进行赋值
父类中private属性,只有在父类里面进行赋值
当然,都可以在父类里写个set函数,通过在子类里面调用,如上面的
void set2(int z){ z=z;}
void set(){ x=100;y=100;set2(100); }
当然,简单的成员属性这样初始化,但是当属性多了这样写效率会很低的
用下面这个方法可以提高效率
在调用子类构造函数时使用 初始化成员列表
A(int x,int y,int z):x(x),y(y),z(z){
}
B(int a,int b,int c):A(a,b,c){
}
class A{
public:
int x;//对于x而言,有三种方法进行初始化
// void set2(int z){
// z=z;
// }
A(){
cout<<"A()"<<endl;
}
A(int x,int y,int z):x(x),y(y),z(z){
// x=x;y=y;z=z;
cout<<"A(int x,int y,int z)"<<endl;
}
protected:
int y;//对y来说,有两种
private:
int z;//对于z而言有两种
};
class B:public A{
// void set(){
// x=100;
// y=100;
// set2(100);
// }
public:
B(){
cout<<"B()"<<endl;
}
B(int a,int b,int c):A(a,b,c){
cout<<"B(int a,int b,int c)"<<endl;
}
};
四.父类与子类成员名冲突
如果我们在构造子类和父类的时候,如果不小心将子类和父类中成员函数和成员变量的名设置相同了,那么会不会继承下去,或者说继承后又该怎么样去使用?
class A{
public:
int a;
void func(){cout<<"A::void func(int a)"<<endl;}
};
class B:public A{
public:
int a;
void func(){cout<<"B::void func(int a)"<<endl;}
};
当在子类B中去使用成员变量a,使用的是子类B中的变量a,同样的道理调用func函数时候一样调用的是子类的中的函数,那么B类有没有继承A类呢,肯定继承了,不过被隐藏了,我们可以用sizeof来求出类B的大小来判断。
cout<<sizeof(B)<<endl;//输出 8
那么该怎么使用到父类中的成员呢?
作用域::
子类对象.父类名::父类成员
B b;
b.A::a=200;
cout<<b.A::a<<endl;//200
用这样的方法就可以调用到子类中隐藏的父类成员。
五.多继承和菱形继承
多继承:
class 子类名:继承方式 父类1 , 继承方式 父类2,
class A{
public:
int a;
};
class B{
public:
int b;
};
class C:public A,public B{
public:
int c;
};
菱形继承:
像这样的继承方式称为菱形继承
class A{
public:
int a;
};
class B:public A{
public:
int b;
};
class C:public A{
public:
int c;
};
class D:public B,public C{
public:
int d;
};
菱形继承有个缺点:当在使用D类对象对a属性进行操作是,编译器不知道该属性是来自B还是来自C
D d;
cout<<sizeof(d)<<endl; //40
d.a=100;//[Error] request for member 'a' is ambiguous
虚继承
引入虚继承来解决这个问题:在继承方式前加上 virtual
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;
};
虚继承的类将共享他们父类的数据
静态成员
静态变量的特点:
1.能继承到子类对象中去
2.当子类中从写父类中的静态变量时,父类中的静态变量被隐藏
3.访问父类中被隐藏的成员时用::
class A{
public:
static int a;//静态成员的定义 ,放在数据区
static int func(){return a;}//静态成员函数只能访问静态成员变量 ,放在代码区
};
int A::a=100; //静态成员的初始化
class B:public A{
};
A a;
cout<<a.func()<<endl;//100
B d;
cout<<d.func()<<endl;//100
b.a=110;//判断共享
cout<<a.func()<<endl;//110
cout<<d.func()<<endl;//110
//静态成员变量是共享的
cout<<sizeof(d)<<endl;//1
父类和子类的静态成员变量之间是共享的,当然共享的不仅仅是变量还有成员函数。