C语言进阶之字符串函数和内存函数的介绍及部分函数的模拟实现

简介: C 字符串的长度由终止空字符确定:C 字符串的长度与字符串开头和终止空字符之间的字符数一样长(不包括终止空字符本身)。

3830e1061ba849bd917d914c90b14c52.png


1.字符串函数介绍


1.1 strlen


头文件<string.h>


获取字符串长度


返回 C 字符串 str 的长度。


C 字符串的长度由终止空字符确定:C 字符串的长度与字符串开头和终止空字符之间的字符数一样长(不包括终止空字符本身)。


这不应与保存字符串的数组的大小混淆。例如:


int main()
{
  char mystr[100] = "test string";
  printf("%d", strlen(mystr));
  return 0;
}

202b15ad4d914463a13575f6dd2a0305.png


定义一个大小为 100 个字符的字符数组,但初始化 mystr 时使用的 C 字符串的长度仅为 11 个字符。因此,当 sizeof(mystr) 的计算结果为 100 时,strlen(mystr) 返回 11。


库中的声明如下:


_Check_return_
size_t __cdecl strlen(
    _In_z_ char const* _Str
    );


1.字符串已经 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。

2.参数指向的字符串必须要以 ‘\0’ 结束。

3.注意函数的返回值为size_t,是无符号的( 易错 )


1.2 strcpy


头文件<string.h>


复制字符串


将源指向的 C 字符串复制到目标指向的数组中,包括终止的 null 字符(并在该点停止)。


为避免溢出,目标指向的数组的大小应足够长,以包含与源相同的 C 字符串(包括终止空字符),并且不应在内存中与源重叠。


int main()
{
  char str1[] = "Sample string";
  char str2[40];
  char str3[40];
  strcpy(str2, str1);
  strcpy(str3, "copy successful");
  printf("str1: %s\nstr2: %s\nstr3: %s\n", str1, str2, str3);
  return 0;
}

ba630e77b17a438997c00ee55aecaeab.png


库中声明如下:


__DEFINE_CPP_OVERLOAD_STANDARD_FUNC_0_1(
    char*, __RETURN_POLICY_DST, __EMPTY_DECLSPEC, strcpy,
    _Out_writes_z_(_String_length_(_Source) + 1), char,        _Destination,
    _In_z_                                        char const*, _Source
    )


  1. 源字符串必须以 ‘\0’ 结束。
  2. 会将源字符串中的 ‘\0’ 拷贝到目标空间。
  3. 目标空间必须足够大,以确保能存放源字符串。
  4. 目标空间必须可变。


1.3 strcat


头文件<string.h>


追加字符串


将源字符串的副本追加到目标字符串。目标中的终止空字符被源的第一个字符覆盖,并且在目标中由两者串联形成的新字符串的末尾包含一个空字符。


目标字符串和源字符串不得重叠。


int main()
{
  char str[80];
  strcpy(str, "these ");
  strcat(str, "strings ");
  strcat(str, "are ");
  strcat(str, "concatenated.");
  puts(str);
  return 0;
}


4c1d400420024444ac99ab6978b4ee29.png


库中声明如下:


__DEFINE_CPP_OVERLOAD_STANDARD_FUNC_0_1(
        char*, __RETURN_POLICY_DST, __EMPTY_DECLSPEC, strcat,
        _Inout_updates_z_(_String_length_(_Destination) + _String_length_(_Source) + 1), char,        _Destination,
        _In_z_                                                                           char const*, _Source
        )


  1. 源字符串必须以 ‘\0’ 结束。
  2. 目标空间必须有足够的大,能容纳下源字符串的内容。
  3. 目标空间必须可修改。


1.4 strcmp


头文件<string.h>


比较两个字符串


将 C 字符串 str1 与 C 字符串 str2 进行比较。


此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续以下对,直到字符不同或达到终止空字符。


此函数执行字符的二进制比较。


返回一个整数值,该值指示字符串之间的关系:


ed1899e3e34240958e84b587517b42f0.png


int main()
{
    char key[] = "apple";
    char buffer[80];
    do {
        printf("Guess my favorite fruit? ");
        fflush(stdout);
        scanf("%79s", buffer);
    } while (strcmp(key, buffer) != 0);
    puts("Correct answer!");
    return 0;
}


b5abdb2f8c7249a78f44d9da555cb403.png


库中声明如下:


_Check_return_
int __cdecl strcmp(
    _In_z_ char const* _Str1,
    _In_z_ char const* _Str2
    );


  1. 第一个字符串大于第二个字符串,则返回大于0的数字(vs中为固定值1)
  2. 第一个字符串等于第二个字符串,则返回0
  3. 第一个字符串小于第二个字符串,则返回小于0的数字(vs中为固定值-1)


1.5 strncpy


头文件<string.h>


从字符串中复制字符


将源的第一个字符数复制到目标。如果在复制 num 个字符之前找到源 C 字符串的末尾(由 null 字符表示),则目标将填充零,直到总共写入 num 个字符为止。


如果源长度超过 num,则不会在目标末尾隐式附加空字符。因此,在这种情况下,不应将目标视为以空结尾的 C 字符串(这样读取它会溢出)。


目标字符串和源字符串不得重叠


int main()
{
  char str1[] = "To be or not to be";
  char str2[40];
  char str3[40];
  strncpy(str2, str1, sizeof(str2));
  strncpy(str3, str2, 5);
  str3[5] = '\0'; 
  puts(str1);
  puts(str2);
  puts(str3);
  return 0;
}

b0925f57e208471dab7879cc66848ac1.png


库中声明如下:


__DEFINE_CPP_OVERLOAD_STANDARD_NFUNC_0_2_EX(
    char*, __RETURN_POLICY_DST, _ACRTIMP, strncpy, strncpy_s,
    _Out_writes_z_(_Size)               char,
    _Out_writes_(_Count) _Post_maybez_, char,        _Destination,
    _In_reads_or_z_(_Count)             char const*, _Source,
    _In_                                size_t,      _Count
    )


1.拷贝num个字符从源字符串到目标空间。

2.如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。


1.6 strncat


头文件<string.h>


从字符串追加字符


将源的第一个数字字符追加到目标,外加一个终止空字符。


如果源中 C 字符串的长度小于 num,则仅复制终止空字符之前的内容。


int main()
{
  char str1[20];
  char str2[20];
  strcpy(str1, "To be ");
  strcpy(str2, "or not to be");
  strncat(str1, str2, 6);
  puts(str1);
  return 0;
}


c827e64070a541c6b09c486b9c3014f3.png


库中声明如下:


__DEFINE_CPP_OVERLOAD_STANDARD_NFUNC_0_2_EX(
    char*, __RETURN_POLICY_DST, _ACRTIMP, strncat, strncat_s,
    _Inout_updates_z_(_Size)   char,
    _Inout_updates_z_(_Count), char,        _Destination,
    _In_reads_or_z_(_Count)    char const*, _Source,
    _In_                       size_t,      _Count
    )


1.7 strncpy


头文件<string.h>


从字符串中复制字符


将源的第一个字符数复制到目标。如果在复制 num 个字符之前找到源 C 字符串的末尾(由 null 字符表示),则目标将填充零,直到总共写入 num 个字符为止。


如果源长度超过 num,则不会在目标末尾隐式附加空字符。因此,在这种情况下,不应将目标视为以空结尾的 C 字符串(这样读取它会溢出)。


目标字符串和源字符串不得重叠


int main()
{
  char str1[] = "To be or not to be";
  char str2[40];
  char str3[40];
  strncpy(str2, str1, sizeof(str2));
  strncpy(str3, str2, 5);
  str3[5] = '\0'; 
  puts(str1);
  puts(str2);
  puts(str3);
  return 0;
}


fa161e6eee2f4e139c2b4b8c374f0ec8.png


库中声明如下:


__DEFINE_CPP_OVERLOAD_STANDARD_NFUNC_0_2_EX(
    char*, __RETURN_POLICY_DST, _ACRTIMP, strncpy, strncpy_s,
    _Out_writes_z_(_Size)               char,
    _Out_writes_(_Count) _Post_maybez_, char,        _Destination,
    _In_reads_or_z_(_Count)             char const*, _Source,
    _In_                                size_t,      _Count
    )


1.8 strstr


头文件<string.h>


查找子字符串


返回指向 str2 中第一次出现的 str1 的指针,如果 str2 不是 str1 的一部分,则返回一个空指针。


匹配过程不包括终止空字符,但它到此为止。


int main()
{
    char str[] = "This is a simple string";
    char* pch;
    pch = strstr(str, "simple");
    puts(pch);
    if (pch != NULL)
        strncpy(pch, "sample", 6);
    puts(str);
    return 0;
}


7c4b86c9f4f745bc9c5fd62b2a3fe326.png


库中声明如下:


_VCRTIMP char _CONST_RETURN* __cdecl strstr(
    _In_z_ char const* _Str,
    _In_z_ char const* _SubStr
    );


1.9 strtok


头文件<string.h>


将字符串拆分为标记


对此函数的一系列调用将 str 拆分为标记,这些标记是由分隔符中的任何字符分隔的连续字符序列。


在第一次调用时,该函数需要一个 C 字符串作为 str 的参数,其第一个字符用作扫描令牌的起始位置。在后续调用中,该函数需要一个空指针,并使用最后一个令牌末尾之后的位置作为扫描的新起始位置。


为了确定标记的开头和结尾,该函数首先从起始位置扫描分隔符中未包含的第一个字符(该字符将成为标记的开头)。然后从令牌的开头开始扫描分隔符中包含的第一个字符,该字符将成为令牌的末尾。如果找到终止空字符,扫描也会停止。


令牌的此结尾将自动替换为空字符,并且令牌的开头由函数返回。


一旦在对 strtok 的调用中找到 str 的终止空字符,则对此函数的所有后续调用(以空指针作为第一个参数)都将返回空指针。


找到最后一个令牌的点由要在下一次调用中使用的函数在内部保留(不需要特定的库实现来避免数据争用)。


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;
}


39d9dfb1c231481091fbf139f76d4274.png


库中声明如下:


_Check_return_ _CRT_INSECURE_DEPRECATE(strtok_s)
_ACRTIMP char* __cdecl strtok(
    _Inout_opt_z_ char*       _String,
    _In_z_        char const* _Delimiter
    );


返回值


1.如果找到令牌,则指向令牌开头的指针。

2.否则为空指针。

3.当在正在扫描的字符串中到达字符串的末尾(即空字符)时,始终返回空指针。


1.10 strerror


头文件<string.h>


获取指向错误消息字符串的指针


解释 errnum 的值,生成一个字符串,其中包含描述错误条件的消息,就像由库的函数设置为 errno 一样。


返回的指针指向静态分配的字符串,程序不应修改该字符串。对此函数的进一步调用可能会覆盖其内容(不需要特定的库实现来避免数据争用)。


strerror 生成的错误字符串可能特定于每个系统和库实现。


int main()
{
    FILE* pFile;
    pFile = fopen("unexist.ent", "r");
    if (pFile == NULL)
        printf("Error opening file unexist.ent: %s\n", strerror(errno));
    return 0;
}


e26c23a41e2443b0b46c54ee6caf0362.png


我们可以给下面这段代码来看看vs的库中前20个错误信息提示是什么


int main()
{
    for (int i = 0; i < 20; i++)
    {
        printf("%d:%s\n", i,strerror(i));
    }
    return 0;
}


f032f646165c46d89f28d1abc818a322.png


1.11 字符分类函数


函数 如果他的参数符合下列条件就返回真
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 任何可打印字符,包括图形字符和空白字符



字符转换示例:


#include <stdio.h>
#include <ctype.h>
int main()
{
    int i = 0;
    char str[] = "Test String.\n";
    char c;
    while (str[i])
    {
        c = str[i];
        if (isupper(c))
            c = tolower(c);
        putchar(c);
        i++;
    }
    return 0;
}


537b6e3a9b1f4a379e06ca197f6ba3a7.png


2.内存函数


2.1 memcpy


头文件<string.h>


复制内存块


将字节数的值从源指向的位置直接复制到目标指向的内存块。


源指针和目标指针指向的对象的基础类型与此函数无关;结果是数据的二进制副本。


该函数不检查源中的任何终止空字符 - 它总是准确地复制字节数。


为避免溢出,目标和源参数指向的数组大小应至少为字节数,并且不应重叠


struct {
  char name[40];
  int age;
} person, person_copy;
int main()
{
  char myname[] = "Pierre de Fermat";
  /* using memcpy to copy string: */
  memcpy(person.name, myname, strlen(myname) + 1);
  person.age = 46;
  /* using memcpy to copy structure: */
  memcpy(&person_copy, &person, sizeof(person));
  printf("person_copy: %s, %d \n", person_copy.name, person_copy.age);
  return 0;
}


25c11bcb538846868c56dcafaf9f595d.png


库中声明如下:


void* __cdecl memcpy(
    _Out_writes_bytes_all_(_Size) void* _Dst,
    _In_reads_bytes_(_Size)       void const* _Src,
    _In_                          size_t      _Size
    );


1.函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。

2.这个函数在遇到 ‘\0’ 的时候并不会停下来。

3.如果source和destination有任何的重叠,复制的结果都是未定义的。


2.2 memmove


头文件<string.h>


移动内存块


将字节数的值从源指向的位置复制到目标指向的内存块。复制就像使用了中间缓冲区一样,允许目标和源重叠。


源指针和目标指针指向的对象的基础类型与此函数无关;结果是数据的二进制副本。


该函数不检查源中的任何终止空字符 - 它总是准确地复制字节数。


为避免溢出,目标参数和源参数指向的数组的大小应至少为字节数。


int main()
{
  char str[] = "memmove can be very useful......";
  memmove(str + 20, str + 15, 11);
  puts(str);
  return 0;
}


86f9cf159695470d95a8821f22310d9f.png


库中声明如下:


_VCRTIMP void* __cdecl memmove(
    _Out_writes_bytes_all_opt_(_Size) void*       _Dst,
    _In_reads_bytes_opt_(_Size)       void const* _Src,
    _In_                              size_t      _Size
    );


1.和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。

2.如果源空间和目标空间出现重叠,就得使用memmove函数处理。


2.3 memcmp


头文件<string.h>


比较两个内存块


将 ptr1 指向的内存块的前 num 字节数与 ptr2 指向的第一个字节数进行比较,如果它们都匹配,则返回零,如果不匹配,则返回一个不同于零的值,表示哪个值更大。 请注意,与 strcmp 不同,该函数在找到空字符后不会停止比较。


int main()
{
  char buffer1[] = "DWgaOtP12df0";
  char buffer2[] = "DWGAOTP12DF0";
  int n;
  n = memcmp(buffer1, buffer2, sizeof(buffer1));
  if (n > 0) printf("'%s' is greater than '%s'.\n", buffer1, buffer2);
  else if (n < 0) printf("'%s' is less than '%s'.\n", buffer1, buffer2);
  else printf("'%s' is the same as '%s'.\n", buffer1, buffer2);
  return 0;
}


0e30e90a36a04686bb37c77535389a0b.png


库中声明如下:


int __cdecl memcmp(
    _In_reads_bytes_(_Size) void const* _Buf1,
    _In_reads_bytes_(_Size) void const* _Buf2,
    _In_                    size_t      _Size
    );


1.比较从ptr1和ptr2指针开始的num个字节

2.返回值

返回一个整数值,该值指示内存块内容之间的关系:


2eeb6721377d469088373ba03b78929f.png


3.函数的模拟实现


3.1 模拟实现strlen


三种方式:

方式1:


//计数器方式
int my_strlen(const char * str)
{
 int count = 0;
 while(*str)
 {
 count++;
 str++;
 }
 return count;
}


方式2:


//不能创建临时变量计数器
int my_strlen(const char * str)
{
 if(*str == '\0')
 return 0;
 else
 return 1+my_strlen(str+1);
}


方式3:


//指针-指针的方式
int my_strlen(char *s)
{
       char *p = s;
       while(*p != ‘\0’ )
              p++;
       return p-s;
}


3.2 模拟实现strcpy


参考代码:


char *my_strcpy(char *dest, const char*src)
{ 
  char *ret = dest;
  assert(dest != NULL);
  assert(src != NULL);
  while((*dest++ = *src++))
  {
    ;
  }
  return ret;
}


3.3 模拟实现strcat


参考代码:


char *my_strcat(char *dest, const char*src)
{
  char *ret = dest;
  assert(dest != NULL);
  assert(src != NULL);
  while(*dest)
  {
    dest++;
  }
  while((*dest++ = *src++))
  {
    ;
  }
  return ret;
}


3.4 模拟实现strstr


char* strstr(const char* str1, const char* str2)
{
    char* cp = (char*)str1;
    char* s1, * s2;
    if (!*str2)
        return((char*)str1);
    while (*cp)
    {
        s1 = cp;
        s2 = (char*)str2;
        while (*s1 && *s2 && !(*s1 - *s2))
            s1++, s2++;
        if (!*s2)
            return(cp);
        cp++;
    }
    return(NULL);
}


3.5 模拟实现strcmp


参考代码:


int my_strcmp(const char* src, const char* dest)
{
    int ret = 0;
    assert(src != NULL);
    assert(dest != NULL);
    while (!(ret = *(unsigned char*)src - *(unsigned char*)dest) && *dest)
        ++src, ++dest;
    if (ret < 0)
        ret = -1;
    else if (ret > 0)
        ret = 1;
    return(ret);
}


3.6 模拟实现memcpy


参考代码:


void* my_memcpy(void* dst, const void* src, size_t count)
{
    void* ret = dst;
    assert(dst);
    assert(src);
    while (count--) {
        *(char*)dst = *(char*)src;
        dst = (char*)dst + 1;
        src = (char*)src + 1;
    }
    return(ret);
}


3.7 模拟实现memmove


参考代码:


void* my_memmove(void* dst, const void* src, size_t count)
{
    void* ret = dst;
    if (dst <= src || (char*)dst >= ((char*)src + count)) {
        while (count--) {
            *(char*)dst = *(char*)src;
            dst = (char*)dst + 1;
            src = (char*)src + 1;
        }
    }
    else {
        dst = (char*)dst + count - 1;
        src = (char*)src + count - 1;
        while (count--) {
            *(char*)dst = *(char*)src;
            dst = (char*)dst - 1;
            src = (char*)src - 1;
        }
    }
    return(ret);
}


结语


有兴趣的小伙伴可以关注作者,如果觉得内容不错,请给个一键三连吧,蟹蟹你哟!!!

制作不易,如有不正之处敬请指出

感谢大家的来访,UU们的观看是我坚持下去的动力

在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!

ec49875c6dd849c9aa8f7afbe33dc05a.png

相关文章
|
1天前
|
程序员 编译器 C语言
C语言----动态内存分配(malloc calloc relloc free)超全知识点
C语言----动态内存分配(malloc calloc relloc free)超全知识点
14 6
|
1天前
|
存储 程序员 编译器
C语言:动态内存管理
C语言:动态内存管理
11 1
|
1天前
|
存储 编译器 程序员
C语言:数据在内存中的存储
C语言:数据在内存中的存储
13 2
|
1天前
|
存储 编译器 C语言
C语言:字符函数 & 字符串函数 & 内存函数
C语言:字符函数 & 字符串函数 & 内存函数
15 2
|
1天前
|
编译器
练习使用动态内存相关的4个函数:malloc、calloc、realloc、free
在了解使用动态内存相关的四个函数之前,我们先了解一下,为什么要有动态内存分配?
17 0
|
1天前
|
存储 编译器 C++
【C++】内存管理和模板基础(new、delete、类及函数模板)
【C++】内存管理和模板基础(new、delete、类及函数模板)
22 1
|
1天前
|
编译器 C语言 C++
详解内存操作函数
详解内存操作函数
|
1天前
|
缓存 安全 编译器
【C 言专栏】C 语言函数的高效编程技巧
【5月更文挑战第1天】本文探讨了C语言中函数的高效编程技巧,包括函数的定义与作用(如代码复用和提高可读性)、设计原则(单一职责和接口简洁)、参数传递方式(值传递、指针传递和引用传递)、返回值管理、调用约定、嵌套与递归调用,以及函数优化技巧和常见错误避免。掌握这些技巧能提升C语言代码的质量和效率。
【C 言专栏】C 语言函数的高效编程技巧
|
1天前
|
存储 C语言
C 语言函数完全指南:创建、调用、参数传递、返回值解析
函数是一段代码块,只有在被调用时才会运行。 您可以将数据(称为参数)传递给函数。 函数用于执行某些操作,它们对于重用代码很重要:定义一次代码,并多次使用。
96 3
|
1天前
|
存储 C语言
C语言函数的返回值
C语言函数的返回值
10 0

热门文章

最新文章