因此我们以后就可以这样创建数据对象
struct book library;//把library设为一个可以使用book结构体的结构体变量,则library这个变量就包含了其book结构体中的所有元素
3、接下来就是一个花括号,括起了结构体成员列表,及每个成员变量,使用的都是其自己的声明方式来描述,用分号来结束描述;
例如:char title[MAXTITL];字符数组就是这样声明的,用分号结束;
注意:其中每个成员可以使用任何一种C数据结构甚至是其他的结构体,也是可以的;
4、在结束花括号后的分号表示结构体设计定义的结束。
**关于其struct声明的位置,也就是这段代码要放到哪里。**同样这也是具有作用域的。
这种声明如果放在任何函数的外面,那么则可选标记可以在本文件中,该声明的后面的所有函数都可以使用。
如果这种声明在某个函数的内部,则它的标记只能在内部使用,并且在其声明之后;
关于我们不断说的,标记名是可选的,那么我们什么时候可以省略,什么时候一定不能省略呢?
如果是上面那种声明定义的方法,并且想在一个地方定义结构体设计,而在其他地方定义实际的结构体变量,那么就必须使用标记;
可以省略,设计的同时就创建该结构体变量,但是这种设计是一次性的。
一般格式就是:
struct 结构体名(也就是可选标记名){ 成员变量;};//使用分号表示定义结束。
C语言结构体定义的三种方式
1、最标准的方式:
#include <stdio.h>struct student //结构体类型的说明与定义分开。声明{int age; /*年龄*/float score; /*分数*/char sex; /*性别*/};int main (){struct student a={ 20,79,'f'}; //定义printf("年龄:%d 分数:%.2f 性别:%c\n", a.age, a.score, a.sex );return 0;
2、不环保的方式
#include <stdio.h>struct student /*声明时直接定义*/{int age; /*年龄*/float score; /*分数*/char sex; /*性别*//*这种方式不环保,只能用一次*/} a={21,80,'n'};int main (){printf("年龄:%d 分数:%.2f 性别:%c\n", a.age, a.score, a.sex );
3、最奈何人的方式
#include <stdio.h>struct //直接定义结构体变量,没有结构体类型名。这种方式最烂{int age;float score;char sex;} t={21,79,'f'};int main (){printf("年龄:%d 分数:%f 性别:%c\n", t.age, t.score, t.sex);return 0;}return 0;}}
定义结构体变量
之前我们结构体类型的定义(结构体的声明)只是告诉编译器该如何表示数据,但是它没有让计算机为其分配空间。
我们要使用结构体,那么就需要创建变量,也就是结构体变量;
创建一个结构体变量;
struct book library;
看到这条指令,编译器才会创建一个结构体变量library,此时编译器才会按照book模板为该变量分配内存空间,并且这里存储空间都是以这个变量结合在一起的。
这也是后面访问结构体变量成员的时候,我们就要用到结构体变量名来访问。
分析:
struct book的作用:
在结构体声明中,struct book所起到的作用就像int,,,,等基础数据类型名作用一样。
struct book s1,s2,*ss;
定义两个struct book结构体类型的结构体变量,还定义了一个指向该结构体的指针,其ss指针可以指向s1,s2,或者任何其他的book结构体变量。
其实;
struct book library;
等效于;
struct book{ char … …. ….. }librar;
这两种是等效的,只是第一种可以减少代码的编写量;
现在还是回到刚才提及的那个问题,可选标志符什么时候可以省略;
其一;
struct{ char title[MAXTITL]; char author[MAXAUTL];float value;}library;
//注意这里不再是定义声明结构体类型,而是直接创建结构体变量了,这个编译器会分配内存的;
//这样的确可以省略标识符也就是结构体名,但是只能使用一次;因为这是;声明结构体的过程和定义结构体变量的过程和在了一起;并且个成员变量没有初始化的;
//如果你想多次使用一个结构体模块,这样子是行不通的;
其二;
用typedef定义新类型名来代替已有类型名,即给已有类型重新命名;
一般格式为;typedef 已有类型 新类型名;
typedef int Elem; typedef struct{ int date; ..... .....}STUDENT;STUDENT stu1,stu2;
总结一下关于结构体变量的定义;
1、先定义结构体类型后再定义结构体变量;
格式为;struct 结构体名 变量名列表;
struct book s1,s2,*ss;//注意这种之前要先定义结构体类型后再定义变量;
**2、**在定义结构体类型的同时定义结构体变量;
格式为;
struct 结构体名{成员列表;}变量名列表;//这里结构体名是可以省的,但尽量别省;struct book{char title[MAXTITL];//一个字符串表示的titile 题目 ;char author[MAXAUTL];//一个字符串表示的author作者 ;float value;//一个浮点型表示的value价格;}s1,s2
直接定义结构体类型变量,就是第二种中省略结构体名的情况;
这种方式不能指明结构体类型名而是直接定义结构体变量,并且在值定义一次结构体变量时适用,无结构体名的结构体类型是无法重复使用的。
也就是说,后面程序不能再定义此类型变量了,除非再写一次重复的struct。
对于结构体变量的初始化
先回忆一下关于基本数据类型和数组类型的初始化;
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中。
(结构体长度、结构体字节对齐、结构体嵌套内容来源于公众号“0基础学单片机”,作者:森林木,感谢原作者的分享)
占用内存空间
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语言位结构体用途详解
在嵌入式开发中,经常需要表示各种系统状态,位结构体的出现大大方便了我们,尤其是在进行一些硬件层操作和数据通信时。但是在使用位结构体的过程中,是否深入思考一下它的相关属性?是否真正用到它的便利性,来提高系统效率?
下面将进行一些相关实验(这里以项目开发中的实际代码为例):
1.位结构体类型设计
[cpp] view plain copy print?//data structure except for number structure typedef struct symbol_struct { uint_32 SYMBOL_TYPE :5; //data type,have the affect on "data display type" uint_32 reserved_1 :4;
uint_32 SYMBOL_NUMBER :7; //effective data number in one element uint_32 SYMBOL_ACTIVE :1;//symbol active status uint_32 SYMBOL_INDEX :8; //data index in norflash,result is related to "xxx_BASE_ADDR" uint_32 reserved_2 :8;
}SYMBOL_STRUCT,_PTR_ SYMBOL_STRUCT_PTR;
分析:这里定义了一个位结构体类型SYMBOL_STRUCT,那么用该类型定义的变量都哪些属性呢?
看下面运行结果: