C 位域
有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有 0 和 1 两种状态,用 1 位二进位即可。为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为"位域"或"位段"。
所谓"位域"是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。
典型的实例:
- 用 1 位二进位存放一个开关量时,只有 0 和 1 两种状态。
- 读取外部文件格式——可以读取非标准的文件格式。例如:9 位的整数。
位域的定义和位域变量的说明
位域定义与结构定义相仿,其形式为:
1 struct 位域结构名 2 { 3 位域列表 4 };
其中位域列表的形式为:
类型说明符 位域名: 位域长度
【示例】
1 struct bs{ 2 int a:8; 3 int b:2; 4 int c:6; 5 }data;
说明 data 为 bs 变量,共占两个字节。其中位域 a 占 8 位,位域 b 占 2 位,位域 c 占 6 位。
【示例2】
1 struct packed_struct { 2 unsigned int f1:1; 3 unsigned int f2:1; 4 unsigned int f3:1; 5 unsigned int f4:1; 6 unsigned int type:4; 7 unsigned int my_int:9; 8 } pack;
在这里,packed_struct 包含了 6 个成员:四个 1 位的标识符 f1..f4、一个 4 位的 type 和一个 9 位的 my_int。
对于位域的定义尚有以下几点说明:
- 一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:
1 struct bs{ 2 unsigned a:4; 3 unsigned :4; /* 空域 */ 4 unsigned b:4; /* 从下一单元开始存放 */ 5 unsigned c:4 6 }
在这个位域定义中,a 占第一字节的 4 位,后 4 位填 0 表示不使用,b 从第二字节开始,占用 4 位,c 占用 4 位。
- 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。如果最大长度大于计算机的整数字长,一些编译器可能会允许域的内存重叠,另外一些编译器可能会把大于一个域的部分存储在下一个字中。
-
位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:
1 struct k{ 2 int a:1; 3 int :2; /* 该 2 位不能使用 */ 4 int b:3; 5 int c:2; 6 };
从以上分析可以看出,位域在本质上就是一种结构类型,不过其成员是按二进位分配的。
位域的使用
位域的使用和结构成员的使用相同,其一般形式为:
位域变量名.位域名 位域变量名->位域名
位域允许用各种格式输出。
【示例】
1 main(){ 2 struct bs{ 3 unsigned a:1; 4 unsigned b:3; 5 unsigned c:4; 6 } bit,*pbit; 7 bit.a=1; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */ 8 bit.b=7; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */ 9 bit.c=15; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */ 10 printf("%d,%d,%d ",bit.a,bit.b,bit.c); /* 以整型量格式输出三个域的内容 */ 11 pbit=&bit; /* 把位域变量 bit 的地址送给指针变量 pbit */ 12 pbit->a=0; /* 用指针方式给位域 a 重新赋值,赋为 0 */ 13 pbit->b&=3; /* 使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 7,与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */ 14 pbit->c|=1; /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */ 15 printf("%d,%d,%d ",pbit->a,pbit->b,pbit->c); /* 用指针方式输出了这三个域的值 */ 16 }
执行结果:
1,7,15 0,3,15
C typedef
C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE:
typedef unsigned char BYTE;
在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写,例如:
BYTE b1, b2;
按照惯例,定义时会大写字母,以便提醒用户类型名称是一个象征性的缩写,但您也可以使用小写字母,如下:
typedef unsigned char byte;
您也可以使用 typedef 来为用户自定义的数据类型取一个新的名字。例如,您可以对结构体使用 typedef 来定义一个新的数据类型名字,然后使用这个新的数据类型来直接定义结构变量,如下:
1 #include <stdio.h> 2 #include <string.h> 3 4 typedef struct Books 5 { 6 char title[50]; 7 char author[50]; 8 char subject[100]; 9 int book_id; 10 } Book; 11 12 int main( ) 13 { 14 Book book; 15 16 strcpy( book.title, "C 教程"); 17 strcpy( book.author, "Runoob"); 18 strcpy( book.subject, "编程语言"); 19 book.book_id = 12345; 20 21 printf( "书标题 : %s ", book.title); 22 printf( "书作者 : %s ", book.author); 23 printf( "书类目 : %s ", book.subject); 24 printf( "书 ID : %d ", book.book_id); 25 26 return 0; 27 }
执行结果:
书标题 : C 教程 书作者 : Runoob 书类目 : 编程语言 书 ID : 12345
typedef vs #define
#define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
- typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
- typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
下面是 #define 的最简单的用法:
1 #include <stdio.h> 2 3 #define TRUE 1 4 #define FALSE 0 5 6 int main( ) 7 { 8 printf( "TRUE 的值: %d ", TRUE); 9 printf( "FALSE 的值: %d ", FALSE); 10 11 return 0; 12 }
执行结果:
TRUE 的值: 1 FALSE 的值: 0
C 输入 & 输出
当我们提到输入时,这意味着要向程序填充一些数据。输入可以是以文件的形式或从命令行中进行。C 语言提供了一系列内置的函数来读取给定的输入,并根据需要填充到程序中。
当我们提到输出时,这意味着要在屏幕上、打印机上或任意文件中显示一些数据。C 语言提供了一系列内置的函数来输出数据到计算机屏幕上和保存数据到文本文件或二进制文件中。
标准文件
C 语言把所有的设备都当作文件。所以设备(比如显示器)被处理的方式与文件相同。以下三个文件会在程序执行时自动打开,以便访问键盘和屏幕。
标准文件 | 文件指针 | 设备 |
---|---|---|
标准输入 | stdin | 键盘 |
标准输出 | stdout | 屏幕 |
标准错误 | stderr | 您的屏幕 |
文件指针是访问文件的方式,本节将讲解如何从屏幕读取值以及如何把结果输出到屏幕上。
C 语言中的 I/O (输入/输出) 通常使用 printf() 和 scanf() 两个函数。
scanf() 函数用于从标准输入(键盘)读取并格式化, printf() 函数发送格式化输出到标准输出(屏幕)。
1 #include <stdio.h> // 执行 printf() 函数需要该库 2 int main() 3 { 4 printf("hello world"); //显示引号中的内容 5 return 0; 6 }
getchar() & putchar() 函数
int getchar(void) 函数从屏幕读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。您可以在循环内使用这个方法,以便从屏幕上读取多个字符。
int putchar(int c) 函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符。您可以在循环内使用这个方法,以便在屏幕上输出多个字符。
请看下面的实例:
1 #include <stdio.h> 2 3 int main( ) 4 { 5 int c; 6 7 printf( "Enter a value :"); 8 c = getchar( ); 9 10 printf( " You entered: "); 11 putchar( c ); 12 printf( " "); 13 return 0; 14 }
执行结果:
Enter a value :runoob You entered: r
gets() & puts() 函数
char *gets(char *s) 函数从 stdin 读取一行到 s 所指向的缓冲区,直到一个终止符或 EOF。
int puts(const char *s) 函数把字符串 s 和一个尾随的换行符写入到 stdout。
1 #include <stdio.h> 2 3 int main( ) 4 { 5 char str[100]; 6 7 printf( "Enter a value :"); 8 gets( str ); 9 10 printf( " You entered: "); 11 puts( str ); 12 return 0; 13 }
执行结果:
Enter a value :runoob You entered: runoob
scanf() 和 printf() 函数
int scanf(const char *format, ...) 函数从标准输入流 stdin 读取输入,并根据提供的 format 来浏览输入。
int printf(const char *format, ...) 函数把输出写入到标准输出流 stdout ,并根据提供的格式产生输出。
format 可以是一个简单的常量字符串,但是您可以分别指定 %s、%d、%c、%f 等来输出或读取字符串、整数、字符或浮点数。还有许多其他可用的格式选项,可以根据需要使用。
【示例】
1 #include <stdio.h> 2 int main( ) { 3 4 char str[100]; 5 int i; 6 7 printf( "Enter a value :"); 8 scanf("%s %d", str, &i); 9 10 printf( " You entered: %s %d ", str, i); 11 printf(" "); 12 return 0; 13 }
执行结果:
Enter a value :runoob 123 You entered: runoob 123