P53_位域
1、单片机
单片机(Microcontrollers)是一种集成电路芯片,是采用超大规模集成电路技术把具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛应用。
2、字节
- Byte简写为B,而bit简写为b;
- 1 Byte(B) = 8 bit(字节)
- 1 KB = 1024 Bytes
- 1 MB = 1024 KB
- 1 GB = 1024 MB
- 一个数字与一个字母都是占1B;一个汉字占两个字节;
- int 占四字节 最大表示2的32次方(无符号)
- 先看byte,byte占一字节,一字节等于八位
- 我们来看看一个位能表示多少东西: 最小0 最大1 表示 2的1次方
- 再看看两个位能表示多少东西: 最小00 最大11 共2的2次方, 能表达四个数
- 再看看三个位能表示多少东西: 最小000 最大111 共2的3次方,能表达8个数
- 那八个位能表示多少?
- 最小:00000000 最大:11111111 这就是8位 也叫一个字节
3、位域
使用位域的做法是在结构体定义时,在结构体成员后面使用冒号(:)和数字来表示该成员所占的位数。
#include <stdio.h>
int main(void)
{
struct Test
{
unsigned int a:1;
unsigned int b:1;
unsigned int c:33;
};
struct Test test;
test.a = 0;
test.b = 1;
test.c = 2;
printf("a = %d, b = %d, c = %d\n", test.a, test.b, test.c);
printf("size of test = %d\n", sizeof(test));
return 0;
}
4、无名位域
位域成员可以没有名称,只要给出数据类型和位宽即可。
struct Test
{
unsigned int x:100;
unsigned int y:200;
unsigned int z:300;
unsigned int :424;
};
P54_位操作
字节尺寸
C语言并没有规定一个字节的尺寸
“可寻址的数据存储单位,其尺寸必须可以容纳运行环境的基本字符集的任何成员”
逻辑位运算符
运算符 | 含义 | 优先级 | 举例 | 说明 |
---|---|---|---|---|
~ | 按位取反 | 高 | ~a | 如果a为1,a为0;如果a为0,a为1。 |
& | 按位与 | 中 | a & b | 只有a和b同时为1,结果才为1;只要a和b其中一个为0,结果为0。 |
^ | 按位异或 | 低 | a ^ b | 如果a和b不同,其结果为1;如果a和b相同,其结果为0。 |
| | 按位或 | 最低 | a | b | 只要a和b其中一个为1,结果为1;只有a和b同时为0,结果为0。 |
按位取反(~)
逻辑位运算符中优先级最高的是按位取反运算符,它的运算符是一个~符号,作用是将1变成0,将0变成1:
按位与(&)
优先级第二高的是按位与运算符,它的运算符是一个&符号(而逻辑与是两个&符号),大家注意区分
按位异或(^)
优先级排第三的是按位异或运算符,它的运算符是一个^符号,只有当两个操作数对应的二进制位不同时,它的结果才为1,否则为0
按位或(|)
逻辑位运算符中优先级最低的是按位或运算符,它的运算符是一个|符号(而逻辑与是两个|符号),大家注意区分:
和赋值号结合
这四个运算符,除了按位取反只有一个操作数之外,其它三个都可以跟赋值号(=)结合到一块,使得代码更加简洁!
#include <stdio.h>
int main(void)
{
int mask = 0xFF;
int v1 = 0xABCDEF;
int v2 = 0xABCDEF;
int v3 = 0xABCDEF;
v1 &= mask;
v2 |= mask;
v3 ^= mask;
printf("v1 = 0x%X\n", v1);
printf("v2 = 0x%X\n", v2);
printf("v3 = 0x%X\n", v3);
return 0;
}
P55_位移和位操作的应用
移位运算符
C语言除了提供四种逻辑位运算符之外,还提供了可以将某个变量中所有的二进制位进行左移或右移的运算符 —— 移位运算符。
左移、右移运算符也可以和赋值号结合
左移位运算符
11001010 << 2
右移位运算符
11001010 >> 2
一些未定义行为
左移、右移运算符右边的操作数如果是为负数,或者右边的操作数大于左边操作数支持的最大宽度,那么表达式的结果均是属于“未定义行为”。
左边的操作数是有符号还是无符号数其实也对移位运算符有着不同的影响。无符号数肯定没问题,因为这时候变量里边所有的位都用于表示该数值的大小。但如果是有符号数,那就要区别对待了,因为有符号数的左边第一位是符号位,所以如果恰好这个操作数是个负数,那么移动之后是否覆盖符号位的决定权还是落到了编译器上。
应用
掩码
if ((value & mask) == mask)
{
printf("Open!\n");
}
打开位
关闭位
转置位
利用XOR进行简单高效地加密
P56_文件操作
什么是文件
“计算机文件(或称文件、电脑档案、档案),是存储在某种长期储存设备或临时存储设备中的一段数据流,并且归属于计算机文件系统管理之下。所谓“长期储存设备”一般指磁盘、光盘、磁带等。而“短期存储设备”一般指计算机内存。需要注意的是,存储于长期存储设备的文件不一定是长期存储的,有些也可能是程序或系统运行中产生的临时数据,并于程序或系统退出后删除。”
常见的文件类型:
文本文件和二进制文件
C语言的文件主要有两种,一种是你看得懂的,一种是让你懵逼的。
你看得懂的是文本文件,由一些字符的序列组成的;另一种让你懵逼的是二进制文件,通常是指除了文本文件以外的文件。
打开和关闭文件
有了上面这些理论知识之后,我们就可以开始来操作了。只有在一个文件被打开的时候,你才能够对其进行读写操作。
“读”就是从文件里获取数据,“写”则相反,是将数据写入到文件里面。
在完成对一个文件的读写操作之后,你必须将其关闭。
案例演示
读取文件
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *file;
int ch;
if ((file = fopen("hello.txt", "r")) == NULL)
{
printf("打开文件失败");
exit(EXIT_FAILURE);
}
while ((ch = getc(file)) != EOF)
{
putchar(ch);
}
fclose(file);
return 0;
}
运行结果:
zystart
P57_读写文件1
上一章我们讲解了 C 语言处理的标准输入和输出设备。本章我们将介绍 C 程序员如何创建、打开、关闭文本文件或二进制文件。
一个文件,无论它是文本文件还是二进制文件,都是代表了一系列的字节。C 语言不仅提供了访问顶层的函数,也提供了底层(OS)调用来处理存储设备上的文件。本章将讲解文件管理的重要调用。
打开文件
您可以使用 fopen( ) 函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE 包含了所有用来控制流的必要的信息。下面是这个函数调用的原型:
FILE *fopen( const char * filename, const char * mode );
在这里,filename 是字符串,用来命名文件,访问模式 mode 的值可以是下列值中的一个:
模式 | 描述 |
---|---|
r | 打开一个已有的文本文件,允许读取文件。 |
w | 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。 |
a | 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。 |
r+ | 打开一个文本文件,允许读写文件。 |
w+ | 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。 |
a+ | 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。 |
如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:
"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"
关闭文件
为了关闭文件,请使用 fclose( ) 函数。函数的原型如下:
int fclose( FILE *fp );
如果成功关闭文件,fclose( ) 函数返回零,如果关闭文件时发生错误,函数返回 EOF。这个函数实际上,会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。EOF 是一个定义在头文件 stdio.h 中的常量。
C 标准库提供了各种函数来按字符或者以固定长度字符串的形式读写文件。
写入文件
下面是把字符写入到流中的最简单的函数:
int fputc( int c, FILE *fp );
函数 fputc() 把参数 c 的字符值写入到 fp 所指向的输出流中。如果写入成功,它会返回写入的字符,如果发生错误,则会返回 EOF。您可以使用下面的函数来把一个以 null 结尾的字符串写入到流中:
int fputs( const char *s, FILE *fp );
函数 fputs() 把字符串 s 写入到 fp 所指向的输出流中。如果写入成功,它会返回一个非负值,如果发生错误,则会返回 EOF。您也可以使用 int fprintf(FILE *fp,const char *format, …) 函数把一个字符串写入到文件中。尝试下面的实例:
**注意:**请确保您有可用的 tmp 目录,如果不存在该目录,则需要在您的计算机上先创建该目录。
/tmp 一般是 Linux 系统上的临时目录,如果你在 Windows 系统上运行,则需要修改为本地环境中已存在的目录,例如: C:\tmp、D:\tmp等。
实例
#include <stdio.h>
int main()
{
FILE *fp = NULL;
fp = fopen("/tmp/test.txt", "w+");
fprintf(fp, "This is testing for fprintf...\n");
fputs("This is testing for fputs...\n", fp);
fclose(fp);
}
上面的代码被编译和执行时,它会在 /tmp 目录中创建一个新的文件 test.txt,并使用两个不同的函数写入两行。接下来让我们来读取这个文件。
读取文件
下面是从文件读取单个字符的最简单的函数:
int fgetc( FILE * fp );
fgetc() 函数从 fp 所指向的输入文件中读取一个字符。返回值是读取的字符,如果发生错误则返回 EOF。下面的函数允许您从流中读取一个字符串:
char *fgets( char *buf, int n, FILE *fp );
函数 fgets() 从 fp 所指向的输入流中读取 n - 1 个字符。它会把读取的字符串复制到缓冲区 buf,并在最后追加一个 null 字符来终止字符串。
如果这个函数在读取最后一个字符之前就遇到一个换行符 ‘\n’ 或文件的末尾 EOF,则只会返回读取到的字符,包括换行符。您也可以使用 int fscanf(FILE *fp, const char *format, …) 函数来从文件中读取字符串,但是在遇到第一个空格和换行符时,它会停止读取。
实例
#include <stdio.h>
int main()
{
FILE *fp = NULL;
char buff[255];
fp = fopen("/tmp/test.txt", "r");
fscanf(fp, "%s", buff);
printf("1: %s\n", buff );
fgets(buff, 255, (FILE*)fp);
printf("2: %s\n", buff );
fgets(buff, 255, (FILE*)fp);
printf("3: %s\n", buff );
fclose(fp);
}
当上面的代码被编译和执行时,它会读取上一部分创建的文件,产生下列结果:
1: This
2: is testing for fprintf...
3: This is testing for fputs...
首先,fscanf() 方法只读取了 This,因为它在后边遇到了一个空格。其次,调用 fgets() 读取剩余的部分,直到行尾。最后,调用 fgets() 完整地读取第二行。
二进制 I/O 函数
下面两个函数用于二进制输入和输出:
size_t fread(void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);
size_t fwrite(const void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);
这两个函数都是用于存储块的读写 - 通常是数组或结构体。
案例说明
case01 读写单个字符
先打开hello.txt文件,fishc.txt文件,获取文件指针。
将hello.txt文件中的内容写入到fishc.txt文件中。
关闭两个文件。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp1;
FILE *fp2;
int ch;
if ((fp1 = fopen("hello.txt", "r")) == NULL)
{
printf("文件打开失败!\n");
exit(EXIT_FAILURE);
}
if ((fp2 = fopen("fishc.txt", "w")) == NULL)
{
printf("文件打开失败!\n");
exit(EXIT_FAILURE);
}
while ((ch = fgetc(fp1)) != EOF)
{
fputc(ch, fp2);
}
fclose(fp1);
fclose(fp2);
return 0;
}
case02 读写字符串
fputs 和fgets
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *fp;
if ((fp = fopen("lines.txt", "w")) == NULL)
{
printf("打开文件失败");
exit(EXIT_FAILURE);
}
fputs("line 1: hello world\n", fp);
fputs("line 2: hello world\n", fp);
fputs("line 3: hello world", fp); // 删掉换行符
fclose(fp);
if ((fp = fopen("lines.txt", "r")) == NULL)
{
printf("打开文件失败");
exit(EXIT_FAILURE);
}
char buffer[1024];
while (!feof(fp))
{
// 如果在读取字符的过程中遇到 EOF,则 eof 指示器被设置;如果还没读入任何字符就遇到这种 EOF,则 s 参数指向的位置保持原来的内容,函数返回 NULL。
fgets(buffer, 1024, fp);
printf("%s", buffer);
}
return 0;
}
P58_读写文件2
格式化读写文件
fscanf和fprintf
实例
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
FILE *fp;
struct tm *p;
time_t t;
time(&t);
p = localtime(&t);
// 打开文件,获取文件指针
if ((fp = fopen("date.txt", "w")) == NULL)
{
printf("打开文件失败");
exit(EXIT_FAILURE);
}
// 写入文件
fprintf(fp, "%d %d %d", p->tm_year + 1900, p->tm_mon, p->tm_mday);
// 关闭文件 一定要哦
fclose(fp);
// 打开文件
if ((fp = fopen("date.txt", "r")) == NULL)
{
printf("打开文件失败2");
exit(EXIT_FAILURE);
}
int year, mon, day;
// 格式化读取
fscanf(fp, "%d %d %d", &year, &mon, &day);
printf("%d %d %d", year, mon, day);
// 关闭
fclose(fp);
return 0;
}
二进制读写文件
fread和fwrite
定义一个结构体Date,用malloc申请空间,使用指针book_for_write 指向结构体
结构体初始化数据,
使用fwrite二进制写入文件中
使用fread读取结构体数据,
测试输出
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Date
{
int year;
int month;
int day;
};
struct Book
{
char name[40];
char author[40];
char publisher[40];
struct Date date;
};
int main(void)
{
FILE *fp;
struct Book *book_for_write, *book_for_read;
book_for_write = (struct Book *)malloc(sizeof(struct Book));
book_for_read = (struct Book *)malloc(sizeof(struct Book));
if (book_for_write == NULL || book_for_read == NULL)
{
printf("内存分配失败!\n");
exit(EXIT_FAILURE);
}
strcpy(book_for_write->name, "《带你学C带你飞》");
strcpy(book_for_write->author, "小甲鱼");
strcpy(book_for_write->publisher, "清华大学出版社");
book_for_write->date.year = 2017;
book_for_write->date.month = 11;
book_for_write->date.day = 11;
if ((fp = fopen("file.txt", "w")) == NULL)
{
printf("打开文件失败!\n");
exit(EXIT_FAILURE);
}
fwrite(book_for_write, sizeof(struct Book), 1, fp);
fclose(fp);
if ((fp = fopen("file.txt", "r")) == NULL)
{
printf("打开文件失败!\n");
exit(EXIT_FAILURE);
}
fread(book_for_read, sizeof(struct Book), 1, fp);
printf("书名:%s\n", book_for_read->name);
printf("作者:%s\n", book_for_read->author);
printf("出版社:%s\n", book_for_read->publisher);
printf("出版日期:%d-%d-%d\n", book_for_read->date.year, book_for_read->date.month, book_for_read->date.day);
fclose(fp);
return 0;
}
P59_随机读写文件
rewind
描述
C 库函数 void rewind(FILE *stream) 设置文件位置为给定流 stream 的文件的开头。
声明
下面是 rewind() 函数的声明。
void rewind(FILE *stream)
参数
- stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
返回值
该函数不返回任何值。
实例
C 库函数 void rewind(FILE *stream) 设置文件位置为给定流 stream 的文件的开头。
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *fp;
// 打开文件,获取文件指针
if ((fp = fopen("hello.txt", "w")) == NULL)
{
printf("打开文件失败");
exit(EXIT_FAILURE);
}
printf("%ld\n", ftell(fp)); // 0
fputs("ZY", fp);
printf("%ld\n", ftell(fp)); // 2
fputs("start", fp);
printf("%ld\n", ftell(fp)); // 7
// 初始化文件头的位置
rewind(fp);
printf("%ld\n", ftell(fp)); // 0
fputs("aaa", fp);
fclose(fp);
return 0;
}
fseek()
描述
C 库函数 int fseek(FILE *stream, long int offset, int whence) 设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。
声明
下面是 fseek() 函数的声明。
int fseek(FILE *stream, long int offset, int whence)
参数
- stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
- offset – 这是相对 whence 的偏移量,以字节为单位。
- whence – 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
常量 | 描述 |
---|---|
SEEK_SET | 文件的开头 |
SEEK_CUR | 文件指针的当前位置 |
SEEK_END | 文件的末尾 |
返回值
如果成功,则该函数返回零,否则返回非零值。
实例
下面的实例演示了 fseek() 函数的用法。
#include <stdio.h>
int main ()
{
FILE *fp;
fp = fopen("file.txt","w+");
fputs("This is runoob.com", fp);
fseek( fp, 7, SEEK_SET );
fputs(" C Programming Langauge", fp);
fclose(fp);
return(0);
}
让我们编译并运行上面的程序,这将创建文件 file.txt,它的内容如下。最初程序创建文件和写入 This is runoob.com,但是之后我们在第七个位置重置了写指针,并使用 puts() 语句来重写文件,内容如下:
This is C Programming Langauge
现在让我们使用下面的程序查看上面文件的内容:
#include <stdio.h>
int main ()
{
FILE *fp;
int c;
fp = fopen("file.txt","r");
while(1)
{
c = fgetc(fp);
if( feof(fp) )
{
break ;
}
printf("%c", c);
}
fclose(fp);
return(0);
}
负号前移,正号后移
fp = fopen("filename", "rb+"); // 假设此语句有定义
fseek(fp, -(long)sizeof(STU), SEEK_END);
// fp 为文件指针,已经预先定义 FILE *fp。stu 为结构体预定义
/*
typedef struct student {
long sno;
char name[10];
float score[3];
} STU;
*/
// SEEK_END 为文件结尾
// 整个意思为从文件末尾向前偏移这个结构体那么多的长度给文件指针fp
P60_标准流和错误处理
1、文件流
2、重定向
这里给大家补充一个课外知识点,由于标准输出和标准错误输出通常都是直接打印到屏幕上,为了区分它们,我们可以使用Linux shell的重定向功能:
- 重定向标准输入使用 <
- 重定向标准输出使用 >
- 重定向标准错误输出使用 2>
3、错误处理
错误指示器——ferror。
使用clearerr函数可以人为地清除文件末尾指示器和错误指示器的状态。
- ferror函数只能检测是否出错,但无法获取错误原因。不过,大多数系统函数在出现错误的时候会将错误原因记录在errno中。
- perror函数可以直观地打印出错误原因。
- strerror函数直接返回错误码对应的错误信息。
P61_IO缓冲区
1、IO缓冲区
标准IO提供的三种类型的缓冲模式:
- 按块缓存
- 按行缓存
- 不缓存
按块缓存也称为全缓存,即在填满缓冲区后才进行实际的设备读写操作;按行缓存是指在接收到换行符(’\n’)之前,数据都是先缓存在缓冲区的;最后一个是不缓存,也就是允许你直接读写设备上的数据。