关于C++:const int *、const int *const和int const *之间的区别是什么?

What is the difference between const int*, const int * const, and int const *?

我总是搞砸如何正确使用const int*const int * constint const *。是否有一套规则定义了你能做和不能做的事情?

我想知道在分配、传递函数等方面的所有应做和不应做的事情。


向后读(由顺时针/螺旋法则驱动):

  • int*—指向int的指针
  • int const *—指向const int的指针
  • int * const—指向int的常量指针
  • int const * constconst指向const int的指针

现在,第一个const可以位于类型的任一侧,因此:

  • const int *==int const *
  • const int * const==int const * const

如果你想发疯,你可以这样做:

  • int **—指向指向int的指针
  • int ** const—指向指向int的指针的常量指针。
  • int * const *—指向常量指针的指针,指向int
  • int const **—指向常量int指针的指针。
  • int * const * const—指向int的const指针的const指针

确保我们对const的含义有明确的认识

1
2
3
const int* foo;
int *const bar; //note, you actually need to set the pointer
                //here because you can't change it later ;)

foo是指向常量整数的变量指针。这允许您更改指向的内容,但不更改指向的值。最常见的情况是使用C样式的字符串,其中有指向const char的指针。您可以更改指向的字符串,但不能更改这些字符串的内容。当字符串本身位于程序的数据段中且不应更改时,这一点很重要。

bar是一个常量或固定指针,指向一个可以更改的值。这就像一个没有额外句法糖分的参考。因为这个事实,通常您会使用一个引用,其中您将使用一个T* const指针,除非您需要允许NULL指针。


对于那些不知道顺时针/螺旋法则的人:从变量名开始,按顺序移动(在本例中,向后移动)到下一个指针或类型。重复,直到表达式结束。

这是一个演示:

pointer to int

const pointer to int const

pointer to int const

pointer to const int

const pointer to int


我想这里已经回答了所有问题,但我想补充一点,你应该小心以东十一〔28〕号!它们不仅仅是文本替换。

例如:

1
2
typedef char *ASTRING;
const ASTRING astring;

astring的类型是char * const,不是const char *。这就是为什么我总是倾向于把const放在类型的右边,而从来没有放在开头。


就像几乎所有人都指出的那样:

const X* pX* const pconst X* const p有什么区别?

You have to read pointer declarations
right-to-left.

  • const X* p means"p points to an X that is const": the X object can't be changed via p.

  • X* const p means"p is a const pointer to an X that is non-const": you can't change the pointer p itself, but you can change the X object via p.

  • const X* const p means"p is a const pointer to an X that is const": you can't change the pointer p itself, nor can you change the X object via p.


  • 常量引用:

    对变量(这里是int)的引用,它是常量。我们主要将变量作为引用传递,因为引用的大小比实际值小,但有一个副作用,这是因为它就像是实际变量的别名。我们可能会通过对别名的完全访问意外地更改主变量,因此我们将其设置为常量以防止这种副作用。

    1
    2
    3
    4
    int var0 = 0;
    const int &ptr1 = var0;
    ptr1 = 8; // Error
    var0 = 6; // OK
  • 常量指针

    一旦常量指针指向一个变量,那么它就不能指向任何其他变量。

    1
    2
    3
    4
    5
    int var1 = 1;
    int var2 = 0;

    int *const ptr2 = &var1;
    ptr2 = &var2; // Error
  • 指向常量的指针

    一个指针,通过它不能改变它所指向的变量的值,称为指向常量的指针。

    1
    2
    int const * ptr3 = &var2;
    *ptr3 = 4; // Error
  • 常量指针指向常量

    指向常量的常量指针是既不能更改其指向的地址,也不能更改保存在该地址的值的指针。

    1
    2
    3
    4
    5
    int var3 = 0;
    int var4 = 0;
    const int * const ptr4 = &var3;
    *ptr4 = 1;     // Error
     ptr4 = &var4; // Error

  • 一般规则是,const关键字应用于它前面的内容。例外情况下,启动const适用于以下内容。

    • const int*int const*相同,意思是"指向常量int的指针"。
    • const int* constint const* const相同,表示"常数指针指向常数int"。

    编辑:对于应该做和不应该做的,如果这个答案不够,你能更准确地说出你想要什么吗?


    这个问题确切地说明了为什么我喜欢按照我在问题中提到的方法来做,即类型ID之后的const是可接受的?

    简而言之,我发现记住规则的最简单方法是"const"跟踪它适用的对象。所以在您的问题中,"int const*"表示int是常量,"int*const"表示指针是常量。

    如果有人决定把它放在最前面(例如:"const int*"),作为一个特殊的例外,它适用于后面的事情。

    许多人喜欢使用这个特殊的例外,因为他们认为它看起来更好。我不喜欢它,因为它是一个例外,因此混淆了事情。


    "const"的简单用法

    最简单的用法是声明一个命名常量。为此,我们声明一个常量,就像它是一个变量一样,但在它前面添加"const"。必须在构造函数中立即初始化它,因为当然,以后不能设置值,因为这样会改变它。例如,

    1
    const int Constant1=96;

    将创建一个整型常量,不可想象地称为"Constant1",值为96。

    这些常量对于程序中使用的参数很有用,但在程序编译后不需要更改。对于程序员来说,它比C预处理器"define"命令有一个优势,因为它被编译器本身理解和使用,而不仅仅是在到达主编译器之前被预处理器替换成程序文本,因此错误消息更有用。

    它也适用于指针,但必须注意"const"的位置,以确定指针或它指向的对象是常量还是两者都是常量。例如,

    1
    const int * Constant2

    声明Constant2是指向常量整数的变量指针,并且

    1
    int const * Constant2

    是执行相同操作的可选语法,而

    1
    int * const Constant3

    声明constant3是指向变量integer的常量指针,并且

    1
    int const * const Constant4

    声明Constant4是指向常量整数的常量指针。基本上,"const"适用于其左上角的任何部分(如果没有任何部分,在这种情况下,它适用于其右上角的任何部分)。

    参考:http://duramecho.com/computerinformation/whyhowcpconst.html


    我遇到了同样的疑问,直到我遇到这本书的C++大师Scott Meyers。参考这本书中的第三项,他详细讨论了使用const

    照这个建议去做

  • 如果星号左边出现"EDOCX1"(0),则所指的是常量。
  • 如果单词const出现在星号的右侧,指针本身就是常量。
  • 如果两边都出现const,两者都是常数。

  • 这很简单,但很棘手。请注意,我们可以用任何数据类型(intcharfloat等)交换const限定符。

    让我们看看下面的例子。

    const int *p==>*p是只读的〔p是指向常量整数的指针〕。

    int const *p==>*p是只读的〔p是指向常量整数的指针〕。

    int *p const==>语句错误。编译器引发语法错误。

    int *const p==>p是只读的〔p是指向整数的常量指针〕。由于指针p是只读的,所以声明和定义应该在同一位置。

    const int *p const==>语句错误。编译器引发语法错误。

    const int const *p==>*p为只读

    const int *const p1==>*pp是只读的〔p是指向常量整数的常量指针〕。由于指针p是只读的,所以声明和定义应该在同一位置。

    int const *p const==>语句错误。编译器引发语法错误。

    int const int *p==>语句错误。编译器引发语法错误。

    int const const *p==>*p为只读,相当于int const *p

    int const *const p==>*pp是只读的〔p是指向常量整数的常量指针〕。由于指针p是只读的,所以声明和定义应该在同一位置。


    C和C++声明语法反复被原始设计者描述为失败的实验。

    相反,让我们将类型命名为“指向Type”,我将称之为Ptr_

    1
    2
    template< class Type >
    using Ptr_ = Type*;

    现在,Ptr_是指向char的指针。

    Ptr_是指向const char的指针。

    const Ptr_const的指针指向const char

    那里。

    enter image description here


    C++中有许多其他的关于const正确性的微妙点。我想这里的问题仅仅是关于C,但是我会给出一些相关的例子,因为标签是C++。

    • 您通常将字符串之类的大型参数作为TYPE const &传递,这样可以防止修改或复制对象。例子:

      TYPE& TYPE::operator=(const TYPE &rhs) { ... return *this; }

      TYPE & const毫无意义,因为引用总是常量。

    • 应始终将不修改类的类方法标记为const,否则不能从TYPE const &引用调用该方法。例子:

      bool TYPE::operator==(const TYPE &rhs) const { ... }

    • 通常情况下,返回值和方法都应该是常量。例子:

      const TYPE TYPE::operator+(const TYPE &rhs) const { ... }

      实际上,const方法不能返回内部类数据作为对非const的引用。

    • 因此,必须经常使用const重载创建const和non-const方法。例如,如果您定义T const& operator[] (unsigned i) const;,那么您可能还需要以下给出的非常量版本:

      inline T& operator[] (unsigned i) {
      return const_cast(
      static_cast(*this)[](i)
      );
      }

    另外,C中没有const函数,非成员函数本身不能在C++中被const,const方法可能会有副作用,编译器不能使用const函数来避免重复函数调用。事实上,即使是一个简单的int const &引用也可能见证它所引用的值在其他地方被更改。


    对我来说,const的位置,也就是说,相对于*,它是出现在左边还是右边,还是同时出现在左边和右边,有助于我理解实际意义。

  • *左边的const表示指针指向的对象是const对象。

  • *右边的const表示指针是const指针。

  • 下表由斯坦福大学CS106L标准C++编程实验室课程阅读器获取。

    enter image description here


    两边都有int的const将成为指向常量int的指针。

    1
    const int *ptr=&i;

    1
    int const *ptr=&i;

    "*"后面的常量将成为指向int的常量指针。

    1
    int *const ptr=&i;

    在这种情况下,所有这些都是指向常量整数的指针,但没有一个是常量指针。

    1
     const int *ptr1=&i, *ptr2=&j;

    在这种情况下,都是指向常量整数的指针,ptr2是指向常量整数的常量指针。但是ptr1不是常量指针。

    1
    int const *ptr1=&i, *const ptr2=&j;

    为了满足C的完整性,遵循其他解释,不确定C++。

    • pp-指向指针的指针
    • P指针
    • 数据-在示例x中指出的事情
    • 粗体-只读变量

    指针

    • P数据-int *p;
    • P数据-int const *p;
    • P数据-int * const p;
    • P数据-int const * const p;

    指向指针的指针

  • p p p数据-int **pp;
  • p p p数据-int ** const pp;
  • p p p数据-int * const *pp;
  • p p p数据-int const **pp;
  • p p p数据-int * const * const pp;
  • p p p数据-int const ** const pp;
  • p p p数据-int const * const *pp;
  • p p p数据-int const * const * const pp;
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    // Example 1
    int x;
    x = 10;
    int *p = NULL;
    p = &x;
    int **pp = NULL;
    pp = &p;
    printf("%d
    "
    , **pp);

    // Example 2
    int x;
    x = 10;
    int *p = NULL;
    p = &x;
    int ** const pp = &p; // Definition must happen during declaration
    printf("%d
    "
    , **pp);

    // Example 3
    int x;
    x = 10;
    int * const p = &x; // Definition must happen during declaration
    int * const *pp = NULL;
    pp = &p;
    printf("%d
    "
    , **pp);

    // Example 4
    int const x = 10; // Definition must happen during declaration
    int const * p = NULL;
    p = &x;
    int const **pp = NULL;
    pp = &p;
    printf("%d
    "
    , **pp);

    // Example 5
    int x;
    x = 10;
    int * const p = &x; // Definition must happen during declaration
    int * const * const pp = &p; // Definition must happen during declaration
    printf("%d
    "
    , **pp);

    // Example 6
    int const x = 10; // Definition must happen during declaration
    int const *p = NULL;
    p = &x;
    int const ** const pp = &p; // Definition must happen during declaration
    printf("%d
    "
    , **pp);

    // Example 7
    int const x = 10; // Definition must happen during declaration
    int const * const p = &x; // Definition must happen during declaration
    int const * const *pp = NULL;
    pp = &p;
    printf("%d
    "
    , **pp);

    // Example 8
    int const x = 10; // Definition must happen during declaration
    int const * const p = &x; // Definition must happen during declaration
    int const * const * const pp = &p; // Definition must happen during declaration
    printf("%d
    "
    , **pp);

    n-取消引用的级别

    继续前进,但希望人类将你逐出教会。

    1
    2
    3
    4
    5
    6
    7
    8
     int x = 10;
     int *p = &x;
     int **pp = &p;
     int ***ppp = &pp;
     int ****pppp = &ppp;

     printf("%d
    "
    , ****pppp);

    这主要涉及第二行:最佳实践、分配、函数参数等。

    一般做法。尽量把你能做的一切都做出来。或者换一种方式,使所有的const都从开始,然后精确地删除允许程序运行所需的const的最小集合。这将对实现常量正确性有很大帮助,并有助于确保当人们试图分配到不应该修改的内容中时,不会引入细微的错误。

    避免像瘟疫一样施法。它有一个或两个合法的用例,但它们非常少,而且相距甚远。如果你试图改变一个const的对象,你会做得更好,在第一步找到宣布它的人const,然后与他们讨论这个问题,就应该发生什么达成共识。

    这很容易导致作业。只有当某事物是非常量时,才能将其赋值。如果您想分配给常量,请参见上面的。记住,在声明中,int const *foo;int * const bar;是不同的,const是不同的,这里的其他答案已经很好地涵盖了这个问题,所以我不会去讨论它。

    功能参数:

    传递值:例如,void func(int param)在主叫站点,您不在乎这一种方式。可以这样做,即声明函数为void func(int const param)是有用例的,但对调用方没有影响,只对函数本身没有影响,因为在调用期间传递的任何值都不能被函数更改。

    通过引用:例如,void func(int ¶m),现在它确实起了作用。正如刚才所宣布的,func可以更改param的内容,任何呼叫站点都应该准备好应对后果。将声明更改为void func(int const ¶m)将更改合同,并保证func现在不能更改param,这意味着传入的内容将返回。正如其他人所指出的,这对于廉价传递一个您不想更改的大对象非常有用。传递引用比按值传递大型对象便宜得多。

    传递指针:例如,void func(int *param)void func(int const *param)这两个函数与它们的引用对应函数几乎是同义词,但需要注意的是,被调用函数现在需要检查nullptr,除非有其他合同保证保证func不会在param中接收到nullptr

    关于那个话题的意见。在这种情况下证明正确是非常困难的,犯错误太容易了。所以不要冒险,总是检查nullptr的指针参数。从长远来看,你会减轻自己的痛苦和痛苦,并且很难找到虫子。至于检查的成本,它是非常便宜的,而且在编译器内置的静态分析可以管理它的情况下,优化器无论如何都会删除它。启用MSVC的链接时间代码生成,或者GCC的WOPR(我认为),您就可以使它在程序范围内,即即使在跨源代码模块边界的函数调用中。

    在一天结束的时候,上面的所有内容都是非常可靠的,总是倾向于引用指针。它们只是更安全而已。