随笔
不知道是什么啊,看到就记录下来了:)
C++ 中的 struct 对 C 中的 struct 进行了扩充,它已经不再只是一个包含不同数据类型的数据结构了,它已经获取了太多的功能。
struct 能包含成员函数吗? 能!
struct 能继承吗? 能!!
struct 能实现多态吗? 能!!!
既然这些它都能实现,那它和 class 还能有什么区别?
最本质的一个区别就是默认的访问控制,体现在两个方面:
1)默认的继承访问权限。struct是public的,class是private的。
你可以写如下的代码:
struct A
{
char a;
};
struct B : A
{
char b;
};
这个时候 B 是 public 继承 A 的。
如果都将上面的 struct 改成 class,那么 B 是 private 继承 A 的。这就是默认的继承访问权限。
所以我们在平时写类继承的时候,通常会这样写:
struct B : public A
就是为了指明是 public 继承,而不是用默认的 private 继承。
当然,到底默认是 public 继承还是 private 继承,取决于子类而不是基类。
我的意思是,struct 可以继承 class,同样 class 也可以继承 struct,那么默认的继承访问权限是看子类到底是用的 struct 还是 class。如下:
struct A{};
class B : A{}; //private继承
struct C : B{}; //public继承
2)struct 作为数据结构的实现体,它默认的数据访问控制是 public 的,而 class 作为对象的实现体,它默认的成员变量访问控制是 private 的。
注意我上面的用词,我依旧强调 struct 是一种数据结构的实现体,虽然它是可以像 class 一样的用。我依旧将 struct 里的变量叫数据,class 内的变量叫成员,虽然它们并无区别。
其实,到底是用 struct 还是 class,完全看个人的喜好,你可以将你程序里所有的 class 全部替换成 struct,它依旧可以很正常的运行。但我给出的最好建议,还是:当你觉得你要做的更像是一种数据结构的话,那么用 struct,如果你要做的更像是一种对象的话,那么用 class。
当然,我在这里还要强调一点的就是,对于访问控制,应该在程序里明确的指出,而不是依靠默认,这是一个良好的习惯,也让你的代码更具可读性。
说到这里,很多了解的人或许都认为这个话题可以结束了,因为他们知道 struct 和 class 的“唯一”区别就是访问控制。很多文献上也确实只提到这一个区别。
但我上面却没有用“唯一”,而是说的“最本质”,那是因为,它们确实还有另一个区别,虽然那个区别我们平时可能很少涉及。那就是:“class” 这个关键字还用于定义模板参数,就像 “typename”。但关键字 “struct” 不用于定义模板参数。这一点在 Stanley B.Lippman 写的 Inside the C++ Object Model 有过说明。
问题讨论到这里,基本上应该可以结束了。但有人曾说过,他还发现过其他的“区别”,那么,让我们来看看,这到底是不是又一个区别。还是上面所说的,C++ 中的 struct 是对 C 中的 struct 的扩充,既然是扩充,那么它就要兼容过去 C 中 struct 应有的所有特性。例如你可以这样写:
struct A //定义一个struct
{
char c1;
int n2;
double db3;
};
A a={‘p’,7,3.1415926}; //定义时直接赋值
也就是说 struct 可以在定义的时候用 {} 赋初值。那么问题来了,class 行不行呢?将上面的 struct 改成 class,试试看。报错!噢~于是那人跳出来说,他又找到了一个区别。我们仔细看看,这真的又是一个区别吗?
你试着向上面的 struct 中加入一个构造函数(或虚函数),你会发现什么?
对,struct 也不能用 {} 赋初值了。
的确,以 {} 的方式来赋初值,只是用一个初始化列表来对数据进行按顺序的初始化,如上面如果写成 A a={‘p’,7}; 则 c1,n2 被初始化,而 db3 没有。这样简单的 copy 操作,只能发生在简单的数据结构上,而不应该放在对象上。加入一个构造函数或是一个虚函数会使 struct 更体现出一种对象的特性,而使此{}操作不再有效。
事实上,是因为加入这样的函数,使得类的内部结构发生了变化。而加入一个普通的成员函数呢?你会发现{}依旧可用。其实你可以将普通的函数理解成对数据结构的一种算法,这并不打破它数据结构的特性。
那么,看到这里,我们发现即使是 struct 想用 {} 来赋初值,它也必须满足很多的约束条件,这些条件实际上就是让 struct 更体现出一种数据机构而不是类的特性。
那为什么我们在上面仅仅将 struct 改成 class,{} 就不能用了呢?
其实问题恰巧是我们之前所讲的——访问控制!你看看,我们忘记了什么?对,将 struct 改成 class 的时候,访问控制由 public 变为 private 了,那当然就不能用 {} 来赋初值了。加上一个 public,你会发现,class 也是能用 {} 的,和 struct 毫无区别!!!
做个总结,从上面的区别,我们可以看出,struct 更适合看成是一个数据结构的实现体,class 更适合看成是一个对象的实现体。
在类的外面,其实也可以用指针访问类内部的私有成员,例如:
#include
using namespace std;
class a // 定义了类a
{
long a0; // 定义私有成员 a0
public:
a(long b)
{
a0=b;
}
void geta()
{
cout<<a0<<endl;
}
};
int main()
{
a b(5); // 定义对象b,并给 b 中的 a0 赋初值
long p;
p=(long)&b; // 令指针 p 指向 b 中前 4 个字节,在这里相当于指向 a0
b.geta(); // 用内部函数访问 a0
cout<<*p<<endl; // 在外部直接访问 a0
*p=8; // 在外部改变 a0 的值
b.geta(); // 输出改变后的结果
cout<<*p<<endl;
return 0;
}
需要注意的是,使用这种方法虽然可以用于基于类的多态原则的一些程序开发,但违反了类的封装原则,在使用指针的类中也极不安全,所以不建议使用。
#include
using namespace std;
class Box{
public:
Box(){;}
~Box(){;}
Box* get_address() //得到this的地址
{
return this;
}
};
int main(){
Box box1;
Box box2;
// Box* 定义指针p接受对象box的get_address()成员函数的返回值,并打印
Box* p = box1.get_address();
cout << p << endl;
p = box2.get_address();
cout << p << endl;
return 0;
}
this 指针的类型可理解为 Box*。
此时得到两个地址分别为 box1 和 box2 对象的地址。
深夜,红色方块砌成的古怪建筑发出卡哒卡哒的物体碰撞声,先是一只手,然后是一整只手臂,最后是一颗前额饱满但有些秃顶的脑袋探了出来,与一旁的列宁半身像神似。列宁手脚并用地爬下了透明的棺材似的盒子,他似乎手脚不是很利索,习惯性地用手捂着心口。“哦,我得快些去见约瑟夫同志”,他小声念叨着偷偷摸摸地溜出了这座陵墓般的怪异的红色建筑。
很快,他走到了一旁的摆放着斯大林半身像的坟墓前。“嘿,约瑟夫,你在吗?我有些事想和你说!”列宁边说边敲了敲斯大林的墓碑,许久都没有回应。列宁右手撑着腰似乎有些不耐烦了,于是敲得更重了些,“嘿,同志,约瑟夫同志,我必须要和你谈谈!”说着他用力地把坟墓的顶盖推开了,这时只听见嘎吱一声,里面棺材被一下子推开了,一只宽大的手伴随着浑厚但却像是刚睡醒的嗓音从里面传了出来“哦,是伊里奇,有什么事儿吗?”
“我做了一个梦,我知道这是梦,梦不是现实对吧?我是说,我做了一个糟糕的梦?”
“你梦见什么了,伊里奇同志?”
“苏维埃不见了,我是说在梦里。它消失了,我梦见军阀和买办又控制了我们的祖国,我看见无产者被他们踩在脚下。我看见…”
“好了,我亲爱的伊里奇同志。这,显然是不可能的,赫鲁晓夫会做得很好的,这只是梦,你懂吗?这只是个梦,你现在需要做的就是回去躺下,休息。”
“约瑟夫,那边的旗帜上印的不是锤子和镰刀,甚至不是红色的背景!”
“……”
赫鲁晓夫站在他的半身像前,有些局促不安。他的面前站着斯大林和列宁。
“这是怎么回事,同志?”斯大林问道,哪怕在晚上也能看出来他的脸色很阴沉。
“我不道啊?”
列宁在一旁,仰着头,手又习惯性地撑着后腰。他伸手拉住斯大林和赫鲁晓夫,说到“哦,我明白了,我完全明白了,斯大林同志,恐怕,我们又得开始工作了。”

