C语言基础(4)—结构

结构

如果需要把一批数据打包成一样东西,就可以使用结构(struct)。struct是structured data type(结构化数据类型)的缩写。有了结构,就可以像下面这样把不同类型的数据写在一起,封装成一个新的大数据类型:

struct fish {
 const char *name;
 const char *species;
 int teeth;
 int age;
};

定义

这段代码会创建一个新的自定义数据类型,它由一批其他数据组成。事实上,结构与数组有些相似,除了以下两点:

  • 结构的大小固定
  • 结构中的数据都有名字

定义新结构以后,如何用它来创建数据?和新建数组很像,你只需要保证每条数据按照它们在结构中定义的顺序出现即可:在这里插入图片描述
我们为什么要选择结构呢,因为把数据放在结构中传递,修改结构的内容时,不必修改使用它的函数。比如
要在fish中多加一个字段:

struct fish {
 const char *name;
 const char *species;
 int teeth;
 int age;
 int favorite_music;
};

函数知道有人会给它一条fish,但却不知道fish中现在有了更多的数据,它也不关心,只要fish有它需要的所有字段就行了。这就意味着,使用结构,不但代码更好读,而且能够更好地应对变化。

以上便是定义自定义数据类型的方法,那么如何读取结构中保存的某条数据呢?

使用“.”

虽然结构和数组很相像,但是读取数据的时候并不能像数组那样使用下标的方法,读取时只能按名访问,可以使用“.”运算符访问结构字段,比如

struct fish snappy = {"Snappy", "piranha", 69, 4};
printf("Name = %s\n", snappy.name);

为结构体赋值

  • 通常情况下不会直接给结构体赋值,但如果新建了一个结构体想为他赋上一个值,就是说结构变量赋给另一个结构变量时,计算机会创建一个全新的结构副本,也就是说,计算机需要再分配一块存储器空间,大小和原来相同,然后把每个字段都复制过去。
    在这里插入图片描述
    所以说这一过程实际上就是相当于,计算机复制数据。

结构体嵌套

定义结构其实就是在创造新的数据类型。C语言有很多内置数据类型,例如int、short和long,但有了结构,我们就可以把现有类型组合起来,向计算机描述更复杂的东西。既然结构可以用现有数据类型创建数据类型,也就能用其他结构创建结构。(我们为什么要嵌套呢,之所以要这么做是为了对抗复杂性。通过使用结构,我们可以建立更大的数据块。通过把结构组合在一起,我们可以创建更大的数据结构。本来你只能用int、short,但有了结构以后,就可以描述十分复杂的东西,比如网络流和视频图像)

struct preferences {
 const char *food;
 float exercise_hours;
};
struct fish {
 const char *name;
 const char *species;
 int teeth;
 int age;
 struct preferences care;
};

当嵌套时,我们如何实现访问呢,可以使用一连串的“.”运算符来访问字段:

printf("Snappy 喜欢吃 %s", snappy.care.food);
printf("Snappy 喜欢锻炼 %f hours", snappy.care.exercise_hours);

typedef命名

当创建内置数据类型变量时,只要写int或double就行了,但每次创建结构变量时,不得不加上struct关键字。

struct cell_phone {
 int cell_no;
 const char *wallpaper;
 float minutes_of_charge;
};
...
struct cell_phone p = {5557879, "sinatra.png", 1.35};

在C语言中,如果我们想实现和int、double一样的方便,那么我们就可以使用typedef,就是为结构体创建别名,只要在struct关键字前加上typedef,并在右花括号后写上类型名,就可以在任何地方使用这种新类型。当你用typedef为结构创建别名,需要决定别名叫什么。别名其实就是类型名,也就是说结构有两个名字:一个是结构名(struct cell_phone),另一个是类型名(phone)。

typedef struct cell_phone {
 int cell_no;
 const char *wallpaper;
 float minutes_of_charge;
} phone;
...
phone p = {5557879, "sinatra.png", 1.35};

当然,如果想让代码更加简单简短,就可以果只写类型名而不写结构名,编译器也没意见:

typedef struct {
 int cell_no;
 const char *wallpaper;
 float minutes_of_charge;
} phone;
phone p = {5557879, "s.png", 1.35};

结构指针

当我们想在正常程序中,实现更新数据,该如何做呢,根据之前非含结构的函数程序代码的经验,我们可以知道直接更改肯定是不行的,所以就需要我们将指针运用到这上面来,以下面一段代码为例

#include <stdio.h>
typedef struct {
 const char *name;
 const char *species;
 int age;
} turtle;
void happy_birthday(turtle t)
{
 t.age = t.age + 1;
 printf("Happy Birthday %s! You are now %i years old!\n",
 t.name, t.age);
}
int main()
{
 turtle myrtle = {"Myrtle", "Leatherback sea turtle", 99};
 happy_birthday(myrtle);
 printf("%s's age is now %i\n", myrtle.name, myrtle.age);
 return 0;
}

运行结果在这里插入图片描述

  • 这段代码创建了一个新的结构,然后把它传给了一个函数,按理说,函数会将结构中某个字段的值递增1,但程序却……你知道age字段在happy_birthday()函数中更新了,因为printf()函数显示了递增以后age的值,但奇怪的是,虽然happy_birthday()更新了age,但程序返回main()函数以后,age又变回了原来的值。
  • 那么如果我们使用结构指针该怎么修改呢?
#include <stdio.h>
typedef struct {
 const char *name;
 const char *species;
 int age;
} turtle;
void happy_birthday(turtle *t)
{
 (*t).age = (*t).age + 1;
 printf("Happy Birthday %s! You are now %i years old!\n",(*t).name, (*t).age);
}
int main()
{
 turtle myrtle = {"Myrtle", "Leatherback sea turtle", 99};
 happy_birthday(myrtle);
 printf("%s's age is now %i\n", myrtle.name, myrtle.age);
 return 0;
}

这里我们要注意,t外面一定要加括号,表达式t.age等于*(t.age)。*(t.age)代表“t.age这个存储器单元中的内容”,但t.age不是存储器单元。在这里插入图片描述
另一种表达方式
如果你嫌打括号麻烦,表示结构指针的方法,它更易于阅读,就是t->aget->age表示“由t指向的结构中的age字段”,也就是说happy_birthday()函数还能这么写:

void happy_birthday(turtle *a)
{
 a->age = a->age + 1;
 printf("Happy Birthday %s! You are now %i years old!\n",
 a->name, a->age);
}

联合

假如想记录某样东西的“量”,既可以用个数,也可以用重量,或者用容积。所以大可在一个结构中创建多个字段:

typedef struct {
 ...
 short count;
 float weight;
 float volume;
 ...
} fruit;
  • 但这并不是一种好的定义方法,因为结构在存储器中占了更多空间;用户可能设置多个值;没有叫“量”的字段。

在这里插入图片描述

定义

当定义联合时,计算机只为其中一个字段分配空间。假设你有一个叫quantity的联合,它有三个字段,分别是count、weight和volume,那么计算机就会为其中最大的字段分配空间,然后由你决定里面保存什么值。无论设置了count、weight和volume中的哪个字段,数据都会保存在存储器中同一个地方。在这里插入图片描述

使用

1.C89

如果联合要保存第一个字段的值,就可以用C89表示法,只要用花括号把值括起来,就可以把值赋给联合
中第一个字段。quantity q = {4};表示“量”是4个

2.指定初始化器

指定初始化器(designated initializer)按名设置联合字段的值:quantity q = {.weight=1.5};把联合设为浮点型的重量值。

3."."表示法

第三种设置联合值的方法是在第一行创建变量,然后在第二行设置字段的值。
quantity q;
q.volume = 3.7;

  • 无论用哪种方法设置联合的值,都只会保存一条数据。联合只是提供了一种让你创建支持不同数据类型的变量的方法。
  • 第二种指定初始化器的方法,可以以用来设置结构字段的初值。如果结构有很多字段,但你只想为其中某些字段设初值,“指定初始化器”就非常有用。同时它还能够提高代码的可读性,可以做到只设置height和gears字段,但不设置color字段。
typedef struct {
 const char *color;
 int gears;
 int height;
} bike;
bike b = {.height=17, .gears=21};

联合与结构

创建联合相当于创建新的数据类型,也就是说可以在任何地方使用它的值,就像使用整型或结构那样的数据类型。

typedef union {
 short count;
 float weight;
 float volume;
} quantity;
typedef struct {
 const char *name;
 const char *country;
 quantity amount;
} fruit_order;

调用方法跟之前的指针结构的两种调用方法相同,

fruit_order apples = {"apples", "England", .amount.weight=4.2};
printf("This order contains %2.2f lbs of %s\n", apples.amount.weight, apples.name);

联合与类型

编译器不会记录你在联合中设置或读取过哪些字段。我们完全可以设置一个字段,读取另一个字段,但有时这会造成很严重的后果。如下代码,我们设置了order中weight是2,并没有设置count,但是在下一行代码中我们打印的确实order.count,

#include <stdio.h>
typedef union {
 float weight;
 int count;
} cupcake;
int main()
{
 cupcake order = {2};
 printf("Cupcakes quantity: %i\n", order.count);
 return 0;
}
  • 如何保证避免这种情况发生,就是创建枚举。

枚举的产生原因有 时 你 不 想 保 存 数 字 或 文 本 , 而 是 想 保 存 一 组 符号。如果你想记录一周中的某一天,只想保存M O N -DAY、TUESDAY、WEDNESDAY……这些符号。你不需要保存文本,因为一共只有七种不同的取值。在这里插入图片描述
用enum colors类型定义的变量只能设为列表中的某个关键字。可以像下面这样定义enum colors变量:
enum colors favorite = PUCE;

位字段

通常我们都是对整体进行控制,如果有时候我们想对结构中的某一位进行控制,其中有很多表示“是”或“非”的值,可以用一些short或int来创建结构:

typedef struct {
 short low_pass_vcf;
 short filter_coupler;
 short reverb;
 short sequential;
 ...
} synth;

我们如何根据需求调整我们的位字段,如何选择位数?

  • 位字段不仅可以用来保存一连串真/假值,还可以用来保存小范围的数字,例如一年中的十二个月。假设想在某个结构中保存月份(0到11的值),就可以用一个4位的位字段来保存,为什么?因为4位可以保存0到15,而
    3位只能保存0到7,需要unsigned int month_no:4;;
  • 但在保存是非位时,就只需要用1位,因为一个位可以保存两个值——真或假
  • 35
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值