111

我最近决定我只需要最终学习 C/C++,关于指针,或者更准确地说,它们的定义,我并不真正了解一件事。

这些例子怎么样:

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

现在,据我了解,前三种情况都在做同样的事情:Test 不是一个 int,而是一个指向 int 的指针。

第二组示例有点棘手。在案例 4 中,test 和 test2 都是指向 int 的指针,而在案例 5 中,只有 test 是指针,而 test2 是“真正的”int。案例6呢?和案例5一样吗?

4

13 回答 13

140

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

int *test, *test2;

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

int* test;
int* test2;
于 2008-10-07T21:04:33.343 回答
51

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

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

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

int *var1;
int var2;
于 2008-10-07T21:05:52.827 回答
38

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


我知道有点旁白,但我发现有用的是向后阅读声明。

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
于 2008-10-07T21:13:22.983 回答
36

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

需要遵循三个简单的步骤:

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

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

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

    *: 指向...的指针

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

此外,声明应尽可能在单独的语句中(绝大多数情况下都是如此)。

于 2008-10-07T21:27:57.117 回答
15

正如其他人提到的,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"

这可能具有误导性;相反,你会写

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

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

int *x, *y; // two pointers

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

于 2011-12-31T00:46:38.210 回答
11
#include <type_traits>

std::add_pointer<int>::type test, test2;
于 2012-09-15T16:27:35.927 回答
11

这个谜题分为三个部分。

第一部分是 C 和 C++ 中的空格通常不重要,除了分隔在其他情况下无法区分的相邻标记。

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

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是“10 元素数组unsigned long int”。该类型完全由声明说明符和声明符的组合={0}指定,初始值由初始化器指定。类似地, 的类型p是“指针unsigned long int”,并且该类型再次由声明说明符和声明符的组合指定,并被初始化为NULL。同样的道理,类型f是“函数返回unsigned long int”。

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

int[10] a;

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

int* p;

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

因此,如果你写

int* p, q;

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

int (*p), q;

因此,所有

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

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

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

T *a[N];

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

T (*a)[N];

你可以让函数返回指针:

T *f(void);

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

T (*f)(void);

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

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

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

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

您可以让函数返回指向指针数组的指针,这些指针指向返回指针的函数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;

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

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

*p = 3;

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

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

这给我们带来了第三个难题——为什么声明是这样构造的。

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

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

表达式*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

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

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

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

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

int (*pa)[N];

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

,和运算符都是代码中表达式*的一部分,因此它们都是声明中的声明符的一部分。声明器告诉您如何在表达式中使用对象。如果您有一个类似 的声明,它会告诉您代码中的表达式将产生一个值。通过扩展,它告诉您表达式产生一个类型为“指向”的值,或者。[]()int *p;*pintpintint *


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

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

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

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

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

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


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

T* p;

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

  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++ 术语略有不同,但概念大致相同。
于 2020-10-13T16:20:52.857 回答
5

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

于 2008-10-07T21:05:21.143 回答
3

C 中的基本原理是您以使用它们的方式声明变量。例如

char *a[100];

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

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

于 2016-01-01T20:29:30.760 回答
3

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

您可以遵循相同的规则,但如果您将星号放在字体一侧,这没什么大不了的。请记住,一致性很重要,所以无论您选择哪一边,始终要让明星站在同一边。

于 2017-09-28T08:48:53.387 回答
3

在我看来,答案是两者皆有,视情况而定。通常,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

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

但是,在某些例外情况下,您可能无法将星号放在对象名称旁边(以及放在哪里很重要)而不会得到不希望的结果 - 例如:

MyClass *volatile MyObjName

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

最后,在某些情况下,将星号放在类型名称旁边可能更清楚,例如:

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

于 2019-05-02T18:19:33.233 回答
1

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

1,2 和 3) 测试属于 (int *) 类型。空格无关紧要。

4,5 和 6) 测试属于 (int *) 类型。Test2 是 int 类型。同样,空格是无关紧要的。

于 2008-10-07T21:07:50.310 回答
-2

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

举个例子:

int const bla;

const 适用于“int”字。指针的星号也是如此,它们适用于它们左侧的关键字。和实际的变量名?是的,这是由它的剩余部分声明的。

于 2008-10-07T21:50:10.223 回答