结构体到底是什么呀?!
我们来思考一个问题,如果我们要保存一个年级所有学生的信息,我们该如何去做呢!我们稍微回忆下以前我学过的知识,大概能想到用数组去做,因为学号啊,姓名啊,成绩啊,都是同类元素的集合,当然用数组了,如果用单个变量真的会累死人呀!但是再进一步思考发觉,学生的信息还是有不少东西的,比如学号,姓名,各科成绩,电话,家庭住址,如果单单用数组,是不是得好多好多数组呀!而且这样呢,管理起来也非常不方便啊,那怎么办啊。其实呢!在高级语言中,有一种类型,就是对基本类型进行重定义。把多个数据类型重新定义成一个新类型。就好比一栋房子,里面有厕所啊,厨房,厨房里还有几颗白菜。如果我们想要表示你有一栋房子,你可以把房子里面的东西全部都说一遍,你还可以把这栋房子就用一栋房子去表示。厨房里的几颗白菜你可以想成放在数组里的,因为几颗白菜是同类元素。一栋房子就相当于我们现在要学习的结构体。各种类型集合成一起,呵呵,这样的话,你就可以用结构体去表示复杂的数据结构啦!
结构体成员变量的访问的思考
我们再思考一个问题,结构体是多种不同的数据类型的集合,所以每个元素的大小都是不一样的,那么如何访问结构体中的成员变量呢!要是数组的话就好办,因为是同类啊,就以前学习的数组寻址公式一下子就访问到了(很多人质疑那个寻址公式,这个质疑很正常,因为你还没有理解)。这确实是一个问题呀!不过,等会应该就会有答案的!
结构体的定义
结构体定义的关键字是 struct. 表示一个信息结构、后面跟着的是结构体的名称,你定义成一个新类型了,得起个名字呀!就好比,你现在造出了1个天使,天使由哪些构成呢,有天使的翅膀,还有像人一样的天使,还有白色的耀眼的裙子。还有天使的红色高跟鞋,还有,天使也许也有编号的吧,不只它1个天使,呵呵!还有天使的名字。现在我们先来造人,呵呵!等以后厉害了再造天使:
造人:
struct Person { char name[64];//定长的名字,如果这里给的类型是char * 就可以不定长了。但是用char *寻址要寻2次。用定长的话一次就到啦! char sex; //性别 int age; //int的age 应该够啦。活得够大啦! float height; };
大家仔细回忆QQ的年龄设置,是不是每一年都得手动去改名字啊,QQ不会自动给你改。这样是不是不太好啊因为每年都要去改年龄!我们是不是应该分别定义年月日啊,每次用当前年月日减去它的初始年月日,这样的才好的啊,因为这样无论在哪1年都知道一个人多大了的啊。比如,现在我们做的学生信息管理系统,要是每年都去改年龄,是不是设计的不太科学啊,所以,我们这里要改1下,但是QQ比较是人才济济的,他们不会傻到这种地步啊,那么它们为什么那这样的做呢,我想主要是考虑到大量算的压力,因为几亿QQ使用者一上线就要请求服务器做下减法,呵呵,这样服务器压力就大啊,但是也可以放在客服端做的呀,那为什么腾讯没有这样做呢!不知道了,慢慢地,努力的学,有一天会知道的。
结构体和INT char其实是同一级别的
我们来看这个例子:
{
char szName[64];
int nYear;
int nMonth;
int nDay;
};
注意1个问题啊!现在我们只是定义了一种数据类型,这里,我们还没有产生变量哦!它其实和int char 是一样的哦.只不过这里的结构体是一个符合类型。也就是这里没有分配内存的哦!比如我们来看下面的错误程序:
正确的做法是先要声明这种类型的变量,其实和int char声明变量是一样的。例如:
{
char szName[64];
int nYear;
int nMonth;
int nDay;
float fScore[3];
};
int main()
{
stStudent.szName[0] = ‘J’;//这样是错误的哦,这里就好比 int = 123;相当于是对类型在赋值。
}
{
char szName[64];
int nYear;
int nMonth;
int nDay;
float fScore[3];
};
int main()
{
stStudent stu;
stu.szName[0] = ‘J’;
}
结构体变量的初始化
stStudent stu = {
"beyond",
1989,
2,
11,
{
99.9
87.3
100
}
}
其实呢,还可以不要中间嵌套的那个括号,为什么呢,我们先来看看内存:
哈哈!0012fef0,CCCCCCCCCCCC!!!为什么会这样呢!嘿嘿!我们当然知道啦,通过以前的学习,我已经条件反射啦!全C是因为是局部变量,就算不看全C,0012fef0也可以知道这是1个局部变量,因为这个地址是栈地址呀!然后我们单步下去,内存地址就依次被赋值啦!所以呢!那个括号是可以不用给的,因为是连续依次排列的啊,但是鉴于代码的清晰,最好还是给括号啦!
唉!刚才我的szName给得太大啦,调试的时候不好截图呀!下面的代码,我把它修改下,修改为16好了。下面我们再来看1个问题,代码修改如下:
那么现在我们思考一个问题,这个结构体一共占多大的内存呢,我们先来算算:这样吧!我们用printf把它给显示出来(偷偷懒!呵呵!)首先我们先看看没有char Sex的时候的大小:
程序打印出40.为什么是40呢?!因为16+4+4+4+12=40呀!
那么现在我们加上这个Sex后,会打印什么结果呢!你可能想啊,char啊!当时是1的啊,40+1=41啊!嗯,我觉得也是这样的,那么我们去看看结果!!!
郁闷呀!结果竟然是44.哦!惊奇啊!怎么会这样呢!其实啊!这里的这个问题到底会打印多少还得看编译环境,到底看哪个编译环境呢,看这个:
哈哈!大家看到了吗?!这里有个结构体成员对齐呀!哦,原来是这样啊!那么我们先来选择1字节对齐再来看打印出什么结果,哇!打印出来了41了呢!其实!VC6.0默认是模4字节对齐的!刚才打印出41就是用的模1地址对齐,也就是不对齐啦!这个真好玩,原来还可以自己去安排一些规则的呀!
VC关于对齐的预编译指令
#pragma pack(n)
#pragma pack(push | pop)
其实这些指令就是微控对齐的控制。具体做法非常简单,MSDN上有详细解释,这里就不示范啦!
为什么要学习地址对齐呢
因为后面用结构体的时候,大多数时候要用到数据包的发送和解析。数据包是有一定格式的,这时候就需要考虑是否考虑对齐问题。否则对齐错了,解析出来的东西就全乱啦!所以一定要注意这点的理解和学习哦!
结构体的嵌套极其注意问题
前面我们的程序,关于日期的定义,我们是不是可以定义一个stDate数据类型啊,这么看起来好像更好看点啊!例如:
struct stDate
{
int nYear;
int nMonth;
int nDay;
};
struct stStudent
{
char szName[64];
struct stDate WoW;
float fScore[3];
};
int main()
{
struct stStudent stu={
"dodolook",
{
1981,
7,
7},
{
100,
99,
60
}
};
return0;
}
嘿嘿!挺好玩吧!嵌套的时候一定要注意1个原则,无论如何去嵌套!只要能sizeof求出大小的,随便你怎么去嵌套,不能sizeof求出大小的就不能嵌套!原因是如果不能sizeof求出大小的结构体,编译器无法给其大小!
现在我们再思考一个问题!如果要把结构体初始化为全0,怎么办呢!其实很简单了,就是给结构体1个0就可以了啊。注意不要什么都不给哦,什么都不给的时候,是一个不确定的值。