11构造类型
构造类型
一、结构体
结构体类型概念
结构体:一种构造类型,由若干成员组成,成员可以是基本数据类型,也可以是构造类型
struct 结构体名
{
成员列表
};
结构体变量定义
方式一:先声明结构体类型,再定义变量
struct Product product1;
struct Product product2;
//Product是结构体类型
//product1、product2是结构体变量名
结构体变量的定义不仅要求指定变量为结构体类型,而且要求指定为某一特定的结构体类型。而基本变量的定义只需要指定类型即可。
定义结构体变量后,系统会为其分配内存单元,大小为定义成员列表的大小总和。
方式二:在声明结构类型的同时定义变量
struct Product{
//成员列表
}product1,product2;
方式三:直接定义结构体类型变量
struct{
//成员列表
}product1,product2;
类型与变量不同,在编译时,计算机不会对类型进行分配内存,只对变量分配内存
结构体变量的引用
对结构体变量进行引用时,不能直接将结构体变量作为一个整体进行输入和输出,在进行 输出时需要对结构体变量进行赋值、存取或运算。赋值方法:结构变量名.成员名=值。
如果成员变量本身是结构体变量,则需要一级一级的赋值,即智能对最低一级进行赋值、运算、存取
#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;
}
结构体类型的初始化
#include<stdio.h>
struct Student /*学生结构*/
{
char cName[20]; /*姓名*/
char cSex; /*性别*/
int iGrade; /*年级*/
} student1={"HanXue",'W',3}; /*定义变量并设置初始值*/
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;
}
二、结构体数组
定义结构体数组
struct 结构体名
{
成员列表
}数组名[];
struct Student{
char cName[20];
int iNumber;
char cSex;
int iGrade;
}student[5];
初始化结构体数组
#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:点运算符
#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:指向运算符
注意:
p->i; //表示指向的结构体变量中成员 i 的值
p->i++; //表示指向结构体成员变量中 i 值,在使用后该值+1;
++p->i; //表示指向结构体成员变量 i 的值加1后进行使用。
(++p)->i; //表示指向结构体成员变量 i 的下一个元素的地址,然后取得该元素的值
(p++)->i; //表示先取得当前元素的成员值,在使得指针p指向下一个元素的地址
#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、使用结构体变量作为函数的参数
将结构体变量作为函数参数进行传递时,其实传递的是全部成员的值,也就是将实参中成员的值一一赋值给对应的形参成员。因此,形参的改变不会影响到实参。
void Display(struct Student stu);
在形参的位置使用结构体变量,但在函数调用期间,形参也要占用大量的内存,这种方式在时间和空间上的消耗都很大
#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、使用指向结构体变量的指针作为函数参数
#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、使用结构体变量的成员作为函数参数
void Display(student.fscore[0]);
包含结构体的结构
#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本身包含的成员变量扩起来
共用体
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;
}
在程序中改变共用体中一个成员的值,其他成员也会随之改变。当给某个特定的成员进行赋值时,其他成员的值也会有一致的含义,因为它们每个二进制位都被新值所覆盖。
共用体变量的初始化
对于共用体变量初始化时,只需要一个初始化值就够了,其类型必须与共用体的第一个成员的类型相一致
#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、存放成员的起始地址必须是该成员有效对齐值的整数倍。
举四个例子

假如结构体起始地址是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 |
代码验证如下:

六、共用体
共用体的概念 :
使几个不同的变量共占同一段内存的结构称为 “共用体”类型的结构。
共用体定义
union 共用体名
{
成员表列
}变量表列;
//例如:
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个字节。
共用体类型数据的特点:
- 同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一种,而不是同时存放几种。
- 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员就失去作用。
- 共用体变量的地址和它的各成员的地址都是同一地址。
- 不能对共用体变量名赋值,也不能企图引用变量名来得到一个值,又不能在定义共用体变量时对它初始化。
- 不能把共用体变量作为函数参数,也不能使函数带回共用体变量,但可以使用指向共用体变量的指针 。
- 共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。
七、枚举类型
在实际问题中,有些变量的取值被限定在一个有限的范围内。 例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等。 如果把这些量说明为整型,字符型或其它类型显然是不妥当的。 为此,C语言提供了一种称为“枚举”的类型。设有变量a,b,c被说明为上述的weekday:
枚举定义与引用
//可采用下述任一种方式:
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
#include <stdio.h> typedef int INTEGER; void main() { INTEGER i = 1; int j = 2; printf("%d, %d\n\n", i, j); } -
声明结构类型:
Typedef struct{ int month; int day; int year;}DATE;举例:
#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];
#include <stdio.h> typedef int NUM[100]; void main() { NUM num = {0}; printf("%d\n\n", sizeof(num)); } -
声明STRING为字符指针类型 :typedef char* STRING;
#include <stdio.h> typedef char* P; void main() { P p1; p1 = "I love Fishc.com"; printf("%s\n", p1); } -
声明 POINTER 为指向函数的指针类型,该函数返回整型值: typedef int (*POINTER)();
#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*
格外注意,一个有分号,一个没有分号!

浙公网安备 33010602011771号