该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
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为数据类型创建别名;
-可以用“指定初始化器”按名设置结构或联合的字段;
-联合可以在同一个存储器单元中保存不同的数据类型;
-可以用枚举创建一组符号;
-可以用位字段控制结构中的某些位;