【C语言进阶剖析】31、字符串典型问题分析


这一篇博客我们分析四个典型的字符串问题。

1 snprintf 函数

下面的代码会输出什么?
在这里插入图片描述
函数 snprintf 的功能和 printf 类似,只不过 printf 是将输出到终端,snprintf 是输出到第一个参数中。这里就是输出到 buf 中。

这里就是将字符串 src 复制到 buf 中,再将 buf 中的字符串打印出来,真的可以打印出 hello %s 吗?我们编译一下看看。

// 31-1.c
#include<stdio.h>
int main()
{
    char buf[10] = {0};
    char src[] = "hello %s";
    snprintf(buf, sizeof(buf), src);
    printf("buf = %s\n", buf);
    return 0;
}
$ gcc 31-1.c -o 31-1
31-1.c: In function ‘main’:
31-1.c:7:32: warning: format not a string literal and no format arguments [-Wformat-security]
     snprintf(buf, sizeof(buf), src);
                                ^~~
$ ./31-1
buf = hello AWA

编译器给出了一条警告,但是依然编译通过了,打印出来的结果并不是 hello %s,这和我们预想的有点不同。

分析:

  • snprintf 函数本身是可变参数函数,原型如下:
int snprintf(char *str, size_t size, const char *format, ...)

功能:将可变参数 “…” 按照 format 的格式格式化为字符串,然后再将其拷贝至 str 中。

当函数只有 3 个参数时,如果第三个参数没有包含格式化信息,函数调用没有问题;相反,如果第三个参数包含了格式化信息,但缺少后续对应参数,则程序行为不确定。

在本案例中,字符串 src 包含了格式化信息 %s,也没有指定后续参数,所以出现问题。

我们将格式化信息去掉,再次尝试,

// 31-1.c
#include<stdio.h>
int main()
{
    char buf[10] = {0};
    char src[] = "hello S";
    snprintf(buf, sizeof(buf), src);
    printf("buf = %s\n", buf);
    return 0;
}
$ gcc 31-1.c -o 31-1
31-1.c: In function ‘main’:
31-1.c:7:32: warning: format not a string literal and no format arguments [-Wformat-security]
     snprintf(buf, sizeof(buf), src);
                                ^~~
$ ./31-1
buf = hello S

可以看到没有格式化信息,snprintf 也只有三个参数,打印结果也是正确的。这里的编译器同样给出警告,但是我们没有格式化信息,没有错误,这个警告不用理会。

我们再将格式化信息加上,也加上后续参数

// 31-1.c
#include<stdio.h>
int main()
{
    char buf[10] = {0};
    char src[] = "hello %s";
    snprintf(buf, sizeof(buf), src, "S");
    printf("buf = %s\n", buf);
    return 0;
}
$ gcc 31-1.c -o 31-1
$ ./31-1
buf = hello S

结果完全正确,编译警告也没有了。

2 strlen 和 sizeof

这里我们说一下字符串长度的问题,字符串本质上就是字符数组,strlen() 是求解字符串长度,也就是第一个出现 ‘\0’ 之前的字符长度;sizeof 是求解整个字符数组的长度。

下面直接看代码

// 31-2.c
#include<stdio.h>
#include<string.h>
int main()
{
    #define STR "Hello, \0World\0"
    char* src = STR;
    char buf[255] = {0};
    snprintf(buf, sizeof(buf), src);
    printf("strlen(STR) = %ld\n", strlen(STR));
    printf("sizeof(STR) = %ld\n", sizeof(STR));

    printf("strlen(src) = %ld\n", strlen(src));
    printf("sizeof(src) = %ld\n", sizeof(src));

    printf("strlen(buf) = %ld\n", strlen(buf));
    printf("sizeof(buf) = %ld\n", sizeof(buf));

    printf("src = %s\n", src);
    printf("buf = %s\n", buf);
    return 0;
}

分析:

  • strlen(STR) 是求字符串 STR 的长度,为第一个 '\0’之前的字符长度,长度为 7;sizeof(STR) 是求字符数组 STR 的长度,“Hello, \0World\0” 中字符长度为 14,加上编译器默认加在末尾的 ‘\0’,一共是 15 个字节。
  • 指针 str 指向 STR,strlen(src) 是求解字符串的长度,所以是求 “Hello, \0World\0” 中第一个 ‘\0’ 之前的字符长度,长度为 7;src 是个指针,指针长度为 8,所以 sizeof(src) 为 8。
  • strlen(buf) 是求解字符串长度,同上,也是 7;sizeof(buf) 是求解整个数组的长度,定义的时候是 255,所以 sizeof(buf) 为 255。
  • printf(“src = %s\n”, src); 和 printf(“buf = %s\n”, buf); 都是打印字符串,打印的是第一个 \0 之前的字符。

备注:复制到 buf 中的字符是 "Hello, ",就是第一个 ‘\0’ 之前的字符。

下面我们编译一下,看看结果和我们想象的是否相同。

$ gcc 31-2.c -o 31-2
31-2.c: In function ‘main’:
31-2.c:9:5: warning: format not a string literal and no format arguments [-Wformat-security]
     snprintf(buf, sizeof(buf), src);
     ^~~~~~~~
$ ./31-2
strlen(STR) = 7
sizeof(STR) = 15
strlen(src) = 7
sizeof(src) = 8
strlen(buf) = 7
sizeof(buf) = 255
src = Hello, 

结果和我们分析的完全一样。

总结:

  1. 字符串相关的函数均以第一个出现的 ‘\0’ 作为结束符
  2. 编译器总是会在字符串字面量的末尾添加 ‘\0’
  3. 字符串字面量的本质是数组

3 比较两字符串相等

下面的代码会输出什么?
在这里插入图片描述

// 31-3.c
#include<stdio.h>
#include<string.h>
int main()
{
    #define S1 "Hello"
    #define S2 "Hello"
    if (S1 == S2)
    {
        printf("Equal\n");
    }
    else
    {
        printf("No Equal\n");
    }

    if (strcmp(S1, S2) == 0)
    {
        printf("Equal\n");
    }
    else
    {
        printf("No Equal\n");
    }
    return 0;
}

分析:
S1,S2 是两个字符串,S1 和 S2 分别是这两个字符串的首元素的地址,虽然两个字符串是一样的,但是对应的地址是不同的,所以 S1,S2 是不相等的。

strcmp 是字符串比较函数,原型是:

int strcmp(const char *s1,const char *s2);

当s1<s2时,返回为负数;
当s1=s2时,返回值= 0;
当s1>s2时,返回正数。

我们来看一下 gcc 编译器的编译结果

$ gcc 31-3.c -o 31-3
$ ./31-3
Equal
Equal

和我们分析的不一样,两个都输出了 Equal。这是为什么呢?先不着急,我们再用 bcc 编译器编译一下,如下所示:
在这里插入图片描述

这下和我们的分析结果一样了,为什么会出现这种情况呢?其实这是 gcc 编译器的优化,vs 编译器和 gcc 编译器都对此作出了优化,当定义了 #define S1 “Hello” 后,编译器继续执行,遇见了 #define S2 “Hello”,这两个字符串不是完全一样的吗?那为什么要定义两次呢,除了浪费内存没有什么用,所以编译器在内存中只有一份字符串 “Hello”。并且让 S1 和 S2 指向同一个位置,就导致了 S1 和 S2 是相同的。

但是我们仍然不能使用这种方式比较字符串,因为这样的代码是依赖于编译器的,并不健壮,换个编译器很可能不能用了。

总结:

  1. 字符串之间的相等比较需要用 strcmp 完成
  2. 不可直接用 == 进行字符串直接的比较
  3. 完全相同的字符串字面量的 == 比较结果为 false

一些现代编译器(如gcc,vs)能够将相同的字符串字面量映射到同一个无名字符数组,因此 == 比较结果为 true。

4 字符串循环右移

要求将一个字符串循环右移 n 位,函数接口如下:

void right_shift_r(const char* src, char* result, unsigned int n);

函数功能:将输入的字符串循环右移 n 位,result 为输出结果。
要求:以效率最高的方式实现
示例:
“abcde” – 2 --> “deabc”
“abcde” – 8 --> “cdeab”

分析:将一个字符串向右移动 n 位后,下标为 i 的字符的下标将会变为 (i + n)%len,其中len 为字符串的长度。

// 31-4.c
#include<stdio.h>
#include<string.h>
void right_shift_r(const char* src, char* result, unsigned int n)
{
    const unsigned int len = strlen(src);
    int i = 0;
    for (i = 0; i < len; i++)
    {
        result[(i + n) % len] = src[i];
    }
    result[len] = '\0';
}
int main()
{
    char result[255] = {0};
    right_shift_r("abcde", result, 2);
    printf("%s\n", result);

    right_shift_r("abcde", result, 5);
    printf("%s\n", result);

    right_shift_r("abcde", result, 8);
    printf("%s\n", result);
    return 0;
}
$ gcc 31-4.c -o 31-4
$ ./31-4
deabc
abcde
cdeab

函数复杂度为 O(n)

发布了248 篇原创文章 · 获赞 115 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/happyjacob/article/details/103375249
今日推荐