全面复盘C语言struct与union

李肖遥 2023-10-09 22:06
    关注、星标公众号,直达精彩内容


struct 结构体

struct即结构体,C程序中经常需要用相关的不同类型的数据来描述一个数据对象。例如,描述学生的综合信息时,需要使用学生的学号、姓名、性别等不同类型的数据时,像这种数据类型总是在一起出现,那么我们不如把这些变量装入同一个“文件夹”中,这时用的关键字struct声明的一种数据类型就是表示这个“文件夹”的使用。那么在说明和使用之前必须先定义它,也就是构造它。如同在说明和调用函数之前要先定义一样。


结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员,结构体也是一种数据类型,它由程序员自己定义,可以包含多个其他类型的数据,成员又称为成员变量,它是结构体所包含的若干个基本的结构类型,必须用“{}”括起来,并且要以分号结束,每个成员应表明具体的数据类型,成员一般用名字访问。结构体和数组类似,也是一组数据的集合,整体使用没有太大的意义。数组使用下标[ ]获访问元素,结构体使用点号.访问单个成员。通过这种方式可以获取成员的值,也可以给成员赋值

数组:a[0]=10;  结构体:today.day  (指针结构体用->访问)结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。

声明定义结构:

struct关键字+结构体的标志名+大括号里边是成员+}后面的声明此结构变量+末尾分号,一般有这些:

struct week{定义一  int x;  char y;   };  struct week p1,p2;  //声明变量p1,p2,里边都是week的值          //里边有x和y的值  //用.访问 :p1.x  p2.x// p1.y, p2.y
struct{定义二 int x; char y; }p1,p2;//在这里声明变量//p1和p2都是一种无名结构,// 里边有X和y 访问一样用.
struct week {定义三 int x; int y;}p1,p2;//常用的一种结构定义声明形式
对于第一和第三种形式,都声明了结构名week,但是第二种没有声明结构名,只是定义了两个结构变量,这种叫无名结构
无名结构: 可以定义无名结构体类型的变量。编译器对无名结构体的处理是随机生成一个不重复的变量名。无名结构的定义方式就是定义无名结构体时必须定义该结构体类型的至少一个变量。
优点:无名结构体的妙用就是可以避免相同类型的结构体的重复定义,这样可以对每一个具体类型的队列都可以定义一个结构体来管理该队列的头尾指针,即使定义多个相同具体类型的队列也不会引发重复定义的编译错误。这样定义了两个队列,其元素类型均为int类型,同时各得到了一个维护队列头尾指针的结构体
缺点:这里定义了一个无名的结构体,同时声明了三个此种类型的变量。但是,因为没有名字,我们在这句之后,无法内再定义与那三种变量相同类型的变量了。除非你再容次去定义一个这样的相同的结构体类型。还有一个重要的原因就是没有办法在其他位置定义我们所需要的结构体变量,每次需要新定义结构体变量的时候都必须要找到最开始结构体代码书写的位置才能定义新的结构体 所以实际编程中无名结构并不常用

注意:

1、结构体本身并不会被作为数据而开辟内存,真正作为数据而在内存中存储的是这种结构体所定义的变量。

2、先声明结构体类型,再定义该类型的变量,声明结构体类型,不分配空间定义结构体类型变量,就要分配内存空间

3、量使用占为少的类型,如,在可能的时候使用short代替int,按数据类型本身占用的位置从大到小排

4、除了可以对成员进行逐一赋值,也可以在定义时整体赋值:p1={struct week}{5,10}; 相当于 p1.x=5,p1.y=10;

p1=p2 表示 p1.x=p2.x ,  p1.y=p2.y; 不过整体赋值仅限于定义结构体变量的时候,在使用过程中只能对成员逐一赋值

5、结构体变量不能相加,相减,也不能相互乘除,但结构体可以相互赋值,也就是说,可以将一个结构体变量赋值给另一个结构体变量。但是前提是这两个结构体变量的结构体类型必须相同

结构体的运算:要访问整个结构,直接用结构变量的名字,对于整个结构,可以做赋值,取地址,也可以传递给函数参数


结构体数值

嵌套的结构体:

struct week{int x;int y;strcut week at;//在结构体又定义了名为at的一个和week同样参数的结构体变量        //其中可以用.运算符访问  see.at.x see.at.y}see;但是其实这样的方式是不建议(非法)的,因为这种声明实际上是一个无限循环,成员at是一个结构体,at的内部还会有成员是结构体,依次下去,无线循环。在分配内存的时候,由于无限嵌套,也无法确定这个结构体的长度,所以这种方式是非法的正确的方式是使用《结构体指针》,因为指针的长度是确定的:
struct week{int x;int y;strcut week *at;//在结构体内定义了一个指向和week一样类型的结构指针}see; 但是注意用指针访问时要用->运算符 see.at->x

结构体相互引用:

一个结构体A中包含一个或多个与结构体B相关的成员, 且结构体B中也包含一个或多个与结构体A相关的成员称为结构体的互引用.

但是要注意: 如果已经定义了两个结构A和B ,在定义结构体A的成员b时,结构体B对A还未可见,故此时编译器会报数据类型B未定义

解决的办法是使用不完整声明:

strcut A;//不完整声明strcut B;//不完整声明strcut _A{               strcut _B{int x;          int x;int y;                 int y;struct _B a;        struct _A b;  //在结构B中定义了一个名为b的和A结构一样类型的结构变量                 //其中可以用点访问 A.a.x   B.b.x}A;                      }B;//但是注意这种方式犯了一个和上面第一个嵌套结构的错误,就是结构体A和B都是直接包含了对方,正确的用法还是使用指针:strcut _A{               strcut _B{int x;          int x;int y;                 int y;struct _B *a;      struct _A *b;  //在结构B中定义了一个名为b的和A结构一样类型的结构指针                       //其中指针要用->访问 A.a->x   B.b->x}A;                                 }B;  //但是注意这种方式犯了一个和上面第一个嵌套结构的错误,就是结构体A和B都是直接包含了对方,正确的用法还是使用指针:strcut _A{               strcut _B{int x;              int x;int y;                 int y;struct _B *a;       struct _A *b;  //在结构B中定义了一个名为b的和A结构一样类型的结构指针                 //其中指针要用->访问 A.a->x   B.b->x}A;                                }B;//所以使用互引用要注意:至少有一个结构必须在另一个结构体中以指针的形式被引用。

结构体函数与函数参数

结构体做函数形参:

整个结构可以作为参数的值传入函数,这时候是在函数内新建一个结构变量,并复制调用者结构的值,也可以返回一个值,这和数组完全不同

用结构体变量作实参时,采取的也是“值传递”方式,将  结构体变量所占的内存单元的内容(结构体变量成员列表)  全部顺序传递给形参,这里形参也得是结构体变量。

#includetypedef struct _node {
int n; char a[100];
}NODE;
void add(NODE a);//这种形式只是用来做值的传递
int main(void) { //以传值方式传递结构需要对整个结构做一份拷贝 NODE t; scanf("%d %d", &t.a[0], &t.n);//输入1 3
printf("1-%d %d\n",t.a[0],t.n);//输出 1 3
add(t); printf("3-%d %d\n", t.a[0], t.n);//输出1 3
//也就是说在add函数里边做修改根本就影响不了主函数这边的值}
void add(NODE a) {
a.a[0] = 100;//在这里能接受到NODE结构里边的成员 a.n = 666;
printf("2-%d %d\n", a.a[0], a.n);//输出100 666
} ****//解决办法是用指针(也是经常用的方式):****#includetypedef struct _node {
int n; char a[100];
}NODE;
int add(NODE a);//这种形式只是用来做值的传递
int main(void) { //以传值方式传递结构需要对整个结构做一份拷贝 NODE t; scanf("%d %d", &t.a[0], &t.n);//输入1 3
printf("1-%d %d\n",t.a[0],t.n);//输出 1 3

add(&t);//这里传进去的是t的地址 printf("3-%d %d\n", t.a[0], t.n);//输出100 666//传进去的是地址,所以就可以达到访问同一个变量的操作}
int add(NODE *) {//定义一个结构指针
a.a[0] = 100;//在这里能接受到NODE结构里边的成员 a.n = 666;
printf("2-%d %d\n", a.a[0], a.n);//输出100 666 return a;//这里返回的是指针 所以能达到访问主函数里边调用的值 //使用指针才可以用返回值}//常用的方式

另一种做法

结构体做函数:

/*上面的第一个的方案,把一个结构传入了函数,然后在函数中操作,但是没有返回回去问题在于传入函数的是外面那个结构的克隆体,而不是指针,传入结构和传入数组是不同的,解决办法是在这个输入函数中,在里边创建一个临时的结构变量,然后把这个结构返回给调用者*/#includetypedef struct _node {  int x;  int y;}NODE;struct _node add();//定义结构类型的函数int main(void) {
NODE a; a.x = 0; a.y = 0; printf("1-%d %d\n", a.x, a.y);// 0 0
a = add();//函数调用 /把n的值又返回到a
printf("3-%d %d\n", a.x, a.y);//所以在这里的时候值已经被改变 return 0;}
struct _node add() {
NODE n; scanf("%d", &n.x);//输入1 3
scanf("%d", &n.y);
printf("2-%d %d\n", n.x, n.y);//在这里的时候赋值就成功了
//return n;//把n的值带回出去}//这种方法也能达到“改变"的效果,但是往往开销内存较大,所以一般情况都是使用指针比较方便

  用结构体变量名作参数,这种传递方式是单向的,如果在执行被调函数期间改变了形参(也是结构体变量)的值,该值不能返回主调函数,这往往造成使用上的不便,因此一般少用这种方法。

和本地变量一样。在函数内部声明的结构只能在函数内部使用,所以通常在函数外部声明一个结构类型的,这样就可以被多个函数所使用

//结构做函数参数例子 (输入今天计算明天)#include#include//利用布尔数据类型struct date {  int year;  int month;  int day;
};
bool If(struct date p);//判断是否是闰年int number(struct date c);//判断是否是此月最后一天
int main(void) {
struct date today,tomorrow; printf("年-月-日\n"); scanf("%d %d %d", &today.year, &today.month, &today.day);
//前面两个判断 是否此月最后一天 是否此年此月最后一天 if (today.day==number(today)&&today.month!=12) {//首月1号 tomorrow.day = 1; tomorrow.month =today.month+1; tomorrow.year = today.year; } else if (today.day == number(today) && today.month == 12) {//下一年 tomorrow.day = 1; tomorrow.month = 1; tomorrow.year =today.year+1; } else { tomorrow.day =today.day+1; tomorrow.month = today.month; tomorrow.year = today.year; }
printf("明天是%d-%d-%d\n", tomorrow.year, tomorrow.month, tomorrow.day); return 0;}
int number(struct date c)//这里的形参接收的today结构体数据{ int day; const int a[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };//这个月最大的天数
if (c.month==22&&If(c)) {//查看是否是二月并且是润年 day = 29;//是润年 } else {
day = a[c.month - 1]; }
return day;}
bool If(struct date p) {//这里的形参接收的today结构体数据 //润年的特点,能被4整除,但不能被100整数,能被100整除,但是不能被400整除 if (p.year % 4 == 0 && p.year / 100 != 0 || p.year % 400 == 0) { return true; } else { return false; }}
//结构体做函数例子 (计算下一秒)#includestruct time {  int hour;  int minute;  int second;};
struct time times(struct time now);//利用结构做函数返回值,形参也是使用结构体做为传值
int main(void) { struct time nows[5] = { {11,50,20},{13,25,59},{12,59,59},{23,59,59},{00,00,00}, };
int i; for (i = 0; i < 5; i++) { printf("时间是 %d:%d:%d\n", nows[i].hour, nows[i].minute, nows[i].second);
nows[i] = times(nows[i]);
printf("下一秒是 %d:%d:%d\n", nows[i].hour, nows[i].minute, nows[i].second); }
return 0;
}
struct time times(struct time now) {
now.second++; if (now.second == 60) {//60秒 now.minute++; now.second = 0;
if (now.minute == 60)//60分 { now.hour++; now.minute = 0; now.second = 0;
if (now.hour == 24) {//零点 now.hour=0; now.minute = 0; now.second = 0; } } }
return now;//返回类型必须也函数类型一致,换句话说只有结构体类型才能返回结构体类型
}

结构体数组

结构体数组,是指数组中的每个元素都是一个结构体。在实际应用中,C语言结构体数组常被用来表示一个拥有相同数据结构的群体,比如一个班的学生、一个车间的职工等。结构体可以存储不同的数据类型,将他们互相联系起来。结构体数组可以连续存储多个结构体,和数组作用相似。比如想定义同一个最小外接矩形的四个坐标值,并给予这个矩形一个特征编号。当需要存储多个最小外接矩形的信息时,就需要动态申请一个结构体数组

定义结构体数组的方法很简单,同定义结构体变量是一样的,只不过将变量改成数组。或者说同前面介绍的普通数组的定义是一模一样的:struct student  tp[10]; 这就定义了一个结构体数组,共有 10 个元素,每个元素都是一个结构体变量,都包含所有的结构体成员。

结构体数组的初始化与前面讲的数值型数组的初始化也是一样的,数值型数组初始化的方法和需要注意的问题在结构体数组的初始化中同样适用,因为不管是数值型数组还是结构体数组都是数组。

//例子:  //寻找学生中 学号最大的# include # include 
struct STU{ char name[20]; int age; char sex[20]; char num[20];
};
void OutputSTU(struct STU stu[]); //函数声明, 该函数的功能是输出成绩最大的学生信息
int main(void){ int i; struct STU stu[5];
for (i = 0; i < 2; ++i) { printf("请按照名字、年龄、性别、学号(1-9数字)输入第%d个学生的信息:", i + 1);
scanf("%s %d %s %s", stu[i].name, &stu[i].age, stu[i].sex, stu[i].num);/*%c前面要加空格, 不然输入时会将空格赋给%c*/ }
OutputSTU(stu);
return 0;}
void OutputSTU(struct STU stu[]){ struct STU stumax = stu[0];//让临时结构stumax保存第一个学生的信息
int j; for (j = 1; j < 2; ++j)//第一个学生依次和后面的学生比较 { if (strcmp(stumax.num, stu[j].num) < 0) //strcmp函数的使用 s1>s2:1 s1 { stumax = stu[j];//让临时结构保存那个学生的信息 } }
printf("学生姓名:%s 学生年龄:%d 学生性别:%s 学生分数:%s\n", stumax.name, stumax.age, stumax.sex, stumax.num);
}

结构体指针

和数组不同,结构变量的名字并不是结构变量的地址,必须使用&运算符  strcut node *tp=&nb;  指针一般用->访问结构体里边的成员

指针变量非常灵活方便,可以指向任一类型的变量    ,若定义指针变量指向结构体类型变量,则可以通过指针来引用结构体类型变量。

#includestruct node{int x;int y;}my;int main(void) {  struct node *p = &my;//定义了一个指针p指向了my的结构体
p->x = 11;//这是一种访问方式(常用的方式) (*p).x = 12;//这是第二种方式, printf("%d", p->x);//输出是12
}以下 2 种形式是等价的:(*指针变量).成员名。指针变量->成员名。其中第 2 种方式很重要,通常都是使用这种方式,另外两种方式用得不多。后面讲链表的时候用的也都是第 3 种方式。

这里说明:结构体和结构体变量是两个不同的概念:结构体是一种数据类型,是一种创建变量的模板,编译器不会为它分配内存空间,就像 int、float、char 这些关键字本身不占用内存一样;结构体变量才包含实实在在的数据,才需要内存来存储。所以用一个结构体去取一个结构体名的地址,这种写法是错误的,也不能将它赋值给其他变量。

#includestruct point {  int x;  int y;};struct point *gt(struct point*p);//结构指针函数
void print(const struct point *p);//结构指针
void out(struct point p);//普通的结构体做函数参数
int main(void) { struct point y = { 0,0 };//以point结构定义一个y的结构变量
//以下三种调用 等价 //注意gt是一个结构体的指针函数 gt(&y); //这是一个函数的返回结果函数 //取y结构的地址传入函数 out(y); out(*gt(&y)); // (里边)的都是做为参数 *gt(&y)做为指针返回值 这个函数它的返回用指针表示 print(gt(&y)); //gt(&y)是一个返回值 这样表示的是利用gt函数的返回值在print函数里边操作
//*get(&y) = (struct point){ 1,2 }; //这也可以做的}
struct point* gt(struct point*p) {// *p要的是&y的地址 scanf("%d", &p->x); scanf("%d", &p->y); printf("a=%d,%d \n", p->x, p->y);//用->来访问指针结构里边的成员
return p;// 用完指针后 返回指针}void out(struct point p) { printf("b=%d,%d\n", p.x, p.y);}
void print(const struct point *p) {//加上const表示不再改动参数 printf("c=%d,%d\n", p->x, p->y);}

指向结构体数组的指针:

在之前讲数值型数组的时候可以将数组名赋给一个指针变量,从而使该指针变量指向数组的首地址,然后用指针访问数组的元素。结构体数组也是数组,所以同样可以这么做。

我们知道,结构体数组的每一个元素都是一个结构体变量。如果定义一个结构体指针变量并把结构体数组的数组名赋给这个指针变量的话,就意味着将结构体数组的第一个元素,即第一个结构体变量的地址,也即第一个结构变量中的第一个成员的地址赋给了这个指针变量

# include struct qt{  char name[5];  int age;  char sex[5];  double scroe;};int main(void){    //定义了一个student的结构数组  struct qt student[5] = { {"李青", 20, "男", 99}, {"黄欢", 20,  "女", 80}, {"七七", 23, "男", 95} };  struct qt *p = student;  int i;  for (i = 0; i < 5; i++) {
printf("%s ", p->name);//利用->可访问成员(访问意味着可以读写) printf("%d ", p->age); printf("%s ", p->sex); printf("%f ", p->scroe); } return 0;}当结构体指针变量指向一个结构体变量数组的时候,此时指针变量的值就是结构体数组的首地址,此时指针变量 p 就指向了结构体数组的第一个元素,即指向 student[0]。我们知道,当一个指针指向一个数组后,指针就可以通过移动的方式指向数组的其他元素。这个原则对结构体数组和结构体指针同样适用,所以 p + 1 就指向 student[1] 的首地址;p + 2 就指向 student[2] 的首地址……所以只要利用 for 循环,指针就能一个个地指向结构体数组元素。
同样需要注意的是,要将一个结构体数组名赋给一个结构体指针变量,那么它们的结构体类型必须相同。

typedef 别名

typedef是在编程语言中用来为复杂的声明定义简单的别名,新的名字是某种类型的别名,这样做改善了程序的可读性,它与宏定义有些差异。它本身是一种存储类的关键字,与auto、extern、mutable、static、register等关键字不能出现在同一个表达式中。

typedef为C语言的关键字,功能是用来声明一个已有的数据类型的新名字,比如 typedef int last ;  这就使得last成为 int 类型的别名  这样last这个名字就可以代替int出现在变量定义和参数声明的地方了

typedef也有一个特别的长处:它符合范围规则,使用typedef定义的变量类型其作用范围限制在所定义的函数或者文件内(取决于此变量定义的位置),而宏定义则没有这种特性。

typedef & 复杂的变量声明理解复杂声明可用的“右左法则”:  从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。举例:  int (*func)(int *p);  首 先找到变量名func,外面有一对圆括号,而且左边是一个*号,这说明func是一个指针;然后跳出这个圆括号,先看右边,又遇到圆括号(只有函数后面才跟形参圆括号),这说明 (*func)是一个函数,所以func是一个指向这类函数的指针,即函数指针,这类函数具有int*类型的形参,返回值类型是int,此处就是声明函数。  int (*func[5])(int *);  func 右边是一个[]运算符,说明func是具有5个元素的数组;func的左边有一个*,说明func的元素是指针(注意这里的*不是修饰func,而是修饰 func[5]的,原因是[]运算符优先级比*高,func先跟[]结合)。跳出这个括号,看右边,又遇到圆括号,说明func数组的元素是函数类型的指 针,它指向的函数具有int*类型的形参,返回值类型为int
也可以记住2个模式:type (*)(....)函数指针type (*)[]数组指针

结构体的内存对齐方式(存储空间)

结构体内存对齐:一个结构体变量定义完之后,其在内存中的存储并不等于其所包含元素的宽度之和,元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每个元素放置到内存中时,它都会认为内存是按照自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始。

内存对齐可以大大提升内存访问速度,是一种用空间换时间的方法。内存不对齐会导致每次读取数据都会读取两次,使得内存读取速度减慢。

cpu把内存当成是一块一块的,块的大小可以是2,4,8,16 个字节,因此CPU在读取内存的时候是一块一块进行读取的,块的大小称为内存读取粒度。

// 内存对齐原则://   1、第一个成员的首地址为0.//   2、每个成员的首地址是自身大小的整数倍//    3、结构体的总大小,为其成员中所含最大类型的整数倍。
#includetypedef struct _node { //char t;//1 //int p; //4 //float y;//8//char t 要存放的偏移量为0,满足对齐方式,t占用一个字节,int p要存储在下一个可用的地址的偏移量为1.不是sizeof(int)=4的倍数,需要补足3个字节才能使偏移量变为4(使其满足对齐方式),因此系统自动填充3个字节使偏移量增加到4int p放到此位置,占用4个字节,下一可用的偏移量为8,满足sizeof(float)=4的对齐,所以float y直接存放在偏移量为8的位置上,它占用4个字节 总共就是 //1+3+4+4=12
double a;//8 char b;//1 int c;//4//double a的要存放的偏移量为0,满足对齐方式直接存储,占用8个字节,char b要存储在下一的可用的地址的偏移量为9直接存储,占用字节为1int c要存储在下一个可用的地址的偏移量为99不满足sizeofint)=4;所以系统会自动分配3的字节增加到1212满足对齐方式,存储下去c占用4个字节。总共就是 //8+1+3+4=16
}NODE;
int main(void) { printf("%d ", sizeof(NODE));}

如果结构体内存在长度大于处理器位数的元素,那么就以处理器的倍数为对齐单位;否则,如果结构体内的元素的长度都小于处理器的倍数的时候,便以结构体里面最长的数据元素为对齐单位。

另外  结构体的内存地址就是它第一个成员变量的地址  isa永远都是结构体中的第一个成员变量  所以结构体的地址也就是其isa指针的地址

内存对齐简介

由于内存的读取时间远远小于CPU的存储速度,这里用设定数据结构的对齐系数,即牺牲空间来换取时间的思想来提高CPU的存储效率。

内存对齐”应该是编译器的“管辖范围”。编译器为程序中的每个“数据单元”安排在适当的位置上。但是C语言的一个特点就是太灵活,太强大,它允许你干预“内存对齐”。如果你想了解更加底层的秘密,“内存对齐”对你就不应该再模糊了。这也是一个大小端模式的问题

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n)来改变这一系数,其中的n就是你要指定的“对齐系数”。

规则:

1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行

2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack 指定的数值和结构(或联合) 最大数据成员长度中,比较小的那个进行对齐。

3、结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

#pragmapack(n)  设定变量以n字节为对齐方式:

作用:指定结构体、联合以及类成员

语法:#pragmapack( [show] | [push | pop] [, identifier], n )

1,pack提供数据声明级别的控制,对定义不起作用;

2,调用pack时不指定参数,n将被设成默认值;

n:可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。

其他参数都是可选的可先不了解

#include#pragma pack(2)// 值只能填1 2 4 8 16  这里最好是看结构里边最小的成员 这里是char 所以最好是填1    //但是当#pragma pack指定的值等于或者超过所有数据成员长度的时候,这个指定值的大小将不产生任何效果typedef struct _A {
double x;//4 int y;//4 char p;//1 }NODE;
//8+4+1+1=14按n为2
//8+4+1+3=16按n为4以上或者使用系统自动对齐
#pragma pack(8)//设定为4字节对齐typedef struct test{ char m1; double m4; int m3;
}NODE2;
//1+1+4+8=14//按n为2//1+7+8+4=20 不满足8的倍数 加4等于24满足 总:1+7+8+4+4=24

int main(void) {
printf("%d\n", sizeof(NODE)); printf("%d", sizeof(NODE2));
}结构、联合或者类的数据成员,第一个放在偏移为0的地方;以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度两个中比较小的那个进行;也就是说,当#pragma pack指定的值等于或者超过所有数据成员长度的时候,这个指定值的大小将不产生任何效果;

每个成员分别对齐,即每个成员按自己的方式对齐,并最小化长度;规则就是每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐。

大小端:

如:int 11 22 33 44在存储的时候大端:11 22 33 44   0  1   2  3   低地址----> 高地址
小端:44 33 22 11 0 1 2 3 低地址----> 高地址大小端的差异在于存放顺序不同常见的操作系统是小端,通讯协议是大端。
//结构体例子:使用尾插法创建链表#include//单链表的创建
typedef struct _node {
int nb;//数值 struct _node *nxte;//定义一个指向下一个的节点的指针
}NODE;
typedef struct _link{//利用这个结构体 封装 首尾节点
NODE *head; NODE *qt;
}link;
void add(link *phead, link *qt, int n);//定义函数将 首尾指针传入
int main(void) {
link head, q;//定义一个结构,连指针都不是的 head.head = q.qt = NULL;//初始化
int n;
for (scanf("%d", &n); n != -1; scanf("%d", &n)) { add(&head, &q, n);//将地址 值传入 }
NODE *t; t = head.head;//利用临时结构将链表输出 for (; t; t = t->nxte) { printf("%d ", t->nb); }
return 0;}
//尾插法void add(link *phead, link *qt, int n) {
NODE *p = (NODE*)malloc(sizeof(NODE));//为新结点开辟空间 p->nb = n; p->nxte = NULL;
if (phead->head == NULL) {//判断首结点是否为空 phead->head = p;//是空的就让首结点等于新结点 } else {//不为空时,让尾结点依次跑到后面去 qt->qt->nxte = p; }
qt->qt = p;}

4、union 共用体(联合体)

在进行某些算法的C语言编程的时候,需要使几种不同类型的变量存放到同一段内存单元中。也就是使用覆盖技术,几个变量互相覆盖。这种几个不同的变量共同占用一段内存的结构,在C语言中 以关键字union声明的一种数据结构,这种被称作“共用体”类型结构,也叫联合体。

        “联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间,一个结构体变量的总长度大于等于各成员长度之和。而在“联合”中,各成员共享一段内存空间,一个联合变量的长度等于各成员中最长的长度。注意这里所谓的共享不是指把多个成员同时装入一个联合变量内,而是指该联合变量可被赋予任一成员值,但每次只能赋一种值,赋入新值则冲去旧值,共用体变量中起作用的成员是最后一次存放的成员,在存入一个新成员后,原有成员就失去作用,共用体变量的地址和它的各成员的地址都是同一地址

一个联合类型必须经过定义之后,才能把变量说明为该联合类型:

联合的定义:定义一个联合类型的一般形式为:union [name](联合名)   name是可选的{成员表};成员表中含有若干成员,成员的一般形式为: 类型说明符 成员名成员名的命名应符合标识符的规定。
union Data{ int i; double f; char str[20];} data;

现在 Data所有的成员共享一个空间,同一时间只有一个成员是的值有效的,Data 类型的变量可以存储一个整数、一个浮点数,或者一个字符串。这意味着一个变量(相同的内存位置)可以存储多个多种类型的数据。您可以根据需要在一个共用体内使用任何内置的或者用户自定义的数据类型。共用体占用的内存应足够存储共用体中最大的成员。例如,在上面的实例中,Data 将占用 20 个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。

注意:1、不能把共用体变量作为函数参数,也不能是函数带回共用体变量,但可以使专用指向共用体变量的指针

2、所有成员占用同一段内存,修改一制个成员会影响其余所有成员。

共用体的访问:

共用体访问成员的值时一般使用.运算符,指针时用->运算符(和结构体是一样的)

typedef union _node {  int a;  double b;  char c;  union _node *p;
}NODE;
int main(void) {
NODE a;//定义变量 NODE t; a.b;//用.访问 t.p->a;//指针用->访问


}联合的使用规则几乎和结构体strtct的规则用法一样,只不过是内部表示的不同。
补充:还有一个是无名联合体,它是和无名结构体的工作原理是相同的
#include//简单的例子#include
typedef union _node{ int a; double b; char c[20];
}NODE;
int main(void) {
NODE a;//这里只定义一个变量
a.a = 666; printf("%d\n", a.a);
a.b = 9.99; printf("%f\n", a.b);
strcpy(a.c, "hello world!"); printf("%s\n", a.c);
//我们看到,三个都被完整的输出了,因为在同一时刻,只有一个成员是有效的
}
输出:6669.990000hellow world!

共用体的作用:

1、节省内存,有两个很长的数据结构,不会同时使用,比如一个表示老师,一个表示学生,如果要统计教师和学生的情况用结构体的话就有点浪费了!用结构体的话,只占用最长的那个数据结构所占用的空间,就足够了!

2、实现不同类型数据之间的类型转换,遇到各种类型的数据共用存储空间,很方便的实现了不同数据类型之间的转换,不需要显示的强制类型转换。

其他:

1、确定CPU的模式:大端、小端模式确定

大小端不同,则存储的方式也存在差别,比如int需要4个字节,而char只需要1个字节,根据1个字节所在的具体位置即可判定CPU的模式

2、寄存器的定义,实现整体的访问和单项的访问

//共用体综合例子:根据输入的数据类型输出需要的相应的数据#include#include//数据类型输出   5*4  m n     n的第几个x 
union node { int a; double b; char c[30];
}add[10000];
char p[10000][30]; //保存的字符串数组
int main(void) { int n, m; scanf("%d %d", &n, &m); int x; double y; char t[50]; int i, j;
for (i = 0; i < n; i++) {//输入 scanf("%s", &p[i]);//作为字符串数组,需要取地址
if (strcmp("INT", p[i]) == 0) {//整形 scanf("%d", &x); add[i].a = x; } else if(strcmp("DOUBLE",p[i])==0){//浮点 scanf("%lf", &y); add[i].b = y; } else if (strcmp("STRCING", p[i]) == 0) {//字符串 scanf("%s", t); strcpy(add[i].c, t); }
}
for (i = 0; i < m; i++) {//输出 scanf("%d", &j);
if (strcmp("INT", p[j]) == 0) { printf("%d\n", add[j].a); } else if (strcmp("DOUBLE", p[j]) == 0) { printf("%f\n", add[j].b); }else if(strcmp("STRING",p[j])==0) { printf("%s\n", add[j].c);
}
}
return 0;
}
//输入:/*5 4INT 456DOUBLE 123.56DOUBLE 0.476STRING welcomeToCSTRING LemonTree0124*/
//输出:/*456123.560.48LemonTree
*/

希望对你有帮助!

版权声明:本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。

‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

关注我的微信公众号,回复“加群”按规则加入技术交流群。


点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。

李肖遥 公众号“技术让梦想更伟大”,作者:李肖遥,专注嵌入式,只推荐适合你的博文,干货,技术心得,与君共勉。
评论
  • 本文介绍瑞芯微RK3588主板/开发板Android12系统下,APK签名文件生成方法。触觉智能EVB3588开发板演示,搭载了瑞芯微RK3588芯片,该开发板是核心板加底板设计,音视频接口、通信接口等各类接口一应俱全,可帮助企业提高产品开发效率,缩短上市时间,降低成本和设计风险。工具准备下载Keytool-ImportKeyPair工具在源码:build/target/product/security/系统初始签名文件目录中,将以下三个文件拷贝出来:platform.pem;platform.
    Industio_触觉智能 2024-12-12 10:27 49浏览
  • 天问Block和Mixly是两个不同的编程工具,分别在单片机开发和教育编程领域有各自的应用。以下是对它们的详细比较: 基本定义 天问Block:天问Block是一个基于区块链技术的数字身份验证和数据交换平台。它的目标是为用户提供一个安全、去中心化、可信任的数字身份验证和数据交换解决方案。 Mixly:Mixly是一款由北京师范大学教育学部创客教育实验室开发的图形化编程软件,旨在为初学者提供一个易于学习和使用的Arduino编程环境。 主要功能 天问Block:支持STC全系列8位单片机,32位
    丙丁先生 2024-12-11 13:15 63浏览
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-11 17:58 86浏览
  • RK3506 是瑞芯微推出的MPU产品,芯片制程为22nm,定位于轻量级、低成本解决方案。该MPU具有低功耗、外设接口丰富、实时性高的特点,适合用多种工商业场景。本文将基于RK3506的设计特点,为大家分析其应用场景。RK3506核心板主要分为三个型号,各型号间的区别如下图:​图 1  RK3506核心板处理器型号场景1:显示HMIRK3506核心板显示接口支持RGB、MIPI、QSPI输出,且支持2D图形加速,轻松运行QT、LVGL等GUI,最快3S内开
    万象奥科 2024-12-11 15:42 86浏览
  • 一、SAE J1939协议概述SAE J1939协议是由美国汽车工程师协会(SAE,Society of Automotive Engineers)定义的一种用于重型车辆和工业设备中的通信协议,主要应用于车辆和设备之间的实时数据交换。J1939基于CAN(Controller Area Network)总线技术,使用29bit的扩展标识符和扩展数据帧,CAN通信速率为250Kbps,用于车载电子控制单元(ECU)之间的通信和控制。小北同学在之前也对J1939协议做过扫盲科普【科普系列】SAE J
    北汇信息 2024-12-11 15:45 110浏览
  • 全球智能电视时代来临这年头若是消费者想随意地从各个通路中选购电视时,不难发现目前市场上的产品都已是具有智能联网功能的智能电视了,可以宣告智能电视的普及时代已到临!Google从2021年开始大力推广Google TV(即原Android TV的升级版),其他各大品牌商也都跟进推出搭载Google TV操作系统的机种,除了Google TV外,LG、Samsung、Panasonic等大厂牌也开发出自家的智能电视平台,可以看出各家业者都一致地看好这块大饼。智能电视的Wi-Fi连线怎么消失了?智能电
    百佳泰测试实验室 2024-12-12 17:33 46浏览
  • 应用环境与极具挑战性的测试需求在服务器制造领域里,系统整合测试(System Integration Test;SIT)是确保产品质量和性能的关键步骤。随着服务器系统的复杂性不断提升,包括:多种硬件组件、操作系统、虚拟化平台以及各种应用程序和服务的整合,服务器制造商面临着更有挑战性的测试需求。这些挑战主要体现在以下五个方面:1. 硬件和软件的高度整合:现代服务器通常包括多个处理器、内存模块、储存设备和网络接口。这些硬件组件必须与操作系统及应用软件无缝整合。SIT测试可以帮助制造商确保这些不同组件
    百佳泰测试实验室 2024-12-12 17:45 43浏览
  • 铁氧体芯片是一种基于铁氧体磁性材料制成的芯片,在通信、传感器、储能等领域有着广泛的应用。铁氧体磁性材料能够通过外加磁场调控其导电性质和反射性质,因此在信号处理和传感器技术方面有着独特的优势。以下是对半导体划片机在铁氧体划切领域应用的详细阐述: 一、半导体划片机的工作原理与特点半导体划片机是一种使用刀片或通过激光等方式高精度切割被加工物的装置,是半导体后道封测中晶圆切割和WLP切割环节的关键设备。它结合了水气电、空气静压高速主轴、精密机械传动、传感器及自动化控制等先进技术,具有高精度、高
    博捷芯划片机 2024-12-12 09:16 85浏览
  • 在智能化技术快速发展当下,图像数据的采集与处理逐渐成为自动驾驶、工业等领域的一项关键技术。高质量的图像数据采集与算法集成测试都是确保系统性能和可靠性的关键。随着技术的不断进步,对于图像数据的采集、处理和分析的需求日益增长,这不仅要求我们拥有高性能的相机硬件,还要求我们能够高效地集成和测试各种算法。我们探索了一种多源相机数据采集与算法集成测试方案,能够满足不同应用场景下对图像采集和算法测试的多样化需求,确保数据的准确性和算法的有效性。一、相机组成相机一般由镜头(Lens),图像传感器(Image
    康谋 2024-12-12 09:45 75浏览
  • 时源芯微——RE超标整机定位与解决详细流程一、 初步测量与问题确认使用专业的电磁辐射测量设备,对整机的辐射发射进行精确测量。确认是否存在RE超标问题,并记录超标频段和幅度。二、电缆检查与处理若存在信号电缆:步骤一:拔掉所有信号电缆,仅保留电源线,再次测量整机的辐射发射。若测量合格:判定问题出在信号电缆上,可能是电缆的共模电流导致。逐一连接信号电缆,每次连接后测量,定位具体哪根电缆或接口导致超标。对问题电缆进行处理,如加共模扼流圈、滤波器,或优化电缆布局和屏蔽。重新连接所有电缆,再次测量
    时源芯微 2024-12-11 17:11 109浏览
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-12 10:13 34浏览
  • 首先在gitee上打个广告:ad5d2f3b647444a88b6f7f9555fd681f.mp4 · 丙丁先生/香河英茂工作室中国 - Gitee.com丙丁先生 (mr-bingding) - Gitee.com2024年对我来说是充满挑战和机遇的一年。在这一年里,我不仅进行了多个开发板的测评,还尝试了多种不同的项目和技术。今天,我想分享一下这一年的故事,希望能给大家带来一些启发和乐趣。 年初的时候,我开始对各种开发板进行测评。从STM32WBA55CG到瑞萨、平头哥和平海的开发板,我都
    丙丁先生 2024-12-11 20:14 69浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦