C语言:近期所学知识汇总(4)
数据类型专题
1.计算机中的数据按照补码形式存储,补码是原码按位取反加一得到。之所以采用补码是为了配合cpu的特性,因为cpu只能进行加法、按位取反、位移这三种逻辑。
2.C语言是一种强类型语言,变量的类型一旦确定就不能再被更改。
3.在C语言中,类型有自己的长度。不加任何前缀修饰时,默认是signed(有符号)类型,最高位代表符号位;当前缀关键字unsigned(无符号类型时),最高位就代表数值位。下面的程序展示了有符号和无符号之间的差别:
int main() {
for (char i = 0;i < 128;i++) {
printf("%4d", i);
}
return 0;
}
由于char类型的值始终介于-128~127之间,所以循环将无限执行。
int main() {
for (unsigned char i = 0;i < 128;i++) {
printf("%4d", i);
}
return 0;
}
由于将char定义成了无符号类型,所以它可以表示128,所以循环将会在循环128次后退出。
4.无符号和有符号的区别更多体现在类型转换方面。C语言的类型转换中,长度短的类型转换成长度长的类型时,需要进行位扩充,对于有符号数来说,扩充的应该是其符号位,之所以要扩充符号位,是因为要保证扩充后的数值正负和大小与原来保持不变;而对于无符号数来说,扩充的均为零。比如下面的程序段:
int main() {
char c = -5;
unsigned int a = 10;
if (c > a) {
printf("%d > %d", c, a);
}
else {
printf("%d < %d", c, a);
}
return 0;
}
程序结果输出-5 > 10,这显然是不符合逻辑的,但是事实上这是类型转换造成的结果。变量c的二进制表示为1111 1011(-5的补码形式),在进行c > a时进行了隐式类型转换,将c的类型转换成了unsigned int类型,所以要进行位扩充。由于c是char类型,扩充符号位,所以扩充后用十六进制表示0xfffffffd,而a用十六进制表示为0x0000000a,对于无类型来说最高位不是符号位,0xfffffffd远远大于0x0000000a,所以最后输出时打印出-5 > 10的情况。由于有隐式类型转换的存在,所以在编程的时候,不要将有符号数和无符号数混用,这样会造成一些严重的问题,比如下面的程序:
int main() {
for (int i = -4;i < sizeof(int);i++) {
printf("%d ", i);
}
return 0;
}
程序将什么也不打印。因为sizeof(int)的返回值是一个无符号整型,所以i将会被进行隐式类型转换。i的十六进制表示为0xfffffffc,如果这是一个有符号数,那么它将是一个负数,但是此时i与一个无类型数进行比较,从int隐式转换成了unsigned int,所以i就变成了一个非常大的数,远远大于sizeof(int)的值,所以循环条件不满足,循环体未执行。
类型转换例题:
int main() {
char c = 128;
unsigned char uc = 128;
unsigned short us = 0;
us = c + uc;
printf("%x \n", us);
us = (unsigned char)c + uc;
printf("%x \n", us);
us = c + (char)uc;
printf("%x \n", us);
us = (unsigned short)c + uc;
printf("%x \n", us);
return 0;
}
需要注意,类型转换规则遵守的是数据原本类型的转换规则,而不是转换后类型的转换规则。
文件专题
文件的概念:一般指存储在外部介质上数据的集合,比如txt,tmp,jpg,exe等,C语言中将输入输出设备也抽象承一种“文件”。我们可以通过数据流向文件中写入或者读出数据。
流的概念:I/O设备型号众多而且标准不一,要想访问它们十分麻烦,所以我们将这些种类繁多的设备统一抽象成了“标准I/O设备”,程序绕过具体的设备,而与“标准I/O设备”进行交互,这样就可以做到不依赖与任何具体I/O设备的统一操作接口。我们将抽象出来的“标准I/O设备”或者“标准文件”称为“流”。对于将任意I/O设备转换为“标准I/O设备”的过程是由系统自动完成,不需要程序员处理。可以认为,任意输入的源端或者任意输出的终端均对应一个“流”。
文件包含三个要素:文件路径,文件名称,文件后缀。
预定义的三个标准流:stdin(标准输入流),stdout(标准输出流),stderr(标准错误流)
文件类型FILE:对象类型,包含I/O流所需的全部信息。
文件操作的三个步骤:打开文件,读写文件,关闭文件。下面分别介绍这三个过程。 1.打开文件 要想对文件进行操作,首先要将文件打开,使用fopen函数达到这个目标。但是在VS2019中原来的fopen函数禁用,改用更安全的fopen_s函数。 该函数接受三个参数,分别是文件指针、文件名、访问类型。如果打开失败,返回errno_t(int)值,用户可以通过返回的值来得知错误类型。访 问类型常用的有以下几种:
读取方式 | 含义 |
---|
r | 只读。文件必须存在才可以读取。 | w | 只写。文件如果已经存在,则清除原文件重新写入;如果文件不存在,则创建新文件写入。 | a | 末尾只写。文件如果已经存在,则在原文件的末尾继续写入;如果文件不存在,则创建新文件写入。 | rb | 二进制文件只读。功能同“r”,以二进制模式打开。 | wb | 二进制文件只写。功能同“w”,以二进制模式打开。 | ab | 二进制文件末尾只写。功能同“a”,以二进制模式打开。 |
2.关闭文件 对文件操作完成之后应当关闭文件,使用fclose函数完成。该函数接受一个参数即文件指针,和动态内存分配中的free函数相似,在调用完fclose函数后, 应当将文件指针置为空值,原理与调用free函数之后将指向堆区内存的指针置为空值相似。 3.读写文件 3.1无格式输入输出
int main() {
const int n = 20;
char ch = 'a';
char stra[n] = { "hdc hello" };
putchar(ch);
putchar('\n');
puts(stra);
puts("\n");
return 0;
}
将字符或者字符串送进标准输出流中。
int main() {
const int n = 100;
char stra[n] = {};
gets_s(stra, n);
printf("%s", stra);
return 0;
}
从标准输入流中获取字符串。
int main() {
char ch = '\0';
ch = getchar();
return 0;
}
从标准输入流中获取一个字符。
int main(int argc, char* argv[]) {
char ch = '\0';
if (argc < 3) {
printf("copy file error \n");
exit(EXIT_FAILURE);
}
FILE* fr, * fw;
errno_t rx = fopen_s(&fr, argv[1], "r");
errno_t wx = fopen_s(&fw, argv[2], "w");
if (fr == nullptr || fw == nullptr) {
printf("open file error \n");
exit(EXIT_FAILURE);
}
while (!feof(fr)) {
ch = fgetc(fr);
fputc(ch, fw);
putchar(ch);
}
fclose(fr);
fr = nullptr;
fclose(fw);
fw = nullptr;
}
将一个文件中的内容拷贝到另外一个文件当中,以读方式打开一个文件,通过fgetc函数获取其字符,然后以写方式打开另一个文件,通过fputc函数将刚才获得的字符送进去,当读文件指针到达文件末尾的时候,拷贝结束。
int main() {
FILE* fpr = nullptr;
FILE* fpw = nullptr;
errno_t ersa = fopen_s(&fpr, "Test7_10.cpp", "r");
if (fpr == nullptr) {
printf("fopen file error %d", ersa);
exit(1);
}
errno_t ersb = fopen_s(&fpw, "D:\\VS2019程序代码\\Test7_10\\Test7_12_copy.cpp", "w");
if (fpw == nullptr) {
printf("fopen file error %d", ersb);
exit(1);
}
char buff[10];
while (!feof(fpr)) {
fgets(buff, 10, fpr);
fputs(buff, fpw);
}
fclose(fpr);
fpr = nullptr;
fclose(fpw);
fpw = nullptr;
}
使用fgets函数读取Test7_10.cpp中的内容进入缓冲区,然后使用fputs函数将缓冲区的数据写入另一个文件。当读文件指针到达文件末尾的时候,拷贝结束。 3.2文本文件的写入和读取 下面的实例是通过SaveData函数创建并保存了一个txt文本文件,然后通过LoadData函数读取文件中的数值并打印至屏幕上。
void LoadData() {
int ar[10];
FILE* fp = nullptr;
errno_t ers = fopen_s(&fp, "hdc.txt", "r");
if (fp == nullptr) {
printf("open file error %d", ers);
exit(1);
}
for (int i = 0;i < 10;i++) {
fscanf_s(fp, "%d", &ar[i]);
printf("%d ", ar[i]);
}
fclose(fp);
fp = nullptr;
}
void SaveData() {
int ar[] = { 12,23,34,45,56,67,78,89,90,100 };
int n = sizeof(ar) / sizeof(ar[0]);
FILE* fp = nullptr;
errno_t ers = fopen_s(&fp, "hdc.txt", "w");
if (fp == nullptr) {
printf("open file error %d", ers);
exit(1);
}
for (int i = 0;i < n;i++) {
fprintf_s(fp, "%d ", ar[i]);
}
fclose(fp);
fp = nullptr;
}
int main() {
SaveData();
LoadData();
return 0;
}
第二个实例与第一个类似,区别是在文件的开头保存了文件中数据的个数,这样做的话在读取文件的时候,读取了文件中第一个值之后就可以知道文件的数据个数。
void LoadData() {
int ar[100];
FILE* fp = nullptr;
errno_t ers = fopen_s(&fp, "data.txt", "r");
if (fp == nullptr) {
printf("fopen file error %d", ers);
exit(1);
}
int n = 0;
fscanf_s(fp, "%d", &n);
for (int i = 0;i < n;i++) {
fscanf_s(fp, "%d", &ar[i]);
printf("%d ", ar[i]);
}
fclose(fp);
fp = nullptr;
}
void SaveData() {
int ar[] = { 12,23,34,45,56,67,78,89,90,100,90,89,78,67,56,45,34,23 };
int n = sizeof(ar) / sizeof(ar[0]);
FILE* fp = nullptr;
errno_t ers = fopen_s(&fp, "data.txt", "w");
if (fp == nullptr) {
printf("fopen file error %d", ers);
exit(1);
}
fprintf_s(fp, "%d\n", n);
for (int i = 0;i < n;i++) {
fprintf_s(fp, "%d ", ar[i]);
}
fclose(fp);
fp = nullptr;
}
int main() {
SaveData();
LoadData();
return 0;
}
3.3二进制文件的写入和读取 相对于文本文件,二进制文件的读写分别采用fread和fwrite函数实现,其中fwrite接受四个参数,指向缓冲区的指针、写入数据的类型,写入数据的个数以及文件指针;fread函数接受四个参数,指向缓冲区的指针、读取数据的类型、读取数据的个数以及文件指针。
void LoadData() {
FILE* fp = nullptr;
errno_t ers = fopen_s(&fp, "data.bin", "rb");
if (fp == nullptr) {
printf("fopen file error %d", ers);
exit(1);
}
int n = 0;
fread(&n,sizeof(int), 1, fp);
int* ar = (int*)malloc(sizeof(int) * n);
if (ar == nullptr)exit(1);
fread(ar,sizeof(int), n, fp);
for (int i = 0;i < n;i++) {
printf("%5d", ar[i]);
}
free(ar);
ar = nullptr;
fclose(fp);
fp = nullptr;
}
void SaveData() {
int ar[] = { 12,23,34,45,56,67,78,89,90,100,90,89,78,67,56,45,34,23 };
int n = sizeof(ar) / sizeof(ar[0]);
FILE* fp = nullptr;
errno_t ers = fopen_s(&fp, "data.bin", "wb");
if (fp == nullptr) {
printf("fopen file error %d", ers);
exit(1);
}
fwrite(&n, sizeof(int), 1, fp);
fwrite(ar, sizeof(int), n, fp);
fclose(fp);
fp = nullptr;
}
int main() {
SaveData();
LoadData();
return 0;
}
文件位置相关函数介绍 ftell:接受文件指针,返回当前文件位置指示值。 fgetpos:与ftell函数功能相似,功能是获取文件位置指示器。文件指针的位置是由专门的fpos_t类型变量给出。 下面的实例会在每次读取一个数据后打印文件指针的位置,可以看出,由于使用文本文件读写,读入的数据都被当作字符类型存储,这样的结果是随着数据的读取,文件位置的增加是没有规律的。
void LoadData() {
int val;
const int n = 10;
FILE* fr = nullptr;
errno_t rx = fopen_s(&fr, "hdc.txt", "r");
if (fr == nullptr) {
printf("open file error\n");
exit(EXIT_FAILURE);
}
fpos_t ps;
fgetpos(fr, &ps);
while (!feof(fr)) {
fscanf_s(fr, "%d", &val);
printf("val = %d ", val);
fgetpos(fr, &ps);
printf("ps = %lld \n", ps);
}
fclose(fr);
fr = nullptr;
}
void SaveData() {
FILE* fw = nullptr;
const int n = 10;
int ar[n] = { 12,23,34,45,56,1,234,2345,6543,231 };
errno_t wx = fopen_s(&fw, "hdc.txt", "w");
if (fw == nullptr) {
printf("open file error\n");
exit(EXIT_FAILURE);
}
for (int i = 0;i < n;i++) {
fprintf(fw, "%d ", ar[i]);
}
fclose(fw);
fw = nullptr;
}
int main(){
SaveData();
LoadData();
return 0;
}
第二个实例指出,如果以二进制文件写如,那么数据在内存中是以什么形式存储的,那么在文件中也是以什么形式存储。所以,随着读取数据的进行,文件位置的增加是均匀的。
void LoadData() {
FILE* fr = nullptr;
const int n = 10;
int val;
errno_t rx = fopen_s(&fr, "hdc.bin", "rb");
if (fr == nullptr) {
printf("open file error\n");
exit(EXIT_FAILURE);
}
int pos = ftell(fr);
for (int i = 0;i < n;i++) {
fread(&val, sizeof(int), 1, fr);
pos = ftell(fr);
printf("val = %d pos = %d\n", val, pos);
}
fclose(fr);
fr = nullptr;
}
void SaveData() {
FILE* fw = nullptr;
const int n = 10;
int ar[n] = { 12,23,34,45,56,1,234,2345,6543,231 };
errno_t wx = fopen_s(&fw, "hdc.bin", "wb");
if (fw == nullptr) {
printf("open file error\n");
exit(EXIT_FAILURE);
}
fwrite(ar, sizeof(int), n, fw);
fclose(fw);
fw = nullptr;
}
int main(){
SaveData();
LoadData();
return 0;
}
fseek:将文件位置指示符移动到指定位置。 fsetpos:将文件位置指示器移动到指定位置。 rewind:将文件位置指示器移动到文件首。 以下实例通过fseek函数将文件位置指示符移动到文件尾部,然后使用ftell可以得到文件的总字节数,向堆区动态申请对应空间,然后通过rewind函数将文件位置指示器移动到文件首,最后使用fread函数读取文件并打印值屏幕上。
int main() {
FILE* fr = nullptr;
errno_t err = fopen_s(&fr, "Test7_19.cpp", "rb");
if (fr == nullptr) {
printf("open file error\n");
exit(EXIT_FAILURE);
}
fseek(fr, 0, SEEK_END);
int len = ftell(fr);
char* buff = (char*)malloc(sizeof(char) * len + 1);
if (buff == nullptr)exit(1);
rewind(fr);
fread(buff, sizeof(char), len, fr);
buff[len] = '\0';
printf("%s", buff);
fclose(fr);
fr = nullptr;
return 0;
}
|