第10章 结构型、共用型、枚举型及用户自定义型数据

来源:互联网 发布:java 动态生成jsp页面 编辑:程序博客网 时间:2024/05/22 03:35

花费一个下午和一个晚上我抄了这么多东西,那么我有怎用的收获呢?2016年11月2日
我总是抄这些知识点是个坏习惯。。。需要想办法解决

  • 数组、变量一旦定义以后只能存储定义的类型数据
  • 一个结构体变量可以包含不同的数据类型的数据
  • 结构体类型是C语言中最重要的构造数据类型。

结构体类型概念及其应用

共用体类型
枚举类型
结构体数组
结构体指针
typedef定义

10.1 结构体类型

10.1.1 结构体类型简介

  • 结构体类型是不同类型数据的集合,它是用户自己定义的数据类型。
    (1)结构体中可以包含不用类型的数据,数组只能包含同一类型的数据
    (2)结构体可以相互赋值,而数组不能直接相互赋值。因为结构体类型本身是数据类型,而数组是同一类型数据的集合。

10.1.2 结构体类型定义

在C语言中,一个学生的信息可以用如下结构体保存:

struct s{    char name[10];    int age;    char[sex];}上述语句定义一个结构类型s,s为结构体名,大括号中为一个结构体包含的成员字符型数组整形变量字符型变量

结构体定义形式如下:

struct 结构体名{    结构体成员}

结构体成员可以是变量、数组、指针、等数据类型,定义了结构体之后,就可以用它来声明结构体变量。
结构体变量声明以后,即可引用结构体中的成员。

结构体变量的定义方式有以下三种:

(1)先定义结构体,后定义结构体变量。

struct s{    char name[10];    int age;    char sex;};struct s s1,s2;

通过结构体S定义了两个结构体变量s1和s2,程序可以直接引用两个结构体变量中的成员。

(2)定义结构体同时定义结构体变量

struct s{    char name[10];    int age;    char sex;}s1,s2;

在定义结构体的同时定义了两个结构体变量s1和s2;

(3)只定义结构体变量

struct{    char name[10];    int age;    char sex;}S1,S2;

没有定义结构体类型名,直接定义了两个结构体变量s1和s2,因为没有定义结构体类型名,因此除了直接定义,不能使用其他方式定义定义结构体变量。

注意:
(1)结构体是用户自定义类型,结构体可用来定义结构体变量,但不能用来传递信息。
(2)结构体中可以嵌套另外一个结构体,即一个结构体中可以包含多个结构体。

struct time{    int hour;    int minute;    int second;};struct data{    int day;    int month;    int year;    struct time t;}d;

在结构体中嵌套了结构体,通过结构体可以用来调用结构体中的成员

10.1.3 结构体类型引用

定义结构体变量之后,可以通过操作符’.‘来引用结构体中的成员。

结构体成员的引用方式:  结构体变量名.成员名;
struct s{    char name[10];    int age;    char sex;}s1;

结构体大括号后的分号为结构体定义基本形式,若没加上则会出错。

范例10.1:从键盘上输入一个学生的信息,将这些信息存储至结构体中并输出。
分析:定义一个结构体,包含学生的一些基本信息,然后对结构体进行赋值,最后输出结构体中的成员。

#include <stdio.h>void main(){    struct s    {        char name[10];        int age;        char sex;        float score;//定义结构体类型变量s1    }s1;    printf("输入学生姓名:");    gets_s(s1.name);    printf("输入学生的性别:");    s1.sex=getchar();    printf("输入学生岁数:");//从键盘获取字符串保存至结构体相应的变量中    scanf("%d",&s1.age);    printf("输入学生的分数:");    scanf("%f",&s1.score);    printf("年龄:%s\n",s1.name);    printf("名字:%s\n",s1.age);    printf("性别:%c\n",s1.sex);//输出这些信息至屏幕上    printf("分数:%f\n",s1.score);}

10.1.4结构体变量初始化

结构体变量与变量相似,在定义结构体变量的同时便可对其各个成员赋初始值,即结构体变量的初始化。

结构体变量的初始化与定义类似:

(1)第一种方式:

struct s{    char name[10];    int age;    char sex;    float score;};struct s s1={'lihai',20,'M',90.5}

(2)第二种方式

struct{    char name[10];    int age;    char sex;    float score;}s1={'lihai',20,'M',90.5};

不能在结构体内部进行初始化

struct{    char name[10] ="LiHai";    int age = 20;    char sex ='M';    float score =90.5;}s1;

10.2 结构体数组

10.2.1 结构体数组定义

结构体定义形式与数组定义形式类似:

(1)先定义结构体,后定义结构体数

struct s{    char name[10];    int age;    char sex;    float score;};struct s a[10];

(2)定义结构体的同时定义结构体数组

struct s{    char name[10];    int age;    char sex;    flaot score;}a[10];定义了包含10个元素的结构体数组,每一个结构体元素占据17个字节,其中字符型数组占10个字节,整形变量占2个字节,字符型变量占1个字节,浮点型变量占4个字节

10.2.2 结构体数组引用

结构体数组定义以后,通过下标即可引用相应的结构体元素。

范例10.2现有3个候选人,10个人进行投票,统计每一个候选人的票数。
分析:将这3个候选人定义为结构体类型,10个人进行投票,将每个人的投票与结构体中的名字进行比较,若相等则其票数加1,最后输出3个候选人的票数。

#include <stdio.h>#include <string.h>struct{    char name[10];    int c;}p[3] = {"zhang",0,"li",0,"hu",0};void main(){    int i, j;    char n[10];    for (i = 0; i < 10; i++)    {        scanf("%s",n);        for (j = 0; j < 3; j++)        {            if (strcmp(n, p[j].name) == 0)                p[j].c++;        }    }    printf("最终的结果:\n");    for (j = 0; j < 3; j++)        printf("%s:%d票\n",p[j].name,p[j].c);}

#  10.2.3 结构体数组的初始化

结构体数组可以在定义时就赋予初值,即进行初始化。结构体数组初始化方式有两种。
(1)初始化数组所有元素

struct{    char name[10];    int age;    float score;}s[3]={{"Zhang lin",20,80.5},{"Li Tao",19,89.0},{"Xiu Yan",21,90.0}};

定义一个包含三个元素的结构体数组,并对其中所有元素进行了初始化。
(2)初始化数组部分元素,且结构体中嵌套另一个结构体。

struct Time{    int hour;    int minute;    int second;    struct Data    {    int year;    int month;    int day;    }    }t[10]={{6,56,34,(2009,5,24)},{7,35,56,(2010,7,2)};};

10.3 结构指针

结构指针指向已定义的结构体变量在内存中的首地址,从而对地址中的内容进行操作。

10.3.1 结构体指针概念及其定义

结构体指针可以指向结构体变量的首地址,其定义形式如下:

struct 结构体类型 *指针名

struct st{    char name[10];    int age;    char sex;    float score;}s;struct st *p;

定义一个结构体st的指针P,但其值是不确定的。通过以下语句可将结构体变量s的地址赋给p,使p指向结构体变量

p = & s;
其中‘&’为取地址符,&s表示结构体变量S的地址。指针赋值后,通过指针即可访问结构体中的成员。
(*p).name (*p).sex
括号不能省去,因为”.“的优先级比”.“高。
在C语言中通过指针还可以用另外一种方式来访问结构体成员,即使用运算符”–>“

p --> name      p-->age        p-->sex

10.3.2 结构体数组指针

结构体数组也可以定义指针,通过指针对其进行操作
(1)(++p)->num和(p++)–>num的区别
先使P的值加1,使P指向数组的下一个元素,再访问该元素的结构体成员num
先访问元素的结构体成员num,再使p的值加1,指向下一个元素。
(2)结构体数组指针P指向数组有以下三种方式

p=s;   p=&s;   p=&s[0];

10.3.3 结构体指针应用

gets_s函数不接受一个参数
double
printf 格式字符串需要类型char的参数,但可变参数1拥有了类型double

#include <stdio.h>#include <stdlib.h>struct st{    char name[10];    char age[5];    char sex[6];    float score;}s;//结构体变量svoid main(){    struct st *p = &s; //定义结构指针    char a[20];    printf("输入名字:");//实现结构成员的数据输入    gets_s(p->name);    printf("输入年龄: ");    gets_s(p->age);    printf("输入性别: ");    gets_s(p->sex);    printf("输入分数: ");    //gets_s(s);    //p->score = atof(s);//该函数的功能是将字符型数据转化为浮点型数据    printf("名字:%s\n",p->name);    printf("性别:%s\n",p->sex);    printf("年龄:%s\n",p->age);    printf("分数: %s\n",p->score);}

10.4 结构与函数参数

  • 结构体可以用来作为函数的参数

10.4.1 结构体变量作为函数参数

  • 将结构体变量作为实参传递给一个函数
  • 将结构体变量的值传递给函数,即传值调用

范例10.7 编写一个程序,将结构体变量作为实参传递给其他的函数,

分析:结构体变量直接传递给函数,是传值调用,因此函数内形参值的变化不会影响实参的值。

#include <stdio.h>#include <stdlib.h>struct st{    char name[10];    char age[5];    char sex[6];    float score;};void out(st stu)//自定义函数{    printf("名字:%s\n",stu.name);//输出结构体成员    printf("性别:%s\n",stu.sex);    printf("年龄:%s\n",stu.age);    printf("分数:%s\n",stu.score);}void in(st stu){    char score[10];    printf("输入名字: ");//从键盘获取字符串至stu.name    gets_s(stu.name);    printf("输入年龄: ");    gets_s(stu.age);    printf("输入性别: ");    gets_s(stu.sex);    printf("输入分数: ");    gets_s(score);    stu.score = atof(score);    out(stu);//调用OUT()函数输出学生信息}    void main()    {        struct st s =                          {"LiMing","20","man",87.5};        in(s);        out(s);//调用in()函数    }

注意:
虽然in()函数中对结构体变量重新进行赋值,但由于传递方式为传值调用,因此不会影响主函数main()中结构体变量的值。

10.4.2 结构体地址作为函数参数

  • 将结构体变量地址作为参数传递给函数,则形参应为相同类型的指针,这种方式为传址方式,因此函数内结构体变量值的变化会影响实参。

范例10.8 编写一个程序,将结构体变量的地址作为实参传递给其他的函数

分析:结构体变量地址传递给函数,是传址调用。因此形参值的变化不会影响实参值。

#include <stdio.h>#include <stdlib.h>struct st{    char name[10];    char age[5];    char sex[6];    float score;};void out(st stu){    printf("名字: %s\n",stu.name);    printf("性别: %s\n",stu.sex);    printf("年龄: %s\n",stu.age);    printf("分数: %s\n",stu.score);}void in(st *stu){    char score[10];    printf("输入名字:");    gets_s(stu->name);    printf("输入性别:");    gets_s(stu->sex);    printf("输入分数:");    gets_s(score);    stu->score = atof(score);}void main(){    struct st s = { "Li maing","20","man",87.5};    out(s);    in(&s);}

10.4.3 结构体数组作为函数参数

  • 数组名可以代表数组的首地址。结构体数组可以作为参数传递给函数,即将结构体数组的首地址传递给函数。

范例10.9 编写一个程序,将结构体数组作为实参传递给其他的函数。

分析:结构体数组传递给函数,即将结构体数组首地址传递给函数,然后便可以对数组中的内容进行操作。

#include <stdio.h>#include <stdlib.h>struct st{    char name[10];    char age[5];    char sex[6];    float score;};void prin(st s[]){    int i;    for (i = 0; i < 3; i++)    {        printf("名字:%s\n",s[i].name);        printf("年龄:%s\n",s[i].age);        printf("性别: %s\n",s[i].sex);        printf("分数:%s\n",s[i].score);    }}void main(){    struct st s[3] = {        {"Li Ming","20","man",87.0},        {"Hu Su","19","women",89.0},        {"Jun Yi","21","man",90.5}    };    prin(s);}

10.5共用体

都是将不同的数据类型组合在一起,共用体与结构体所占的空间不同

10.5.1 共用体概念及其定义

  • 共用体为用户自定义构造类型
  • 结构体类型存储时采用覆盖方式,即后一个类型的数据会覆盖前一个类型数据。
  • 结构体变量所占空间为所有成员占据的空间之和。
  • 共用体变量所占空间为成员中所占空间最大的成员字节数。

共用体定义形式如下

union  共用体名{    成员表}共用体变量名;

例子:

union  day{    int x;    float y;    double z;    char c;}t;

成员指针运算符
成员选择运算符

结构体与共用体的区别:

对共用体变量进行操作,可以通过’.‘和 ’->‘运算符来实现。若操作对象为共用体变量,则用”.“运算符。若操作对象为共用体类型指针,则使用”->”运算符。
- (1)结构体成员都有自己的存储空间。结构体变量所占存储空间为各个成员所占存储空间之和
共用体变量所占存储空间为各个成员中占存储空间最大的成员的字节数。
- (2)结构体变量可以同时对其所有成员赋值,
共用体类型不能同时对所有的成员赋值,只能单独赋值
- (3)结构体变量可以初始化,
共用体在定义时不能赋值

10.5.2 共用体变量应用

要引用共用体变量中成员,可以通过共用体变量来引用,也可以通过共用体指针来引用。

(1)共用体变量的值为最后一次赋的值

union  day{    char c;    int i;    float j;}t;t.c ='f';t.i ='6';t.j =4.5;最后一个共用体变量t的值为4.5,前面两个值都被覆盖。

(2)共用体变量在定义时不能赋值
如何定义共用体变量?

union day{    char c;    int i;    float j;}t={'z',4,9.0};上述程序是错误的

(2)不能对共用体变量整体进行赋值。

union day{    char c;    int i;    float j;}t;t='w';t=98;最后两行都是错误的

(4)与结构体类似,可以定义数组

union  day{    char c;    int i;    float j;}t[5];

范例10.10:通过下面的例子,了解共用体的存储过程

分析:共用体是用户自定义类型,是不同数据类型的集合。

#include <stdio.h>union{    int x;    char y;    float z;}s;void main(){    printf("sizeof(s.x)=%d\n",sizeof(s.x));    printf("sizeof(s.y)=%d\n",sizeof(s.y));    printf("sizeof(s.z)=%d\n",sizeof(s.z));    printf("sizeof(s)=%d\n",sizeof(s));}

10.5.3 共同体与结构体的嵌套

C语言中,结构体还可以与共用体嵌套使用

范例10.11 编写一个程序,实现结构体与共用体的嵌套使用。

分析:
结构体可以嵌套共用体,共用体也可以嵌套结构体。当要引用其中的成员时,可以通过”.“或者”->”

#include <stdio.h>void main(){    struct std    {        union        {            int m;            int n;        }a;        int x;        int y;    }s;    s.x = 3;    s.y = 5;    s.a.m = s.x*s.y;    s.a.n = s.y - s.x;    printf("结果:%d,%d\n",s.a.m,s.a.n);}

范例10.12
现假设有若干人的信息要记录,其中包括学生和老师。学生的信息包括姓名、性别、编号、职业、平均成绩,老师的信息包括姓名、性别、编号、职业、任课科目,编写一个程序,从键盘上输入数据,并将结果输出至屏幕。

#include <stdio.h>struct{    char n[10];    char sex;    char num[20];    char j;    union    {        float score;        char course[10];    }a;}s[3];void main(){    int i;    for (i = 0; i < 3; i++)    {        scanf("%s %c %c",&s[i].n,&s[i].sex,&s[i].num,&s[i].j);        if (s[i].j == 's')            scanf("%f", &s[i].a.score);        else            scanf("%f",&s[i].a.course);    }    for (i = 0; i < 3; i++)    {        if (s[i].j == 's')            printf("%s,%c,%s,%c,%f\n", s[i].n, s[i].sex, s[i].num, s[i].j, s[i].a.score);        else            printf("%s,%c,%s,%c,%s\n", s[i].n, s[i].sex, s[i].num, s[i].j, s[i].a.course);    }}

10.6 枚举类型

enum 枚举类型名(枚举元素表)

例子:

enum  color(red,blue,green,white,black);

enum为关键字,color为枚举类型名
red 、blue green white 和black为枚举元素

定义了枚举类型后,可以用来定义枚举类型的变量

enum  color a,b;

定义了两个**枚举类型变量**a和b,其取值范围在枚举元素之内,只能为其中的某一个值。

a=red;b=white;

枚举类型具有以下4个特点。
(1)枚举元素为常量,从起始位置开始值为0,1,2。。。

enum color(red,blue,green,white,black);enum color c;c= green;printf("%d",c);输出结果为2,因为green在枚举元素的第三个位置,因此其默认值为2.

(2)枚举元素为常量,不能赋值,但可以在定义时进行赋值

enum color(red,blue,green,white,black);red =5;green = 0;上述程序是错误的,编译时将会出错

(3)枚举常量可以用来进行比较

if(c>red)if(c<white)因为枚举元素都有一个默认值,因此可将枚举类型变量的值与枚举元素进行比较。

(4)枚举类型不能直接进行赋值,但可以强制转化进行赋值。

c =4;是错误的c = (enum color)4;将数字强制转化再进行赋值,是正确的

范例10.13 现有红、黄、蓝、绿、白5中颜色,从中取出两个球,打印出所有可能的结果。

#include <stdio.h>void main(){    enum color { red, yellow, blue, green, white };    enum color i, j;    for (i = red; i <= white; i++)    {        for (j = red; j <= white; j++)            if (i != j)                printf("%d ,%d\n", i, j);    }}//枚举类型上的此操作需要适用于用户定义的运算符函数
二进制“++”:“main::color”不定义该运算符或到预定义运算符可接收的类型的转换   

10.7 用户自定义类型

C语言中可以用关键字typedef为已有类型重新定义名称,其定义形式如下:

typedef  类型名 标识名

其中类型名可以为C语言中的任意类型,标识名用户自己命名的名字。

typedef  int integer

上述程序用integer表示int类型。

integer x;int x;

定义变量的方式是等价的,都表示定义一个整型变量

10.14 编写一个程序,通过typedef自定义类型

分析:typedef关键字自定义类型,但它不是创建一个新的类型,而是将已有的类型用新的标识符来表示。

#include <stdio.h>typedef union  //通过typedef自定义类型{    int a[5];    float b[5];    char c[5];}ns;void main(){    ns x;  //通过自定义类型定义变量    printf("共用体所占空间:%d\n",sizeof(x));}//本例用typedef 自定义类型//通过关键字typedef为共同体类型定义了一个新的标识符//利用新定义的标识符定义变量

10.8 链表

链表是C语言中重要的数据结构,常常被用来处理大量数据。虽然数组也可以用来存储大量的数据,但数据必须要先声明其大小才能使用。在程序的执行过程中不能改变数组的大小,因此定义时一般将数组定的很大,这样往往会造成存储空间的浪费。
链表是一种动态的存储结构,她可以根据实际情况改变内存空间的大小。若需要则临时开辟存储空间,不需要则释放存储空间,这样可以很好地解决存储空间浪费的问题。
链表是很复杂的数据结构,

10.8.1单向链表

单向链表是有若干个结点组成的,每个结点是有数据区域和指针区域两个部分组成的。
数据区域用来存放用户需要存储的数据,
指针区域用来存放下一个结点的地址,便于寻找下一个结点
链表中一般都有一个头结点head,用来指向链表的第一个结点,即整个链表的首地址。
链表中的最后一个结点中指针区域值为空,即NULL,作为链表的结束标志。
数组的存储空间是连续的,而链表是根据实际情况动态申请内存的,因此其存储空间不是连续的。单项链表的示意结构

单向链表每一个结点中的指针区域必须存放下一个结点的地址,才能寻找到下一个结点,否则将不能寻找到下一个结点。

单向链表的每一个结点数据结构定义形式如下:

struct node{    int data;    struct node *next;};定义一个结点类型node,通过该标识符可以定义变量,整形变量指向struct node 类型的指针next

范例10.15 通过下面的例子,简单了解链表的概念

分析:单向链表的存储是单向的,其存取必须按照顺序进行。每个结点指针区域应存放下一个结点的地址,只有最后一个结点的指针区域为空。

#include <stdio.h>struct st{    char name[10];    int n;    float score;    struct st *next; //结构体类型指针};void main(){    struct st s1 = { "wanghu",5324,98.5,NULL };//定义结构体变量s1并进行初始化    struct st s2 = { "liuli",32121,78.5,NULL };    struct st s3 = { "Hutao",45221,86.0,NULL };    struct st s4 = { "Yuyan",21243,89.9,NULL };    struct st *head;  //定义结构体头指针    head = &s1;    //使head指向结构体s1    s1.next = &s2;  //使s1中next指针指向结构体s2    s2.next = &s3;    s3.next = &s4;    s4.next = NULL; //使s4中next指针为空    struct st *p = NULL; //定义结构体指针指向链表头结点    while (p != NULL)//通过while循环输出所有结点    {        printf("%s,%d,%.lf\n",p->name,p->n,p->score);        p = p->next;    }}

定义了一个链表的结点类型
其中包括三种数据级一个指向结构体的指针
定义4个结点并对其初始化
定义一个头结点,用来指向第一个结点
将第一个结点S1的地址赋给头结点,后一个结点的地址赋给前一个结点的指针域,最后一个结点的指针赋值为空

10.8.3 双向链表

双向链表包含有两个指针。单向链表只有一个指针,指向下一个结点,因此要在单向链表中寻找一个结点,只能从开始的位置逐个往下寻找,
双向链表中包含有两个指针,分别指向前驱和后继,因此可以从该链表中的任一结点出发来寻找某一结点,可以很方便地访问一个结点的前驱和后继

双向链表的定义形式如下:

struct node {    int data;    struct node *pre;    struct node *next;};

上述程序定义了一个双向链表结点类型,其中包含三个部分,数据域data,指向结点前驱指针pre和后向结点后继指针next。

10.17 编写一个程序,实现双向链表的输入、输出

#include <stdio.h>#include <malloc.h>typedef struct node{    int data;    struct node *pre;    struct node *next;}lnode,*link;void create(link *l){    int i, n;    link p, q;    (*l) = (link)malloc(sizeof(node));    (*l)->pre = *l;    (*l)->next = NULL;    q = *l;    printf("输入链表的大小: ");    scanf("%d",&n);    for (i = 1; i <= n; i++)    {        p = (link)malloc(sizeof(node));        printf("输入数据: ");        scanf("%d",&p->data);        p->next = NULL;        p->pre = q;        p->next = p;        q = p;    }}void prin(link l){    l = l->next;    while (l)    {        printf("%d ",l->data);        l = l->next;    }}void main(){    link L;    create(&L);    prin(L);}定义了双向链表结点类型名node和类型指针link.自定义函数create()通过该函数来创建双向链表通过while循环判断下一个结点是否为空来输出链表中的结点。

10.8.4 链表中插入结点和删除节点

10.18
修改10.17,实现链表中的结点的插入和删除功能。
分析:要插入一个结点,首先应明确其要插入的位置,可通过改变相邻位置结点指针域的值来实现结点的插入。
要删除一个结点,也可以通过改变指针域来实现。

#include <stdio.h>#include <malloc.h>typedef struct node{    int data;    struct node *next;}lnode, *link;void create(link *l){    int i, n;    link p, q;    (*l) = (link)malloc(sizeof(node));    (*l)->next = NULL;    q = *l;    printf("输入链表的大小: ");    scanf("%d", &n);    for (i = 0; i < n; i++)    {        p = (link)malloc(sizeof(node));        printf("输入数据: ");        scanf("%d", &p->data);        p->next = NULL;        p->next = p;        q = p;    }}void insert(link *l){    int i, n, e;    link p = *l, s;    printf("输入要插入结点的位置: ");    scanf("%d", &n);    printf("输入要插入结点的值: ");    scanf("%d", &e);    for (i = 1; i < n; i++)    {        p = p->next;        s = (link)malloc(sizeof(node));        s->data = e;        s->next = p->next;        p->next = s;    }}void idelete(link *l){    int i, n;    link p = *l, s;    printf("输入要删除结点的位置: ");    scanf("%d", &n);    for (i = 1; i < n; i++)    {        p = p->next;        s = p->next;        p = p->next;        p->next = s->next;    }}void prin(link l){    l = l->next;    while (l)    {        printf("%d ", l->data);        l = l->next;    }    printf("\n");}void main(){    link L;    create(&L);    prin(L);    insert(&L);    prin(L);}

自定义函数insert().该函数的功能将一个结点插入至链表的指定位置,形成一个新的链表。
自定义函数prin(),此函数用来删除链表上特定位置上的结点,从而实现删除结点的功能。

0 0
原创粉丝点击