笔记20-1(C语言进阶 字符串函数介绍)

笔记20-1(C语言进阶 字符串函数介绍),第1张

目录

注:

 长度受限制的字符串函数

strlen

工作原理

实例

模拟实现strlen函数

strcpy

 工作原理

解析工作原理

strcat - 字符串追加/连接

工作原理

实例

模拟实现

注意

strcmp - 字符串比较函数

工作原理

实例

模拟实现

优化my_strcmp函数

长度受限制的字符串函数

strncpy

实例

strncat

实例

strncmp

实例

strstr - 寻找字符串的子串

实例

模拟实现

strtok - 切割字符串

实例

strerror

实例

 存在一个与strerror函数相关的函数 - perror函数

实例


注:

 本笔记参考B站up鹏哥C语言的视频


 长度受限制的字符串函数 strlen

工作原理
  1. 字符串 ''''无符号的'''' 作为结束标志,strlen函数返回的是字符串中 (unsigned int) 前面出现的字符串个数(不包含   )。
  2. 参数指向的字符串必须要以 分析:这里是 结束。
  3. 注意函数的返回值是size_t,是无符号整型相减''a''

解释第3点

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

打印结果为:>'b'

'c''随机值' ,虽然 3 - 6 = -3 ,但这里的 -3 的补码会直接被解析成原码,故-3在被解析后就是一个很大的正数。

实例
#include
#include

int main()
{
	char arr[] = "abc";
	int len = strlen(arr);

	printf("%d\n", len);

	return 0;
}

打印结果是 3 。此时没有计算 'source'

而如果将数组arr改为:char arr[] = { 拷贝, destination, ( };  此时数组内没有放入 source,打印结果就是)

这里之所以是74,是因为strlen函数在内存中持续往后查找,在某个位置找到了 '('

模拟实现strlen函数

提供一种写法

#include
#include

int my_strlen(const char* str)//加上const,使代码更加“健壮”
{
	assert(str != NULL);//记得断言
	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;
}

strcpy

 工作原理

把指针变量source所指向的空间内的数据)到指针变量'('所指向的空间内。

  1. 源字符串source)
  2. 目标空间必须可变。
  3. 必须以 int main() { char arr[20] = { 0 }; arr = "Hello"; return 0; } 结束。
  4. 会将源字符串'''''''
  5. 目标空间必须足够大,以确保能存放源字符串。
  6. '''''''(arr_1)
  7. 目标空间必须可变。
  8. 中的 常量字符串无法修改 拷贝到目标空间。
  9. 目标空间必须足够大,以确保能存放源字符串source追加destination
  10. (

存放字符串的错误示范

source

这是行不通的,因为 arr 是数组首元素的地址。

解析工作原理
  • 源字符串必须以 ) 结束。
  • 会将源字符串中的 ' 结束。' 拷贝到目标空间。

使用strcpy的正确示范

#include
#include

int main()
{
	char arr[20] = "#########";
	strcpy(arr, "Hello");//字符串"Hello"在被使用时,使用的是"H"的地址
	printf("%s\n", arr);
	return 0;
}

打印结果:

执行调试,看见

这里 目标空间必须足够大,能容纳下源字符串( 也被带过去了。

---

如果被拷贝的字符串没有 source 结尾,如:

#include
#include

int main()
{
	char arr_1[20] = "#########";
	char arr_2[] = { 'a', 'b', 'c' };

	strcpy(arr_1, arr_2);

	printf("%s\n", arr_1);
	return 0;
}

执行代码,发现

 程序挂了。

分析:数组arr_2最后没有 )的内容。 结尾,strcpy函数会从数组arr_2的首地址出发,寻找 目标空间必须可以修改。 ,在找到 字符串无法自己追加自己。 之前,无法确认strcpy函数在内存中找到了什么。

---

    ' '

还有一种情况

#include
#include

int main()
{
	char arr_1[5] = "#####";
	char* p = "Hello World";
	strcpy(arr_1, p);
	printf("%s\n", arr_1);
	return 0;
}

这种情况执行代码会出现

这是因为目标数组' '空间太小,强行将字符串拷贝后发生溢出,但是程序崩溃了 - 数组arr_1被撑爆了。

---

    "Hello ' '##########"
#include
#include

int main()
{
	char* str = "xxxxxxxxxxxxxxxx";
	char* p = "Hello World";
	strcpy(str, p);
	printf("%s\n", str);
	return 0;
}

执行代码,发现:

程序崩溃了。

分析:目标空间必须可以修改。指针变量str内存放的是一个常量字符串的地址,' '

strcat - 字符串追加/连接

工作原理

把指针变量' '所指向的空间内的数据中的一个#号会被覆盖掉到指针变量'同时,这也说明'所指向的空间内。

  1. 源字符串指针变量source所指向的空间内必须存在 必须以 ' 'void my_strcat(char* dest,const char* src)
  2. src' 'dest
  3. src
  4. src
实例
#include
#include

int main()
{
	char arr_1[20] = "Hello ";
	char arr_2[] = "World";
	strcat(arr_1, arr_2);//字符串追加/连接
	printf("%s\n", arr_1);
	return 0;
}

打印结果为:

运行调试,发现

注意,原本的数组arr_1内存放的那个 'W'dest没有了。

分析:

一种测试方法(测试是否会追加arr_2中的 ' '(arr_1)

将数组arr_1改为 char arr_1[20] = ' ';  注意:arr_1内原本就有一个 (arr_2)' ',这里多放了一个 destion库函数的实现参考进去,并且在后面追加了#号,如果strcat函数执行,如果拿取了arr_2的 int main() { char arr[20] = "abcd"; strcat(arr, arr); printf("%s\n", arr); return 0; }'a' 'b' 'c' 'd' ,则arr_1''a''

现在开始调试,发现:

说明数组arr_2的 '''a''' 也会被抓取,(ps:笔者在VS2022上用64位环境测试该库函数时,代码没有挂掉。而如果测试自己编写的my_strcat函数,发现代码挂掉了,推测这里是编译器本身导致的问题。)'a''a'(如果使用my_strcat函数,这两个寄存器是不会被调用的。)source'

  • 标准规定:
  • '。

    模拟实现

    分析:

    参考库函数写法

    注意

    if (指向的空间不需要改变,加入const 。

    注意:

    字符数组被初始化后,如果存在没有被初始化的部分,这部分会自动被放入 "OBC""ABCDEF"

    对于指针变量)return,存在

    要把*s1 - *s2指向的 库函数的实现参考 追加到

  • strcpy
  • 指向的
  • strcat
  • strcmp
  • 的位置上:

    1. 找到目标字符串
    2. strncpy
    3. 中的
    4. strncat
    5. strncmp
    6. 源数据source追加过去,包含numdestination
    7. 注意返回类型,返回目标空间((存在字符限制 - num限制了可以拷贝的字符的个数))的起始地址。

    #include
    #include
    char* my_strcat(char* dest,const char* src)
    {
    	char* ret = dest;
    	assert(dest && src);
    	//找到目标字符串(arr_1)中的'\0'
    	while (*dest)
    	{
    		dest++;
    	}
    	//while循环停下时,dest指向 '\0',即0
    
    	//源数据(arr_2)追加过去,包含'\0'
    	while (*dest++ = *src++)
    	{
    		;
    	}
    	//当*++dest = *src++拿取'\0'时,表达式的结果就是'\0'
    
    	return ret;//返回目标空间的起始地址
    }
    
    int main()
    {
    	char arr_1[20] = "Hello \0##########";
    	char arr_2[] = "World";
    	printf("%s\n", my_strcat(arr_1, arr_2));
            //由于返回值(char*),所以可以直接打印
    
    	return 0;
    }

    三个参数

    注意

    对strcat函数而言,无法将目标字符串拷贝到其自身后面,如:

    destination

    运行代码,发现

    代码挂掉了。

    原因:数组arr内原本被放入的是 sourcenum ,在追加字符串时,字符source会把原本的 num 覆盖掉,导致最后无法找到 '注意' ,又开始拿取 字符6,陷入死循环。

    '库函数实现参考'

    以下为一些推测

    开始调试,打开反汇编和寄存器

    当执行到这一步时,在寄存器rdx和rcx内部分别存入了一个数组arr首元素的地址

    接下来继续执行反汇编

    此时再看寄存器

    此时寄存器RCX存储的地址变成了0000000000000004,而寄存器RDX存储的地址与原本相比,向后了4个字节,打开内存

    ASCII码的61对应的数值就是字符source,而原本字符destination所在的位置应该在往前4个字节,

    这说明此时已经复制成功。再看寄存器

    发现寄存器R11存储了数组arr首元素的地址,结合源代码,也就是说,R11应该对应返回的地址。

    而此时寄存器R9和R10也被调用了source

    查询资料分析寄存器R10被用作数据存储,在使用之前会保存原值。

    ------

    猜测:在函数strcat被调用时,编译器会先在寄存器R10内保存数组arr内的数据,在数组arr被改变时,num实际是调用了寄存器R10内的数据,被改变的数组本身则在寄存器R11内被存储起来,这样就达成了函数执行的目的。

    strcmp - 字符串比较函数

    工作原理

    字符串比较和长度无关,两个字符串从首地址开始比较:

    ▲如果字符相同,比较下一位;

    ▲如果字符不同,则认为该位字符的ASCII值大的字符串更大。

    而如果两个字符串完全相同,则当比较完 source 后,函数结束,认为两个字符串相等。

      num

                    ○第一个字符串大于第二个字符串,则返回大于0的数字;
                    ○第一个字符串等于第二个字符串,则返回0;
                    ○第一个字符串小于第二个字符串,则返回小于0的数字。

    库函数实现参考

    int main()
    {
    	char* p = "OBC";
    	char* q = "ABCDEF";
    	if (p > q)
    		printf(">\n");
    	else
    		printf("<=\n");
    	return 0;
    }
    

    这种写法是不行的。p和q分别是指向两块不同空间的指针:

     调试可以看出地址的不同。

    上述代码是在比较两个地址的大小,并不是比较字符串的大小。

    同理,str1str2 >

  • 比较字符,发现字符不同;
  • 'num' 这种写法也是在比较字符串首地址大小,也不行。

    实例
    #include
    #include
    int main()
    {
    	int ret = strcmp("abbb", "abc");
    	printf("%d\n", ret);
    
    	return 0;
    }

    程序执行结果:-1


    #include
    #include
    int main()
    {
    	char* p = "abcdef";
    	char* q = "abbb";
    	int ret = strcmp(p, q);
    	if (ret > 0)
    	{
    		printf("p > q\n");
    	}
    	else if (ret < 0)
    	{
    		printf("p < q\n");
    	}
    	else
    	{
    		printf("p == q\n");
    	}
    	return 0;
    }

    程序执行结果:

    模拟实现
    #include
    #include
    int my_strcmp(const char* s1, const char* s2)
    {
    	assert(s1 && s2);
    	while (*s1 == *s2)
    	{
    		if (*s1 == '0')
    		{
    			return 0;
    		}
    		s1++;
    		s2++;
    	}
    	if (*s1 > *s2)
    	{
    		return 1;
    	}
    	else
    	{
    		return -1;
    	}
    }
    
    int main()
    {
    	char* p = "abcdef";
    	char* q = "abbb";
    	int ret = my_strcmp(p, q);
    	if (ret > 0)
    	{
    		printf("p > q\n");
    	}
    	else if (ret < 0)
    	{
    		printf("p < q\n");
    	}
    	else
    	{
    		printf("p == q\n");
    	}
    	return 0;
    }

    分析:

    当函数内部传入数据时,指针s1和s2指向首字符的地址。

     首先首字符'a'进行比较,发现相同,向后寻找;字符'b'也相同;再找,找到字符'b'和'c'不同,开始比较。

    优化my_strcmp函数

    my_strcmp函数中

    if (*s1 > *s2)
    {
    	return 1;
    }
    else
    {
    	return -1;
    }

    可以被改为 num str2;

    str1

    长度受限制的字符串函数

    长度不受限制的字符串函数

      str1str2str2

    与之对应,存在长度受限制的字符串函数

      str1conststr1
    strncpy

    拷贝

  • 比较字符'b'和'c',又发现不相等,继续向后寻找;
  • 内开始('c')的字符到'c'内。str1

    str2

    • 目标字符串(
    • 继续向后比对……;
    • )的首元素地址;
    • 源字符串(str2)的首元素地址;
    • 要拷贝的字符数目(''a'和'b'')。

    这个函数的好处是可以控制拷贝字符的个数,可以使字符数组不容易被撑爆,不容易出现警告。

    当源字符串(str1)长度不满足字符数目(str1)的要求时,会自动补上 'b'

    实例
    #include
    #include
    int main()
    {
    	char arr1[20] = "abcdef";
    	char arr2[] = "qwer";
    	strncpy(arr1, arr2, 2);
    	printf("%s\n", arr1);
    	return 0;
    }

    打印结果:

    'b'

    如果strncpy函数内部变成 strncpy(arr1, arr2, 'b'); 的形式,开始调试,发现:

    这里确实拷贝了6个字符,最后两个是 'b'

    'c'

    strncat

    追加字符串,从源字符串(str1)的第一个字符开始,追加到目标字符串( )的末尾。

    如果源字符串(-)的长度( )少于要求,只追加到源字符串(回正) '\0' 的位置。

    实例
    #include
    #include
    int main()
    {
    	char arr1[20] = "Hello ";
    	char arr2[] = "World";
    	strncat(arr1, arr2, 3);
    	printf("%s\n", arr1);
    	return 0;
    }

    打印结果:

    同时,如果把长度((即原本开始匹配位置的下一个位置))改为6,也可以正常打印:

    str2

    strncmp

    从指针str2str1的指向字符串的首字符的地址开始比较。存在三种情况使函数结束:

      s1
    • 找到终止空字符 s2
    • 被比较的字符数目和指定的比对数目(str1)匹配。
    实例
    #include
    #include
    int main()
    {
    	char* p = "abcdef";
    	char* q = "abcqwert";
    	int ret = strncmp(p, q, 3);
    
    	printf("%d\n", ret);
    
    	return 0;
    }

    打印结果:

    而如果把比对数目(cp)改为4,打印结果:

    strstr - 寻找字符串的子串

    • 如果库函数实现参考
    • sep参数是个字符串,定义了用作分隔符的字符集合;
    • 的子串,则返回
    • 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中的一个或者多个分隔符分割的标记;
    • 中第一次出现子串'char tmp[20] = { 0 }; strcpy(tmp, arr); char* ret = strtok(tmp, p);'的地址;
    • 如果其中arr就是原本的字符串。不是三种情况的子串,则返回一个空指针。
    实例
    #include
    #include
    int main()
    {
    	char arr1[] = "abcdefabcdef";
    	char arr2[] = "bcd";
    
    	//在arr1中查找是否包含arr2数组
    	char* ret = strstr(arr1, arr2);
    
    	if (ret == NULL)
    		printf("没找到\n");
    	else
    		printf("找到了:%s\n", ret);
    
    	return 0;
    }

    打印结果:

    模拟实现
    #include
    #include
    char* my_strstr(const char* str1, const char* str2)
    {
    	assert(str1 && str2);
    	//存储起始位置
    	const char* s1 = NULL;
    	const char* s2 = NULL;
    
    	char* cp = str1;//指针cp一开始是维护str1的
    
    #include
    char* my_strstr(const char* str1, const char* str2)
    {
    	assert(str1 && str2);
    	//存储起始位置
    	const char* s1 = NULL;
    	const char* s2 = NULL;
    
    	const char* cp = str1;//指针cp一开始是维护str1的
    
    	//匹配
    	if (*str2 == '\0')
    	{
    		return (char*)str1;
    	}
    	while (*cp)
    	{
    		//赋值与回正
    		s1 = cp;
    		s2 = str2;
    
    		while (*s1 && *s2 && (*s1 == *s2))
    		{
    			s1++;
    			s2++;
    		}
    		if (*s2 == '\0')
    		{
    			return cp;
    		}
    		cp++;//cp向后找一个元素
    	}
    
    	return NULL;
    }
    int main()
    {
    	char arr1[] = "abbbcde";
    	char arr2[] = "bbc";
    
    	//在arr1中查找是否包含arr2数组
    	char* ret = my_strstr(arr1, arr2);
    
    	if (ret == NULL)
    		printf("没找到\n");
    	else
    		printf("找到了:%s\n", ret);
    
    	return 0;
    }
    

    分析:

    对于传来的两个地址,函数内部只进行比较,加上 不为 保护数据。

    在比较过程中,存在两种情况:

    情况一

    1. 比较字符'a'和字符'c',发现字符不相等,NULL继续寻找下一个元素;
    2. strtok函数将保存它在字符串中的位置
    3. 比较字符NULL,发现相等;
    4. 将在同一个字符串中被保存的位置开始NULL分别向后一个元素并进行比对,发现相等;
    5. 注意:strtok函数需要记录
    6. ' 的位置,即需要有记录功能(静态变量 - '找到 static ,说明查找完毕,说明找到子串。

    情况二


         1.字符 修饰局部变量)。不相等,"255往后寻找一个元素;
         2.@找到字符255,字符.和字符255相等,开始向后匹配,找到:

          3.发现字符-255"不相等,接下来"255会向前一个元素255.255-255",再进行一次比对(此时"255也要回正):

          4.循环步骤,最后发现255指向的字符串不是255指向的字符串。

    所以如果想要回正,就需要新的指针-255"。但是这样是不够的,因为这样只是解决了起始位置的问题,但是"255的回正并不一定是回到起始位置,所以还需要一个指针255

    故函数开始时,存在:

    255

    strtok - 切割字符串

      255"(int errno)
    • strtok函数找到str中的下一个标记,并将其用 全局的 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被 *** 作的字符串,所以在使用strtok函数切分字符串时,一般都是临时拷贝的内容,并且该内容可以被修改。)
  • 该函数直接拿取errno内的错误码,
  • 打印错误信息,不用通过错误码转换。
    1. strtok函数的第一个参数,函数找到str中的一个标记,;
    2. strtok函数的第一个参数,函数,查找下一个标记。
    3. 如果字符串中不存在更多的标记,则返回指针。

    实例

    借用cplusplus的例子

    #include 
    #include 
    
    int main ()
    {
      char str[] ="- This, a sample string.";
      char * pch;
      printf ("Splitting string \"%s\" into tokens:\n",str);
      pch = strtok (str," ,.-");
      while (pch != NULL)
      {
        printf ("%s\n",pch);
        pch = strtok (NULL, " ,.-");
      }
      return 0;
    }

    代码执行的结果:

    例子2

    #include
    #include
    
    int main()
    {
    	char arr[] = "255@255.255-255";
    	char* p = "@.-";
    	char tmp[20] = { 0 };
    	strcpy(tmp, arr);
    
    	char* ret = NULL;
    
    	for (ret = strtok(tmp, p);
    		ret != NULL; 
    		ret = strtok(NULL, p))
    	{
    		printf("%s\n", ret);
    	}
    	return 0;
    }

    打印结果:

    分析:

    数组tmp内原本存放:

    1. 第一次使用strtok函数,数组tmp内发生改变:
    2. 第二次使用strtok函数:
    3. 第三次使用strtok函数:

    strerror

    使用库函数的时候,有可能出现调用失败
    调用失败时,都会设置错误码
    一般地,错误码都会被放入变量 errno
    errno中一般存储着整数,需要使用函数strerror进行翻译才能变成可以查看的信息

    注意:errno是一个错误码。想要使用,需要引用头文件

    实例

    例1

    #include
    #include
    #include
    
    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));
    	return 0;
    }

    打印结果:

    例2

    #include
    #include
    #include
    
    int main()
    {
    	FILE* pf = fopen("test.txt", "r");
    	//打开文件test.txt 以只读的形式打开
    
    	if (pf == NULL)//如果文件存在,返回一个有效指针;如果文件不存在,pf就是有一个空指针
    	{
    		printf("%s\n", strerror(errno));
                    return 1;
    	}
            //读文件
    	//...
    	fclose(pf);//关闭文件
    	pf = NULL;
    
    	return 0;
    }

    打印结果:

    如果对应文件夹下没有文件test.txt,则

     存在一个与strerror函数相关的函数 - perror函数

      与strerror函数相比,可以加入自定义信息。

      实例
      #include
      
      int main()
      {
      	FILE* pf = fopen("test.txt", "r");
      	//打开文件test.txt 以只读的形式打开
      
      	if (pf == NULL)//如果文件不存在,pf就是有一个空指针
      	{
      		perror("fopen");
      		return 1;
      	}
      	//...
      	fclose(pf);//关闭文件
      	pf = NULL;
      
      	return 0;
      }
      

      打印结果:

      欢迎分享,转载请注明来源:内存溢出

      原文地址:https://54852.com/langs/875945.html

      (0)
      打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
      上一篇 2022-05-13
      下一篇2022-05-13

      发表评论

      登录后才能评论

      评论列表(0条)

        保存