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

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

我想知道所有的做的和所有的不做的任务,传递给函数,等等。

661169 次浏览

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

  • 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 *-指向int的const指针的指针
  • 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指针。

一般规则是const关键字适用于它前面的内容。例外,起始const适用于后面的内容。

  • const int*int const*相同,表示"指向常量int的指针"
  • const int* constint const* const相同,表示"指向常量int的常量指针"

编辑:对于Dos和Don'ts,如果这个答案还不够,你能更准确地知道你想要什么吗?

简单使用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

几乎所有人都指出:

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

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

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

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

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

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

例如:

typedef char *ASTRING;const ASTRING astring;

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

这个问题显示了正是为什么我喜欢按照我在问题1中提到的方式做事

简而言之,我发现记住规则的最简单方法是“const”去之后它适用的东西。所以在你的问题中,“int const*”意味着int是常量,而“int*const”意味着指针是常量。

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

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

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。例子:

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

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

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

    内联T&运算符[](无符号i){返回const_cast(static_cast(*this)[](i));

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

  1. 不断引用:

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

    int var0 = 0;const int &ptr1 = var0;ptr1 = 8; // Errorvar0 = 6; // OK
  2. Constant pointers

    Once a constant pointer points to a variable then it cannot point to any other variable.

    int var1 = 1;int var2 = 0;
    int *const ptr2 = &var1;ptr2 = &var2; // Error
  3. Pointer to constant

    A pointer through which one cannot change the value of a variable it points is known as a pointer to constant.

    int const * ptr3 = &var2;*ptr3 = 4; // Error
  4. Constant pointer to a constant

    A constant pointer to a constant is a pointer that can neither change the address it's pointing to and nor can it change the value kept at that address.

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

它很简单,但很棘手。请注意,我们可以将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 p==>*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++大师斯科特·迈耶斯的第一条。请参阅本书中的第三项,他详细讨论了使用const

听从这个建议

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

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

下面是一个demo:

指向int的指针

const指向int const的指针

指向int const的指针

指向const int的指针

常量指针到int

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

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

template< class Type >using Ptr_ = Type*;

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

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

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

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

一般做法。尽量把所有东西都设置为0。或者换句话说,一开始就设置为0,然后删除程序运行所需的最小0。这将对实现常量正确性有很大帮助,并将有助于确保当人们尝试分配到他们不应该修改的东西时不会引入细微的错误。

避免const_cast<>像瘟疫一样。有一两个合法的用例,但它们很少。如果你试图改变一个const对象,最好找到第一步宣布它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,除非其他合同保证funcparam中永远不会收到nullptr

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

在一天结束的时候,上面的所有内容都提供了一个非常可靠的案例,即总是更喜欢引用而不是指针。

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

const int *ptr=&i;

或:

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;

对我来说,const的位置,即它相对于*是否出现在左边或右边,或者在左边和右边,帮助我弄清楚实际的含义。

  1. const*的LEFT表示指针指向的对象是const对象。

  2. *右边的const表示指针是const指针。

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

在此处输入图片描述

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

  • pp-指针到指针
  • p-指针
  • data-指向的东西,在示例x
  • 大胆-只读变量

指针

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

指向指针的指针

  1. pp p data-int **pp;
  2. pp p数据-int ** const pp;
  3. ppp数据-int * const *pp;
  4. pp p数据-int const **pp;
  5. ppp数据-int * const * const pp;
  6. pp p数据-int const ** const pp;
  7. PPp数据-int const * const *pp;
  8. ppp数据-int const * const * const pp;
// Example 1int x;x = 10;int *p = NULL;p = &x;int **pp = NULL;pp = &p;printf("%d\n", **pp);
// Example 2int x;x = 10;int *p = NULL;p = &x;int ** const pp = &p; // Definition must happen during declarationprintf("%d\n", **pp);
// Example 3int x;x = 10;int * const p = &x; // Definition must happen during declarationint * const *pp = NULL;pp = &p;printf("%d\n", **pp);
// Example 4int const x = 10; // Definition must happen during declarationint const * p = NULL;p = &x;int const **pp = NULL;pp = &p;printf("%d\n", **pp);
// Example 5int x;x = 10;int * const p = &x; // Definition must happen during declarationint * const * const pp = &p; // Definition must happen during declarationprintf("%d\n", **pp);
// Example 6int const x = 10; // Definition must happen during declarationint const *p = NULL;p = &x;int const ** const pp = &p; // Definition must happen during declarationprintf("%d\n", **pp);
// Example 7int const x = 10; // Definition must happen during declarationint const * const p = &x; // Definition must happen during declarationint const * const *pp = NULL;pp = &p;printf("%d\n", **pp);
// Example 8int const x = 10; // Definition must happen during declarationint const * const p = &x; // Definition must happen during declarationint const * const * const pp = &p; // Definition must happen during declarationprintf("%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*向右,它指的是指针本身
  • 两者都可以同时存在

重要的一点:const int *p并不意味着你所指的值是恒定的!!。这意味着你不能改变它通过那个指针(意思是,你不能赋值$*p=…')。值本身可以通过其他方式改变。例如

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

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

  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提出的顺时针/螺旋规则;但不要与Ross J. Anderson提出的安德森规则混淆,这是非常不同的东西。

很多人回答正确,我将在这里组织得很好,并在给出的答案中添加一些缺少的额外信息。

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

const int a=3,b;
a=4;  // give errorb=5;  // give error as b is also const int
you have to intialize while declaring itself as no way to assignit 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. const int*

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

i.*at start

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

ii.开始时的const

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

iii. int at start

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

主要类型2. const const int*

在2个相似的4个地方安排4件事的方法4! /2!=12

i.*at start

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

ii. int at start

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

ii.开始时常量

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是对const int(5)的ptr

const int *pint const *p
int const const *pconst const int  *pconst int  const *p

类型b。p是const ptr到int(2)

int * const pint * const const p;

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

int const * const pconst int * const p

只是小小的计算

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

Little Extra

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

int*const p, p2;

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

int const*const p, p2;常量p, p2常量p, p2常量p, p2常量p, p2常量p, p2常量p, p2常量p, p2常量p, p2常量p, p2常量p, p2常量p, p2常量p, p2常量p, p2常

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

已完结

简单助记符:

type指针<-*->指针name


我喜欢将int *i视为声明“i的取消引用是int”;在这个意义上,const int *i意味着“i的取消引用是const int”,而int *const i意味着“const i的取消引用是int”。

(这样想的一个危险是它可能会导致有利于int const *i风格的声明,人们可能会讨厌/不允许)

以简单的方式记住:

如果const在*之前,则value为常量。

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

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

e. g.

  1. int*const var;//这里的地址是常量。

  2. int const*var;//这里的值是常量。

  3. int const*const var;//值和地址都是常量。

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

int const vconst int v是相同的。

输入图片描述

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

声明模仿表达式。

我将重复这一点,因为它非常重要,并提供了一个清晰的策略来解析即使是最复杂的声明:

声明模仿表达式。

声明包含与声明标识符稍后可以出现的表达式相同的运算符,它们在表达式中的优先级相同。这就是为什么“顺时针螺旋规则”是错误的:计算顺序严格由运算符优先级决定,完全无视左、右或旋转方向。

以下是一些例子,按照增加复杂性的顺序:

  • #0:当i按原样使用时,它是int类型的表达式。因此,i是int。
  • #0:当p*取消引用时,表达式的类型为int。因此,p是指向int的指针。
  • #0:当p*取消引用时,表达式的类型为const int。因此,p是指向const int的指针。
  • #0p是const。如果使用*取消引用此常量表达式,则表达式的类型为int。因此,p是指向int的const指针。
  • #0p是const。如果使用*取消引用此常量表达式,则表达式的类型为const int。因此,p是指向const int的const指针。

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

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

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

  • #0:先调用函数,所以f是一个函数。调用必须取消引用才能产生int,所以返回值是指向int的指针。用法:int i = *f();

  • #0:括号改变了运算符应用的顺序。因为我们必须先取消引用,我们知道fp是指向某物的指针。因为我们可以将函数调用操作符应用于*fp,我们知道(在C中)fp是指向函数的指针;在C++我们只知道它是operator()()定义的东西。由于调用不接受参数并返回一个int,因此fpC++指向具有该签名的函数的指针。(在C中,空参数列表表示对参数一无所知,但未来的C规范可能会禁止这种过时的使用。)

  • #0:当然,我们可以从指向的函数返回指向int的指针。

  • #0:首先取消引用,因此是指针;接下来应用函数调用操作符,因此是指向函数的指针;再次取消引用返回值,因此是指向返回指针的函数的指针;将索引运算符应用于即:指针到返回数组指针的函数。结果是一个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;}}

请注意,声明模仿表达式是多么漂亮!