C语言(进阶)—字符函数和字符串函数

简介: C语言(进阶)—字符函数和字符串函数

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);两者是一样的

image.png

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;
}

image.png

但若将返回值类型强转成    int

int main()
{
  if ((int)strlen("abc") - (int)strlen("abcdef") > 0)
    printf(">=\n");
  else
    printf("<\n");
 
 
  return 0;
}

image.png

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.目标空间必须足够大,以确保能存放源字符串

image.png

2.目标空间必须可变

image.png

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 );

image.png

模拟实现

#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 个 

image.png

image.png

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       ===> 追加的字符数

image.png

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        ===> 比较的字符串个数

image.png

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语言标准库中的一个文件操作函数,它的作用是将文件指针重置为文件开头。具体用法如下:


  1. 引入头文件:#include
  2. 打开文件:使用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);
}

image.png

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.文件缓冲区

 文件缓冲区是在读写文件时数据传输过程的一个中间环节。在进行文件读写时,数据会先被读入到内存中的文件缓冲区,然后再由文件缓冲区输出到外存上。文件缓冲区存在的目的是为了提高文件读写的效率,因为与直接读写外存相比,内存中的读写速度更快。此外,文件缓冲区还可以减少对外存的访问次数,从而减少了对外设的磨损。文件缓冲区需要装满后再输送是因为这样可以减少对外存的访问次数,提高读写效率。最终,文件缓冲区会通过系统调用接口刷新到文件内核缓冲区,再通过文件内核缓冲区刷新到外设(磁盘、显示器等等)。

image.png

证明文件缓冲区的存在:


#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;
}

image.png

这里可以得出一个结论:


因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做,可能导致读写文件的问题;

相关文章
|
4天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
19 6
|
24天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
33 10
|
17天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
23天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
53 7
|
23天前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
29 4
|
28天前
|
存储 编译器 C语言
C语言函数的定义与函数的声明的区别
C语言中,函数的定义包含函数的实现,即具体执行的代码块;而函数的声明仅描述函数的名称、返回类型和参数列表,用于告知编译器函数的存在,但不包含实现细节。声明通常放在头文件中,定义则在源文件中。
|
20天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
18 0
|
20天前
|
C语言
【c语言】qsort函数及泛型冒泡排序的模拟实现
本文介绍了C语言中的`qsort`函数及其背后的回调函数概念。`qsort`函数用于对任意类型的数据进行排序,其核心在于通过函数指针调用用户自定义的比较函数。文章还详细讲解了如何实现一个泛型冒泡排序,包括比较函数、交换函数和排序函数的编写,并展示了完整的代码示例。最后,通过实际运行验证了排序的正确性,展示了泛型编程的优势。
18 0
|
24天前
|
算法 C语言
factorial函数c语言
C语言中实现阶乘函数提供了直接循环和递归两种思路,各有优劣。循环实现更适用于大规模数值,避免了栈溢出风险;而递归实现则在代码简洁度上占优,但需警惕深度递归带来的潜在问题。在实际开发中,根据具体需求与环境选择合适的实现方式至关重要。
21 0
|
6月前
|
存储 C语言
C 语言函数完全指南:创建、调用、参数传递、返回值解析
函数是一段代码块,只有在被调用时才会运行。 您可以将数据(称为参数)传递给函数。 函数用于执行某些操作,它们对于重用代码很重要:定义一次代码,并多次使用。
185 3