[英]What is the difference between const int*, const int * const, and int const *?
我总是弄乱如何正确使用const int*
、 const int * const
和int const *
。 是否有一套规则定义您可以做什么和不能做什么?
我想知道在分配、传递给函数等方面所有该做的和不该做的。
向后阅读(由顺时针/螺旋规则驱动):
int*
- 指向 int 的指针int const *
- 指向 const int 的指针int * const
- 指向 int 的 const 指针int const * const
- const 指向 const int 的指针现在第一个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 指针的 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
是指向可以更改的值的常量或固定指针。 这就像一个没有额外语法糖的引用。 由于这个事实,除非您需要允许NULL
指针,否则通常您会在使用T* const
指针的地方使用引用。
对于那些不知道顺时针/螺旋规则的人:从变量的名称开始,顺时针移动(在这种情况下,向后移动)到下一个指针或键入。 重复直到表达式结束。
这是一个演示:
我认为这里已经回答了所有问题,但我只想补充一点,您应该提防typedef
! 它们不仅仅是文本替换。
例如:
typedef char *ASTRING;
const ASTRING astring;
astring
的类型是char * const
,而不是const char *
。 这就是我总是倾向于将const
放在类型右侧的一个原因,而不是在开始时。
就像几乎每个人都指出的那样:
const X* p
、 X* const p
和const X* const p
之间有什么区别?
您必须从右到左阅读指针声明。
const X* p
意思是“p 指向一个 const 的 X”:X 对象不能通过 p 改变。
X* const p
意思是“p 是一个指向非常量 X 的常量指针”:你不能改变指针 p 本身,但是你可以通过 p 改变 X 对象。
const X* const p
意思是“p 是一个指向const X* const p
的常量指针”:你不能改变指针 p 本身,也不能通过 p 改变 X 对象。
常数参考:
对变量(此处为 int)的引用,该变量是常量。 我们主要是将变量作为引用传递,因为引用的大小比实际值要小,但是有一个副作用,那就是它就像是实际变量的别名。 我们可能会通过对别名的完全访问而意外更改主变量,因此我们将其设为常量以防止这种副作用。
int var0 = 0; const int &ptr1 = var0; ptr1 = 8; // Error var0 = 6; // OK
常量指针
一旦常量指针指向一个变量,它就不能指向任何其他变量。
int var1 = 1; int var2 = 0; int *const ptr2 = &var1; ptr2 = &var2; // Error
指向常量的指针
一个不能改变它所指向的变量值的指针被称为常量指针。
int const * ptr3 = &var2; *ptr3 = 4; // Error
常量指针
指向常量的常量指针是一个指针,它既不能改变它指向的地址,也不能改变保存在该地址的值。
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* const
与int const* const
相同,表示“指向常量 int 的常量指针” 。编辑:对于该做和不该做的事情,如果这个答案还不够,你能更准确地说明你想要什么吗?
这个问题恰恰说明了为什么我喜欢按照我在问题中提到的方式做事的原因是 const after type id 可以接受?
简而言之,我发现记住规则的最简单方法是“const”在它适用的事物之后。 所以在你的问题中,“int const *”意味着int是常量,而“int * const”意味着指针是常量。
如果有人决定把它放在最前面(例如:“const int *”),作为这种情况下的特殊例外,它适用于它之后的事物。
许多人喜欢使用这个特殊的例外,因为他们认为它看起来更好。 我不喜欢它,因为它是一个例外,从而使事情变得混乱。
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
我和你有同样的疑问,直到我看到 C++ 大师 Scott Meyers 写的这本书。 请参阅本书中的第三项,他详细介绍了如何使用const
。
只要遵循这个建议
const
出现在星号的左侧,则指向的是常量const
出现在星号的右侧,则指针本身是常量const
出现在两边,则两者都是常数这很简单但很棘手。 请注意,我们可以将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 p1
==> *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 和 C++ 声明语法一再被原始设计者描述为失败的实验。
相反,让我们将类型命名为“指向Type
指针”; 我将其称为Ptr_
:
template< class Type >
using Ptr_ = Type*;
现在Ptr_<char>
是一个指向char
的指针。
Ptr_<const char>
是一个指向const char
的指针。
而const Ptr_<const char>
是一个指向const char
的const
指针。
在 C++ 中,还有许多其他关于 const 正确性的微妙点。 我想这里的问题只是关于 C,但我会给出一些相关的例子,因为标签是 C++:
您经常将像字符串这样的大参数作为TYPE const &
传递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 和非常量方法。 例如,如果你定义了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 &
引用也可能见证它所引用的值在其他地方被改变。
两侧带有 int 的 const 将指向常量 int :
const int *ptr=&i;
或者:
int const *ptr=&i;
*
之后的const
将使常量指针指向 int :
int *const ptr=&i;
在这种情况下,所有这些都是指向常量 integer 的指针,但这些都不是常量指针:
const int *ptr1=&i, *ptr2=&j;
在这种情况下,所有都是指向常量 integer 的指针,ptr2 是指向常量 integer 的常量指针。 但 ptr1 不是常量指针:
int const *ptr1=&i, *const ptr2=&j;
这主要解决了第二行:最佳实践、分配、函数参数等。
一般做法。 尽量使一切都保持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 ¶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(我认为),你会得到它的程序范围,即即使在跨越源代码模块边界的函数调用中。
归根结底,以上所有内容都提供了一个非常可靠的案例,即始终更喜欢对指针的引用。 他们只是更安全。
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
这主要用于函数签名,以保证函数不会意外更改传递的参数。
如果 const 在 * 之前,则 value 是常量。
如果 const 在 * 之后,则地址是常量。
如果 const 在 * 之前和之后都可用,则 value 和 address 都是常量。
例如
int * const var; //这里地址是常量。
int const * var; //这里的值是常数。
int const * const var; // 值和地址都是常量。
只是为了 C 遵循其他解释的完整性,不确定 C++。
x
int *p;
int const *p;
int * const p;
int const * const p;
int **pp;
int ** const pp;
int * const *pp;
int const **pp;
int * const * const pp;
int const ** const pp;
int const * const *pp;
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);
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 制定的顺时针/螺旋规则; 但不要与罗斯 J. 安德森 (Ross J. Anderson) 制定的安德森规则( Anderson’s Rule)混淆,后者是非常不同的。
简单的助记符:
type
指针 <- *
-> 指针name
我喜欢将int *i
视为声明“对i
的取消引用是int
”; 在这个意义上, const int *i
是指“的DEREF i
是const int
”,而int *const i
的意思是“的DEREF const i
是int
”。
(这样思考的一个危险是它可能会导致偏爱int const *i
风格的声明,人们可能会讨厌/不允许)
没有人提到 Kernighan 和 Ritchie 在他们的 C 书中指出的系统底层声明:
声明模仿表达式。
我会重复这一点,因为它非常重要,并且提供了一个清晰的策略来解析最复杂的声明:
声明包含与声明的标识符稍后可以出现的表达式相同的运算符,它们在表达式中具有相同的优先级。 这就是“顺时针螺旋规则”错误的原因:评估顺序严格由运算符优先级决定,完全无视左、右或旋转方向。
以下是一些示例,按照复杂程度递增的顺序排列:
int i;
:当i
按原样使用时,它是int
类型的表达式。 因此, i
是一个整数。int *p;
:当p
被*
取消引用时,表达式的类型为int
。 因此, p
是指向 int 的指针。const int *p;
:当p
被*
取消引用时,表达式的类型为const int
。 因此, p
是指向 const int 的指针。int *const p;
: p
是常量。 如果使用*
取消引用此常量表达式,则该表达式的类型为int
。 因此, p
是指向 int 的 const 指针。const int *const p;
: p
是常量。 如果用*
取消引用此常量表达式,则该表达式的类型为const int
。 因此, p
是指向 const int 的 const 指针。到目前为止,我们还没有任何关于运算符优先级的问题:我们只是简单地从右到左求值。 当我们使用 arrays 的指针和指向 arrays 的指针时,这种情况会发生变化。您可能需要打开一个 备忘单。
int a[3];
:当我们将数组索引运算符应用于a
时,结果是一个int
。 因此, a
是一个int数组。int *a[3];
:这里索引运算符有更高的优先级,所以我们首先应用它:当我们将数组索引运算符应用于a
时,结果是一个int *
。 因此, a
是一个指向 int 的指针数组。 这并不罕见。int (*a)[3];
:这里的运算符优先级被圆括号覆盖,就像在任何表达式中一样。 因此,我们首先取消引用。 我们现在知道a
是指向某种类型的指针。 *a
,取消引用的指针,是该类型的表达式。 当我们将数组索引运算符应用于*a
时,我们获得了一个普通的 int,这意味着*a
是一个包含三个 int 的数组,而a
是指向该数组的指针。 这在 C++ 模板之外相当罕见,这就是运算符优先级不适合这种情况的原因。 请注意这样一个指针的使用方式是 model 的声明: int i = (*a)[1];
. 括号必须首先取消引用。int (*a)[3][2];
: 没有什么能阻止任何人获得指向多维 arrays 的指针,在这种情况下,循环螺旋顺时针建议变得明显无意义。 现实生活中有时会出现的事情是 function 指针。 我们在那里也需要括号,因为 function 调用运算符(C++ 中的operator()()
,C 中的简单语法规则)比解引用operator*()
具有更高的优先级,同样是因为函数返回指针比指针更常见职能:
int *f();
: Function 首先调用,所以f
是一个 function。调用必须被解引用才能得到一个 int,所以返回值是一个指向 int 的指针。 用法: int i = *f();
.
int (*fp)();
: 括号更改运算符应用程序的顺序。 因为我们必须首先取消引用,所以我们知道fp
是指向某物的指针。 因为我们可以将 function 调用运算符应用于*fp
,所以我们知道(在 C 中) fp
是指向 function 的指针; 在 C++ 中,我们只知道它是为其定义了operator()()
的东西。 由于调用不带参数并返回一个 int,因此fp
在 C++ 中是指向具有该签名的 function 的指针。 (在C中,一个空的参数列表表示对这些参数一无所知,但未来的C规范可能会禁止这种过时的使用。)
int *(*fp)();
: 当然我们可以从指向的 function 返回指向 int 的指针。
int (*(*fp)())[3];
:首先取消引用,因此是一个指针; 接下来应用 function 调用运算符,因此指向 function; 再次取消引用返回值,因此指向 function 的指针返回一个指针; 将索引运算符应用于:指向 function 的指针返回指向数组的指针。 结果是一个 int,因此指向 function 的指针返回指向 int 数组的指针。-
所有括号都是必需的:如前所述,我们必须在发生任何其他事情之前优先使用(*fp)
取消引用 function 指针。 显然,我们需要调用 function; 并且由于 function 返回一个指向数组的指针(而不是指向它的第一个元素)。 我们必须在索引它之前取消引用它,我承认我写了一个测试程序来检查它,因为我不确定; 即使使用这种万无一失的方法。-):它是:
#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;
}
}
请注意声明模仿表达式的方式多么漂亮!
很多人回答正确,我将在这里组织好并添加一些在给定答案中缺少的额外信息。
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. 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]
三、 开始时的 int
int const *p type a.
int * const p type b. p is const ptr to int
主要类型 2. const const int *
在 4 个地方排列 4 个东西的方法,其中 2 个地方相似 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
三、 开始时为常量
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 是 ptr 到 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 const * 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 * const p,p2 ;
similarly
here p is const ptr to int (type b.)
but p2 is just int not even cost int
int const * const p,p2 ;
here p is const ptr to const int (type c.)
but p2 is just const int.
完成的
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.