编码运行环境:VS2017+Win32+Debug,Win32表示生成32bits的应用程序。
结构体(struct)与共用体(union)是 C 语言中就已经存在的数据类型,C++ 对他们进行了扩充,最大的变化是允许在结构和共用体中定义成员函数。下面将通过实例讲解二者的特性和用法。
1.struct
以下是一个使用了结构体的 C++ 程序。
#include <iostream>
using namespace std;
struct Room {
int floor;
int No;
};
struct Student {
int age;
int score;
Student(int a,int s){
age=a;
score=s;
}
};
int main(int argc,char* argv[]) {
Room r[3]={{1,101},{2,201},{3,301}};
Student s(18,89);
cout<<"the room are:";
cout<<r[0].floor<<"-"<<r[0].No<<" ";
cout<<r[1].floor<<"-"<<r[1].No<<" ";
cout<<r[2].floor<<"-"<<r[2].No<<endl;
cout<<"the student's age:"<<s.age<<" score:"<<s.score<<endl;
}
程序运行结果:
the room are:1-101 2-201 3-301
the student's age:18 score:89
阅读以上程序,在 C++ 中使用结构体需要注意以下几点:
(1)C++ 中,结构体是一种真正的数据类型,在利用结构定义变量时,不需要像在 C 中带上 struct 关键字,或先使用typedef struct structname structalias 的方式进行申明。
(2)C++ 对 C 中的 struct 进行了扩充,允许在 struct 中定义成员函数。struct 中的成员变量和成员函数也有访问权限,在 class 中,默认的访问权限是 private,而在 struct 中默认访问权限是 public,这是结构体和类的唯一区别。struct成员的默认访问权限设为 public 是 C++ 保持与 C 语言兼容而采取的一项策略。
(3)如果 struct 中没有显示定义任何构造函数,那么结构变量可以像在 C 语言中那样用花括号顺序指明数据成员的值来进行初始化。但是一旦显示定义了任何一个构造函数,就不能用这种方式初始化了。如果在 class 中只有若干 public 型的数据成员,而没有显示定义任何构造函数,也可以使用花括号进行初始化。
(4)用 sizeof 运算符计算结构的大小时,要考虑结构体内部变量的对齐问题。
2.union
共用体(union),又名联合体,是一种特殊的类,从 C 语言继承而来,其基本语义没有发生什么变化,只是具有了类的一些特性(允许定义成员函数)。在实际的编程实践中,使用频率没有 struct 高。与 struct 相比,最显著的区别是 union 的数据成员共享同一段内存,以达到节省空间的目的。
2.1 基本性质
通过如下程序考察 union 变量的占用空间,成员赋值时相互影响。
#include <iostream>
using namespace std;
union testunion {
char c;
int i;
};
int main(int argc,char* argv[]) {
cout<<sizeof(testunion)<<endl;
testunion* pt=new testunion;
char* p=reinterpret_cast<char*>(pt);
for(int i=0;i<sizeof(*pt);i++) {
cout<<int(p[i])<<" ";
}
cout<<endl;
cout<<pt->i<<endl;
pt->c='A';
cout<<pt->c<<endl;
for(int i=0;i<sizeof(*pt);i++) {
cout<<int(p[i])<<" ";
}
cout<<endl;
cout<<pt->i<<endl;
delete pt;
}
程序运行结果:
4
-51 -51 -51 -51
-842150451
A
65 -51 -51 -51
-842150591
可以看出,union testunion 变量的体积是 4,它是由两个数据成员中体积较大的一个(int)类型来决定的。对其中一个数据成员的修改,一定会同时改变所有其他数据成员的值。不过对体积较小的数据成员的修改,只会影响到该成员应该占用的那些字节,对超出部分(高位字节)没有什么影响。
2.2 高级特性
观察如下程序。
#include <iostream>
using namespace std;
struct Student {
int age;
int score;
Student(int a,int s) {
age=a;
score=s;
}
};
union testunion {
char c;
int i;
};
class someClass {
int num;
public:
void show(){cout<<num<<endl;}
};
union A {
char c;
int i;
double d;
someClass s;
};
union B {
char c;
int i;
double d;
B(){d=8.9;}
};
union {
char c;
int i;
double d;
void show(){cout<<c<<endl;}
} u = {'U'};
int main(int argc,char* argv[]) {
A a={'A'};
B b;
cout<<a.c<<endl;
cout<<b.d<<endl;
a.s.show();
u.show();
// 匿名共用体。
union {
int p;
int q;
};
p=3;
cout<<q<<endl;
}
程序运行结果:
A
8.9
65
U
3
阅读以上程序,需要注意以下几点:
(1)union 可以指定成员的访问权限,默认情况下,与 struct 具有一样的权限(public)。
(2)union 也可以定义成员函数,包括构造函数和析构函数。与struct不同的是,它不能作为基类被继承。
(3)union 不能拥有静态数据成员或引用成员,因为静态数据成员实际上并不是共用体的数据成员,它无法和共用体的其它数据成员共享空间。对于引用变量,引用本质上是一个指针常量,它的值一旦初始化就不允许修改。如果共用体有引用成员,那么共用体对象一创建初始化后就无法修改,只能作为一个普通的引用使用,这就失去了共用体存在的意义。
(4)union 允许其他类的对象成为自己的数据成员,但是要求该类对象所属类不能定义 constructor,copy constructor,destructor,assignment operator,virtual function 中的任意一个。因为:
- union 数据成员共享内存,union构造函数在执行的时候,不能调用数据成员为类对象的构造函数,否则就改变了其他数据成员的值。
- 同样,union 的对象成员的析构函数也不能被调用,因为其他数据成员的值对于对象成员而言可能毫无意义。
- union 的对象成员的赋值应该维持其原始语义,不建议进行赋值运算符的重载,因为赋值运算符重载一般用于“深拷贝”等场合,而在对象空间与其它变量共享的情况下,“深拷贝”引入的内存资源,指向内存资源的指针往往会被其它共用体数据成员修改,导致内存资源无法寻址,造成内存泄漏。此外,因为 union 的对象成员没有自定义的析构函数,也会导致内存泄漏。
- 拥有虚函数的类对象,虚函数表指针可能会在共用体对象初始化时被覆盖,导致无法寻址虚函数表,所以也不能拥有虚函数。
(5)如果 union 类型旨在定义该类的同时使用一次,以后不再使用了,那么也可以不给出 union 的名称。如上例中变量 u 就是这种情况。这种情况下,无法为该 union 定义构造函数。
(6)匿名共用体(Anonymous Union),也就是给出一个不带名称的共用体的申明后,并不定义任何该union的变量,而是直接以分号结尾。严格来说,匿名共用体并不是一种数据结构,因为它不能用来定义共用体对象,它只是指明若干个变量共享一片内存单元。在上例中,对变量 p 的修改实际上修改了变量 q。可以看出,尽管匿名共用体中的变量被定义在同一个共用体中,他们与同一个程序块的任何其他局部变量具有相同的作用域级别。这意味着匿名共用体内的成员的名称不能与同一个作用域内的其它标识符相冲突。另外,对匿名共用体还存在如下限制:
- 匿名共用体不允许有成员函数;
- 匿名共用体也不能包含私有或者保护成员;
- 全局匿名共用体中的成员必须是全局或静态变量。
参考文献
C++高级进阶教程.陈刚.武汉大学出版社