C语言中结构体(struct)的详细分解与使用(上)https://developer.aliyun.com/article/1389316
第五:对于结构体变量的初始化
先回忆一下关于基本数据类型和数组类型的初始化:
int a = 0; int array[4] = {1,2,3,4};//每个元素用逗号隔开
回忆一下数组初始化问题:
再回到结构体变量的初始化吧
关于结构体变量的初始化与初始化数组类似;
也是使用花括号括起来,用逗号分隔的初始化好项目列表。注意,每个初始化项目必须要和要初始化的结构体成员类型相匹配。
struct book s1= {//对结构体初始化 "yuwen", //title为字符串 "guojiajiaoyun", //author为字符数组 22.5 //value为flaot型 }; //要对应起来,用逗号分隔开来,与数组初始化一样;
加入一点小知识,关于结构体初始化和存储类时期的问题:如果要初始化一个具有静态存储时期的结构体,初始化项目列表中的值必须是常量表达式;
注意,如果在定义结构体变量的时候没有初始化,那么后面就不能全部一起初始化了;意思就是:
/这样是可以的,在定义变量的时候就初始化了; struct book s1= {//对结构体初始化 "guojiajiaoyun",//author为字符数组 "yuwen",//title为字符串 22.5 }; /这种就不行了,在定义变量之后,若再要对变量的成员赋值,那么只能单个赋值了; struct book s1; s1={ "guojiajiaoyun",//author为字符数组 "yuwen",//title为字符串 22.5 };//这样就是不行的,只能在定义的时候初始化才能全部赋值,之后就不能再全体赋值了,只能单个赋值; 只能: s1.title = "yuwen";........//单个赋值;
对于结构体的指定初始化:
访问结构体成员
结构体就像一个超级数组,在这个超级数组内,一个元素可以是char类型,下个元素就可以是flaot类型,再下个还可以是int数组型,这些都是存在的。
在数组里面我们通过下标可以访问一个数组的各个元素,那么如何访问结构体中的各个成员呢?
用结构成员运算符点(.)就可以了;
结构体变量名.成员名;
注意,点其结合性是自左至右的,它在所有的运算符中优先级是最高的;
例如,s1.title指的就是s1的title部分;s1.author指的就是s1的author部分;s1.value指的就是s1的value部分。
然后就可以像字符数组那样使用s1.title,像使用float数据类型一样使用s1.value;
注意,s1 虽然是个结构体,但是 s1.value 却是 float 型的。
因此 s1.value 就相当于 float 类型的变量名一样,按照 float 类型来使用;
例如:
printf(“%s\n%s\n%f”,s1.title,s1.author,s1.value); //访问结构体变量元素
注意 scanf(“%d”,&s1.value); 这语句存在两个运算符,&和结构成员运算符点。
按照道理我们应该将(s1.value括起来,因为他们是整体,表示s1的value部分)但是我们不括起来也是一样的,因为点的优先级要高于&。
如果其成员本身又是一种结构体类型,那么可以通过若干个成员运算符,一级一级的找到最低一级成员再对其进行操作;
结构体变量名.成员.子成员………最低一级子成员;
struct date { int year; int month; int day; }; struct student { char name[10]; struct date birthday; }student1; //若想引用student的出生年月日,可表示为;student.brithday.year; brithday是student的成员;year是brithday的成员;
第六:整体与分开
可以将一个结构体变量作为一个整体赋值给另一相同类型的结构体变量,可以到达整体赋值的效果;这个成员变量的值都将全部整体赋值给另外一个变量;
不能将一个结构体变量作为一个整体进行输入和输出;在输入输出结构体数据时,必须分别指明结构体变量的各成员;
小结:除去“相同类型的结构体变量可以相互整体赋值”外,其他情况下,不能整体引用,只能对各个成员分别引用;
第七:结构体长度
数据类型的字节数:
16位编译器
char :1个字节
char*(即指针变量): 2个字节
short int : 2个字节
int: 2个字节
unsigned int : 2个字节
float: 4个字节
double: 8个字节
long: 4个字节
long long: 8个字节
unsigned long: 4个字节
32位编译器
char :1个字节
char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 4个字节long long: 8个字节
unsigned long: 4个字节
那么,下面这个结构体类型占几个字节呢?
typedef struct { char addr; char name; int id; }PERSON;
通过printf("PERSON长度=%d字节\n",sizeof(PERSON));可以看到结果:
结构体字节对齐
通过下面的方式,可以清楚知道为什么是8字节。
1、定义20个char元素的数组
char ss[20]={0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29};
2、定义结构体类型的指针ps指向ss数组
PERSON *ps=(PERSON *)ss;
3、打印输出各个成员
printf("0x%02x,0x%02x,0x%02x\n",ps->addr,ps->name,ps->id);printf("PERSON长度=%d字节\n",sizeof(PERSON));
可以看到addr和name都只占一个字节,但是未满4字节,跳过2字节后才是id的值,这就是4字节对齐。结构体成员有int型,会自动按照4字节对齐。
把结构体成员顺序调换位置
typedef struct { char addr; int id; char name; }PERSON;
输出:
按照下面的顺序排列:
typedef struct { int id; char addr; char name; }PERSON;
输出:
可见,结构体成员顺序优化,可节省空间。
如果全部成员都是 char 型,会按照 1 字节对齐,即
typedef struct { char addr; char name; char id; }PERSON;
输出结果:
结构体嵌套
结构体嵌套结构体方式:
typedef struct { char addr; char name; int id; }PERSON; typedef struct { char age; PERSON ps1; }STUDENT;
先定义结构体类型PERSON,再定义结构体STUDENT,PERSON作为它的一个成员。
按照前面的方法,打印各成员的值。
1、定义STUDENT 指针变量指向数组 ss
STUDENT *stu=(STUDENT *)ss;
2、打印输出各成员和长度
printf("0x%02x,0x%02x,0x%02x,0x%02x\n",stu->ps1.addr,stu->ps1.name,stu->ps1.id,stu->age); printf("STUDENT长度=%d字节\n",sizeof(STUDENT));
调换STUDENT成员顺序,
typedef struct { PERSON ps1; char age; }STUDENT;
输出结果:
结构体嵌套其实没有太意外的东西,只要遵循一定规律即可:
//对于“一锤子买卖”,只对最终的结构体变量感兴趣,其中A、B也可删,不过最好带着 struct A{ struct B{ int c; }b; }a; //使用如下方式访问: a.b.c = 10;
特别的,可以一边定义结构体B,一边就使用上:
struct A{ struct B{ int c; }b; struct B sb; }a;
使用方法与测试:
a.b.c = 11; printf("%d\n",a.b.c); a.sb.c = 22; printf("%d\n",a.sb.c);
结果无误。
但是如果嵌套的结构体B是在A内部才声明的,并且没定义一个对应的对象实体b,这个结构体B的大小还是不算进结构体A中。
占用内存空间
struct结构体,在结构体定义的时候不能申请内存空间,不过如果是结构体变量,声明的时候就可以分配——两者关系就像C++的类与对象,对象才分配内存(不过严格讲,作为代码段,结构体定义部分“.text”真的就不占空间了么?当然,这是另外一个范畴的话题)。
结构体的大小通常(只是通常)是结构体所含变量大小的总和,下面打印输出上述结构体的size:
printf("size of struct man:%d\n",sizeof(struct man)); printf("size:%d\n",sizeof(Huqinwei));
结果毫无悬念,都是28:分别是char数组20,int变量4,浮点变量4。
下边说说不通常的情况
对于结构体中比较小的成员,可能会被强行对齐,造成空间的空置,这和读取内存的机制有关。
为了效率,通常32位机按4字节对齐,小于的都当4字节,有连续小于4字节的,可以不着急对齐,等到凑够了整,加上下一个元素超出一个对齐位置,才开始调整,比如3+2或者1+4,后者都需要另起(下边的结构体大小是8bytes),相关例子就多了,不赘述。
struct s { char a; short b; int c; };
相应的,64 位机按 8 字节对齐。不过对齐不是绝对的,用#pragma pack()可以修改对齐,如果改成1,结构体大小就是实实在在的成员变量大小的总和了。
和C++的类不一样,结构体不可以给结构体内部变量初始化,。
如下,为错误示范:
#include<stdio.h> //直接带变量名 struct stuff{ // char job[20] = "Programmer"; // char job[]; // int age = 27; // float height = 185; };
PS:结构体的声明也要注意位置的,作用域不一样。
C++的结构体变量的声明定义和C有略微不同,说白了就是更“面向对象”风格化,要求更低。
为什么有些函数的参数是结构体指针型
如果函数的参数比较多,很容易产生“重复C语言代码”,例如:
int get_video(char **name, long *address, int *size, time_t *time, int *alg) { ... } int handle_video(char *name, long address, int size, time_t time, int alg) { ... } int send_video(char *name, long address, int size, time_t time, int alg) { ... }
上述C语言代码定义了三个函数:get_video() 用于获取一段视频信息,包括:视频的名称,地址,大小,时间,编码算法。
然后 handle_video() 函数根据视频的这些参数处理视频,之后 send_video() 负责将处理后的视频发送出去。下面是一次调用:
char *name = NULL; long address; int size, alg; time_t time; get_video(&name, &address, &size, &time, &alg); handle_video(name, address, size, time, alg); send_video(name, address, size, time, alg);
从上面这段C语言代码来看,为了完成视频的一次“获取”——“处理”——“发送”操作,C语言程序不得不定义多个变量,并且这些变量需要重复写至少三遍。
虽说C语言程序的代码风格因人而异,但是“重复的代码”永远是应尽力避免的,不管怎么说,每次使用这几个函数,都需要定义很多临时变量,总是非常麻烦的。所以,这种情况下,完全可以使用C语言的结构体语法:
struct video_info { char *name; long address; int size; int alg; time_t time; };
定义好 video_info 结构体后,上述三个C语言函数的参数可以如下写,请看:
int get_video(struct video_info *vinfo) { ... } int handle_video(struct video_info *vinfo) { ... } int send_video(struct video_info *vinfo) { ... }
修改后的C语言代码明显精简多了,在函数内部,视频的各个信息可以通过结构体指针 vinfo 访问,例如:
printf("video name: %s\n", vinfo->name); long addr = vinfo->address; int size = vinfo->size;
事实上,使用结构体 video_info 封装视频信息的各个参数后,调用这几个修改后的函数也是非常简洁的:
struct video_info vinfo = {0}; get_video(&vinfo); handle_video(&vinfo); send_video(&vinfo);
从上述C语言代码可以看出,使用修改后的函数只需定义一个临时变量,整个代码变得非常精简。
读者应该注意到了,修改之前的 handle_video() 和 send_video() 函数原型如下:
int handle_video(char *name, long address, int size, time_t time, int alg); int send_video(char *name, long address, int size, time_t time, int alg);
根据这段C语言代码,我们知道 handle_video() 和 send_video() 函数只需要读取参数信息,并不再修改参数,那为什么使用结构体 video_info 封装数据,修改后的 handle_video() 和 send_video() 函数参数是 struct video_info *指针型呢?
int handle_video(struct video_info *vinfo); int send_video(struct video_info *vinfo);
既然 handle_video() 和 send_video() 函数只需要读取参数信息,那我们就无需再使用指针型了呀?的确如此,这两个函数的参数直接使用 struct video_info 型也是可以的:
int handle_video(struct video_info vinfo) { ... } int send_video(struct video_info vinfo) { ... }
似乎这种写法和使用 struct video_info *指针型 参数的区别,无非就是函数内部访问数据的方式改变了而已。但是,如果读者能够想到我们之前讨论过的 C语言函数的“栈帧”概念,应该能够发现,使用指针型参数的 handle_video() 和 send_video() 函数效率更好,开销更小。
C语言中结构体(struct)的详细分解与使用(下)https://developer.aliyun.com/article/1389347