C语言从入门到放弃——字符串和内存操作函数

简介: C语言从入门到放弃——字符串和内存操作函数

字符串,是一种由双引号引起的一整串字符,在C语言中,字符串是没有类型的,通常我们将字符串放在字符数组当中,同时,我们对于字符串的操作是很频繁的,因为对于字符串的操作频繁,所以C语言本身提供了一些对字符串进行处理的函数。


字符操作函数


strlen


strlen用于计算我们的字符串长度,对于strlen而言,是不会将结尾处的'\0'计算进去的,所以我们在用strlen的时候注意,参数的结尾一定要有'\0',不然strlen会返回一个随机值,对于我们的strlen来说,返回值一定是一个无符号的数,字符串的长度不可能出现负数,最小也是0,知道这些注意事项之后,我们进行模拟实现,因为我们知道strlen是计算'\0'之前出现的数,那我们就可以定义一个计数器,用循环语句,将'\0'作为我们的判断条件,每次进入的时候都将计数器加一次,同时我们的字符串也像后加加,当遇到'\0'的时候,不在进入循环,返回计数器的值。


#include<stdio.h>
#include<string.h>
#include<assert.h>
size_t my_strlen(const char* ch)
{
  assert(ch);
  size_t count = 0;
  while (*ch++)
  {
  count++;
  }
  return count;
}
int main()
{
  char ch[] = "asdfghj";
  int len1 = strlen(ch);
  int len2 = my_strlen(ch);
  printf("%d\n", len1);
  printf("%d\n", len2);
  return 0;
}


0a2653c851af460fa595bd959398a8f1.png


strcpy


strcpy,字符串拷贝,是将我们想要拷贝的字符串拷贝到目标空间的一个函数,同样,对于我们想要拷贝的字符串,也是需要结尾处一定要有'\0'的,不然会在字符串后面带上一堆乱码,因为我们的strcpy也是以'\0'为判断条件,还有需要注意的是,我们要让目的地的空间放的下我们要拷贝进去的字符串,不然就会进行越界,目的地也必须是可变的,不能是常量字符串,那知道这些注意事项,我们进行模拟实现。


#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strcpy(char* ch1, const char* ch2)
{
  char* cp = ch1;
  assert(ch1 && ch2);
  while (*ch1++=*ch2++)
  {
  ;
  }
  return cp;
}
int main()
{
  char ch1[25] = { 0 };
  char ch2[] = "asdfghjkl";
  //strcpy(ch1, ch2);
  my_strcpy(ch1, ch2);
  printf("%s", ch1);
  return 0;
}


0eacb84100b54626af849e6b562bf92a.png2d65d23f6d4748949b924e4057485923.png


strcat


strcat,字符串追加,可以将一个字符串的内容追加到另一个字符串的后面,这里要注意我们的两个字符串结尾处要有'\0',并且我们的目标空间要足够大,能放的下两个字符串才可以,并且我们的目的地要可以被修改,实现的话和我们的srecpy差不多,只需要提前找到我们目的地的'\0',然后进行追加。


#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strcat(char* ch1, const char* ch2)
{
  char* cp = ch1;
  assert(ch1 && ch2);
  while (*ch1++)
  {
  ;
  }
  ch1--;
  while (*ch1++ = *ch2++)
  {
  ;
  }
  return cp;
}
int main()
{
  char ch1[20] = "hello ";
  char ch2[] = "world";
  //strcat(ch1, ch2);
  my_strcat(ch1, ch2);
  printf("%s", ch1);
}


2e9b90b2ca334476abebe75bafe6eeaa.png



那如果我们让它把自己的内容追加在自己的后面怎么办呢?那要自己追加自己,我们模拟实现的思路就行不通了,那我们优化一下,一开始记录下我们要追加在后面的字符串的地址,然后追加行不行呢?这样的话,目的地的字符串就变成了一个结尾没有'\0'的字符串,就会一直追加下去,那我们可不可以先记住要追加字符串的起始地址,然后用一个计数器,记住要追加的字符串有多少字符,追加完这些字符后,我们自己补一个'\0',是不是问题就解决了。


#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strcat(char* ch1, const char* ch2)
{
  char* cp = ch1;
  assert(ch1 && ch2);
  const char* c = ch2;
  int count = 0;
  while (*ch1++)
  {
  ;
  }
  ch1--;
  while (*ch2++)
  {
  count++;
  }
  while (count--)
  {
  *ch1++ = *c++;
  }
  *ch1 = '\0';
  return cp;
}
int main()
{
  char ch1[20] = "hello ";
  char ch2[] = "world";
  //strcat(ch1, ch2);
  my_strcat(ch1, ch1);
  printf("%s", ch1);
}


strcmp


strcmp,字符串比较,顾名思义,用来比较两个字符串的大小的,在C语言中,我们不能直接用大于小于号来确定字符串谁大谁小,但是本质上,字符串比较的是下标一样的字符的ASCII码值,如果大于则返回一个大于0的数,如果小于返回一个小于0的数,如果等于返回0,那知道这些我们就可以对其进行模拟实现。


#include<stdio.h>
#include<string.h>
#include<assert.h>
int my_strcmp(const char* ch1, const char* ch2)
{
  assert(ch1 && ch2);
  while (*ch1++ == *ch2++)
  {
  if (*ch2 == '\0')
  {
    return 0;
  }
  }
  ch1--;
  ch2--;
  return *ch1 - *ch2;
}
int main()
{
  char ch1[] = "asdfghjkq";
  char ch2[] = "asdfghjkl";
  int len1 = strcmp(ch1, ch2);
  int len2 = my_strcmp(ch1, ch2);
  if (len1 > 0 && len2 > 0)
  {
  printf("ch1>ch2");
  }
  else if (len1 == 0 && len2 == 0)
  {
  printf("ch1==ch2");
  }
  else
  {
  printf("ch1<ch2");
  }
  return 0;
}


4cebaac233b3433da32a72337a77fc60.png


strstr


strstr,字符串查找,可以在一个字符串里面找另一个字符串,在实现strstr的时候,我们要注意,查找的时候可能会出现"dddf"里面找"ddf",当是这样的时候,我们找到"dd",如果让本身的地址进行加加,就会使让我们对比下一个字符串的时候对比的是'd'和'f',这样就算我们这个字符串包括另一个字符串也会返回空指针。


#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strstr(const char* ch1, const char* ch2)
{
  assert(ch1 && ch2);
  const char* cp1 = ch1;
  const char* cp2 = ch2;
  const char* p = ch1;
  while (*p)
  {
  cp2 = ch2;
  cp1 = p;
  while (*cp1!='\0'&&*cp2!='\0'&& * cp1 == *cp2)
  {
    cp1++;
    cp2++;
  }
  if (*ch2 == '\0')
  {
    return (char*)p;
  }
  p++;
  }
  return NULL;
}
int main()
{
  char ch1[] = "asdfffghjsd";
  char ch2[] = "ffh";
  char* cp1 = strstr(ch1, ch2);
  char* cp2 = my_strstr(ch1, ch2);
  if (cp1 == NULL && cp2 == NULL)
  {
  printf("ch2不是ch1的字串");
  }
  else
  {
  printf("ch2是ch1的字串");
  }
  return 0;
}

6de278e6d6694ce5bb08e7e842b7e74b.png


strtok


strtok,是一个特殊的函数,它可以将字符串分割,按照我们给出的分隔符分隔,我们进行第一次传参的时候,把字符串传过去,然后开始找分隔符,找到分隔符用'\0'代替,当要进行第二次分割的的时候,传参传空指针即可,当传参传空指针的时候,我们的strtok函数会找到上一次标记的地址,然后向后进行切割,如果在我们的字符串中,没有我们的分隔符,就会返回一个空指针。


strerror


strerror是一个用来报告错误的函数,它可以将错误码转换成错误信息,其中我们的错误码会保存在一个叫做errno的函数中,它需要引头文件<errno.h>。


其他字符操作函数


除去我们上面的这些字符操作函数,还有很多,只是用处不是很大,下面对其做一些简单的介绍:


参数符合条件返回真


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 转换成小写字母

toupper 转换成大写字母

除了这些字符操作函数之外,还有一些,是在上面有些函数的基础上进行改造的,有strncpy、strncat、strncmp,这些函数都比原函数多了一个需要传的参数,这个参数限制我们要将多少个字符放到目的地或者进行比较,实现也和上面差不多,就不过多介绍了。除了我们这些能操作字符串的函数之外,还有一种函数,它比我们这种字符串的操作函数能应用的范围更加广泛一些,用于操作我们的内存。


内存操作函数


memcpy


memcpy,内存拷贝函数,它和strcpy的用处一样,传参是三个,第三个参数用于确定,我们要拷贝多少个字节到目的地,实现也与strlen大同小异。

#include<stdio.h>
#include<string.h>
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t byte)
{
  void* p = dest;
  assert(dest && src);
  while (byte--)
  {
  *(char*)dest = *(char*)src;
  dest = (char*)dest + 1;
  src = (char*)src + 1;
  }
  return p;
}
int main()
{
  int arr1[20] = { 0 };
  int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
  int i = 0;
  //memcpy(arr1, arr2, 20);
  my_memcpy(arr1, arr2, 20);
  for (; i < 5; i++)
  {
  printf("%d ", arr1[i]);
  }
  return 0;
}


7a399525ddec4b77923c464820b33738 (1).png


memmove


memmove对比上面的memcpy功能基本一致,但是memmove可以进行目的地和要拷贝的内容重叠在一起的时候,拷贝出来没有问题。


#include<stdio.h>
#include<string.h>
#include<assert.h>
void* my_memmove(void* dest, const void* src, size_t byte)
{
  void* p = dest;
  assert(dest && src);
  while (byte--)
  {
  *(char*)dest = *(char*)src;
  dest = (char*)dest + 1;
  src = (char*)src + 1;
  }
  return p;
}
int main()
{
  int arr1[20] = { 0 };
  int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
  int i = 0;
  my_memmove(arr1, arr2, 20);
  for (; i < 5; i++)
  {
  printf("%d ", arr1[i]);
  }
  return 0;
}


12c3b7f3f8814309a195c64f051d4445.png


memcmp

memcmp,和我们的字符串比较函数一样,都是比较大小用的,它比较的值有一个范围,和strncmp是一样的,实现也差不多,不进行过多讲解。


总结


对于这些我们来说,熟练使用这些函数,在某种情况下可以更快的去实现我们的想法,所以要多运用这些函数。


相关文章
|
12天前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
52 24
|
8天前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
46 16
|
7天前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
19 3
|
7天前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
11 2
|
11天前
|
存储 编译器 C语言
【C语言程序设计——函数】回文数判定(头歌实践教学平台习题)【合集】
算术运算于 C 语言仿若精密 “齿轮组”,驱动着数值处理流程。编写函数求区间[100,500]中所有的回文数,要求每行打印10个数。根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码。如果操作数是浮点数,在 C 语言中是不允许直接进行。的结果是 -1,因为 -7 除以 3 商为 -2,余数为 -1;注意:每一个数据输出格式为 printf("%4d", i);的结果是 1,因为 7 除以 -3 商为 -2,余数为 1。取余运算要求两个操作数必须是整数类型,包括。开始你的任务吧,祝你成功!
41 1
|
2月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
505 1
|
1月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
2月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
2月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
29 3
|
2月前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
61 1