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.模板类
+补坑…