【c语言】字符串函数和内存函数

简介: 本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。

前言

       在编程的过程中,我们经常要对字符串和内存进行各种各样的处理,c语言提供了一系列字符串函数和内存函数,便于我们对字符串或者内存空间进行操作。本篇文章我们就来学习其中的一些函数。


一、字符串函数

1.strlen的使用和模拟实现

       c语言中,strlen函数用于计算一个字符串的长度。它的原型如下:


size_t  strlen ( const char * str ) ;


这里需要注意的是:

1.字符串以\0为结束标志,这个函数返回值是字符串的长度,不包括\0。

2.传参时要传入字符、0串的首元素地址,指向的字符串要以\0为结尾。

3.函数返回值类型是size_t,是一个无符号整数

4.strlen函数使用需要引头文件string.h


接下来我们试着使用一下strlen函数:

#include <stdio.h>
#include<string.h>
 
int main()
{
    char str[] = "hello";
    int ret = strlen(str);
    printf("%d\n", ret);
    return 0;
}

运行结果:



可以看到,"hello"一共有五个字符,所以字符串的长度就是5。也就是说,这个函数的原理就是统计出\0之前的字符个数。这样,我们尝试模拟实现一下它:


方法1:计数器

size_t my_strlen(const char* str)
{
    size_t count = 0;//定义变量统计字符个数
    while (*str != '\0')
    {
        str++;
        count++;
    }
    return count;
}

这里我们定义了一个变量count,负责统计\0之前的字符个数。当指针走到\0处时,循环停止。


方法2:递归

size_t my_strlen(const char* str)
{
    size_t count = 0;//定义变量统计字符个数
    while (*str != '\0')
    {
        str++;
        count++;
    }
    return count;
}

方法3:指针减指针

size_t my_strlen(const char* str)
{
    char* p = str;
    while (*str++);//指针持续往后走,知道遇到\0为止
    return str - p - 1;//后置++使得指针多走了一步,多减一个1
}

这里先记录一下首元素地址,然后让str跑到字符串末尾,再减去首元素地址就得到两地址之间的元素个数,也就是字符串长度。


2.strcpy的使用和模拟实现

       strcpy这个函数的作用是将源字符串中的内容拷贝到目标字符串中。它的原型如下:


char* strcpy ( char* dest, const char* src ) ;


这里需要注意:

1.第一个参数是目标字符串的首地址,第二个参数是源字符串的首地址。

2.源字符串必须以\0结尾。

3.目标字符串的空间必须足够大,能够包含整个源字符串;目标字符串不能是常量字符串。

4.函数返回值是目标字符串的首元素地址。

5.源字符串中的\0也会一同拷贝。


我们尝试使用一下它:

#include <stdio.h>
#include <string.h>
 
int main()
{
    char str1[] = "xxxxxxxxxxxx";
    char str2[] = "hello";
    printf("%s\n", strcpy(str1, str2));
    return 0;
}

运行结果:



模拟实现:

char* my_strcpy(char* dest,const char* src)
{
    char* ret = dest;//记录首元素地址
    while (*dest++ = *src++);
    return ret;
}

这里我们首先记录了原字符串的首地址,然后循环赋值完成拷贝,最后返回首地址。


3.strcat的使用和模拟实现

       strcat函数的作用是将源字符串内容追加到目标字符串上。原型如下:


char * strcat ( char * dest, const char * src ) ;


要注意的是:

1.第一个参数是目标字符串的首地址,第二个参数是源字符串的首地址。

2.源字符串和目标字符串都必须以\0结尾。

3.目标字符串的空间必须足够大;目标字符串不能是常量字符串。

4.函数返回值是目标字符串的首地址。


接着我们使用一下这个函数:

#include <stdio.h>
#include <string.h>
 
int main()
{
    char str1[20] = "hello ";
    char str2[] = "world";
    printf("%s\n", strcat(str1, str2));
    return 0;
}

运行结果:



模拟实现:

char* my_strcat(char* dest, const char* src)
{
    char* ret = dest;//记录目标字符串首地址
    while (*dest)
    {
        dest++;
    }//使指针指向目标字符串末尾
    while (*dest++ = *src++);//循环追加,直到源字符串末尾
    return ret;
}

4.strcmp的使用和模拟实现

       strcmp函数是一个很重要的函数,它用于比较两个字符串的大小。字符串的比较在很多实例中会使用到,它的比较规则如下



在了解了比较规则之后,我们来看一下函数原型:


int strcmp ( const char* str1, const char* str2 ) ;


1.参数传入要比较的两个字符串的首地址。

2.两个字符串都要以\0结尾。

3.如果str1大于str2,函数返回正数,否则返回负数,相等则返回0。


我们写一个程序使用该函数,实现两个字符串比较:

#include <stdio.h>
#include <string.h>
 
int main()
{
    char str1[] = "abcd";
    char str2[] = "abdc";
    int flag = strcmp(str1, str2);
    if (flag > 0)
    {
        printf("str1大于str2\n");
    }
    else if (flag < 0)
    {
        printf("str1小于str2\n");
    }
    else
    {
        printf("两者相等\n");
    }
    return 0;
}

运行结果:



接着,我们尝试模拟实strcmp:

int my_strcmp(const char* str1, const char* str2)
{
    while (*str1 == *str2)
    {
        if (*str1 == '\0')//处理出现空字符串或者字符比较到末尾的情况
        {
            return 0;
        }
        str1++;
        str2++;
    }
    return *str1 - *str2;
}

5.strstr的使用和模拟实现

       接下来,我们学习一下strstr函数。strstr函数的作用是判断一个字符串是否是另一个字符串的子字符串(是否为包含关系)


它的原型如下:


char * strstr ( const char * str1, const char * str2) ;


1.两个字符串必须要以\0结尾。

2.函数的返回值是str2在str1中第一次出现的位置,如果没找到,返回空指针。

我们尝试使用这个函数:

#include <stdio.h>
#include <string.h>
 
int main()
{
    char str1[] = "123456789";
    char str2[] = "3456";
    printf("%s\n", strstr(str1, str2));
    return 0;
}

运行结果:



可以看到,函数确实找到了第二个字符串在第一个字符串中的位置。接下来我们模拟实现一下它:

char* my_strstr(const char* str1, const char* str2)
{
    char* cur = str1;
    if (*str2 == '\0')//空字符串的情况
    {
        return str1;
    }
    while (*cur != '\0')
    {
        char* s1 = cur;
        char *s2 = str2;
        while (*s1 && *s2 && (*s1 - *s2) == '\0')//遍历相等的部分
        {
            s1++;
            s2++;
        }
        if (*s2 == '\0')//遍历结束后如果str2已结束,说明是子串
        {
            return cur;
        }
        cur++;
    }
    return NULL;
}

6.strncmp、strncpy、strncat的使用

       这三个函数是在原来功能的基础之上,限定了比较/拷贝/追加的字符个数。函数的参数部分多了一个num,表示限定的字符个数。举个例子:

#include <stdio.h>
#include <string.h>
 
int main()
{
    char str1[] = "xxxxxxxxx";
    char str2[] = "hehe";
    strncpy(str1, str2, 2);
    printf("%s\n", str1);
    char str3[20] = "hello ";
    char str4[] = "world";
    strncat(str3, str4, 3);
    printf("%s\n", str3);
    return 0;
}

运行结果:



可以看到,程序根据我们限定的字符个数完成了拷贝和追加。


7.strtok的使用

       strtok函数也叫做字符串分割函数。顾名思义,它的作用就是根据给出的特定字符来分割字符串。下面是它的原型:


char* strtok ( char* str, const char* sep ) ;


这里需要注意以下几点:

1.第一个参数是要被分割的字符串,第二个参数是由分隔符组成的字符串。

2.strtok函数会在str中寻找限定的分隔符,遇到分隔符就会在字符串中将其变为\0,完成分割。函数返回上一次分割后\0的下一个字符的地址;如果是第一次分割,则返回字符串首地址。

3.对一个字符串进行多次分割时,需要多次调用该函数:第一次调用时str传入要被分割的字符串,函数会使用静态变量保存被分割的位置,之后调用时str要传入NULL,函数就会从保存的位置开始进行分割。

4.当字符串按照分隔符完成了所有分割之后,就会返回NULL。

我们写代码体现一下它的使用效果:

#include <stdio.h>
#include <string.h>
 
int main()
{
    char str[] = "123.12.14.1.50";
    char* p = NULL;
    printf("%s\n", str);
    for (p = strtok(str, "."); p != NULL; p = strtok(NULL, "."))
    {
        printf("%s\n", p);
    }
    return 0;
}

运行结果:



可以看到字符串成功被分割成几部分。不难发现,strtok函数可以和for循环结合使用,达到分割字符串的效果


二、内存函数

       在学习了这些字符串函数之后,我们可以发现,它们虽然实用,但是却只能对字符串进行操作。那么是否有一些函数可以让我们对任何类型也进行相似操作呢?它就是内存函数。这些内存函数的使用也需要引头文件string.h


1.memcpy的使用和模拟实现

       memcpy函数也叫做内存拷贝函数,它能够将特定字节的内存值拷贝到其他内存位置中。它的原型:


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


其中的前两个参数分别表示目标空间和源空间的首地址,第三个参数表示要拷贝的字节数。函数的返回值是目标空间的首地址。我们来使用一下它:

#include <stdio.h>
#include <string.h>
 
int main()
{
    int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr2[10] = { 0 };
    memcpy(arr2, arr1, 5 * sizeof(int));
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", arr2[i]);
    }
    return 0;
}

运行结果:



可以看到,有五个元素的内容被拷贝到arr2当中。


接下来我们尝试模拟实现

void* my_memcpy(void* dest, const void* src, size_t num)
{
    void* ret = dest;
    while (num--)
    {
        *(char*)dest = *(char*)src;
        (char*)dest += 1;//这里由于强制类型转换是临时的,不能直接写++
        (char*)src += 1;
    }
    return ret;
}

可以看出,我们这里使用了泛型编程的思想,将void*指针强转为char*类型,然后根据给定的字节数一个字节一个字节赋值。


2.memmove的使用和模拟实现

       对于刚才的memcpy函数,如果想要拷贝的两个内存空间之间出现重叠情况,那么函数就无法完成正常的拷贝。而memmove函数就弥补了这个缺陷。原型如下:


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


它的参数含义和返回值和memcpy是完全相同的。我们尝试使用它来拷贝重叠的内存:

#include <stdio.h>
#include <string.h>
 
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    memmove(arr, arr + 2, 5 * sizeof(int));
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

运行结果:



可以看到,它成功地完成了重叠内存的拷贝。那我们是否可以模拟实现这个函数呢?


我们首先来思考一下,现在有一个数组arr,它的内容如下:



现在我们想要将1,2,3,4,5这五个元素拷贝到3,4,5,6,7的位置上,能否完成呢?


将1赋值到3的位置上,将2赋值到4的位置上,到赋值3的时候,就会发现,此时的3已经被覆盖成1了,这样下去,最终结果就会变成1,2,1,2,1,2,1,8,9,10。


聪明的你肯定会想到,既然从前往后不行,那么我们就从后往前进行拷贝:先将5赋值到7的位置,再将4赋值到6的位置,以此类推...你就会发现结果就是1,2,1,2,3,4,5,8,9,10,说明成功了。


难道所有的情况都可以通过从后往前拷贝的方法赋值成功吗?当我们反过来将3,4,5,6,7拷贝到1,2,3,4,5的位置上,我们就发现从后往前的拷贝就不行了,就要用从前往后拷贝的方式。


所以我们就可以得出以下结论:在有重叠空间的情况下,如果dest的地址小于src,那么就从前向后拷贝;如果dest的地址大于src,就从后向前拷贝。没有重叠空间时怎样都行。图示:



这里由于右边非重叠空间的部分和中间部分是连续的,所以我们使用从后向前拷贝的方法会更加方便编写代码


代码如下:

void* my_memmove(void* dest, const void* src, size_t num)
{
    void* ret = dest;
    if (dest < src)//从前向后
    {
        while (num--)
        {
            *(char*)dest = *(char*)src;
            (char*)dest += 1;
            (char*)src += 1;
        }
    }
    else//从后向前
    {
        while (num--)
        {
            *((char*)dest + num) = *((char*)src + num);
        }
    }
    return ret;
}

3.memset的使用

       memset函数也叫做内存设置函数,它可以对特定区域的内存全部赋成你想要的值。它的原型如下:


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


它的第一个参数是要设置的内存空间的首元素地址,第二个参数是要赋的值,第三个参数是内存空间的大小(字节)。

它的返回值是内存空间的首元素地址。


我们来使用一下这个函数:

#include <stdio.h>
#include <string.h>
 
int main()
{
    char str[] = "hello world";
    memset(str, 'x', 6);
    printf(str);
    return 0;
}

运行结果:



可以看到,前六个字符都被赋值成了'x'。


总结

       本篇文章我们学习了字符串函数和内存函数的相关知识,它们在我们的编程当中十分常见和实用。同时,我们也学会了如何思考问题,解决问题。之后博主会更新数据存储方式相关的内容。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤

相关文章
|
2月前
|
安全 C语言 C++
比较C++的内存分配与管理方式new/delete与C语言中的malloc/realloc/calloc/free。
在实用性方面,C++的内存管理方式提供了面向对象的特性,它是处理构造和析构、需要类型安全和异常处理的首选方案。而C语言的内存管理函数适用于简单的内存分配,例如分配原始内存块或复杂性较低的数据结构,没有构造和析构的要求。当从C迁移到C++,或在C++中使用C代码时,了解两种内存管理方式的差异非常重要。
122 26
|
2月前
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
235 15
|
7月前
|
人工智能 Java 程序员
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
175 1
一文彻底搞清楚C语言的函数
|
8月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
198 3
|
8月前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
167 2
|
10月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
245 1
|
程序员 C语言 C++
【C语言基础】:动态内存管理(含经典笔试题分析)-2
【C语言基础】:动态内存管理(含经典笔试题分析)
|
程序员 编译器 C语言
【C语言基础】:动态内存管理(含经典笔试题分析)-1
【C语言基础】:动态内存管理(含经典笔试题分析)
|
Java 数据库连接 C语言
C语言进阶教程(内存分配常见问题分析)
C语言进阶教程(内存分配常见问题分析)
127 0