继承补充进阶
基类指针只能访问派生类的成员变量,不能访问派生类的成员函数
赋值的机制: 编译器通过指针来访问成员变量,指针指向哪个对象就使用哪个对象的数据;编译器通过指针的类型来访问成员函数,指针属于哪个类的类型就使用哪个类的函数
见’c+±面向对象-继承’
#include <iostream>
using namespace std;
class People
{
protected:
char *name;
int age;
public:
People(char *name,int age);
void print();
};
People::People(char *name,int age)
{
this->name=name;
this->age=age;
}
void People::print()
{
cout<<"class people"<<endl;
cout<<this->name<<" "<<this->age<<endl;
}
class Student:public People
{
private:
float score;
public:
Student(char *name,int age,float score);
void print();
};
Student::Student(char *name,int age,float score)
:People(name,age)
{
this->score=score;
}
void Student::print()
{
cout<<"class student"<<endl;
cout<<this->name<<" "<<this->age<<" "<<this->score;
}
int main()
{
People *p=new People("zhangsan",10);
/*
class people
zhangsan 10
*/
p->print();
p=new Student("lisi",20,3.);
/*
class people
lisi 20
*/
p->print(); // 使用了派生类的成员变量,但是没有使用派生类的成员函数,仍然使用的是基类的成员函数
return 0;
}
虚函数与多态
将上述的例子进行修改(将基类的成员函数修改为虚函数virtual function)
注意与 虚继承 的概念分开
普通继承下:
基类指针只能访问派生类的成员变量,不能访问派生类的成员函数
赋值的机制: 编译器通过指针来访问成员变量,指针指向哪个对象就使用哪个对象的数据;编译器通过指针的类型来访问成员函数,指针属于哪个类的类型就使用哪个类的函数
见’c+±面向对象-继承’
在虚函数下:
基类指针指向基类对象时就使用基类的成员(成员变量、成员函数); 指向派生类对象时就使用派生类的成员(成员变量、成员函数)
多态: 基类指针可以按照基类的方式做事,也可以按照派生类的方式做事
虚函数的唯一作用就是构成多态
多态:可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行访问,尤其是成员函数
有了多态,只需要一个指针就可以调用所有派生类的虚函数
构成多态的条件
存在继承关系
继承关系中具有同名的虚函数,且是覆盖关系(函数原型相同)
存在基类的指针,利用基类指针调用虚函数
#include <iostream>
using namespace std;
class People
{
protected:
char *name;
int age;
public:
People(char *name,int age);
virtual void print();
};
People::People(char *name,int age)
{
this->name=name;
this->age=age;
}
void People::print()
{
cout<<"class people"<<endl;
cout<<this->name<<" "<<this->age<<endl;
}
class Student:public People
{
private:
float score;
public:
Student(char *name,int age,float score);
void print();
};
Student::Student(char *name,int age,float score)
:People(name,age)
{
this->score=score;
}
void Student::print()
{
cout<<"class student"<<endl;
cout<<this->name<<" "<<this->age<<" "<<this->score;
}
int main()
{
People *p=new People("zhangsan",10);
/*
class people
zhangsan 10
*/
p->print();
p=new Student("lisi",20,3.);
/*
class student
lisi 20
*/
p->print();
return 0;
}
例子 - 重要
#include <iostream>
using namespace std;
class School
{
public:
virtual void print();
};
void School::print()
{
cout<<"school class"<<endl;
}
class Grade:public School
{
public:
void print();
};
void Grade::print()
{
cout<<"grade class"<<endl;
}
class ClassG:public Grade
{
public:
void print();
};
void ClassG::print()
{
cout<<"classG class"<<endl;
}
class Student:public ClassG
{
public:
void print();
};
void Student::print()
{
cout<<"student class"<<endl;
}
int main()
{
School *p=new School;
p->print();
Student *pstu=new Student;
pstu->print();
pstu->School::print();
cout<<"******"<<endl;
p=new Grade;
p->print();
p=new ClassG;
p->print();
p=new Student;
p->print();
delete p;
return 0;
}
虚函数
函数声明必须加上virtual,函数定义加不加virtual都行
可以只将基类的中的函数声明为virtual,派生类中具有相同名字的函数自动为虚函数
在基类中定义了虚函数,如果派生类中没有定义新的函数来遮蔽此函数,那么将使用基类的虚函数
只有派生类的虚函数覆盖基类的虚函数(函数原型相同(必须完全一样))才构成多态,即可利用基类指针访问派生类函数 (基类func()、派生类func(int) 基类指针->func(1)会出错,基类指针->func()会调用基类的func())
基类和派生类的构造函数不具有虚函数的机制,不能将构造函数声明为虚函数
析构函数可以声明为虚函数,有时必须声明为虚函数
使用场景
成员函数在类的继承后有无可能被更改功能,有更改则一般声明为虚函数
class People
{
public:
virtual void func();
virtual void func(int a);
};
void People::func()
{
cout<<"people class virtual func()"<<endl;
}
void People::func(int a)
{
cout<<"people class virtual func(int)"<<endl;
}
class Student:public People
{
public:
void func();
void func(int a);
void func(char *name);
};
void Student::func()
{
cout<<"Student class virtual func()"<<endl;
}
void Student::func(int a)
{
cout<<"Student class virtual func(int)"<<endl;
}
void Student::func(char *name)
{
cout<<"Student class virtual func(char *name)"<<endl;
}
int main()
{
People *p=new People;
p->func();
p->func(10);
// p->func("zhangsan"); //ERROR
p=new Student;
p->func();
p->func(20);
// p->func("lisi"); // ERROR 因为不是虚函数 ,通过基类的指针只能访问从基类继承过去的成员,不能访问派生类新增的成员
return 0;
}
例子2
#include <iostream>
using namespace std;
class People
{
protected:
char *name;
public:
People(char *name);
~People();
virtual void func();
};
People::People(char *name)
{
this->name=name;
}
People::~People()
{
}
void People::func()
{
cout<<this->name<<endl;
}
class Student:public People
{
private:
int age;
public:
Student(char*name,int age);
~Student();
void func();
};
Student::Student(char*name,int age)
:People(name)
{
this->age=age;
}
Student::~Student()
{
}
void Student::func()
{
cout<<this->name<<" "<<this->age<<endl;
}
int main()
{
People *p=new People("zhangsan");
p->func();
p=new Student("lisi",10);
p->func(); // 如果不是virtual 只打印lisi 基类的成员函数访问派生类的成员变量
return 0;
}
虚 析构函数
构造函数不能是虚函数
大部分情况下,都应该将基类的析构函数声明为虚函数
例子1
基类的指针指向了派生类对象,不对调用派生类的析构函数 --> 造成内部泄漏
因为析构函数仍然是成员函数,且该例子中是非虚函数,通过指针访问非虚函数时,编译器会根据指针的类型来确定要调用的函数,也就是说指针指向哪个类就调用哪个类的函数,除非是虚函数
基类指针只能访问派生类的成员变量,不能访问派生类的成员函数
赋值的机制: 编译器通过指针来访问成员变量,指针指向哪个对象就使用哪个对象的数据;编译器通过指针的类型来访问成员函数,指针属于哪个类的类型就使用哪个类的函数
而派生类的析构函数始终且一定会调用基类的析构函数
class People
{
protected:
char *name;
public:
People();
~People();
};
People::People()
{
name=new char[100];
cout<<"people class 构造"<<endl;
}
People::~People()
{
delete[] name;
cout<<"people class 析构"<<endl;
}
class Student:public People
{
private:
char *str;
public:
Student();
~Student();
};
Student::Student()
{
str=new char[100];
cout<<"student class 构造"<<endl;
}
Student::~Student()
{
delete[] str;
cout<<"student class 析构"<<endl;
}
int main()
{
/*
people class 构造
people class 析构
*/
People *p=new People();
delete p;
cout<<"*******"<<endl;
/*
people class 构造
student class 构造
student class 析构
people class 析构
*/
Student *pstu=new Student();
delete pstu;
cout<<"*******"<<endl;
/*
// 基类的指针指向了派生类对象,不对调用派生类的析构函数
people class 构造
student class 构造
people class 析构
*/
People *p2=new Student;
delete p2;
return 0;
}
例子2
修改例子1,将基类的析构函数修改为 虚析构函数,那么就可以利用多态的性质进行调用(又因为派生类的析构函数一定会调用基类的析构函数)
将基类的析构函数声明为虚函数后,派生类的析构函数自动称为虚函数
class People
{
protected:
char *name;
public:
People();
virtual ~People();
};
/* == main() ==*/
/*
people class 构造
student class 构造
student class 析构
people class 析构
*/
People *p2=new Student;
delete p2;
纯虚函数与抽象类
纯虚函数没有函数体,只有函数声明,且=0,表明函数是纯虚函数
纯虚函数无法调用,无法为其分配内存空间
virtual returnType func(params)=0;
抽象类:包含纯虚函数的类为抽象类
抽象类无法实例化,无能创建实例对象
抽象类通常作为基类,让派生类去实现纯虚函数,且派生类必须实现纯虚函数才能被实例化
抽象类的作用:约束派生类的功能,并实现多态
class People
{
protected:
float score;
public:
People(float score);
virtual float setdoublescore()=0;
};
People::People(float score)
{
this->score=score;
}
class Student:public People
{
protected:
float age;
public:
Student(float age,float score);
float setdoublescore();
};
Student::Student(float age,float score)
:People(score)
{
this->score=score;
}
float Student::setdoublescore()
{
return this->score*2;
}
int main()
{
Student *pstu=new Student(1.,2.);
cout<<pstu->setdoublescore()<<endl;
return 0;
}