在指针声明中放置星号

我最近决定我必须最终学习 C/C + + ,关于指针,有一件事我并不真正理解,或者更确切地说,它们的定义。

How about these examples:

  1. int* test;
  2. int *test;
  3. int * test;
  4. int* test,test2;
  5. int *test,test2;
  6. int * test,test2;

现在,根据我的理解,前三种情况都是一样的: Test 不是 int,而是指向 one 的指针。

第二组示例有点复杂。在情况4中,test 和 test2都是指向 int 的指针,而在情况5中,只有 test 是指针,test2是“ real”int。案例6呢?和第五个案子一样吗?

43357 次浏览

4、5和6是一回事,只有 测试是一个指针。如果你想要两个指针,你应该使用:

int *test, *test2;

或者,更好的办法是(把一切都说清楚) :

int* test;
int* test2;

在4、5和6中,test始终是一个指针,而 test2不是一个指针。空白在 C + + 中(几乎)从来都不重要。

星号周围的空白没有任何意义。这三个字母的意思是一样的:

int* test;
int *test;
int * test;

int *var1, var2”是一种邪恶的语法,只是为了迷惑人们,应该避免使用。它扩展到:

int *var1;
int var2;

指针是类型的修饰符。最好从右至左阅读它们,以便更好地理解星号是如何修改类型的。‘ int *’可以读作“指向 int 的指针”。在多个声明中,您必须指定每个变量都是一个指针,否则它将被创建为一个标准变量。

1、2和3) Test 是(int *)类型的,空格并不重要。

4,5 and 6) Test is of type (int *). Test2 is of type int. Again whitespace is inconsequential.

许多编码指南建议您只声明 每行一个变量。这样就避免了在问这个问题之前出现的任何混淆。我合作过的大多数 C + + 程序员似乎都坚持这一点。


A bit of an aside I know, but something I found useful is to read declarations backwards.

int* test;   // test is a pointer to an int

这开始工作得非常好,特别是当你开始声明 const 指针时,要弄清楚是指针是 const 还是指针指向的东西是 const 就变得非常困难。

int* const test; // test is a const pointer to an int


int const * test; // test is a pointer to a const int ... but many people write this as
const int * test; // test is a pointer to an int that's const

使用 “顺时针螺旋规则”来帮助解析 C/C + + 声明;

有三个简单的步骤可以遵循:

  1. 从未知元素开始,以螺旋/顺时针方向移动 当遇到以下元素时,将它们替换为 相应的英文声明:

    数组 X 的大小... 或数组未定义的大小..。

    函数传递 type1和 type2返回..。

    指向... 的指针。

  2. 继续以螺旋/顺时针方向方式执行此操作,直到所有代码都被覆盖。
  3. 总是首先解决括号中的任何问题!

此外,声明应该在可能的情况下在单独的声明中(这在绝大多数情况下是正确的)。

一个很好的经验法则是,许多人似乎通过以下方式掌握了这些概念: 在 C + + 中,许多语义意义是由关键字或标识符的左绑定派生出来的。

Take for example:

int const bla;

Const 适用于“ int”单词。指针的星号也是一样,它们适用于指针左边的关键字。实际的变量名呢?是的,它的残骸说明了这一点。

正如其他人提到的,4、5和6是相同的。通常,人们使用这些例子来证明 *属于变量而不是类型。虽然这是一个风格的问题,但是关于你是否应该这样思考和写作还存在一些争议:

int* x; // "x is a pointer to int"

或者这样:

int *x; // "*x is an int"

FWIW 我站在第一阵营,但是其他人之所以支持第二种形式,是因为它(主要)解决了这个特殊的问题:

int* x,y; // "x is a pointer to int, y is an int"

which is potentially misleading; instead you would write either

int *x,y; // it's a little clearer what is going on here

或者如果你真的想要两个指针,

int *x, *y; // two pointers

就我个人而言,我建议每行只保留一个变量,那么你喜欢哪种风格并不重要。

#include <type_traits>


std::add_pointer<int>::type test, test2;

C 语言的基本原理是,按照使用变量的方式声明变量

char *a[100];

*a[42]char。和 a[42]一个字符指针。因此,a是一个字符指针数组。

这是因为原始编译器编写者希望对表达式和声明使用相同的解析器。(对于语言设计选择来说,这不是一个非常合理的理由)

我想说的是,最初的约定是将星号放在指针名称侧(声明的右侧)

你可以遵循同样的规则,但是如果你把星星放在字体上,那就没什么大不了的。 记住,一致性是很重要的,所以无论你选择哪一边,星星总是在同一边。

在我看来,答案是两者都有,这取决于具体情况。 一般来说,IMO 最好在指针名称旁边加星号,而不是在指针类型旁边加星号:

int *pointer1, *pointer2; // Fully consistent, two pointers
int* pointer1, pointer2;  // Inconsistent -- because only the first one is a pointer, the second one is an int variable
// The second case is unexpected, and thus prone to errors

为什么第二种情况不一致?因为例如,int x,y;声明了两个相同类型的变量,但类型只在声明中提到一次。这创造了一个先例和预期的行为。int* pointer1, pointer2;与之不一致,因为它声明 pointer1为指针,但是 pointer2是一个整数变量。显然容易出错,因此应该避免(通过将星号放在指针名称旁边,而不是类型旁边)。

然而 ,有些 例外可能无法将星号放在对象名称的旁边(以及放在哪里很重要) ,而不会得到不想要的结果ーー例如:

MyClass *volatile MyObjName

void test (const char *const p) // const value pointed to by a const pointer

最后,在某些情况下,在 类型名称旁边加星号可能会被认为是 clearer,例如:

void* ClassName::getItemPtr () {return &item;} // Clear at first sight

这个谜题有三个部分。

第一部分是 C 和 C + + 中的空白通常不重要,除了分隔相邻的标记,否则不可区分。

During the preprocessing stage, the source text is broken up into a sequence of 代币 - identifiers, punctuators, numeric literals, string literals, etc. That sequence of tokens is later analyzed for syntax and meaning. The tokenizer is "greedy" and will build the longest valid token that's possible. If you write something like

inttest;

标记器只看到两个标记——标识符 inttest后面跟着标点符 ;。在这个阶段,它不会将 int识别为一个单独的关键字(这将在后面的过程中发生)。因此,为了将该行读作一个名为 test的整数的声明,我们必须使用空格来分隔标识符标记:

int test;

*字符不是任何标识符的一部分; 它本身是一个单独的标记(标点符号)。所以如果你写

int*test;

编译器看到4个单独的标记-int*test;。因此,空格在指针声明中并不重要

int *test;
int* test;
int*test;
int     *     test;

都是一样的解释。


这个难题的第二部分是 C 和 C + + 1中的声明实际上是如何工作的。声明被分成两个主要部分——一个 声明说明符序列(存储类说明符、类型说明符、类型限定符等) ,接着是一个以逗号分隔的 宣言者列表(可能已初始化)。在宣言里

unsigned long int a[10]={0}, *p=NULL, f(void);

声明说明符是 unsigned long int,声明符是 a[10]={0}*p=NULLf(void)。声明程序引入被声明事物的名称(apf)以及关于该事物的数组性、指针性和函数性的信息。声明程序还可以具有相关联的初始值设定项。

a的类型是“ unsigned long int的10个元素数组”。该类型由声明说明符和声明符的 密码完全指定,初始值由初始化器 ={0}指定。类似地,p的类型是“指向 unsigned long int的指针”,同样,该类型由声明说明符和声明符的组合指定,并初始化为 NULL。根据同样的推理,f的类型是“函数返回 unsigned long int”。

这是关键-没有“指向”type specifier,就像没有“数组”类型说明符,就像没有“函数返回”类型说明符。我们不能将数组声明为

int[10] a;

因为 []操作符的操作数是 a,而不是 int

int* p;

*的操作数是 p,而不是 int。但是由于间接运算符是一元运算符,空格不重要,如果我们这样写,编译器不会抱怨。但是,它是 一直都是解释为 int (*p);

因此,如果你写

int* p, q;

*的操作数是 p,因此它将被解释为

int (*p), q;

因此,所有的

int *test1, test2;
int* test1, test2;
int * test1, test2;

做同样的事情-在所有三种情况下,test1*的操作数,因此有类型“指向 int的指针”,而 test2有类型 int

声明函数可以任意复杂,可以有指针数组:

T *a[N];

你可以有指向数组的指针:

T (*a)[N];

you can have functions returning pointers:

T *f(void);

你可以有指向函数的指针:

T (*f)(void);

你可以有指向函数的指针数组:

T (*a[N])(void);

函数可以返回指向数组的指针:

T (*f(void))[N];

you can have functions returning pointers to arrays of pointers to functions returning pointers to T:

T *(*(*f(void))[N])(void); // yes, it's eye-stabby.  Welcome to C and C++.

然后是 signal:

void (*signal(int, void (*)(int)))(int);

上面写着

       signal                             -- signal
signal(                 )          -- is a function taking
signal(                 )          --   unnamed parameter
signal(int              )          --   is an int
signal(int,             )          --   unnamed parameter
signal(int,      (*)    )          --   is a pointer to
signal(int,      (*)(  ))          --     a function taking
signal(int,      (*)(  ))          --       unnamed parameter
signal(int,      (*)(int))         --       is an int
signal(int, void (*)(int))         --     returning void
(*signal(int, void (*)(int)))        -- returning a pointer to
(*signal(int, void (*)(int)))(   )   --   a function taking
(*signal(int, void (*)(int)))(   )   --     unnamed parameter
(*signal(int, void (*)(int)))(int)   --     is an int
void (*signal(int, void (*)(int)))(int);  --   returning void
    

而这仅仅触及了可能性的表面。但是请注意,数组性、指针性和函数性总是声明器的一部分,而不是类型说明符。

需要注意的一点是—— const可以同时修改指针类型和指向类型:

const int *p;
int const *p;

上述两种方法都将 p声明为指向 const int对象的指针。你可以写一个新的值到 p设置它指向一个不同的对象:

const int x = 1;
const int y = 2;


const int *p = &x;
p = &y;

但不能写入指向对象:

*p = 3; // constraint violation, the pointed-to object is const

但是,

int * const p;

声明 p作为指向非常数 intconst指针; 可以写入 p指向的内容

int x = 1;
int y = 2;
int * const p = &x;


*p = 3;

但是你不能设置 p指向不同的对象:

p = &y; // constraint violation, p is const

这就把我们带到了谜题的第三部分——为什么声明是这样构造的。

其目的是声明的结构应该紧密地反映代码中表达式的结构(“声明模仿使用”)。例如,假设我们有一个名为 ap的指向 int的指针数组,并且我们希望访问由 i‘ th 元素指向的 int值。我们将按如下方式访问该值:

printf( "%d", *ap[i] );

The 表情 *ap[i] has type int; thus, the declaration of ap is written as

int *ap[N]; // ap is an array of pointer to int, fully specified by the combination
// of the type specifier and declarator

声明器 *ap[N]具有与表达式 *ap[i]相同的结构。操作符 *[]在声明中的行为与在表达式中的行为相同-[]的优先级高于一元 *,因此 *的操作数是 ap[N](它被解析为 *(ap[N]))。

再举一个例子,假设我们有一个指向 int数组的指针,名为 pa,我们想要访问 i‘ th 元素的值。我们会写成

printf( "%d", (*pa)[i] );

表达式 (*pa)[i]的类型是 int,因此声明的写法是

int (*pa)[N];

Again, the same rules of precedence and associativity apply. In this case, we don't want to dereference the i'th element of pa, we want to access the i'th element of what pa 指向, so we have to explicitly group the * operator with pa.

The *, [] and () operators are all part of the 表情 in the code, so they are all part of the []0 in the declaration. The declarator tells you how to use the object in an expression. If you have a declaration like int *p;, that tells you that the expression *p in your code will yield an int value. By extension, it tells you that the expression p yields a value of type "pointer to int", or int *.


那么,类似于 cast 和 sizeof表达式这样的东西怎么样呢,我们使用 (int *)或者 sizeof (int [10])之类的东西?我该怎么读呢

void foo( int *, int (*)[10] );

没有声明符,难道 *[]操作符不是直接修改类型吗?

嗯,没有-仍然有一个声明器,只有一个空标识符(称为 abstract declarator)。如果我们用符号 λ 表示一个空标识符,那么我们可以将这些内容读取为 (int *λ)sizeof (int λ[10])

void foo( int *λ, int (*λ)[10] );

and they behave exactly like any other declaration. int *[10] represents an array of 10 pointers, while int (*)[10] represents a pointer to an array.


And now the opinionated portion of this answer. I am not fond of the C++ convention of declaring simple pointers as

T* p;

并认为它 bad practice的原因如下:

  1. 它与语法不一致;
  2. 它引入了混淆(从这个问题可以看出,这个问题的所有重复问题,关于 T* p, q;的意义的问题,关于 那些问题的所有重复问题等等) ;
  3. 它不是内部一致的-声明一个指针数组作为 T* a[N]是不对称的使用(除非你有写 * a[i]的习惯) ;
  4. 它不能应用于指针到数组或指针到函数类型(除非您创建 typedef,这样您就可以清楚地应用 T* p约定,它... ... 没有) ;
  5. 这样做的原因——“它强调了对象的指针性”——是伪造的。它不能应用于数组或函数类型,我认为强调这些特性同样重要。

最后,它只是表明了对这两种语言的类型系统如何工作的混乱思考。

分开声明项目有很好的理由,但是解决不好的实践(T* p, q;)不是其中之一。如果编写声明符 正确(T *p, q;) ,则不太可能引起混淆。

我认为这类似于故意编写所有简单的 for循环

i = 0;
for( ; i < N; )
{
...
i++;
}

句法上有效,但是令人困惑,而且意图很可能被误解。然而,T* p;约定在 C + + 社区中根深蒂固,我在自己的 C + + 代码中使用它,因为整个代码库的一致性是一件好事,但是每次我这样做的时候,它都让我发痒。


  1. 我将使用 C 术语-C + + 术语有一点不同,但概念大体相同。

我一直喜欢这样声明指针:

int* i;

我读这个是为了说“ i是 int 指针类型的”。如果每个声明只声明一个变量,则可以避免这种解释。

It is an uncomfortable truth, however, that this reading is 错了. C 程序设计语言,第2版。 (p. 94) explains the opposite paradigm, which is the one used in the C standards:

指针 ip的声明,

int *ip;

是作为一个助记符; 它说表达式 *ip是一个 int. The syntax of the declaration for a variable mimics the syntax of expressions in which the variable might appear. This reasoning 也适用于函数声明。例如,

double *dp, atof(char *);

says that in an expression *dp and atof(s) have values of type 而且 atof的参数是指向 char的指针。

根据 C 语言的推理,当你声明

int* test, test2;

您没有声明 int*类型的两个变量,而是引入了两个计算结果为 int类型的表达式,没有附加到内存中 int的分配。

编译器非常乐意接受以下内容:

int *ip, i;
i = *ip;

因为在 C 范例中,编译器只需要跟踪 *ipi类型。程序员应该跟踪 *ipi意义。在这种情况下,ip是未初始化的,因此程序员有责任在解除引用之前将它指向一些有意义的内容。