①、继承可以理解为一个类从另一个类获取成员变量和成员函数的过程,被继承的类称为基类,继承的类称为派生类。
派生类除了拥有基类成员,还可以自定义新成员。
#include<iostream>
using namespace std;
//基类 Pelple
class People{
public:
void setname(char *name);
void setage(int age);
char *getname();
int getage();
private:
char *m_name;
int m_age;
};
void People::setname(char *name){ m_name = name; }
void People::setage(int age){ m_age = age; }
char* People::getname(){ return m_name; }
int People::getage(){ return m_age;}
//派生类 Student
class Student: public People{ //Student类继承People类,public方式;
public:
void setscore(float score);
float getscore();
private:
float m_score;
};
void Student::setscore(float score){ m_score = score; }
float Student::getscore(){ return m_score; }
int main(){
Student stu;
stu.setname("小明");
stu.setage(16);
stu.setscore(95.5f);
cout<<stu.getname()<<"的年龄是 "<<stu.getage()<<",成绩是 "<<stu.getscore()<<endl;
return 0;
}
由此可看出继承的一般方式为:
class 派生类名:[继承方式] 基类名{
//派生类新增成员;
};
继承方式有:public(公有)、private(私有)和 protected(受保护),默认 private。
类成员的访问权限:public > protected > private
其中,protected 成员也是和 private 成员类似,也不能通过对象访问;但是在继承的时候,基类的 protected 成员可以在派生类中使用,但是 private 成员不行。
不同继承方式对不同属性成员的影响
②、继承中的名字遮蔽
如果派生类中的成员和基类中的成员重名,那么会遮蔽从基类继承过来的成员,即使用该成员时实际上使用的是派生类中的成员。因此,基类和派生类中的同名函数不构成重载。例:
#include<iostream>
using namespace std;
//基类Base
class Base{
public:
void func();
void func(int);
};
void Base::func(){ cout<<"Base::func()"<<endl; }
void Base::func(int a){ cout<<"Base::func(int)"<<endl; }
//派生类Derived
class Derived: public Base{
public:
void func(char *);
void func(bool);
};
void Derived::func(char *str){ cout<<"Derived::func(char *)"<<endl; }
void Derived::func(bool is){ cout<<"Derived::func(bool)"<<endl; }
int main(){
Derived d;
d.func("hello C++");
d.func(true);
// d.func(); //compile error
// d.func(10); //compile error
d.Base::func();
d.Base::func(100);
return 0;
}
上例中,构成重载的只是 Base 类的两个 func 或者 Derive 类的两个 func。所以要是想访问基类的同名函数,需要指定作用域,如 28、29 两行所示。
③、派生类的构造函数
类的构造函数不能被继承,所以在初始化基类的成员变量时,是在派生类的构造函数中调用基类的构造函数来完成初始化。
#include<iostream>
using namespace std;
//基类People
class People{
protected:
char *m_name;
int m_age;
public:
People(char*, int);
};
People::People(char *name, int age): m_name(name), m_age(age){}
//派生类Student
class Student: public People{
private:
float m_score;
public:
Student(char *name, int age, float score);
void display();
};
//People(name, age)就是调用基类的构造函数
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
void Student::display(){
cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<"。"<<endl;
}
int main(){
Student stu("小明", 16, 90.5);
stu.display();
return 0;
}
像 24 行代码一样,People(name, age) 就是调用基类的构造函数,并将 name 和 age 作为实参传递给它,m_score(score) 是派生类的参数初始化列表,它们之间用逗号隔开。
注: 多层派生中,派生类的构造函数中只能调用直接基类的构造函数,不能调用间接基类的。
同理,析构函数也是不能被继承的,并且因为每个类只有一个析构函数,所以派生类的析构函数不需要显式地调用基类的构造函数,编译器会自动选择。
另外,析构函数的执行顺序和构造函数的执行顺序也刚好相反:创建派生类对象时,构造函数的执行顺序和继承顺序相同,即先执行基类构造函数,再执行派生类构造函数。而销毁派生类对象时,析构函数的执行顺序和继承顺序相反,即先执行派生类析构函数,再执行基类析构函数。
④、C++指针可以突破权限限制
万能的指针是可以穿透地址,随意修改类的成员,即使是 private、const 成员也无济于事。
⑤、C++向上转型(将派生类赋值给基类)
C/C++中对不同类型的变量进行赋值时,编译器会将其转化成同类型,然后再赋值。同理,类也是一种数据类型,也可以进行数据转化,这种转化一般只发生在基类和派生类之间。将派生类赋值给基类称为向上转型,将基类赋值给派生类称为向下转型。
向上转型很安全,由编译器完成,向下转型有风险,需要程序员手动干预。
#include <iostream>
using namespace std;
//基类
class A{
public:
A(int a);
void display();
int m_a;
};
A::A(int a): m_a(a){ }
void A::display(){
cout<<"Class A: m_a="<<m_a<<endl;
}
//派生类
class B: public A{
public:
B(int a, int b);
void display();
int m_b;
};
B::B(int a, int b): A(a), m_b(b){ }
void B::display(){
cout<<"Class B: m_a="<<m_a<<", m_b="<<m_b<<endl;
}
int main(){
A a(10);
B b(66, 99);
//赋值前
a.display();
b.display();
cout<<"--------------"<<endl;
//赋值后
a = b;
a.display();
b.display();
return 0;
}
//运行结果:
Class A: m_a=10
Class B: m_a=66, m_b=99
----------------------------
Class A: m_a=66
Class B: m_a=66, m_b=99
将派生类对象赋值给基类对象时,会舍弃派生类新增的成员:
注:同一基类的不同派生类对象之间也不能赋值