一 C++总体结构
(1)类型
简单类型
复合类型:
数组
指针
枚举
联合
结构
类
(1)成员函数
1)构造函数
-
-
- 拷贝构造函数
- 默认构造函数
-
2)析构函数
3)运算符函数
-
-
- 转换运算符
-
5)覆盖函数
若在类声明中在函数前指定了virtual,则virtual关键字不能再次出现在类声明之外的函数实现中
纯虚函数是这样一种虚拟函数,他只是起到一个占位符的作用,为其子类提供一个可供改写的 接口,它本身并不能通过虚拟机制被调用
virtual void ForFutrueUse() = 0;
(2)成员变量
-
-
- 类成员变量
-
(3)非成员函数与类
(4)非成员运算符和类
继承类
(1)成员函数
1)构造函数
基类构造函数的参数应在派生类构造函数的定义中有明确描述
-
-
- 拷贝构造函数
- 默认构造函数
-
的构造严格按照在类声明中的顺序,他们的销毁则按照相反的顺序进行。
2)析构函数
3)运算符函数
-
-
- 转换运算符
-
4)重载函数
派生类中如果出现和基类中名字相同的成员函数或成员变量,则在派生类中相对应的基类版本成员变量或成员函数将会不可见。除非使用类域来显式指出。之所以相同函数名的函数不能直接在基类和派生类之间重载,是因为重载函数必须都出现在同一个域中。而基类的函数属于基类的域,派生类的函数属于派生类的域
5)覆盖函数
#include <iostream>
using namespace std;
class Base{
protected:
virtual void fn()
{
cout <<"In Base class/n";
}
protected:
virtual void vfn() =0;
};
class SubClass :public Base{
public:
void fn()
{
cout <<"In SubClass/n";
}
virtual void vfn()
{
cout<<"vfn In SubClass/n";
}
};
class SubSubClass :public SubClass{
public:
void fn()
{
cout <<"In SubSubClass/n";
}
};
void test(Base& b)
{
//b.fn();
}
void main()
{
//Base bc;
SubClass sc;
SubSubClass ssc;
// cout <<"Calling test(bc)/n";
//test(bc);
//cout <<"Calling test(sc)/n";
//test(sc);
//sc.vfn();
//cout<<"Calling test(ssc)/n";
//test(ssc);
}
虚函数与虚继承
C++2.0以后全面支持虚函数与虚继承,这两个特性的引入为C++增强了不少功能,也引入了不少烦恼。虚函数与虚继承有哪些特性,今天就不记录了,如 果能搞了解一下编译器是如何实现虚函数和虚继承,它们在类的内存空间中又是如何布局的,却可以对C++的了解深入不少。这段时间花了一些时间了解这些玩 意,搞得偶都,不过总算有些收获,嘿嘿。
先看一段代码
class A
{
virtual aa(){};
};
class B : public virtual A
{
char j[3]; //加入一个变量是为了看清楚class中的vfptr放在什么位置
public:
virtual bb(){};
};
class C : public virtual B
{
char i[3];
public:
virtual cc(){};
};
这次先不给结果,先分析一下,也好加强一下印象。
1、对于class A,由于只有一个虚函数,那么必须得有一个对应的虚函数表,来记录对应的函数入口地址。同时在class A的内存空间中之需要有个vfptr_A指向该表。sizeof(A)也很容易确定,为4。
2、对于class B,由于class B虚基础了class A,同时还拥有自己的虚函数。那么class B中首先拥有一个vfptr_B,指向自己的虚函数表。还有char j[3],做一次alignment,一般大小为4。可虚继承该如何实现咧?this is 啊 problem!偶之前是不晓得的,还好C++ Object Model上有介绍。首先要通过加入一个虚l类指针(记vbptr_B_A)来指向其父类,然后还要包含父类的所有内容。有些复杂了,不过还不难想象。 sizeof(B)= 4+4+4+4=16(vfptr_B、char j[3]做alignment、vbptr_B_A和class A)。
3、在接着是class C了。class C首先也得有个vfptr_C,然后是char i[3],然后是vbptr_C_B,然后是class B,所以sizeof(C)=4+4+4+16=28(vfptr_C、char i[3]做alignment、vbptr_C_A和class B)。
在VC 6.0下写了个程序,把上面几个类的大小打印出来,果然结果为4、16、28。hoho搞定!真的搞定了?也许经过上面的分析,虽然每个类具体的内存布局还不大清楚,但其中的内容应该不会错了。嘿嘿,在没跟踪时偶确实也是这么想的,但结果却是……
VC中虚继承的内存布局——单继承
画了个图,简单表示一下我跟踪后的结果
class A的情况太简单,没问题。从class B的内存布局图可以得出下面的结论。
1、vf_ptr B放在了类的首部,那么如果要想直接拿memcpy完成类的复制是很危险的,用struct也是不行的。改天再深入学习一下struct 和class的区别,可以看出这里的差别来。
2、vbtbl_ptr_B,为什么不是先前我描述的vbptr_B_A呢?因为这个指针与我先前猜测的内容有很大区别。这个指针指向的是class B的虚类表(嗯,俺自个儿起的名字,实在是学艺不精)。看看VB table,VB table有两项,第一项为FFFFFFFC,这一项的值可能没啥意义,可能是为了保证虚类表不为空吧。第二项为8,看起来像是class B中的class A相对该vbtbl_ptr_B的位移,也就是一个offset。类似的方法在C++ Object Model(P121)有介绍,可以去看看。
class C的内存布局就比较复杂了,不过它的内存布局也更一步说明我对vbtbl_ptr_B中的内容,也就是虚类表的理解是正确的。不过值得关注的是class B中的class A在布局时被移到前面去了,虽然整个大小没变,但这样一来如果做这样的操作 C c; B *b;b=&c;时b的操作如何呢?此时只要从c的虚类表里获得class B的位置既可赋值给b。但是在构建class C时会复杂一些,后面的使用还是非常简单的,效率也比较高。class A的内存布局被前移可能是考虑倒C的虚继承顺序吧。
(2)成员变量
-
-
- 类成员变量
-
在继承关系中,基类的private成员不但对应用程序隐藏,即使是派生类也是隐藏不可访问的,而基类的保护成员只对应用程序隐藏,对于派生类来说是不隐藏的,保护继承与私有继承在实际编程工作中使用是极其少见的,他们只在技术理论上有意义。
(3)非成员函数与类
(4)非成员运算符和类
多重继承类
虚拟继承
{};
class b:public a
{};
class c:public a
{};
class d:public b,c
{};
d x;
这时x会有a的两份拷贝,浪费空间,也存在二义性
此时b,c要这样声明:
class b:virtual public a
{};
class c:virtual public b
{};
代价就是不能用基类对象的指针指向虚拟继承类的对象.
例2
#include <iostream.h>
#include <memory.h>
{
int k;
public:
void f() {cout << "CA::f" << endl;}
};
{
};
{
};
{
};
{
CB d1;
d.f();
d1.f();
cout<<sizeof(d)<<" "<<sizeof(d1)<<endl;
}
----
|CB|
|CC|
|CA|
----
同时,在CB、CC中都分别包含了一个指向CA的vbptr(virtual base table pointer),其中记录的是从CB、CC的元素到CA的元素之间的偏移量。此时,不会生成各子类的函数f标识,除非子类重载了该函数,从而达到“共享”的目的。
也正因此,此时的sizeof(CD) = 12(两个vbptr + sizoef(int));
所有这一切都是编译期间决定的,只是编译器为了提供这样一个新的语法功能为我们多作了一些事情而已。
纯虚函数防止产生VTABLE,但这并不意味着我们不希望对其他函数产生函数体。我们常常希望调用一个函数的基类版本,即便它是虚拟的。把公共
代码放在尽可能靠近我们的类层次根的地方,这是很好的想法。这不仅节省了代码空间,而且能允许使改变的传播变得容易
模板
1.模板的成员类型(以容器vector为例)
template <class T, class A = allocator<T> >
class std::vector {
public:
typedef T value_type; //元素类型
typedef A allocator_type; //元素存储管理器类型
typedef typename A::size_type;
typedef typename A::difference_type difference_type;
}
2.
(2)声明
(3)变量
(4)表达式
(5)语句
(7)函数
(8)名字空间和作用域
(9)异常:将错误报告和错误处理分离开的手段
(10)源文件、编译单位和程序、程序的物理结构和逻辑结构
(11)STL库