一万字带你详解C语言字符函数、字符串函数、内存函数 下

简介: 一万字带你详解C语言字符函数、字符串函数、内存函数

五、错误信息报告

💦 strerror

🔗 函数原型和头:

🔗 函数的返回值:

🔗 功能:

🔗 函数详解:

在调用库函数失败时,都会设置错误码

C语言中有一个全局的错误码 -> int errno ,只要调用库函数发生了错误,就会把错误码放到errno里去

这里strerror就会把错误码翻译成对应的错误信息,然后再把错误信息以字符串首地址返回回来

通常strerror都会和errno一起使用

使用errno需要头文件errno.h


#include<stdio.h>
#include<string.h>
int main()
{
  printf("%s\n", strerror(0));
  printf("%s\n", strerror(1));
  printf("%s\n", strerror(2));
  printf("%s\n", strerror(3));
  printf("%s\n", strerror(4));
  printf("%s\n", strerror(5));
  return 0;
}

⭕ 结果与分析:


❓ 具体是怎么用的

注:以下所使用的对文件操作的一些函数会在后面进行了解

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
  //以读的形式打开test.txt文件,这个文件如果不存在就会打开失败,然后返回一个空指针
  FILE* pf = fopen("test.txt", "r");
  //失败就输出失败的原因,也就题错误信息
  if(pf == NULL)
  {
    printf("%s\n", strerror(errno));                
    return 1;
  }
  //...
  fclose(pf);//关闭文件
  pf == NULL;
  return 0;
}

⭕ 结果:


💦 perror

🔗 函数原型和头:

🔗 函数的返回值:

🔗 功能:


#include<stdio.h>
#include<stdio.h>
int main()
{
  FILE* pf = fopen("test.txt", "r");
  if(pf == NULL)
  {
    perror("fopen");    
    return 1;
  }
  //...
  fclose(pf);
  pf == NULL;
  return 0;
}

⭕ 结果与分析:

六、字符操作函数

1、字符分类函数

🧿 注:简单介绍几个,其余的函数可以照猫画虎


💦 isdigit

🔗 函数原型和头:

🔗 函数的返回值:

🔗 功能:

#include<stdio.h>
#include<ctype.h>
int main()
{
  char ch = '@';
  //如果是数字字符返回非0的值,否则返回0
  int ret = isdigit(ch);
  printf("%d\n", ret);
  return 0;
}

💦 islower

🔗 函数原型和头:

🔗 函数的返回值:

🔗 功能:

#include<stdio.h>
#include<ctype.h>
int main()
{
  char ch = 'A';
  //如果是小写字母返回非0的值,否则返回0
  int ret = islower(ch);
  printf("%d\n", ret);
  return 0;
}

2、字符转换函数

函数 功能
tolower 大写转小写
toupper 小写转大写

💦 tolower

🔗 函数原型和头:

🔗 函数的返回值:

🔗 功能:

#include<stdio.h>
#include<ctype.h>
int main()
{
  char arr[20] = { 0 };
  scanf("%s", arr);
  int i = 0;
  while(arr[i] != '\0')
  {
    printf("%c ", tolower(arr[i]));
    i++;
  }
  return 0;
}

⭕ 结果:

七、内存操作函数

💦 memcpy

🔗 函数原型和头:

🔗 函数的返回值:

🔗 功能:


#include<stdio.h>
#include<string.h>
int main()
{
  int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int arr2[20] = { 0 };
  memcpy(arr2, arr1, 20);//注意第3个参数的单位是字节
  return 0;
}

⭕ 结果:

❓ 我们注意到memcpy的前2个参数是void*类型的,那是不是说它可以拷贝不同的数据

#include<stdio.h>
#include<string.h>
int main()
{
  char arr1[] = "abcdef";
  char arr2[20] = { 0 };
  memcpy(arr2, arr1, 6);
  printf("%s\n", arr2);
  return 0;
}

⭕ 结果:


❓ 对于strcpy函数在拷贝字符串时,如果遇到’\0’它会停止拷贝,那么思考memcpy在拷贝字符串时遇到’\0’是否也会停止

#include<stdio.h>
#include<string.h>
int main()
{
  char arr1[] = "abc\0def";
  char arr2[20] = { 0 };
  memcpy(arr2, arr1, 6);
  printf("%s\n", arr2);
  return 0;
}

⭕ 结果:


这里有一个场景 ❓

#include<stdio.h>
#include<string.h>
int main()
{
  int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
  memcpy(arr1 + 2, arr1, 20);
  return 0;
}

⭕ 结果与分析:

💦 memmove

🔗 函数原型和头:

🔗 函数的返回值:

🔗 功能:


#include<stdio.h>
#include<string.h>
int main()
{
  int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
  memmove(arr1 + 2, arr1, 20);
  return 0;
}

⭕ 结果与分析:

💦 memset

🔗 函数原型和头:

🔗 函数的返回值:

🔗 功能:


#include<stdio.h>
#include<string.h>
int main()
{
  int arr[10] = { 0 };
  memset(arr, 1, 20);
  return 0;
}

⭕ 结果与分析:


❓ 使用memset设置一个10个元素的数组的元素为1

#include<stdio.h>
#include<string.h>
int main()
{
  int arr[10] = { 0 };
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    memset(&arr[i], 1, 1);
  }
  return 0;
}

⭕ 结果:

💦 memcmp

🔗 函数原型和头:

🔗 函数的返回值:

🔗 功能:


#include<stdio.h>
#include<string.h>
int main()
{
  float arr1[] = { 1.0, 2.0, 3.0, 4.0 };
  float arr2[] = { 1.0, 3.0 };
  int ret = memcmp(arr1, arr2, 8);//注意第3个参数的单位是字节
  printf("%d\n", ret);
  return 0;
}

八、函数的模拟实现

💦 strlen

1、计数器的版本(需要临时变量)

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

❗ 缺陷和不足:

❌ 在my_strlen函数里对指针直接进行解引用这是不安全的,因为指针一定得是有效的

❌ my_strlen函数的功能是求字符串的长度,而这个my_strlen函数有权限去改变字符串里的内容,因此这是不安全的

❌ my_strlen这个函数的返回值是int,如果返回的是一个负数,那可能就当场懵逼

💯优化

✔ 使用断言assert,需要引头文件<assert.h>

✔ 使用const对str进行限制

✔ 将函数的返回值int更改为size_t

✔ 对代码整体进行更简洁的优化

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

2、递归版本(不使用临时变量)

#include<stdio.h>
#include<assert.h>
int my_strlen(const char* str)
{
  assert(str);
  if(*str)
    return 1 + my_strlen(str+1);
  else 
    return 0;
}
int main()
{
  char arr[] = "abc";
  printf("%d\n", my_strlen(arr));
  return 0;
}

3、指针版本(指针-指针)

字符串长度 = '\0’的地址 - 首元素的地址

#include<stdio.h>
#include<assert.h>
int my_strlen(char* first, char* end)
{
  assert(first);
  assert(end);
  return end - first;
}
int main()
{
  char arr[] = "abc";
  int left = 0;
  int right = sizeof(arr)/sizeof(arr[0]) - 1;
  printf("%d\n", my_strlen(&arr[left], &arr[right]));
  return 0;
}

💦 strcpy

#include<stdio.h>
#include<assert.h>
void my_strcpy(char* dest, char* src)
{
  while(*src != 0)
  {
    *dest = *src;
    dest++;
    src++;
  }
  src = 0;//将str1的最后一个元素拷贝为0
}
int main()
{
  char arr1[20] = { 0 };
  char arr2[] = "hello bit";
  my_strcpy(arr1, arr2);
  printf("%s\n", arr1);
  return 0;
}

❗ 缺陷和不足:

❌ 在my_strcpy函数里对指针直接进行解引用这是不安全的,因为指针一定得是有效的

❌对于my_strcpy这个函数的2个参数,其一是目标字符串,其二是源字符串,目标字符串必须保证可被修改,而源字符串不能被修改

❌ 这里是先把除 ‘\0’ 其它字符先拷贝,再拷贝 ‘\0’

❌ 这个函数的返回值是void,而在库里是char*,返回的是目标字符串的首地址 :

💯优化

✔ 使用断言assert,需要引头文件<assert.h>

✔ 使用const对str2进行限制

✔ 将 ‘\0’ 和其它字符一起进行拷贝

✔ 将函数的返回值设置为char*

#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{
  assert(dest);
  assert(src);
  char* temp = dest;//备份一份首地址
  while (*dest++ = *src++)
  {
    ;
  }
  return temp;//返回备份的首地址 
}
int main()
{
  char arr1[20] = { 0 };
  char arr2[] = "hello bit";
  printf("%s\n", my_strcpy(arr1, arr2));
  return 0;
}

💦 strcat

#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
  assert(dest && src);
  char* temp = dest;//备份一份首地址 
  //找到目标字符串中的'\0'
  while(*dest)
  {
    dest++;
  }
  while(*dest++ = *src++)
  {
    ;
  }
  return temp;//返回首地址 
}
int main()
{
  char arr1[20] = "hello";
  char arr2[] = "bit";
  printf("%s\n", my_strcat(arr1, arr2));
  return 0;
}

💦 strstr

#include<stdio.h>
#include<assert.h>
char* my_strstr(const char* father, const char* son)
{
  assert(father && son);
  const char* f1 = NULL;            
  const char* s2 = NULL;
  const char* ret = father; 
  while(*ret)
  {
    f1 = ret;
    s2 = son;
    if(*son == '\0')
    {
      return (char*)father;
    }
    while(*f1 && *s2 && (*f1 == *s2))
    {
      f1++;
      s2++; 
    }
    if(*s2 == '\0')
    {
      return (char*)ret;
    }
    ret++;
  }
  return NULL;
}
int main()
{
  char arr1[] = "abbbcdef";
  char arr2[] = "bbc";
  char* ret = my_strstr(arr1, arr2);
  if(ret == NULL)
  {
    printf("没找到\n");
  }   
  else
  {
    printf("找到了:%s\n", ret);
  }
  return 0;
}

⭕ 分析:

▶ 考虑到如果查找失败的情况,不一定是找不到子串,所以要从新再匹配直到*father == '\0’就找不到子串

▶ 需要定义2个指针f1(指向father首地址)和s2(指向son首地址)来帮我们往下去匹配

▶ 如果匹配失败指针要回到该回到的地方

对于s2:s2 = son

对于f1:这里就还需要另1个指针ret(指向fther首地址)来记录,每一次匹配失败,都让ret++,然后f1 = ret


🍳拓展:KMP算法 - 字符串查找算法 - 大家可以了解一下

💦 strcmp

#include<stdio.h>
#include<assert.h>
int my_strcmp1(const char* str1, const char* str2)
{
  assert(str1 && str2);
  while(1)
  {
    if(*str1 > *str2)
      return 1;
    else if(*str1 < *str2)
      return -1;
    else
    {
      if(*str1 == *str2 && *str1 == '\0')
        return 0;
      else
      {
        str1++;
        str2++; 
      }
    } 
  }
}
int my_strcmp2(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 main()
{
  char* p1 = "def";
  char* p2 = "abcdef";
  //int ret = my_strcmp1(p1, p2);//版本1
  int ret = my_strcmp2(p1, p2);//版本2
  if(ret > 0)
    printf("p1 > p2\n");
  else if (ret < 0)
    printf("p1 < p2\n");
  else 
    printf("p1 = p2\n");
  return 0;
}

⭕ 优化冗余:

#include<stdio.h>
#include<assert.h>
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;
}
int main()
{
  char* p1 = "def";
  char* p2 = "abcdef";
  int ret = my_strcmp(p1, p2);
  if(ret > 0)
    printf("p1 > p2\n");
  else if (ret < 0)
    printf("p1 < p2\n");
  else 
    printf("p1 = p2\n");
  return 0;
}

💦 memcpy

#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
  assert(dest && src);
  void* ret = dest;
  while(num--)
  {
    /*
    *(char*)dest = *(char*)src;
    dest = (char*)dest + 1;
    src = (char*)src + 1;
    */
    *((char*)dest)++ = *((char*)src)++;
  }
  return ret;
}
int main()
{
  int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int arr2[20] = { 0 };
  my_memcpy(arr2, arr1, 20);
}

❗ 注意:


❓ my_memcpy能否拷贝重叠的内存空间

#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
  assert(dest && src);
  void* ret = dest;
  while(num--)
  {
    *(char*)dest = *(char*)src;
    dest = (char*)dest + 1;
    src = (char*)src + 1;
  }
  return ret;
}
int main()
{
  int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
  my_memcpy(arr1 + 2, arr1, 20);
}

⭕ 结果:

💦 memmove

❓ 相信小伙伴们都很好奇memmove是怎么处理内存重叠的情况的

🔎 分析一波

#include<stdio.h>
#include<assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
  assert(dest && src);
  void* ret = dest;
  if(dest < src)
  {
    //从前向后拷贝
    while(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 arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
  my_memmove(arr1 + 2, arr1, 20);
  return 0;
}


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

热门文章

最新文章