结构体使用方法大全(定义,内存大小,初始化,结构数组,结构指针,位域,结构和联合体的嵌套,结构体包含函数指针)
“结构”是一种构造类型,它是由若干“成员”组成的。每一个成员可以是一个基本数据类型或者又是一个构造类型。结构既是一种“构造”而成的数据类型,那么在说明和使用之前必须先定义它,也就是构造它。如同在说明和调用函数之前要先定义函数一样。
一、结构的定义
说明结构变量有以下三种方法。
-
先定义结构,再说明结构变量。如:
struct stu
{
int num;
char name[20];
char sex;
float score;
};
struct stu boy1,boy2;
说明了两个变量boy1和boy2为stu结构类型。 -
在定义结构类型的同时说明结构变量。例如:
struct stu
{
int num;
char name[20];
char sex;
float score;
}boy1,boy2; -
直接说明结构变量。例如:
struct
{
int num;
char name[20];
char sex;
float score;
}boy1,boy2; -
定义结构指针、
struct stu
{
int num;
char name[20];
char sex;
float score;
}*boy1,boy2; //boy1是结构体指针,boy2是结构体(*boy1).num或者boy1->num都可以
boy1=(struct stu*)malloc(sizeof(struct stu));
ps->;num=102;
ps->;name=“Zhang ping”;
ps->;sex=‘M’;
ps->;score=62.5;
free(boy1);
-
可以用typedef来定义
typedef struct stu
{
int num;
char name[20];
char sex;
float score;
}stu;或者
typedef struct
{
int num;
char name[20];
char sex;
float score;
}stu;上面两种定义的方式在大部分情况下没有差别,只有在结构体中定义本结构指针时,第一种结构定义方式是正确的方式,如下:
typedef struct stu
{
int num;
char name[20];
char sex;
float score;struct stu * next;
}stu;
而第二种定义方式就不能在结构体中定义本结构指针,因为前面的语句并没有出现struct stu 。
二、结构体占用字节大小的计算
约定为32位系统,即char 1字节、short 2字节、int 4字节
该问题总结为两条规律:
-
每个结构体成员的起始地址为该成员大小的整数倍,即int型成员的其实地址只能为0、4、8等
-
结构体的大小为其中最大成员大小的整数倍
struct A{ char a;//1 int b;//空3 + 4 = 7 (规则1) short c;//2+空2=4 (规则2) }; struct B{ char a;//1 short b;//空1 + 2 = 3 (规则1) int c;//4 };
上面是问题的简化版,其实还有另外两条规则,下面严格按照定义补充完整:
数据类型的起始地址为其大小的整数倍
结构体的自身对齐值为其中最大的成员大小
可以使用关键词#pragma pack(1) 来指定结构体的对齐值
有效对齐值为自身对齐值与指定对齐值中较小的一个。(即指定对齐值超过自身对齐值无意义)
三、结构变量的初始化和赋值
如果结构变量是全局变量或为静态变量, 则可对它作初始化赋值。对局部或自动结构变量不能作初始化赋值。
struct stu /*定义结构*/
{
int num;
char *name;
char sex;
float score;
} boy2,boy1={102,"Zhang ping",'M',78.5};
结构数组的初始化:
struct stu
{
int num;
char *name;
char sex;
float score;
}boy[5]={
{101,"Li ping","M",45},
{102,"Zhang ping","M",62.5},
{103,"He fang","F",92.5},
{104,"Cheng ling","F",87},
{105,"Wang ming","M",58};
}
当然如果结构体数据很复杂,不可能一一进行初始化或者赋值的话,可以使用memset函数进行初始化。
#include <string.h>
void *memset(void *s, int c, unsigned long n);
函数的功能是:将指针变量 s 所指向的前 n 字节的内存单元用一个“整数” c 替换,注意 c 是 int 型。s 是 void* 型的指针变量,所以它可以为任何类型的数据进行初始化。
memset() 的作用是在一段内存块中填充某个给定的值。因为它只能填充一个值,所以该函数的初始化为原始初始化,无法将变量初始化为程序中需要的数据。用memset初始化完后,后面程序中再向该内存空间中存放需要的数据。
memset 一般使用“0”初始化内存单元,而且通常是给数组或结构体进行初始化。一般的变量如 char、int、float、double 等类型的变量直接初始化即可,没有必要用 memset。如果用 memset 的话反而显得麻烦。
当然,数组也可以直接进行初始化,但 memset 是对较大的数组或结构体进行清零初始化的最快方法,因为它是直接对内存进行操作的。
这时有人会问:“字符串数组不是最好用’\0’进行初始化吗?那么可以用 memset 给字符串数组进行初始化吗?也就是说参数 c 可以赋值为’\0’吗?”
可以的。虽然参数 c 要求是一个整数,但是整型和字符型是互通的。但是赋值为 ‘\0’ 和 0 是等价的,因为字符 ‘\0’ 在内存中就是 0。所以在 memset 中初始化为 0 也具有结束标志符 ‘\0’ 的作用,所以通常我们就写“0”。
memset 函数的第三个参数 n 的值一般用 sizeof() 获取,这样比较专业。注意,如果是对指针变量所指向的内存单元进行清零初始化,那么一定要先对这个指针变量进行初始化,即一定要先让它指向某个有效的地址。而且用memset给指针变量如p所指向的内存单元进行初始化时,n 千万别写成 sizeof§,这是新手经常会犯的错误。因为 p 是指针变量,不管 p 指向什么类型的变量,sizeof§ 的值都是 4。
下面写一个程序:
#include <stdio.h>
#include <string.h>
int main(void)
{
int i; //循环变量
char str[10];
char *p = str;
memset(str, 0, sizeof(str)); //只能写sizeof(str), 不能写sizeof(p)
for (i=0; i<10; ++i)
{
printf("%d\x20", str[i]);
}
printf("\n");
return 0;
}
根据memset函数的不同,输出结果也不同,分为以下几种情况:
memset(p, 0, sizeof§); //地址的大小都是4字节
0 0 0 0 -52 -52 -52 -52 -52 -52
memset(p, 0, sizeof(*p)); //*p表示的是一个字符变量, 只有一字节
0 -52 -52 -52 -52 -52 -52 -52 -52 -52
memset(p, 0, sizeof(str));
0 0 0 0 0 0 0 0 0 0
memset(str, 0, sizeof(str));
0 0 0 0 0 0 0 0 0 0
memset(p, 0, 10); //直接写10也行, 但不专业
0 0 0 0 0 0 0 0 0 0
四、结构数组的定义和初始化
具体示例如下:
struct Student //定义struct Student结构体类型
{
int num;
char name[10];
char sex;
};
struct Student stus[20]; //定义struct Student结构体数组stus
struct Student
{
int num;
char name[10];
char sex;
}stus[20];
struct Student students[3]={{0001, "Zhang San",'M'},
{0002, "Li Si",'W'},
{0003, "Zhao Liu",'M'}
};
struct Student
{
int num;
char name[10];
char sex;
}students[3]={ {0001, "Zhang San",'M'},
{0002, "Li Si",'W'},
{0003, "Zhao Liu",'M'}};
struct city{
char name[20];
double population;
double housing;
double ave;
};
city cities[2];
cities[0]={"A",10000,11};
cities[1]={"B",20000,11};
为什么不行啊? city cities[2]={{“A”,10000,11},{“B”,20000,11}}; 我知道这种赋值方法,但我就是想问问什么上面那种不行?别的数组是可以的啊
并不是aleda所说的原因,你可以试一下,即便不要结构体不要字符串
struct city
{
double population;
double housing;
double ave;
};
city cities[2];
cities[0]={22,10000,11};
cities[1]={22,20000,11};
这里与结构体匹配了,但是同样不可能通过
原因是C\C++语法是禁止结构体这样赋值的!!
你提到了初始化,的确结构体数组初始化是可以通过大括号这种形式进行初始化的,但是一定要注意
类似这样的语句,不是初始化,而是重新赋值!
cities[0]={“A”,10000,11};
cities[1]={“B”,20000,11};
结构体数组的初始化在city cities[2]这一句的时候就完成了(初始化了cities[0]和cities[1]这两个元素)
所以正确的初始化格式应该如下:
city cities[2] = {{“a”, 1,1,1}, {“b”, 1,1,1}}; // 还要注意一点,你问题处的成员少给了一个,结构体里有三个double,你只给了两
记住一点:
初始化永远是在定义的时候完成的,(或者如果是类的成员变量,其初始化是在构造函数的初始化列表中完成,如果你学的是C,可以忽略)
非定义处的只有重新赋值,没有初始化!
五、结构指针
前面讲过,&student1 表示结构体变量 student1 的首地址,即 student1 第一个项的地址。如果定义一个指针变量 p 指向这个地址的话,p 就可以指向结构体变量 student1 中的任意一个成员。
那么,这个指针变量定义成什么类型呢?只能定义成结构体类型,且指向什么结构体类型的结构体变量,就要定义成什么样的结构体类型。比如指向 struct STUDENT 类型的结构体变量,那么指针变量就一定要定义成 struct STUDENT* 类型。
下面将前面的程序用指针的方式修改一下:
# include <stdio.h>
# include <string.h>
struct AGE
{
int year;
int month;
int day;
};
struct STUDENT
{
char name[20]; //姓名
int num; //学号
struct AGE birthday; //生日
float score; //分数
};
int main(void)
{
struct STUDENT student1; /*用struct STUDENT结构体类型定义结构体变量student1*/
struct STUDENT *p = NULL; /*定义一个指向struct STUDENT结构体类型的指针变量p*/
p = &student1; /*p指向结构体变量student1的首地址, 即第一个成员的地址*/
strcpy((*p).name, "小明"); //(*p).name等价于student1.name
(*p).birthday.year = 1989;
(*p).birthday.month = 3;
(*p).birthday.day = 29;
(*p).num = 1207041;
(*p).score = 100;
printf("name : %s\n", (*p).name); //(*p).name不能写成p
printf("birthday : %d-%d-%d\n", (*p).birthday.year, (*p).birthday.month, (*p).birthday.day);
printf("num : %d\n", (*p).num);
printf("score : %.1f\n", (*p).score);
return 0;
}
输出结果是:
name : 小明
birthday : 1989-3-29
num : 1207041
score : 100.0
我们看到,用指针引用结构体变量成员的方式是:
(*指针变量名).成员名
注意,p 两边的括号不可省略,因为成员运算符“.”的优先级高于指针运算符“”,所以如果 *p 两边的括号省略的话,那么 *p.num 就等价于 *(p.num) 了。
从该程序也可以看出:因为指针变量 p 指向的是结构体变量 student1 第一个成员的地址,即字符数组 name 的首地址,*所以 p 和 (p).name 是等价的。
但是,“等价”仅仅是说它们表示的是同一个内存单元的地址,但它们的类型是不同的。指针变量 p 是 struct STUDENT* 型的,而 (p).name 是 char 型的。所以在 strcpy 中不能将 (*p).name 改成 p。用 %s 进行输入或输出时,输入参数或输出参数也只能写成 (*p).name 而不能写成 p。
同样,虽然 &student1 和 student1.name 表示的是同一个内存单元的地址,但它们的类型是不同的。&student1 是 struct STUDENT* 型的,而 student1.name 是 char* 型的,所以在对 p 进行初始化时,“p=&student1;”不能写成“p=student1.name”。因为 p 是 struct STUDENT* 型的,所以不能将 char* 型的 student1.name 赋给 p。
此外为了使用的方便和直观,用指针引用结构体变量成员的方式:(*指针变量名).成员名
可以直接用:指针变量名->成员名来代替,它们是等价的。“->”是“指向结构体成员运算符”,它的优先级同结构体成员运算符“.”一样高。p->num 的含义是:指针变量 p 所指向的结构体变量中的 num 成员。p->num 最终代表的就是 num 这个成员中的内容。
下面再将程序用“->”修改一下:
# include <stdio.h>
# include <string.h>
struct AGE
{
int year;
int month;
int day;
};
struct STUDENT
{
char name[20]; //姓名
int num; //学号
struct AGE birthday; /*用struct AGE结构体类型定义结构体变量birthday, 生日*/
float score; //分数
};
int main(void)
{
struct STUDENT student1; /*用struct STUDENT结构体类型定义结构体变量student1*/
struct STUDENT *p = NULL; /*定义struct STUDENT结构体类型的指针变量p*/
p = &student1; /*p指向结构体变量student1的首地址, 即第一项的地址*/
strcpy(p->name, "小明");
p->birthday.year = 1989;
p->birthday.month = 3;
p->birthday.day = 29;
p->num = 1207041;
p->score = 100;
printf("name : %s\n", p->name); //p->name不能写成p
printf("birthday : %d-%d-%d\n", p->birthday.year, p->birthday.month, p->birthday.day);
printf("num : %d\n", p->num);
printf("score : %.1f\n", p->score);
return 0;
}
输出结果是:
name : 小明
birthday : 1989-3-29
num : 1207041
score : 100.0
但是要注意的是,只有“指针变量名”后面才能加“->”,千万不要在成员名如 birthday 后面加“->”。
综上所述,以下 3 种形式是等价的:
- 结构体变量.成员名。
- (*指针变量).成员名。
- 指针变量->成员名。
其中第 3 种方式很重要,通常都是使用这种方式,另外两种方式用得不多。后面讲链表的时候用的也都是第 3 种方式。
指向结构体数组的指针
在前面讲数值型数组的时候可以将数组名赋给一个指针变量,从而使该指针变量指向数组的首地址,然后用指针访问数组的元素。结构体数组也是数组,所以同样可以这么做。
我们知道,结构体数组的每一个元素都是一个结构体变量。如果定义一个结构体指针变量并把结构体数组的数组名赋给这个指针变量的话,就意味着将结构体数组的第一个元素,即第一个结构体变量的地址,也即第一个结构变量中的第一个成员的地址赋给了这个指针变量。比如:
# include <stdio.h>
struct STU
{
char name[20];
int age;
char sex;
char num[20];
};
int main(void)
{
struct STU stu[5] = {{"小红", 22, 'F', "Z1207031"}, {"小明", 21, 'M', "Z1207035"}, {"小七", 23, 'F', "Z1207022"}};
struct STU *p = stu;
return 0;
}
此时指针变量 p 就指向了结构体数组的第一个元素,即指向 stu[0]。我们知道,当一个指针指向一个数组后,指针就可以通过移动的方式指向数组的其他元素。
这个原则对结构体数组和结构体指针同样适用,所以 p+1 就指向 stu[1] 的首地址;p+2 就指向 stu[2] 的首地址……所以只要利用 for 循环,指针就能一个个地指向结构体数组元素。
同样需要注意的是,要将一个结构体数组名赋给一个结构体指针变量,那么它们的结构体类型必须相同。
下面编写一个程序:
# include <stdio.h>
struct STU
{
char name[20];
int age;
char sex;
char num[20];
};
int main(void)
{
struct STU stu[3] = {{"小红", 22, 'F', "Z1207031"}, {"小明", 21, 'M', "Z1207035"}, {"小七", 23, 'F', "Z1207022"}};
struct STU *p = stu;
for (; p<stu+3; ++p)
{
printf("name:%s; age:%d; sex:%c; num:%s\n", p->name, p->age, p->sex, p->num);
}
return 0;
}
输出结果是:
name:小红; age:22; sex:F; num:Z1207031
name:小明; age:21; sex:M; num:Z1207035
name:小七; age:23; sex:F; num:Z1207022
此外同前面“普通数组和指针的关系”一样,当指针变量 p 指向 stu[0] 时,p[0] 就等价于 stu[0];p[1] 就等价于 stu[1];p[2] 就等价于 stu[2]……所以 stu[0].num 就可以写成 p[0].num,其他同理。下面将上面的程序用 p[i] 的方式修改一下:
# include <stdio.h>
struct STU
{
char name[20];
int age;
char sex;
char num[20];
};
int main(void)
{
struct STU stu[3] = {{"小红", 22, 'F', "Z1207031"}, {"小明", 21, 'M', "Z1207035"}, {"小七", 23, 'F', "Z1207022"}};
struct STU *p = stu;
int i = 0;
for (; i<3; ++i)
{
printf("name:%s; age:%d; sex:%c; num:%s\n", p[i].name, p[i].age, p[i].sex, p[i].num);
}
return 0;
}
输出结果是:
name:小红; age:22; sex:F; num:Z1207031
name:小明; age:21; sex:M; num:Z1207035
name:小七; age:23; sex:F; num:Z1207022
六、位域
有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几 个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。
struct 位域结构名
{ 位域列表 };
其中位域列表的形式为: 类型说明符 位域名:位域长度
例如:
struct bs
{
int a:8;
int b:2;
int c:6;
};
位域变量的说明与结构变量说明的方式相同。 可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如:
说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。对于位域的定义尚有以下几点说明:
struct bs
{
int a:8;
int b:2;
int c:6;
}data;
一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:
struct bs
{
unsigned a:4
unsigned :0 /*空域*/
unsigned b:4 /*从下一单元开始存放*/
unsigned c:4
};
在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。
位域的长度不能大于一个int的长度,也就是说不能超过32位。
位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:
struct k
{
int a:1
int :2 /*该2位不能使用*/
int b:3
int c:2
};
从以上分析可以看出,位域在本质上就是一种结构类型, 不过其成员是按二进位分配的。
位域的使用
位域的使用和结构成员的使用相同,其一般形式为: 位域变量名·位域名 位域允许用各种格式输出。
上例程序中定义了位域结构bs,三个位域为a,b,c。说明了bs类型的变量bit和指向bs类型的指针变量pbit。这表示位域也是可以使用指针的。
程序的9、10、11三行分别给三个位域赋值。( 应注意赋值不能超过该位域的允许范围)程序第12行以整型量格式输出三个域的内容。第13行把位域变量bit的地址送给指针变量pbit。第14行用指针 方式给位域a重新赋值,赋为0。第15行使用了复合的位运算符"&=", 该行相当于: pbit->b=pbit->b&3位域b中原有值为7,与3作按位与运算的结果为3(111&011=011,十进制值为 3)。同样,程序第16行中使用了复合位运算"|=", 相当于: pbit->c=pbit->c|1其结果为15。程序第17行用指针方式输出了这三个域的值。
为了节省空间,可以把几个数据压缩到少数的几个类型空间上,比如需要表示二个3位二进制的数,一个2位二进制的数,则可以用一个8位的字符表示之。
这个结构体所占空间为一个字节,8位。节省了空间。
struct
{
char a : 3;
char b : 3;
char c : 2;
};
这个结构体所占空间为一个字节,8位。节省了空间。
main(){
struct bs
{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1;
bit.b=7;
bit.c=15;
printf("%d,%d,%d\n",bit.a,bit.b,bit.c);
pbit=&bit;
pbit->a=0;
pbit->b&=3;
pbit->c|=1;
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c);
}
这个结构体所占空间为一个字节,8位。节省了空间。
位域可以和union结合起来一起用会更加方便
union hhh
{
char x;//注意位与外面变量占的字节大小要相同比较好。
struct
{
char a : 3;
char b : 3;
char c : 2;
}bitvar;
};
七、结构体和联合体的嵌套
嵌套可以有这些情况:结构体中嵌套结构体、结构体中嵌套联合体、联合体中嵌套联合体、联合体中嵌套结构体。
下面就一一对上面的嵌套形式进行说明,并讲解这些嵌套形式的使用方法。
结构体中嵌套结构体,有点像c++中类的继承,应该是使用的比较常见的。
C语言中的嵌套结构体表示在一个结构体中可以使用另一个结构作为成员。在C语言中定义结构体嵌套有两种方法:
- 通过独立的结构
- 通过嵌入式结构
独立结构
我们可以创建2个结构体,但在主结构中应该使用依赖其它结构体作为成员。我们来看看嵌套结构体的代码。
struct Date
{
int dd;
int mm;
int yyyy;
};
struct Employee
{
int id;
char name[20];
struct Date doj; // 嵌套一个结构体:Date,用于存储日期
}emp1;
如上所见,doj
(加入日期)是Date
类型的变量。 这里doj
用作为Employee
结构体中的成员。通过这样的方式,我们可以在其他结构体中使用Date
结构。
嵌入式结构体
我们可以在结构体内定义结构体,它比第一种方式需要更少的代码。但它不能用于其它结构中。
struct Employee
{
int id;
char name[20];
struct Date
{
int dd;
int mm;
int yyyy;
}doj;
}emp1;
访问嵌套结构体
我们可以通过Outer_Structure.Nested_Structure.member
访问嵌套结构的成员,如下所示:
e1.doj.dd
e1.doj.mm
e1.doj.yyyy
C
嵌套结构体示例
我们来看看C语言中嵌套结构体的一个简单例子。创建一个源代码文件:nested-structure.c,其代码如下
#include <stdio.h>
#include <string.h>
struct Employee
{
int id;
char name[20];
struct Date
{
int dd;
int mm;
int yyyy;
}doj;
}e1;
int main()
{
//storing employee information
e1.id = 1001;
strcpy(e1.name, "Maxsu");//copying string into char array
e1.doj.dd = 12;
e1.doj.mm = 11;
e1.doj.yyyy = 2018;
//printing first employee information
printf("employee id : %d\n", e1.id);
printf("employee name : %s\n", e1.name);
printf("employee date of joining (dd/mm/yyyy) : %d/%d/%d\n", e1.doj.dd, e1.doj.mm, e1.doj.yyyy);
return 0;
}
执行上面示例代码,得到以下结果 -
employee id : 1001
employee name : Maxsu
employee date of joining (dd/mm/yyyy) : 12/11/2018
结构体中嵌套联合体,也就是在结构体中加入了一个联合体,也是比较常见的。
示例代码:
enum DATA_PKG_TYPE
{
DATA_PKG1 = 1,
DATA_PKG2,
DATA_PKG3
};
struct data_pkg1
{
// ...
};
struct data_pkg2
{
// ...
};
struct data_pkg3
{
// ...
};
struct data_pkg
{
enum DATA_PKG_TYPE data_pkg_type;
union
{
struct data_pkg1 data_pkg1_info;
struct data_pkg2 data_pkg2_info;
struct data_pkg3 data_pkg3_info;
}data_pkg_info;
};
这里把struct data_pkg1、struct data_pkg2、struct data_pkg3三个结构体放到了struct data_pkg这个结构体里进行管理,把data_pkg_type与union里的三个结构体建立一一对应关系,我们需要用哪一结构体数据就通过data_pkg_type来进行选中。
在进行数据组包的时候,先给data_pkg_type进行赋值,确定数据包的类型,再给对应的union里的结构体进行赋值;在进行数据解析的时候,通过data_pkg_type来选择解析哪一组数据。
联合体中嵌套联合体
待补充。
联合体中嵌套结构体
typedef union
{
unsigned int u;
struct
{
unsigned char a :1;
unsigned char b :1;
unsigned char c :6;
unsigned char d :1;
} ST;
}UN;
- 注意联合体的定义,就是组成联合体的变量共用一个空间。这个 例子中变量u和ST共用一个空间
- 现在用的pc机大多为小段结构,我的结果13也是在小段机测试的,如果架构改变,结果可能不同
- 基于小段结构,数据的低字节保存在内存的低地址中,ST占用9位(Bit),与变量u(32Bit)共用低位的9位,根据小段结构,变量a的地址应该最低,往后依次是b,c,d
- un.u = 0; 执行这一步,变量所对应的空间二进制全部为0,即00000000 00000000 00000000 00000000
- un.ST.a = 1;执行这一步,变量最后一位变化,即00000000 00000000 00000000 00000001
- un.ST.b = 2;执行这一步,由于1位空间无法存储2,所以赋值被截断,原值不变
- un.ST.c = 3;执行这一步,变量第3-8位发生变化,变量值变为00000000 00000000 00000000 00001101
- un.ST.d = 4;执行这一步,由于1位空间无法存储4,所以赋值被截断,原值不变
所以最终的结果就是00000000 00000000 00000000 00001101 - printf("%d\n", un.u); 输出结果就是13
union{ /*定义一个联合*/
int i;
struct{ /*在联合中定义一个结构*/
char first;
char second;
}half;
}number;
八、使用结构体实现面向对象编程(函数指针)
一个对象就是由或多或少的针对这个对象的过程构成的,当然其中是少不了必要的属性。
一个过程是针对一个或者是多个对象所进行的操作。两者是可以互相转换的,关键是哪一种方式更能适合你现在的需求,更能让你的软件开发锦上添花。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5kaHgLD5-1612002946554)(file:///C:/Users/10521/AppData/Local/Temp/msohtmlclip1/01/clip_image001.png)]
我个人认为一般情况下,一个更容易扩展、维护的软件通常采用的是OOP的思想,添加一个原本不存在的相对无关单独的个体,总比在一个已经存在的过程内硬塞进去一个对象要简单;而且面向过程更容易导致混乱的维护。
C是一门面向过程的语言,但它依旧可以实现大多数面向对象所能完成的工作。比如面向对象的三大特性:封装、继承、多态。我们以下图来写代码举例子。
封装
由于面象向对象是将数据与方法封装到一个类里。使用者无需关心类是怎么实现的。在 C_OOP 中贯彻了这一思想,C中有一种复杂的数据结构叫做struct。struct是C里面的结构体。
如上图假如我们要对鸟bird进行封装,bird可能包括姓名、颜色、栖息地、重量、属性等信息。我们就可以对它封装如下:
struct Bird{
char name[20];//姓名
char color; //颜色
char addr[30]; //栖息地
int weight; //体重
int other; //属性
};
继承
在常见用C语言实现继承的机制中,多半是用结构体组合实现的,同样利用struct,我们来创建一个Bird结构,同时继承结构体Bird,如下:
struct fBird{
struct Bird p;
char fly[20]; //飞翔
int scream; //鸣叫
};
多态
C_OOP中的一个核心就是多态,C中对于多态的实现可以借助函数指针来实现。为了简单起见,我们假设Bird这个结构体中,只有一个函数指针。
struct Bird{
void (*print)(void *p);
};
struct fBird{
struct Bird p;
};
而Bird和fBird这两个结构体的print函数实现如下:
void printBird(void *Bird){
if(NULL == Bird)
return ;
struct Bird *p = (struct Bird *)Bird;
printf("run in the Bird!!\n");
}
void printfBird(void *Bird){
if(NULL == Bird)
return ;
struct Bird *p = (struct Bird *)Bird;
printf("run in the fBird!!\n");
}
我们写一个函数来调用他们:
void print(void *Bird){
if(NULL == Bird)
return ;
struct Bird *p = (struct Bird *)Bird;
p->print(Bird);
}
int main(){
struct Bird bird;
struct fBird fbird;
Bird.print = printBird;
fBird.p.print = printfBird;
print(&bird); //实参为Bird的对象
print(&fbird); //实参为fBird的对象
return 0;
}
他们的输出为:
run in the Bird!!
run in the fBird!!
其实这个也不难理解,无论是fBird还是Bird,他们在内存中只有一个变量,就是那个函数指针,而void表示任何类型的指针,当我们将它强制转换成**struct Bird类型时,p->print指向的自然就是传入实参的print地址。
函数指针变量
一个数据变量的内存地址可以存储在相应的指针变量中,函数的首地址也以存储在某个函数指针变量中。这样,我就可以通过这个函数指针变量来调用所指向的函数了。
在C系列语言中,任何一个变量,总是要先声明,之后才能使用的。函数指针变量也应该要先声明。
函数指针变量的声明:
void (*funP)(int) ; //声明一个指向同样参数、返回值的函数指针变量。
(整个函数指针变量的声明格式如同函数myFun的声明处一样,只不过——我们把myFun改成(*funP)而已,这样就有了一个能指向myFun函数的指针了。当然,这个funP指针变量也可以指向所有其它具有相同参数及返回值的函数。)
#include <stdio.h>
struct stu{
int x;
int y;
char *name;
int (*p)(int,int);
};
int sum(int x,int y){
return x+y;
}
int main(){
struct stu students = {4,5,"xky",NULL};
students.p = sum;
printf("%d",students.p(students.x,students.y));
printf("\n%d",students.p(5,6));
return 0;
}
以上内容很多事参考各种博客,感谢大家的分享。