2014.3.11
分析offviewer时,有一些问题,很基础的,但是忘记了,发现问题那就快点搞定它
以下内容参考自百度百科:
(2)struct 结构体有点忘记了,要复习一下
定义一个结构的一般形式为:
struct 结构名
{
成员表列
};
成员表由若干个成员组成, 每个成员都是该结构的一个组成部分。对每个成员也必须作类型说明,其形式为:
类型说明符 成员名;
例如:
struct stu
{
int num;
char name[20];
char sex;
float score;
} ;
在这个结构定义中,结构名为stu,该结构由4个成员组成。 第一个成员为num,整型变量;第二个成员为name,字符型数组;第三个成员为sex,字符型变量;第四个成员为score,浮点型变量。 应注意在括号后的分号是必不可少的。
在C++中,struct的功能得到了强化,struct不仅可以添加成员变量,还可以添加成员函数,和class类似
结构定义之后,即可进行变量说明。 凡说明为结构stu的变量都由上述4个成员组成。由此可见, 结构是一种复杂的数据类型,是数目固定,类型不同的若干有序变量的集合。
请看下面的结构:
struct MyStruct
{
double dda1;
char dda;
int type;
};
对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求:
sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13
但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。你知道为什么在VC中会得出这样一个结果吗?
其实,这是VC对
变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些
变量的起始地址做了“
对齐”处理。在默认情况下,VC规定各
成员变量存放的起始地址相对于结构的起始地址的
偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)。
类型
对齐方式(
变量存放的起始地址相对于结构的起始地址的
偏移量)
Char
int
float
double
偏移量必须为sizeof(double)即8的倍数
Short
偏移量必须为sizeof(short)即2的倍数
默认的对齐方式
各
成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的
字节VC会自动填充。同时VC为了确保结构的大小为结构的
字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个
成员变量申请空间后,还会根据需要自动填充空缺的字节。
下面用前面的例子来说明VC到底怎么样来存放结构的。
struct MyStruct
{
double dda1;
char dda;
int type;
};
为上面的结构分配空间的时候,VC根据
成员变量出现的顺序和对齐方式,先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好
偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的
偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用sizeof(char)=1个字节;接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是sizeof(int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的地址对于结构的起始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把type存放在偏移量为12的地方,该成员变量占用sizeof(int)=4个字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以没有空缺的字节需要填充。所以整个结构的大小为:sizeof(MyStruct)=8+1+3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。
下面再举个例子,交换一下上面的MyStruct的
成员变量的位置,使它变成下面的情况:
struct MyStruct
{
char dda;
double dda1;
int type;
};
这个结构占用的空间为多大呢?在VC6.0环境下,可以得到sizeof(MyStruct)为24。结合上面提到的分配空间的一些原则,分析下VC怎么样为上面的结构分配空间的。(简单说明)
struct MyStruct
{
char dda;//
偏移量为0,满足对齐方式,dda占用1个字节;
double dda1;//下一个可用的地址的
偏移量为1,不是sizeof(double)=8
//的倍数,需要补足7个字节才能使
偏移量变为8(满足对齐
//方式),因此VC自动填充7个字节,dda1存放在
偏移量为8
//的地址上,它占用8个字节。
int type;//下一个可用的地址的
偏移量为16,是sizeof(int)=4的倍
//数,满足int的对齐方式,所以不需要VC自动填充,type存
};//所有
成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构
//的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof
//(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为
//sizeof(double)=8的倍数。
所以该结构总的大小为:sizeof(MyStruct)为1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。
字节的对齐方式
VC对结构的存储的特殊处理确实提高了CPU存储
变量的速度,但有时也会带来一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。
VC中提供了
#pragmapack(n)来设定
变量以n
字节对齐方式。n
字节对齐就是说
变量存放的起始地址的
偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有
成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;
否则必须为n的倍数。下面举例说明其用法。
struct test
{
char m1;
double m4;
int m3;
};
以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4
字节对齐),m1占用1个字节。接着开始为m4分配空间,这时其
偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其
偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有
成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的
#pragmapack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。(请读者自己分析)
(一)基本概念
结构体是一个或是多个变量的集合,这些变量可能为不同的类型,为了处理的方便而将这些变量组合在一个名字之下。我们将关键字struct引入了结构声明中。结构声明包含在花括号内的一系列声明组成。例如:
// 以下是一个巨普通的结构体,表示坐标。
// point 为结构标记;x, y为成员。
struct point
{
int x;
int y;
};
// 以下是跟变量表的情形
struct point
{
int x;
int y;
}px, py, pz;
(二)使用方法
1. 初始化方法
(1) 结构体变量的初始化方式分别给结构体的成员变量以初始值.
具体的形式如下:
struct 结构体标识符
{
成员变量列表;
…
};
第一种初始化:
struct 结构体标识符 变量名 = {初始化值1,初始化值2,…, 初始化值n };
struct point impl = {100, 200};
第二种初始化:
struct 结构体标识符 变量名;
变量名.成员 = 值;
struct point impl;
impl.x = 100;
impl.y = 200;
第三种初始化方法:
struct 结构体标识符
{
成员变量列表;
…
}变量名 = {初始化值1,初始化值2,…, 初始化值n };
struct point
{
int x;
int y;
}impl = {100, 200};
对于结构体内还有结构体的形式,可以用以下的方式来解决:
struct rect
{
struct point pt1;
struct point pt2;
};
第一种初始化方法:
struct rect rect_impl;
rect_impl.pt1.x = 100;
第二种初始化方法:
struct rect
{
struct point pt1;
struct point pt2;
}rect_impl = {
{100, 200},
{300, 400}
};
2. 结构体与指针
结构与指针其实密不可分,在函数的返回和调用中,我们不能用复制整个结构体的方式,因为这样的效率实在是太低了。谁都不希望将一个大象抗在身上,然后到处跑!
这个时候,指针出现了。就像救世主出现在人间,赶走了大象,留下了羽毛。
以下是实现形式:
struct point *ppt, origin;
origin.x = 100;
origin.y = 200;
ppt = &origin; // 将origin的地址传给ppt
// 以下是结构体指针的两种调用形式
std::cout << (*ppt).x << std::endl; // 100
std::cout << ppt->x << std::endl; // 100
// 对于函数的调用
struct point *func(struct point *, int x){}
3. 结构体与数组,链表
如果说当个结构体个体只是一块板砖的话,那么与数组结合,就是一堆板砖啊!
我们常使用数组来储存一些东西,这些东东看重的数组的线性结构。当然,如果结构体内部有结构体指针,那么我们可以来组链表。线性结构的关键在于查找时的时间损耗,O(1)是许多人追求的梦想。而链表的容量巨大,只要你内存够大,它就能让你欲罢不能
!鱼和熊掌不能兼得,O(n)时间的损耗是你要权衡的。
以下是数组
实现形式:
// 结构体与数组
// 第一种实现形式:
struct point
{
int x;
int y;
}pta[] = {
2, 2,
3, 3,
4, 3
};
// 第二种实现形式:
struct point
{
int x;
int y;
};
struct point pta[10]; // 定义了10个point
pta[1].x = 100;
以下是链表
的形式:
// 二叉树的链式结构
typedef struct tNode
{
int Elem;
struct tNode *LChild;
struct tNode *RChild;
}*BiTree;
// 构造二叉树的方法
// 构造一棵空的二叉树
void InitBiTree(BiTree &BIT)
{
if (!(BIT = (BiTree)malloc(sizeof(tNode)))){exit(1);}
BIT->LChild = NULL; // 左子树为空
BIT->RChild = NULL; // 右子树为空
}
4. 结构体与typedef
typedef用来建立新的数据类型名。以下用代码来解释:
// 二叉树的链式结构
typedef struct tNode
{
Data_Type_BiTR Elem;
struct tNode *LChild;
struct tNode *RChild;
}*BiTree;
// 也可用下面的方式
struct tNode
{
Data_Type_BiTR Elem;
struct tNode *LChild;
struct tNode *RChild;
};
typedef struct tNode *BiTree;
// 以上两种效果都一样
其实,typedef没有创建新的类型。只是增加了一个新的名词而已。这样做的好处有倆
:首先呢,程序参数化,提高移植性。其次,就是好改啦,改一个地方就可以影响所有的地方了。