【程序设计基于C】第七章 结构体和联合体数据类型 【期末复习】

结构体和联合体数据类型

所谓的结构体,实际上就是一种特殊的类型,千万不能想的复杂。我的理解为,其产生主要是为了处理一些原本存在的类型之间的一一对应关系,其重点就在于解决一一对应的问题。

试想一下,一个学生的最基本的个人信息,包括姓名和学号,而且这两者之间是一一对应的(让我们暂时忽略掉重名的情况)。但是我们应该怎么表示这个一一对应的关系呢?在没学结构体之前我就思考过类似的问题,当时的想法是:

  • 构建一个数组和一个字符串组,然后保证对应序号的名字和学号一一对应就好了

此处这个字符串组的意思是:一个组,组里的每个基本元素都是一个字符串

这样看似问题解决了,但是又引申出了两个新的问题:

  • 如果需要移动的话,必须保证学号和姓名同时移动,这样才能保证其一一应对的关系不变
  • 怎样定义一个“组”让其基本元素是字符串

而结构体的出现,就完美的解决了我刚才提到的这些问题,只需要定义一个student变量类型,这个变量类型里有一个char *name记录名字,同时还有一个int ID记录学号,用计算机语言来表示就是:

struct student
{
	char name[20];
    int ID;
};

也就是说每个student都有其对应的nameID,且nameID保持一一对应的关系,在移动的时候会保证两者一起移动,且对其中的单独元素也可进行修改。

也可也把结构体看成一个大的组,里边可以装各种各样的东西,不仅这些东西可以类型不一样intchar,甚至本身就可以是数组或者字符串,更夸张的是结构体里也能套结构体

1. 结构体类型的定义和使用

而为了便于理解,我将通过一道题目《通讯录排序》为例子进行说明:

该题目要求:输入n个朋友的信息,包括姓名、生日、电话号码,本题要求编写程序,按照年龄从大到小的顺序依次输出通讯录。题目保证所有人的生日均不相同。

1.1 结构体类型的定义

1.1.1 一般形式定义
  • 结构体定义类型的一般形式为:【后边了解typedef之后还有些写法】
struct 结构体名
{
	数据类型名 结构体成员1;
    数据类型名 结构体成员i;
};

其实这个定义并不用刻意的去记忆,见过一两次例题,基本上就都知道了

而真正需要注意的是:

  1. 在调用自己定义的结构体类型的时候需要加上struct

    如区分下边两种情况

    • 正确:struct friend x[100];
    • 错误:friend x[100];
  2. 在定义结构体的末尾需要加上;c语言的语句(除了预处理)后边都需要加上分号

1.1.2 typedef定义形式
  • typedef关键词:其作用在于给现有的数据类型取别名,一般形式为:

    typedef 数据类型名 别名;

数据类型名放在中间,别名放在后边,有了这种方法过后原本比较复杂的结构体名,就可以用一个简单是形式进行表示。其使用具体方式如下

//定义方式1
typedef struct firend
{
    char name[11];    //姓名
    char birthday[9]; //生日
    char phone[30];   //电话

} FRE;

//定义方式2
typedef struct
{
    char name[11];    //姓名
    char birthday[9]; //生日
    char phone[30];   //电话

} FRE;

这两种方式都可以将原本的struct friend简化成FRE,个人更习惯于使用第二种,原因在于可以少想一个结构体名(起名废的快乐就是如此的简单)

1.2 结构体变量的使用方法

刚才的内容,其实都是在说,这种变量类型是如何定义的,但是定义之后的使用才是重点需要掌握的部分。其使用方法和一般变量的使用情况是基本类似的,只是多了一个选择成员的步骤。

1.2.1 变量的初始化(赋值)
  • 结构体变量的初始化:需要对每个成员量进行初始化

可以通过直接赋初值和通过输入的形式赋初值两种形式,其要求和每个变量的初值要求相同,具体情况可以参考以下代码:

#include <stdio.h>
#include <stdlib.h>
#define N 10

typedef struct firend
{
    char name[11];    //姓名
    char birthday[9]; //生日
    char phone[30];

} FRE;

void Input(FRE *p) //录入信息
{
    scanf("%s %s %s", p->name, p->birthday, p->phone);
}


int main()
{
    int n;
    scanf("%d", &n);
    FRE friends;

    //初始化
    friends = {"wang", "19821020", "+86-0571-88018448"};
    //赋值情况1
    Input(&friends); //指针形式
    //赋值情况2
    scanf("%s %s %s", friends.name, friends.birthday, friends.phone);

    return 0;
}

需要注意的是:

  • 表达friend中的成员name有两种方式:
    1. friend.name
    2. friend->name
  • 通过外部输入的时候不要忘记&符号(此处均为字符串,本身就是地址)
  • 初始化的时候,注意区分' '" ",其中字符串用双引号
1.2.2 变量的输出

和刚才提到的变量的输入一样,变量的输出和输入完全是一样的

在此以上边信息的输出函数为例,展示变量的输出

void Output(FRE *p, int n) //输出信息
{
    for (int i = 0; i < n; i++)
        printf("%s %s %s\n", (p + i)->name, (p + i)->birthday, (p + i)->phone);
}

输入输出其实都很简单,主要就在于清楚的表示各成员量,因此在此处就不进行过多的说明

1.2.3 变量的运算

结构体虽然不能直接数学运算(主要是因为其内部成员太多太杂),但是其内部的每个成员,都满足和原来单个出现时候一样的运算规则。由于变量的运算可以说基本没有差别,因此此处重点说说,结构体的函数调用形式,在此通过一个排序函数进行说明:

void Sort(FRE *p, int n) //排序
{
    FRE temp;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n - i - 1; j++)
            if (atoi((p + j)->birthday) > atoi((p + j + 1)->birthday))
                temp = *(p + j), *(p + j) = *(p + j + 1), *(p + j + 1) = temp;
}

题目要求按照人们的年龄进行排序,其实也就是按照生日进行排序。

atoi,即ASCII to int,是一个将字符转化为数字的函数

  • 其函数原型为:int atoi(const char *nptr);

此处根据采用冒泡算法针对年龄进行排序,想说明的有两点:

  1. 是结构体类型的变量,只要是同类型的就可以直接进行赋值

    需要尤其注意的是,即使两个结构体成员内容完全一致,且一一对应,只要不是同一个类型,都不可以

    在此通过一个程序更好的进行理解:

    #include <stdio.h>
    
    typedef struct
    {
        char name[11]; 
    } FRE1;
    
    typedef struct
    {
        char name[11];
    } FRE2;
    
    int main()
    {
        FRE1 a,b;
        FRE2 c;
    
        a = b;
        a = c;
    
        return 0;
    }
    

    可以看出的是

    • 只要是类型名相同的结构体变量,就可以相互赋值a = b;

    • 反之,即使FRE1FRE2内容完全一样,且对应,依然不能进行赋值a = c;(会报错)

  2. 结构体变量传入的是值,而不是地址(这里和数组不同,更像一般的变量)

    而这要求我们如果希望达到处理数组的效果(影响会在主函数保留),就需要通过传递地址的方式处理。

    就如同上一个例子中,排序的处理,传进来的是*p,也就是对应的结构体的地址。如果只是一结构体作为参数,传入值的话,那么需要将结构体设置为返回值。但是此处为结构体数组,本身就是一组结构体,处理起来会稍微有些麻烦。

    个人的建议就是,习惯用传地址的方式进行书写,因为我们能遇到的情况,传地址都能解决问题。

2. 结构体数组和指针的关系

其实在前边的内容里,或多或少的都有提到关于结构体数组和指针的关系,在此单独列出,是为了进行更加详细的解释说明。

2.1 结构体变量和指针的关系

  • 定义形式:FRE *p;:定义变量类型为FRE的指针,可以指向FRE类型的结构体

指针的定义和赋值形式,都和常规变量类型一致,只需要牢牢的保证变量类型和指针类型相对应的原则即可。需要注意的是如何用结构体的指针变量访问成员分量,有两种访问方式:

  1. 指针指向结构体:*p = &friend

  2. 常规式:(*p).name:即访问地址p的结构体的name分量

    请务必不要忘记大括号,因为访问分量的.->的优先级都是最高的

    如果不加括号,则意味着,先访问分量再取地址,会出现错误

  3. 箭头式:p->name:个人喜欢用第二种,因为简单(不用打括号)

    具体的例子可以看上边的程序,是通过第二种方式方位p处的分量

2.2 结构体类型数组和指针的关系

其实整理到这里的时候,我感觉内容已经有些过于冗长,原因是因为正如前边所说,结构体其实就是一种自己定义的新的变量类型。新加入的规则并不多,按照数组的方式理解结构体数组就完全没有问题。因此在此部分,个人重点介绍,自己认为比较重要的地方:

正如最开始所说,结构体的一个特别之处在于,其可以在结构体里套用结构体,反复嵌套,通过一个程序进行说明:

#include <stdio.h>

struct T1 {
	char c[4];
	char *s;
} s1= {"abc","def"};

struct T2 {
	char *cp;
	struct T1 ss1;
} s2= {"ghi","jkl","mno"};

int main() {
	printf("%c,%c\n",s1.c[0],*s1.s);
	printf("%s,%s\n",s1.c,s1.s);
	printf("%s,%s\n",s2.cp,s2.ss1.s);
	printf("%s,%s\n",++s2.cp,++s2.ss1.s);
	return 0;
}

输出结果

a,d
abc,def
ghi,mno
hi,no
  • 存在嵌套的初始化:按照顺序一一赋值

从定义和输出的结果中我们不难看出,对于内含结构体T1的结构体T2,其赋值遵循从上到下一一赋值的规律

struct T2 
{
	char *cp;
	struct T1 ss1;
} s2= {"ghi",{"jkl","mno"}}; //加上大括号会更加的规范

而这样赋予初值的意思就是:给cpT1的第一个分量(c[4]),T2的第一个分量(*s),依次赋值。

  • 存在嵌套的指针:只有第一层用指针

如果我想通过指针的方式调用s2.ss1.s,存在下列两种写法

T2 *p = &s2;
printf("%s,%s\n", ++s2.cp, p->ss1.s);  //写法1
printf("%s,%s\n", ++s2.cp, p->ss1->s); //写法2

曾经的我认为两种写法都是可以的,毕竟我平常就习惯用->的方式写。不过当我真正写过一次之后,我发现写法2是会报错的。原因是因为ss1实际上并不是地址。


未完待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值