ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

您可以使用 "Clockwise/Spiral Rule" 来解读大多数 C 和 C++ 声明。
cdecl.org 是一个很棒的网站,可以为您自动翻译 C 声明。
@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 这样易于阅读的语言中,等效的单行声明会是什么?
@MarkKCowan 在 Pascal 中,它类似于 function(x:^char):^int。函数类型意味着指向函数的指针,因此无需指定它,并且 Pascal 不强制 const 正确性。它可以从左到右阅读。
“const”左边的第一件事是常数。如果“const”是最左边的东西,那么它右边的第一个东西就是常数。

R
RobertS supports Monica Cellio

向后阅读(由 Clockwise/Spiral Rule 驱动):

int* - 指向 int 的指针

int const * - 指向 const int 的指针

int * const - 指向 int 的 const 指针

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

现在第一个 const 可以在类型的任一侧,所以:

常量 int * == int 常量 *

const int * const == int const * const

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

int ** - 指向 int 的指针

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

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

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

int * const * const - 一个 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 风格的字符串,其中有一个指向 const char 的指针。您可以更改指向的字符串,但不能更改这些字符串的内容。当字符串本身位于程序的数据段中并且不应更改时,这一点很重要。

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


我想附加一条经验法则,它可以帮助您记住如何发现“const”是否适用于指针或指向的数据:将语句拆分为星号,然后,如果 const 关键字出现在左侧(如'const int * foo') - 它属于指向数据,如果它在右侧部分 ('int * const bar') - 它与指针有关。
@Michael:感谢 Michael 这么简单的规则来记住/理解 const 规则。
@Jeffrey:只要没有括号,向后阅读就可以了。然后,嗯... use typedefs
+1,尽管更好的总结是:向后阅读指针声明,这意味着接近@Michael的声明:在第一个星号处停止正常的从左到右阅读。
@gedamial 确实如此,它工作正常,但是您必须在声明它的同时分配它(因为您不能重新分配“const 指针”)。 const int x = 0; const int *const px = &x; const int *const *const p = &px; 工作得很好。
F
Felipe Augusto

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

这是一个演示:

https://i.stack.imgur.com/sT6ng.png

https://i.stack.imgur.com/Zt0G2.png

https://i.stack.imgur.com/kXH8P.png

https://i.stack.imgur.com/UeqZO.png

https://i.stack.imgur.com/f5ftV.png


@Jan 复杂示例的链接没有权限。可以直接发到这里,还是去掉查看限制?
@Rog 它曾经拥有所有开放访问权限......不幸的是,我没有写这篇文章,我自己也没有访问权限。但是,这里有一个仍然有效的文章存档版本:archive.is/SsfMX
复杂的例子仍然只是从右到左,但包括以通常的方式解析括号。整个顺时针螺旋的事情并没有让这变得更容易。
终极示例:来自 archive.is/SsfMX 的 void (*signal(int, void (*fp)(int)))(int);
不要依赖这个规则。这不是普遍的。在某些情况下它会失败。
A
Azeem

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

例如:

typedef char *ASTRING;
const ASTRING astring;

astring 的类型是 char * const,而不是 const char *。这是我总是倾向于将 const 放在类型右侧的原因之一,而不是放在开头。


对我来说,这就是永远不要 typedef 指针的原因。我没有看到像 typedef int* PINT 这样的好处(我认为它来自 C 中的实践,并且许多开发人员一直在这样做)。太好了,我用 P 替换了那个 *,它不会加快打字速度,而且还引入了您提到的问题。
@Mephane - 我可以看到。然而,对我来说,为了继续使用特殊的语法规则(关于“const”放置),避免使用一个好的语言特性似乎有点倒退,而不是避免使用特殊的语法规则,这样你就可以安全地使用这个语言特性.
@Mephane PINT 确实是 typedef 的一个相当愚蠢的用法,特别是因为它让我认为系统存储使用啤酒作为内存。不过 typedef 对于处理指向函数的指针非常有用。
@KazDragon 谢谢!没有它,我会搞砸 Win32 api 中所有那些 typedef PVOIDLPTSTR 的东西!
@Mephane:在使用某些为接受类型而编写的遗留宏时,我不得不使用 pSomething 几次,但如果类型不是单个字母数字标识符,则会分解。 :)
D
Donald Duck

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

What’s the difference between const X* p, X* const p and const X* const p?

您必须从右到左阅读指针声明。 const X* p 的意思是“p 指向一个 const 的 X”:X 对象不能通过 p 改变。 X* const p 表示“p 是指向非 const 的 X 的 const 指针”:您不能更改指针 p 本身,但可以通过 p 更改 X 对象。 const X* const p 的意思是“p 是一个指向 const 的 X 的 const 指针”:你不能改变指针 p 本身,也不能通过 p 改变 X 对象。


不要忘记 const X* p; == X const * p;"p points to an X that is const": the X object can't be changed via p.
P
Peter Mortensen

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


A
Azeem

一般规则是 const 关键字适用于紧接在其前面的内容。例外,起始 const 适用于以下内容。

const int* 与 int const* 相同,意思是“指向常量 int 的指针”。

const int* const 与 int const* const 相同,意思是“指向常量 int 的常量指针”。

编辑:对于注意事项,如果 this answer 还不够,您能否更准确地说明您想要什么?


C
Community

这个问题准确地说明了为什么我喜欢按照我在问题 is const after type id acceptable? 中提到的方式做事

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

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

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


我在这个问题上被撕裂了。从逻辑上讲,这是有道理的。然而,大多数 c++ 开发人员会编写 const T* 并且它变得更加自然。无论如何,您多久使用一次 T* const,通常引用就可以了。当我想要一个 boost::shared_ptr<const T> 时,我被这一切所困扰,而是写了 const boost::shared_ptr<T>。在略有不同的上下文中的相同问题。
实际上,我使用常量指针比使用常量更频繁。此外,您必须考虑在存在指向指针的指针(等)的情况下您将如何反应。诚然,这些情况很少见,但是以一种可以轻松处理这些情况的方式来思考事情会很好。
将 const 放在类型右侧的另一个好处是,现在任何 const 左侧的所有内容都是 const 的类型,而其右侧的所有内容实际上都是 const。以 int const * const * p; 为例。不,我通常不会那样写,这只是一个例子。第一个const:类型int,而int即const是const指针的内容,即p的内容。第二个 const:type 是指向 const int 的指针,const oblect 是 p 的内容
F
Felipe Augusto

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


F
Fabio says Reinstate Monica

这很简单但很棘手。请注意,我们可以将 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 是只读的,所以声明和定义应该在同一个地方。


r
rgk

在我遇到 C++ 大师 Scott Meyers 的这个 book 之前,我和你有同样的疑问。请参阅本书的第三项,其中他详细介绍了使用 const

只需遵循此建议

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


M
MoBaShiR

以简单的方式记住:

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

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

如果 const 在 * 之前和之后都可用,则 value 和 address 都是常量。

例如

int * 常量变量; //这里的地址是不变的。 int 常量 * 变量; //这里的值是常数。 int 常量 * 常量变量; // 值和地址都是常量。


Y
Yun

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

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

template< class Type >
using Ptr_ = Type*;

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

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

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


你有第一句话的报价吗?
@sp2danny:在谷歌上搜索“C 语法失败的实验”只会咳出一些对 Bjarne Stroustrup 的采访,他在那个方向表达了他的观点,例如在 Slashdot 采访中“我认为 C 声明符语法是一个失败的实验”。因此,对于 C 的原始设计者的观点,我没有任何参考依据。我想可以通过足够强大的研究工作来找到它,或者仅仅通过询问他们就可以反驳,但我认为现在这样更好。索赔的那一部分,仍未决定,可能是真的:)
“C 和 C++ 声明语法一再被原始设计者描述为失败的实验。” C 错误 请更改您对 C 的句子或提供一些引用。
@Stargateur:显然你已经阅读了前面的评论,发现了一些你可以用来学究气的东西。祝你生活顺利。无论如何,像我这样的老前辈记得很多,如果不进行非常耗时的研究,我们就无法证明。你可以相信我的话。
@Stargateur "Sethi (...) observed that many of the nested declarations and expressions would become simpler if the indirection operator had been taken as a postfix operator instead of prefix, but by then it was too late to change." 来自 DMR。当然,DMR 并没有发明 const 和 volatile 关键字,它们来自 C++ / X3J11,正如该页面所证明的那样。
s
sri

对我来说,const 的位置,即它相对于 * 出现在 LEFT 或 RIGHT 或同时出现在 LEFT 和 RIGHT 上,这有助于我弄清楚实际含义。

* LEFT 的 const 表示指针指向的对象是 const 对象。 * 右侧的 const 表示指针是 const 指针。

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

https://i.stack.imgur.com/aijhB.png


指针的“重新分配”和“修改”有什么区别?
J
Jeff Burdges

在 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;,那么您可能还需要由以下给出的非 const 版本:inline T& operator[] (unsigned i) { return const_cast( static_cast (*this)[](i) ); }

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


F
Felipe Augusto

两边都有 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;

b
blue_note

如果 const 在 * 的左边,它指的是值(不管是 const int 还是 int const)

如果 const 在 * 的右边,它指的是指针本身

它可以同时是两者

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

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

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


d
dgnuff

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

一般做法。尽量做到const。或者换一种说法,让所有内容都以 const 开头,然后准确删除允许程序运行所需的最小 const 集。这将大大有助于实现 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 它会永远不会在 param 中收到 nullptr

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

归根结底,以上所有内容都提供了一个非常可靠的案例,即始终更喜欢对指针的引用。他们只是更安全。


F
Felipe Augusto

只是为了在其他解释之后 C 的完整性,对于 C++ 不确定。

pp - 指向指针的指针

- 指针

数据 - 在示例 x 中指出的东西

粗体 - 只读变量

指针

数据 - int *p;

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

级取消引用

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

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

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

R
RobertS supports Monica Cellio

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

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

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

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

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

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

实际上,还有第四个选项:

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

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

如果您想确定某种类型的 C 或 C++ 构造,您可以使用 David Anderson 制作的 Clockwise/Spiral Rule;但不要与 Ross J. Anderson 的 Anderson`s Rule 混淆,这是非常不同的。


l
l.k

简单的助记符:

type 指针 <- * ->指针name

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

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


s
slh

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

int const vconst int v 是相同的。

https://i.stack.imgur.com/JwWMu.jpg


A
Abhishek Mane

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

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.

如何阅读 ?

只需从右到左阅读每个语句都可以顺利运行

主要内容

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

一世。 * 在开始时

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

ii.常量在开始

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. const const int*

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

一世。 * 在开始时

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

ii. 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 是指向 const int (5) 的指针

const int *p
int const *p

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

b型。 p 是 const ptr 到 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. 

完成的