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

这些示例如何:


int* test;
int *test;
int * test;
int* test,test2;
int *test,test2;
int * test,test2;

现在,据我所知,前三个案例都做同样的事情:Test不是一个int,而是一个指向它的指针。

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

评论

在C / C ++中,空格不会改变含义。

7. int * test ;?

+1是因为我只想问1-3。读这个问题使我了解到了4-6的一些我从未想过的东西。

@Sulthan 99%的时间是正确的,但并非总是如此。在我脑海中,在模板类型空间要求(C ++ 11之前)中有模板类型的类型。在Foo >中,必须将>>编写为>>,以免被视为右移。

@AnorZaken您说得对,这是一个相当古老的评论。在多种情况下,空格将改变含义,例如,不能用空格分隔递增++运算符,不能用空格分隔标识符(并且结果对于编译器仍然合法,但具有未定义的运行时行为) 。考虑到C / C ++的语法混乱,很难定义确切的情况。

#1 楼

4、5和6是同一件事,只是测试是一个指针。如果要使用两个指针,则应使用:

int *test, *test2;


,或者甚至更好(使所有内容都清楚):

int* test;
int* test2;


评论


那么案例4实际上是一个死亡陷阱吗?有没有说明或解释为什么int * test,test2仅使第一个变量成为指针的规范?

–Michael Stum♦
08年10月7日在21:06

@ Michael Stum这是C ++,所以您真的认为有一个合乎逻辑的解释吗?

–乔·菲利普斯(Joe Phillips)
08-10-7在21:07

阅读K&R(C编程语言)。它非常清楚地解释了所有这些。

–Ferruccio
08年10月7日在21:09

案例4、5和6是“死亡陷阱”。这就是为什么许多C / C ++样式指导建议每个语句仅一个声明的原因之一。

– Michael Burr
08-10-7在21:23

空格对于C编译器而言无关紧要(忽略预处理器)。因此,无论星号与其周围环境之间有多少空间,它的含义都完全相同。

–短暂
08年10月7日在21:59

#2 楼

星号周围的空白不重要。这三个含义相同:

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


int *var1, var2”是一种邪恶的语法,仅是在混淆人们,应该避免。它扩展为:

int *var1;
int var2;


评论


不要忘记int * test。

–玛蒂·乌尔哈克(Mateen Ulhaq)
2011年5月7日,下午2:38

星号之前或之后的空间只是美学问题。但是,Google Coding标准与int * test(google-styleguide.googlecode.com/svn/trunk/…)一起提供。保持一致

–user2489252
13年9月15日在17:09

@SebastianRaschka Google C ++样式指南明确允许使用任何一个星号。自您阅读以来,它可能已更改。

–Jared Beck
2014年5月12日22:45



#3 楼

许多编码指南建议您每行仅声明一个变量。这样可以避免您在问这个问题之前感到困惑。与我合作过的大多数C ++程序员似乎都坚持这一点。


我知道还有一点,但是我发现有用的是向后读取声明。

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


这开始工作得很好,尤其是当您开始声明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


评论


尽管“每行一个变量”似乎很有用,但我们仍未完全解决星号偏左或偏右的情况。我很确定,在狂野的代码中,一种变体会流行。有点像有些国家在右边开车,而其他国家在错误的方向开车,例如英国。 ;-)

–雪橇
3月30日16:59

不幸的是,从我的野外冒险中,我看到了很多两种风格。现在,在我的团队中,我们使用clang格式和我们已经同意的样式。至少这意味着我们团队生成的所有代码在空白处都具有相同的样式。

–斯科特·朗厄姆(Scott Langham)
3月30日18:36

#4 楼

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


只需执行三个简单步骤:



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

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

(type1, type2):传递类型1和类型2的函数返回...

*:指向...的指针。

一直沿螺旋/顺时针方向执行此操作,直到所有令牌已被覆盖。
始终首先用括号解决任何问题!



此外,声明应尽可能在单独的语句中使用(绝大多数情况下是这样)。 />

评论


遗憾的是,这看起来令人生畏并且非常恐怖。

–乔·菲利普斯(Joe Phillips)
08年10月7日在21:43

确实如此,但是对于某些更复杂的结构似乎是一个很好的解释

–Michael Stum♦
08年10月7日在22:27

@ d03boy:毫无疑问-C / C ++声明可能是一场噩梦。

– Michael Burr
08年10月7日在22:35

“螺旋”没有任何意义,更不用说“顺时针”了。我宁愿将其命名为“左右规则”,因为该语法不会使您看起来从右下到左上,而只能从右到左。

–俄罗斯
18年2月3日,9:38



我将此称为“左右左右”规则。 C ++的人经常喜欢假装所有类型信息在左边,这导致了int * x。样式,而不是传统的int * x;风格。当然,间距对编译器无关紧要,但确实会影响人类。拒绝实际语法会导致样式规则,这些规则会使读者感到烦恼和困惑。

–阿德里安·麦卡锡(Adrian McCarthy)
19年5月2日在18:51

#5 楼

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


我个人说将其保持为每行一个变量,那么无论您喜欢哪种样式都没关系。

评论


这是假的,你怎么称呼int * MyFunc(void)? * MyFunc是返回int的函数吗?没有。显然,我们应该编写int * MyFunc(void),并说MyFunc是一个返回int *的函数。因此,对我来说很明显,C和C ++语法解析规则对于变量声明完全是错误的。它们应该已经将指针限定符作为整个逗号序列的共享类型的一部分。

–v.oddou
17年5月19日在9:07



但是* MyFunc()是一个int。 C语法的问题在于混合使用前缀和后缀语法-如果仅使用后缀,则不会造成混淆。

–安蒂·哈帕拉(Antti Haapala)
18/12/4在16:52

第一个阵营与语言的语法作斗争,导致诸如int const * x;之类的结构令人困惑,我发现它与* x + b * y一样具有误导性。

–阿德里安·麦卡锡(Adrian McCarthy)
19年5月2日,18:55

#6 楼

#include <type_traits>

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


评论


#include LPINT测试,test2;

– Stefan Dragnev
16年5月19日在9:46

#7 楼

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

#8 楼

C语言的基本原理是您以使用变量的方式声明变量。例如,

char *a[100];


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

这是因为原始的编译器编写者希望对表达式和声明使用相同的解析器。 (选择语言设计不是很明智的原因)

评论


然而写char * a [100];还推论* a [42];将是一个字符和一个[42];一个char指针。

–yyny
16 Mar 7 '16 at 16:04

好吧,我们都得出相同的结论,只是顺序是变化的。

–米歇尔·比洛(Michel Billaud)
16 Mar 7 '16 at 19:50

Quote:“说* a [42]将是一个字符。而a [42]将是一个字符指针”。您确定不是相反的方式吗?

–deLock
19年6月30日在11:11



如果您更喜欢另一种方法,则说a [42]是char指针,* a [42]是char。

–米歇尔·比洛(Michel Billaud)
19年7月1日在18:21



#9 楼

我认为,答案都是根据情况而定。
通常,对于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

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

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

#10 楼

我会说,最初的约定是将星号放在指针名称一侧(Dennis M. Ritchie在c编程语言中的声明的右侧


),星号位于在声明的右侧。通过查看https://github.com/torvalds/linux/blob/master/init/main.c上的linux源代码,我们可以看到星号是也可以在右侧。

您可以遵循相同的规则,但是如果将星形放在类型侧并不重要。
请记住,一致性很重要,因此始终但重要不论您选择哪一侧,都可以在同一边上保持星形。

评论


很好-解析器似乎允许任何一种变体,但是如果Dennis和Linus认为它应该在右侧,那将非常引人注目。但是,我们仍然缺乏一些基本原理,因此也缺少这样做的理由。这有点像制表符与空间的情况-除​​了解决了一个问题之外,因为根据StackOverflow的说法,使用空格而不是制表符的人能赚更多的钱... :-)

–雪橇
3月30日16:57

#11 楼

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

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

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

#12 楼

这个难题有三部分。
第一部分是C和C ++中的空格通常不重要,除非将相邻的标记分开,否则它们是无法区分的。
在预处理阶段,源文本被分解了。分为一系列标记-标识符,标点符号,数字文字,字符串文字等。稍后将对该标记序列进行语法和含义分析。令牌生成器是“贪婪的”,将构建可能的最长有效令牌。如果您编写类似
inttest;

的内容,则令牌生成器只会看到两个令牌-标识符inttest和后跟标点符号;。在此阶段,它不会将int识别为单独的关键字(此过程稍后会发生)。因此,对于要读取的行来说,它是一个名为test的整数的声明,我们必须使用空格分隔标识符标记:
int test;

*字符不属于任何标识符;它本身是一个单独的令牌(标点符号)。因此,如果您编写
int*test;

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

的解释方式都相同。

难题的第二部分是声明如何在C和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”。
这是关键-没有“ pointer-to”类型说明符,就像没有“ 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的类型为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声明为指向非常量constint指针;您可以将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]相同的结构。运算符*[]在声明中的行为相同,它们在表达式中的行为相同-[]的优先级高于一元*,因此*的操作数为ap[N](被解析为*(ap[N]))。
再举一个例子,假设我们有一个指向名为intpa数组的指针,并且我们想访问第i个元素的值。我们将其写为
printf( "%d", (*pa)[i] );

表达式的类型为(*pa)[i],因此声明再次写为
int (*pa)[N];

,同样适用优先级和关联性规则。在这种情况下,我们不想取消引用int的第i个元素,而是要访问pa所指向的第i个元素,因此我们必须将pa运算符与*明确组合在一起。
pa*[]运算符都是代码中表达式的一部分,因此它们都是声明中声明符的一部分。声明器告诉您如何在表达式中使用对象。如果您有一个类似()的声明,则表明您代码中的表达式int *p;将产生一个*p值。通过扩展,它告诉您表达式int产生类型为“指向p的指针”或int的值。

那么,诸如cast和int *表达式之类的事情呢? sizeof或类似的东西?我如何阅读类似的内容
void foo( int *, int (*)[10] );

没有声明符,(int *)sizeof (int [10])运算符不是直接修改类型吗?
不,没有-仍然有一个声明符,只是一个空标识符(称为抽象声明符)。如果我们用符号λ表示一个空标识符,那么我们可以将它们读为*[]
void foo( int *λ, int (*λ)[10] );

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

现在这个答案有意思的部分。我不喜欢将简单的指针声明为
T* p;

的C ++约定,并且由于以下原因而将其视为不好的做法:

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

最后,它仅表示对这两种语言的类型系统如何工作的困惑。
有充分的理由分别声明项目;解决不良行为(* a[i])并不是其中一种。如果正确编写声明符(T* p),则不太可能引起混淆。
我认为类似于故意将所有简单的T* p, q;循环写为
i = 0;
for( ; i < N; ) 
{ 
  ... 
  i++ 
}

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

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


#13 楼

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

例如:

int const bla;


const适用于“ int”一词。指针的星号也是如此,它们适用于它们左侧的关键字。以及实际的变量名?是的,这是用剩下的来声明的。

评论


这不能回答问题。更糟糕的是,如果我们尝试从中推断出答案,则意味着星号绑定到其左侧的类型,正如其他所有人所说的那样,它是错误的。它绑定到其右侧的单个变量名称。

– underscore_d
17年4月4日在20:25