13 C语言进阶字符串函数内存函数

字符函数和字符串函数

函数部分的解析来源于( C++资源网络)

  • 求字符串长度
    strlen
  • 长度不受限制的字符串函数
    strcpy
    strcat
    strcmp
  • 长度受限制的字符串函数介绍
    strncpy
    strncat
    strncmp
  • 字符串查找
    strstr
    strtok
  • 错误信息报告
    strerror
  • 字符操作
  • 内存函数操作
    memcpy
    memmove
    memset
    memcmp

C语言中的字符串问题

写代码时对字符和字符串的处理很频繁,但是C语言中本身没有字符串类型,字符串通常存储在常量字符串中或者字符数组中。常量字符串适用于那些对它不做修改的字符串函数。

函数介绍

1. strlen

1.1 功能:

Get string length获取字符串长度

返回C语言中字符串的长度。

1.2 库函数形式
size_t strlen ( const char * str );
1.3 注意事项:

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

注: 1. 字符串以’\0’作为结束标志。 2. 函数的返回值为size_t,是无符号的。 size_t==unsigned int

1.4 实例
int main ()
{
  char szInput[256];
  printf ("Enter a sentence: ");
  gets (szInput);
  printf ("The sentence entered is %u characters long.\n",(unsigned)strlen(szInput));
  return 0;
}

2. strcpy

2.1 功能:

Copy block of memory复制字符串

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

2.2 库函数形式
char * strcpy ( char * destination, const char * source );
2.3 注意事项:

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

注:1. 源字符串必须以’\0’结束。2. 结束标志’\0’也会被拷贝。 3. 目标空间必须足够大。 4. 目标空间必须可变。

2.4 实例
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;
}

3. strcat

3.1 功能:

Concatenate strings连接字符串

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

3.2 库函数形式
char * strcat ( char * destination, const char * source );
3.3 注意事项:

‎目的地‎‎和‎‎来源‎‎不得重叠。自己不能给自己追加。

注:1. 源数组必须以’\0’结束。2. 目标空间必须足够大,能容纳下源字符串的内容。 3. 目标空间必须是可修改的。**重点:**strcat不能自己给自己追加。

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

4. strcmp

4.1 功能:

Compare two strings比较两个字符串

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

返回值 表明
<0 第一个不匹配的字符在ptr1中的值低于ptr2中的值
0 两个字符中的内容相等
>0 第一个不匹配的字符在ptr1中的值低于ptr2中的值
4.2 库函数形式
int strcmp ( const char * str1, const char * str2 );
4.3 注意事项:

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

4.4 实例
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;
}

5. strncpy

5.1 功能:

Copy characters from string从字符串中复制字符

的前num字符复制到目标。如果在复制num个字符之前找到C 字符串(由空字符指示)的末尾,则目标将填充零,直到向其写入总共num个字符。

5.2 库函数形式
char * strncpy ( char * destination, const char * source, size_t num );
5.3 注意事项:

‎如果源的长度超过 num,则不会在目标末尾隐式追加空字符。
因此,在这种情况下,目标不应被视为以空值结尾的 C 字符串(这样读取它会溢出)。
目的地和来源不得重叠(重叠时,请参阅 memmove 以了解更安全的替代方案)。

5.4 实例
int main ()
{
  char str1[]= "To be or not to be";
  char str2[40];
  char str3[40];

  /* copy to sized buffer (overflow safe): */
  strncpy ( str2, str1, sizeof(str2) );

  /* partial copy (only 5 chars): */
  strncpy ( str3, str2, 5 );
  str3[5] = '\0';   /* null character manually added */

  puts (str1);
  puts (str2);
  puts (str3);

  return 0;
}

6. strncat

6.1 功能:

Append characters from string

的前num个字符追加到目标,加上一个终止空字符。

6.2 库函数形式
char * strncat ( char * destination, const char * source, size_t num );
6.3 注意事项:

如果源中 C 字符串的长度小于num,则仅复制直到终止空字符的内容。
如果source拷贝进去之后desination还有原本存在的字符会主动追加一个’\0’。
如果追加长度num大于strlen(source),则只追加source原本的字符串和一个’\0’

6.4 实例
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;
}

7. strncmp

7.1 功能:

Compare characters of two strings 比较两个字符串的字符

将 C 字符串str1最多 num个字符与 C 字符串str2的字符数进行比较。

7.2 库函数形式
int strncmp ( const char * str1, const char * str2, size_t num );
7.3 注意事项:

此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续向后比较,直到字符不同,直到达到终止空字符,或者直到两个字符串中的num字符匹配,以先发生的情况为准。

7.4 实例
int main ()
{
  char str[][5] = { "R2D2" , "C3PO" , "R2A6" };
  int n;
  puts ("Looking for R2 astromech droids...");
  for (n=0 ; n<3 ; n++)
    if (strncmp (str[n],"R2xx",2) == 0)
    {
      printf ("found %s\n",str[n]);
    }
  return 0;
}

8. strstr

8.1 功能:

**Locate substring ** 定位子字符串

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

8.2 库函数形式
const char * strstr ( const char * str1, const char * str2 );
      char * strstr (       char * str1, const char * str2 );
8.3 注意事项:

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

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

9. strtok

9.1 功能:

Split string into tokens 将字符串拆分为标记

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

9.1 库函数形式
char * strtok ( char * str, const char * delimiters );
9.3 注意事项:

在第一次调用时,函数需要一个C字符串作为str的参数,str的第一个字符用作扫描标记的起始位置。在随后的调用中,函数需要一个空指针,并使用最后一个标记结束后的位置作为扫描的新起始位置。

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

令牌的这一端将自动替换为空字符,函数将返回令牌的开头。

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

第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。

strtok函数找到str中的下一个标记,并将其用’\0’结尾,返回一个指向这个标记的指针。
(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改)<提前备份>

strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存在字符串中的位置。

strtok函数的第一个参数为NULL,函数将在同一个字符串(我的理解是上次传递的字符串)中被保存的位置开始,查找下一个标记。

如果字符串中不存在更多的标记,则返回NULL指针。

9.4 实例
int main ()
{
  char str[] ="- This, a sample string.";
  char * pch;
  printf ("Splitting string \"%s\" into tokens:\n",str);
  pch = strtok (str," ,.-");
  //因为每次只切割出一部分所以要使用while循环。
  while (pch != NULL)
  {
    printf ("%s\n",pch);
    pch = strtok (NULL, " ,.-");
  }
  return 0;
}

10. strerror

10.1 功能:

Get pointer to error message string获取指向错误消息字符串的指针

错误码是程序员知道的,但是要转化为用户能看懂的错误信息,就需要使用函数将其转化为错误信息。

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

10.2 库函数形式
char * strerror ( int errnum );
10.3 注意事项:

字符串。对此函数的进一步调用可能会覆盖其内容(不需要特定的库实现来避免数据争用)。strerror 生成的错误字符串可能特定于每个系统和库实现。

10.4 实例
#include 
#include 
#include 

int main ()
{
  FILE * pFile;
  //打开文件
  pFile = fopen ("unexist.ent","r");
  if (pFile == NULL)
    printf ("Error opening file unexist.ent: %s\n",strerror(errno));
  else{
      printf("Win opening file!")
  }
  return 0;
}
//errno在头文件
//errno是一个全局的错误码变量
//当C语言的库函数在执行过程中,发生错误,就会把对应的错误码,赋值到errno中

字符分类函数

函数使用需要的头文件

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

一定是字符转换,不是字符串转化。

tolower(c);//转小写字母
toupper(c);//转大写字母

使用案例

int main(){
    char str[] = "I Am A Good Student";
    int i = 0;
    while(str[i]){
        if(isupper(str[i])){
            str[i] = tolower(str[i]);
        }
        i++;
    }
    printf("%s\n",str);
    return 0;
}

字符串函数主要功能是拷贝字符串,而拷贝数字的时候可能会出现提前结束的情况(1在内存中01 00 00 00,在拷贝的时候00等价于字符串结束的标志)。那么我们想要拷贝除了字符串,像数字这种会随时出现字符串终止标志的应该怎么处理呢?我们就出现了基于内存的操作。

11. memcpy

11.1 功能:

Copy block of memory 复制内存块

num字节的值从指向的位置直接复制到目标所指向的内存块。源指针和目标指针所指向的对象的基础类型与此函数无关。

11.2 库函数形式
void * memcpy ( void * destination, const void * source, size_t num );
11.3 注意事项:

结果是数据的二进制副本。该函数不检查中是否有任何终止空字符 - 它始终精确地复制数字字节。

为避免溢出,目标参数和源参数所指向的数组的大小应至少为 num 字节,并且不应重叠(对于重叠的内存块,memmove 是一种更安全的方法)。

11.4 实例

简单理解

int main(){
    int arr[] = {1,2,3,4,5};
    int arr2[5] = {0};
    memcpy(arr2,arr1,sizeof(arr1));
    return 0;
}

进阶理解

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

12. memmove

12.1 功能:

Move block of memory 移动内存块

num字节的值从所指向的位置复制到目标所指向的内存块。

注:处理重叠拷贝的情况

12.2 库函数形式
void * memmove ( void * destination, const void * source, size_t num );
12.3 注意事项:

复制就像使用中间缓冲区一样进行,允许目标重叠。
源指针和目标指针所指向的对象的基础类型与此函数无关;结果是数据的二进制副本。该函数不检查中是否有任何终止空字符 - 它始终精确地复制数字字节。

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

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

C语言标准规定

memcpy 只要处理,不重叠的内存拷贝就可以
memmove处理重叠内存的拷贝。

但是现在好多的编译器memcpy都可以进行重叠拷贝

13. memcmp

13.1 功能:

比较两个字符串

ptr1指向的内存块的第一个字节数与ptr2指向的第一个数字字节进行比较

返回值 表明
<0 在两个内存块中不匹配的第一个字节在ptr1中的值低于ptr2中的值(如果计算为无符号字符值)
0 两个内存块的内容相等
>0 两个内存块中不匹配的第一个字节在ptr1中的值大于ptr2中的值(如果计算为无符号 char值)
13.2 库函数形式
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
13.3 注意事项:

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

13.4 实例
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;
}

14.memset

14.1功能

Fill block of memory填充内存块

14.2库函数形式
void * memset ( void * ptr, int value, size_t num );
14.3注意事项

将ptr指向的内存块 的前num字节设置为指定值(解释为unsigned char)。

ptr指向要填充的内存块的指针。num指的是字节数

14.4使用实例
#include 
#include 

int main ()
{
  char str[] = "almost every programmer should know memset!";
  memset (str,'-',6);
  puts (str);
  return 0;
}

函数的模拟实现

strlen的模拟实现

//计数器方式
int my_strlen(const char *str) {
    int count = 0;
    while (*str) {
        count++;
        str++;
    }
    return count;
}
//不能创建临时变量计数器 --递归方式实现
int my_strlen(const char *str) {
    if (*str == '\0')
        return 0;
    else
        return 1 + my_strlen(str + 1);
}
//指针的方式实现
int my_strlen(char *s) {
    char *p = s;
    while (*p != '\0')
        p++;
    return p - s;
}

strcpy的模拟实现

//1.参数顺序
//2.函数的功能,停止条件
//3.assert
//4.const修饰指针
//5.函数返回值
char *my_strcpy(char *dest, const char *src) {
    //保留原来的地址。
    char *ret = dest;
    //保证输入的是有效的指针。
    assert(dest != NULL);
    assert(src != NULL);
	//赋值过程
    while ((*dest++ = *src++)) { ;
    }
    return ret;
}

strcat的模拟实现

char *my_strcat(char *dest, const char *src) {
    //备份初始位置
    char *ret = dest;
    //保证指针的有效性
    assert(dest != NULL);
    assert(src != NULL);
    //将dest的指向位置改为,当前字符串的'\0'位置,即拼接开始的地方
    while (*dest) {
        dest++;
    }
    //开始拼接操作 要将最后'\0'也拼接上
    while ((*dest++ = *src++)) ;
    return ret;
}

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

strcmp的模拟实现

//只返回0,1,-1型
int my_strcmp(const char *word1,const char *word2) {
    //保证指针的有效性
    assert(word1!=NULL);
    assert(word2!=NULL);
   //相等的情况
    while (*word1==*word2){
        if(*word2=='\0'){
            return 0;
        }
        word1++;
        word2++;
    }
    //不相等的情况
    if (*word1 > *word2){
        return 1 ;
    } else{
        return -1;
    }
}
//返回动态值的方法
int my_strcmp(const char *word1,const char *word2){
    assert(word1!=NULL);
    assert(word2!=NULL);
    while (*word1==*word2){
        if(*word2=='\0'){
            return 0;
        }
        word1++;
        word2++;
    }
    return (*word1-*word2);
}

strncpy的模拟实现

char *my_strncpy(char *dest, const char *src, unsigned int count) {
    //保证指针的有效性
    assert(dest != NULL);
    assert(src != NULL);
    //备份返回值
    char *res = dest;
    //复制字符串
    while (count && (*src)) {
        *dest = *src;
        src++;
        dest++;
        count--;
    }
    //超出的部分补位'\0'
    if (count) {
        while (count) {
            count--;
            *dest= '\0';
            dest++ ;
        }
    }
    return res;
}

strncat的模拟实现

char* my_strncat(char* front,const char * back,unsigned int count){
    //保证指针的有效性
    assert(front != NULL);
    assert(back != NULL);
    //备份返回值
    char * res = front;
    //找到被拼接的末尾位置的结束标志
    while(*front++);
    front--;//多计算了一次
    //拷贝
    while(count--){
        if (!(*front++ = * *back++)){
            return r;
        }
    }
    *front = '\0';
    return (res);
    
}

strstr的模拟实现

char *my_strstr(const char *str1, const char *str2) {
    assert(str1 != NULL);
    assert(str2 != NULL);
    //拷贝
    char *cp = (char *) str1;
    //运算指针
    char *s1 = NULL;
    char *s2 = NULL;
    //如果是空指针的话直接返回
    if (!*str2)
        return ((char *) str1);
    //查找子集的过程
    while (*cp) {
        s1 = cp;//str1的运算指针
        s2 = (char *) str2;//str2的运算指针
        while ((*str1 != '\0') && (*str2 != '\0') && (*str1 == *str2)) {
            s1++;
            s2++;
        }
        // 找到子串
        if (!*s2)
            return cp;
        cp++;
    }
    // 找不到子串
    return (NULL);
}

注:NULL是空指针的意思,Null是’\0’。

memcpy的模拟实现

不能实现重叠拷贝

//使用无类型指针接受参数
void *memcpy(void *dst, const void *src, size_t count) {
    void *res = dst;
    assert(dst);
    assert(src);
    //每次拷贝一个字节
    while (count--) {
        *(char *) dst = *(char *) src;
        dst = (char *) dst + 1;//地址加一,还可以写成++(char*)dst
        src = (char *) src + 1;//地址加一,还可以写成++(char*)src
    }
    return res;
}

memmove的模拟实现

可以实现重叠拷贝。

void *my_memmove(void *dest, const void *src, size_t count) {
    //count是字节的个数
    //有效性
    assert(dest != NULL);
    assert(src != NULL);
    //备份返回值,因为不知道用于何处返回的是void类型的指针。
    void *res = dest;
    //判断dest和src的位置关系并决定拷贝方向
    //从前往后  重叠时dest在src的左边,和不重叠的情况
    if (dest < src || (char *) dest >= ((char *) src + count)) {
        while (count--) {
            *(char *) dest = *(char *) src;//一个字节一个字节的拷贝
            dest = (char *) dest + 1;//这里不能使用内置加加的方法
            src = (char *) src + 1;
        }
    }
        // 从后往前
    else {
        dest = (char *) dest + count - 1;//更改开始位置
        src = (char *) src + count - 1;//更改开始位置
        while (count--) {
            *(char *) dest = *(char *) src;
            dest = (char *) dest - 1;
            src = (char *) src - 1;
        }
    }
    return res;
}

注:void*是无具体类型的指针。这样的函数是需要返回值的。

你可能感兴趣的:(C语言,c语言,开发语言,后端)