在指针声明中放置星号

Mic*_*tum 86 c c++ pointers declaration

我最近决定我必须最终学习C/C++,有一件事我不太了解指针,或者更确切地说,他们的定义.

这些例子怎么样:

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

现在,根据我的理解,前三个案例都是相同的:测试不是一个int,而是一个指针.

第二组示例有点棘手.在case 4中,test和test2都是指向int的指针,而在case 5中,只有test是指针,而test2是"real"int.案例6怎么样?案例5相同?

Mil*_*kov 121

4,5和6是同一个东西,只有test是一个指针.如果你想要两个指针,你应该使用:

int *test, *test2;
Run Code Online (Sandbox Code Playgroud)

或者,甚至更好(使一切都清楚):

int* test;
int* test2;
Run Code Online (Sandbox Code Playgroud)

  • 空格对于C编译器来说是无关紧要的(忽略预处理器).因此,无论星号与周围环境之间有多少空间,它都具有完全相同的含义. (13认同)
  • @ Michael Stum这是C++所以你真的认为有合理的解释吗? (6认同)
  • 阅读K&R(C编程语言).它非常清楚地解释了这一切. (6认同)
  • 案例4,5和6是"死亡陷阱".这就是为什么许多C/C++风格的gudes建议每个语句只有一个声明的原因之一. (6认同)
  • @MichaelStum 死亡陷阱?`int *p, i, (*f)();` 是一个非常清晰的声明。只需在使用之前了解您的语言,并适当地命名您的变量(“int p, *i;”*是一个陷阱,“int p; int *i;”也是如此)。 (3认同)
  • 那么案例4实际上是一个死亡陷阱呢?是否有任何规范或进一步阅读解释为什么int*test,test2只使第一个变量成为指针? (2认同)

Ate*_*ral 42

星号周围的白色空间没有意义.这三个意思都是一样的:

int* test;
int *test;
int * test;
Run Code Online (Sandbox Code Playgroud)

" int *var1, var2"是一种邪恶的语法,只是为了让人迷惑,应该避免.它扩展到:

int *var1;
int var2;
Run Code Online (Sandbox Code Playgroud)

  • 不要忘记`int*test`. (13认同)
  • 星号前后的空间只是美学问题。但是,Google 编码标准使用 `int *test` (http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Pointer_and_Reference_Expressions)。保持一致 (2认同)

Mic*_*urr 34

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

有三个简单的步骤:

  1. 从未知元素开始,以螺旋/顺时针方向移动; 当遇到以下元素时,用相应的英语语句替换它们:

    [X][]:数组X大小...或数组未定义大小...

    (type1, type2):函数传递type1和type2返回...

    *:指向...的指针

  2. 继续以螺旋/顺时针方向执行此操作,直到所有令牌都被覆盖.
  3. 始终先解决括号中的任何内容!

此外,声明应尽可能在单独的陈述中(绝大多数时候都是如此).

  • 它确实如此,但对于一些更复杂的结构似乎是一个很好的解释 (7认同)
  • 遗憾的是,这看起来令人生畏,非常可怕. (4认同)
  • 它既不是“螺旋”,也不是“从右到左”,也不是任何其他特定模式:它只是根据括号、优先级和各自的计算顺序(从左到右或从右到左)应用运算符,就像在相应的表达式中一样这会产生左侧的内置类型。`int *arr[1][2][3][4]`中你的螺旋或左右在哪里? (4认同)
  • "螺旋"没有任何意义,更不用说"顺时针"了.我宁愿将它命名为"左右规则",因为语法不会让你看起来是右下 - 左下 - 顶部,只是右上角. (2认同)

Sco*_*ham 31

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


我知道一点点,但我发现有用的东西是向后阅读声明.

int* test;   // test is a pointer to an int
Run Code Online (Sandbox Code Playgroud)

这开始工作得很好,特别是当你开始声明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
Run Code Online (Sandbox Code Playgroud)


hus*_*had 12

正如其他人所提到的,4,5和6是相同的.通常,人们使用这些示例来创建*属于变量而不是类型的参数.虽然这是一个风格问题,但是对于你是否应该这样思考和写作存在一些争论:

int* x; // "x is a pointer to int"
Run Code Online (Sandbox Code Playgroud)

或者这样:

int *x; // "*x is an int"
Run Code Online (Sandbox Code Playgroud)

FWIW我在第一个阵营,但是其他人为第二个阵营提出论证的原因是它(大部分)解决了这个特殊问题:

int* x,y; // "x is a pointer to int, y is an int"
Run Code Online (Sandbox Code Playgroud)

这可能会产生误导; 相反,你会写

int *x,y; // it's a little clearer what is going on here
Run Code Online (Sandbox Code Playgroud)

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

int *x, *y; // two pointers
Run Code Online (Sandbox Code Playgroud)

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

  • 这是假的,你叫什么叫`int*MyFunc(void)`?`*MyFunc`是一个返回`int`的函数?没有.显然我们应该编写`int*MyFunc(void)`,并说`MyFunc`是一个返回`int*`的函数.所以对我来说很明显,C和C++语法分析规则对于变量声明来说是完全错误的.它们应该包含指针限定作为整个逗号序列的共享类型的一部分. (5认同)
  • 第一个阵营与语言的语法作斗争,导致像 `int const* x;` 这样令人困惑的结构,我认为它与 `a * x+b * y` 一样具有误导性。 (3认同)
  • 但是`*MyFunc()` **是**一个`int`。C 语法的问题是混合使用 *prefix* 和 *postfix* 语法——如果使用 *only* 后缀,就不会有混淆。 (2认同)

fre*_*low 11

#include <type_traits>

std::add_pointer<int>::type test, test2;
Run Code Online (Sandbox Code Playgroud)


Joh*_*ode 6

这个谜题一共有三块。

第一部分是 C 和 C++ 中的空白通常不重要,除了分隔无法区分的相邻标记之外。

在预处理阶段,源文本被分解为一系列标记——标识符、标点符号、数字文字、字符串文字等。稍后分析该标记序列的语法和含义。分词器是“贪婪的”,将构建可能的最长有效标记。如果你写这样的东西

inttest;
Run Code Online (Sandbox Code Playgroud)

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

int test;
Run Code Online (Sandbox Code Playgroud)

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

int*test;
Run Code Online (Sandbox Code Playgroud)

编译器看到4个独立的令牌- ,int*test;。因此,空格在指针声明中并不重要,并且所有的

int *test;
int* test;
int*test;
int     *     test;
Run Code Online (Sandbox Code Playgroud)

以同样的方式解释。


难题的第二部分是声明在 C 和 C++ 1 中的实际工作方式。声明分为两个主要部分 - 一系列声明说明符(存储类说明符、类型说明符、类型限定符等),后跟逗号分隔的(可能已初始化的)声明符列表。在声明中

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

声明说明符unsigned long int和说明符a[10]={0}*p=NULLf(void)。声明符引入了被声明事物的名称(ap、 和f)以及关于该事物的数组性、指针性和函数性的信息。声明符也可能有一个关联的初始化器。

的类型a是“10 元素数组unsigned long int”。该类型完全由声明说明符和声明符的组合指定,初始值由初始化程序指定={0}。类似地, 的类型p是“指向unsigned long int”的类型,该类型再次由声明说明符和声明符的组合指定,并被初始化为NULL。同理,类型f是“函数返回unsigned long int”。

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

int[10] a;
Run Code Online (Sandbox Code Playgroud)

因为运算[]符的操作数是a,不是int。同样,在声明中

int* p;
Run Code Online (Sandbox Code Playgroud)

的操作数*p,不是int。但是因为间接运算符是一元的并且空格不重要,所以如果我们这样写,编译器不会抱怨。但是,它始终被解释为int (*p);

因此,如果你写

int* p, q;
Run Code Online (Sandbox Code Playgroud)

的操作数*p,所以它会被解释为

int (*p), q;
Run Code Online (Sandbox Code Playgroud)

因此,所有

int *test1, test2;
int* test1, test2;
int * test1, test2;
Run Code Online (Sandbox Code Playgroud)

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

声明符可以变得任意复杂。您可以拥有指针数组:

T *a[N];
Run Code Online (Sandbox Code Playgroud)

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

T (*a)[N];
Run Code Online (Sandbox Code Playgroud)

你可以让函数返回指针:

T *f(void);
Run Code Online (Sandbox Code Playgroud)

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

T (*f)(void);
Run Code Online (Sandbox Code Playgroud)

您可以拥有指向函数的指针数组:

T (*a[N])(void);
Run Code Online (Sandbox Code Playgroud)

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

T (*f(void))[N];
Run Code Online (Sandbox Code Playgroud)

您可以让函数返回指向指向函数的指针数组的指针,该函数返回指针T

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

然后你有signal

void (*signal(int, void (*)(int)))(int);
Run Code Online (Sandbox Code Playgroud)

读作

       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
    
Run Code Online (Sandbox Code Playgroud)

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

需要注意的一件事 -const可以修改指针类型和指向类型:

const int *p;  
int const *p;
Run Code Online (Sandbox Code Playgroud)

以上都声明p为指向const int对象的指针。您可以编写一个新值以p将其设置为指向不同的对象:

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

const int *p = &x;
p = &y;
Run Code Online (Sandbox Code Playgroud)

但您不能写入指向的对象:

*p = 3; // constraint violation, the pointed-to object is const
Run Code Online (Sandbox Code Playgroud)

然而,

int * const p;
Run Code Online (Sandbox Code Playgroud)

声明pconst指向非常量的指针int;你可以写东西p指向

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

*p = 3;
Run Code Online (Sandbox Code Playgroud)

但您不能设置p为指向不同的对象:

p = &y; // constraint violation, p is const
Run Code Online (Sandbox Code Playgroud)

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

目的是声明的结构应该密切反映代码中表达式的结构(“声明模拟使用”)。例如,假设我们有一个指向intnamed的指针数组ap,并且我们想要访问inti'th 个元素所指向的值。我们将按如下方式访问该值:

printf( "%d", *ap[i] );
Run Code Online (Sandbox Code Playgroud)

表达 *ap[i]有类型int; 因此,的声明ap写为

int *ap[N]; // ap is an array of pointer to int, fully specified by the combination
            // of the type specifier and declarator
Run Code Online (Sandbox Code Playgroud)

声明符*ap[N]与表达式具有相同的结构*ap[i]。运算符*and[]在声明中的行为与它们在表达式中的行为相同 -[]具有比 unary 更高的优先级*,因此操作数*is ap[N](它被解析为*(ap[N]))。

再举一个例子,假设我们有一个指向intnamed数组的指针pa,我们想访问第i' 个元素的值。我们会写成

printf( "%d", (*pa)[i] );
Run Code Online (Sandbox Code Playgroud)

表达式的类型(*pa)[i]int,所以声明写成

int (*pa)[N];
Run Code Online (Sandbox Code Playgroud)

同样,优先级和关联性的规则同样适用。在这种情况下,我们不想取消引用 的第i' 个元素pa,我们想要访问指向i的第 ' 个元素,因此我们必须显式地将运算符与.pa *pa

*[]()操作员是所有部分表达中的代码,所以它们是所有部分声明符中的声明。声明符告诉您如何在表达式中使用对象。如果你有一个像 的声明int *p;,它告诉你*p代码中的表达式将产生一个int值。通过扩展,它告诉您该表达式p产生一个“指向int”类型的值,或int *


那么,像演员表和sizeof表达式之类的东西,我们在哪里使用诸如(int *)sizeof (int [10])或之类的东西呢?我如何阅读类似的东西

void foo( int *, int (*)[10] );
Run Code Online (Sandbox Code Playgroud)

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

好吧,不 - 仍然有一个声明符,只有一个空标识符(称为抽象声明符)。如果我们用符号 ? 表示一个空标识符,那么我们可以将这些内容读作(int *?), sizeof (int ?[10]), 和

void foo( int *λ, int (*λ)[10] );
Run Code Online (Sandbox Code Playgroud)

它们的行为与任何其他声明完全一样。 int *[10]表示一个包含 10 个指针的数组,而int (*)[10]表示一个指向数组的指针。


现在是这个答案的自以为是的部分。我不喜欢将简单指针声明为的 C++ 约定

T* p;
Run Code Online (Sandbox Code Playgroud)

并认为这是不好的做法,原因如下:

  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++; 
}
Run Code Online (Sandbox Code Playgroud)

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


  1. 我将使用 C 术语 - C++ 术语略有不同,但概念基本相同。

  • 这是这个问题的最佳答案。它应该得到更高的投票。 (5认同)

180*_*ION 5

在4,5和6中,test始终是指针而test2不是指针.在C++中,空白(几乎)从不重要.