结构体是一种构造数据类型,用于把不同类型的数据组合成一个整体;
struct 结构名 // 结构名可省,但会导致重用不便
{
类型 成员1; // 可定义多个成员
类型 成员2; // 成员也可以是其他结构体
}; // 末尾的分号勿漏
结构体类型定义只描述结构的阻止形式,不分配内存;
结构体类型定义的作用域与变量的作用域类似,若将类型定义放在某个函数内,则只能在该函数内定义这种结构体类型的变量;
先定义结构体类型,再定义结构体变量;
struct complex // 定义复数的结构体类型
{
float re; // 复数的实部
float im; // 复数的虚部
}; // 注意分号别漏
complex data; // 先定义结构体类型,后定义结构体变量
定义结构类型的同时定义结构体变量:
struct complex // 定义复数的结构体类型
{
float re; // 复数的实部
float im; // 复数的虚部
}data1, data2; // 定义结构体complex的同时定义结构体变量data1和data2;
直接定义结构体变量(省略结构体名称)
struct // 定义无“结构名”的结构体,即所谓直接定义结构体变量
{
float re; // 复数的实部
float im; // 复数的虚部
}data1, data2; // 定义结构体complex的同时定义结构体变量data1和data2;
说明
示例程序:
struct student
{
int num; // 学号
char name[20]; // 姓名
char sex; // 性别
int age; // 年龄
float score; // 分数
char addr[30]; // 家庭住址
};
student stu1, stu2;
引用方式:结构体变量名\textcolor{red}{.}成员名;
stu1.num = 10; // 赋值
stu1.score += stu2.score // 计算
引用规则:结构体变量不能整体引用,只能引用变量成员:错误示范:
if (stu1 == stu2) // 不能直接使用结构体变量整体
printf(“%d %s %c %d %f %s\n” stu1); // 无法直接引用结构体变量整体
stu1={101,“Wan Li”,‘M’,19,87.5,“DaLian”}; // 整体进行赋值是不可以的!
结构体嵌套时应逐级引用;
结构体变量可以整体赋值给另一个结构体变量,可用于结构体的初始化;
注意到,结构体变量的定义有三种方式,在定义变量的同时都可以对结构体变量进行初始化:
struct student
{
int num; // 学号
char name[20]; // 姓名
char sex; // 性别
int age; // 年龄
float score; // 分数
char addr[30]; // 家庭住址
};
student stu1 = {112,“Wang Lin”,‘M’,19, “200 Beijing Road”}; // 定义结构体变量的同时进行初始化。前面提到**几条狗提变量不能整体引用,即:
// stu1 = {112,“Wang Lin”,‘M’,19, “200 Beijing Road”}; 是错误的用法
sizeof(变量或类型说明符)
:
#include<stdio.h>
main()
{
char str[20];
struct date
{ int year, month, day; } today;
struct address
{ char name[30], street[40], city[20], state[2];
unsigned long int zip; } wang;
printf("char: %d\t", sizeof(char));
printf("int: %d\t", sizeof(int));
printf("long: %d\t", sizeof(long));
printf("float: %d\n", sizeof(float));
printf("double: %d\t", sizeof(double));
printf("str: %d\t", sizeof(str));
printf("date: %d\t", sizeof(struct date));
printf("wang: %d\n", sizeof(wang));
}
结构体是一种自定义的数据类型,它和其他基本数据类型int,float
等没太大区别,除了结构体类型非常灵活也相对复杂。因此,结构体数组和整型数组等其他基本数组使用方法类似。
结构体变量的定义有三种方式,这三种方式中也可以定义结构体数组。最常用的方式是先定义结构体类型再定义结构变量(数组):
struct student
{
int num;
char name[20];
char sex;
int age;
};
struct student stu[3]; // 最常用的情况:先定义结构体类型,后定义结构体数组
// 结构体数组的初始化:本质上是分别初始化数组中的每个结构体元素
// struct stu[3] = {{...}, {...}, {...}};
struct student stu[] = { {100,“Wang Lin”,‘M’,20},
{101,“Li Gang", 'M',19}
{110,“Liu Yan”,‘F’,19}
};
结构体数组的引用:先通过数组访问符[]
访问数组中的结构体类型元素,接着使用成员访问符.
访问成员:stu[1].num = 20;
;
示例程序:
struct s
{
int x;
int *y;
};
int data[5] = {10,20,30,40,50};
struct s array[5] = {100,&data[0],
200,&data[1],
300,&data[2],
400,&data[3],
500,&data[4]
};/* arrayy: 结构数 结构数组,初始化 初始化 */
main()
{
int i=0; /* 说明变量i并赋初值 */
struct s s_var; /* s_ver: 一般的结构变量 */
s_var = array[0]; /* 整体赋给s_var */
printf("%d\n", s_var.x); // 取取s var s_var的成员 的成员xx的值:100
printf("%d\n", *s_var.y); // 取s_var的成员指针y所指的内容:10
printf("%d\n", array[i].x); // 取array[i]的x的值,i=0,故:100
printf("%d\n", *array[i].y); // 取array[i]的指针y所指的内容,i=0,故:10
printf("%d\n", ++array[i].x); // 取array[i]的x的值,x加1后输出:101
printf("%d\n", ++*array[i]].y); // 取array[i]的y所指的内容加1输出:11
printf("%d\n", array[++i].x); // i先加1后取array[1]的成员x的值:200
printf("%d\n", *++array[i].y); // 将array[i]的指针y先加1后再取y所指的内容输出:30
printf("%d\n", (*array[i].y)++); // 取array[i]的指针y的内容,输出y,y的内容再加1:31
printf("%d\n", *(array[i].y++)); // 取array[i]的指针y的内容,输出 y,指针y再加 1:31
printf("%d\n", *array[i].y++); // 同 *(array[i].y++),根据运算符的结合性,输出:40
printf("%d\n", *array[i].y); // 输出 y(注意上一行后指针y自增1了),故结果:50
}
struct date * pdate, today;
。该语句声明了一个结构体类型的指针pdate
和一个结构体变量today
,注意today
不是指针;结构指针->成员名
等价于(*结构指针).成员名
。注意:
() [] . ->
这四个运算符的优先级都是最高的15级,并从左到右进行接合。因此,使用(*结构指针).成员名
的方式访问指针指向的结构体成员时,小括号()
一定不能漏掉;struct s
{
int x;
int *y; /* y:: 结构中的成员是指向整型的指针 结构中的成员是指向整型的指针 */
} *p; /* p: 指向结构的指针 */
int data[5] = {10,20,30,40,50}; /* data: 整型数组 */
struct s array[5] = {100,&data[0],
200,&data[1],
300,&data[2],
400,&data[3],
500,&data[4]
};/* arrayy: 结构数 结构数组,初始化 初始化 */
main()
{
p = array; /* 指针p指向结构数组的首地址 */
printf("%d\n", p->x); // 取结构指针p指向的结构的成员x的值,输出 100
printf("%d\n", (*p).x); // 取结构指针p的内容所指的成员x的值,输出 100;访问方式不同
printf("%d\n", *p->y); // 取结构指针p的指针成员y所指的内容,输出 10
printf("%d\n", *(*p).y);// 取指针p的内容的指针成员y的内容,输出 10
printf("%d\n", ++p->x); // p所指的 x 加1,x 先加1后再输出101,p不加1
printf("%d\n", (++p)->x);// p指向下一个变量的成员 x
}
### 指针运算与++运算规律小结
由运算符的优先级和结合性决定++操作的对象; 由++的前缀/后缀形式,决定操作的时机。
```c++
++p->x; // -> 优先级高:先获取p指向的成员x的值,再执行 ++
++ * pp->yy; // 先获取p指向的成员y,接着去除y指针指向的内容,最后执行 ++,即y指向的内容自增 1
* ++ p->y; // 先获取p指向的成员y,接着指针y自增 1,最后获取自增 1 后的指针指向的值
*(++p)->y; // 注意:()和->的优先级都为最高的15级,结合性从左至右,因此先执行 ++p,即结构指针自增 1,接着获取自增 1 后的结构体指针指向的成员y,最后获取指针y指向的值
包括以下三种关系:
在函数中传递结构成员的方法与传递简单变量的方法相同:
printf("%d", man.birthday.year); 传递结构成员的值
scanf("%d", &man.birthday.year); 传递结构成员的地址
#include<stdio.h>
#define STNUM 5
struct xscj
{
char xh[5];
float cj[2];
float av;
};
float ave(float x,float y)
{
float a;
a=(x+y)/2.0;
return a;
}
// 定义函数:以结构类型的变量作为形参
void f1(struct xscj tj)
{
tj.av=(tj.cj[0]+tj.cj[1])/2.0;
printf("%s,%5.1f,%5.1f,%5.1f\n", tj.xh,tj.cj[0],tj.cj[1],tj.av);
}
main()
{
struct xscj xs[4]={{"01",70,80}, {"02",78,67},{"03",56,78}, {"04",90,80}};
int i;
for (i=0;i<4;i++)
{
xs[i].av = ave(xs[i].cj[0],xs[i].cj[1]); // 结构体成员作为函数的实参
// f1(xs[i]); // 结构体变量作为函数的实参
printf("%s,%5.1f,%5.1f,%5.1f\n",xs[i].xh, xs[i].cj[0],xs[i].cj[1],xs[i].av);
}
}
联合和结构的定义和使用非常相似。举个通俗的例子,假设有多种物品需要存放,物品的类型和大小有一定差异,那么结构就是为所有物品开辟一块空间用来存放它们,而联合则仅开辟一块足够放下需存放物品中最大物品的空间。可见,联合用来保存多种数据中的一个,而结构用来保存所有的数据;
union <联合类型名>
{
数据类型 成员名1;
数据类型 成员名2;
...
数据类型 成员名n;
};
main()
{
union /* 定义联合并说明联合变量mix */
{
long i; /* 定义long型成员 */
int k; /* 定义int型成员 */
char ch;; /*定义定义char char型成员 型成员 */
char s[4]; /* 定义char型数组成员 */
}mix;
mix.i == 0x12345678; /* 通过联合中的long型成员i为联合赋初值 */
printf("mix.i=%lx\n", mix.i);
printf("mix.k=%x\n", mix.k);
printf("mix.ch=%x\n", mix.i);
printf("mix.s[0]=%x\t mix.s[1]=%x\n", mix.s[0], mix.s[1]);
printf("mix.s[2]=%x\t mix.s[3]=%x\n", mix.s[2], mix.s[3]);
}
同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一种,而不是同时存放几种。也就是说,每一瞬时只有一个成员起作用,其他的成员不起作用,即不是同时都存在和起作用。
联合变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员就失去作用。如有以下赋值语句:mix.i=1; mix.ch='a';
在完成以上2个赋值运算以后,只有mix.ch
是有效的,mix.i
已经无意义了,此时用printf(“%d”,mix.i)
是错误的;
联合变量的地址和它的各成员的地址都是同一地址。例如:
&mix.i, &mix.k, mix.ch, mix.s
都是同一地址值;
以下做法均是错误的:
union example
{
int i;
char ch;
float f;
};
union example a={1,'a',1.5};
(不能初始化);a=1;
(不能对联合变量赋值 不能对联合变量赋值);m=a;
(不能引用联合变量名以得到一个值)不能把联合变量作为函数参数,也不能使函数返回联合变量(因为数据类型是可变的),但可以使用指向联合变量的指针(因为指针的类型是不变的)
联合类型可以出现在结构体类型定义中,也可以定义联合数组。反之,结构体也可以出现在联合类型定义中,数组也可以作为联合的成员。
typedef
自定义类型typedef
用于自定义新的数据类型,它和宏定义类似,作用于字符串替换相近,但也有微小的差别;
typedef
自定义数据类型的步骤int i
);i
换成COUNT
);typedef
(例如:typypedef int COUNT);示例:
int n[100];
n
换成自己指定的类型名:int NUM[100];
;typedef
,得到typedef int NUM[100];
;NUM n;
,此时它等价于int n[100];
;typedef
可以声明各种类型名,但不能用来定义变量。typedef
只是对已经存在的类型增加一个类型名,而没有创造新的类型。typedef
声明一些数据类型,把它们单独放在一个文件中,然后在需要用到它们的文件中用#include
命令把它们包含进来。typedef
有利于程序序的通用与移植。typedef
与#define有相似之处,例如:typedef int COUNT;
和#define COUNT int
的作用都是用COUNT
代表int
。但事实上,它们二者是不同的:
#define
是在预编译时处理的,它只能作简单的字符串替换;typedef
是在编译时处理的。实际上它并不是作简单的字符串替换,而是采用如同定义变量的方法那样那样来声明一个类型。结构和联合等数据结构让我们可以非常自由地设计数据类型,与它们相反的是,枚举意在限制数据类型的定义。在定义只有7天星期类型、只有15个学院的学院类型等情境下尤为有用。
枚举类型:如果一个变量只有几种可能的值,可以定义为枚举类型。所谓“枚举”是指将变量的值一一列举出来出来,变量的值只限于列举出来的值的范围内 变量的值只限于列举出来的值的范围内。
格式:enum 类型名 {数据表列};
,例如:enum weekday {sun,mon,tue,wed,thu,fri fri,,sat sat};
sun=0;mon=1;
是错误的。enum weekday{sun=7,mon=1,tue,wed,thu,fri, sat}workday, weekend;
定义sun
为7
,mon=1
,以后顺序加1,sat为为6;if(workday == mon)...
和if(workday>sun)...
。枚举值的比较规则是按其在定义时的顺序号比较。如果定义时未人为指定,则第一个枚举元素的值认作0。故mon
大于sun
,sat>fri
;workday=2;
是不对的,它们属于不同的类型。应先进行强制类型转换才能赋值。如:workday=(enum weekday)2
;解决数据动态存储的问题。
\0
”(NULL
)malloc
和free
函数动态存储分配函数malloc
:void * malloc(int size)
. 使用示例:
struct student * ps;
ps=(struct student *)malloc(sizeof(struct student));
malloc
函数默认返回void
类型指针,因此需使用强制类型转换来转换指针类型:struct student *
;malloc
需要知道需分配的内存的大小,sizeof(struct student)
可获取结构变量所占用的内存大小;释放内存函数:void free(void *p)
。每个malloc
分配的内存都需要和一个free
函数配对以实现内存的分配和释放。
//#include <stdio.h> // 包含标准输入输出函数库
//#include <stdlib.h> // 包含标准函数库:malloc函数和free函数
struct node
{
char name[20],address[20],phone[15]; // 步骤 1:定义节点 = 数据域 + 指针域
struct node * link; /* 步骤 2:指针域,定义node型结构指针 */
}; /* 定义结构 */
typedef node NODE; /* 定义结点类型:节点名大写,提高可读性 */
main()
{
NODE * head ; // 步骤 2:说明头指针 head
NODE * p;
p =(NODE *)malloc(sizeof(NODE)); /* 步骤 3:开辟新存储区,申请表头节点 */
p->link = NULL; /* 将表头节点的link置为NULL*/
head = p; /* 将处理好的节点地址赋给头指针,完成头指针的创建 */
int create(NODE * head , int n); /* 使用create前需要先声明该函数 */
create(head, 2);
p =(NODE *)malloc(sizeof(NODE));
gets(p->name);
int insert_node(NODE * head , NODE * p , int i);
insert_node(head, p, 1);
int output(NODE * head);
output(head);
int delete_node(NODE * head , int i);
delete_node(head,2);
output(head);
getchar();
}
// 创建节点:创建的新节点都在头节点后插入,形参n为希望插入的节点个数
int create(NODE * head , int n)
{
NODE * p; // 声明节点指针备用
for(; n>0 ; n--)
{
p =(NODE *)malloc(sizeof(NODE)); // 开辟新存储区,申请新节点
gets(p->name); // 为节点的数据域赋值
p->link = head->link; // 将新节点p指针域指向节点插入前头结点的下一个节点
head->link = p; // 将新节点p插入到指针之后
}
return 0; // 插入结束则返回 0,私以为返回值意义不大
}
// 输出节点
int output(NODE * head)
{
NODE * p; // 声明节点指针备用
p = head->link; /* p 指向第一个数据节点,头指针是没有数据的 */
while(p!=NULL)
{ puts( p->name); /* 输出 p 所指向节点的数据 */
p = p->link ; /* p 指向下一个数据节点 */
}
return 0;
}
// 在第 i 个节点后插入新节点 p
int insert_node(NODE * head , NODE * p , int i)
{
NODE * q;
int n=0;
// 条件 n < i 控制循环在 i = n 时退出循环
for(q = head; n<i && q->link!=NULL; ++n)
q = q->link; /* ① 定位 */
p->link = q->link; /* ② 链接后面指针 */
q->link = p; /* ③ 链接前面指针 */
return 0;
}
// 删除第 i 个节点
int delete_node(NODE * head , int i)
{
NODE * q, * p;
int n;
for(n=0, q = head; n<i-1 && q->link!=NULL; ++n)
q = q->link; /* ① 定位 */
if(i>0 && q->link != NULL)
{
p = q->link; /* p 指向被删除节点 */
q->link = p->link; /* ② 摘链 */
free(p); /* ③ 释放 p节点 */
}
}
// 获取链表的长度
int length(NODE * head)
{
int len; // 定义整型变量用于计数节点
NODE *p; // 说明节点指针备用
for(len = 0, p = head->link; p != NULL; ++len)
p = p->link;
return(len);
}
15个基督教徒和 个基督教徒和1515个异教徒在海上遇险 个异教徒在海上遇险,必须将 半的人 必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一个圆圈,从第一个人开始依次报数,每数到第 9 个人就将他扔入大海,如此循环进行直到仅余 15 人为止。问怎样排法,才能使每次投入大海的都是异教徒?
/*约瑟夫问题*/
// #include <stdio.h> /* 包含标准输入输出函数库 */
// #include <stdlib.h> /* 包含标准函数库:malloc函数和free函数 */
// 创建链表的步骤
struct node { // 步骤 1:定义节点 = 数据域 + 指针域
int no; // 数据域:人员的编号
struct node * next; // 指针域:指向下一个节点(人员)
};
main()
{
int i, k;
struct node *head, *p, *q; // 步骤 2:说明头指针 head
// 步骤 3:按照结构大小分配一块内存并将内存地址赋值给头指针
head =(struct node *)malloc(sizeof(struct node));
head->no = -1; // 为头指针数据域赋值(一般不赋值),但赋一个特殊值可标志表头指针
head->next = head; // 为头指针的指针域赋值(一般为NULL)。此处头指针的指针域设置头指针本身,可实现循环链表
// 按照顺序创建链表:新节点总是在表头后插入,因此编号1的节点在最后插入,而编号30的节点第一个插入
for(i=30; i>0; i--) /* 步骤 3:生成循环链表 */
{
p =(struct node *)malloc(sizeof(struct node)); // 继续分配一块内存区域
p->next = head->next; // 新节点在表头节点后插入,新插入的节点后面的节点恰为插入前表头节点后的节点
p->no = i; // 为新节点数据域赋值:人员编号
head->next = p; // 新插入的节点总是在表头节点之后
}
// 输出整个链表
printf("\nThe original circle is :");
while(p->next != head) /* 循环链跳过表头结点 */
p = p->next;
// 循环结束时,p指向最后一个节点,即 p 指向第 30 个节点
p->next = head->next; /* 删除头结点,使得第30个节点的下一个节点指向第 1 个节点 */
// 约瑟夫问题求解的核心代码从此处开始
// 游戏规则是当只剩15个人时终止,由于总共30人,因此需要把把15个人扔到海里。故循环需执行 15次
for(i=0; i<15; i++)
{
// 每一次都需要找到被扔到海里的那个人:从当前第一个人开始数,每数到第9个人被人出去
for(k=1 ; k<9 ; k++) // 先略过前8个不被扔到海里的人
p = p->next;
q = p->next; /* p 的下一个结点为第9个,恰是要出列的结点 */
p->next = q->next; /* 删除节点:循环链表跳过要出列的结点 */
printf("%3d", q->no); /* 输出 q 结点的编号 */
free(q); /* 释放 q 结点 */
}
}