const int *,const int * const和int const *有什么区别?


1354

我总是搞砸了怎么用const int*const int * constint const *正确的。是否有一组规则定义您可以做什么和不能做什么?

我想知道在分配,传递给函数等方面所有需要做的事情。


175
您可以使用“顺时针/螺旋规则”来解密大多数C和C ++声明。
James McNellis

52
cdecl.org是一个很棒的网站,可以为您自动翻译C声明。
戴夫·加拉格尔

6
@Calmarius:在类型名称应为/的位置开始,在可以的情况下向右移动,在必须的情况下向左移动int *(*)(char const * const)。从括号的右边开始,*然后我们必须向左移动:pointer。在括号外,我们可以向右移动:pointer to function of ...。然后我们必须向左移动:pointer to function of ... that returns pointer to int。重复扩大参数(...pointer to function of (constant pointer to constant char) that returns pointer to int。像Pascal这样的易读语言中等效的单行声明是什么?
Mark K Cowan

1
@MarkKCowan在Pascal中会是这样function(x:^char):^int。函数类型隐含了指向函数的指针,因此无需指定它,并且Pascal不会强制执行const正确性。可以从左到右读取。
Calmarius

5
“ const”左侧的第一件事是常量。如果“ const”是最左边的东西,那么它右边的第一件事就是常量。
纸杯蛋糕

Answers:


2205

向后读取(受“ 顺时针/螺旋规则”驱动):

  • int* -指向int的指针
  • int const * -指向const int的指针
  • int * const -指向int的const指针
  • int const * const -指向const int的const指针

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

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

如果您想发疯,可以执行以下操作:

  • int ** -指向int的指针
  • int ** const -指向int的const指针
  • int * const * -指向const的指针指向int的指针
  • int const ** -指向const int的指针
  • int * const * const -指向int的const指针的const指针
  • ...

并确保我们清楚以下含义const

int a = 5, b = 10, c = 15;

const int* foo;     // pointer to constant int.
foo = &a;           // assignment to where foo points to.

/* dummy statement*/
*foo = 6;           // the value of a can´t get changed through the pointer.

foo = &b;           // the pointer foo can be changed.



int *const bar = &c;  // constant pointer to int 
                      // note, you actually need to set the pointer 
                      // here because you can't change it later ;)

*bar = 16;            // the value of c can be changed through the pointer.    

/* dummy statement*/
bar = &a;             // not possible because bar is a constant pointer.           

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

bar是指向可以更改的值的常量或固定指针。这就像没有多余语法糖的参考。因此,通常T* const除非需要允许使用指针,否则通常会在引用中使用NULL指针。


480
我想附加一条经验法则,它可以帮助您记住如何发现'const'是适用于指针还是指向数据:将语句拆分为星号,然后在左侧显示const关键字(例如'const int * foo')-如果属于正确的数据,则它属于指向的数据('int * const bar')-与指针有关。
迈克尔

14
@Michael:为Michael记住/理解const规则这样简单的规则而致以荣誉。
sivabudh'2

10
@Jeffrey:只要没有括号,请向后阅读。然后,好吧…… 使用
typedefs

12
+1,尽管更好的总结是:向后读取指针声明,这意味着接近@Michael的声明:在第一个星号处停止从左到右的正常读取。

3
@gedamial可以,它可以正常工作,但是必须在声明它的同时分配它(因为您不能重新分配“ const指针”)。const int x = 0; const int *const px = &x; const int *const *const p = &px;效果很好。
拉斯塔杰迪

356

对于那些不了解顺时针/螺旋规则的人:从变量名开始,顺时针移动(在这种情况下,向后移动)到下一个指针键入。重复直到表达式结束。

这是一个演示:

指向int的指针

const指向int const的指针

指向int const的指针

指向const int的指针

指向int的const指针


8
@Jan复杂示例的链接没有权限。您可以直接在此处发布它,还是可以取消查看限制?
R71

8
@Rog曾经拥有所有开放访问权限...不幸的是,我没有写这篇文章,也没有自己的访问权限。但是,这里是该文章的存档版本仍然有效:archive.is/SsfMX
JanRüegg16年

8
复杂的例子仍然是从右到左,但包括以通常的方式解决括号。整个顺时针旋转不会使事情变得容易。
马修(Matthew)

4
最终示例:void (*signal(int, void (*fp)(int)))(int);来自archive.is/SsfMX
naXa

3
不要依赖这个规则。这不是普遍的。在某些情况下,它会失败。

150

我认为这里已经回答了所有问题,但是我想补充一点,您应该提防typedefs!它们不只是文本替换。

例如:

typedef char *ASTRING;
const ASTRING astring;

该类型的astringchar * const,没有const char *。这就是我总是倾向于放在const类型右侧的原因,而不是一开始。


20
对我来说,这就是永远不要typedef指针的原因。我看不到类似的好处typedef int* PINT(我认为它来自C的实践,许多开发人员一直在这样做)。太好了,我用代替*P它,它不能加快键入速度,而且还引入了您提到的问题。
Mephane 2011年

1
@Mephane-我可以看到。但是,对我来说,为了继续使用特殊的语法规则(关于“ const”的位置)而避免使用好的语言功能,而不是避免使用特殊的语法规则,似乎有点倒退,因此您可以放心地使用此语言功能。
TED

6
@Mephane PINT确实是typedef的相当笨拙的用法,尤其是因为它使我认为系统存储使用啤酒来存储内存。不过,typedef对于处理指向函数的指针非常有用。
接近

5
@KazDragon谢谢!没有它,我就已经搞砸了与所有这些typedef操作PVOIDLPTSTR东西在WIN32 API!
David Lee

2
@Mephane:当使用某些遗留宏时,我不得不使用pSomething几次,这些宏被编写为接受一种类型,但是如果该类型不是单个字母数字标识符,则会崩溃。:)
Groo

56

几乎每个人都指出:

什么之间的区别const X* pX* const p以及const X* const p

您必须从右到左读取指针声明。

  • const X* p 表示“ p指向一个X是const”:不能通过p更改X对象。

  • X* const p 表示“ p是指向非常量X的const指针”:您不能更改指针p本身,但可以通过p更改X对象。

  • const X* const p 表示“ p是指向X的const指针,它是const”:您不能更改指针p本身,也不能通过p更改X对象。


3
不要忘记const X* p;==,X const * p;例如"p points to an X that is const": the X object can't be changed via p.
Jesse Chisholm

简单漂亮的解释!
Edison Lo

50
  1. 常数参考:

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

    int var0 = 0;
    const int &ptr1 = var0;
    ptr1 = 8; // Error
    var0 = 6; // OK
  2. 常量指针

    一旦常量指针指向变量,则它不能指向任何其他变量。

    int var1 = 1;
    int var2 = 0;
    
    int *const ptr2 = &var1;
    ptr2 = &var2; // Error
  3. 指向常量的指针

    不能更改其所指向的变量值的指针称为常量指针。

    int const * ptr3 = &var2;
    *ptr3 = 4; // Error
  4. 指向常量的常量指针

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

    int var3 = 0;
    int var4 = 0;
    const int * const ptr4 = &var3;
    *ptr4 = 1;     // Error
     ptr4 = &var4; // Error

20

一般规则是,const关键字适用于紧随其后的关键字。例外,以下内容const适用于开始。

  • const int*与相同,int const*并表示“指向常数int的指针”
  • const int* const与相同,int const* const并表示“指向常量int的常量指针”

编辑: 对于该做什么和不该做什么,如果这个答案还不够,您能否更精确地知道自己想要什么?


19

这个问题正好说明了为什么我喜欢以我在问题中提到的方式在类型ID可接受之后以const的方式进行操作

总之,我觉得要记住规则最简单的方法是“常量”云放在要应用的内容。因此,在您的问题中,“ int const *”表示int是常量,而“ int * const”将表示指针是常量。

如果有人决定将其放在最前面(例如:“ const int *”),则在这种情况下,它是一个特殊的例外,适用于其后的事物。

许多人喜欢使用该特殊异常,因为他们认为它看起来更好。我不喜欢它,因为这是一个例外,因此使事情变得混乱。


2
我在这个问题上感到痛苦。从逻辑上讲,这是有道理的。但是,大多数c ++开发人员都会写const T*,而且变得更加自然。T* const无论如何,您多久使用一次引用,通常引用就可以了。我想要一次boost::shared_ptr<const T>而被所有这​​一次咬住了,而是写了const boost::shared_ptr<T>。同一问题在稍有不同的情况下。
马特·普赖斯

实际上,与使用常量相比,我更常使用常量指针。另外,您必须考虑在存在指针(例如,指针)的情况下将如何作出反应。诚然,这些情况比较少见,但是最好以一种可以用applomb处理这些情况的方式来考虑问题。
TED

1
将const放在类型右侧的另一个不错的好处是,现在any左侧的所有const内容都是const类型,而其右侧的所有内容实际上就是const。以int const * const * p;作为一个例子。不,我通常不会这样写,这只是一个例子。首先const:键入int,并且const的int是const指针的内容,而const指针是的内容p。第二个const:类型是指向constint的指针,const oblect是p
dgnuff

18

简单的使用const

最简单的用法是声明一个命名常量。为此,将一个常量声明为好像是一个变量,const然后在其前面加上一个。一个人必须在构造函数中立即对其进行初始化,因为,当然,一个人以后不能设置该值,因为那会改变它。例如:

const int Constant1=96; 

会创建一个整数常量,这个名字很难想象 Constant1其值,值为96。

此类常数对于程序中使用的参数很有用,但在编译程序后无需更改。与C预处理程序相比,它对程序员具有优势#define命令,因为它可以由编译器本身理解和使用,而不是在到达主编译器之前由预处理程序替换为程序文本,因此错误消息会更有帮助。

它也适用于指针,但是必须小心在哪里const确定指针或其指向的对象是常量还是两者。例如:

const int * Constant2 

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

int const * Constant2

是一种替代语法,具有相同的功能,而

int * const Constant3

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

int const * const Constant4

声明这Constant4是指向常量整数的常量指针。基本上,“ const”适用于其紧靠左边的任何东西(除非没有什么东西,在这种情况下它适用于其紧靠右边的任何东西)。

参考:http : //duramecho.com/ComputerInformation/WhyHowCppConst.html


9

在您遇到C ++专家Scott Meyers的这本书之前,我一直和您有同样的疑问。请参阅本书的第三项,他在其中详细介绍了有关使用的内容const

只要遵循这个建议

  1. 如果单词const出现在星号的左侧,则指向的是常量
  2. 如果单词const出现在星号的右边,则指针本身是常量
  3. 如果const两边都出现,则两者都是常数

7

简单但棘手。请注意,我们可以交换const与任何数据类型(预选赛intcharfloat,等)。

让我们看下面的例子。


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此处的指针是只读的,因此声明和定义应位于同一位置。


6

关于C ++中的const正确性,还有许多其他的细微之处。我想这里的问题只是关于C的,但是由于标签是C ++,我将给出一些相关的示例:

  • 您通常会传递诸如字符串之类的大参数,TYPE const &以防止对象被修改或复制。范例:

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

    但是TYPE & const没有意义,因为引用始终是const。

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

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

  • 在通常情况下,返回值和方法都应为const。范例:

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

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

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

    inline T& operator[] (unsigned i) { return const_cast<char&>( static_cast<const TYPE&>(*this)[](i) ); }

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


6

最初的设计者多次将C和C ++声明语法描述为失败的实验。

相反,让我们将类型命名为“指向”的指针Type;我称它为Ptr_

template< class Type >
using Ptr_ = Type*;

现在Ptr_<char>是指向的指针char

Ptr_<const char>是指向的指针const char

并且const Ptr_<const char>是的const指针const char

那里。

在此处输入图片说明


3
你有第一句话的报价吗?
sp2danny

@ sp2danny:谷歌搜索“ C语法失败的实验”只会引起对Bjarne Stroustrup的多次采访,他在那方面表达了自己的见解,例如,在Slashdot采访中,“我认为C声明符语法失败了”。因此,对于有关C原始设计师的观点的主张,我没有任何参考。我认为可以通过足够强大的研究工作来找到它,或者可以简单地通过询问来证明它,但是我认为现在这样更好。与那部分索赔,仍未确定,可能是正确的:)
干杯和hth。-Alf

1
“最初的设计者曾多次将C和C ++声明语法描述为一次失败的实验。” C错误,请更改有关C的句子或提供一些引号。
Stargateur

3
@Stargateur:显然,您已经阅读了前面的评论,并且发现了可以用于修脚的东西。祝您生活愉快。无论如何,像我这样的老朋友记住了很多,如果不进行非常耗时的研究就无法证明。你可以相信我的话。
干杯和健康。-Alf

1
@Stargateur “ Sethi(...)观察到,如果将间接操作符用作后缀运算符而不是前缀,则许多嵌套的声明和表达式将变得更简单,但到那时为时已晚。来自DMR。当然,DMR并没有发明const和volatile关键字,它们来自C ++ / X3J11,如该页所示。
安蒂·哈帕拉

6

对我而言,const即相对于左侧或右侧出现还是相对于左侧和右侧出现的位置*有助于我弄清实际含义。

  1. constLEFT的A *表示指针所指向的const对象是对象。

  2. constRIGHT的A *表示指针是const指针。

下表摘自Stanford CS106L标准C ++编程实验室课程阅读器。

在此处输入图片说明


3

这主要涉及第二行:最佳实践,分配,功能参数等。

一般惯例。尽一切const可能。或者换一种说法,使所有事情都const开始,然后精确地删除const使程序运行所需的最小s 集。这将对实现const正确性有很大帮助,并且将有助于确保在人们尝试分配不应修改的内容时不会引入细微的错误。

避免像瘟疫一样使用const_cast <>。有一个或两个合法的用例,但它们之间相差无几。如果您要更改一个const对象,那么您会做得更好,以找到谁先声明该对象,const然后与他们讨论此事以达成共识。

这非常巧妙地导致了任务。仅当它是非常量时,才可以分配它。如果要分配给const,请参见上文。请记住,在声明int const *foo;int * const bar;不同的事情const-在这里其他的答案已经覆盖了这个问题令人钦佩,所以我不会进入它。

功能参数:

按值传递:例如,void func(int param)您不在乎呼叫方的一种方式。可以说有一些用例将函数声明为,void func(int const param)但对调用者没有影响,仅对函数本身没有影响,因为在调用过程中,传递的任何值都不能由函数更改。

通过引用传递:例如,void func(int &param)现在确实有所作为。正如刚刚声明的那样func,允许更改param,并且任何调用站点都应准备好处理后果。更改声明以void func(int const &param)更改合同,并保证func现在不能更改param,这意味着传入的是返回的内容。正如其他人指出的那样,这对于廉价地传递不想更改的大对象非常有用。传递引用比按值传递大对象便宜得多。

通过指针传递:例如void func(int *param)和,void func(int const *param)这两个和它们的参考对应物几乎是同义的,需要警告的是,nullptr除非有其他合同保证不能保证func不会收到nullptrin ,否则被调用函数现在需要检查param

关于该主题的观点。在这样的情况下证明正确性是非常困难的,犯错误太容易了。因此,不要冒险,并始终检查的指针参数nullptr。从长远来看,您将避免痛苦和痛苦,并且难以发现错误。至于检查的成本,它非常便宜,并且在编译器中内置的静态分析可以管理它的情况下,优化器仍然会忽略它。打开MSVC的链接时间代码生成功能,或者打开GCC的WOPR(我认为)功能,您将在程序范围内使用它,即,即使在跨越源代码模块边界的函数调用中也是如此。

归根结底,上述所有情况都是一个非常可靠的案例,总是喜欢引用指针。他们只是更安全。


3

两侧都带有int的const将使指向常量int的指针

const int *ptr=&i;

要么:

int const *ptr=&i;

const之后*将使常量指向int

int *const ptr=&i;

在这种情况下,所有这些都是指向常量integer的指针,但是这些都不是常量指针:

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

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

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

3
  • 如果const向左*,它指的是价值(也不要紧,无论是const intint const
  • 如果const向右*,它指的是指针本身
  • 可以同时

重要的一点:const int *p 并不意味着您所指的值是恒定的!。这意味着您不能通过该指针更改它(意味着,您不能分配$ * p = ...`)。该值本身可以以其他方式更改。例如

int x = 5;
const int *p = &x;
x = 6; //legal
printf("%d", *p) // prints 6
*p = 7; //error 

这主要用于函数签名中,以确保函数不会意外更改传递的参数。


2

只是为了完整起见,请遵循其他说明,对于C ++并不确定。

  • pp-指向指针的指针
  • p-指针
  • 数据-例子中指出的东西 x
  • 粗体 -只读变量

指针

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

指针指向

  1. pp p数据- int **pp;
  2. pp p数据-int ** const pp;
  3. pp p数据-int * const *pp;
  4. pp p 数据 -int const **pp;
  5. pp p数据-int * const * const pp;
  6. pp p 数据 -int const ** const pp;
  7. pp p 数据 -int const * const *pp;
  8. pp p 数据 -int const * const * const pp;
// Example 1
int x;
x = 10;
int *p = NULL;
p = &x;
int **pp = NULL;
pp = &p;
printf("%d\n", **pp);

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

// Example 3
int x;
x = 10;
int * const p = &x; // Definition must happen during declaration
int * const *pp = NULL;
pp = &p;
printf("%d\n", **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\n", **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\n", **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\n", **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\n", **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\n", **pp);

N级取消引用

只要继续前进,但人类可能会驱逐您。

int x = 10;
int *p = &x;
int **pp = &p;
int ***ppp = &pp;
int ****pppp = &ppp;

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

0
  1. const int*-指向常量int对象的指针。

您可以更改指针的值。您不能更改int指针指向的对象的值。


  1. const int * const-指向常量int对象的常量指针。

您不能更改指针的值或int指针所指向的对象的值。


  1. int const *-指向常量int对象的指针。

该语句等效于1。- const int*您可以更改指针的值,但不能更改int指针指向的对象的值。


实际上,有第四个选择:

  1. int * const-指向int对象的常量指针。

您可以更改指针指向的对象的值,但是不能更改指针本身的值。指针将始终指向同一int对象,但是int可以更改此对象的该值。


如果要确定某种类型的C或C ++构造,可以使用David Anderson提出的Clockwise / Spiral Rule;但不要与罗斯·安德森(Ross J. Anderson)制定的安德森(Anderson)规则混淆,这是非常不同的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.