C++ STL ~ string 字符串有没有结束符

发布时间 2023-11-12 00:06:50作者: liuxh_cn

C 语言没有专门的字符串类型,它用一个以 \0 做结尾的字符数组来表示一个字符串,这通常称为 C 语言风格的字符串。后来面向对象的 C++ 在标准类库中提供了专门的字符串类 string,也就是 C++ 风格的字符串。除了拥有很多方便的接口,本质上,C++ string 依然是对一个字符数组的封装。比如,我们可以使用下标来访问它的元素,而且打印的地址显示这些元素确实是连在一起的一个数组。于是,自然而然地,在遇到一些接口需要传递一个 char* / const char* 参数时,我们想直接使用 &str[index]

但是,这里有一个潜在的问题,C++ string 底层所维护的这个字符数组是否还是以 \0 结尾。或者说,C++ string 在创建时,是否会自动在字符数组末尾添加 \0

这个问题是有意义的。一方面,string 是可以不使用 \0 做结尾的,作为一个复杂的类,它可以维护一个成员变量来记录有效字符的长度,使用“数组 + 有效长度”的方式表示一个字符串,以替代 C 语言中“数组 + 终止符”的方式。另一方面,如果 string 不使用 \0 做结尾,那它就不能用在以 C 语言字符串做参数的接口上。

目前没有找到确切的官方资料,但从代码执行结果(Linux g++9.4)上判断是肯定的,C++ string 底层维护的字符数组是以 \0 做结尾,我们可以方便地将 &str[index] 用在众多 C 语言字符串接口上。因为调用 string 的 c_str() 可以返回一个 C 字符串指针。如果 string 不以 \0 结尾,那么返回的这个 C 字符串应该另起炉灶,重新申请内存,然后把所有字符拷贝进去,最后在末尾添加 \0。单从描述上就能感受到这是一个多么耗时,且可以轻易避免如此耗时的操作。我们可以打印 str[0]str.c_str()[0] 的地址,来确定这二者是否是同一块内存,从而确定 string 底层维护的字符数组是否以 \0 结尾。

int main(){
    string str = "hello";
    cout << str.size() << " " << str.capacity() << endl;
    cout << endl;

    for(int i = 0; i < str.size(); ++i){
        printf("%p\n", &str[i]);
    }
    cout << endl;

    for(int i = 0; i < str.size(); ++i){
        printf("%p\n", &str.c_str()[i]);
    }
    cout << endl;
}

//    5 15
//
//    00000045fb3ffc30
//    00000045fb3ffc31
//    00000045fb3ffc32
//    00000045fb3ffc33
//    00000045fb3ffc34
//
//    00000045fb3ffc30
//    00000045fb3ffc31
//    00000045fb3ffc32
//    00000045fb3ffc33
//    00000045fb3ffc34
//    Process finished with exit code 0

值得注意的是,string 提供的 capacity() 接口返回的长度,并不包含 \0,这个也可以从上面代码的执行结果看到。不得不说,这一点很让人困惑,目前我没有找到这样做的原因。


最后,分享一个我遇到的基于上述特性的方便操作。

// param = Frame-0
int SetParam(const char* param, …) {
	int index = atoi(const_cast<char*>(param)[strlen("Frame-")])![image]
	
}