整理介绍字符函数和字符串函数+内存函数

简介: 字符串将’\0’作为结束标志,strlen函数返回的是在’\0’之前出现的字符个数但不包含‘\0’。

本篇重点介绍处理字符和字符串的库函数的使用和注意事项


本篇重点


前言:


C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常要放在常量字符串中或者字符数组中。


字符串常量适用于那些对它不做修改的字符串函数


求字符串长度


strlen


41d201d565384703a7a98db11f3cb723.png


注意点:

1. 字符串将’\0’作为结束标志,strlen函数返回的是在’\0’之前出现的字符个数但不包含‘\0’。

2. 参数指向的字符串必须要有’\0’,以’\0’为结束标志

3. 注意函数的返回值是size_t ,是无符号整数

4. 最好理解strlen函数的模拟实现


在这里我会展示3种方法来模拟实现strlen函数。


strlen函数模拟实现:


第一种:暴力法死算


#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{
    assert(str);
  int count = 0;
  while (*str!='\0')
  {
    count++;
    str++;
  }
  return count;
}
int main()
{
  char arr[] = "abcdef";
  int ret = my_strlen(arr);
  printf("%d", ret);
  return 0;
}


第二种:指针-指针=指针之间元素的个数


size_t my_strlen(const char* str)
{
    assert(str);
  char* start = str;
  while (*str != 0)
  {
    str++;
  }
  return str - start;
}
int main()
{
  char arr[] = "abcdef";
  int ret = my_strlen(arr);
  printf("%d", ret);
  return 0;
}


第三种:递归法


size_t my_strlen(const char* str)
{
  assert(str);
  if (*str != '\0')
  {
    return 1 + my_strlen(str + 1);
  }
  else
    return 0;
}
int main()
{
  char arr[] = "abcdef";
  int ret = my_strlen(arr);
  printf("%d", ret);
  return 0;
}


还有一个题目来考考你:涉及上面的注意点喔


#include <string.h>
int main()
{
  const char* str1 = "abcdef";
  const char* str2 = "bbb";
  if (strlen(str2) - strlen(str1) > 0)
  {
    printf("str2>str1\n");
  }
  else
  {
    printf("srt1>str2\n");
  }
  return 0;
}


结果:


77842542ee454be394468c7d3308d39d.png


为什么呢?


计算str1的字符串大小应该为6,计算str2的字符串大小应该是3,3-6应该小于0,应该打印str1>str2才对呢。


是不是忘记了什么?strlen函数的返回值是什么呢?是size_t类型的,两个无符号数相减,得到的也是无符号数,所以-3就被当作无符号数来看啦,这将是一个非常大的数。所以肯定大于0。


拷贝字符串函数


strcpy


6ee498b032f34dbcb14111186084c054.png


注意点:

1. 将源指向的C字符串复制到目标指向的数组中,包括

终止空字符(并在该点停止)

2. 源字符串必须以’\0’结束

3. 会将源字符串的’\0’拷贝到目的空间

4. 目标空间要足够大,以确保源字符串能存放进去

5. 目标空间必须可变,不能是常量

6. 要理解strcpy模拟实现


strcpy函数模拟实现


#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
  assert(dest && src);//断言判断dest和src指针不为空指针
  char* start = dest;//记录dest的起始位置,因为最后还要将目标空间的起始地址返回
  while (*dest++ = *src++)
  {
    ;//将源字符串全部拷贝到目的空间里,当*src为'\0'时,赋值给dest后该表达式为假,跳出循环,全部拷贝成功
  }
  return start;//将起始地址传回
}
int main()
{
  char arr1[20] = { 0 };//目标空间要足够大
  char arr2[] = "abcdef";
  char *ret=my_strcpy(arr1, arr2);
  printf("%s", ret);
  return 0;
}

结果:


c64f7986628448f18d6557427e19c4e4.png


(追加)连接字符串函数


strcat


9542e4d6b70e4bcbbf682870252bd161.png


注意点:

1. 将源字符串的副本追加到目标字符串。终止的空字符

in destination被源的第一个字符覆盖,并且包含一个空字符

在由目的地中的二者串联形成的新字符串的末尾。

2. 源字符串必须以’\0’结束

3. 目标空间必须要足够大,能容纳源字符串

4. 目的空间要能修改

5. 该函数不能用于字符串给自己追加


strcat函数模拟实现:


#include <stdio.h>
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
  assert(dest && src);//断言判断
  char* start = dest;
  //首先要找到目的空间的'\0'
  while (*dest!='\0')
  {
    dest++;
  }
  //再将字符串追加到'\0'的后面,包括'\0'
  while (*dest++ == *src++)
  {
    ;
  }
  return start;//最后返回目标空间的起始地址
}
int main()
{
  char arr1[20] = "hello ";
  char arr2[] = "world";
  char* ret =my_strcat(arr1, arr2);
  printf("%s",ret );
  return 0;
}


结果:


cfd314e0628e436d87b5837868c5442c.png


比较两个字符串函数


strcmp


f72becccc32c4264bb7fe1b93856590b.png


注意点:

1. 此函数开始比较每个字符串的第一个字符。如果它们相等,它继续使用,直到字符不同或终止达到空字符。

2. 标志规定:


第一个字符串大于第二个字符串,则返回大于0的数字

第一个字符串等于第二个字符串,则返回0

第一个字符串小于第二个字符串,则返回小于0的数


3. 比较的是相同位上字符的ASCII 码值


模拟实现strcmp函数


int my_strcmp(const char* str1, const char* str2)
{
  assert(str1 && str2);
  //先比较第一个字符,如果相同则比下一个,后面的类似。
  //直到相同位的字符不一样比较字符ASCII码值
  while (*str1 == *str2)
  {
    if (*str1 == '\0')//在这个循环里,如果str1和str2中有一个等于0则表示两个字符串是相等的,因为在str1==str2的条件下,又等于0
    {//肯定是相等的
      return 0;
    }
    str1++;
    str2++;
  }
  return *str1 - *str2;
}
int  main()
{
  char arr1[] = "abc";
  char arr2[] = "abd";
  int ret=my_strcmp(arr1, arr2);
  if (ret > 0)
  {
    printf("arr1>arr2");
  }
  else if (ret < 0)
  {
    printf("arr1<arr2");
  }
  else
    printf("arr1=arr2");
  return 0;
}


结果:


24d024b7c3bd4a5db5b46e21df556e50.png


还有一个注意点:


4.在VS编译器环境下strcmp比较两个字符串时,第一个字符串大于第二个字符串返回的是1,第一个字符串小于第二字符串返回的是-1,相同时返回是0,但在不同的环境下,strcmp函数的返回的值可能不是1,-1,所以不能一概而论。
比如这题这样写你觉得合适吗?


int  main()
{
  char arr1[] = "abc";
  char arr2[] = "abd";
  int ret=my_strcmp(arr1, arr2);
  if (ret==1)
  {
    printf("arr1>arr2");
  }
  else if (ret==-1)
  {
    printf("arr1<arr2");
  }
  else
    printf("arr1=arr2");
  return 0;
}


虽然也能算出来,但在不同的环境下,就不一定能算出来了所以最好还是写成大于0,小于0的形式比较保险。


对上面改进字符串函数介绍


strncpy


是对strcpy函数的改进版,增加了可以拷贝几个字符的功能,其他都一样。


843b7266b66145deab2a9e6b38551f40.png


注意点:

1. 将源的前num个字符复制到目标。如果源C字符串的结尾

(由空字符发出信号)在num个字符被复制之前被发现,

目标用零填充,直到总共写入num个字符。

2. 拷贝num个字符从源字符串到目的空间

3. 如果源字符串的长度小于num,则拷贝源字符串后,在目标的后面追加0,直到num为止。

strncat


是对strcat函数的改进版,增加了可以追加几个字符的功能,其他的都一样。


7d3f7085188d44c69ac618d2a7fc37cb.png


注意点:


将源的前num个字符追加到目标,加上终止的空字符。

如果源代码中的C字符串的长度小于num,则仅复制终止空字符之前的内容。。


strncmp


是strcmp函数的改进版,可以选择比较前num个字符的大小


e4d909367fe148368bf6d9031b4bed32.png


注意点:


比较出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。


查找子字符串函数


strstr


30ddf1f196c0459ca1405eebae7a15e2.png


注意点:

1. 返回值是指向str1中首次出现str2所指向的代表的字符的地址,如果序列在str1中不存在,则返回NULL指针

2. 是查找str1中是否有str2这个字符子串。


例子:


int main()
{
  char str[] = "This is a simple string";
  char* ret;
  ret = strstr(str, "simple");
  //strstr返回的是在str数组中首次出现"simple"的指针,如果没有出现就返回NULL;
  printf("%s", ret);
  return 0;
}


结果:


54f1dce474684e93a9541e7d680a9191.png


模拟实现strstr函数:


char* my_strstr(const char* str1, const char* str2)
{
  assert(str1 && str2);
  if (*str2 == '\0')
  {
    return str1;
  }
  char* s1 = NULL;
  char* s2 = NULL;
  char* cp = str1;
  //
  while (*cp)
  {
    s1 = cp;
    s2 = str2;
    while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
    {
      s1++;
      s2++;
    }
    if (*s2 == '\0')
    {
      return cp;
    }
    cp++;
  }
  return NULL;
}
int main()
{
  char arr1[] = "abbbcd";
  char arr2[] = "bc";
  char* ret = my_strstr(arr1, arr2);
  printf("%s", ret);
  return 0;
}


结果:


f4fef831a94e4bc9845d6db70cd7a3eb.png


将字符串拆分为标记的函数


strtok


45596f8a1850495f8ae4a04d716c5ccb.png


注意点:


1.参数 deli是个字符串,定义了用作分隔符的字符集合

2.第一个参数指定了要一个字符串,它必须包含0个或多个由deli字符串中一个或者多个分隔符分割的标志

3.strtok函数找到str中的一个标记,会将其用’\0’替换,返回一个指向这个标记的指针。(strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容)

4.strtok函数的第一个参数不为NULL,函数将会找到str中的第一个标记,strtok函数将保存它在字符串中的位置

5.strtok的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记。

6.如果字符串中不存在更多的标记,则返回NULL指针


例子:


#include <string.h>
int main()
{
  char arr1[] = "xiao tao lailo@qq.com";//要分割的字符串C
  char arr2[] = "@.";//分割符形成的集合分割符@ 和 分割符 .
  char arr3[100] = { 0 };
  strcpy(arr3, arr1);//将数据临时拷贝一份,处理arr1中的内容
  char*ret=strtok(arr3, arr2);//第一次要将完整的要分割的字符串作为参数
  printf("%s\n", ret);
  ret = strtok(NULL, arr2);//第二次就不需要字符串了,只需要NULL作为参数就可以
  printf("%s\n", ret);
  ret = strtok(NULL, arr2);//第三次也是一样
  printf("%s\n", ret);
  return 0;
}


daea0a435be446198b1e05fd963ca928.png


也可以把分割的过程写成一个循环,因为就第一次不同,后面的都是一样的可以写成下面这样:


#include <string.h>
int main()
{
  char arr1[] = "xiao tao lailo@qq.com";//要分割的字符串C
  char arr2[] = "@.";//分割符形成的集合分割符@ 和 分割符 .
  char arr3[100] = { 0 };
  strcpy(arr3, arr1);//将数据临时拷贝一份,处理arr1中的内容
  //char*ret=strtok(arr3, arr2);//第一次要将完整的要分割的字符串作为参数
  //printf("%s\n", ret);
  //ret = strtok(NULL, arr2);//第二次就不需要字符串了,只需要NULL作为参数就可以
  //printf("%s\n", ret);
  //ret = strtok(NULL, arr2);//第三次也是一样
  //printf("%s\n", ret);
  char* ret = NULL;
  for (ret = strtok(arr3, arr2); ret != NULL; ret = strtok(NULL, arr2))
  {
    printf("%s\n", ret);
  }
  return 0;
}


返回错误信息的函数


strerror


464aedb00e0c4a27b010b3e24a67f7d0.png


注意点:这个函数的功能是,返回错误码,所对应的错误信息


例子:


#include <errno.h>//使用该库函数需要引用头文件
#include <string.h>
int main()
{
  FILE* pFile = fopen("unexist.txt", "r");//打开一个不存在的文件
  if (pFile == NULL)//打开失败会进行报错
  {
    printf("%s", strerror(errno));//错误信息打印
  }
  else
    fclose(pFile);
  return 0;
}


结果:


4ec69f2bdbdb4f869da1975d7fe570c8.png


perror


这个相比较上面的更方便些,因为这个直接可以打印报错


9f08daa2fe5f48609c77075a1164dede.png


例子:


#include <string.h>
int main()
{
  FILE* pFile = fopen("unexist.txt", "r");//打开一个不存在的文件
  if (pFile == NULL)//打开失败会进行报错
  {
    perror("fopen");//参数是在哪个过程会报错呢就写哪个过程。
  }
  else
    fclose(pFile);
  return 0;
}


结果:


95104b936ca7420c8a1fa293d97f324d.png


字符分类函数


123420809e6547709c1df6b35f47dff7.png


islower


判断是否是小写字母函数


ad80858ec2f14a079d14a379a002caa7.png


返回值:


f238202234ee4fff8e374f9c794929da.png


是—返回非0,不是返回0.


例子:


#include <stdio.h>
#include <ctype.h>
int main()
{
  int ret1 = islower('A');
  int ret2 = islower('a');
  printf("%d\n", ret1);//不是小写字母返回0
  printf("%d\n", ret2);//是小写字母返回非0
  return 0;
}

0df6e9488b5f4283a81b7a7ad12a277d.png


isupper


1.检查字符是否为大写字母

c175fd9d9e344273ba4df799400b295a.png


2.返回值:

811dad8d0fc3483b97d0cbb800f5125b.png



是大写字母就返回非0,不是就返回0.


3.例子:


#include <stdio.h>
#include <ctype.h>
int main()
{
  int ret1 = isupper('A');//是大写字母返回非0
  int ret2 = isupper('a');//不是大写字母返回0
  printf("%d\n", ret1);
  printf("%d\n", ret2);
  return 0;
}

a94ca29acea74d9b866beeaf8f859562.png


归纳总结


以上字符分类函数的结构都是一样的,返回值也是差不多的,如果是真就返回非0的数,如果是假就返回0。


都是只有一个参数,一个整数返回值。参数取决于是分类什么。


字符转换函数:


tolower


4561daf35a3748bd8f963c6a98d7ebcc.png


例子1:


#include <ctype.h>
int main()
{
  int ret=tolower('A');//将大写转换为小写
  printf("%c", ret);
  return 0;
}


f862558ce6bb478f8b5a8972da8ad8fc.png


例子2:


#include <ctype.h>
int main()
{
  int i = 0;
  char arr[] = "Xiao Tao Lai Lo";
  char c;
  while (arr[i])
  {
    c = arr[i];
    if (isupper(c))//如果是大写
    {
      c = tolower(c);//转换成小写
    }
    putchar(c);//输出
    i++;
  }
  return 0;
}


结果:


e9c01c129c9949db872d23daeb29751a.png


toupper


将小写字母转换为大写字母


af08af171aa444a18e46d98c6c41ff20.png


例子:


#include <ctype.h>
int main()
{
  int ret=toupper('a');//将小写转换为大写
  printf("%c", ret);
  return 0;
}


4969c5452e2a4bfd99c7f7f3d5612587.png


以上函数都是针对字符或者字符串的,下面将介绍可以改变各种类型的函数————内存函数


针对内存的函数


memset


62d24734cbff48c5819c34d2e7178d2d.png


注意点:

1. 这是内存设置函数

2. 以字节为单位来设置内存中的数据


例子:


#include <string.h>
int main()
{
  char arr[] = "xiao tao lai lo";
  memset(arr, 'x', 4);//第一个参数是要指向设置的内存块的指针
                      //第二个参数是要设置的值
                      //第三个参数是要设置多少字节数
  printf("%s\n", arr);
  memset(arr + 5, 'y', 3);
  printf("%s\n", arr);
  return 0;
}


99ecc55ee8ce4783a8bf3f97f14ed425.png


注意:这个是以字节为单位改变内存的

int main()
{
    int arr[10] = { 0 };
    memset(arr, '1', 40);//要将arr这个内存块40个字节全部设置成1可以吗?
    return 0;
}


6d98714024ef4f5c8114e102f011a564.png


可是通过监测分析arr中每个数都是一个很大的数字这是为什么呢?


这是因为memset是以字节为单位来改变内存的,它每次修改一个字节,也就是每次将一个字节修改为1,所以一个int类型4个字节,就改成了4个1了。


d01fea4eeb2c43acb36b60966aa908a6.png


一开始内存中都是0,使用memset函数后看内存中是什么样子呢?


0a63ef6969844090a3a1c4527c6f29ad.png


所以在使用memset函数时要注意这点,不然容易出错。一般memset函数用于将一个变量初始化为0,比较容易。


memcpy


49cb98abe29644d4a39206c44d1d16cd.png


例子:


int main()
{
  int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
  int arr2[10] = { 0 };
  memcpy(arr2, arr1, 40);//参数1是要拷贝的目标空间,参数2是拷贝的源数据,参数3是拷贝的数量
  return 0;
}

a73da9ac25c149cd9a260b7a352a74c8.png


我们可以来深入的理解这个函数,来模拟实现下


memcpy函数模拟实现:


void* my_memcpy(void* dest, const void* src, size_t num)
{
  void* ret = dest;//记录一下目标空间的起始地址,最后返回要用
  //由于void*类型无法使用我们需要把它强制类型转换为char*
  while (num--)//循环num次
  {
    *(char*)dest = *(char*)src;
    dest = (char*)dest + 1;
    src = (char*)src + 1;
    //注意这里不能写成(char*)dest++这种形式,因为强制类型转换的只是暂时的,++后还是void*类型的
    //但是可以++(char*)dest这样写
  }
  return ret;//返回目标空间的起始地址
}
int main()
{
  int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
  int arr2[10] = { 0 };
  my_memcpy(arr2, arr1, 40);
  return 0;
}

dc99f6dd24a54d48ba92fbc7a44a2a84.png


注意点2:


1d2638ceb50d47b1af28f9a80df62913.png


该函数要求要拷贝的空间与源数据的空间不能由重叠


举个例子:



05338869a2ff475d867e0611cd48bb16.png


它要求使用memmove这个函数来处理有重叠部分。其实在VS环境下,memove的功能与memcpy功能是差不多的都可以处理


但有的环境不可以,所以保险起见,当处理有重叠部分的需要使用memove函数,接下来就介绍memove函数


memmove


cfbea4d976084bb4a08956f97850155e.png


这个功能结构都是与memcpy是一样的,只不过更全面可以用于重叠部分的拷贝


int main()
{
  int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
  memmove(arr, arr + 2, 20);
  //要求将 3 4 5 6 7拷贝到 1 2 3 4 5内容去,
  return 0;
}


1c83a18dbee04444970ebd6396c4c50a.png


那怎么实现重叠部分的拷贝的呢?


分情况讨论下,1.当目标空间在源数据空间的左边时


f1f9ec74c55145b187247e1a90ef8bac.png

e6e74b73c7774d6181c8958d6bf1218d.png


2.当目标空间在源数据的右边时


7cb6f17d3a6b48fa9c3873cb66cdc2a6.png


所以总结下,当目的空间地址小于源数据空间时,从前往后交换


当目的空间地址大于源数据空间时,从后往前交换


模拟实现下这个memmove函数吧


void* my_memmove(void* dest, void* src, size_t num)
{
  void* ret = dest;//记录一下目标空间的起始地址,最后返回要用
  if (dest < src)
  {
    //从前往后交换,跟memcpy一样直接复制过来就可以
    while (num--)//循环num次
    {
      *(char*)dest = *(char*)src;
      dest = (char*)dest + 1;
      src = (char*)src + 1;
    }
  }
  else
  {
    //从后完前交换
    while (num--)
    {
      *((char*)dest + num) = *((char*)src + num);
    }
  }
  return ret;
}
int main()
{
  int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
  my_memmove(arr+2, arr, 20);
  //要求将 1 2 3 4 5拷贝到 3 4 5 6 7内容去,
  return 0;
}


memcmp


1097db71aa7b4d0982ec00e9509eb989.png


返回值:


7ee24612375a465ab2656f430ab3bcae.png


例子:


int main()
{
  int arr1[] = { 1,2,3 };
  int arr2[] = { 1,2,4 };
  int ret=memcmp(arr1, arr2, 8);//前两个都一样所以最后结果应该为0
  printf("%d", ret);
  return 0;
}


int main()
{
  int arr1[] = { 1,2,3 };
  int arr2[] = { 1,2,4 };
  int ret=memcmp(arr1, arr2, 12);//前两个都一样,但第三个不同进行比较,3小于4,所以最好结果为<0
  printf("%d", ret);
  return 0;
}


bd20b2239f5a41c4b02932d0933e513e.png

相关文章
|
4月前
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
310 15
|
11月前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
403 6
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
214 0
|
4月前
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
1514 0
|
4月前
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
454 1
|
4月前
|
存储 弹性计算 固态存储
阿里云服务器配置费用整理,支持一万人CPU内存、公网带宽和存储IO性能全解析
要支撑1万人在线流量,需选择阿里云企业级ECS服务器,如通用型g系列、高主频型hf系列或通用算力型u1实例,配置如16核64G及以上,搭配高带宽与SSD/ESSD云盘,费用约数千元每月。
427 0
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
908 0
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。

热门文章

最新文章