`const char * const`和`const char *`有什么区别?

132

我正在运行一些示例程序以重新熟悉 C++,并遇到了以下问题。首先是示例代码:

void print_string(const char * the_string)
{
    cout << the_string << endl;
}

int main () {
    print_string("What's up?");
}

在上面的代码中,print_string函数的参数可以改为const char * const the_string。对于这个问题哪个更正确?

我理解它们之间的区别是一个是指向常量字符的指针,而另一个是常量指针指向常量字符。但是为什么两者都能工作呢?什么情况下会有影响?


“卓越问题”徽章 12年后获得 - Peter Mortensen
12个回答

291

后一种方式防止你在 print_string 函数中修改 the_string。实际上这里使用这种方式是合适的,但也许这种冗长的方式把开发人员吓怕了。

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 的值。


13
对于最后一句话点个赞。保持常量正确性可能会增加代码长度,但绝对是值得的。 - mskfisher
6
@Xeo:你的表达方式更加混淆,因为它只需要转换一个位置就会完全改变它的意思。使用 const char * 会更好,因为 const 在完全相反的位置上。 - R.. GitHub STOP HELPING ICE
7
对我来说不是这样的。从右到左阅读,我得到的是“指向常量字符”的意思。对我来说,这种方式更好。 - Xeo
13
我有些尴尬,显然我是唯一一个不理解这个问题的人...但是“指向它的char **”和“指向它的char **位置”之间有什么区别呢? - Lack
8
-1 代表不清楚;这个答案写得真的不太有帮助(虽然不应该被称为愚蠢)。建议将 "const char* the_string : I can change the char to which the_string points, but I cannot modify the char at which it points." 改为 "const char* the_string:我可以改变指针,但无法修改它所指向的字符。" 等等。 - Don Hatch
显示剩余11条评论

166
  1. 可变的指向可变字符的指针

char *p;
  • 指向常量字符的可变指针

  • const char *p;
    
  • 指向可变字符的常量指针

  • char * const p; 
    
  • 常量指针指向常量字符

  • const char * const p;
    

    这不应该是: const char* p; --> 指向可变字符的常量指针char *const p; --> 指向常量字符的可变指针 - JamesWebbTelescopeAlien
    9
    不。C++声明是从右向左形成的。因此,您可以将“const char * p”解读为:“p是指向字符常量的指针”,或者像James正确所述的那样,是一个可变指针指向常量字符。第二个也是相同的。 - Samidamaru

    33

    const char * const 意味着指针本身以及指针所指向的数据,都是 不可变(const)的!

    const char * 则意味着只有指针所指向的数据是 不可变 的,指针本身并非 const。

    示例:

    const char *p = "Nawaz";
    p[2] = 'S'; //error, changing the const data!
    p="Sarfaraz"; //okay, changing the non-const pointer. 
    
    const char * const p = "Nawaz";
    p[2] = 'S'; //error, changing the const data!
    p="Sarfaraz"; //error, changing the const pointer. 
    

    29

    (我知道这个很老了,但我还是想分享一下。)

    我只是想详细解释一下Thomas Matthews的答案。C类型声明的右左规则基本上是这样说的:当阅读C类型声明时,从标识符开始,可以向右走就向右走,不能向右走就向左走。

    这最好通过几个例子来解释:

    示例1

    • 从标识符开始,我们不能向右走,所以向左走

    const char* const foo
                ^^^^^
    

    foo是一个常数......

  • 向左继续

    const char* const foo
              ^
    

    foo是一个指向常量的指针...

  • 向左继续

  • const char* const foo
          ^^^^
    

    foo是一个指向字符类型的常量指针...

  • 继续向左

  • const char* const foo
    ^^^^^
    

    foo是指向常量字符常量的指针(完整!)

    • 从标识符开始,我们无法向右移动,因此向左移动

    char* const foo
          ^^^^^
    

    foo是一个常量...

  • 向左继续

    char* const foo
        ^
    

    foo是一个指向常量的指针...

  • 向左继续

  • char* const foo
    ^^^^
    

    foo是一个指向char类型的常量指针(完整!)

    示例1337

    • 从标识符开始,但现在我们可以向右移动!

    • const char* const* (*foo[8])()
                              ^^^
      

      foo是一个包含8个元素的数组...

    • 遇到括号时,如果不能继续向右移动,则向左移动

    • const char* const* (*foo[8])()
                          ^
      

      foo是一个包含8个指向的数组...

    • 括号内的内容已完结,现在可以向右移动

    • const char* const* (*foo[8])()
                                  ^^
      

      foo是一个包含8个指针的数组,这些指针指向返回函数的指针...

    • 没有更多向右的内容,请向左走

    • const char* const* (*foo[8])()
                       ^
      

      foo是一个包含8个指向返回指向a的指针的函数指针的数组...

    • 继续往左

    • const char* const* (*foo[8])()
                  ^^^^^
      

      foo是一个包含8个指向返回指向常量的函数的指针的数组...

    • 继续向左

    • const char* const* (*foo[8])()
                ^
      

      foo是一个包含8个指向函数的指针数组,这些函数返回一个指向常量指针的指针...

    • 继续向左

    • const char* const* (*foo[8])()
            ^^^^
      

      foo是一个包含8个指向返回指向char的常量指针函数的数组...

    • 继续向左

    • const char* const* (*foo[8])()
      ^^^^^
      

      foo是一个数组,它包含8个指向函数的指针,这些函数返回一个指向常量字符指针的指针。(完成!)

    更多解释请参考:http://www.unixwiz.net/techtips/reading-cdecl.html


    1
    请纠正我,const char* const foo 应该等同于 char const * const foo 吗? - luochenhuan
    1
    @luochenhuan 是的,确实如此。 - Garrett
    1
    CMIIW = 如果我错了,请纠正我 - Peter Mortensen

    16

    很多人建议从右向左阅读类型说明符。

    const char * // Pointer to a `char` that is constant, it can't be changed.
    const char * const // A const pointer to const data.
    

    两种形式中,指针都指向常量或只读数据。

    在第二种形式中,指针是不可更改的;指针将永远指向同一位置。


    4
    几乎所有其他答案都是正确的,但它们忽略了其中一个方面:当您在函数声明中使用额外的const参数时,编译器将基本上会忽略它。暂时忽略你的示例是指针的复杂性,只使用一个int
    void foo(const int x);
    

    声明与之相同的函数
    void foo(int x);
    

    只有在函数的定义中,额外的const才有意义:

    void foo(const int x) {
        // do something with x here, but you cannot change it
    }
    

    这个定义与上述任何一个声明兼容。调用者不关心 x 是否为 const - 这是一个在调用点不相关的实现细节。

    如果您有一个指向 const 数据的 const 指针,则同样适用以下规则:

    // these declarations are equivalent
    void print_string(const char * const the_string);
    void print_string(const char * the_string);
    
    // In this definition, you cannot change the value of the pointer within the
    // body of the function.  It's essentially a const local variable.
    void print_string(const char * const the_string) {
        cout << the_string << endl;
        the_string = nullptr;  // COMPILER ERROR HERE
    }
    
    // In this definition, you can change the value of the pointer (but you 
    // still can't change the data it's pointed to).  And even if you change
    // the_string, that has no effect outside this function.
    void print_string(const char * the_string) {
        cout << the_string << endl;
        the_string = nullptr;  // OK, but not observable outside this func
    }
    

    很少有C++程序员会将参数定义为const,即使这些参数是指针类型。

    “编译器基本上会忽略它”并不总是正确的,在定义中添加额外的 const 到函数参数时,Visual C++ 2015 会产生警告,但在声明中不会。 - raymai97
    1
    @raymai97:我认为这是2015编译器中的一个bug,但我手头没有2015版本来测试。根据我与一些标准专家的交流,它在2017年的表现方式符合预期。 - Adrian McCarthy
    const int t 是一个奇怪的函数声明,这就是为什么我们在 clang-tidy 中有 readability-avoid-const-params-in-decls... 我猜这就是 Visual C++ 2015 尝试实现的(瞎猜)。 - malat

    3

    const char * 的意思是你不能使用指针来改变所指向的内容。但你可以改变指针指向的位置。

    考虑以下代码:

    const char * promptTextWithDefault(const char * text)
    {
        if ((text == NULL) || (*text == '\0'))
            text = "C>";
        return text;
    }
    

    该参数是一个指向常量字符的非常量指针,因此可以更改为另一个 const char *值(如常量字符串)。但如果我们错误地编写了* text =' \ 0',那么会得到编译错误。

    可以说,如果您不打算更改参数所指向的内容,则可以将参数设置为 const char * const text ,但这并不常见。我们通常允许函数更改传递给参数的值(因为我们通过值传递参数,任何更改都不会影响调用者)。

    顺便说一下:避免使用 char const *是一个好习惯,因为它经常被错误地理解 - 它意味着与 const char *相同,但太多人将其解释为 char * const


    哇!我一直在尝试弄清楚我的const char *和签名char const *之间的区别 - 你所措辞的方式确实帮了我很多! - sage

    2

    区别在于没有额外的const,程序员可以在方法内更改指针指向的位置;例如:

     void print_string(const char * the_string)
     {
        cout << the_string << endl;
        //....
        the_string = another_string();
        //....
    
     }
    

    如果签名是void print_string(const char * const the_string),那么省略const关键字将是非法的。

    许多程序员觉得在大多数情况下过于冗长,因此省略了额外的const关键字,即使从语义上讲它是正确的。


    2

    没有理由不行。 print_string() 的功能只是打印值,它并没有尝试修改它。

    将不修改标记参数的函数定义为常量是一个好主意。优点是可以将不能更改(或者不想更改)的变量传递给这些函数而不会出错。

    关于确切的语法,您需要指示哪种类型的参数可以安全地传递给函数。


    1
    在后者中,您保证不修改指针或字符。在前者中,您只保证内容不会改变,但可以移动指针。

    1
    啊,所以如果没有最后的const,我实际上可以将指针设置为指向完全不同的字符串? - pict
    是的,如果没有最后那个const,你可以使用参数指针进行一些指针算术迭代,而如果有了那个const,你就必须创建一个该参数的副本指针。 - Jesus Ramos

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