C++ Primer 第15章 面向对象程序设计
重要知识点
面向对象程序设计(object-oriented programming)的核心思想是数据抽象、继承 和动态绑定。
任何构造函数之外 的非静态函数 都可以是虚函数。
关键字virtual只能出现在类内部的声明语句之前 而不能用于类外部的函数定义。
派生类能访问公有成员,而不能访问私有成员,也可以访问受保护的(protected)成员。
从派生类向基类的类型转换只对指针 或引用类型 有效。
如果我们通过基类的引用或指针调用函数,则使用基类中定义的默认实参,即使实际运行的是派生类中的函数版本也是如此。
派生类的成员和友元只能访问派生类对象中的基类部分的受保护成员;对于普通的基类对象中的成员不具有特殊的访问权限。
例如(代码一):
class Base{
protected:
int prot_mem;
};
class Sneaky: public Base{
friend void clobber(Sneaky&); // 能访问Sneaky::prot_mem
friend void clobber(Base&); // 不能访问Base::prot_mem
int j; // j默认为private
};
// 正确:clobber能访问Sneaky对象的private和protect成员
void clobber(Sneaky &s) { s.j = s.prot_mem = 0; }
// 错误:clobber不能访问Base的protected成员
void clobber(Base &b) { b.prot_mem = 0; }
派生访问说明符对于派生类的成员(及友元)能否访问其直接基类的成员没什么影响,对基类成员的访问权限只与基类中的访问说明符有关。
派生访问说明符的目的是控制派生类用户。
例如(代码二):
class Base{
public:
void pub_mem();
protected:
int prot_mem;
private:
char priv_mem;
};
struct Pub_Derv : public Base{
// 正确
int f() { return prot_mem; }
// 错误:private成员对于派生类来说是不可访问的
// char g() { return priv_mem; }
};
struct Priv_Derv : private Base{
// private 不影响派生类的访问权限
int f1() const { return prot_mem; }
};
struct Derived_from_Public : public Pub_Derv {
// 正确: prot_mem 在 Pub_Derv 中 仍然是 protected 的
int use_base() { return prot_mem; }
};
struct Derived_from_Private : public Priv_Derv{
// 错误:prot_mem 在 Priv_Derv 中是 private 的
// int use_base() { return prot_mem; }
};
Priv_Derv d2;
// d2.pub_mem(); // 错误: pub_mem在派生类中是private的
派生类向基类转换的可访问性
假定D继承自B:
- 只有当D公有地继承B时,用户代码才能使用派生类向基类的转换;如果D继承B的方式是受保护的或者私有的,则用户代码不能使用该转换。
例如:
//假定使用了代码二中的类
Base *p = &d1; // d1的类型是Pub_Derv 正确
p = &d2; // d2的类型是Priv_Derv 错误
p = &d3; // d3的类型是prov_Derv 错误
p = &dd1; // dd1的类型是Derived_from_Public 正确
p = &dd2; // dd2的类型是Derived_from_Private 错误
p = &dd3; // dd3的类型是Derived_from_Protect 错误
- 不论D以什么方式继承B,D的成员函数和友元都能使用派生类向基类的转换;派生类向其直接基类的类型转换对于派生类的成员和友元来说永远是可访问的。
//假定在代码二中的类有如下形式的成员函数:
void memfcn(Base &b) { b = *this; }
// Pub_Derv 可以
// Priv_Derv 可以
// Prot_Derv 可以
- 如果D继承B的方式是公有的或者受保护的,则D的派生类的成员和友元可以使用D向B的类型转换;反之,如果D继承B的方式是私有的,则不能使用。
//假定在代码二中的类有如下形式的成员函数: void memfcn(Base &b) { b = *this; } // Derived_from_Public 可以 // Derived_from_Private 不可以 // Derived_from_Protect 可以
不能继承友元关系;每个类复制控制各自成员的访问权限。
派生类只能为那些它可以访问的名字提供using声明。
默认情况下,使用class关键字定义的派生类是私有继承;而使用struct关键字定义的派生类是公有继承。
派生类的成员将隐藏同名的基类成员。
如果派生类希望所有的重载版本对于它来说都是可见的,那么它就需要覆盖所有的版本,或者一个也不覆盖。一个好的解决方法是为重载的成员提供一条using声明语句,using声明语句指定一个名字而不指定形参列表,所以一条基类成员函数的using声明语句就可以把该函数的所有重载实例添加到派生类的作用域中。此时,派生类只需要定义其特有的函数就可以了,而无须为继承而来的其他函数重新定义。
一个构造函数的using声明不会改变该构造函数的访问级别。
当一个基类构造函数含有默认实参时,这些实参不会被继承。
如果一个类只含有继承的构造函数,则它也将拥有一个合成的默认构造函数。
如果构造函数或析构函数调用了某个虚函数,则我们应该执行与构造函数或析构函数所属类型相对应的虚函数版本。
#include<iostream>
class A {
public:
A() {
show();
}
~A() {
show();
}
virtual void show() {
std::cout << "a" << std::endl;
}
};
class B : public A {
public:
B() :A() {
}
~B(){}
void show() override{
std::cout << "b" << std::endl;
}
};
int main()
{
B* ptrb = new B(); //输出a
delete ptrb;//输出a
return 0;
}
编程建议
基类通常都应该定义一个虚析构函数。
如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一样。
除了覆盖继承而来的虚函数之外,派生类最好不要重用其他定义在基类中的名字。