从其他类成员自动计算出一个类成员?

3
  1. 我对编程一窍不通,现在正在尝试通过解决一些简单的 C++“问题”来自学基础知识。

  2. 在发帖之前,我已经在网上搜索了确切的答案,但是还没有找到,可能是因为(1)的原因。

所以,我正在寻找一种方法来声明一个类成员,该成员可以从同一类的其他成员自动计算,以便可以像明确定义的类成员一样使用计算后的类成员。例如,想象一个名为 creature 的结构体,它具有属性/成员 creature.numberofhands、creature.fingersperhand 和最终从上述成员自动计算出的属性 creature.totalfingers。

这里是我接近实现目标的示例:

#include <iostream>

typedef struct creature {
    int numberofhands;
    int fingersperhand;
    int totalfingers();
} creature;    

int creature::totalfingers()
{
    return numberofhands * fingersperhand;
};

int main()
{
    creature human;
    human.numberofhands = 2;
    human.fingersperhand = 5;

    printf("%d",human.totalfingers());
    return(0);
}

这真的很让我烦恼,因为我必须将计算出来的结果与明确定义的结果区分开来,也就是说,我必须在它后面加上“()”。

我该如何更改代码,以便我可以使用:human.totalfingers,而无需明确定义它?


3
在C++中不需要使用typedef struct - user2672107
你可以将常规成员更改为也使用 (),或者创建一个带有 operator int() 的代理对象,但最后一个选项在不带帮助的 printf 省略号下无法工作。 - Jarod42
我实际上不会这样做,但是为了好玩,您可以为每个要使用相同语法调用的函数定义一个令牌,例如变量#define totalNoOfFingers creature::totalfingers() - George
5个回答

4

最简单的方法是使用公共成员函数并隐藏实际属性。像这样:

class Creature {
public:
    Creature(int numhands, int fingersperhand) // constructor
        : m_numhands{numhands}, m_fingersperhand{fingersperhand}
        { }
    int fingersPerHand() const { return m_fingersperhand; }
    int numberOfHands() const { return m_numhands; }
    int totalFingers() const { return numberOfHands() * fingersPerHand(); }

private:
    const int m_numhands;
    const int m_fingersperhand;
};

私有成员变量是一种实现细节。类的使用者只需在构造后使用三个公有成员函数来获取不同数量的手指,不需要关心其中两个返回常数存储数字,第三个函数返回计算出的值 - 这对用户来说是无关紧要的。
使用示例:
#include <iostream>
int main()
{
    Creature human{2, 5};
    std::cout << "A human has "
        << human.totalFingers() << " fingers. "
        << human.fingersPerHand() << " on each of their "
        << human.numberOfHands() << " hands.\n";
    return 0;
}

如果 - 根据您的评论 - 您不想使用构造函数(虽然这是确保您不会忘记初始化成员的最安全方法),您可以像这样修改类:

class CreatureV2 {
public:
    int fingersPerHand() const { return m_fingersperhand; }
    int numberOfHands() const { return m_numhands; }
    int totalFingers() const { return numberOfHands() * fingersPerHand(); }

    void setFingersPerHand(int num) { m_fingersperhand = num; }
    void setNumberOfHands(int num) { m_numhands = num; }

private:
    // Note: these are no longer `const` and I've given them default
    // values matching a human, so if you do nothing you'll get
    // human hands.
    int m_numhands = 2;
    int m_fingersperhand = 5;
};

使用修改后的类的示例:

#include <iostream>
int main()
{
    CreatureV2 human;
    std::cout << "A human has "
        << human.totalFingers() << " fingers. "
        << human.fingersPerHand() << " on each of their "
        << human.numberOfHands() << " hands.\n";

    CreatureV2 monster;
    monster.setFingersPerHand(7);
    monster.setNumberOfHands(5);
    std::cout << "A monster has "
        << monster.totalFingers() << " fingers. "
        << monster.fingersPerHand() << " on each of their "
        << monster.numberOfHands() << " hands.\n";

    CreatureV2 freak;
    freak.setFingersPerHand(9);
    // Note: I forgot to specify the number of hands, so a freak get 
    // the default 2.
    std::cout << "A freak has "
        << freak.totalFingers() << " fingers. "
        << freak.fingersPerHand() << " on each of their "
        << freak.numberOfHands() << " hands.\n";

    return 0;
}

注意:以上所有内容都假定您使用的是现代的C++14编译器。

我怎样用这种方式定义某个生物? - TrveBlack
好的,我看到你添加了一个使用示例。然而,现在我看到的问题是,在定义“人类”时,我必须记住声明“生物”的成员的顺序,而不是通过它们的“名称”来定义它。 如果我有50个易于记忆的名称(如手、脚、肢体、鼻子、耳朵等),但没有特定的顺序可以轻松推断,该怎么办? - TrveBlack
1
@TrveBlack,我希望这个答案现在已经完全回答了你的问题 :-) - Jesper Juhl
这种方法的问题在于每次访问派生属性时都必须重新计算。理想的解决方案是仅在基础变量更改时计算它。如果所涉及的计算耗时,则此举非常重要。 - passerby51

2
你所描述的是为什么封装和“成员变量应该是私有的”是C++中推荐的做法之一的原因之一。
如果每个变量都通过函数访问,那么一切都是一致的,并且可以从成员变量重构到计算。
一些语言,如C#或D,具有“属性”的概念,这提供了一种解决问题的方法,但是C++没有这样的结构。

2

为了好玩,可以使用代理来避免额外的括号(但需要付出一些额外的代价):

class RefMul
{
public:
    RefMul(int& a, int& b) : a(a), b(b) {}

    operator int() const { return a * b; }

private:
    int& a;
    int& b;
};

struct creature {
    int numberofhands;
    int fingersperhand;
    RefMul totalfingers{numberofhands, fingersperhand};
};

演示

注意:如果要在printf中使用RefMul,必须先将其转换为int类型:

printf("%d", int(human.totalfingers));

如果您使用C++的方式打印文本,则不需要使用该语句。
std::cout << human.totalfingers;

虽然从技术上回答了这个问题,但在问题所提供的上下文中使用类似这样的东西真的是非常糟糕的设计。 - user4442671
@Frank:我同意不应该使用它。 - Jarod42

1
如果你想要保持一致性,可以采取另一种方法进行更改。用常量方法替换两个成员变量,这些方法只返回成员变量的副本。这样,访问数据的方式就是一致的,你不必担心某些代码在不应该更改成员变量的情况下更改了它们的值。

0

其他人提供了非常好的答案。如果您想要一致性,可能最简单的方法是利用成员函数(如@Jesper Juhl所回答的)。

另一方面,如果您严格要求使用从其他成员自动计算出来的类成员,则可以使用属性。属性(如在C#和Groovy中定义)不是C++的标准特性,但有办法在C++中实现它们。这个SO问题概述了在C++中定义和使用属性的方式。我定义属性的最喜欢方式是利用Visual C++中属性的微软特定扩展(显然,这种方法只适用于Microsoft Visual C++)。Visual C++中属性的文档可以在MSDN中找到。使用Visual C++中的属性,您的代码可以修改为:

struct creature {
    int numberofhands; // use of public member variables are generally discouraged
    int fingersperhand;

    __declspec(property(get = get_totalfingers)) // Microsoft-specific
    int totalfingers;

private:
    int fingers;
    int get_totalfingers() 
    {
        return numberofhands * fingersperhand; // This is where the automatic calculation takes place. 
    }
}; 

这个类可以像这样使用:

#include <iostream>
int main()
{
    creature martian;
    martian.numberofhands = 2;
    martian.fingersperhand = 4; // Marvin the Martian had 4!

    // This line will print 8
    std::cout << "Total fingers: " << martian.totalfingers << std::endl;

    return 0;
}

正如我之前所说,属性不是 C++ 的标准特性,但有一些方法可以在 C++ 中获得它们,这些方法要么依赖于巧妙的技巧,要么使用编译器特定的功能。在我看来,使用简单的函数(如 @Jesper Juhl 所描述的)是更好的选择。

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