const char * 与 char const * const 的区别(不是关于 const 的问题)

12

我知道char const *、char * const和char const * const之间的差异,分别是:

char* the_string:我可以更改the_string指向的char,并且可以修改它所指向的char。

const char* the_string:我可以更改the_string指向的char,但不能修改它所指向的char。

char* const the_string:我无法更改the_string指向的char,但可以修改它所指向的char。

const char* const the_string:我既不能更改the_string指向的char,也不能修改它所指向的char。

(摘自const char * const versus const char *?

现在我的问题是:假设我正在编写一个不会修改传递给它的C字符串的函数,例如:

int countA(??? string) {
    int count = 0;
    int i;
    for (i=0; i<strlen(string); i++) {
         if (string[i] == 'A') count++;
    }
    return count;
}

那么,这个标题应该是什么?

int countA(char const * string); 
int countA(char const * const string);

我的感觉是应该使用第二种方式,因为我既不会修改指针本身,也不会修改数组的内容。但是当我查看标准函数的头文件时,它们使用第一种方式。例如:

char * strcpy ( char * destination, const char * source );

为什么?

(实际上,char const * 对我来说并没有什么意义,因为如果你考虑的是抽象的字符串,那么要么你不修改字符串(因此使用 char const * const ,因为你既不会修改指针,也不会修改内容),要么你会修改字符串(所以只需使用 char * ,因为你可能需要修改内容,也可能需要分配更多内存,因此可能需要修改指针)

希望有人能够让我彻底明白,谢谢。

5个回答

6
在这种情况下,指针本身是否是const并不重要,因为它无论如何都是按值传递的:无论strcpy对source做什么,都不会影响调用者的变量,因为strcpy将在堆栈上的调用者source的副本上操作,而不是原始的source。请注意,我说的是指针值,而不是指针指向的内容,显然不应改变,因为它是源参数。
char dest[10];
char const * source = "Hello";
strcpy( dest, source );
// no matter what strcpy does, the value of source will be unchanged

strcpy 函数中,你需要遍历指向 destinationsource 的数组的指针。如果不将参数声明为 const,则允许该函数直接使用堆栈中的值,而无需首先进行复制/强制转换。


5
拥有 const char * 表示一份合同。这是一个承诺,该函数不会使用该指针修改用户传递的内容。指针本身是否为常量并不那么重要。
因此,对于调用者而言,指针本身是否为 const 没有任何区别。
在许多实现中,“字符串”函数通常会修改传递的指针而不修改其内容。如果规范(C标准)要求指针本身为常量,将会成为所有实现的限制因素,而对调用者没有任何好处。
作为一个附带说明,我认为你不应该把所有东西都设为 const,然后再绕开它。只有当感觉函数不应该更改时,才将其设置为 const。简而言之,不要过度使用 const,它并非灵丹妙药,可能会导致沮丧。

3
非定义性声明:
int countA(char const * string);
int countA(char const * const string);
int countA(char const * foobar);
int countA(char const *);

所有这些写法都是等价的。在实现 countA 函数中,string 参数名(实际上)相当于局部变量。调用者不需要知道实现是否修改该变量,这不会影响函数签名。

但如果函数修改了 string 的引用,则调用者需要知道这一点,因此第一个 const 很重要。参数变量的名称对调用者而言略有影响,仅仅是因为约定俗成地在声明中将参数命名以提示其用途。文档是传达每个参数含义的完整方式,而不是参数名称。

如果你需要一个规则:省略第二个 const,因为它会使函数声明变得混乱,且不会告诉调用者任何有用信息。

你可以在函数定义中包含第二个 const,但这样做存在一些问题。你要么需要保持头文件与定义文件同步(在这种情况下,由于实现细节的更改而不影响调用者,有时候你会发现自己不得不改变头文件)。或者你必须接受头文件与定义文件不匹配,这会偶尔让看到以下代码的人感到不满:

int countA(char const * const string) {
    return 0; 
}

搜索头文件中的int countA(char const * const string),或者反过来查看头文件并搜索源代码。需要更聪明的搜索词。


1
我并不知道第二个 const 实际上 不会 影响签名! - DevSolar
1
@DevSolar:没错。我太懒了,不想在标准里查找,但如果我没记错的话,有明确的文本说明这一点。 - Steve Jessop

3
当你将一个函数参数声明为const char * const时,对于调用者来说与const char *没有区别:他们不关心你对该指针做了什么,因为对于他们来说这都是“按值传递”。
第二个const是为你自己而存在的,而不是为了你的用户。如果你知道你不会修改那个指针,那么请将其声明为const char * const:这将有助于你和其他维护代码的人在以后捕捉错误。
至于标准库,我猜想它们没有将其设置为const char * const是因为它们想要修改指针的能力:
char * strcpy ( char * destination, const char * source ) {
    char *res = destination;
    while (*destination++ = *source++)
        ;
    return res;
}

2
  • char const *ss 是指向 const char 的指针。
  • char *const ss 是指向 char 的常量指针。

s 是函数参数时,第一种表示法比第二种更有用。

  • 使用 char const *,您无法修改指向的值。
  • 使用 char *const,您无法修改指针的值。这就像在函数参数中使用 int const:您无法直接对参数进行操作。对于调用函数来说,它不会改变任何东西。(现在,对于编译器来说几乎没有用处,但对程序员来说有意义)

实际上,在这种情况下,一个好的程序员会知道s是按值传递的,所以const无论如何都不重要... - DevSolar
当然;就像我说的,这对于调用函数没有任何影响,所以它只是以一种表现力的方式使用(作为编译器优化器的辅助工具)。这就像参数中的整数常量,有些程序员使用它(我也是)。 - md5
但是 const char* const* 是什么? - mohammadsdtmnd

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