在指针声明中放置星号

时间:2008-10-07 21:02:43

标签: 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;
  7. 现在,根据我的理解,前三个案例都是一样的:测试不是一个int,而是一个指针。

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

15 个答案:

答案 0 :(得分:118)

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

int *test, *test2;

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

int* test;
int* test2;

答案 1 :(得分:42)

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

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

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

int *var1;
int var2;

答案 2 :(得分:32)

使用"Clockwise Spiral Rule"帮助解析C / C ++声明;

  

有三个简单的步骤:

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

         

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

         

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

         

    *:指向...

  2. 的指针   
  3. 以螺旋/顺时针方向继续这样做,直到所有标记都被覆盖。
  4.   
  5. 首先要在括号中解决任何问题!
  6.   

此外,声明应尽可能在单独的陈述中(绝大部分时间都是如此)。

答案 3 :(得分:29)

许多编码指南建议您只声明每行一个变量。这可以避免在提出这个问题之前遇到的任何混淆。我与之合作的大多数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

答案 4 :(得分:12)

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

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

答案 5 :(得分:11)

#include <type_traits>

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

答案 6 :(得分:5)

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

答案 7 :(得分:1)

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

1,2和3)测试的类型为(int *)。空白并不重要。

4,5和6)测试的类型为(int *)。 Test2的类型为int。再次,空白是无关紧要的。

答案 8 :(得分:1)

您可以将4,5和6视为如下: 声明类型只需要执行一次,但如果要声明指向该类型的指针(通过添加星号),则必须为每个变量执行此操作。

当声明指针变量时,我总是在变量和星号之间添加空格,即使我在一行中声明多个也是如此。如果不这样做,我几乎每次都会把它混淆为解引用表达式。

答案 9 :(得分:1)

这个难题有三部分。

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

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

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的类型为{ {1}}。

声明符可以任意复杂。您可以具有多个指针数组:

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; 声明为指向非常量p的{​​{1}}指针;您可以写const指向的东西

int

但是您不能设置p指向另一个对象:

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

*p = 3;

这将我们带入难题的第三部分-为什么声明是用这种方式构造的。

其目的是声明的结构应与代码中的表达式的结构紧密镜像(“声明模仿使用”)。例如,假设我们有一个名为p的指向p = &y; // constraint violation, p is const 的指针数组,并且我们想访问第int个元素所指向的ap值。我们将按以下方式访问该值:

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]在声明中的行为与在表达式中的行为相同-*的优先级比一元[]高,因此{{1 }}是[](解析为*)。

作为另一个示例,假设我们有一个名为*的{​​{1}}数组的指针,并且我们想访问第ap[N]个元素的值。我们将其写为

*(ap[N])

表达式int的类型为pa,因此声明写为

i

同样,优先级和关联性规则相同。在这种情况下,我们不想取消引用printf( "%d", (*pa)[i] ); 的第(*pa)[i]个元素,而是要访问int 的第int (*pa)[N]; 个元素指向,因此我们必须将i运算符与pa进行分组。

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


那么,诸如cast和int表达式之类的东西在我们使用pint之类的东西时又如何呢?我怎么读类似的东西

int *

没有声明符,sizeof(int *)运算符不是直接修改类型吗?

好吧,不-还有一个声明符,只是一个空标识符(称为抽象声明符)。如果我们用符号λ表示一个空标识符,那么我们可以将它们读为sizeof (int [10])void foo( int *, int (*)[10] );

*

,它们的行为与其他任何声明完全相同。 []代表由10个指针组成的数组,而(int *λ)代表指向该数组的指针。


现在,这个答案中带有观点的部分。我不喜欢将简单指针声明为的C ++约定

sizeof (int λ[10])

,并出于以下原因将其视为不良做法

  1. 与语法不一致;
  2. 它引入了混乱(此问题,该问题的所有重复项,关于void foo( int *λ, int (*λ)[10] );的含义的问题,那些问题的所有重复项等证明); < / li>
  3. 这在内部是不一致的-将指针数组声明为int *[10]在使用上是不对称的(除非您习惯于编写int (*)[10]);
  4. 它不能应用于数组指针或函数指针类型(除非创建typedef只是为了可以干净地应用T* p; 约定,否则... );
  5. 这样做的原因-“强调对象的指针性”-是虚假的。它不能应用于数组或函数类型,我想强调的那些品质同样重要。

最后,它只是表示对两种语言的类型系统如何工作的困惑。

有充分的理由分别声明项目;解决不良做法(T* p, q;)并不是其中之一。如果正确地 T* a[N])写声明符,则不太可能引起混乱。

我认为故意将所有简单的* a[i]循环写为

T* p

语法上有效,但令人困惑,其意图可能会被误解。但是,T* p, q;约定在C ++社区中根深蒂固,我在自己的C ++代码中使用它,因为跨代码库的一致性是一件好事,但每次执行时都会让我感到厌烦。


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

答案 10 :(得分:0)

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

char *a[100];

表示*a[42]将是char。并a[42]一个字符指针。因此a是一个char指针数组。

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

答案 11 :(得分:0)

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

你可以遵循相同的规则,但如果你在类型方面放置星星,这不是什么大问题。 请记住,一致性很重要,因此无论您选择哪一方,总是在同一侧的明星。

答案 12 :(得分:0)

我认为,最好将星号放在指针名称旁边,而不是类型旁边。比较例如:

int *pointer1, *pointer2; // Fully consistent, two pointers
int* pointer1, pointer2;  // Inconsistent, unexpected, and thus prone to errors

为什么第二种情况不一致?因为例如int x,y;声明了两个相同类型的变量,但在声明中仅提及一次该类型。这创建了先例和预期行为。 int* pointer1, pointer2;与此不一致。

答案 13 :(得分:-2)

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

以例如:

int const bla;

const适用于“int”字。指针的星号也是如此,它们适用于左边的关键字。而实际的变量名称?是的,这是由剩下的东西宣布的。

答案 14 :(得分:-3)

案例1,2和3是相同的,它们声明了指向int变量的指针。情况3,4和5是相同的,因为它们分别声明一个指针和一个int变量。如果你想在一行中声明两个指针(你不应该),你需要在每个变量名前加一个星号:

int *test, *test2;

没有一种正确的方法可以说出星号的位置。 int* test看起来更好,因为我们更容易想象将*附加到类型的末尾意味着“指向”该类型的指针。但是,int *test更有意义,因为你可以像数学中的减号那样使用它:

-(-x) = x

类似于

*(*test) = test

这一直对我有所帮助。可悲的是,这一切的结果是有时我使用int* test,有时使用int *test