C++字符串:[]与*的区别

18

一直在思考,使用[]或*声明变量有什么区别?我个人的看法是:

char *str = new char[100];
char str2[] = "Hi world!";

..应该是主要区别,不过我不确定是否可以做类似的事情

char *str = "Hi all";

..由于指针应该引用静态成员,我不知道它是否可以?

无论如何,真正困扰我的是了解以下两者之间的区别:

void upperCaseString(char *_str) {};
void upperCaseString(char _str[]) {};

如果有人能告诉我两者之间的区别,我将不胜感激。我有一种预感它们可能会被编译成相同的代码,除非在某些特殊情况下需要不同的处理方法?

谢谢。

6个回答

42

让我们来看看它(请注意,对于以下内容,char constconst char在C ++中是相同的):

字符串字面量和char *

"hello"是一个由6个const字符组成的数组:char const[6]。像每个数组一样,它可以隐式转换为指向其第一个元素的指针:char const * s = "hello"; 为了与C代码兼容,C ++允许另一种转换,否则将导致错误:char * s =“hello”; 它会去掉const!这是一个例外,允许编译C风格的代码,但是使char * 指向字符串文字已被弃用。那么对于char * s =“foo”;,我们有什么呢?

"foo"-> array-to-pointer -> char const *-> qualification-conversion -> char *。字符串文字是只读的,并且不会在堆栈上分配。您可以将指针自由地指向它们,并从函数返回该指针,而不会导致崩溃 :)

使用字符串字面量初始化数组

现在,char s [] =“hello”;是另一回事。这将创建一个字符数组,并用字符串"hello"填充它。不指向字面量,而是将其复制到字符数组中。并且数组是在堆栈上创建的。您不能从函数中有效地返回指向它的指针。

数组参数类型。

如何使您的函数接受数组作为参数?只需声明您的参数为数组:

void accept_array(char foo[]); 

但您忽略了大小。实际上,任何大小都可以,因为它只是被忽略了:标准规定以这种方式声明的参数将被转换为与

void accept_array(char * foo);

小插曲:多维数组

char替换为任何类型,包括数组本身:

void accept_array(char foo[][10]);

接受一个二维数组,其最后一个维度大小为10。多维数组的第一个元素是其下一维度的第一个子数组!现在,让我们对其进行转换。它将再次成为指向其第一个元素的指针。因此,实际上它将接受指向包含10个字符的数组的指针:(去掉头部的[],然后只需创建指向您在脑海中看到的类型的指针):

void accept_array(char (*foo)[10]);

由于数组会隐式转换为指向它们第一个元素的指针,因此您可以直接将大小为10的二维数组传递给它,并且它将起作用。实际上,对于任何n维数组,包括n = 1的特殊情况,都是这种情况。

结论

void upperCaseString(char *_str) {}; 
and
void upperCaseString(char _str[]) {};

它们是相同的,因为第一个只是一个 char 指针。但请注意,如果您想将一个字符串字面量传递给它(假设不会更改其参数),那么您应该将参数更改为 char const* _str,这样您就不会做过时的事情。


你知道吗,我从来没有花时间去理解[]数组与指针声明的复杂性。我总是使用显式长度的数组或指针。谢谢! - Nick Bedford

12

这三种不同的声明让指针指向不同的内存段:

char* str = new char[100];

让 str 指向堆内存。

char str2[] = "Hi world!";

将字符串推入栈中。

char* str3 = "Hi world!";

指向数据段的位置。

这两个声明

void upperCaseString(char *_str) {};
void upperCaseString(char _str[]) {};

如果两个函数的名称相同且参数数量、类型以及顺序都相同,编译器会在同一作用域中声明它们时报错,因为这意味着该函数已经有了函数体。


2

好的,我之前留了两条负面评论,这样做并没有什么用,我已经删除了它们。

  • 下面的代码初始化了一个字符指针,指向动态分配内存部分(在堆中)的开头。

char *str = new char[100];

使用delete []可以释放此块。

  • 以下代码在堆栈中创建一个char数组,并用字符串常量指定的值对其进行初始化。

char [] str2 = "Hi world!";

这个数组可以毫无问题地修改,这非常好。所以


str2[0] = 'N';
cout << str2;

应该将Ni world!打印到标准输出,让某些骑士感到非常不舒服。

  • 以下代码在堆栈中创建了一个char指针,指向一个字符串字面量...指针可以重新分配而不会出问题,但指向的块不能被修改(这是未定义的行为;例如在Linux下它会发生segfault)。

char *str = "Hi all";
str[0] = 'N'; // ERROR!

以下是两个声明

void upperCaseString(char *_str) {};
void upperCaseString(char [] _str) {};

在我看来,它们看起来一样,而且在你的情况下(你想要原地将字符串转换为大写),这确实并不重要。

然而,所有这些都引出了一个问题:为什么你要在C++中使用char *来表示字符串呢?


0

作为对已经给出的答案的补充,您应该阅读有关数组与指针的C FAQ。是的,这是一个C FAQ而不是C++ FAQ,但在这个领域两种语言之间几乎没有实质性的区别。

另外,顺便提一下,避免使用带有前导下划线的变量名。这是为编译器和标准库定义的符号保留的。


那不是真的。以初始下划线后跟大写字母的符号被保留供实现使用。下划线后跟小写字母是可以的,至少在C++中是这样。我没有检查过C标准,但它保留了各种各样的东西,包括以s或w开头的函数名,如果我没记错的话。 - Steve Jessop
好的,为了澄清这一点,规则似乎是前导下划线后跟大写字母始终被保留,而前导下划线后跟小写字母仅在std::和全局命名空间中被保留,因此成员名称前的下划线实际上是可以的。 - Tyler McHenry
“仅在std::和::中保留”。我不太理解这个,因为您无法向std::添加符号(也就是说,整个命名空间都保留给实现)。 - Steve Jessop

0

-1

第一种选择动态分配了100个字节。

第二种选择静态分配了10个字节(9个用于字符串+nul字符)。

您的第三个示例不应该有效-您正在尝试静态填充动态项目。

至于upperCaseString()问题,一旦C字符串被分配和定义,您可以通过数组索引或指针符号遍历它,因为数组实际上只是在C中包装指针算术的方便方法。


那是一个简单的答案 - 我期望其他人会从规范中给出权威而复杂的答案 :))


1
第三个示例确实可以工作,它会指向包含字符串的只读内存。但是,如果你试图更改它,你会遇到麻烦。 - Graeme Perrow
这取决于你的编译器...或者你设置的错误级别:上次我做#3时,它输出了大量警告和几个错误。 - warren
1
#3 在GCC中使用-Wwrite-strings会发出警告,但这不包括在-Wall或-Wextra中。我认为,字面值->非const char *已经被弃用了,因此发出警告是合理的。 - Steve Jessop
我倾向于将我的错误/警告级别设置得非常高,因此这可能是我看到的不是默认行为的行为 :) - warren

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