C语言(Head First C)-6_2:结构、联合与位字段:结构更新、联合、枚举和位字段

该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!

 6_2:结构、联合与位字段:结构更新、联合、枚举和位字段

 

 如何更新结构:

 结构就是把一组绑在一起的变量当做一条数据处理;我们已经学会了创建结构对象,并使用点表示法访问结构的值,那么怎么修改结构中已经存在的某个值呢?

 我们可以像修改变量一样修改字段:structname.name = xxx;

 (Code6_1)

/*
 *
 */

#include <stdio.h>

typedef struct{
    const char * name;
    const char * species;
    int age;
}turtle;

void happy_birthday(turtle t){
    printf("F1-Happy birthday:%s!\nYou are now %i years old!\n",t.name,t.age);
    t.age = t.age + 1;
    printf("F2-Happy birthday:%s!\nYou are now %i years old!\n",t.name,t.age);
}

int main() {
    
    turtle myrtle = {"Flower","Beauty",20};
    happy_birthday(myrtle);
    printf("M1-Happy birthday:%s!\nYou are now %i years old!\n",myrtle.name,myrtle.age);
    
    return 0;
}

 log:

 F1-Happy birthday:Flower!

 You are now 20 years old!

 F2-Happy birthday:Flower!

 You are now 21 years old!

 M1-Happy birthday:Flower!

 You are now 20 years old!

 

 现在我们都应该知道,只所以age没变,是因为我们创建的结构会复制给形参;

 

 当把一个结构赋给另一个时,结构的值复制到了新结构中;

     C语言中,参数按值传递给函数;调用函数时,传入的值会赋给形参;

 

 如果想把结构传给函数并在函数中更新它的值,该怎么做呢?

 

 需要结构指针:

     使用指针,可以把变量在存储器中的位置告诉函数,函数才能更新保存在那里的数据,才能更新变量;

     我们可以通过传递结构的地址来更新结构:

         void happy_birthday(turtle * t);

         happy_birthday(&myrtle);

 现在修改代码重新运行:

 (Code6_2)

/*
 *
 */

#include <stdio.h>

typedef struct{
    const char * name;
    const char * species;
    int age;
}turtle;

void happy_birthday(turtle * t){
    printf("F1-Happy birthday:%s!\nYou are now %i years old!\n",(*t).name,(*t).age);
    (*t).age = (*t).age + 1;
    printf("F2-Happy birthday:%s!\nYou are now %i years old!\n",(*t).name,(*t).age);
}

int main() {
    
    turtle myrtle = {"Flower","Beauty",20};
    happy_birthday(&myrtle);
    printf("M1-Happy birthday:%s!\nYou are now %i years old!\n",myrtle.name,myrtle.age);
    
    return 0;
}

 log:

 F1-Happy birthday:Flower!

 You are now 20 years old!

 F2-Happy birthday:Flower!

 You are now 21 years old!

 M1-Happy birthday:Flower!

 You are now 21 years old!

 

 代码分析:

     得到指针指向的值,需要把*放在指针变量名前;

     *t我们使用可圆括号括起来了,括号很重要,省略会出错;

 

 (*t).age和*t.age:

     之所以有区别,是因为这是两个完全不同的表达式;

     前者好理解,t指向结构,取结构的age变量;——(*t).age

     后者其实是,指向t结构age变量的内容;——*(t.age)

     "."运算符的优先级更高;

 

 使用结构时要小心括号的位置,它会影响表达式的值;

 

 现在,我们通过传递结构指针,函数更新了原来的数据,而不是修改本地的副本;

 

 另一种表示结构指针的方法:

     更简洁,更易懂:t->age; <=> (*t).age;

     t->age表示 由t指向的结构中的age字段;

 

 现在看一个问题:

     我们可以用结构来模拟现实世界中错综复杂的事物;但是有些数据不只一种类型,比如某些描述“量”的数据,商店卖了3个苹果,也可以说成1kg苹果,或者一袋苹果;他们是以不同的方式在描述量,正对应了同一种事物,不同的数据类型;

     当然,我们也可以使用结构来创建多个字段进行使用;

 but:

     结构在存储器中占了更多的空间;

     用户可能设置多个值(但都表示同一个事物);

     没有叫“量”的字段;

 所以:

     要是能定义一种叫“量”的数据类型,然后根据特定的数据决定要保存的是个数、重量还是体积(其中无论哪一个都可以表示量的概念);

     C语言中,联合可以做到这点;

 

 联合:

     联合可以有效的使用存储器空间;

     在创建结构时,计算机会在存储器中相继摆放字段;联合则不同;当定义联合时,计算机只会为其中一个字段分配空间;例:

 typedef union {

     short count;//数量

     float weight;//重量

     float volume;//体积

 } quantity;

     这个联合中,计算机会为其中最大的字段分配空间,然后由你决定里面保存什么值;无论设置了count,weight和volume中的哪个字段,数据都会保存在存储器中的一个地方;

 

     联合使用union关键字;且所有字段都保存在同一个地方;

 

 使用联合:
 1)C89方式:

     如果联合要保存第一个字段的值,可以用这种方式,只要用花括号把值括起来,就可以把值赋给联合中第一个字段;

     quantity q = {4};//设成count值

 2)指定初始化器:

     指定初始化器按名设置联合字段的值;

     quantity q = {.weight = 1.5};//设成weight值;

     属于C99标准,OC支持“指定初始化器”,C++不支持;

 3)“点”表示法:

     第一行创建变量,第二行设置变量的值;

     quantity q;

     q.volume = 3.8;

 

 切记:无论使用那种方式进行联合的赋值,都只会保存一条数据;

 联合提供了一种让你创建不同数据类型的变量的方法;

 

 我们看到的指定初始化器的方式,其实也可以用来设置结构字段的初值:

     如果结构有很多字段,但只想为其中某些字段赋初值,指定初始化器就非常有用;同时还能提高代码的可读性;

 例:

 typedef struct {

     int age;

     float height;

 }person;

 person p = {.age = 21 , .height = 165.5};

 

 联合常和结构一起用:

     创建的联合是一种新的数据类型;就像使用整形或结构那样的数据类型;

     可以使用之前使用过的“点”表示法 或 “->”表示法访问“结构/联合”组合中的值;

 (Code6_3)

/*
 * 结构和联合的结合使用
 */

#include <stdio.h>

typedef union {
    float lemon;
    int lime_pieces;
}lemon_lime;

typedef struct {
    float tequila;
    float cointreau;
    lemon_lime citrus;
}margarita;

int main() {
    
    margarita m = {2.0,1.0,{4}};
    margarita m1 = {2.0,1.0,{.lime_pieces = 2}};
    margarita m2 = {2.0,1.0,.citrus.lime_pieces = 3};
    margarita m3 = {2.0,1.0,0.5};
    
    printf("m :%4.1f-%4.1f-%4.1f\n",m.tequila,m.cointreau,m.citrus.lemon);
    printf("m1:%4.1f-%4.1f-%i\n",m1.tequila,m1.cointreau,m1.citrus.lime_pieces);
    printf("m2:%4.1f-%4.1f-%i\n",m2.tequila,m2.cointreau,m2.citrus.lime_pieces);
    printf("m3:%4.1f-%4.1f-%4.1f\n",m3.tequila,m3.cointreau,m3.citrus.lemon);
    
    return 0;
}

 log:

 m : 2.0- 1.0- 4.0

 m1: 2.0- 1.0-2

 m2: 2.0- 1.0-3

 m3: 2.0- 1.0- 0.5

 

 上述代码提供了联合和结构结合使用时常用的几种初始赋值的方式;注意最后一种也是可行的,但最好不要这样写;

 

 变身编译器:

     如下两种方式的代码,哪段能编译通过?

 (1)margarita m = {2.0 , 1.5 , {0.5}};

 (2)margarita m;

 m = {2.0 , 1.5 , {0.5}};

 

 这里第二段代码将不能编译,因为只有把数据和结构声明写在一行,编译器才知道它代表结构,否则,编译器会认为它是数组;

 

 现在又一个问题:

     我们可以在联合中保存各种可能的值,但是保存以后,无法知道它的类型;编译器不会记录已经设置过的字段,我们可以设置一个字段,然后去读另外一个字段,最终得到的结果可能是错的;

     我们需要某种方式记录我们在联合中保存的是什么值;C程序员常使用的一种技巧是创建枚举;

 

 枚举变量保存符号:

     有时不想保存数组或文本,指向保存一组符号;

     有了枚举就可以创建这样一组符号;

 例:

 enum colors {

     RED,

     GREEN,

     BLACK,

 };

 

 用enum colors类型定义的变量只能设为列表中的某个关键字,如:enum colors favorite = RED;

     在幕后,计算机会为列表中的每个符号分配一个数字,枚举变量中也只保存数字(是什么不用操心);

     枚举让代码更易读,也防止值的错误设置;

 注意:

     结构和联合使用分号来分割数据项,而枚举用逗号;

 

 现在我们来处理之前提出的问题:

     我们可以通过枚举(数据类型)的值来判断,使用联合的时候,该使用的字段以及数据类型是什么;

 

 有时你想控制某一位:

     假设一个结构,其中多个字段表示‘是’或‘非’;

     我们可以使用short来创建结构,但问题是,真/假的值只需要一位就能表示,short占用了太多的空间,太浪费了;

     要是结构的字段的值能只用一位表示就好了——所以有了位字段(bitfield);

 

二级制须知:

     处理二级制时,如果能够以某种方法在字面值中指定0或1就好了,类似int x = 01010100;

     可惜,C语言不支持二进制字面值(毕竟太low),但是它支持十六进制字面值;如int x = 0x54;(等价于二进制的 0101 0100)

 

 位字段的位数可调:

     可以用位字段指定一个字段有多少位;

 typedef struct {

     unsigned int low_pass_vcf:1;

     unsigned int filter_couples:1;

     unsigned int reverb:1;

     unsigned int sequential:1;

 }synth;

     如上示例中使用的位字段,可以保证每个字段只占一位;

 

 如果是一连串的位字段,计算机会放到一起,以节省空间;连续8个位字段,会被放到一个字节中;

 

 巧用位字段——如何选择位数:

     位字段不仅可以保存一连串真/假值,还可以保存小范围的数组;

     如1-12月,就可以用一个4位的位字段(0-15)来保存:unsigned int month_no:4;

 

 要点:

 -可以用联合在同一个存储器单元中保存不同数据类型;

 -“指定初始化器”按名设置字段的值;

 -C99标准支持“指定初始化器”,C++不支持;

 -如果用{花括号}中的值初始化联合,这个值会以第一个字段的类型保存;

 -你完全可以读取联合中未初始化过的字段,编译器不会干涉,但要小心,因为这么做很有可能会出错;

 -枚举保存符号;

 -可以用位字段自定义字段的位数;

 -位字段应当声明为unsigned int;

 

 C语言工具箱:

 -结构把数据类型组合在一起;

 -可以像初始化数组那样初始化结构;

 -可以用“点”表示法读取结构中的字段;

 -有了“->”表示法,就能用结构指针更新字段,十分方便;

 -可以用typedef为数据类型创建别名;

 -可以用“指定初始化器”按名设置结构或联合的字段;

 -联合可以在同一个存储器单元中保存不同的数据类型;

 -可以用枚举创建一组符号;

 -可以用位字段控制结构中的某些位;

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值