在一行代码中声明多个对象指针会导致编译错误。

34

当我在我的课堂上这样做时

public:
    Entity()
    {
        re_sprite_eyes = new sf::Sprite();
        re_sprite_hair = new sf::Sprite();
        re_sprite_body = new sf::Sprite();
    }

private:
    sf::Sprite* re_sprite_hair;
    sf::Sprite* re_sprite_body;
    sf::Sprite* re_sprite_eyes;

一切都正常工作。但是,如果我更改声明为:

private:
    sf::Sprite* re_sprite_hair, re_sprite_body, re_sprite_eyes;

我遇到了这个编译错误:

error: no match for 'operator=' in '((Entity*)this)->Entity::re_sprite_eyes = (operator new(272u), (<statement>, ((sf::Sprite*)<anonymous>)))

接下来它说re_sprite_eyes的候选者是sf::Sprite对象和/或引用。

为什么这不能运行?这些声明不是一样的吗?


12
您发现了C/C++声明的一个有趣特性:星号属于变量,而不是类型。为了时刻提醒自己这一点,一种方法是在类型和星号之间加上空格。 - Sergey Kalinichenko
5个回答

71

sf::Sprite* re_sprite_hair;

sf::Sprite re_sprite_body;

sf::Sprite re_sprite_eyes;

sf::Sprite* re_sprite_hair;
sf::Sprite re_sprite_body;
sf::Sprite re_sprite_eyes;

你想要做的是:

sf::Sprite *re_sprite_hair, *re_sprite_body, *re_sprite_eyes;

每个变量需要加一个星号。在这种情况下,我更喜欢将星号放在变量的一侧,而不是类型的一侧,以清晰表达这种情况。


请参考John Bode和我的回答以获取解释。 - Jive Dadson
1
好的解释,但我更喜欢多行格式。虽然有点冗长,但更易于阅读。 - gornvix

39
在C和C++中,*将绑定到declarator而非类型说明符。在这两种语言中,声明是基于表达式的类型而非对象的。
例如,假设您有一个指向名为pint类型的指针,并且您想要访问p指向的int值。您可以使用一元运算符*对指针进行解引用操作来实现。
x = *p;
*p的类型是int,因此p的声明为:
int *p;

无论在同一声明语句中声明了多少指针,这都是真实的;如果需要将q和r声明为指针,那么它们也需要在声明符中加上一元运算符*
int *p, *q, *r;

因为表达式*q*r的类型是int,所以会出现这种情况。在C和C++语法中,你可以写成T *pT* pT * p,这只是C和C++语法的偶然性;所有这些声明都会被解释为T (*p)
这就是为什么我不喜欢C++风格的指针和引用类型声明的原因。
T* p;
T& r;

因为它意味着对C和C++声明语法工作方式的错误看法,导致了你刚刚遇到的确切混淆。然而,我写过足够多的C++,意识到在定义容器类型时,有时候这种风格确实可以使代码的意图更清晰。
但它仍然是不正确的。
这是(两年晚了)对Lightness Races in Orbit(以及任何反对我将T* p规则称为“错误”的人)的回应……
首先,正是由于使用T* p规则而引起了像这样的问题和特别是产生了像这个问题一样的问题,以及它不能按照人们的期望工作。这个网站上有多少关于“为什么T* p,q不会将pq都声明为指针?”这样的问题?
引入了混乱——仅仅这一点就足以阻止它的使用。
但除此之外,它是不一致的。你无法将数组性或函数性与声明符分开,那你为什么要将指针性与它分开?
“嗯,那是因为[]()是后缀运算符,而*是一元的。”是的,它是一元的,那么为什么你不将运算符与其操作数关联起来?在声明T* p中,T不是*的操作数,为什么我们要像它是一样写声明呢?
如果a是“指针数组”,为什么我们要写T* a[N]?如果f是“返回指针的函数”,为什么我们要写T* f()?如果你把这些声明写成T *a[N]T *f(),那么声明符系统就更有意义内部一致性。这从我可以使用T作为任何类型(实际上,对于任何一组声明说明符)的替代品这一事实中应该是显而易见的。
然后你有指向数组和指向函数的指针,其中*必须明确绑定到声明符1
T (*a)[N];
T (*f)();

是的,指针性是您声明的事物的重要属性,但数组性和函数性也同样重要,强调其中之一会带来比解决问题更多的问题。正如这个问题所显示的那样,T* p 惯例 引入了混淆
因为 * 是一元运算符并且是一个单独的标记,您可以编写 T* pT *pT*pT * p,它们都将被编译器接受,但它们都将被解释为 T (*p)。更重要的是,T* p, q, r 将被解释为 T (*p), q, r。如果您编写 T *p, q, r,那种解释更加明显。是的,“每行只声明一个东西就不会有问题。”,但是您知道还有另外一种方法吗?正确编写您的声明符。声明符系统本身将变得更加清晰,您也不太可能犯错误。
我们不是在争论语言的“古怪”,这是语言语法和哲学的基本组成部分。指针性是声明符的属性,就像数组性和函数性一样,假装它在某种程度上不是会导致混淆,并使 C 和 C++ 比需要理解的更困难。
我认为将取消引用运算符设为一元运算符而不是后缀运算符是一个错误2,但这是 B 中的工作方式,Ritchie 想尽可能保留 B 的许多特点。我还会认为 Bjarne 推广 T* p 惯例是一个错误。
  1. At this point in the discussion, somebody will suggest using a typedef like
    typedef T arrtype[N]; 
    arrtype* p;
    which just totally misses the point and earns the suggester a beating with the first edition of "C: The Complete Reference" because it's big and heavy and no good for anything else.
  2. Writing T a*[N]*() as opposed to T (*(*a)[N])() is definitely less eye-stabby and scans much more easily.

2
使用&表示引用是C++的构造,与原始的C声明不太相符。早些时候,我对引用进入C++的方式有所保留,部分原因就在于此。取地址运算符以一种令人困惑的方式进行了重载 - 虽然没有<<和>>那么糟糕。 :-) - Jive Dadson
1
@JiveDadson:就语法而言,T& r是“错误的”(它被解释为T (&r),因此多个声明必须写成T &r, &s, &q)。我理解你的观点(&x的类型是T *,而不是T),是的,以这种方式重载&确实会引起一些困扰。 - John Bode
除了一个特定的情况(你本来就不应该这样做)(好吧,如果你愿意,有一些复杂类型是古老写法),它几乎没有任何影响,所以C++风格总体上要好得多。 这并不“错”。“将符号右对齐只是为了迎合语言内部的古怪现象而做出的一个最大的泄露”。 - Lightness Races in Orbit
我不同意“C++风格是错误的”和“C++风格是优越的”这两个前提。C++风格在某些情况下运作良好(例如,在每个声明中仅声明一个变量时),而在其他情况下会误导程序员(例如,暗示Foo* a, b声明了两个指针而不是一个)。一种替代方案(具有技术和宗教基础的优缺点)是使用typedef或(自C++以来)类型别名,并避免争论变量/参数声明中*&的位置,同时在更改类型时简化代码维护。 - Peter
1
@Peter:也许并不令人惊讶,我对于在“typedef”后隐藏指针性质也有非常强烈的看法;除非你愿意构建一个完全抽象出所有指针操作的API,否则就别费心了。如果我必须知道p是一个指针才能正确使用它(例如,我必须直接引用它),那么就不要将这些信息隐藏在typedef或其他类型别名之后。在声明中明确它。我宁愿处理T* p;而不是typedef-name-that-doesn't-indicate-pointerness-at-all p; - John Bode
显示剩余2条评论

5

在C++11中,你有一个很好的小技巧,可能比来回移动空格更好:

template<typename T> using type=T;
template<typename T> using func=T*;

// I don't like this style, but type<int*> i, j; works ok
type<int*> i = new int{3},
           j = new int{4};

// But this one, imho, is much more readable than int(*f)(int, int) = ...
func<int(int, int)> f = [](int x, int y){return x + y;},
                    g = [](int x, int y){return x - y;};

4

Another thing that may call your attention is the line:

int * p1, * p2;

This declares the two pointers used in the previous example. But notice that there is an asterisk (*) for each pointer, in order for both to have type int* (pointer to int). This is required due to the precedence rules. Note that if, instead, the code was:

int * p1, p2;

p1 would indeed be of type int*, but p2 would be of type int. Spaces do not matter at all for this purpose. But anyway, simply remembering to put one asterisk per pointer is enough for most pointer users interested in declaring multiple pointers per statement. Or even better: use a different statemet for each variable.

来自http://www.cplusplus.com/doc/tutorial/pointers/

指针是C++编程语言中的一个重要概念。它们提供了一种间接访问内存位置的方式,这对于许多任务非常有用。本教程将介绍指针的基础知识,并解释如何使用它们进行操作。


1

星号绑定到指针变量名称。记住这一点的方法是注意在C/C++中,声明模仿使用。

指针可能会像这样使用:

sf::Sprite *re_sprite_body;
// ...
sf::Sprite sprite_bod = *re_sprite_body;

同样地,

char *foo[3];
// ...
char fooch = *foo[1];

在这两种情况下,都有一个基础的类型说明符和运算符或运算符集合,用于在表达式中“获取”该类型的对象。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接