我总是搞砸如何正确使用const int*、const int*const和int const*。有没有一套规则来定义你能做什么和不能做什么?
我想知道在赋值、传递给函数等方面的所有注意事项。
我总是搞砸如何正确使用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指针。
其他回答
反向阅读(由顺时针/螺旋法则驱动):
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指针。
就像每个人都指出的那样:
常量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对象。
这很简单,但很棘手。请注意,我们可以将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是只读的,所以声明和定义应该在同一位置。
简单助记符:
类型指针<-*->指针对象名称
我喜欢把int*I看作是声明“I的解引用是int”;在这个意义上,constint*i表示“i的deref是constint”,而int*consti表示“consti的derref是int”。
(像这样思考的一个危险是,它可能会导致人们倾向于int const*i风格的声明,而人们可能会讨厌/拒绝这种声明)
没有人提到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;
}
}
请注意,声明模仿表达式是多么美妙!