目录
一,友元
友元函数是指某些虽然不是类成员函数却能够访问类的所有成员的函数。类授予它的友元特别的访问权,这样该友元函数就能访问到类中的所有成员。
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。
友元可以直接访问本类的私有成员,提高编程的灵活性和程序执行效率。
- 需要访问非公有成员的非成员函数只能是类的友元函数使用友元类时注意:
- (1)友元关系不能被继承。
- (2)友元关系是单向的,不具有交换性。若类乙是类阿的友元,类阿不一定是类乙的友元,要看在类中是否有相应的声明。
- (3)友元关系不具有传递性。若类乙是类阿的友元,类Ç是乙的友元,类Ç不一定是类阿的友元,同样要看类中是否有相应的申明。
- (4)当用到友元成员函数时,需注意友元声明和友元定义之间的相互依赖,在该例子中,类乙必须先定义,否则类甲就不能将一个乙的函数指定为友元。然而,只有在定义了类甲之后,才能定义类乙的该成员函数。更一般的讲,必须先定义包含成员函数的类,才能将成员函数设为友元。另一方面,不必预先声明类和非成员函数来将它们设为友元。
二,尽可能使用常量
(1),常量变量好处
使用const的好处在于它允许指定一种语意上的约束 - 某种对象不能被修改 - 编译器具体来实施这种约束。
通过常量,你可以通知编译器和其他程序员某个值要保持不变。
借助编译器的帮助确保这种约束不被破坏。
(2),如何区别常量变量
//const,或二者同时指定为 const,还有,两者都不指定为 const:
char *p = "Hello"; // 非 const 指针,非 const 数据
const char *p = "Hello"; // 非 const 指针,const 数据
char * const p= "Hello"; // const 指针,非 const 数据
const char * const p = "Hello"; // const 指针,const 数据
常量语法并非看起来那么变化多端一般来说,你可以在头脑里画一条垂直线穿过指针声明中的星号(*)位置,如果const的出现在线的左边,指针指向的数据为常量;如果常量出现在线的右边,指针本身为常量;如果const的在线的两边都出现,二者都是常量 。
(3),C ++中const的成员函数及变量
- const的成员函数的目的当然是为了指明哪个成员函数可以在const的对象上被调用。
- 在C ++中,只有被声明为const的成员函数才能被一个const类对象调用,const对象只能访问const成员函数或者成员变量。
- 若将成员成员函数声明为常量,则该函数不允许修改类的数据成员。
- 常量的成员函数可以访问非对象的常量非常量的数据成员,常量的数据成员,也可以访问常量的对象内的所有数据成员。
- 非常量的成员函数可以访问非常量的对象的非常量的数据成员,常量数据成员,但不可以访问常量对象的任意数据成员。
- const的成员函数只是用于非静态成员函数,不能用于静态成员函数。
三,程序的二义性
C ++中存在着重载与继承,所以会存在多个函数名相同而参数列表不同的函数,当编译器不知道调用哪一个函数时,就会产生所谓的二义性。
- 多继承(见条款43)充满了潜在二义性的可能。最常发生的一种情况是当一个派生类从多个基类继承了相同的成员名时。
class Base1 {
public:int doIt();
};
class Base2 {
public:
void doIt();
};
// Derived 没有声明
class Derived: public Base1,public Base2 {
// 一个叫做 doIt 的函数
...
};
Derived d;
d.doIt();
// 错误! — — 二义
解决方法:
- 使用预解析符调用函数
- d.Base1 :: doIt方法(); //正确,调用Base1 :: doIt
- d.Base2 :: doIt方法(); //正确,调用Base2 :: doIt
- 使用虚基类,虚继承
四,划分全局名字空间
将定义的名字都放在这个单一的空间中,从而不可避免地导致名字冲突。
namespace sdm {
const double BOOK_VERSION = 2.0;
class Handle { ... };
Handle& getHandle();
}
/*
用户于是可以通过三种方法来访问这一名字空间里的符号:
1、将名字空间中的所有符号全部引入到某一用户空间;
2、将部分符号引入到某一用户空间;
3、或通过修饰符显式地一次性使用某个符号:
*/
五、纯虚函数与虚函数
虚函数
- C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现。
- 子类可以重写父类的虚函数实现子类的特殊化。
- 当虚函数初始化为0时即为纯虚函数。
- 虚函数必须是基类的非静态成员函数,其访问权限可以是protected或public
virtual 函数返回值类型 虚函数名 (形参表) { 函数体 }
纯虚函数
- C++中包含纯虚函数的类被称为是“抽象类”。抽象类不能使用new出对象,只有实现了这个纯虚函数的子类才能new出对象。
- C++中的纯虚函数更像是“只提供申明,没有实现”,是对子类的约束,是“接口继承”。
- C++中的纯虚函数也是一种“运行时多态”。
class <类名> { virtual <类型><函数名>(形参表) = 0; ... }
virtual在函数中的使用限制
- 普通函数不能是虚函数,也就是说这个函数必须是某一个类的成员函数,不可以是一个全局函数,否则会导致编译错误。
- 静态成员函数不能是虚函数 static成员函数是和类同生共处的,他不属于任何对象,使用virtual也将导致错误。
- 内联函数不能是虚函数如果修饰内联函数如果内联函数被虚修,计算机会忽略内联使它变成存在的虚函数。
- 构造函数不能是虚函数,否则会出现编译错误。
- 构造函数不能是虚函数理解
- 虚函数表vtbl由类的实例化对象的vptr指针,该指针存放在对象的内部空间中,使用时需要进行初始化。
- 当构造函数为虚函数时,类没有被实例化即一下虚函数所需要的资源没有被实例化,所以无法通过vptr指针进行虚函数的调用,所以构造函数不能是虚函数
- 当构造函数被定义成虚函数时,编译器会进行检查,所以编译的时候就会报错。
- virtual - >分构函数实现删除父类指针调用子类的分构函数,否则可能会出现内存泄漏。
六、对象模型
在此模型下,nonstatic 数据成员被置于每一个类对象中,而static数据成员被置于类对象之外。static与nonstatic函数也都放在类对象之外,而对于virtual 函数,则通过虚函数表+虚指针来支持:
- 每个类生成一个表格,称为虚表(virtual table,简称vtbl)。虚表中存放着一堆指针,这些指针指向该类每一个虚函数。虚表中的函数地址将按声明时的顺序排列。
- 每个类对象都拥有一个虚表指针(vptr),由编译器为其生成。虚表指针的设定与重置皆由类的复制控制(也即是构造函数、析构函数、赋值操作符)来完成。vptr的位置为编译器决定,传统上它被放在所有显示声明的成员之后,不过现在许多编译器把vptr放在一个类对象的最前端(也就是说对象的地址就是vptr的地址)。
- 虚函数表的前面设置了一个指向type_info的指针,用以支持RTTI(Run Time Type Identification,运行时类型识别)。RTTI是为多态而生成的信息,包括对象继承关系,对象本身的描述等,只有具有虚函数的对象在会生成。

单继承(父类含虚函数)
原则:对普通单继承而言
- 子类与父类拥有各自的一个虚函数表
- 若子类并无overwrite父类虚函数,用父类虚函数
- 若子类重写(overwrite)了父类的虚函数,则子类虚函数将覆盖虚表中对应的父类虚函数
- 若子声明了自己新的虚函数,则该虚函数地址将扩充到虚函数表最后
#include <iostream>
using namespace std;
class Base
{
public:
virtual void fun1(){ cout << "Base fun1" << endl; }
virtual void fun2(){ cout << "Base fun2" << endl; }
private:
int a;
};
class Derive : public Base
{
public:
void fun2(){ cout << "Derive fun2" << endl; }
virtual void fun3(){}
private:
int b;
};
int main()
{
Base b;
Derive d;
Base *p = &d;
p->fun1();
p->fun2();
system("pause");
return 0;
}
一般多继承
这里讲的是不考虑菱形继承的多继承,因为菱形继承需要用到虚继承,放到之后考虑
原则:
- 若子类新增虚函数,放在声明的第一个父类的虚函数表中
- 若子类重写了父类的虚函数,所有父类的虚函数表都要改变:如fun1
- 内存布局中,父类按照其声明顺序排列