【C语言进阶】 一篇带你掌握字符串和内存函数(二)

简介: 【C语言进阶】 一篇带你掌握字符串和内存函数(二)

九:strerror

strerror:把错误码转换成错误信息的函数

char * strerror ( int errnum );

注意事项:

  • C语言的库函数在运行的时候,如果发生错误,就会把错误码存在一个变量中,这个变量是:errno
  • 返回的指针指向静态分配的字符串(错误信息字符串)

一些错误码对应的错误信息:

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;
}
//结果:
No error
Operation not permitted
No such file or directory
No such process
Interrupted function call
Input/output error

strerror函数的一般用法:

int main()
{
  //打开文件
  FILE* pf = fopen("test.c", "r");
  if (pf == NULL)
  {
    printf("%s\n", strerror(errno));//需要包含头文件#include<errno.h>
    return 1;
  }
  //读文件
  //关闭文件
  fclose(pf);
  return 0;
}
//打开失败时屏幕显示:
No such file or directory

 fopen是一个打开文件的函数,第一参数是文件名,第二个参数是打开方式,如果打开成功会返回一个有限的指针,打开失败会返回一个空指针,此时我们就可以通过strerror函数去查看为什么打开失败

十:perror

perror:直接打印错误信息的函数

void perror ( const char * str );

注意事项:

str指向一个字符串,这个字符串包含要在错误消息本身之前打印的自定义消息。如果是空指针,则不会打印前面的自定义消息,但仍会打印错误消息

如果参数 str 不是空指针,则打印 str 后跟冒号 (:)和一个空间。然后,无论 str 是否为空指针,都会打印生成的错误描述,后跟换行符 ('\n')

perror函数的一般用法:

int main()
{
  //打开文件
  FILE* pf = fopen("test.txt", "r");
  if (pf == NULL)
  {
    perror("fopen");
    return 1;
  }
  //读文件
  //关闭文件
  fclose(pf);
  return 0;
}
//打开失败时显示:
fopen: No such file or directory

十一:一些字符函数

字符分类函数:

函数 如果它的参数复合下列条件就返回真(一个非零数字)
iscntrl 任何控制字符
isspace 空白字符:空格’ ‘,换页’\f’,换行’\n’,回车’\r’,制表符’\t’或者垂直制表符’\v’
isdigit 十进制数字0~9
isxdigit 十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F
islower 小写字母a~z
isupper 大写字母A~Z
isalpha 字母a~z或A~Z
isalnum 字母或者数字,a~z,A~Z ,0~9
ispunct 标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph 任何图形字符
isprint 任何可打印字符,包括图形字符和空白字符

字符转换函数:

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

int tolower ( int c );

注意事项:

如果 c 是大写字母并且具有小写等效字母,则将 c 转换为其小写等效项。如果无法进行此类转换,则返回的值为 c 不变。

大写字母是以下任意字母:A B C D E F G H I J K L M N O P Q R S T U V W X Y Z,分别翻译为:a b c d e f g h i j k l m n o p q r s t u v w x y z

这里是值传递,不会改变实参

toupper:将小写字母转换成大写字母

int toupper ( int c );

注意事项:

  • 如果 c 是小写字母并且具有大写等效字母,则将 c 转换为其大写等效字母。如果无法进行此类转换,则返回的值为 c 不变
  • 这里是值传递,不会改变实参

应用:

int main()
{
  char arr[] = "I Have An Apple";
  int i = 0;
  while (arr[i])
  {
    if (isupper(arr[i]))
    {
      printf("%c", tolower(arr[i]));//把转换的小写字母直接打印出来
    }
    else
    {
      printf("%c", arr[i]);
    }
    i++;
  }
  return 0;
}
//结果:
i have an apple
int main()
{
  char arr[] = "I Have An Apple";
  int i = 0;
  while (arr[i])
  {
    if (isupper(arr[i]))
    {
      arr[i] = tolower(arr[i]);//用转换后的小写字符替换原有的大写字符
    }
    printf("%c", arr[i]);
    i++;
  }
  return 0;
}

 以上介绍的这些函数都是针对字符串或者字符的,那如果我们要拷贝其他类型的数据呢?用上面这些函数自然就行不通了,此时就要用到即将介绍的内存函数了。

十二:memcpy

memcpy:内存拷贝函数

void * memcpy ( void * destination, const void * source, size_t num );

注意事项:

 这里的destination指向要在其中赋值内容的目标数组,source指向要复制的数据源,num是要复制的字节数,注意这里前两个指针的的类型还有函数返回值都是void*,这是因为,memcpy这个函数是内存拷贝函数,它有可能拷贝整型,浮点型,结构体等等各种类型的数据……虽然返回类型是void*,但他也是必不可少的,void*也表示一个地址,用户可以把它强制转换成自己需要的类型去使用。

应用:

//把arr1中的1、2、3、4、5拷贝到arr2数组中
int main()
{
  int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
  int arr2[10] = { 0 };
  memcpy(arr2, arr1, 20);//拷贝5个整型,就是20个字节
  return 0;
}
//把arr1中的3、4、5、6拷贝到arr2数组中
int main()
{
  int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
  int arr2[10] = { 0 };
  memcpy(arr2, arr1+2, 16);//此时只要改变参数中数据源的地址就可以,把3的地址传过去就行,复制4个整型就是1个字节
  return 0;
}

模拟实现:

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

上面代码中模拟出来的my_memcpy函数已经可以实现把arr1数组中的元素拷贝到arr2数组里面去。但当我们想把arr1数组中的1、2、3、4、5拷贝到arr1数组中的3、4、5、6、7上去时,就会发生错误,如下图:

d03004525c6e4a09b34e763c5fd97fe2.png

原因是:当1拷贝到3上时,原来的3已经被1替换,当2拷贝到4上的时候,原来的4已将被2替换。所以当拷贝arr[2]到arr[4]上的时候,原本arr[2]里面存放的3已将被1替换了,同理,所以才得出了不符合我们预期的结果。那如何解决这个问题呢?先来分析这个问题产生的原因,这是因为源空间与目标空间之间有重叠,这里的arr[2]、arr[3]、arr[4]既是源空间也是目标空间,当拷贝1和2的时候把源空间中开没有拷贝的3和4就给覆盖了,此时源空间arr[2]和arr[3]里面存的就不再是3和4了,而是1和2,所以此时拷贝arr[2]和arr[3]里面的数据,其实拷贝的就是1和2。为了解决这个问题,我们可以从后往前拷贝,此时就不会出现这样的问题

f21ec47a35654ae89103db1adfa99477.png

解决的思路就是:先拷贝重叠区域的元素,避免其还没拷贝就被覆盖掉。但并不是一遇到源空间和目标空间重叠就从后王前拷贝,比如下面这样,此时要把3、4、5、6、7拷贝到1、2、3、4、5里面

a7377b6b25804fc0978966f01853d720.png

此时就不能用上面的方法从后往前拷贝了,但是可以根据上面提到的解决思路:先拷贝重叠区域的元素,所以此时就应该从前往后拷贝,即按照3、4、5、6、7的顺序去拷贝

 因此在拷贝的时候,是按照从前往后的顺序还是从后往前的顺序得看具体的情况。当目标空间的起始位置在源空间的前面时,就得从前往后进行拷贝。当目标空间的起始位置在源空间的后面的时候,就要从后往前拷贝。当源空间和目标空间没有任何重叠的时候,不管是从前往后拷贝还是从后往前拷贝效果都一样。

 接下来介绍的memmove函数就可以完成重叠空间的元素拷贝。

十三:memmove

memmove:内存拷贝函数

void * memmove ( void * destination, const void * source, size_t num );

 它的参数、返回值与memcpy函数一模一样。这里就不过多介绍。对于这两个函数来说,目标空间必须足够大,不然就会发生越界访问。

模拟实现:

void* my_memmove(void* dest, const void* src, int 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--)//num为1的时候,下面的num就是0
    {
      *((char*)dest + num) = *((char*)src + num);//通过num的减减就可以实现对每一个字节的访问
    }
  }
  return ret;
}
int main()
{
  int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
  my_memmove(arr1+2, arr1, 20);
  return 0;
}

十四:memcmp

memcmp:内存比较函数

int memcmp ( const void * ptr1, const void * ptr2, size_t num );

注意事项:

  • 比较从ptr1和ptr2指针开始的num个字节
  • 两个内存块中不匹配的第一个字节在 ptr1 中的值低于 ptr2 中的值返回一个小于零的数子,相等返回零,两个内存块中不匹配的第一个字节在 ptr1 中的值大于在 ptr2 中的值返回一个大于零的数子

应用

int main()
{
  int arr1[] = { 1,2,6 };//01 00 00 00 02 00 00 00 06 00 00 00
  int arr2[] = { 1,2,5 };//01 00 00 00 02 00 00 00 05 00 00 00
  int ret = memcmp(arr1, arr2, 9);
  printf("%d\n", ret);
  return 0;
}//结果:
1

十四:memset

memset:内存设置函数

void * memset ( void * ptr, int value, size_t num );

注意事项:

以字节为单位来设置内存中的数据,把从ptr开始往后的num个字节设置成value

形参value也可以是字符,字符其实也是整型,因为字符在内存中存的是其ASCII

value如果是整数的话,需要注意它的取值范围,因为一个字节最大可以存储255,超过255就会发生截断

应用:

int main()
{
  char arr[] = "hello word";
  memset(arr, 'x', 5);//先把"hello"改成"xxxxx"
  printf("%s\n", arr);
  memset(arr + 6, 'y', 4);//再把"word"改成"yyyy"
  printf("%s\n", arr);
  return 0;
}
//


目录
相关文章
|
1天前
|
算法 搜索推荐 C语言
【C语言篇】深入理解指针4(模拟实现qsort函数)
【C语言篇】深入理解指针4(模拟实现qsort函数)
10 2
|
1天前
|
C语言
【C语言】探索文件读写函数的全貌(三)
【C语言】探索文件读写函数的全貌
|
1天前
|
存储 C语言
【C语言】探索文件读写函数的全貌(二)
【C语言】探索文件读写函数的全貌
|
1天前
|
C语言 C++
【C语言】解决不同场景字符串问题:巧妙运用字符串函数
【C语言】解决不同场景字符串问题:巧妙运用字符串函数
|
1天前
|
存储 编译器 C语言
【C语言】函数(涉及生命周期与作用域)
【C语言】函数(涉及生命周期与作用域)
|
1天前
|
存储 编译器 C语言
【C语言篇】数组和函数的实践:扫雷游戏(附源码)
【C语言篇】数组和函数的实践:扫雷游戏(附源码)
7 0
|
1天前
|
C语言
【C语言】探索文件读写函数的全貌(一)
【C语言】探索文件读写函数的全貌
|
1天前
|
编译器 C语言 C++
【C语言】精妙运用内存函数:深入底层逻辑的探索
【C语言】精妙运用内存函数:深入底层逻辑的探索
|
1天前
|
程序员 编译器 数据处理
【C语言】深度解析:动态内存管理的机制与实践
【C语言】深度解析:动态内存管理的机制与实践
|
2月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
178 0