继承语法
protected访问权限
在Human类中,我们让其成员都是public的,因此子类Student内部都可以访问,但是类的成员一般是private访问权限,这些成员在子类中可以被访问吗?
class Human
{
public:
void init(string n, bool is_m, int a, string i) {
name = n;
is_male = is_m;
age = a;
id = i;
}
...
private:
string name;
...
};
class Student : public Human
{
public:
void init(string n, bool is_m, int a, string i, int g, string s_id) {
name = n; // 错误,不能访问name
is_male = is_m; // 错误,不能访问is_male
age = a; // 错误,不能访问age
id = i; // 错误,不能访问id
grade = g;
school_id = s_id;
}
private:
int grade;
string school_id;
};
int main()
{
...
}
可以看到,父类的private成员在子类中是无法访问的,private访问权限不但对外部保护了其修饰的成员,对子类也保护起来了。因为子类继承了父类,子类其实也含有了这些成员的,但是private访问权限阻止了我们在子类中对它们的访问。为了在子类中可以访问,需要以protected修饰父类的成员。
class Human
{
public:
void init(string n, bool is_m, int a, string i) {
name = n;
is_male = is_m;
age = a;
id = i;
}
...
protected: // 数据的访问权限是protected,子类能访问
string name;
...
};
class Student : public Human
{
public:
void init(string n, bool is_m, int a, string i, int g, string s_id) {
name = n; // 正确
is_male = is_m;
age = a;
id = i;
grade = g;
school_id = s_id;
}
private:
int grade;
string school_id;
};
int main()
{
...
}
protected 与 private类似,在类的外部不能访问。它们的唯一区别在继承时才表现出来,当定义一个子类的时候,父类的protected 成员可以被子类的其它成员所使用,然而private 成员就不可以。
可以访问 | public | protected | private |
---|---|---|---|
本class的成员 | yes | yes | yes |
子类的成员 | yes | yes | no |
非成员 | yes | no | no |
因此,对于父类成员:
- 如果希望类的外部可访问 ~ public
- 如果希望类的外部不可访问,但是子类(成员函数)可访问~protected
- 如果希望类的外部和子类都不可访问~private
public继承类型
class Student : public Human
类定义中 public
表示继承类型为public继承,还有protected继承和private继承。
继承类型指明父类的public和protected成员在子类中新的访问权限。与类成员的public、protected、private访问权限的概念有联系又有区别。
public继承使父类的public成员在子类中仍然是public的,父类的protected成员在子类中仍然是protected的。这意味着父类提供的方法子类也提供(public),这与继承表达了“是一个”关系是一致的。
Student::set_name // public
Student::get_name // public
…Student::name // protected
Student::age // protected
…
class Student : private Human 这样定义子类称为private继承,private继承让父类的public和protected成员在子类中都是private的,它被用来将父类完全封装起来,因为在这种情况下,除了子类自身外,其它任何程序都不能访问那些从父类继承而来的成员。
继承表达了“是一个”的关系前提是它是public继承类型。private继承和protected继承具有它们的特殊作用,我们只有知道存在这两种继承即可。
定义子类时,如果不写继承类型,默认是private继承。一般情况下我们需要的是public继承,因此不要忘记写public。
语法参考
继承语法参考类之间的继承(Inheritance between classes)
继承的其它知识点
继承中的函数重载
重载就是指函数同名但不同参数。
子类可以重载父类的成员函数,比如Student::init(string n, bool is_m, int a, string i, int g, string s_id)重载了Human::init(string n, bool is_m, int a, string i)。
继承时如果发生了函数重载,子类的函数就会隐藏了父类的同名函数。在子类中或者通过子类对象调用父类的那个被重载了的同名函数,要加上范围操作符::,指明调用的是父类的那个被重载的函数。
class Student : public Human
{
public:
void init(string n, bool is_m, int a, string i, int g, string s_id)
{
init(n, is_m, a, i); // 错误,父类的init函数被子类的重载函数init隐藏了
Human::init(n, is_m, a, i); // 正确,指明调用父类的init函数
grade = g;
school_id = s_id;
}
private:
int grade;
string school_id;
};
int main()
{
Student s;
s.init("张三", true, 20, "123456"); // 错误,父类的init函数被子类的重载函数init隐藏了
s.Human::init("张三", true, 20, "123456"); // 正确,指明调用父类的init函数
...
return 0;
}
继承中构造函数和析构函数的调用顺序
我们知道继承使子类对象包含了父类对象。创建子类对象时,在调用子类构造函数之前会调用父类的构造函数。与构造函数调用顺序相反,在销毁子类对象时,在调用子类的析构函数之后会调用父类的析构函数。
#include <iostream>
using namespace std;
class Base
{
public:
int x;
void show_x(){ cout << x << endl;}
Base(){ cout << "Base constructor calling" << endl; }
~Base(){ cout << "Base destructor calling" << endl; }
};
class Derived : public Base
{
public:
int y;
void show_y(){ cout << y << endl;}
Derived(){ cout << "Derived constructor calling" << endl; }
~Derived(){ cout << "Derived destructor calling" << endl; }
};
int main()
{
{
Derived d;
cout << endl;
}
return 0;
}
上述代码在main函数中将对象d创建在一对大括号中是因为大括号在程序中是一个作用域的标志,进入和退出这个区域,它里面的对象就会被创建和销毁,因此我们能及时地看到d的析构函数的调用。
这和对象与其成员对象的构造函数调用顺序是类似的,先调用对象中成员对象的构造函数,再调用那个对象的构造函数。因此是先部分后整体的顺序。调用析构时的顺序相反。对比类之间的关系(1. 使用关系和组合关系)
继承中调用父类带参数的构造函数
在子类构造函数中如果需要调用父类带参数的构造函数来初始化子类从父类继承的那部分成员,就需要使用成员初始化列表。
#include <iostream>
using namespace std;
class Base
{
public:
int x;
void show_x(){ cout << x << endl;}
Base(int x_) // 父类的构造函数带参数
{
x = x_;
cout << "Base constructor calling, x = " << x << endl;
}
};
class Derived : public Base
{
public:
int y;
void show_y(){ cout << y << endl;}
Derived(int x_, int y_) : Base(x_) // 在子类的成员初始化列表中给父类的构造函数传值
{
y = y_;
cout << "Derived constructor calling, y = " << y << endl;
}
};
int main()
{
Derived d(10, 20); // 10是用来初始化d.x的
return 0;
}
如果父类有默认构造函数,在子类的构造函数中可以不使用成员初始化列表,那以调用父类默认构造函数的方式来初始化父类成员。就像在调用顺序那里展示的一样。
如果父类没有默认构造函数,只有带参数的构造函数,那么被子类继承后,一定要在子类的构造函数中使用成员初始化列表来调用父类的带参数的构造函数。
可见,成员初始化列表可以:
在继承关系中,调用父类的带参数的构造函数。
例子
Human类有带参数的构造函数,
class Human
{
public:
Human(string n, bool is_m, int a, string i)
{
name = n;
is_male = is_m;
age = a;
id = i;
}
...
string name;
bool is_male;
int age;
string id;
};
Student类继承了Human类,如果需要调用Human类带参数的构造函数来初始化Human类的成员,那么要使用成员初始化列表
class Student : public Human
{
public:
Student(string n, bool is_m, int a, string i, int g) : Human(n, is_m, a, i)
{
grade = g;
}
int grade;
};
或者,grade成员也在成员初始化列表中初始化
class Student : public Human
{
public:
Student(string n, bool is_m, int a, string i, int g) : Human(n, is_m, a, i), grade(g)
{
}
int grade;
};