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

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


当前回答

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

下面是一个演示:

其他回答

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,它引用的值也可能在其他地方更改。

为了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);

最初的设计者多次将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指针。

没有人提到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;
  }
}

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

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

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