C语言第11章 复杂数据类型

第11章 复杂数据类型

11.1 结构体

11.1.1结构体类型的定义
  1. 定义
struct[结构体类型名]
{
	数据类型名1 成员名1;
	数据类型名2 成员名2;
	...
};

//例如:
struct Date
{
	int year; 
	int mouth;
	int day;
};

注意: 结构类型只是用户自定义的一种数据类型, 与之间介绍的简单数据类型一样, 他本身不需要占用内存单元, 只有用它来定义某个变量时, 才会为该变量分配结构类型所需要大小的内存单元.

11.1.2结构体变量的定义和引用
  1. 结构体变量的定义
    (1)间接定义法
struct 结构体类型名
{
	数据类型名1 成员名1;
	..
	数据类型名N  成员名N;
};
struct 数据类型名 数据名列表;

(2)直接定义法

struct [结构体类型名]
{
	数据类型名1 成员名1;
	..
	数据类型名N  成员名N;
}变量名列表;


// 例如: 下面的方法直接定义结构体变量 student1 和 student2
struct Student_Info
{
	char no[9];
	char name[20];
	char sex;
	unsigned int age;
	unsigned int classno;
	float grade;
}student1,student2;

struct //无名结构体定义变量只能一次
{
	char no[9];
	char name[20];
	char sex;
	unsigned int age;
	unsigned int classno;
	float grade;
}student1,student2;

说明:

结构体类型与结构体变量是两种不同的概念, 其区别同int 类型和int 型变量的区别一样. 结构体类型不分配内存; 而结构体变量分配内存; 结构体类型不能赋值、存取、运算; 但是结构体变量可以.
结构体可以嵌套.

  1. 结构体变量的引用
    对于非指针型结构体变量,要通过成员运算符" ." , 逐一访问其成员.
    结构体变量名.成员名
    而对于指针型结构体变量来说, 需要用“–>” 来访问
    结构体指针-->成员名 或(*结构体指针).成员名
    如果某结构体成员本身又是一个结构体类型, 则只能通过多级的分量运算,对最低一级的成员进行引用.
    结构体变量名.成员名.子成员名...... 最低级子成员名
struct Student_ Info stu;
struct Student_ Info *pstu;
pstu = &stu;//指针pstu指向stu
strcpy (stu.name, " zhangMing") ;//将" zhangMing "复制到stu的name成员中
stu.grade = 80;//対stu的成员grade赋值カ80
pstu->grade += 10;//將stu的成员grade的值増加10
printf ("%s %f", stu. name, (*pstu) .grade) ;//输出stu的成员name和grade的值

说明:

结构体变量不能整体引用, 只能引用变量成员
可以将一个结构体变量赋值给另一个结构体变量.
即可以应用结构体变量成员的地址,也可引用结构体变量的地址

11.1.3 结构体变量的赋值
  1. 结构体变量初始化赋值
    (1)先定义结构体类型,在定义结构体变量时赋初值.
struct 结构体类型名
{
....
};
struct 结构体类型名 变量名={成员1的值,成员2的值....};

注意: 赋初值时, { }中间的数据顺序必须与结构体成员的定义顺序一致, 否则就会出现混乱.

(2)定义结构体类型的同时, 定义结构体变量并赋初值

struct [结构体类型名]
{
	....
}变量名 ={成员1的值,成员2的值....};

// 例如: 
struct Date
{
	int year, month, day;
}birthday={1096,12,10};
  1. 结构体变量在程序中赋值
    如果在定义结构体变量时并未对其赋初始值, 那么在程序中要对它赋值的话, 就只要一个一个地对其成员逐一赋值. 或者用已赋值的同类型的结构体变量对它赋值
//例如:
struct Student_info stu; // 只定义了结构体变量stu, 并未对其赋值
//一下通过语句对stu的各个成员逐一赋值
strcpy(stu.no,"20020306");
strcpy(stu.name,"ZhangMing");
stu.sex = 'M';
stu.age = 18;
stu.classno =1;
stu.grade=90;

//如果再定义一个结构体变量stu1, 那么也可以将上面赋值后的stu赋值给stu1
struct Student_Info stu1;
stu1 = stu;

//执行后的结果将是吧stu的各个成员的值赋值给stu1对应的成员,相当于执行下列语句:
strcpy(stu1.no,stu.no);
strcpy(stu1.name,stu.name);
stu1.sex = stu.sex;
stu1.age = stu.age;
stu1.classno = stu.classno;
stu1.grade = stu.grade;

//也可以用memcpy函数来替代, 
memcpy(&stu1,&stu,sizeof(struct Student_Info));

注意:

对结构体变量不可整体赋值. stu1 ={ “20020306”,“ZhangMing”,‘M’,18,1,90}这是错误的语句

//计算学生5门课的平均成绩,最低分最高分
#include<stdio.h>
struct score
{
	float grade[5];
	float avergrade, maxgrade, mingrade;
};

void main()
{
	int i;
	struct score m;
	printf("input the grade of five course:\n");
	for(i=0;i<5;i++)
		scanf("%f",m.grade[i])
	
	m.avegrade = 0;
	m.maxgrade = m.grade[0];
	m.mingrade = m.grade[0];
	for(i=0;i<5;i++) //求平均分、最高分、最低分
	{
		m.avegrade += m.grade[i];
		m.maxgrade = (m.grade[i] > m.maxgrade)?m.grade[i]:m.maxgrade;
		m.mingrade = (m.grade[i] < m.mingrade)?m.grade[i]:m.mingrade;
	}
	m.avegraed /=5;
	printf("avegrade = %5.lf  maxgrade = %5.1lf   mingrade = %5.1f\n",m.avefrade, m.amxgrade, m.mingrade);
}
11.1.4 简化结构体类型名

可以使用typedef来为结构体类型起别名
格式: typedef 类型名 类型名的别名;

其中:

类型名必须是已经定义的数据类型名或C语言提供的基本类型名
类型名的别名必须是合法的标识符, 通常用大写字符来表示
typedef 语句要以分号结尾

11.1.5 结构体数组

结构体数组的每一个元素都是具有相同结构体类型的下标结构变量. 在实际应用中,经常用结构体数组来表示具有相同数据结构的一个群体.
从某种意义上来讲, 结构体数组就相当于一张二维表. 一个表的框架对应的就是某种结构体类型,标中的每一列对应该结构体的成员, 表中每一行信息对应该结构体数组元素各成员的具体值, 表中的行数对应结构体数组的大小.

  1. 结构体数组的定义
struct Student_Info
{	
	char no[9],name[20],sex;
	unsigned int age, classno; 
	float grade;
}stu[10];

//或者
struct Student_Info stu[10];

//结构体数组stu的每个元素所占内存大小为: sizeof(struct Student_Info)
  1. 结构体数组的初始化
struct 结构体类型名
{
	....
};

struct 结构体类型名 结构体数组[size] = {{初值表1},{初值表2},....};

或者:

struct [结构体类型名]
{
	...
}结构体数组[size] = {{初值表1},{初值表2}....};
  1. 结构体数组的引用
    结构体数组名[下标].成员名;
struct Student_Info stu[3];
strcpy(stu[0].name,"Wangfei"); //对数组元素stu[0]中的成员name赋值
stu[1].grade++; //对数组元素stu[1]中的成员grade值
printf("%s",stu[0].name); //显示数组元素stu[0]中的成员name的值
  1. 结构体数组的应用
    统计候选人选票
#include<stdio.h>
#include<string.h>
struct person
{
	char name[20]; //候选人姓名
	int count; //得票数
}leader[3]={"LI",0,"zhang",0,"wang",0};

void main()
{
	int i, j;
	char leader_name[20];
	while(1)//统计候选人得票数
	{
		scanf("%s",leader_name); 
		if(strcmp(leader_name,"0")==0); //输入为“0”结束
			break;
		for(j=0;j<3;j++)
			if(strcmp(leader_name,leader[j].name)==0); //合法
				leader[j].count++;
	}
	
	for(i=0;i<3;i++)
		printf("%5s : %d\n",keader[i].name,leader[i].count);
}
//定义一个结构体类型person, 其中包含两个成员,候选人姓名name和得票数count,以此结构体类型定义了候选人数组leader,并对它赋初值.

11.2 线性链表

  1. 线性链表概述和结构
    当一组数据元素形成了“前后”关系时, 称为线性表.
    线性表再内存有两种存放形式:
  1. 顺序表 : 以数组的形式存放,数组元素在内存中是连续存放的
  2. 线性链表: 数据元素在内存中不需要连续存放,而是通过指针将各个数据元素连接起来,像一条链子一样将数据单元前后元素链接起来.
    对于线性链表来说,它的存放形式不需要提供连续的内存块 , 当插入或删除一个数据元素时,不需要移动其他数据元素,因而其实用性更广.

线性链表逻辑结构图:

线性链表的数据单元包含两个部分 : 指针域和数据域
链表的头节点的数据域不存放有效数据,它的指针域存放实际数据链表的第一个数据单元的地址.
尾节点的指针域置为NULL,作为链表结束的标志
最开始的实际数据链表建立之初, head = tail 表示实际数据链表为空, 即链表中没有任何有效数据.

链表中定义聂店的结构体类型一般格式:

struct 节点结构体类型名
{
	数据成员定义;
	struct 节点结构体类型名 *指针变量名;
}
  1. 线性链表的基本操作
    基本操作: 创建、插入、删除、输出和销毁
    (1) 链表的创建操作
    链表创建是指从无到有建立起一个链表, 往空链表中依次插入若干个节点,并保持节点之间的前驱和后继的关系.

基本思想:首先创建—个头节点,让头指针head和尾指针tail都指向该节点,并设置该节点的指针域为NULL(链尾标志);

然后为实际数据创建—个节点,用指针pnew指向它,并将实际数据放在该节点的数据域,其指针域置为NULL ;

最后将该节点插入到tail所指向节点的后面,同时使tail指向pnew所指向的节点。其具体操作见下面的函数Create_ LinkList。

//链表创建操作函数函数Create_LinkList
NODE *Create_ LinkList ( )
{
	NODE *head, *tail, *pnew;
		int score;
	head = (NODE *)malloc (sizeof (NODE) ) ;//创建头节点
	if (head == NULL)	//创建失败,则返回
		{
		printf ("no enough memory! \n") ;
		return (NULL) ;
		}
		head->next = NULL;//头节点的指针域置NULL
		tail = head;//开始时尾指针指向头节点
		printf ("input the score of students: \n") ;
		while (1) //创建学生成绩线性链表
		{
			scanf("号d", &score) ;//输入成绩 
			if (score < 0)//成绩为负,循环退出
				break;
			pnew = (NODE *)malloc (sizeof (NODE)); //创建一新节点
			if (pnew == NULL)//创建新节点失败,则返回
			{
				printf ("no enough memory! \n") ;
				return (NULL) ;
			}
			pnew->score = score;//新节点数据域放输入的成绩
			pnew->next = NULL;//新节点指针域置NULL
			tail->next = pnew; //新节点插入到链表尾
			tail = pnew; //尾指针指向当前的尾节点
		}
		return (head); //返回创建的链表的头指针
	}

(2)链表的插入操作
插入操作是在第i个节点Ni与第i+1节点Ni+1之间插入一个新的节点N ,使线性表
的长度增1,且Ni与Ni+1的逻辑关系发生如下变化:插入前,Ni是Ni+1的前驱,Ni+1
是Ni的后继;插入后,新插入的节点N成为Ni的后继、Ni+1的前驱。

基本思想:通过单链表的头指针head ,首先找到链表的第一个节点 ;然后顺着节点
的指针域找到第i个节点,最后将pnew指向的新节点插入到第i个节点之后。插入时首先将新节点的指针域指向第i个节点的后继节点,然后再将第i个节点的指针域指向新节点。注意顺序不可颠倒。当i=0时,表示头节点。

void Insert_ LinkList (NODE *head, NODE *pnew, int i)
{
	NODE *p;
	int j;
	p = head;
	for(j = 0;j< i &&p!= NULL; j++) // 将P指向要插入的第i个节点
		p = p->next;
	if (p == NULL) //表明链表中第i个节点不存在
	{
		printf ("the 号d node not foundt! \n",i) ;
		return;
	}
	pnew- > next = P-> next ;	//将插入节点的指针域指向第i个节点的后继节点
	p -> next = pnew;//将第i个节点的指针域指向插入节点
}

(3) 链表的删除操作
删除操作是删除链表中的第i个节点Ni ,使线性表的长度减1。删除前,节点Ni-1
是Ni的前驱,Ni+1是Ni的后继 ; 删除后,节点Ni+1成为Ni+1的后继。

基本思想 : 通过单链表的头指针head ,首先找到链表中指向第i个节点的前驱节点
的指针p和指向第i个节点的指针q ;然后删除第i个节点。删除时只需执行p ->next = q->next即可,当然不要忘了释放节点i的内存单元。注意当i=0时,表示头节点,是不
可删除的。

void Delete_ LinkList (NODE *head,int i)
{
	NODE *p, *q;
	int j;
	if(i==0)//删除的是头指针,则返回
		return;
	P = head;
	for (j =1; j <i && p->next != NULL; j++)
		P = p->next; //将p指向要删除的第i个节点的前驱节点
	if (p->next == NULL) / /表明链表中第i个节点不存在
	{
		printf ("the 8d node not foundt! \n",i) ;
		return;
	}
	
	q = p->next; // q指向待删除的节点i
	p->next = q->next;//删除节点I,也可写成p->next = p->next->next;
	free(q) ;//释放节点I的内存单元
}

(4)链表的输出操作
输出操作是指,将链表中节点的数据域的值显示出来。如果在输出过程中,对数据
进行相应的比较,则可实现对链表的检索操作。
基本思想:通过单链表的头指针head ,使指针p指向实际数据链表的第一个节点,
输出其数据值, 接着p又指向下一个节点,输出其数据值,如此进行下去,直到尾节点
的数据项输出完为止,即p为NULL为止。

void Display_LinkList(NODE *head)
{
	NODE *p;
	for(p = head->next; p!=NULL; p=p->next)
		printf("%d",p->score);
	printf("\n");
}

(5)链表的销毁操作
销毁操作是将创建的链表从内存中释放掉, 达到销毁的目的.

基本思想:每次删除头节点的后继节点 , 最后删除头节点。注意 , 不要以为只要删
除了头节点就可以删除整个链表,要知道链表是一个节点一个节点建立起来的, 所以销毁它也必须一个一个节点的删除才行。

void Free_LinkList(NODE *head)
{
	NODE *p, *q;
	p=head;
	while(p->next !=NULL)
	{
		q=p->next;
		p->next = q->next;
		free(q);
	}
	free(head);
}
  1. 线性链表应用举例
    建立一个学生成绩的线性链表,然后对其进行插入、删除、显示, 最后销毁该链表.
#include<stdio.h>
#include<stdlib.h>

struct Grade_Info
{
	int score;
	struct Gread_Info *next;
}
typedef struct Grade_Info NODE;

NODE *Create_LinkList();
void Insert_LinkList(NODE *head, NODE *pnew, int i);
void Delete_LinkList(NODE *head, int i);
void Display_LinkList(NODE *head);
void Free_LinkList(NODE *head);

void main()
{
	NODE *head, *pnew;
	head = Create_ LinkList();//创建链表
	if(head == NULL)//创建失败
		return; 
	printf ("after create: ");
	Display_LinkList(head);//输出链表中的值
	pnew = (NODE *) malloc (sizeof (NODE)); //新建一插入的节点
	if (pnew == NULL)//创建失败,则返回
	{
		printf ("no enough memory! \n") ;
		return;
	}
	pnew->score = 88;
	Insert_ LinkList (head, pnew, 3); //将新节 点插入节点3的后面
	printf ("after insert: ") ;
	Display_ LinkList (head) ;//输出链表中的值
	Delete_ LinkList (head,3);//删除链表中节点3
	printf ("after delete: ");
	Display_ LinkList(head);//输出链表中的值
	Free_LinkList(head);//销毁链表
}

11.3 联合体

11.3.1 联合体类型的定义

联合体类型的定义格式

union [联合体类型名]
{
	数据类型名1 成员名1;
	数据类型名2 成员名2;
	....
	数据类型名n 成员n;
};
union UData
{
	short i;
	char ch;
	float f;
};
//联合体UData包含三个成员,他们使用同一地址的内存.
//联合体的大小是成员中占内存最大的成员的大小.
//UData的大小由最大的成员确定, float f 的大小为4, 所以联合体的大小也为4
像结构体类型定义一样, 联合体类型定义时是不分配内存单元的,只有定义联合体类型变量才分配内存单元.

结构体:

struct SData
{
	short i;
	char ch;
	float f;
}
11.3.2 联合体变量的定义和引用
  1. 联合体变量的定义

  2. 联合体变量的引用
    对联合体成员的引用格式与对结构体成员的引用格式相同,如果通过联合体变量来引用成员,则要使用”.”, 如果是通过联合体指针来引用成员,则要使用”->”。例如:union UData data, *p,d[10]

11.3.3 联合体变量的赋值
  1. 联合体变量的赋初值
    定义联合体变量时可以对变量赋初值, 但只能对变量的第-一个成员赋初值,不可像结构体变量那样对所有的成员赋初值。
union UData data = {10} ;//10赋给成员i
union UData data = {'A'} ;//' A'赋给成员i,即i的值为65 ( ' A'的ASCII码)
union UData data = {10'A'12.5}; //错误, {}中只能有一个值
union UData data = 10;//错误,初值必须用{ }括起来
  1. 联合体变量在程序中赋值
  1. 定义了联合体变量以后, 如果要对其赋值, 则只能通过对其成员赋值, 不可对其整体赋值.
union UData data, *p,d[10];
data = [10];//错误
data = 10;//错误
data.i =10;//正确,将10赋给data的成员i
p = &data; // p指向Ddata
p->f =12.5; //正确,将12.5赋给data的成员f
d[0].ch='A' //正确,将'A'赋给d[0]的成员ch
  1. 像相同结构体类型的变量之间可以彼此赋值一样, 具有相同联合体类型的变量之间也可以相互赋值.
union UData data1 = {10}, data2;
data2 = data1;  //正确

几点说明:
1)由于联合体变量的各成员共享同一地址的内存单元,所以在对其成员赋值的某一时刻, 存放的和起作用的将是最后一次存入的成员值.

union UData data;
data.i=10;
data.ch='a';
data.f=12.5;

2)对联合体变量的某个成员赋值时, 也该变量其他成员的值, 因为他们共享一个内存地址.
3)由于联合体变量所有成员共享同一内存空间,因此联合体变量与其各成员的地址相同。

11.4 位域

如果要访问结构体或联合体中的成员所对应内存单元的若干位,就需要用位域

对位域成员的引用方法与结构体或联合体成员引用方法- -样,用”."或”->”来引用这些成员。

#include <stdio.h>
struct MyStruct
{
	unsigned char a : 1 ;
	unsigned char b : 5;
	unsigned short c : 10;
};
union MyUnion
{
	unsigned short x;
	struct MyStruct y ;
];
void main ( )
{
	union MyUnion m = { (unsigned short) 0XFFF1] };
	printf("m.y.a = %uln", m.y.a) ;
	printf("m.y.b = %uln", m.y.b) ;
	printf("m.y.c = %uln", m.y.c) ;
	m.y.b=0;
	printf ("%Xn", m.x) ;

在VC下, C的数据类型是short, 与 a 、b 的数据类型 char不一致, 所以C不可存取者16位的第7位到第15位的数据,而是起默认值0

11.5 枚举类型变量的定义和引用

枚举就是吧这种类型数据可取的值一一列举出来,一个枚举型变量取值仅限于列出值的范围.
枚举数据类型定义:

enum 枚举类型名
{
	枚举元素表
};

//例子: 定义表示日期的枚举类型 weekday
enum weekday{sun, mon, tue, wed, thu, fir, sat};

遇到枚举元素列表时, 编译程序就把其中第一个标识符赋0值, 第二、第三…个标识符依次赋1,2…
因此枚举值赋给枚举变量时,该变量实际得到一个整数值.

enum weekday{sun, mon, tue, wed, thu, fir, sat};
today = sun;
printf("today = %d",today);
//输出的结果将是L today = 0;

同时也可以在枚举类型定义时指定枚举元素的值.

enum weekday(sun =7;mon=1,tue,wed,thu,fri,sat);
//这时, sun =7, mon =1, tue 以后的元素的值 从mon =1, tue =2, wed =3 依次类推

枚举元素是常量,在程序中不可对它赋值

11.6 总结、容易错误的地方

11.6.1 常见错误
  1. 把数据类型名当作变量名
struct Date
{
	int year,month,day ;
};
Date.year=2004;
Date.month=10;
Date.day=18;
//上面是错误的,需要创建一个变量来进行复制

struct Date d;
d.year = 2004;
d.month = 10;
d.day = 18;
  1. 不能对结构体变量进行整体赋值
  2. 结构体类型定义在一个函数内,而其他函数中用其定义结构体变量
void main()
{
	struct Date
	{
		int year,month,day;
	};
	....
}

void func(){
struct Date d; //错误, Date没有定义
}

/*
在编译时会指出func中的Date没有定义, 结构类型名也是标识符,函数中定义的标识符的作用域是它所处的函数,这个函数外不可见,所以对于func()函数中的Date没有定义
*/
  1. 在typedef语句中漏掉分号
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

suiuko

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值