好吧,这篇文章主要讲一下 C++中 struct 和 class 之间的情感纠葛
什么时候应该使用struct取代class?
答案是: 当你觉得开心的时候….
好吧, 看到这个答案各位看官肯定心里不由自主的来了一句 “MMP,你TM确定不是在逗我?”
诶~ 诶~ 诶~ 诶~ ,别打我~ 别打我~ ,先停一下。
虽然这个答案并没有任何技术水平,不过这的确指出了一个重要的特性 :关键词 struct 本身并不一定象征其后声明的任何东西。
我们完全可以使用struct 来替代class,struct 任然可以声明 public 、protect、private等等存取级别与一个完全 public 的接口,以及 virtual function 和单一继承,多重继承,虚拟继承…
不过大多数教科书中说到 struct 的时候,大多数意思都是指一个数据集合体,没有 private data ,也没有相应的操作(member function)。
也就是纯 C 的用法。这种用法应该跟 C++ 的 自定义类型(user_defined_type)用法区别开来。
在C所支持的 struct 和 C++所支持的 class 之间,有一个观念上的重要差异。
我的重点很简单:关键词本身并不提供这种差异。也就是说,下面这个段代码,可以说 “卧槽,这是一个 class” ,也可以说 “哎哟,这是一个struct”。
//struct 名称(或class 名称)暂时省略
{
public:
operator int();
virtual void foo();
//···
protected:
static int object_count;
//mumble
}
事实上你可以说他是个struct,也可以说他是个class。这两种声明观念上的意义取决对“声明”本身(declarationbody)的检验(又是一句废话~ ~ ~ ~ ~)。
再来看一个例子:
#include <iostream>
using namespace std;
struct A;
class B
{
public:
B();
~B();
A *a;
void Test();
};
class A
{
int x;
public:
A(){};
~A(){};
int y;
};
B::B()
{
a = new A();
}
B::~B()
{
delete a;
}
void B::Test()
{
cout << a->y << endl;
}
大家不妨猜猜看这个代码会不会有问题?
大家会很奇怪,为什么声明使用的是class 前置声明却使用的是struct 而且还不会有问题…
事实上这在C++中是允许的,完全能成功编译,只不过编译器会报一个不一致的警告罢了:
warning C4099: “A”: 类型名称以前使用“struct”现在使用的是“class”
以上为Visual Studio 2017 测试结果↑
真正的问题并不在于所有“用户自定义类型”的声明是否必须使用相同的关键字,而在于使用class或struct关键字是否可以给予“类型的内部声明”以某种承诺。
也就是说,如果struct关键字的使用实现了C的数据抽象观念,而class关键字实现的是C++的ADT(Abstract Data Type)观念,那么当然“不一致”是一种错误的语法,就好像接下来你将看到的错误,一个object被矛盾的声明为static和extern:
//不合法? 是的
//以下两个声明造成矛盾的存储空间
static int foo;
·······
extern int foo;
这组声明对于foo的存储空间造成矛盾。但是struct 和 class 这两个关键词并不会造成这样的矛盾。一个类型是struct还是class是由声明本身(declarationbody)决定的,“一致性的用法”只不过是一种风格上的问题而已。
那么再看一种情况:
template <struct T>
struct MyStruct
{
T x;
};
这样子合法?
答案肯定是否。
要这样子:
template <class T>
struct MyStruct
{
T x;
};
和这样子:
template <typename T>
struct MyStruct
{
T x;
};
才是OK的。
好奇的你一定在问为什么呢,不是我开心就好,难道不是我想怎么用就怎么用?
老铁,这次真的不是你开心就好了。因为C++保留struct本身就是为了与C 兼容,而C中并没有 templates 这个东西,那么也就没有必要在模板中支持strcut声明模板形参。
看到现在大家是不是觉得C++怎么这么多事,如果这个语言只支持一个关键字,那得省掉多少混淆和迷惑啊。
But body you need to know ,如果C++需要支持现存的C程序代码,它就不能不支持struct 。
好的,找茬的又来了。那你就不能不引入class 而只使用 struct ?
额 这个 我也不知道搞出C++的那群人怎么想的 。 而事实上引入它的确非常的令人满意嘛(are you kidding me?),因为这个语言引入的不只是关键字,还有它所支持的封装和继承的则哲学。
啊,苍天啊,估计又让你回忆起了 C++ 老师在你耳边大声讲的万物皆类的思想…
估计大家还想起一个问题:柔性数组
这是C程序员的一种神奇技巧,在C中把一个元素个数为0(当然你想设成,1、2、3····也不是不可以,只不过需要自己代码中使用的时候相应的改变一下)的数组放在struct的末尾(只能是末尾)。
例如下面的代码就实现了一个大小可变的数组:
#include<stdio.h>
#include<malloc.h>
typedef struct _SoftArray{
int len;
int array[];
}SoftArray;
int main()
{
int len = 10;
SoftArray *p=(SoftArray*)malloc(sizeof(SoftArray) + sizeof(int)*len);
//······
free(p);
return 0;
}
但是如果我们改用class来声明,而该class具有一下条件之N:
- 指定多个access section,内含数据
- 从另一个class派生而来
- 定义了一个以上virtual functions
那么或许可以顺利转化,或许不行。
原因如下:
- C++中凡是处于同一个access section 的数据,必定保证以其声明的顺序出现在内存布局之中,然而被放置在多个access section 中的数据,排列顺序就不一定了。所以以下代码只有当按照public,protect,private的顺序存放数据的时候才能使用柔性数组(But 这一点我们是不能保证的,C++标准中没有强制规定,即按各厂商自己的实现方式来)
class Test
{
public:
//·····
protect:
//·····
private
//·····
char array[];
}
- 同理,base class 和 dived class 的data members的布局也未规定谁先谁后,因而也不能保证柔性数组一定有效。
- virtual function的存在也使柔性数组是否可行成为一个问号。
所以最好的忠告就是,不要在C++风格的class/struct中(当然C风格的struct是完全没问题的)使用柔性数组,除非你真的很清楚你所使用的编译器会怎么实现。
总结一下:C++中struct跟class的区别主要有以下几点。
- struct 中默认访问权限是 public的,而class 是private的。
- 模板声明不能使用struct
- 柔性数组C风格struct 支持, C++风格是否支持随编译器实现而定
到底是用struct还是class,完全看个人的喜好,你可以将程序里所有的class全部替换成struct,它依旧可以很正常的运行。
但我给出的最好建议,还是:当你觉得你要做的更像是一种数据结构的话,那么用struct,如果你要做的更像是一种对象的话,那么用class。
当然,我在这里还要强调一点的就是,对于访问控制,应该在程序里明确的指出,而不是依靠默认,这是一个良好的习惯,也让你的代码更具可读性。