C++索引结构体

3

我想知道是否有一种方法可以对结构体进行索引,以便通过for循环运行成员变量。我一直在重复地询问一些东西,并通过cin>>获取输入:

struct Fruits {
   string name;
   int weight, diameter, prize;
   string colour, smell, taste;
}apple;

cout << "Apple Name?"; cin >> apple.name;
cout << "Apple weight?"; cin >> apple.weight;
.
.

你有想法。我希望像这样得到一些东西。
    vector<string> questions {"Apple Name?", "Apple weight?", ... };
    for (int j = 0; j < "StructureSize of Fruits"; j++)
    {
    cout << questions[j]; cin >> apple.j; //apple.j is the key point I want to know
    }

非常感谢您!


2
实际上并不需要使用不同的类型。你可以为你的类创建一个 operator[] 或者 operator(),但这对于这种情况来说是滥用。只需要编写一次 Fruits readFruit(); 函数就好了。 - Baum mit Augen
1
结构体字段的名称是C++中的编译时构造。它们在编译后的程序中不再可用(除了调试信息)。因此,很遗憾,您的问题无法通过这种方式解决。 - dhke
@BaummitAugen 这个函数可能是我可以尝试的,但其他部分目前超出了我的能力范围 :( - Caroline Schuster
@dhke - 谢谢你!我以为这可能是其他人已经遇到过的问题。但我找不到任何相关信息。现在我知道原因了 :/ - Caroline Schuster
1
@CarolineSchuster 这个函数是最好的(即最易读/易维护)解决方案。我只是提到了操作符的事情,因为我担心有人会建议这样做。在我看来,这是非常糟糕的想法。 - Baum mit Augen
5个回答

2

很抱歉,你不能这样做。

数组的访问方式如下。

int A[4];
A[0] = 5;
A[1] = 7;
A[3] = 8;

你知道在某些架构中,int类型占用4个字节。所以,数组A的第一个元素位于地址A,下一个元素位于地址A+4,也就是说A+sizeof(int)。
当你执行第一个赋值操作A[0]时,实际上代码类似于A+sizeof(int)*0,接下来的A[1]类似于A+sizeof(int)*1。如果你看到了这一点,你就知道A里面有整数,并且你知道访问每个元素的模式,在for循环中的通用模式是A+sizeof(int)*j。但是在你的结构体中,你不知道里面有什么,你不能做A+sizeof(X)*j,因为X不是固定的,所以如果你想这样做,你必须自己实现,但是这会很痛苦,因为你有混合数据类型。
如果你的结构体是固定的,那么用户的字符串也应该是固定的,那么为什么要尝试从循环中打印它们呢?向量需要分配空间和GPU,我认为最好的方法是只打印每个选项,然后读取用户输入。

我明白你的意思。希望我的问题不太傻。我打代码没问题,只是为了可读性考虑想用初始化循环。我认为真正的程序员不会把所有东西都写出来,肯定有缩短代码的方法。但如果这样做没问题,也没有人会因为长度而笑话,那我完全没问题 :) - Caroline Schuster
1
@CarolineSchuster 不用担心,这并不傻,你想要的东西确实很好,但是C/C++是一种有类型的语言,这有一些优缺点 :) - lcjury
@LightnessRacesinOrbit - 不明白这个笑话 :( - Caroline Schuster

2

你可以这样做。

但是这样做可能会过度杀死它。

#include <string>
#include <iostream>
#include <memory>

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/for_each.hpp>

struct Fruit {
    std::string name;
    int weight, diameter, price;
    std::string colour, smell, taste;
};

BOOST_FUSION_ADAPT_STRUCT(
    Fruit,
    (std::string, name)
    (int, weight)
    (int, diameter)
    (int, price)
    (std::string, colour)
    (std::string, smell)
    (std::string, taste)
)

std::array<char const *const, 7> questions = {
    "Name ?",
    "Weight ?",
    "Diameter ?",
    "Price ?",
    "Colour ?",
    "Smell ?",
    "Taste ?"
};

int main() {
    Fruit apple;

    auto input = [i = std::size_t{0u}](auto &field) mutable {
        do {
        std::cout << questions[i] << ' ';
        } while(!(std::cin >> field));
        ++i;
    };
    boost::fusion::for_each(apple, std::ref(input));

    std::cout << "----------\n";

    auto output = [i = std::size_t{0u}](auto const &field) mutable {
        std::cout << questions[i] << ' ' << field << '\n';
        ++i;
    };
    boost::fusion::for_each(apple, std::ref(output));
}

我刚刚得知boost::fusion::for_each不能接受可变的函数对象,因此需要使用std::ref()来解决,这有点奇怪并且有点危险...真是遗憾。
Name ? Apple
Weight ? 150
Diameter ? 8
Price ? 1
Colour ? red
Smell ? good    
Taste ? sweet
----------
Name ? Apple
Weight ? 150
Diameter ? 8
Price ? 1
Colour ? red
Smell ? good
Taste ? sweet

哇..你的意思是“但不能过度杀死我。”..哈哈。 那个有效吗? 我只理解了一半。 boost_,_std :: ref_,_mutable - 这些我还没有听说过的东西。 我需要几天时间来理解你的代码:( - 但现在,谢谢! - Caroline Schuster
1
@CarolineSchuster 当你想要杀一只苍蝇时,拿起一个苍蝇拍或者霰弹枪,Boost 经常是轨道打击。但它确实有效 :p - Quentin

1
enum FruitValueProperty { weight=0, diameter, prize, lastValueProperty };
enum FruitStringProperty { name=0, colour, smell, taste, lastStringProperty };

struct Fruit {
    int ValueProperty[3];
    string StringProperty[4];

    int Weight() { return ValueProperty[weight]; }
    int Diameter() { return ValueProperty[diameter]; }
    int Prize() { return ValueProperty[prize]; }

    string Name() { return StringProperty[name]; }
    string Colour() { return StringProperty[colour]; }
    string Smell() { return StringProperty[smell]; }
    string Taste () { return StringProperty[taste ]; }
};

Fruit fruit;
vector<string> questions {"Fruit weight?", ... "Fruit name?", ... };

for (int i=0; i<lastValueProperty; i++) {
    cout << questions[j];
    cin >> fruit.ValueProperty[i];
}

for (int j=0; i<lastStringProperty; i++) {
    cout << questions[lastValueProperty+j];
    cin >> fruit.StringProperty[j];
}

int dim = fruit.Diameter();
string sm = fruit.Smell();

如果您对字符串属性使用枚举(限制值的范围,例如名称的枚举,颜色的枚举等),那么您可以通过将两个循环压缩为一个循环来获得结果。
或者,您可以设置int值的属性,该属性接受一个字符串并将其解析为int,然后采用这种方法,但我认为上述方法是最简单和最清晰的方式,几乎可以实现您想要的目标。

谢谢!看起来很容易理解。你认为如果结构体只包含_ints_,我的问题会更容易吗? - Caroline Schuster
1
你可以使用枚举(enum)替换每个字符串,例如:enum Color { Blue, Red ... },然后将枚举值存储在单个整型 ValueProperty 数组中。你的 get 存取器函数可以将枚举转换为字符串,即你可以保留 string Name() 函数 - 但现在它必须进行枚举到字符串的转换。之后,你可以使用单个循环进行操作。你可以自行决定哪种选择更可取。 - Ricibob
1
你的用户输入问题"What color"将会是一个字符串。我猜真正的问题是你想要解析、验证和强制转换成固定值(例如蓝色)的程度,或者允许自由文本(例如浅绿色)。对于前者,请按照上面评论中建议的做法,对于后者,请按照原始答案进行操作。 - Ricibob
但是,如果你要做这样的事情,并且使用C ++,我们可以使用映射吗? - lcjury

1
这只是我使用纯C++、邪恶的宏和类型转换的看法。虽然有些过度,但基本计划是提供一个 operator[] 返回一个变量类型代理类,该代理类处理io流。此接口允许您按照所选择的方式以及所需的顺序公开类成员。为了避免重复自己并避免保持同步的负担,我在单独的头文件中声明要使用的字段,并在两次包含之间包含该头文件,但更改包含的宏。现在,如果我添加或删除一个字段,则只需要更改一个地方。

fruit_fields.h

//NO Header Guards desired since we want to include multiple times

DECLARE_FIELD(std::string,  Fruit, name)
DECLARE_FIELD(int,          Fruit, weight)
DECLARE_FIELD(int,          Fruit, diameter)
DECLARE_FIELD(int,          Fruit, prize)
DECLARE_FIELD(std::string,  Fruit, colour)
DECLARE_FIELD(std::string,  Fruit, smell)
DECLARE_FIELD(std::string,  Fruit, taste)

实用类

#include <iostream>
#include <string>
#include <assert.h>
#include <vector>
#include <array>
#include <typeinfo>


//TypeId info
namespace FieldType
{
    static const size_t String = typeid(std::string).hash_code();
    static const size_t Integer = typeid(int).hash_code();
};


/*
Field Proxy is a simple variant class
If you want to support more field types then more code is needed
*/
class FieldProxy
{

public:
    FieldProxy(int* value)
        : m_typeHash( typeid(int).hash_code() )
        , m_intValue(value)
    {}

    FieldProxy(std::string* value)
        : m_typeHash(typeid(std::string).hash_code())
        , m_stringValue(value)
    {}

    operator int() const
    {
        assert(m_typeHash == typeid(int).hash_code());
        return *m_intValue;
    }

    operator std::string() const
    {
        assert(m_typeHash == typeid(std::string).hash_code());
        return *m_stringValue;
    }

    friend std::ostream& operator<<(std::ostream& os, const FieldProxy& field);
    friend std::istream& operator>>(std::istream& os, FieldProxy& field);


private:
    size_t m_typeHash;
    union
    {
        int* m_intValue;
        std::string* m_stringValue;
    };

};
std::ostream& operator<<(std::ostream& os, const FieldProxy& field)
{
    if (field.m_typeHash == FieldType::Integer)
    {
        os << *(field.m_intValue);
    }
    else if (field.m_typeHash == FieldType::String)
    {
        os << *(field.m_stringValue);
    }

    return os;
}

std::istream& operator>>(std::istream& is, FieldProxy& field)
{

    if (field.m_typeHash == FieldType::Integer)
    {
        is >> *(field.m_intValue);
    }
    else if (field.m_typeHash == FieldType::String)
    {
        is >> *(field.m_stringValue);
    }       

    return is;
}

//data to obtain pointer to given field
struct FieldInfo
{
    size_t fieldType;
    size_t offset;
};

水果类

//The struct we actually care about
struct Fruit
{
public:

    static size_t getNumberFields() 
    {
        return m_numberFields;
    }

    FieldProxy operator[](size_t index);

private:
//First include just declares the class members as you might expect
// string name;
// int    weight; etc

#define DECLARE_FIELD(t, c, f) t f;
#include "fruit_fields.h"
#undef DECLARE_FIELD


    static FieldInfo m_fields[];
    static const size_t m_numberFields;

};

//Work out where the fields are positioned in the class
FieldInfo Fruit::m_fields[] =
{
   //second time around - define the macro to generate the data
   //needed to build the FieldProxy

#define DECLARE_FIELD( t, c, f) {typeid(t).hash_code(), offsetof(c,f)},
#include "fruit_fields.h"
#undef DECLARE_FIELD
};

//calculate how many fields there are
const size_t Fruit::m_numberFields = sizeof(Fruit::m_fields) / sizeof(Fruit::m_fields[0]);

//Index to a FieldInfo object and use it to create a FieldProxy variant
FieldProxy Fruit::operator[](size_t index)
{
    assert(index < m_numberFields);

    auto& fieldInfo = m_fields[index];

    if (fieldInfo.fieldType == FieldType::Integer)
    {
        return FieldProxy(reinterpret_cast<int*> (reinterpret_cast<char*>(this) + fieldInfo.offset));
    }
    else if (fieldInfo.fieldType == FieldType::String)
    {
        return FieldProxy(reinterpret_cast<std::string*> (reinterpret_cast<char*>(this) + fieldInfo.offset));
    }
    else
    {
        assert(false);
        return FieldProxy((int*)nullptr);
    }
}

主要
现在,您可以非常简单地索引到水果类:

int main() 
{
    Fruit apple;

    std::array<std::string, 7> questions = {
        "Name ?",
        "Weight ?",
        "Diameter ?",
        "Price ?",
        "Colour ?",
        "Smell ?",
        "Taste ?"
    };

    assert(questions.size() == Fruit::getNumberFields());

    for (size_t index = 0; index < Fruit::getNumberFields(); ++index)
    {
        bool succeeded = false;
        do {
            std::cout << questions[index] << ' ';
            if (!(std::cin >> apple[index]))
            {
                std::cin.clear();
                std::cin.ignore(std::numeric_limits<int>::max(), '\n');
            }
            else
            {
                succeeded = true;
            }
        } while (!succeeded);
    }

    std::cout << "----------" << std::endl;

    for (size_t index = 0; index < Fruit::getNumberFields(); ++index)
    {
        std::cout << questions[index] << ' ' << apple[index] << std::endl;
    }

    return 0;
}

哦,亲爱的,绝对过度了:o...我没想到会这么难。虽然我在要求的结构体中有20个成员变量,但手动输入所有的cin>>可能更好。我害怕这是一个真正的程序员会嘲笑我的事情,但在这里看来还好。 - Caroline Schuster

0

好的,这个线程已经开始很久了。

为了对具有相同类型值的结构进行索引,我编写了以下代码:

struct DataValues
{
    int Value1;
    int Value2;
    int Value3;
    int Value4;
    int Value5;
    int Value6;
    int Value7;
    int Value8;

    // the operator
    // return the reference to make it settable.
    int& operator[](int n) 
    {
        // the idea, get the pointer of the first element
        // and treat it as an array
        return (&Value1)[n];
    }
};

用法:

int main()
{
    DataValues values;

    values.Value4 = 6;
    values[1] = 3;
    values[0] = sizeof(DataValues);

    return 0;
}

缺点是您必须检查输入参数,以避免访问不属于结构的内存。

谢谢


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