将未知类型的元素输入到矢量中

Mon*_*que 8 c++ types vector

我正在开发一个程序,它接受用户的元素并对它们进行排序.对于这个程序,我必须使用向量,因为在用户输入之前元素列表的大小是未知的.我们的指示是:

用C++编写程序来实现元素列表的排序.元素可以是任何类型,但都可以是相同类型,如所有整数或所有浮点数或所有字符或所有字符串(字符串应按字典排序).您可以实现您选择的任何排序算法.

  1. 询问用户将有多少元素
  2. 要求用户输入元素
  3. 要求用户选择排序顺序:升序或降序或两者
  4. 打印输入和输出列表
  5. 用户不会提供有关元素类型的任何信息

我对矢量不太熟悉(老师基本上在课堂上略读主题)而且我的书并没有给我很多关于这个主题的信息.我遇到的问题是,在用户开始输入之前,我不知道元素列表的类型.到目前为止,我尝试过:

  • 创建一个void类型向量(现在我已经研究过它了,显然不允许)
  • insertInVector通过将第一个元素发送到函数并让函数根据第一个元素的类型确定要创建哪个向量类型来重载调用的函数(当我想到它时,这似乎是我最好的选择,除了我需要访问向量在函数终止之后,所以最终也是一个不行的)
  • #include <typeinfo>在程序中,找到第一个元素的类型,然后创建一个矢量使用vector<typeid(firstElement).name()>,老实说,我不知道为什么这不起作用,但事实并非如此.

就像我说的那样,我对矢量的经验非常有限,因为这是我第一次使用它们.我也是一个相当新的程序员,所以我在这方面所做的很多研究都已经过去了.任何可以提供的帮助都将非常感激!

Mat*_* M. 5

C++ 是一种静态类型语言。这意味着所有类型都应该在编译期间确定:运行程序时不能引入新类型。

  • 创建一个 void 类型向量(显然不允许,因为我已经研究过它了,哎呀)

void实际上是一个相当奇怪的类型,主要是当您期望一个类型(如函数返回类型)但没有提供任何类型时的占位符。void*用作指向未知类型的指针(主要在 C 中),但这完全是一个 hack,因为有关原始类型的信息被丢弃(就语言而言),因此这会导致实际使用该值执行操作时出现问题,因此获得。

  • 通过将第一个元素发送到函数并让函数根据第一个元素的类型确定要创建的向量类型来重载名为 insertInVector 的函数

  • #include <typeinfo>在程序中,找到第一个元素的类型,然后使用创建一个向量vector<typeid(firstElement).name()>,老实说,我不确定为什么这不起作用,但它没有。

不幸的是,这两种情况都不可能:既然你不能声明没有类型的变量,那么从什么类型firstElement开始呢?


你所描述的问题总体来说是比较困难的。基本上,这意味着您必须接受一串字符,然后编写一组规则来确定如何解释这些字符。这通常是通过使用语法对这些规则进行编码来完成的;但对于可能很简单的任务来说,语法可能会变得复杂。

让我举一个小例子:

class Input {
public:
    enum Type {
        Int,
        Double,
        String
    };

    static Input Parse(std::string const& s);

    Input(): _type(Int), _int(0), _double(0.0) {} // need to define a default...

    Type type() const { return _type; }

    int asInt() const {
        assert(_type == Int && "not an int");
        return _int;
    }

    double asDouble() const {
        assert(_type == Double && "not a double");
        return _double;
    }

    std::string const& asString() const {
        assert(_type == String && "not a string");
        return _string; 
    }

private:
    Type _type;
    int _int;
    double _double;
    std::string _string;
};
Run Code Online (Sandbox Code Playgroud)

显然,真正的挑战是正确Parse输入。

这个想法是使用一组规则,例如:

  • anint仅由数字组成,可选地带有前缀-
  • adouble完全由数字组成,最多 1 个.,并且可以选择添加前缀-
  • astring可以是任何东西,因此是我们的包罗万象

然后我们就可以编写方法的识别部分了Parse

static bool isInt(std::string const& s) {
    if (s.empty()) { return false; }
    
    // The first character may be among digits and '-'
    char const first = s.at(0);
    if (not isdigit(first) and first != '-') { return false; }

    // Subsequent characters may only be digits
    for (char c: s.substr(1)) {
        if (not isdigit(c)) { return false; }
    }

    // Looks like it is an int :)
    return true;
} // isInt

// Note: any int could be interpreted as a double too
static bool maybeDouble(std::string const& s) {
    if (s.empty()) { return false; }

    // The first character may be among digits, '.' and '-'
    char const first = s.at(0);
    if (not isdigit(first) and first != '.' and first != '-') { return false; }

    // There may only be one dot
    bool hasSeenDot = s.at(0) == '.';

    // Subsequent characters may only be digits and a dot now
    for (char c: s.substr(1)) {
        if (not isdigit(c) and c != '.') { return false; }

        if (c == '.') {
            if (hasSeenDot) { return false; } // no second dot allowed
            hasSeenDot = true;
        }
    }

    // Looks like it could be a double
    return true;
} // maybeDouble

static Input::Type guessType(std::string const& s) {
    if (isInt(s)) { return Input::Int; }

    // Test double after we ensured it was not an int
    if (maybeDouble(s)) { return Input::Double; }

    return Input::String;
} // guessType
Run Code Online (Sandbox Code Playgroud)

加上猜测逻辑,最后解析出来了:

Input Input::Parse(std::string const& s) {
    Input result;

    result._type = guessType(s);

    switch(result._type) {
    case Input::Int: {
        std::istringstream stream(s);
        s >> result._int;
        return result;
    }
    case Input::Double: {
        std::istringstream stream(s);
        s >> result._double;
        return result;
    }
    case Input::String:
        result._string = s;
        return result;
    }

    // Unreachable (normally)
    abort();
} // Input::Parse
Run Code Online (Sandbox Code Playgroud)

唷!

所以 ?差不多了。现在我们需要确定如何比较两个输入。如果它们都具有相同的类型,则很容易,否则您将需要确定任意逻辑。您可以轻松地将输入 Int 转换为输入 Double,但对于字符串来说有点奇怪。

// define < for comparing two instance of "Input",
// assuming they both have the same type
bool operator<(Input const& left, Input const& right) {
    assert(left.type() == right.type() && "Different Types!");

    switch(left.type()) {
    case Input::Int: return left.asInt() < right.asInt();
    case Input::Double: return left.asDouble() < right.asDouble();
    case Input::String: return left.asString() < right.asString();
    }
} // operator<
Run Code Online (Sandbox Code Playgroud)

最后,该程序:

int main(int argc, char* argv[]) {
    // parse command line
    std::vector<Input> inputs;

    // by convention argv[0] is the program name, it does not count!
    for (int i = 1; i != argc; ++i) {
        inputs.push_back(Input::Parse(argv[i]));

        // Detect that the type is the same as the first input
        if (inputs.size() >= 2) {
            if (inputs.back().type() != inputs.front().type()) {
                std::cerr << "Please only use one type among Int, Double and String\n";
                return 1; // non-0 is an error
            }
        }
    }

    // sort
    std::sort(inputs.begin(), inputs.end());

    // echo back to the user
    for (Input const& i: inputs) {
        switch(i.type()) {
        case Input::Int: std::cout << i.asInt() << "\n"; break;
        case Input::Double: std::cout << i.asDouble() << "\n"; break;
        case Input::String: std::cout << i.asString() << "\n"; break;
        }
    }

    // End of the program
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

当然,因为我不知道你想要处理的类型..我已经决定了一个任意的集合;)但这应该给你一个框架来作为自己的基础。

  • 从未听说过“boost::variant”?这比自己编写要理想得多。另外,您忘记了开关上的“break”语句。 (2认同)

aka*_*ppa 1

如果您知道用户可能输入的类型是什么,则可以使用模板和继承:

class Generic {
public:
  virtual void process_input() = 0; // Handles the next input from user
  virtual void process_output() = 0; // Processes the data inserted
};

template <typename T>
class HandleInput : public Generic {
private:
    std::vector<T> storage;
public:
    HandleInput(T first)
    {
      storage.push_back(first);
    }

    void process_input()
    {
      // do whatever you want
    }

    void process_output()
    {
      // do whatever you want
    }
};

int main(int argc, char **argv)
{
  // Get first input
  Input i = input();
  Generic *g;

  // Instantiate the "right" generic with a switch
  switch (i.type) {
    case T:
      g = new HandleInput<T>(i.value);
  }

  // Use Generic from here onwards
}
Run Code Online (Sandbox Code Playgroud)

这只是一个想法(Input不能这样实现,您需要使用从用户获取某些内容并确定其类型的逻辑来更改该部分),但它的好处是将类型屏蔽为泛型类,因此您可以考虑您的代码围绕提供的接口Generic.

另一个想法(可能更简单)是使用 astd::vector<void*>和 anenum来告诉您向量中存储的数据的类型是什么。当您将来需要在某个地方处理该数据时,您可以打开枚举以将向量元素适当地转换为正确的类型并将它们分派给适当的代码。

编辑:另一个想法是定义一个模板化函数,它接受输入并使用标准比较器对数组进行排序:

#include <iostream>

#include <vector>
#include <algorithm>
#include <boost/lexical_cast.hpp>

template <typename T>
void print_v(std::vector<T> &v)
{
    typename std::vector<T>::iterator it;
    for (it = v.begin(); it != v.end(); it++)
        std::cout << *it << " ";
    std::cout << std::endl;
}

template <typename T>
void sort_and_print(T first, size_t n, bool asc)
{
    std::vector<T> v;
    v.push_back(first);
    for (size_t i = 0; i < n; i++) {
        std::string s;
        std::cin >> s;
        T e = boost::lexical_cast<T>(s);
        v.push_back(e);
    }

    print_v(v);
    if (asc)
        std::sort(v.begin(), v.end(), std::greater<T>());
    else
        std::sort(v.begin(), v.end());
    print_v(v);
}

int main(int argc, char **argv)
{
    std::string s = "test";
    sort_and_print(s, 2, true);
    unsigned int j = 3;
    sort_and_print(j, 2, true);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

确定第一个输入类型的逻辑取决于您(也许您可以提出另一个问题);)