C语言结构体和其他数据形式(C Primer Plus 第六版)

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

这节会学到->运算符和struct结构 学完这些,再去学数据结构就很简单了! 加油!

资源在这

Screenshot_20211001_171431_com.tencent.mm.jpg

一、主要内容

关键字:struct、union、typedef

运算符:. ->

什么是C结构,如何创建结构模板和结构变量

如何访问结构的成员,如何编写处理结构的函数

联合和指向函数的指针

二、生活中的实例

书店老板要去打印一份图书目录。她想打印每本书的各种信息,如作者,书名,出版社、价格等等,这些项目可以存放在不同的数组中,可是用这么多数组表示有点麻烦和繁琐了,因此如果能把图书目录的信息都包含在一个数组里更好,其中每个元素包含一个本书的相关信息

C结构就能满足这个需求

实例

#include <stdio.h>
#include <string.h>

char * s_gets(char * st,int n);

#define MAXTITL 41  /*书名的最大长度+1*/
#define MAXAUTL 31  /*作者姓名的最大长度*/

struct book {   //结构模板:标记是book
    char title[MAXTITL];
    char author[MAXAUTL];
    float value;
};              //结构模板结束

int main(void)
{
    struct book library;   //把library声明为一个book类型的变量

    printf("Please enter the book title.\n");
    s_gets(library.title,MAXTITL);  //访问title部分
    printf("Now enter the author.\n");
    s_gets(library.author,MAXAUTL);
    printf("Now enter the value.\n");
    scanf("%f",&library.value);
    printf("%s by %s: $%.2f\n",library.title,library.author,library.value);
    printf("%s: \"%s\" ($%.2f)\n",library.author,library.title,library.value);
    printf("Done.\n");

}

char * s_gets(char * st,int n)
{
    char * ret_val;
    char * find;

    ret_val = fgets(st,n,stdin);
    if (ret_val)
    {
        find = strchr(st,'\n');
        if (find)
        
            *find = '\0';
        else
            while (getchar() != '\n')
                continue;
    }
    return ret_val;    
}


输出的结果为:
PS D:\Code\C\结构> cd "d:\Code\C\结构\" ; if ($?) { gcc struct.c -o struct } ;
 if ($?) { .\struct }
Please enter the book title.
Now enter the author.
罗贯中
Now enter the value.
99.9
三国演义 by 罗贯中: $99.90
罗贯中: "三国演义" ($99.90)
Done.
复制代码

程序中创建的结构有3部分,每个部分都称为成员(member))或字段(field)。这3部分中,一部分储存书名,一部分储存作者名,一部分储存价格。下面是必须掌握的3个技巧:

  • 为结构建立一个格式或样式;
  • 声明一个适合该样式的变量;
  • 访问结构变量的各个部分。

三、建立结构声明

结构声明(structure declaration)描述了一个结构的组织布局。声明类似下面这样:

struct book {   //结构模板:标记是book
    char title[MAXTITL];
    char author[MAXAUTL];
    float value;
}; 
复制代码

​ 该声明描述了一个由两个字符数组和一个float类型变量组成的结构。该声明并未创建实际的数据对象,只描述了该对象由什么组成。(有时,我们把结构声明称为模板,因为它勾勒出结构是如何储存数据的.如果读者知道C++的模板,此模板非彼模板,C++中的模板更为强大。)我们来分析一些细节。首先是关键字struct,它表明跟在其后的是一个结构,后面是一个可选的标记(该例中是 book),稍后程序中可以使用该标记引用该结构。所以,我们在后面的程序中可以这样声明: ​ struct book library; 这把library声明为一个使用book 结构布局的结构变量。

四、定义结构变量

​ 结构有两层含义。一层含义是“结构布局”,刚才已经讨论过了。结构布局告诉编译器如何表示数据,但是它并未让编译器为数据分配空间。下一步是创建一个结构变量,即是结构的另一层含义。程序中创建结构变量的一行是: ​ struct book library; ​ 编译器执行这行代码便创建了一个结构变量library。编译器使用book模板为该变量分配空间:个内含MAXTITL个元素的char数组、一个内含MAXAUTL个元素的char数组和一个float类型的变量。这些存储空间都与一个名称library结合在一起 ​ 在结构变量的声明中,**struct book所起的作用相当于一般声明中的int或float。**例如,可以定义两个struct book类型的变量,或者甚至是指向struct book类型结构的指针: ​ struct book doyle, panshin, * ptbook;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nI9ueNJZ-1634262932337)(E:\Typora\Image\image-20211014180105069.png)]

结构变量 doyle和 panshin中都包含title、author和 value部分。指针 ptbook可以指向doyle,panshin或任何其他book类型的结构变量。从本质上看, book结构声明创建了一个名为structbook的新类型。 就计算机而言,下面的声明:

​ struct book library;

​ 是以下声明的简化:

​ struct book { ​ char title [ MAXTITL];

​ char author [AXA0TL];

​ float value; ​ }library; //声明的右右花括号后跟变量名

换言之,声明结构的过程和定义结构变量的过程可以组合成一个步骤。如下所示,组合后的结构声明和结构变量定义不需要使用结构标记: struct { //无结构标记 char title [ MAXTITL];

​ char author [ MAXAUTL];

​ float value; ​ } library; 然而,如果打算多次使用结构模板,就要使用带标记的形式;在这个例子中,并未初始化结构变量。

4.1 初始化结构

初始化变量和数组如下:

int count = 0 ;

int fibo [ 7] = {0,1,1,2,3,5,8 };

​ 结构变量是否也可以这样初始化?是的,可以。初始化一个结构变量(ANSI之前,不能用自动变量初始化结构:ANSI之后可以用任意存储类别)与初始化数组的语法类似

​ 简而言之,我们使用在一对花括号中括起来的初始化列表进行初始化,各初始化项用逗号分隔 因此,**title成员可以被初始化为一个字符串,value 成员可以被初始化为一个数字。**为了让初始化项与结构中各成员的关联更加明显,我们让每个成员的初始化项独占一行。这样做只是为了提高代码的可读性,对编译器而言,只需要用逗号分隔各成员的初始化项即可。

不是我说,这本书写的真好!

4.2 访问结构成员

结构类似于一个“超级数组”,这个超级数组中,可以是一个元素为char类型,下一个元素为forat类型,下一个元素为int 数组。可以通过数组下标单独访问数组中的各元素,那么,如何访问结构中的成员﹖使用结构成员运算符——一点( .)访问结构中的成员。例如,library.value即访问library的value部分。可以像使用任何float类型变量那样使用library.value。与此类似,可以像使用字符数组那样使用library.title。因此,上面的程序中有 s_gets(library.title,MAXTITL);和scanf(""号f",&library.value);这样的代码。

C语言不亏是基础,如果首先学会C语言,学面向对象这种访问方式应该更好理解了!

学过其他语言应该会很好理解,就像Java中访问类中的方法也是类名.方法名()

五、结构数组

实例:

#include <stdio.h>

int main(void)
{

    struct book
    {
        char title[MAXTITL];
        char author[MAXAUTL];
        float value;
    };
    

    return 0;
}

复制代码

学完这个就能学到->运算符了,学完它再去学数据结构就够了!真的令人兴奋啊!

5.1 声明结构数组

​ 声明结构数组和声明其他类型的数组类似。下面是一个声明结构数组的例子:

​ struct book library [MAXBKS] ;

以上代码把library声明为一个内含MAXBKS 个元素的数组。数组的每个元素都是一个book类型的数组。因此,library [0]是第1个book类型的结构变量,library[1]是第2个book类型的结构变量,以此类推。参看图14.2可以帮助读者理解。数组名library本身不是结构名,它是一个数组名,该数组中的每个元素都是struct book类型的结构变量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DwAhlymI-1634262932341)(E:\Typora\Image\image-20211014182903568.png)]

5.2 标识结构数组的成员

​ 为了标识结构数组中的成员,可以采用访问单独结构的规则:在结构名后面加一个点运算符,再在点运算符后面写上成员名。如下所示: ​ library [ 0 ].value //第1个数组元素与value相关联

​ library [ 4 ].title //第5个数组元素与title 相关联

​ 注意,数组下标紧跟在library后面,不是成员名后面:

​ library.value [ 2] //错误 ​ library [2] .valuel //正确 ​ 使用library[2] .value的原因是: library[2]是结构变量名,正如library[1]是另一个变量名。顺带一提,下面的表达式代表什么? ​ library [2 ].title [ 4 ] ​ 这是library数组第3个结构变量(library[2]部分)中书名的第5个字符(title[ 4]部分)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Swj1oaVi-1634262932343)(E:\Typora\Image\image-20211014183405537.png)]

书上写的太详细了!

5.3 结构数组的嵌套

struct names                    //定义结构体names
{
    char first[LEN];
    char last[LEN];
};

struct guy                      //定义结构体guy
{
    struct names handle;
    char favfood[LEN];
    char job[LEN];
    float income; 
};

//初始化数组
int main(void)
{
    struct guy fellow[2] = {    
        //这是一个结构嵌套,guy结构里嵌套了names结构
        //初始化结构数组fellow,每个元素都是一个结构变量
        {{"Ewen","Villard"},
        "girlled salmon",
        "personality coach",
        68112.00
        },
        {{"Rodney","Swillbelly"},
        "tripe",
        "tabloid editor",
        432400.00
        }
	};
}
复制代码

应该很容易理解,在结构体gay里放了一个 names 结构体,然后就是赋值了

六、指向结构的指针

​ 喜欢使用指针的人一定很高兴能使用指向结构的指针。至少有4个理由可以解释为何要使用指向结构的指针。第一,就像指向数组的指针比数组本身更容易操控(如,排序问题)一样,指向结构的指针通常比结构本身更容易操控。第二,在一些早期的C实现中,结构不能作为参数传递给函数,但是可以传递指向结构的指针。第三,即使能传递一个结构,传递指针通常更有效率。第四,一些用于表示数据的结构中包含指向其他结构的指针。

good,指针真不错!

实例

#include <stdio.h>
#define LEN 20

struct names                    //定义结构体names
{
    char first[LEN];
    char last[LEN];
};

struct guy                      //定义结构体guy
{
    struct names handle;
    char favfood[LEN];
    char job[LEN];
    float income; 
};

int main(void)
{
    struct guy fellow[2] = {    
        //这是一个结构嵌套,guy结构里嵌套了names结构
        //初始化结构数组fellow,每个元素都是一个结构变量
        {{"Ewen","Villard"},
        "girlled salmon",
        "personality coach",
        68112.00
        },
        {{"Rodney","Swillbelly"},
        "tripe",
        "tabloid editor",
        432400.00
        }
    };


    struct guy * him;       //这是一个指向结构的指针
    printf("address #1:%p #2:%p\n",&fellow[0],&fellow[1]);
    him = &fellow[0];       //告诉编译器该指针指向何处
    printf("pointer #1:%p #2:%p\n",him,him+1);//两个地址
    printf("him->income is $%.2f:(*him).income is $%.2f\n",him->income,(*him).income);//68112.00
    //指向下一个结构,him加1相当于him指向的地址加84。names结构占40个字节,favfood占20字节,handle占20字节,float占4个字节,所以地址会加84
    him++;     
    printf("him->favfood is %s: him->handle.last is %s\n",him->favfood,him->handle.last);
    //因为有了上面的him++,所以指向的是favfood1[1],
    return 0;
}




输出结果为
PS D:\Code\C\结构> cd "d:\Code\C\结构\" ; if ($?) { gcc structDemo02.c -o structDemo02 } ; if ($?) { .\structDemo02 }
address #1:000000000061FD70 #2:000000000061FDC4
pointer #1:000000000061FD70 #2:000000000061FDC4
him->income is $68112.00:(*him).income is $68112.00
him->favfood is tripe: him->handle.last is Swillbelly
   
复制代码

这个例题看懂了,自我感觉指针学的算是有个基本了解了!给自己点赞哦!

6.1 声明和初始化结构指针

​ 声明结构指针很简单

​ struct guy * him;

​ 声明结构指针很简单:struct guy * him; 首先是关键字 struct,其次是结构标记 guy,然后是一个星号(*),其后跟着指针名。这个语法和其他指针声明一样。 ​ 该声明并未创建一个新的结构,但是指针him现在可以指向任意现有的guy类型的结构。例如,如果barney是一个guy类型的结构,可以这样写: ​ him = &barney; ​ 和数组不同的是,结构名并不是结构的地址,因此要在结构名前面加上&运算符。 ​ 在本例中,fellow是一个结构数组,这意味着fellow[0]是一个结构。所以,要让 him指向fellow [ 0],可以这样写:

​ him = &fellow[0];

​ 输出的前两行说明赋值成功。比较这两行发现,him指向fellow[0],him + 1指向fellow[1]。注意,him加1相当于him指向的地址加84。在十六进制中,874 - 820 = 54(十六进制)= 84 (十进制),因为每个guy结构都占用84字节的内存: names.first占用20字节,names.last占用20字节,favfood占用20字节,job占用20字节,income占用4字节(假设系统中float占用4字节).顺带一提,在有些系统中,一个结构的大小可能大于它各成员大小之和。这是因为系统对数据进行校准的过程中产生了一些“缝隙”。例如,有些系统必须把每个成员都放在偶数地址上,或4的倍数的地址上。在这种系统中,结构的内部就存在未使用的“缝隙”。

6.2 用指针访问成员

指针him指向结构变量fellow[0],如何通过him获得fellow[0]的成员的值? 第1种方法也是最常用的方法:使用->运算符。该运算符由一个连接号(-)后跟一个大于号(>)组成。我们有下面的关系:

如果him == &barney,那么him->income 即是barney.income如果him == &fellow [ 0],那么him->income 即是 fellow [ 0 ] .income

​ 换句话说,->运算符后面的结构指针和.运算符后面的结构名工作方式相同(不能写成him.income,因为him不是结构名)。 ​ 这里要着重理解him是一个指针,但是 him->income是该指针所指向结构的一个成员。所以在该例中,him->income是一个float类型的变量。 ​ 第2种方法是,以这样的顺序指定结构成员的值:如果him == &fellow[0],那么him == fellow[0]因为**&和***是一对互逆运算符。因此,可以做以下替代:

fellow[0].income == (*him) .income
复制代码

必须要使用圆括号,因为.运算符比*运算符的优先级高。 ​ 总之,如果him是指向guy类型结构barney的指针,下面的关系恒成立: ​ barney.income == (*him).income == him->income //假设him == &barney

七、联合简介

​ 联合(union)是一种数据类型,它能在同一个内存空间中储存不同的数据类型(不是同时储存)。其典型的用法是,设计一种表以储存既无规律、事先也不知道顺序的混合类型。使用联合类型的数组,其中的联合都大小相等,每个联合可以储存各种数据类型。

​ 创建联合和创建结构的方式相同,需要一个联合模板和联合变量。可以用一个步骤定义联合,也可以用联合标记分两步定义。下面是一个带标记的联合模板:

union hold { int digit;

​ double bigfl;

​ char letter; };

​ 根据以上形式声明的结构可以储存一个int类型、一个double类型和char类型的值。然而,声明的联合只能储存一个int类型的值或一个double类型的值或char类型的值。

​ 下面定义了3个与hold类型相关的变量:

​ union hold fit; // hold类型的联合变量

​ union hold save [10]; //内含10个联合变量的数 union hold * pu; //指向hold类型联合变量的指针

​ 第1个声明创建了一个单独的联合变量fit。编译器分配足够的空间以便它能储存联合声明中占用最大字节的类型。在本例中,占用空间最大的是double类型的数据。在我们的系统中,double类型占64位,即8字节。第2个声明创建了一个数组save,内含10个元素,每个元素都是8字节。第3个声明创建了一个指针,该指针变量储存hold类型联合变量的地址。

7.1 联合和结构体的区别

联合体 用途:使几个不同类型的变量共占一段内存(相互覆盖)

结构体是一种构造数据类型 用途:把不同类型的数据组合成一个整体-------自定义数据类型

八、枚举类型

一个集的 枚举 是列出某些有穷 序列 集的所有成员的程序,或者是一种特定类型对象的计数。

​ 可以用枚举类型((enumerated type)声明符号名称来表示整型常量。使用enum关键字,可以创建一个新“类型”并指定它可具有的值(实际上,enum常量是int类型,因此,只要能使用int类型可以使用枚举类型)。枚举类型的目的是提高程序的可读性。它的语法与结构的语法相同。例如,可以这样声明: ​ enum spectrum (red,orange,yellow,green,blue,violet} ;

​ enum spectrum color;

第1个声明创建了spetrum作为标记名,允许把enum spetrum作为一个类型名使用。第2个声明使color作为该类型的变量。第1个声明中花括号内的标识符枚举了spectrum变量可能有的值。因此,color可能的值是red、orange、yellow 等。这些符号常量被称为枚举符(enumerator)。然后便可这样用: int c; color = blue; if (color == yellow)

​ ...

8.1 enum的用法

枚举类型的目的是为了提高程序的可读性和可维护性。如果要处理颜色,使用red和 blue 比使用0和1更直观。注意,枚举类型只能在内部使用。如果要输入color中orange的值,只能输入1,而不是单词orange。或者,让程序先读入字符串"orange",再将其转换为orange 代表的值。

​ 因为枚举类型是整数类型,所以可以在表达式中以使用整数变量的方式使用enum变量。它们用在case语句中很方便。

九、typedef简介

typedef 工具是一个高级数据特性,利用typedef可以为某一类型自定义名称。这方面与#define类似,但是两者有3处不同:

  • 与#define不同,typedef创建的符号名只受限于类型,不能用于值。
  • typedef由编译器解释,不是预处理器。
  • 在其受限范围内,typedef 比#define更灵活。

​ 下面介绍typedef的工作原理。假设要用BYTE表示1字节的数组。只需像定义个char 类型变量一样定义BYTE,然后在定义前面加上关键字typedef 即可: ​ typedef unsigned char BYTE; ​ 随后,便可使用BYTE来定义变量:

​ BYTE x, y [10],* Z;

​ 该定义的作用域取决于typedef定义所在的位置。如果定义在函数中,就具有局部作用域,受限于定义所在的函数。如果定义在函数外面,就具有文件作用域。 ​ 通常,typedef定义中用大写字母表示被定义的名称,以提醒用户这个类型名实际上是一个符号缩写。当然,也可以用小写: ​ typedef unsigned char byte; ​ typedef中使用的名称遵循变量的命名规则。 ​ 为现有类型创建一个名称,看上去真是多此一举,但是它有时的确很有用。在前面的示例中,用BYTE代替unsigned char表明你打算用BYTE类型的变量表示数字,而不是字符码

typedef的一些特性与#define的功能重合。例如:

#define BYTE unsigned char

这使预处理器用BYTE替换unsigned char。但是也有#define没有的功能:

typedef char * STRING;

**没有typedef关键字,编译器将把STRING识别为一个指向char的指针变量。有了typedef关键字,编译器则把STRING解释成一个类型的标识符,该类型是指向char的指针。**因此:

STRING name,sign ;

相当于: *char name, * sign ;

但是,如果这样假设:#define STRING char *然后,下面的声明: STRING name,sign;

将被翻译成:

char *name, sign;

这导致只有name才是指针。

还可以把typedef用于结构:

typedef struct complex {

​ float real;

​ float imag;

}COMPLEX;

​ 然后便可使用COMPLEX类型代替complex结构来表示复数。使用typedef 的第1个原因是 **为经常出现的类型创建一个方便、易识别的类型名。**例如,前面的例子中,许多人更倾向于使用STRING或与其等价的标记。

​ 用typedef来命名一个结构类型时,可以省略该结构的标签:

typedef struct ( double x; double y; } rect;

假设这样使用typedef定义的类型名:

rect r1= { 3.0,6.0 ) ;

rect r2 ;

以上代码将被翻译成:

struct { double x; double y; } r1= { 3.0, 6.0 };

struct { double x; double y; } r2;

r2 = r1;

​ 这两个结构在声明时都没有标记,它们的成员完全相同(成员名及其类型都匹配),C认为这两个结构的类型相同,所以r1和r2间的赋值是有效操作。

​ 使用t ypedef的第2个原因是: t**ypedef常用于给复杂的类型命名。*例如,下面的声明;typedef char ( FRPTC ())[5]; 把FRPTC声明为一个函数类型,该函数返回一个指针,该指针指向内含5个char类型元素的数组(参见下一节的讨论)。

​ 使用typedef时要记住,**typedef并没有创建任何新类型,它只是为某个已存在的类型增加了一个方便使用的标签。**以前面的STRING为例,这意味着我们创建的STRING类型变量可以作为实参传递给以指向char指针作为形参的函数。 通过结构、联合和typedef,C提供了有效处理数据的工具和处理可移植数据的工具。

十、关键概念

​ 我们在编程中要表示的信息通常不只是一个数字或一些列数字。程序可能要处理具有多种属性的实体。例如,通过姓名、地址、电话号码和其他信息表示一名客户;或者,通过电影名、发行人、播放时长、售价等表示一部电影DVD。C结构可以把这些信息都放在一个单元内。在组织程序时这很重要,因为这样可以把相关的信息都储存在一处,而不是分散储存在多个变量中。

​ 设计结构时,开发一个与之配套的函数包通常很有用。例如,写一个以结构(或结构的地址)为参数的函数打印结构内容,比用一堆printf()语句强得多。因为只需要一个参数就能打印结构中的所有信息。如果把信息放到零散的变量中,每个部分都需要一个参数。另外,如果要在结构中增加一个成员,只需重写函数,不必改写函数调用。这在修改结构时很方便。

​ 联合声明与结构声明类似。但是,联合的成员共享相同的存储空间,而且在联合中同一时间内只能有一个成员。实质上,可以在联合变量中储存一个类型不唯一的值。

​ enum 工具提供一种定义符号常量的方法,typedef 工具提供一种为基本或派生类型创建新标识符的方法。

指向函数的指针提供一种告诉函数应使用哪一个函数的方法。(这个文章没有更新,用的不多,有需要的话再回来更新)

十一、总结

​ C结构提供在相同的数据对象中储存多个不同类型数据项的方法。可以使用标记来标识一个具体的结构模板,并声明该类型的变量。通过成员点运算符(.)可以使用结构模版中的标签来访问结构的各个成员。

​ 如果有一个指向结构的指针,可以用该指针和间接成员运算符(->)代替结构名和点运算符来访问结构的各成员。和数组不同,结构名不是结构的地址,要在结构名前使用&运算符才能获得结构的地址。

​ 一贯以来,与结构相关的函数都使用指向结构的指针作为参数。现在的C允许把结构作为参数传递,作为返回值和同类型结构之间赋值。然而,传递结构的地址通常更有效。

联合使用与结构相同的语法。然而,联合的成员共享一个共同的存储空间。联合同一时间内只能储存一个单独的数据项,不像结构那样同时储存多种数据类型。也就是说,结构可以同时储存一个int 类型数据、一个double类型数据和一个char类型数据,而相应的联合只能保存一个int类型数据,或者一个double类型数据,或者一个char类型数据。

通过枚举可以创建一系列代表整型常量(枚举常量)的符号和定义相关联的枚举类型。

typedef 工具可用于建立C标准类型的别名或缩写。

猜你喜欢

转载自juejin.im/post/7019122868499775518