Loading... # 构造类型 ## 一、结构体 结构体类型概念 结构体:一种构造类型,由若干成员组成,成员可以是基本数据类型,也可以是构造类型 ```c struct 结构体名 { 成员列表 }; ``` ### 结构体变量定义 **方式一**:先声明结构体类型,再定义变量 ```c struct Product product1; struct Product product2; //Product是结构体类型 //product1、product2是结构体变量名 ``` 结构体变量的定义不仅要求指定变量为结构体类型,而且要求指定为某一特定的结构体类型。而基本变量的定义只需要指定类型即可。 定义结构体变量后,系统会为其分配内存单元,大小为定义成员列表的大小总和。 **方式二**:在声明结构类型的同时定义变量 ```c struct Product{ //成员列表 }product1,product2; ``` **方式三**:直接定义结构体类型变量 ```c struct{ //成员列表 }product1,product2; ``` **类型**与**变量**不同,在编译时,计算机不会对类型进行分配内存,只对变量分配内存 ### 结构体变量的引用 对结构体变量进行引用时,不能直接将结构体变量作为一个整体进行输入和输出,在进行 输出时需要对结构体变量进行赋值、存取或运算。赋值方法:结构变量名.成员名=值。 如果成员变量本身是结构体变量,则需要一级一级的赋值,即智能对最低一级进行赋值、运算、存取 ```c #include <stdio.h> #include <stdlib.h> struct test{ int num; char name; }test01; int main() { scanf("%d",&test01.num); printf("%d",test01.num); return 0; } ``` **结构体类型的初始化** ```c #include<stdio.h> struct Student /*学生结构*/ { char cName[20]; /*姓名*/ char cSex; /*性别*/ int iGrade; /*年级*/ } student1={"HanXue",'W',3}; /*定义变量并设置初始值*/ ``` ```c int main() { struct Student student2={"WangJiasheng",'M',3};/*定义变量并设置初始值*/ /*将第一个结构体中的数据输出*/ printf("the student1's information:\n"); printf("Name: %s\n",student1.cName); printf("Sex: %c\n",student1.cSex); printf("Grade: %d\n",student1.iGrade); /*将第二个结构体中的数据输出*/ printf("the student2's information:\n"); printf("Name: %s\n",student2.cName); printf("Sex: %c\n",student2.cSex); printf("Grade: %d\n",student2.iGrade); return 0; } ``` ## 二、结构体数组 ### **定义结构体数组** ```c struct 结构体名 { 成员列表 }数组名[]; struct Student{ char cName[20]; int iNumber; char cSex; int iGrade; }student[5]; ``` ### 初始化结构体数组 ```c #include<stdio.h> struct Student /*学生结构*/ { char cName[20]; /*姓名*/ int iNumber; /*学号*/ char cSex; /*性别*/ int iGrade; /*年级*/ } student[5]={{"WangJiasheng",12062212,'M',3}, {"YuLongjiao",12062213,'W',3}, {"JiangXuehuan",12062214,'W',3}, {"ZhangMeng",12062215,'W',3}, {"HanLiang",12062216,'M',3}}; /*定义数组并设置初始值*/ int main() { int i; /*循环控制变量*/ for(i=0;i<5;i++) /*使用for进行5次循环*/ { printf("NO%d student:\n",i+1); /*首先输出学生的名次*/ printf("Name: %s, Number: %d\n",student[i].cName,student[i].iNumber);/*使用变量i做下标,输出数组中的元素数据*/ printf("Sex: %c, Grade: %d\n",student[i].cSex,student[i]. iGrade); printf("\n"); /*空格行*/ } return 0; } ``` ## 三、结构体指针 定义:结构体类型 *指针名 #### 引用方式1:点运算符 ```c #include<stdio.h> int main() { struct Student /*学生结构*/ { char cName[20]; /*姓名*/ int iNumber; /*学号*/ char cSex; /*性别*/ int iGrade; /*年级*/ }student={"SuYuQun",12061212,'W',2}; /*对结构变量进行初始化*/ struct Student *pStruct; /*定义结构体类型指针*/ pStruct=&student; /*指针指向结构体变量*/ printf("-----the student's information-----\n"); /*消息提示*/ printf("Name: %s\n",(*pStruct).cName); /*使用指针引用变量中的成员*/ printf("Number: %d\n",(*pStruct).iNumber); printf("Sex: %c\n",(*pStruct).cSex); printf("Grade: %d\n",(*pStruct).iGrade); return 0; } ``` #### 引用方式2:指向运算符 注意: ```c p->i; //表示指向的结构体变量中成员 i 的值 p->i++; //表示指向结构体成员变量中 i 值,在使用后该值+1; ++p->i; //表示指向结构体成员变量 i 的值加1后进行使用。 (++p)->i; //表示指向结构体成员变量 i 的下一个元素的地址,然后取得该元素的值 (p++)->i; //表示先取得当前元素的成员值,在使得指针p指向下一个元素的地址 ``` ```c #include<stdio.h> #include<string.h> struct Student /*学生结构*/ { char cName[20]; /*姓名*/ int iNumber; /*学号*/ char cSex; /*性别*/ int iGrade; /*年级*/ }student; /*定义变量*/ int main() { struct Student *pStruct; /*定义结构体类型指针*/ pStruct=&student; /*指针指向结构体变量*/ strcpy(pStruct->cName,"SuYuQun"); /*将字符串常量复制到成员变量中*/ pStruct->iNumber=12061212; /*为成员变量赋值*/ pStruct->cSex='W'; pStruct->iGrade=2; printf("-----the student's information-----\n"); /*消息提示*/ printf("Name: %s\n",student.cName); /*使用变量直接输出*/ printf("Number: %d\n",student.iNumber); printf("Sex: %c\n",student.cSex); printf("Grade: %d\n",student.iGrade); return 0; } ``` ## 四、结构体作为函数参数 ### 1、使用结构体变量作为函数的参数 将结构体变量作为函数参数进行传递时,其实传递的是全部成员的值,也就是将实参中成员的值一一赋值给对应的形参成员。因此,形参的改变不会影响到实参。 ```c void Display(struct Student stu); ``` 在形参的位置使用结构体变量,但在函数调用期间,形参也要占用大量的内存,这种方式在时间和空间上的消耗都很大 ```c #include<stdio.h> struct Student /*学生结构*/ { char cName[20]; /*姓名*/ float fScore[3]; /*分数*/ }student={"SuYuQun",98.5f,89.0,93.5f}; /*定义变量*/ void Display(struct Student stu) /*形参为结构体变量*/ { printf("-----Information-----\n"); /*提示信息*/ printf("Name: %s\n",stu.cName); /*引用结构成员*/ printf("Chinese: %.2f\n",stu.fScore[0]); printf("Math: %.2f\n",stu.fScore[1]); printf("English: %.2f\n",stu.fScore[2]); /*计算平均分数*/ printf("Average score:%.2f\n",(stu.fScore[0]+stu.fScore[1]+stu.fScore[2])/3); } int main() { Display(student); /*调用函数,结构变量作为实参进行传递*/ return 0; } ``` ### 2、使用指向结构体变量的指针作为函数参数 ```c #include<stdio.h> struct Student /*学生结构*/ { char cName[20]; /*姓名*/ float fScore[3]; /*分数*/ }student={"SuYuQun",98.5f,89.0,93.5f}; /*定义变量*/ void Display(struct Student* stu) /*形参为结构体变量的指针*/ { printf("-----Information-----\n"); /*提示信息*/ printf("Name: %s\n",stu->cName); /*使用指针引用结构体变量中的成员*/ printf("English: %.2f\n",stu->fScore[2]); stu->fScore[2]=90.0f; /*更改成员变量的值*/ } int main() { struct Student* pStruct=&student; /*定义结构体变量指针*/ Display(pStruct); /*调用函数,结构变量作为实参进行传递*/ printf("Changed English: %.2f\n",pStruct->fScore[2]); /*输出成员的值*/ return 0; } ``` ### 3、使用结构体变量的成员作为函数参数 ```c void Display(student.fscore[0]); ``` 包含结构体的结构 ```c #include<stdio.h> struct date /*时间结构*/ { int year; /*年*/ int month; /*月*/ int day; /*日*/ }; struct student /*学生信息结构*/ { char name[30]; /*姓名*/ int num; /*学号*/ char sex; /*性别*/ struct date birthday; /*出生日期*/ }student={"SuYuQun",12061212,'W',{1986,12,6}}; /*为结构变量初始化*/ int main() { printf("-----Information-----\n"); printf("Name: %s\n",student.name); /*输出结构成员*/ printf("Number: %d\n",student.num); printf("Sex: %c\n",student.sex); printf("Birthday: %d,%d,%d\n",student.birthday.year, student.birthday.month,student.birthday.day);/*将成员结构体数据输出*/ return 0; } ``` 因为date成员变量本身是结构体,则在使用的时候需要用大括号将date本身包含的成员变量扩起来 共用体 ```c union 共用体名 { 成员列表 }变量列表; #include<stdio.h> union DataUnion /*声明共用体类型*/ { int iInt; /*成员变量*/ char cChar; }; int main() { union DataUnion Union; /*定义共用体变量*/ Union.iInt=97; /*为共用体变量中成员赋值*/ printf("iInt: %d\n",Union.iInt); /*输出成员变量数据*/ printf("cChar: %c\n",Union.cChar); Union.cChar='A'; /*改变成员的数据*/ printf("iInt: %d\n",Union.iInt); /*输出成员变量数据*/ printf("cChar: %c\n",Union.cChar); return 0; } ``` 在程序中改变共用体中一个成员的值,其他成员也会随之改变。当给某个特定的成员进行赋值时,其他成员的值也会有一致的含义,因为它们每个二进制位都被新值所覆盖。 共用体变量的初始化 对于共用体变量初始化时,只需要一个初始化值就够了,其类型必须与共用体的第一个成员的类型相一致 ```c #include<stdio.h> union DataUnion /*声明共用体类型*/ { int iInt; /*成员变量*/ char cChar; }; int main() { union DataUnion Union={97}; /*定义共用体变量,并进行初始化*/ printf("iInt: %d\n",Union.iInt); /*输出成员变量数据*/ printf("cChar: %c\n",Union.cChar); return 0; } ``` 如果共用体中的第一个成员是结构体类型,则初始化值可以包含多个用于初始化该结构的表达式 ①对于共用体同一内存中可以存放几种不同的数据类型,但是每次只能存放一种,而不能同时存放所有的类型,即只有一个成员变量起作用。 ②共用体中起作用的是最后一个放进去的成员,存放入新的成员后,原成员就失去作用 ③共用体变量的地址和其它各成员的地址是一样的 ④不能对共用体变量名赋值,要求不能用 变量名来得到一个值。 ## 五、 结构体字节对齐规则 三个概念:自身对齐值、指定对齐值、有效对齐值。(如果题目中没要求按照对齐分配出,则默认使用按需分配) **自身对齐值**:数据类型本身的对齐值,例如char类型的自身对齐值是1,short类型是2; **指定对齐值**:编译器或程序员指定的对齐值,32位单片机的指定对齐值默认是4; **有效对齐值**:自身对齐值和指定对齐值中较小的那个。 对齐有两个规则: 1、不但结构体的成员有有效对齐值,结构体本身也有对齐值,这主要是考虑结构体的数组,对于结构体或者类,要将其补齐为其有效对齐值的整数倍。结构体的有效对齐值是其最大数据成员的自身对齐值; 2、存放成员的起始地址必须是该成员有效对齐值的整数倍。 举四个例子 ![](https://wmicheng.top/images/images/gz01.jpg) 假如结构体起始地址是0x0000, 成员a的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0000是1的整数倍,故a存放起始地址是0x0000,占一个字节; 成员b的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0001是1的整数倍,故b存放起始地址是0x0001,占一个字节; 成员c的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0002是1的整数倍,故c存放起始地址是0x0002,占一个字节; 成员d的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0003是1的整数倍,故d存放起始地址是0x0003,占一个字节; 此时结构体A的有效对齐值是其最大数据成员的自身对齐值,它的成员都是char类型,故结构体A的有效对齐值是1. 结构体A的存储结构如下,其中Y是根据规则1补齐的字节,x是规则2补齐的字节。 | 0x0000 | 0x00001 | 0x0002 | 0x0003 | | ------ | ------- | ------ | ------ | | a | b | c | d | 根据以上规则可以知道其他结构体的存储结构: 结构体B占6个字节 | 0x0000 | 0x00001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | | ------ | ------- | ------ | ------ | ------ | ------ | | a | x | b | b | c | d | 结构体C占12个字节 成员a的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0000是1的整数倍,故a存放起始地址是0x0000,占一个字节; 成员b的自身对齐值4,指定对齐值4,所以有效对齐值是4,地址0x0004是4的整数倍,故b存放起始地址是0x0004,占四个字节; 成员c的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0008是1的整数倍,故c存放起始地址是0x0008,占一个字节; 成员d的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0009是1的整数倍,故d存放起始地址是0x0009,占一个字节; 结构体C的成员占据10个字节,而结构体C的有效对齐值是其成员b的自身对齐值4,10不是4的倍数,故还需补齐两个字节,此时结构体C占据12个字节,是4的倍数 如下: | 0x0000 | 0x00001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | 0x0006 | 0x0007 | 0x0008 | 0x0009 | 0x000A | 0x000B | | ------ | ------- | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | | a | x | x | x | b | b | b | b | c | d | Y | Y | 结构体D占16个字节 | 0x0000 | 0x00001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | 0x0006 | 0x0007 | 0x0008 | 0x0009 | 0x000A | 0x000B | 0x000C | 0x000D | 0x000E | 0x000F | | ------ | ------- | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | | a | x | x | x | b | b | b | b | b | b | b | b | c | d | Y | Y | 代码验证如下: ![](https://wmicheng.top/images/images/gz02.jpg) ## 六、共用体 ### 共用体的概念 : 使几个不同的变量共占同一段内存的结构称为 “共用体”类型的结构。 ### 共用体定义 union 共用体名 { 成员表列 }变量表列; ```c //例如: union data { int i; char ch; float f; }a,b,c; //或者 union data { int i; char ch; float f; }; union data a,b,c; ``` ### 共用体引用 只有先定义了共用体变量才能引用它,而且不能引用共用体变量,而只能引用共用体变量中的成员。例如:前面定义了a、b、c为共用体变量: - a.i (引用共用体变量中的整型变量i) - a.ch(引用共用体变量中的字符变量ch) - a.f (引用共用体变量中的实型变量f) ### 共用体和结构体的比较: - 结构体变量所占内存长度是各成员占的内存长度之和。每个成员分别占有其自己的内存单元。 - 共用体变量所占的内存长度等于最长的成员的长度。 例如: 上面定义的“共用体”变量a、b、c各占4个字节(因为一个实/整型变量占4个字节),而不是各占4+1+4=9个字节。 ### 共用体类型数据的特点: 1. 同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一种,**而不是同时存放几种**。 2. 共用体变量中起作用的成员是**最后一次存放**的成员,在存入一个新的成员后原有的成员就失去作用。 3. 共用体变量的地址和它的各成员的地址都是同一地址。 4. 不能对共用体变量名赋值,也不能企图引用变量名来得到一个值,又不能在定义共用体变量时对它初始化。 5. 不能把共用体变量作为函数参数,也不能使函数带回共用体变量,但可以使用指向共用体变量的指针 。 6. 共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。 ## 七、枚举类型 在实际问题中,有些变量的取值被限定在一个有限的范围内。 例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等。 如果把这些量说明为整型,字符型或其它类型显然是不妥当的。 为此,C语言提供了一种称为“枚举”的类型。设有变量a,b,c被说明为上述的weekday: ### 枚举定义与引用 ```c //可采用下述任一种方式: enum weekday{ sun,mon,tue,wed,thu,fri,sat }; enum weekday a, b, c; //或者为: enum weekday{ sun,mon,tue,wed,thu,fri,sat }a, b, c; //或者为: enum { sun,mon,tue,wed,thu,fri,sat }a, b, c; ``` ### 枚举类型中需要注意的地方: - 在“枚举”类型的定义中列举出所有可能的取值,被说明为该“枚举”类型的变量取值不能超过定义的范围。 - 应该说明的是,枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。 - 在枚举值表中应罗列出所有可用值。这些值也称为枚举元素。 - 在C编译中,对枚举元素按常量处理,故称枚举常量。它们不是变量,不能对它们赋值。 - 枚举元素作为常量,它们是有值的,C语言编译按定义时的顺序使它们的值为0,1,2… - 枚举值可以用来作判断比较。 - 一个整数不能直接赋给一个枚举变量。 ## 八、用typedef定义类型 用typedef声明新的类型名来代替已有的类型名: - 声明INTEGER为整型:typedef int INTEGER ```c #include <stdio.h> typedef int INTEGER; void main() { INTEGER i = 1; int j = 2; printf("%d, %d\n\n", i, j); } ``` - 声明结构类型: ```c Typedef struct{ int month; int day; int year;}DATE; ``` > 举例: ```c #include <stdio.h> typedef struct { int month; int day; int year; }DATE; void main() { DATE date_one; date_one.month = 12; date_one.day = 31; date_one.year = 2012; printf("%d - %d - %d \n", date_one.year, date_one.month, date_one.day); } ``` - 声明NUM为整型数组类型 :typedef int NUM[100]; ```c #include <stdio.h> typedef int NUM[100]; void main() { NUM num = {0}; printf("%d\n\n", sizeof(num)); } ``` - 声明STRING为字符指针类型 :typedef char* STRING; ```c #include <stdio.h> typedef char* P; void main() { P p1; p1 = "I love Fishc.com"; printf("%s\n", p1); } ``` - 声明 POINTER 为指向函数的指针类型,该函数返回整型值: typedef int (*POINTER)(); ```c #include <stdio.h> typedef void (*P)(); void fun(); void main() { P p1; // void (*p1)(); char a[10] = "Fishc.com!"; printf("%d %d\n", a, &a);//数组地址和数组名地址相同 p1 = fun;//等同于p1 = &fun; 函数名称和函数地址相同,与数组地址和数组名地址道理相同 (p1)(); } void fun() { printf("I love Fishc.com!\n"); } ``` 用typedef定义类型的方法:先按定义变量的方法写出定义体(如:int i) ,将变量名换成新类型名(例如:将i换成COUNT)。即在最前面加typedef(例如:typedef int COUNT),然后可以用新类型名去定义变量 (例如:COUNT i, j;) ### 关于 typedef的说明: - 用typedef 可以声明各种类型名,但不能用来定义变量。 - 用typedef 只是对已经存在的类型增加一个类型名,而没有创造新的类型。 - 当不同源文件中用到同一类型数据时,常用typedef 声明一些数据类型,把它们单独放在一个文件中,然后在需要用到它们的文件中用#include命令把它们包含进来。 - 使用typedef 有利于程序的通用与移植。 - typedef与#define有相似之处,例如:typedef int COUNT;#define COUNT int 的作用都是用COUNT 代表 int。但是,它们二者是不同的。 - \#define是在预编译时处理的,它只能作简单的字符串替换,而typedef是在编译时处理的。实际上它并不是作简单的字符串替换,而是采用如同定义变量的方法那样来声明一个类型。 ### 区别: typedef 和 define `typedef (int*) p1;`和 `#define p2 int*` 格外**注意**,一个有分号,一个没有分号! 最后修改:2022 年 07 月 23 日 © 允许规范转载 打赏 赞赏作者 赞 如果觉得我的文章对你有用,请随意赞赏