您的位置:首页 > 编程语言 > C语言/C++

【重构C++知识体系】类 虚类 纯虚类

2016-03-18 16:05 671 查看

/*在C++中,我们通过类来定义自己的数据结构*/


类的作用(思想)

为了使C++遵守OOP基本法的数据抽象和封装.

数据抽象:就是将”接口”和”实现”实现分离的编程技术.

封装:隐藏对象的属性和实现细节,仅对外公开接口.

拓展:设计模式6大原则

1.单一职责原则:一个类只负责一项职责

2.里氏替换原则:子类可以扩展父类的功能,但不能改变父类原有的功能

3.依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象

4.接口隔离原则:不多干也不少干(包含合成聚合复用原则)

5.迪米特法则:一个对象应该对其他对象保持最少的了解(降低耦合)

6.开闭原则:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化

类的组成

/*包含成员函数和成员变量*/


只举几个特殊的例子来了解

成员变量

空类

类在实例化之后叫做一个实例.类是静态的,不占进程内存,而实例拥有动态内存.

现在考虑一个问题:如果空类的实例不占用内存,如何唯一的表示?上代码

#include <bits/stdc++.h>
using namespace std;

class a{};

int main()
{
a b;
cout<<"sizeof(a) ="<<sizeof(a)<<endl;
cout<<"sizeof(b) ="<<sizeof(b)<<endl;
return 0;
}


output:

sizeof(a) =1

sizeof(b) =1

可以看到,空类的实例大小不是0,而是1.这是编译器为空类隐式添加的一个字符.

static成员

#include <bits/stdc++.h>
using namespace std;

class a
{
public:
static int v;
};

int main()
{
a b;
cout<<"sizeof(a) ="<<sizeof(a)<<endl;
cout<<"sizeof(b) ="<<sizeof(b)<<endl;
cout<<"sizeof(a::v) ="<<sizeof(a::v)<<endl;
return 0;
}


output:

sizeof(a) =1

sizeof(b) =1

sizeof(a::v) =4

可以看到 static 的变量并没有出现在类或者实例中.那么他存在哪里?

静态数据成员被编辑器存放在全局数据区中,而不是栈中.

拓展:程序运行时的内存分配:
1. 栈区(stack)
由编译器自动分配释放,存放函数的参数值,局部变量的值等,内存的分配是连续的.当我们声明变量时,那么编译器会自动接着当前栈区的结尾来分配内存.
思考:怎么返回函数中的局部变量.
2. 堆区(heap)
一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统回收.在内存中的分布不是连续的,它们是不同区域的内存块通过指针链接起来的.一旦某一节点从链中断开,我们要人为的把所断开的节点从内存中释放.
拓展^2:垃圾回收机制
3. 全局区(静态区)(static)
全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域.程序结束后由系统释放
4. 文字常量区
常量字符串就是放在这里的.程序结束后由系统释放
思考:
`char s[]="zyk";char *s1="zyk";`
这两者到底有什么区别?
5. 程序代码区
存放函数体的二进制代码.


成员函数

我们将重点介绍虚函数和纯虚函数,首选(参考)[]/article/1422724.html]:

定义一个函数为虚函数,不代表函数为不被实现的函数。
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。
定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。


虚函数

声明定义时在前加 virtual 关键字的函数

参考此样例:

class A
{
public:
virtual void foo()
{
cout<<"A::foo() is called"<<endl;
}
};
class B:public A
{
public:
void foo()
{
cout<<"B::foo() is called"<<endl;
}
};
int main(void)
{
A *a = new B();
a->foo();   // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!
return 0;
}


这是 OOP 基本法中的多态.对于 类A 中的 函数foo ,在编译时间不能被确定他是指向 基类的foo 还是 子类的foo .

也就是说
虚就虚在所谓“推迟联编”或者“动态联编”上
.

纯虚函数

C++ 中引入纯虚函数是遵守 OOP基本法 的接口.首先含有纯虚函数的类称为
抽象类
,不能被实例化,也就是和 Java 中的接口类似.

编译器要求在派生类中必须予以重写.

定义方法
virtual ReturnType FunctionName() = 0;


虚函数表

虚函数表是为了让虚函数运行时能方便找到联编的位置而设置的. C++ standard 中并没有提到过, 所以每个编译器都可能不一样, 比如 gcc 和 微软编译器 都是将 vptr(虚函数指针,指向虚函数表) 放在对象内存布局最前面. 但是虚函数表的位置却放在不同位置上.

#include <bits/stdc++.h>
using namespace std;

class A
{
virtual void f1()
{
cout<<"af1"<<endl;
}
virtual void f2()
{
cout<<"af2"<<endl;
}
public:
int v;
};
class B
{
virtual void f1()
{
cout<<"bf1"<<endl;
}
virtual void f2()
{
cout<<"bf2"<<endl;
}
};

typedef void (*PTR)();

int main(void)
{
A a;
cout<<"sizeof(A)\t\t\t\t="<<sizeof(A)<<endl;
cout<<"sizeof(B)\t\t\t\t="<<sizeof(B)<<endl;
cout<<"&a\t\t\t\t\t="<<&a<<endl;
cout<<"*(int*)(&a) /*VFT的位置*/\t\t="<<(int*)(&a)<<endl;
cout<<"(int*)*(int*)(&a)/*第一个函数的位置*/\t="<<(int*)*(int*)(&a)<<endl;
PTR foo = (PTR)*((int*)*(int*)(&a));
foo();
foo = (PTR)*(((int*)*(int*)(&a))+1);
foo();
return 0;
}


output:

sizeof(A) =8

sizeof(B) =4

&a =0x68fee4

(int)(&a) /VFT的位置/ =0x68fee4

(int*)(int)(&a)/第一个函数的位置/ =0x473618

af1

af2

抽象类

抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层(联系:设计原则)。

抽象类的定义

带有纯虚函数的类为抽象类。

抽象类的作用:

抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。

特点

抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。

抽象类是不能定义对象的。

类的继承

声明:

class 派生类名:继承方式 基类名1, 继承方式 基类名2,...,继承方式 基类名n
{
派生类成员声明;
};


公有继承

当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可访问。即基类的公有成员和保护成员被继承到派生类中仍作为派生类的公有成员和保护成员。派生类的其他成员可以直接访问它们。无论派生类的成员还是派生类的对象都无法访问基类的私有成员。

私有继承

当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可访问。基类的公有成员和保护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但是在类外部通过派生类的对象无法访问。无论是派生类的成员还是通过派生类的对象,都无法访问从基类继承的私有成员。通过多次私有继承后,对于基类的成员都会成为不可访问。因此私有继承比较少用。

保护继承

保护继承中,基类的公有成员和私有成员都以保护成员的身份出现在派生类中,而基类的私有成员不可访问。派生类的其他成员可以直接访问从基类继承来的公有和保护成员,但是类外部通过派生类的对象无法访问它们,无论派生类的成员还是派生类的对象,都无法访问基类的私有成员。

构造函数和析构函数

派生类构造函数的语法:

派生类名::派生类名(参数总表):基类名1(参数表1),基类名(参数名2)....基类名n(参数名n),内嵌子对象1(参数表1),内嵌子对象2(参数表2)....内嵌子对象n(参数表n)
{
派生类新增成员的初始化语句;
}


构造函数的初始化顺序并不以上面的顺序进行,而是根据声明的顺序初始化。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: