我总是搞砸如何正确使用const int*、const int*const和int const*。有没有一套规则来定义你能做什么和不能做什么?

我想知道在赋值、传递给函数等方面的所有注意事项。


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

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

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

常量int*==int常量*const int*const==int const*const

如果你想变得疯狂,你可以这样做:

int**-指向int指针的指针int**const-指向int指针的const指针int*const*-指向int的const指针int const**-指向常量int的指针int*const*const-指向int的常量指针的常量指针...

为了确保我们清楚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样式字符串中有一个指向常量字符的指针。您可以更改指向的字符串,但不能更改这些字符串的内容。当字符串本身位于程序的数据段中且不应更改时,这一点很重要。

bar是指向可更改值的常量或固定指针。这就像没有额外语法糖的引用。由于这一事实,通常您会在使用T*常量指针的地方使用引用,除非您需要允许NULL指针。


一般规则是const关键字应用于它前面的内容。异常,起始常量适用于以下内容。

constint*与intconst*相同,表示“指向常量int的指针”。const int*const与int const*const相同,表示“指向常量int的常量指针”。

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


常量的简单使用。

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

const int Constant1=96; 

将创建一个值为96的整数常量,无想象力地称为常量1。

这些常数对于程序中使用但在程序编译后不需要更改的参数是有用的。与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


就像每个人都指出的那样:

常量X*p、X*constp和常量X*const p之间有什么区别?

必须读取指针声明从右到左。const X*p表示“p指向常量的X”:X对象不能通过p更改。X*const p表示“p是指向非常量X的常量指针”:不能更改指针p本身,但可以通过p更改X对象。const X*const p表示“p是指向常量X的常量指针”:不能更改指针p本身,也不能通过p更改X对象。


我想这里已经回答了所有问题,但我只想补充一点,你应该小心typedefs!它们不仅仅是文本替换。

例如:

typedef char *ASTRING;
const ASTRING astring;

跨接的类型是char*const,而不是const char*。这是我总是倾向于将常量放在类型的右边,而从不放在开头的原因之一。


这个问题确切地说明了为什么我喜欢用我在问题中提到的在类型id可接受之后是常量的方式做事?

简而言之,我发现记住这个规则最简单的方法是“const”跟在它应用的对象后面。所以在你的问题中,“int const*”表示int是常量,而“int*const”表示指针是常量。

如果有人决定把它放在最前面(例如:“constint*”),作为这种情况下的一个特殊例外,它适用于后面的东西。

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


C++中围绕常量正确性还有许多其他微妙之处。我想这里的问题只是关于C,但我会给出一些相关的例子,因为标记是C++:

通常将字符串等大参数作为TYPE const&传递,这会防止对象被修改或复制。例子:TYPE&TYPE::operator=(const TYPE&rhs){…return*this;}但是TYPE&const是没有意义的,因为引用总是常量。您应该始终将不修改类的类方法标记为const,否则不能从TYPE const引用调用该方法。例子:bool TYPE::运算符==(const TYPE&rhs)const{…}在一些常见情况下,返回值和方法都应该是常量。例子:const TYPE TYPE::运算符+(const TYPE&rhs)const{…}事实上,const方法不能返回内部类数据作为对非常量的引用。因此,必须经常使用常量重载创建常量和非常量方法。例如,如果定义T const&operator[](unsigned i)const;,那么您可能还需要以下给出的非常量版本:内联T运算符[](无符号i)(&O){返回const_cast<char&>(static_cast<const TYPE&>(*this)[](i));}

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


常量参考:对变量(此处为int)的引用,该变量是常量。我们主要将变量作为引用传递,因为引用的大小比实际值小,但有一个副作用,这是因为它像实际变量的别名。通过对别名的完全访问,我们可能会意外地更改主变量,因此我们将其设置为常量以防止这种副作用。int var0=0;常量int&ptr1=var0;ptr1=8;//错误var0=6;//好啊常量指针一旦常量指针指向一个变量,那么它就不能指向任何其他变量。int var1=1;int var2=0;int*const ptr2=&var1;ptr2=&var2;//错误指向常量的指针不能改变所指向变量值的指针称为常量指针。int const*ptr3=&var2;*ptr3=4;//错误指向常量的常量指针指向常量的常量指针是一个既不能改变它所指向的地址,也不能改变保存在该地址的值的指针。int var3=0;int var4=0;const int*const ptr4=&var3;*ptr4=1;//错误ptr4=&var4;//错误


这很简单,但很棘手。请注意,我们可以将const限定符应用于任何数据类型(int、char、float等)。

让我们看看下面的例子。


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 p==>*p和p是只读的[p是指向常量整数的常量指针]。因为这里的指针p是只读的,所以声明和定义应该在同一位置。


int const*p const==>错误语句。编译器抛出语法错误。

int const int*p==>错误语句。编译器抛出语法错误。

int const const*p==>*p是只读的,与int const*p等效

int const*const p==>*p和p是只读的[p是指向常量整数的常量指针]。因为这里的指针p是只读的,所以声明和定义应该在同一位置。


在我读到C++大师斯科特·梅耶斯(Scott Meyers)的这本书之前,我和你一样心存疑虑。请参阅本书中的第三项,其中他详细介绍了如何使用const。

只需遵循以下建议

如果单词const出现在星号的左侧,则所指向的是常量如果单词const出现在星号的右侧,则指针本身是常量如果常量出现在两侧,则两者都是常量


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

下面是一个演示:


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

相反,让我们将类型命名为“pointer to type”;我叫它Ptr_:

template< class Type >
using Ptr_ = Type*;

现在Ptr_<char>是一个指向char的指针。

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

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


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

一般做法。尽可能使一切保持稳定。或者换一种说法,从一开始就让所有的常量都是常量,然后完全删除允许程序运行所需的最小常量集。这将大大有助于实现常量正确性,并有助于确保在人们尝试分配不应该修改的内容时不会引入细微的错误。

避免像瘟疫一样的const_cast<>。它有一两个合法的使用案例,但它们非常少。如果你试图改变一个常量对象,你会做得更好,找到第一步中声明它常量的人,并与他们讨论此事,以就应该发生的事情达成共识。

这很好地引导到作业中。只有当它是非常量时,才能将其赋值。如果您想分配给常量,请参见上文。请记住,在声明中int const*foo;和int*const bar;不同的东西是恒定的——这里的其他答案都很好地涵盖了这个问题,所以我就不赘述了。

功能参数:

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

通过引用传递:例如,void func(int&param)现在它确实起了作用。正如刚才声明的,func可以更改参数,任何调用站点都应该准备好处理结果。将声明更改为void func(int const&param)将更改约定,并保证func现在不能更改param,这意味着传入的内容将返回。正如其他人所指出的,这对于便宜地传递不想更改的大型对象非常有用。传递引用比按值传递大型对象要便宜得多。

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

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

在一天结束时,以上所有内容都证明了一个非常可靠的理由,即总是倾向于引用指针。他们只是更安全。


两边带有int的常量将使指针指向常量int:

const int *ptr=&i;

or:

int const *ptr=&i;

*后的const将使常量指针指向int:

int *const ptr=&i;

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

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

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

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

对我来说,常量的位置,即它相对于*是出现在左侧还是右侧,还是同时出现在左侧和右侧,有助于我理解实际含义。

*左边的常量表示指针指向的对象是常量对象。*右边的常量表示指针是常量指针。

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


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

pp-指针到指针p-指针数据-在示例x中指出的内容粗体-只读变量

指针

p数据-int*p;p数据-int常量*p;p数据-int*const p;p数据-int const*const p;

指向指针的指针

pp p数据-int**pp;pp p数据-int**const pp;pp p数据-int*const*pp;pp p数据-int常量**pp;pp p数据-int*const*const pp;pp p数据-int const**const pp;pp p数据-int const*const*pp;pp p数据-int常量*常量*常量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);

如果const在*的左边,它指的是值(不管它是const int还是int const)如果const位于*的右侧,则它引用指针本身可以同时使用

重要的一点是:constint*p并不意味着你所指的值是常量!!。这意味着您不能通过该指针更改它(意味着您不能分配$*p=…`)。值本身可能会以其他方式更改。如

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

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


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

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


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

不能更改指针的值,也不能更改指针指向的int对象的值。


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

此语句相当于1。constint*-可以更改指针的值,但不能更改指针指向的int对象的值。


实际上,还有第四种选择:

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

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


如果你想确定某种类型的C或C++结构,你可以使用David Anderson制定的顺时针/螺旋规则;但不要与罗斯·J·安德森(Ross J.Anderson)制定的安德森规则混淆,这是一个非常独特的规则。


很多人都答对了,我会在这里整理好,并在给出的答案中添加一些缺失的额外信息。

Const是C语言中的关键字,也称为限定符。Const罐应用于任何变量的声明,以指定其值不会改变

const int a=3,b;

a=4;  // give error
b=5;  // give error as b is also const int 

you have to intialize while declaring itself as no way to assign
it afterwards.

如何阅读?

只需从右到左阅读每一条语句即可顺利完成

3件主要事情

type a.    p is ptr to const int

type b.    p is const ptr to int 
 
type c.    p is const ptr to const int

[错误]

if * comes before int 

两种类型

1. const int *

2. const const int *

我们先看

主要类型1。常量int*

在3个地方安排3件事的方法3=6.

i.*开始时

*const int p      [Error]
*int const p      [Error]

二。开始时常量

const int *p      type a. p is ptr to const int 
const *int p      [Error]

iii.开始时int

int const *p      type a. 
int * const p     type b. p is const ptr to int

主要类型2。常量常量int*

在4个地方安排4件事情的方法,其中2件是相同的4件/2!=12

i.*开始时

* int const const p     [Error]
* const int const p     [Error]
* const const int p     [Error]
 

二。开始时为int

int const const *p      type a. p is ptr to const int
int const * const p     type c. p is const ptr to const int
int * const const p     type b. p is const ptr to int

iii.启动时的常量

const const int *p     type a.
const const * int p    [Error]

const int const *p      type a.
const int * const p     type c.

const * int const p    [Error]
const * const int p    [Error]

挤成一体

类型a.p是常量int(5)的指针

const int *p
int const *p

int const const *p
const const int  *p
const int  const *p

类型b.p是int(2)的常量指针

int * const p
int * const const p;

类型c.p是const ptr到const int(2)

int const * const p
const int * const p

只是很少的计算

1. const int * p        total arrangemets (6)   [Errors] (3)
2. const const int * p  total arrangemets (12)  [Errors] (6)

小小的额外

int常量*p,p2;

here p is ptr to const int  (type a.) 
but p2 is just const int please note that it is not ptr

int*常量p,p2;

similarly 
here p is const ptr to int  (type b.)   
but p2 is just int not even cost int

int常量*常量p,p2;

here p is const ptr to const int  (type c.)
but p2 is just const int. 

完成了


简单助记符:

类型指针<-*->指针对象名称


我喜欢把int*I看作是声明“I的解引用是int”;在这个意义上,constint*i表示“i的deref是constint”,而int*consti表示“consti的derref是int”。

(像这样思考的一个危险是,它可能会导致人们倾向于int const*i风格的声明,而人们可能会讨厌/拒绝这种声明)


要简单地记住:

若const在*之前,则值为常量。

如果const在*之后,则地址为常量。

如果const在*之前和之后都可用,则值和地址都是常量。

e.g.

int*常量变量//这里地址是恒定的。int常量*var//这里的值是恒定的。int常量*常量变量;//值和地址都是常量。


我在下面画了一张图片来解释这一点,也许会有所帮助。

int const v和const int v是相同的。


没有人提到Kernighan和Ritchie在其C书中指出的系统声明:

声明模拟表达式。

我将重复这一点,因为它非常重要,并且给出了一个清晰的策略来解析最复杂的声明:

声明模拟表达式。

声明包含的运算符与声明的标识符可以在后面出现的表达式相同,在表达式中具有相同的优先级。这就是为什么“顺时针螺旋法则”是错误的:求值顺序严格由操作员优先级决定,完全不考虑左、右或旋转方向。

以下是几个示例,按复杂性增加的顺序排列:

整数i;:当i按原样使用时,它是int类型的表达式。因此,i是int。int*p;:当p用*解引用时,表达式的类型为int。因此,p是指向int的指针。常量int*p;:当p用*解引用时,表达式的类型为const int。因此,p是指向const int的指针。int*常量p;:p是常量。如果此常量表达式用*解引用,则该表达式的类型为int。因此,p是int的常量指针。常量int*常量p;:p是常量。如果此常量表达式用*解引用,则该表达式的类型为const int。因此,p是指向const int的常量指针。

到目前为止,我们还没有遇到运算符优先级的任何问题:我们只是从右到左计算。当我们使用指针数组和指向数组的指针时,这会发生变化。你可能想打开一张备忘单。

int a[3];:当我们对a应用数组索引运算符时,结果是int。因此,a是int的数组。int*a[3];:这里,索引运算符具有更高的优先级,因此我们首先应用它:当我们将数组索引运算符应用于时,结果是int*。因此,a是指向int的指针数组。这并不罕见。int(*a)[3];:这里,运算符优先级被圆括号覆盖,与任何表达式中的完全相同。因此,我们首先取消引用。我们现在知道a是指向某种类型的指针*a、 解引用指针是该类型的表达式。当我们将数组索引运算符应用于*a时,我们获得了一个纯int,这意味着*a是一个由三个int组成的数组,a是指向该数组的指针。这在C++模板之外是相当罕见的,这就是为什么运算符优先级不适合这种情况的原因。请注意,使用这样的指针是如何实现其声明的模型:int i=(*a)[1];。括号必须先取消引用。int(*a)[3][2];:没有什么能阻止任何人拥有指向多维数组的指针,在这种情况下,圆形螺旋顺时针方向的建议变得毫无意义。

在现实生活中有时会出现函数指针。我们也需要括号,因为函数调用运算符(C++中的operator(),C中的简单语法规则)比解引用运算符*()具有更高的优先级,再次因为函数返回指针比函数指针更常见:

int*f();:首先调用函数,所以f是一个函数。调用必须被解引用才能产生int,因此返回值是指向int的指针。用法:int i=*f();。int(*fp)();:括号更改运算符应用程序的顺序。因为我们必须首先取消引用,所以我们知道fp是指向某个对象的指针。因为我们可以将函数调用运算符应用于*fp,所以我们知道(在C中)fp是指向函数的指针;在C++中,我们只知道它是定义了运算符()的对象。由于调用不接受参数并返回int,因此fp在C++中是指向具有该签名的函数的指针。(在C中,空的参数列表表示对参数一无所知,但未来的C规范可能会禁止这种过时的使用。)int*(*fp)();:当然,我们可以从指向的函数返回指向int的指针。int(*(*fp)())[3];:首先取消引用,因此是指针;接下来应用函数调用运算符,因此是指向函数的指针;再次取消引用返回值,因此指向返回指针的函数的指针;将索引运算符应用于:返回数组指针的函数指针。结果是一个int,因此指向函数的指针返回指向int数组的指针-所有的括号都是必要的:正如所讨论的,我们必须在发生任何其他事情之前,优先使用(*fp)取消函数指针的引用。显然,我们需要函数调用;而且由于函数返回一个指向数组的指针(而不是指向它的第一个元素!),所以我们必须在索引它之前取消引用它。我承认我写了一个测试程序来检查这一点,因为我不确定,即使使用这种防错方法;-)。这里是:

#include <iostream>
using namespace std;

int (*f())[3]
{
  static int arr[3] = {1,2,3};
  return &arr;
}

int (*(*fp)())[3] = &f;

int main()
{
  for(int i=0; i<3; i++)
  {
    cout << (*(*fp)())[i] << endl;
  }
}

请注意,声明模仿表达式是多么美妙!