1.求字符串长度
strlen
size_t strlen ( const char * str );
字符串已经 '\0' 作为结束标志,
strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
参数指向的字符串必须要以 '\0' 结束。
注意函数的返回值为size_t,是无符号的( 易错 )
1.概念理解
#include <string.h> int main() { const char* str = "abcdef"; size_t len1 = strlen("abcdef"); size_t len2 = strlen(str); printf("%d\n", len1); printf("%d\n", len2); return 0; }
1.size_t len1 = strlen("abcdef");
size_t len2 = strlen(str);两者是一样的
2.//两个无符号数相减得到的还是无符号数
// 3 - 6
//-3
//10000000000000000000000000000011
//11111111111111111111111111111100
//11111111111111111111111111111101
//-3的补码当做无符号数进行处理将是一个很大的正数
#include <string.h> int main() { if (strlen("abc") - strlen("abcdef") > 0) printf(">=\n"); else printf("<\n"); return 0; }
但若将返回值类型强转成 int
int main() { if ((int)strlen("abc") - (int)strlen("abcdef") > 0) printf(">=\n"); else printf("<\n"); return 0; }
2.学会strlen函数的模拟实现
1. 计数器
首先创建计数器count,根据指针str所指向的内容判断进行操作,如果str指向的是字符'\0'之外的字符,则count+1,并且str指向下一个字符;如果指向的是字符'\0'则停止计数;
size_t my_strlen(const char* str) { assert(str != NULL); int count = 0; //count用来计数 while (*str++) //指针str指向字符串首元素地址, //当str指向的不是字符'\0',进入循环count++, //并且str指向字符串下一个元素 { count++; } return count; }
2. 递归
函数功能:接受一个字符指针,然后返回从该字符指针开始往后到字符'\\0'之间字符的个数;
递归:如果函数接收的字符指针str所指向的内容不为'\0',则说明当前字符串长度为1加上my_strlen(str+1),这里my_strlen(str+1)计算的是从字符指针str+1开始往后到字符'\0'之间字符的个数;
回归条件:如果函数接收的字符指针指向的内容是'\0'就返回0,并且结束递归;
size_t my_strlen(const char* str) { assert(str != NULL); if (*str == '\0') { return 0; //如果指针str指向的内容为字符'\0', //说明字符串长度为0,直接返回0 } else { return 1 + my_strlen(str + 1); //如果指针str指向的内容不为字符'\0', //说明字符串长度至少为1, //然后看看str+1指向的内容是否为'\0', //如果不是则+1,并且继续看看下一个元素, //否则直接返回结果 } }
3. 指针-指针
指针运算中,指针1与指针2相减的返回值为两指针之间元素个数,
所以一个指针指向字符串首元素,另一个指针指向字符'\0',两者相减即为字符串的长度;
size_t my_strlen(const char* str) { assert(str != NULL); char* start = str; while (*str) //若str指向字符'\0'循环停止 { str++; } return str - start; }
2.长度不受限制的字符串函数
1.strcpy
复制—实现将第一个字符数组中的字符串复制到第二个字符数组中,将第一个字符数组中相应的字符覆盖
char* strcpy(char * destination, const char * source );
Copies the C string pointed by source into the array pointed by destination, including the terminating null
character (and stopping at that point).
源字符串必须以 '\0' 结束
会将源字符串中的 '\0' 拷贝到目标空间
目标空间必须足够大,以确保能存放源字符串
目标空间必须可变
学会模拟实现
#include <string.h> int main() { char arr1[20] = {0}; char arr2[] = "HELLO"; strcpy(arr1, arr2); printf("%s\n", arr1); return 0; }
1.目标空间必须足够大,以确保能存放源字符串
2.目标空间必须可变
3.my_strcpy的实现
#include <assert.h> //strcpy函数返回的是目标空间的起始地址 char* my_strcpy(char* dest, const char* src) { char* ret = dest;//断言判断这两个是否为空 assert(dest && src); while (*dest++ = *src++) { ; } return ret; }
2.strcat
追加—实现将第二个字符数组的字符串连接到第一个字符数组的字符串后面;
char * strcat ( char * destination, const char * source );
注意:
1.目标空间必须足够大,可以修改
2.目标空间中必须得有\0 (保证能找到目标空间的尾)
3.原字符串中也得有\0,在拷贝时将源字符串中 \0也要拷贝过去。
int main() { char arr1[20] = "abc"; strcat(arr1, arr1); printf("%s\n", arr1); return 0; }
模拟实现
#include <stdio.h> #include <string.h> #include <assert.h> //strcat函数,返回的是目标空间的起始地址 char* my_strcat(char* dest, const char* src) { char* ret = dest; assert(dest && src); //1. 找到目标空间的末尾 while (*dest != '\0') { dest++; } //2. 数据追加 while (*dest++ = *src++) { ; } return ret; }
3.strcmp
比较—比较两个字符串,不是比较长度,而是比较对应位置上字符的大小,ASCII码值,不改变其内容。
int strcmp ( const char * str1, const char * str2 );
模拟实现
#include <string.h> int my_strcmp(const char* str1, const char* str2) { assert(str1 && str2); while (*str1 == *str2) { if (*str1 == '\0') return 0; str1++; str2++; } if (*str1 > *str2) return 1; else return -1; }
int my_strcmp(const char* str1, const char* str2) { assert(str1 && str2); while (*str1 == *str2) { if (*str1 == '\0') return 0; str1++; str2++; } return *str1 - *str2; }
3.长度受限制的字符串函数介绍
1.strncpy
char * strncpy ( char * destination, const char * source, size_t num );
char * strncpy ( char * destination, const char * source, size_t num ); char * destination ===> 要拷贝到哪儿的地址 const char * source ===> 被拷贝过去的字符串地址 size_t num ===> 拷贝的字符数 拷贝 num 个字符从源字符串到目标空间 如果源字符串的长度小于 num ,则拷贝完源字符串之后,在目标的后面追加 0 ,直到 num 个
2.strncat
char * strncat ( char * destination, const char * source, size_t num );
char * strncat ( char * destination, const char * source, size_t num ); 返回类型是 char* 类型的,是追加到哪儿的首地址 char * destination ===> 要追加到哪儿的地址 const char * source ===> 被追加过去的字符串地址 size_t num ===> 追加的字符数
3.strncmp
int strncmp ( const char * str1, const char * str2, size_t num );
int strncmp ( const char * str1, const char * str2, size_t num ); 返回值是int类型; str1 > str2 返回 >0 str1 = str2 返回 =0 str1 < str2 返回 <0 const char * str1 ===> 比较的第一个字符串地址 const char * str2 ===> 比较的第二个字符串地址 ize_t num ===> 比较的字符串个数
7.文件的随机读写
1.fseek
seek函数是C标准库中的文件操作函数之一,用于在打开的文件中移动文件指针的位置。
它的原型为:
int fseek(FILE *stream, long offset, int whence);
其中,stream是指向FILE类型的文件指针,offset是要移动的字节数,whence指定了基准位置。whence可以取以下值:
- SEEK_SET:从文件开头计算偏移量
- SEEK_CUR:从当前位置计算偏移量
- SEEK_END:从文件末尾计算偏移量
例如,要将文件指针移动到文件开头,可以使用以下代码:
fseek(fp, 0L, SEEK_SET);
要将文件指针移动到文件末尾,可以使用以下代码:
fseek(fp, 0L, SEEK_END);
要将文件指针向前移动10个字节,可以使用以下代码:
fseek(fp, 10L, SEEK_CUR);
fseek函数返回0表示成功,返回非0值表示失败
以下是一个使用fseek函数移动文件指针的例子
#include <stdio.h> int main() { FILE *fp = fopen("test.txt", "r"); if (fp == NULL) { printf("Failed to open file."); return 1; } // 移动文件指针到第10个字节处 fseek(fp, 10, SEEK_SET); // 读取文件指针当前位置的字符 char ch = fgetc(fp); printf("The character at position 10 is '%c'\n", ch); fclose(fp); return 0; }
2. ftell
返回文件指针相对于起始位置的偏移量
long int ftell ( FILE * stream );
参数stream是指向FILE类型的指针,指定要获取位置的文件流。
返回值为long int类型,表示当前读写指针相对于文件开头的偏移量。如果ftell执行失败,则返回-1。
例如,以下代码演示了如何使用ftell函数获取文件读写指针的当前位置:
#include <stdio.h> int main() { FILE *fp = fopen("example.txt", "r"); if (fp == NULL) { printf("Failed to open file."); return 1; } long int pos = ftell(fp); printf("Current position: %ld\n", pos); fclose(fp); return 0; }
此代码打开一个名为example.txt的文件并使用ftell函数获取当前读写指针的位置,并将其打印到控制台上。最后关闭文件。
3.rewind
让文件指针的位置回到文件的起始位置
void rewind ( FILE * stream );
在编程中,rewind函数是C语言标准库中的一个文件操作函数,它的作用是将文件指针重置为文件开头。具体用法如下:
- 引入头文件:
#include
- 打开文件:使用fopen函数打开文件,并返回一个指向文件的指针。
FILE *fp; fp = fopen("file.txt", "r");
3.读取文件:使用fgets、fscanf、fread等函数读取文件内容。
char buffer[4096]; fgets(buffer, 4096, fp);
4.重置文件指针:使用rewind函数将文件指针重置为文件开头。
rewind(fp);
5.关闭文件:使用fclose函数关闭文件
fclose(fp);
需要注意的是,rewind函数仅适用于顺序访问模式打开的文件。对于二进制文件或随机访问模式打开的文件,使用fseek函数进行指针重定位。
使用例子:
#include <stdio.h> int main() { FILE *fp; char c; fp = fopen("example.txt", "r"); if (fp == NULL) { printf("Cannot open file \n"); return 0; } // 读取文件内容 while ((c = getc(fp)) != EOF) { printf("%c", c); } // 将文件指针重置到开始位置 rewind(fp); // 再次读取文件内容 while ((c = getc(fp)) != EOF) { printf("%c", c); } fclose(fp); return 0; }
在上述示例中,首先打开了一个文件对象example.txt,读取了该文件的内容,并将文件指针移到了文件结尾。然后,使用rewind()函数将文件指针重置到文件开头,再次读取文件的内容。
8.文本文件和二进制文件
文本文件是可阅读的,例如用Windows自带的记事本、写字板所编辑出来的文件,就是文本文件,文本文件是以字符码(字符的二进制码)的形式进行存储的,用户可以随时打开文本文件,阅读文件的内容。
而二进制文件则不是以字符码形式进行存储的文件,例如图片、音乐、视频都是属于二进制文件,由于这些文件所存储的并非是字符,无法以字符的形式进行阅读,通常要用专门的软件进行图片的查看或者音乐、视频的播放。因此,我们所编写的程序源代码文件就属于文本文件,而编译生成的可执行文件就属于二进制文件。
二进制文件的存取与文本文件的存取类似,两者只是编解码的方式不同。文本文件的可读性好,而二进制文件的可读性差。
9.文件结束的判定
被错误使用的 feof
int feof(FILE *stream);
参数stream是指向已打开文件的指针。如果函数返回值为非零,表示已经到达文件结尾。
feof函数的使用一般结合文件读取函数(如fgetc、fgets、fread等)来判断文件读取是否已经结束。
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
1. 文本文件读取是否结束,判断返回值是否为EOF (fgetc),或者NULL(fgets)
例如:
fgetc判断是否为EOF.
fgets判断返回值是否为NULL.
2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
fread判断返回值是否小于实际要读的个数。
例如:
#include <stdio.h> int main() { FILE *fp = fopen("test.txt", "r"); if (fp == NULL) { printf("Could not open file test.txt\n"); return 1; } int c; while ((c = fgetc(fp)) != EOF) { putchar(c); } if (feof(fp)) { printf("End of file reached.\n"); } else { printf("File read error occurred.\n"); } fclose(fp); return 0; }
注意,在使用feof函数之前,一定要先读取文件内容。否则,如果文件为空,feof函数会返回true,从而将误判为空文件。
1.文件文本例子:
#include <stdio.h> #include <stdlib.h> int main(void) { int c; // 注意:int,非char,要求处理EOF FILE* fp = fopen("test.txt", "r"); if (!fp) { perror("File opening failed"); return EXIT_FAILURE; } //fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环 { putchar(c); } //判断是什么原因结束的 if (ferror(fp)) puts("I/O error when reading"); else if (feof(fp)) puts("End of file reached successfully"); fclose(fp); }
2.二进制文件的例子:
#include <stdio.h> enum { SIZE = 5 }; int main(void) { double a[SIZE] = {1.0,2.0,3.0,4.0,5.0}; double b = 0.0; size_t ret_code = 0; FILE *fp = fopen("test.bin", "wb"); // 必须用二进制模式 fwrite(a, sizeof(*a), SIZE, fp); // 写 double 的数组 fclose(fp); fp = fopen("test.bin","rb"); // 读 double 的数组 while((ret_code = fread(&b, sizeof(double), 1, fp))>=1) { printf("%lf\n",b); } if (feof(fp)) printf("Error reading test.bin: unexpected end of file\n"); else if (ferror(fp)) { perror("Error reading test.bin"); } fclose(fp); fp = NULL; }
10.文件缓冲区
文件缓冲区是在读写文件时数据传输过程的一个中间环节。在进行文件读写时,数据会先被读入到内存中的文件缓冲区,然后再由文件缓冲区输出到外存上。文件缓冲区存在的目的是为了提高文件读写的效率,因为与直接读写外存相比,内存中的读写速度更快。此外,文件缓冲区还可以减少对外存的访问次数,从而减少了对外设的磨损。文件缓冲区需要装满后再输送是因为这样可以减少对外存的访问次数,提高读写效率。最终,文件缓冲区会通过系统调用接口刷新到文件内核缓冲区,再通过文件内核缓冲区刷新到外设(磁盘、显示器等等)。
证明文件缓冲区的存在:
#include <stdio.h> #include <windows.h> //VS2022 WIN11环境测试 int main() { FILE* pf = fopen("test.txt", "w"); fputs("abcdef", pf);//先将代码放在输出缓冲区 printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n"); Sleep(10000); printf("刷新缓冲区\n"); fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘) //注:fflush 在高版本的VS上不能使用了 printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n"); Sleep(10000); fclose(pf); //注:fclose在关闭文件的时候,也会刷新缓冲区 pf = NULL; return 0; }
这里可以得出一个结论:
因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做,可能导致读写文件的问题;