C++中string和char[]类型的区别

151

对于 C 语言,我们使用 char[] 来表示字符串。

而对于 C++ 语言,我看到一些例子同时使用 std::stringchar 数组。

#include <iostream>
#include <string>
using namespace std;

int main () {
  string name;

  cout << "What's your name? ";
  getline(cin, name);
  cout << "Hello " << name << ".\n";

  return 0;
}
#include <iostream>
using namespace std;

int main () {
  char name[256];

  cout << "What's your name? ";
  cin.getline(name, 256);
  cout << "Hello " << name << ".\n";

  return 0;
}

(这两个示例都改编自http://www.cplusplus.com。)

在C++中,这两种类型有什么区别?(就性能、API集成、优缺点等方面而言)


1
这可能会有所帮助:C++ char* vs std::string - Wael Dalloul
7个回答

222

字符数组就是由字符组成的数组:

  • 如果在堆栈上分配(像你的示例中一样),无论它包含多长的文本,它总是占用256字节。
  • 如果在堆上分配(使用malloc()或new char[]),则需要负责释放内存,并且始终存在堆分配的开销。
  • 如果将超过256个字符的文本复制到数组中,可能会导致崩溃、产生难看的断言消息或在程序的其他地方引起无法解释的(错误)行为。
  • 要确定文本的长度,必须逐个字符扫描数组,直到找到\0字符。

字符串是一个类,包含一个字符数组,但会自动管理。大多数字符串实现都有一个内置的16个字符数组(因此短字符串不会使堆碎片化),并为较长的字符串使用堆。

您可以这样访问字符串的字符数组:

std::string myString = "Hello World";
const char *myStringChars = myString.c_str();

C++字符串可以包含嵌入式\0字符,无需计算即可知道它们的长度,对于短文本而言比堆分配的char数组快,同时可以保护您免受缓冲区溢出的风险。此外,它们更易读且更易使用。


然而,C++字符串不太适用于跨DLL边界的使用,因为这将需要确保每个使用该DLL函数的用户都使用完全相同的编译器和C++运行时实现,否则就会存在风险使得他们的字符串类表现不同。

通常,一个字符串类也会在调用堆上释放其堆内存,所以只有在使用共享(.dll或.so)版本的运行时时才能再次释放内存。

简而言之:在所有内部函数和方法中使用C++字符串。如果您编写了.dll或.so,请在公共(dll/so暴露的)函数中使用C字符串。


10
此外,字符串有很多辅助函数,非常实用。 - Håkon
1
我不相信有关DLL边界的那一点。在非常特殊的情况下,它可能会破坏(一个DLL静态链接到与其他DLL使用的运行时版本不同的版本),并且在这些情况下更糟糕的事情可能会首先发生,但在每个人都使用标准运行时的默认共享版本(默认情况下)的一般情况下,这种情况不会发生。 - Martin York
2
你分发了一个名为libfoo的公共库的VC2008SP1编译二进制文件,其中包含一个std::string&在其公共API中。现在有人下载了你的libfoo.dll并进行了调试构建。他的std::string很可能会有一些额外的调试字段,导致动态字符串指针的偏移量移动。 - Cygon
2
在2010年,有人下载了您的libfoo.dll并在他的VC2010构建的应用程序中使用它。他的代码加载MSVCP100.dll,而您的libfoo.dll仍然加载MSVCP90.dll -> 您会得到两个堆-> 如果libfoo修改字符串引用并返回一个带有新指针的std :: string,则无法释放内存,在调试模式下会出现断言错误。 - Cygon
3
我只想坚持使用C++字符串在所有内部函数和方法中。尝试理解你的例子让我的大脑崩溃了。 - Stephen
显示剩余2条评论

20

Arkaitz是正确的,string 是一种托管类型。这对于 来说意味着你永远不用担心字符串的长度,也不需要担心释放或重新分配字符串的内存。

另一方面,在上面的例子中,char[] 表示法将字符缓冲区限制为恰好256个字符。如果您尝试向该缓冲区写入超过256个字符,最好情况下,您将覆盖程序“拥有”的其他内存。最坏的情况是,您将尝试覆盖您不拥有的内存,然后您的操作系统将立即终止您的程序。

底线?字符串更加友好,char[] 对计算机而言更加高效。


7
最坏的情况是,其他人可能会覆盖你电脑上的存储空间并运行恶意代码。参见缓冲区溢出 - David Johnstone

7

string类型是一个完全受控制的字符字符串类,而char[]仍然像在C语言中一样,是一个表示字符字符串的字节数组。

就API和标准库而言,所有内容都是基于字符串实现的,而不是char[],但是仍然有很多来自libc的函数需要使用char[],所以你可能需要为此使用它,除此之外,我总是会使用std::string。

就效率而言,当然,对于许多事情,未经管理的原始缓冲区几乎总是更快的,但需要注意例如字符串比较,std::string始终具有要首先检查其大小,而使用char[]则需要逐个字符进行比较。


5
我个人认为,除了与旧代码兼容性外,没有理由使用char*或char[]。std::string的速度不比使用c-string慢,只是它会为您处理重新分配。您可以在创建时设置其大小,从而避免重新分配。它的索引运算符([])提供常数时间访问(并且在每个意义上都与使用c-string索引器完全相同)。使用at方法还可以获得边界检查安全性,这是c-strings所缺乏的,除非您编写它。在发布模式下,您的编译器通常会优化掉索引器的使用。玩弄c-strings很容易出错;例如delete vs delete[]、异常安全性甚至如何重新分配c-string。

当您必须处理高级概念,例如具有COW字符串和非COW的MT等时,您将需要std::string。

如果您担心副本问题,只要尽可能使用引用和const引用,您就不会因为副本而产生任何开销,这与您使用c-string时的情况相同。


+1 虽然您没有考虑 DLL 兼容性等实现问题,但您获得了 COW。 - undefined
如果我知道我的字符数组是12个字节,那怎么办?如果我为此实例化一个字符串,可能并不真正高效,对吧? - David 天宇 Wong
@David:如果你的代码非常注重性能,那么是的。你可能会认为std::string构造函数调用是一种额外的开销,除了初始化std::string成员变量之外。但请记住,过早地进行优化会使许多代码库不必要地采用C风格,所以要小心。 - Abhay

2

其中一个区别是空终止符(\0)。

在C和C++中,char*或char[]将以单个字符的指针作为参数,并沿着内存跟踪,直到达到0内存值(通常称为空终止符)。

C ++字符串可以包含嵌入的\0字符,并且可以知道它们的长度而无需计数。

#include<stdio.h>
#include<string.h>
#include<iostream>

using namespace std;

void NullTerminatedString(string str){
   int NUll_term = 3;
   str[NUll_term] = '\0';       // specific character is kept as NULL in string
   cout << str << endl <<endl <<endl;
}

void NullTerminatedChar(char *str){
   int NUll_term = 3;
   str[NUll_term] = 0;     // from specific, all the character are removed 
   cout << str << endl;
}

int main(){
  string str = "Feels Happy";
  printf("string = %s\n", str.c_str());
  printf("strlen = %d\n", strlen(str.c_str()));  
  printf("size = %d\n", str.size());  
  printf("sizeof = %d\n", sizeof(str)); // sizeof std::string class  and compiler dependent
  NullTerminatedString(str);


  char str1[12] = "Feels Happy";
  printf("char[] = %s\n", str1);
  printf("strlen = %d\n", strlen(str1));
  printf("sizeof = %d\n", sizeof(str1));    // sizeof char array
  NullTerminatedChar(str1);
  return 0;
}

输出:

strlen = 11
size = 11
sizeof = 32  
Fee s Happy


strlen = 11
sizeof = 12
Fee

从特定位置开始,所有字符都被删除了,不,它们并没有被“删除”,打印字符指针只会打印到空终止符为止(因为这是 char* 知道结尾的唯一方式)。而 string 类知道自己的完整大小,所以它直接使用那个。如果你知道你的 char* 的大小,你也可以自己打印/使用所有的字符。 - Puddle

1

字符串具有辅助函数并自动管理字符数组。您可以连接字符串,但对于字符数组,您需要将其复制到新数组中;字符串可以在运行时更改其长度。与字符串相比,字符数组更难管理,并且某些函数可能仅接受字符串作为输入,需要您将数组转换为字符串。最好使用字符串,因为它们是专门为了避免使用数组而创建的。如果数组客观上更好,我们就不会有字符串。


1
将(char *)视为string.begin()。本质区别在于(char *)是迭代器,而std::string是容器。如果你坚持使用基本字符串,(char *)将给你std::string::iterator的效果。当你想要迭代器的好处并且与C兼容时,可以使用(char *),但这是例外而不是规则。一如既往地,要注意迭代器失效。当人们说(char *)不安全时,指的就是这个。它和其他任何C++迭代器一样安全。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接