我总是搞砸如何正确使用 const int*
, const int * const
和 int const *
。是否有一套规则来定义你能做什么和不能做什么?
我想知道在分配、传递给函数等方面的所有注意事项。
原文由 ultraman 发布,翻译遵循 CC BY-SA 4.0 许可协议
没有人提到 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 指针。到目前为止,我们还没有遇到运算符优先级的任何问题:我们只是从右到左进行评估。当我们对指针数组和指向数组的指针感兴趣时,情况就会改变。您可能希望打开 备忘单。
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++ 模板之外相当少见,这就是运算符优先级不适合这种情况的原因。请注意,如何使用这样的指针是其声明的模型: int i = (*a)[1];
。括号是必须首先取消引用的。int (*a)[3][2];
:没有什么可以阻止任何人拥有指向多维数组的指针,在这种情况下,圆形螺旋顺时针建议变得明显是无稽之谈。现实生活中有时会出现的东西是函数指针。我们也需要括号,因为函数调用运算符( operator()()
在 C++ 中,简单的语法规则在 C 中)比取消引用具有更高的优先级 operator*()
,同样因为拥有函数更常见返回指针而不是指向函数的指针:
int *f();
:首先调用函数,所以 f
是一个函数。调用必须取消引用才能产生一个 int,因此返回值是一个指向 int 的指针。用法: int i = *f();
。
int (*fp)();
:括号更改运算符应用程序的顺序。因为我们必须首先取消引用,所以我们知道 fp
是指向某物的指针。因为我们可以将函数调用运算符应用于 *fp
我们知道(在 C 中) fp
是指向函数的指针;在 C++ 中,我们只知道定义了 operator()()
的东西。由于调用不带参数并返回一个 int, fp
在 C++ 中是指向具有该签名的函数的指针。 (在 C 中,空参数列表表示对参数一无所知,但未来的 C 规范可能会禁止这种过时的使用。)
int *(*fp)();
:当然我们可以从指向的函数返回指向 int 的指针。
int (*(*fp)())[3];
:首先取消引用,因此是指针;应用函数调用运算符 next,因此是指向函数的指针;再次取消引用返回值,因此指向返回指针的函数的指针;将索引运算符应用于 : 指向函数的指针,返回指向数组的指针。结果是一个整数,因此指向函数的指针返回指向整数数组的指针。-
所有括号都是必需的:如前所述,我们必须优先使用 (*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;
}
}
注意声明模仿表达的方式多么美妙!
原文由 Peter - Reinstate Monica 发布,翻译遵循 CC BY-SA 4.0 许可协议
3 回答2k 阅读✓ 已解决
2 回答3.9k 阅读✓ 已解决
2 回答3.2k 阅读✓ 已解决
1 回答3.2k 阅读✓ 已解决
1 回答2.7k 阅读✓ 已解决
3 回答3.4k 阅读
1 回答1.6k 阅读✓ 已解决
向后阅读(由 Clockwise/Spiral Rule 驱动):
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 *
- 一个指向 const 的指针,指向一个 intint const **
- 指向 const int 的指针int * const * const
- 一个指向 int 的 const 指针并确保我们清楚
const
的含义:foo
是一个指向常量整数的变量指针。这使您可以更改指向的内容,但不能更改指向的值。最常见的情况是 C 风格的字符串,其中您有一个指向const char
的指针。您可以更改指向的字符串,但不能更改这些字符串的内容。当字符串本身位于程序的数据段中并且不应更改时,这一点很重要。bar
是指向可以更改的值的常量或固定指针。这就像一个没有额外语法糖的参考。由于这个事实,通常你会使用一个引用,你会使用T* const
指针,除非你需要允许NULL
指针。