C语言-基础入门-学习笔记(14):共用体、枚举和位域
一、共用体
共用体(union)是C语言中的另一种高级语言,它可以使几个不同成员共享同一块内存。
1. 声明共用体类型
在程序中,有时为了节约内存空间,可以使用共用体来使多个变量共享一块内存。声明如下:
union 共用体名{
数据类型名 成员名1;
数据类型名 成员名2;
数据类型名 成员名3;
···
}
与结构体结构不同的是,共用体的所有成员共享同一块内存,而结构体的每个成员都有自己的内存空间。
一个共用体类型的字节长度为占用内存空间最多的成员变量的字节长度。
范例1
#include <stdio.h>
union data_u_t{
int a;
char b;
long long e;
};
int main(void){
printf("sizeof(union data_u_t) = %d\n",sizeof(union data_u_t));
return 0;
}
从结果可以看出共用体的字节长度为最长的long long型变量的长度。
1. 定义共用体变量
与结构体的三种情况相同,下面以typedef为例:
typedef union my_data_u_t{
int a;
char b;
long long c;
}data_u_t;
data_u_t v; //定义共用体变量
使用共用体变量时要注意:
- 由于共用体实际上只有一个有效成员,因此在初始化时,只能使用含一个值的序列对其初始化。
data_u_t v = {1};
- 不可以直接为共用体变量赋值,需要通过赋值其成员来改变共用体的内存空间。
v.a = 2;
v.b = 'c';
v.c = 23LL;
- 可以使用共用体变量来获得共用体的值。
v.a = 2;
printf("%d",v.a);
printf("%d",v);
- 由于所有成员共享同一空间,因此,所有成员的地址都相同。
- 共用体可以作为数组元素,可以作为结构体成员,也可以作为共用体的成员。
- 由于共用体中的所有成员共享一块内存,因此改变其中任一变量都会影响其他变量。
范例2
#include <stdio.h>
typedef union my_data_u_t{
char a;
short b;
int c;
}data_u_t;
void output(data_u_t v){
printf("Output elements in this union:\n");
printf("\tv.a = %d, v.b = %d, v.c = %d, v = %d\n",
v.a,v.b,v.c,v);
}
int main(void){
data_u_t v = {0xff + 2};
output(v);
v.b = 0xffff + 3;
output(v);
v.c = 0xffff + 4;
output(v);
printf("\t&a = %p, &b = %p, &c = %p, &v = %p\n",
&v.a,&v.b,&v.c,&v);
return 0;
}
由程序可知,受不同类型的影响输出的值可能不同,但共用体本身输出的值一定是正确值。
二、枚举
1. 声明枚举类型
在程序中,常常会处理一组相关的属性。如果用const常量可以做如下处理。
const YELLOW = 1;
const RED = 2;
const BLUE = 3;
const BLACK = 4;
car_colour = YELLOW;
bike_colour = BLUE;
但在使用时无法显示地说明这些不同常量之间存在的关系,同时,还无法自动限制这些const常量的值。
枚举类型是一组相关数据的集合,它可以在定义常量的同时,将这些常量结合在一个集合中加强彼此的关系。 声明形式如下:
enum 枚举类型名{
枚举成员1,
枚举成员2,
枚举成员3,
···
}
其中的枚举成员都为int型,如果没有显示赋值,系统会自动将所有枚举成员从0开始赋值,而后面的枚举成员一次比前一个大1。
//形式1
enum colour_t{
YELLOW,
RED,
BLUE,
BLACK
};
enum colour_t car_colour = YELLOW;
enum colour_t car_colour = BLUE;
//形式2
typedef enum my_colour_t{
YELLOW,
RED,
BLUE,
BLACK
}colour_t;
colour_t car_colour = YELLOW;
colour_t car_colour = BLUE;
范例3
#include <stdio.h>
enum colour_t{
YELLOW,RED,BLUE,BLACK
};
int main(void){
enum colour_t c = YELLOW;
printf("sizeof(enum colour_t) = %d\n",sizeof(enum colour_t));
printf("c = %d\n",c);
c = RED;
printf("c = %d\n",c);
c = BLACK;
printf("c = %d\n",c);
return 0;
}
2. 使用枚举变量
- 枚举成员值依次增加1:如果第一次成员被初始化为4的话,下一个从5开始。后面的某个变量被初始化某个值时,它最后的变量依次加1。
- 不要使用枚举成员之外的值初始化枚举变量
enum month_t m = 2; //错误
enum month_t m = (enum month_t) 2; //正确
- 使用整型数值初始化:只能使用整型数值初始化。
- 不使用只有一个枚举成员的枚举类型:如果只声明一个就没什么意义了····
范例4
#include <stdio.h>
typedef enum my_month_t{
JAN = 1,FEB,MAR,APR,
MAY,JUN,JUL,AUG,SEP,
OCT,NOV,DEC
}month_t;
void output_days(month_t m){
switch(m){
case JAN: case MAR:
case MAY: case JUL:
case AUG: case OCT:
case DEC:
printf("There are 31 days in this month.\n");
break;
case FEB:
printf("There are 28 or 29 days in this month.\n");
break;
case APR: case JUN:
case SEP: case NOV:
printf("There are 30 days in this month.\n");
break;
default:
printf("Error month.\n");
break;
}
}
int main(void){
month_t c;
printf("Please input which month:\n");
scanf("%d",(int *)&c);
/*
scanf("%d",&a);
c = (month_t)a;
*/
output_days(c);
return 0;
}
三、位域
在一些任务中,需要处理的信息可能只占用一个或几个二进制位,这种情况下如果使用一般的数据结构进行处理,会占用额外的空间。
1. 位域的概念
位域属于结构体,它允许在结构体内以位为单位将其空间划分为多个区域,并将其分配给结构体的各个成员。利用位域可以使用较少的字节来存储信息,形式如下:
struct 结构体名{
//定义位域成员
unsigned 位域成员1:常量1;
unsigned 位域成员2:常量2;
int 位域成员3:常量3;
int 位域成员4:常量4;
···
//定义其他普通成员
数据类型名 成员1;
数据类型名 成员2;
···
}
- 位域成员的类型只能为int型或unsigned型。
- 位域成员声明中最后的常量值指定了该区域所占的位数,且该数值不能大于其数据类型所占位数。
- 位域成员和普通成员的定义顺序可以交叉声明。
- 位域成员的访问方式与普通成员访问方式完全一致。
2. 位域的字长
下面为一个位域类型的声明:
struct bit_type{ //位域
unsigned a:1;
unsigned b:2;
unsigned c:3;
unsigned d:4;
}
这个位域共有4个成员,a成员占1字节,b成员占2字节,c占3字节,d占4字节。如果位域中的成员字节长度和小于unsigned型字节空间时,那么余下的字节空间也会分配给该位域类型。
范例5
#include <stdio.h>
//unsigned的字节长度为4
typedef struct{
unsigned a;
unsigned b;
unsigned c;
unsigned d;
}type_a;
typedef struct{
unsigned a:8;
unsigned b:8;
unsigned c:16;
unsigned d:8;
}type_b;
typedef struct{
unsigned a:1;
}type_c;
int main(void){
printf("sizeof(type_a) = %d\n",sizeof(type_a));//4 * 4 = 16
printf("sizeof(type_b) = %d\n",sizeof(type_b));//8+8+16+8=40<8*(4*2)
printf("sizeof(type_c) = %d\n",sizeof(type_c));//1<8*(4*1)
return 0;
}
3. 位域的存储形式
在正常情况下,位域中的位域成员的内存是相邻的。但如果一个位域单元的剩余空间不能容纳一个位域成员,那么,该单元剩余空间会被闲置,从下一个单元开始存放该成员。
例如,有以下位域类型:
struct bit_filed_1{
int a: 16;
int b: 17;
}
由于位域成员a占用了16位,该位域单元剩下16位,不足以分配给b成员。那么第一个位域单元的剩余16位被闲置,将第2个位域单元的开始17位分配给b。
此外还可以人为地指定某一位域成员从下一位域单元开始分配,只要在该位域成员前声明一个长度为0的空成员即可。
struct bit_filed_2{
int a: 1;
iny : 0;
int b: 1;
}
范例6
#include <stdio.h>
#include <string.h>
typedef unsigned long long uInt64;
//声明位域类型biefield_a
typedef struct{
unsigned a: 2;
unsigned b: 3;
unsigned c: 4;
int d: 24;
}bitfield_a;
typedef struct{
unsigned a: 2;
unsigned : 0;
unsigned b: 2;
}bitfield_b;
//输出整数的二进制形式
void print_bin(const uInt64 a, const int size){
uInt64 flag = 1ULL << (size * 8 - 1); //将unsigned long long置为1用1ULL
int count = 0;
while(flag != 0){
printf("%d",0 != (a & flag));
flag >>= 1;
++count;
if(count % 8 == 0)
printf(" ");
}
printf("\n");
}
int main(void){
bitfield_a a;
bitfield_b b;
//将空间清零
memset(&a,0,sizeof(bitfield_a));
memset(&b,0,sizeof(bitfield_b));
a.a |= ~0; //将a.a成员置为全1
print_bin(*(uInt64 *)&a,sizeof(bitfield_a));
a.a &= 0; //清0
a.b |= ~0; //将a.b成员置为全1
print_bin(*(uInt64 *)&a,sizeof(bitfield_a));
a.b &= 0; //清0
a.c |= ~0; //将a.c成员置为全1
print_bin(*(uInt64 *)&a,sizeof(bitfield_a));
a.c &= 0; //清0
a.d |= ~0; //将a.d成员置为全1
print_bin(*(uInt64 *)&a,sizeof(bitfield_a));
b.a |= ~0;
print_bin(*(uInt64 *)&b,sizeof(bitfield_b));
b.a &= 0; //清0
b.b |= ~0;
print_bin(*(uInt64 *)&b,sizeof(bitfield_b));
return 0;
}
从图中可以看到,由于a.d成员占24位,而第一个位域中a.a、a.b和a.c共占了9位,而int型共有32位,所以此位域共有32位,32-9=23位,23<24,不够存储a.d成员,所以a.d成员要存储在下一个位域中,如图中第4行所示。
由于b.a成员和b.b成员中间插入了空成员,所以b.b成员从新的位域开始存储。
练习1
使用union和struct设计一个可以同时处理char型数据、int型数据和double型数据的数据类型,并设计两个函数,分别实现对这个数据类型的输入和输出。
#include <stdio.h>
typedef struct my_data_u_t{
union{
char c;
int i;
double d;
}value;
char type;
}data_u_t;
int input_value(data_u_t *v,void *data){
//先选择数据类型
switch(v->type){
case 'c':
v->value.c = *((char *)data);
break;
case 'i':
v->value.i = *((int *)data);
break;
case 'd':
v->value.d = *((double *)data);
break;
default:
printf("Error type.\n");
return -1;
break;
}
return 0;
}
void output_value(data_u_t v){
switch(v.type){
case 'c':
printf("%c\n",v.value.c);
break;
case 'i':
printf("%d\n",v.value.i);
break;
case 'd':
printf("%f\n",v.value.d);
break;
default:
printf("Error type.\n");
break;
}
}
int main(void){
data_u_t v = {0,'d'};//初始化union成员,并说明其类型
double key = 23.5;
input_value(&v,&key);
output_value(v);
return 0;
}