抛砖引玉
前面介绍了类,一个类中包含了若干数据成员和成员函数。在不同的类中,数据成员和成员函数是不相同的。但有时两个类的内容基本相同或有一部分相同。假设我们已声明了学生基本数据的类 Student ;其中包括学生的学号、姓名、性别的信息,如果我们还需要用到年龄、家庭住址等信息,就要重新再定义一个学生类。很显然,我们新定义的学生类有相当一部分是原来已有的(学号、姓名、相别),那么是否能利用原来声明的 Student 类作为基础,在加上新的内容即可。C++ 提供的继承机制就很好的解决了这个问题。
继承和派生(概念及关系)
一起来先看一个例子:
class Student
{
public:
void show()
{
cin >> num >> name;
}
void display()
{
cout << "num: " << num << endl;
cout << "name: " << name << endl;
}
int num;
int name;
};
class Student1:public Student
{
public:
void display1()
{
cout << "num: " << num << endl;
cout << "name: " << name << endl;
cout << "age: " << age << endl;
}
private:
int age;
};
可以看出,在 Student1 中并未创建 num 和 name 成员,但依旧可以访问,这就是继承。“继承”就是在一个已存在的类的基础上建立一个新的类。
一个新类从已有类那里获得其已有的特性称为“类的继承”。
通过继承,一个新建子类从已有的父类那里获得父类的特性。换句话说,从已有的类产生一个新的子类,称为类的派生。
继承和派生的关系如下图所示:
>
概括的讲,派生类是基类的具体化,而基类是派生类的抽象。基类综合了派生类的公共特征,派生类则在基类的基础上增加某些特征。
派生类继承了基类的所有数据成员和成员函数,并可以对成员作必要的增加或调整。一个基类可有多个派生类,一个派生类也可有作为基类再派生出新的派生类。
继承方式分类
在刚才的代码中,可以看出声明派生类的一般形式为:
class 派生类名:[继承方式] 基类名
{
派生类新增加的成员
}
在派生类中,对基类的继承方式可以有 public (公有的)、private(私有的)、protected(保护的)3种。不同的继承方式决定了基类成员在派生类中的访问属性。
- 继承关系为 public 时:
class Student
{
public:
Student()
{
cout << "Student" << endl;
}
virtual ~Student()
{
cout << "~Student" << endl;
}
void showStudent()
{
cout << "_num" << _num << endl;
}
int _num;
protected:
int _name;
private:
int _gender;
};
class Student1:public Student
{
public:
Student1()
{
cout << "Student1()" << endl;
}
virtual ~Student1()
{
cout << "~Student()" << endl;
}
void showStudent1()
{
cout << "_addr" << _addr << endl;
}
void test1()
{
Student1 s1;
s1._num = 2;//可以访问
s1._name = 3;//可以访问
s1._gender = 4;//不能访问
}
public:
int _addr;
protected:
int _age;
private:
int _id;
};
void test2()
{
Student1 s2;
s2._num = 5;//可以访问
s2._name = 6;//不能访问
s2._gender = 7;//不能访问
}
可以看出,在派生类中,基类的公有成员和保护成员可以访问,但私有成员不能访问;在派生类外,基类中只有公有成员可以访问,私有成员和保护成员均不能被访问。
- 继承关系为 protected 时:
class Student
{
public:
Student()
{
cout << "Student" << endl;
}
virtual ~Student()
{
cout << "~Student" << endl;
}
void showStudent()
{
cout << "_num" << _num << endl;
}
int _num;
protected:
int _name;
private:
int _gender;
};
class Student1:protected Student
{
public:
Student1()
{
cout << "Student1()" << endl;
}
virtual ~Student1()
{
cout << "~Student()" << endl;
}
void showStudent1()
{
cout << "_addr" << _addr << endl;
}
void test1()
{
Student1 s1;
s1._num = 2;//可以访问
s1._name = 3;//可以访问
s1._gender = 4;//不能访问
}
public:
int _addr;
protected:
int _age;
private:
int _id;
};
void test2()
{
Student1 s2;
s2._num = 5;//不能访问
s2._name = 6;//不能访问
s2._gender = 7;//不能访问
}
在派生类中,基类的公有成员和保护成员可以访问,但私有成员不能访问;在派生类外,基类中所有成员都不能被访问。由此可见,基类中的成员变量被继承下来后把所有公有访问权限改成保护类型。
- 继承权限为 private 时:
class Student
{
public:
Student()
{
cout << "Student" << endl;
}
virtual ~Student()
{
cout << "~Student" << endl;
}
void showStudent()
{
cout << "_num" << _num << endl;
}
int _num;
protected:
int _name;
private:
int _gender;
};
class Student1:private Student
{
public:
Student1()
{
cout << "Student1()" << endl;
}
virtual ~Student1()
{
cout << "~Student()" << endl;
}
void showStudent1()
{
cout << "_addr" << _addr << endl;
}
void test1()
{
Student1 s1;
s1._num = 2;//可以访问
s1._name = 3;//可以访问
s1._gender = 4;//不能访问
}
public:
int _addr;
protected:
int _age;
private:
int _id;
};
void test2()
{
Student1 s2;
s2._num = 5;//不能访问
s2._name = 6;//不能访问
s2._gender = 7;//不能访问
}
可见,当继承方式为私有时,不论在派生类外还是派生类内,基类的所有成员都不能被访问。
通过对三种继承方式的分析,我们可以得出以下结论:
这些一定要牢记,不然到后面会很绕,接下来我们对概念做一个简单的总结。
概念总结
- 基类 private 成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为 protected 。可以看出保护成员限定符是因继承才出现的。
- public 继承是一个接口继承,保持 is-a 原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。
- protected / private 继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数场景下使用的都是公有继承。 - 不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)。
- 使用关键字 class 时默认的继承方式为 private ,使用 struct 时默认的继承方式为 public ,不过最好显示的写出继承方式。
父子俩的构造和析构函数
先看一个例子吧!
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;
}
程序结果为:
由上面的例子可以得出一个结论:
创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数;
派生类对象过期时,程序首先调用派生类析构函数,然后再调用基类析构函数。
这是为什么呢?前面我们讲到派生类不能访问基类的私有成员,而必须通过基类的公有方法来访问私有的基类成员,具体地说,派生类构造函数必须使用基类构造函数。
C++使用成员初始化列表来完成这种工作。
有关派生类构造函数的要点如下:
- 首先创建基类对象。
- 派生类的构造函数应通过成员初始化列表将基类信息传递给基类构造函数。
- 派生类的构造函数应初始化派生类新增的数据成员。
派生类和基类之间的赋值兼容规则
前提:必须为 public 的继承方式
- 基类不能赋值给派生类。
- 派生类可以赋值给基类。
- 基类的指针或引用可以在不进行显示类型转换的时候指向或引用派生类对象;但是基类的指针或引用只能调用基类的方法。
- 派生类的指针或引用不能指向基类对象。
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
void name() const;
};
class B :public A
{
public:
B()
{
cout << "B()" << endl;
}
~B()
{
cout << "~B()" << endl;
}
};
int main()
{
A a;
B b;
A& ra = b;//可以访问
A* pa = &b;//可以访问
B& rb = a;//不能访问
B* pb = &a;//不能访问
ra.name();//可以访问
pa->name();//可以访问
return 0;
}
最后,还有一点需要注意:友元是不能被继承的。