我最近决定我必须最终学习C / C ++,有一点我对指针或更确切地说,他们的定义并不是很了解。
这些例子怎么样:
int* test;
int *test;
int * test;
int* test,test2;
int *test,test2;
int * test,test2;
现在,根据我的理解,前三个案例都是一样的:测试不是一个int,而是一个指针。
第二组例子有点棘手。在case 4中,test和test2都是指向int的指针,而在case 5中,只有test是指针,而test2是“real”int。案例6怎么样?与案例5相同?
答案 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 ++声明;
有三个简单的步骤:
- 的指针
从未知元素开始,以螺旋/顺时针方向移动 方向;当遇到以下元素时替换它们 相应的英语陈述:
[X]
或[]
:数组X大小为......或数组未定义大小...
(type1, type2)
:函数传递type1和type2返回...
*
:指向...- 以螺旋/顺时针方向继续这样做,直到所有标记都被覆盖。
- 首先要在括号中解决任何问题!
醇>
此外,声明应尽可能在单独的陈述中(绝大部分时间都是如此)。
答案 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=NULL
和f(void)
。声明器介绍要声明的事物的名称(a
,p
和f
)以及有关事物的数组性,指针性和功能性的信息。声明器可能还具有关联的初始化器。
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
进行分组。
i
,pa
和*
运算符都是代码中 expression 的一部分,因此它们都是 declarator的一部分。声明器告诉您如何在表达式中使用对象。如果您有一个类似pa
的声明,则说明您代码中的表达式*
将产生一个[]
值。通过扩展,它告诉您表达式()
产生类型为“指向int *p;
的指针”或“ *p
”的值。
那么,诸如cast和int
表达式之类的东西在我们使用p
或int
之类的东西时又如何呢?我怎么读类似的东西
int *
没有声明符,sizeof
和(int *)
运算符不是直接修改类型吗?
好吧,不-还有一个声明符,只是一个空标识符(称为抽象声明符)。如果我们用符号λ表示一个空标识符,那么我们可以将它们读为sizeof (int [10])
,void foo( int *, int (*)[10] );
和
*
,它们的行为与其他任何声明完全相同。 []
代表由10个指针组成的数组,而(int *λ)
代表指向该数组的指针。
现在,这个答案中带有观点的部分。我不喜欢将简单指针声明为的C ++约定
sizeof (int λ[10])
,并出于以下原因将其视为不良做法:
void foo( int *λ, int (*λ)[10] );
的含义的问题,那些问题的所有重复项等证明); < / li>
int *[10]
在使用上是不对称的(除非您习惯于编写int (*)[10]
); T* p;
约定,否则... 否); 最后,它只是表示对两种语言的类型系统如何工作的困惑。
有充分的理由分别声明项目;解决不良做法(T* p, q;
)并不是其中之一。如果正确地 (T* a[N]
)写声明符,则不太可能引起混乱。
我认为故意将所有简单的* a[i]
循环写为
T* p
语法上有效,但令人困惑,其意图可能会被误解。但是,T* p, q;
约定在C ++社区中根深蒂固,我在自己的C ++代码中使用它,因为跨代码库的一致性是一件好事,但每次执行时都会让我感到厌烦。
答案 10 :(得分:0)
C中的基本原理是您以使用它们的方式声明变量。例如
char *a[100];
表示*a[42]
将是char
。并a[42]
一个字符指针。因此a
是一个char指针数组。
这是因为原始编译器编写者想要对表达式和声明使用相同的解析器。 (选择语言设计不是一个非常明智的理由)
答案 11 :(得分:0)
我想说最初的约定是将星号放在指针名称一侧(声明的右侧)
明星位于声明的右侧。
查看https://github.com/torvalds/linux/blob/master/init/main.c的linux源代码 我们可以看到这颗恒星也在右侧。
你可以遵循相同的规则,但如果你在类型方面放置星星,这不是什么大问题。 请记住,一致性很重要,因此无论您选择哪一方,总是在同一侧的明星。
答案 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
。