程序代码:
2011.7.3
第15章 面向对象编程
面向对象编程(Object-oriented programmingm OOP)基于三个基本概念:数据抽象、继承和动态绑定。
多态性,通过继承而相关联的类型为多态类型,是因为在许多情况下可以互换地使用派生类型或基类型的“许多形态”。
在C++中,多态性仅用于通过继承而相关联的类型的引用或指针。
在C++中,基类必须指出希望派生类重定义哪些函数,定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。
在C++中,通过基类的引用(或指针)调用虚函数时,发生动态绑定。
继承层次的根类一般都要定义虚析构函数即可。
在非虚函数的调用在编译时确定。除了构造函数之外,任意非static成员函数都可以是虚函数,保留字virtual不能用在类定义体外部出现的函数定义上。
protected成员可以被派生类对象访问但不能被该类型的普通用户访问。
派生类只能通过派生类对象访问其基类的protected成员,派生类对其基类类型对象的protected成员没有特殊访问权限。
提供给派生类型的接口是protected成员和public成员的组合。
类派生列表指定了一个或多个基类。
访问标号决定了对继承成员的(最大)访问权限。 这句话自己添加的不是很确切,主要是指继承成员的访问权限等于或严格于派生类的访问标号、
如果想要继承基类的接口,则应该进行public派生。 经常见到的也是这种派生方式。
派生类一般会重定义所继承的虚函数。
如果派生类没有重定义,则使用基类中定义的版本。
派生类必须对想要重定义的每个继承成员进行声明。
派生类中虚函数的声明必须与基类中的定义方式完全匹配。 一个例外:返回对基类型的引用(或指针)的虚函数,派生类中的虚函数可以返回基类函数所返回类型的派生类的引用(或指针)。
派生类重定义虚函数时,可以使用virtual,但不是必须这样做。
已定义的类才可以用作基类。
暗示着不可能从类自身派生出一个类。
基类本身可以是一个派生类。
如果需要声明(但并不实现)一个派生类,则声明包含类名但不包含派生列表。
可将基类类型的引用绑定到派生类对象的基类部分,指针也是如此。 这里注意是基类的引用或指针可以绑定到派生类上,反之则不行。
任何可以在基类对象上执行的操作也可以通过派生类对象使用。
引用和指针的静态类型与动态类型可以不同,这是C++用以支持多态性的基石。 静态类型:static type, 在编译时可知的引用类型或指针类型。 动态类型:dynamic type, 指针或引用绑定的对象的类型,这是仅在运行时可知的)。
如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型所定义的函数。
只有通过引用或指针调用,虚函数才在运行时确定。
非虚函数总是在编译时根据调用该函数的对象、引用或指针的类型而确定。
只有成员函数中的代码才应该使用作用域操作符覆盖虚函数机制。
最常见的理由是为了派生类虚函数调用基类的版本。
通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值。
派生类可以进一步限制但不能放松对所继承的成员的访问。
无论派生列表中是什么访问标号,所有继承Base的类对Base中的成员具有相同的访问。
在继承层次的设计中,有一个重要概念:是一种(Is A) 有一个(Has A)
为了使size在Derived中成为public,可以在Derived的public部分增加一个using声明:using Base::size; , 从而恢复继承成员的访问级别。
私有继承相当罕见,所以如果确实需要,显式指定可避免误解。 struct派生则默认为public, class派生则默认为private。
友元可以访问类的private和protected数据。
友元关系不能继承。如果基类被授予友元关系,则只有基类具有特殊访问权限。
每个static成员只有一个实例。
确定派生类到基类转换的可访问性取决于在派生类的派生列表中指定的访问标号。
从基类到派生类的自动转换是不存在的。 如果确定可以,需要使用static_case强制转换。
构造函数和复制控制成员不能继承。
派生类构造函数的初始化列表只能初始化派生类的成员,不能直接初始化继承成员。
可以将基类包含在构造函数初始化列表中间接初始化。
class Bulk_item : public Item_base {
public:
Bulk_item(const std::string& book, double sales_price,
std::size_t qty = , double disc_rate = 0.0) :
Item_base(book, sales_price),
min_qty(qty), discount(disc_rate) { }
};
一个类只能初始化自己的直接基类。 直接基类就是在派生列表中指定的类。
派生类应通过使用基类构造函数尊重基类的初始化意图。
具有指针成员的类一般需要定义自己的复制控制来管理成员。
派生类析构函数不负责撤销基类对象的成员。
基类中的析构函数必须是虚函数。
即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。
强调之前,虚函数必须在基类和派生类中具有相同的形参。
在继承情况下,派生类的作用域嵌套在基类作用域中。
与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。
可以使用作用域操作符来访问被屏蔽的基类成员。
设计派生类时,只要可能,最好避免与基类成员的名字冲突。
在派生类作用域中派生类成员屏蔽基类成员,即使函数原型不同,基类成员也会被屏蔽。
成员函数(无论虚还是非虚)可以重载。
如果派生类重定义了重载函数,则通过派生类型只能访问派生类中重定义的那些成员。
派生类不用重定义所继承的每一个基类版本,可以为重载成员提供using声明,一个using声明只能指定一个名字。
通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类。
在函数形参表后面写上=0以指定纯虚函数。
含有(或继承)一个或多个纯虚函数的类是抽象基类(Abstract base class)。
不能创建抽象类型的对象。
后面关于指针或引用管理的内容跳过。
第16章 模板与泛型编程
这一章之所以阅读的主要原因在于看一位大牛的网站,他写的数据结构学习笔记,一直使用template的东西,简单阅读这一章,做到可以看懂代码即可,所以仅做了解。
泛型编程就是以独立于任何特定类型的方式编写代码。
模板是泛型编程的基础。
函数模板是一个独立于类型的函数。
template <typename T>
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
inline函数模板,需要注意inline的位置:
template <typename T> inline T min(const T&, const T&);
书上16.1.2中,又一次提醒了关于重载条件的复习,详见P378.
模板形参遵循常规名字屏蔽规则。与全局作用域中声明的对象、函数或类型同名的模板形参会屏蔽全局名字。
每个非类型形参前面必须带上类型名字,省略关键字或类型说明符是错误的:
template <typename T, U> T calc (const T&, const U&);
改正为:template <typename T, typename U> T calc (const T&, const U&);
typename显式指明一个名字是一个类型。所以在类型之前指定typename没有害处。
在调用函数时非类型形参将用值代替,值的类型在模板形参表中指定。
这里书上例子,很能说明问题:
template <class T, size_t N> void array_init(T (&parm)[N])
{
for (size_t i = 0; i != N; ++i) {
parm[i] = 0;
}
}
编译器从数组实参计算非类型形参的值:
int x[42];
double y[10];
array_init(x); // 实例化为array_init(int(&)[42]) 因为后面不再继续写了,所以这里提一下实例化的概念:产生模板的特定类型实例的过程称为实例化。
array_init(y); // 实例化为array_init(double(&)[10])
对模板的非类型形参而言,求值结果相同的表达式将认为是等价的。调用的是相同的实例——array_init<int, 42>:
int x[42];
const int sz = 40;
int y[sz + 2];
array_init(x);
array_init(y);
编写泛型代码的两个重要原则:
模板的形参是const引用。
函数体中的测试只用<比较。 在本章笔记开头的代码就体现了这种优势。
详细的原因,参见P534-P535