簡體   English   中英

將未知類型的元素輸入到矢量中

[英]Inputting elements of unknown type into a vector

我正在開發一個程序,它接受用戶的元素並對它們進行排序。 對於這個程序,我必須使用向量,因為在用戶輸入之前元素列表的大小是未知的。 我們的指示是:

用C ++編寫程序來實現元素列表的排序。 元素可以是任何類型,但都可以是相同類型,如所有整數或所有浮點數或所有字符或所有字符串(字符串應按字典排序)。 您可以實現您選擇的任何排序算法。

  1. 詢問用戶將有多少元素
  2. 要求用戶輸入元素
  3. 要求用戶選擇排序順序:升序或降序或兩者
  4. 打印輸入和輸出列表
  5. 用戶不會提供有關元素類型的任何信息

我對矢量不太熟悉(老師基本上在課堂上略讀主題)而且我的書並沒有給我很多關於這個主題的信息。 我遇到的問題是,在用戶開始輸入之前,我不知道元素列表的類型。 到目前為止,我嘗試過:

  • 創建一個void類型向量(現在我已經研究過它了,顯然不允許)
  • 通過將第一個元素發送到函數並讓函數根據第一個元素的類型確定要創建哪個矢量類型來重載一個名為insertInVector的函數(當我想到它時,這似乎是我最好的選擇,除了我需要訪問函數終止后的向量,所以最終也是不行的)
  • #include <typeinfo>在程序中,找到第一個元素的類型,然后使用vector<typeid(firstElement).name()>創建一個向量,老實說,我不確定為什么這不起作用,但它沒有“T。

就像我說的那樣,我對矢量的經驗非常有限,因為這是我第一次使用它們。 我也是一個相當新的程序員,所以我在這方面所做的很多研究都已經過去了。 任何可以提供的幫助都將非常感激!

C ++是一種靜態類型的語言。 這意味着在編譯期間應確定所有類型:在運行程序時不能引入新類型。

  • 創建一個void類型向量(現在我已經研究過它了,顯然不允許)

void實際上是一個非常奇怪的類型,大多數是占位符,當你期望一個類型(如函數返回類型)並且沒有提供時。 void*用作指向未知類型的指針(主要在C中),但這是一個非常黑客,因為關於原始的信息被丟棄(就語言而言)所以這會導致問題實際上與如此獲得的價值。

  • 通過將第一個元素發送到函數並讓函數根據第一個元素的類型確定要創建的向量類型來重載一個名為insertInVector的函數

  • #include <typeinfo>在程序中,找到第一個元素的類型,然后使用vector<typeid(firstElement).name()>創建一個向量,老實說,我不確定為什么這不起作用,但它沒有“T。

遺憾的是兩者都不可能:因為你不能在沒有類型的情況下聲明變量, 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;
};

顯然,真正的挑戰是正確Parse輸入。

這個想法是使用一組規則,例如:

  • int只由數字組成,可選擇帶前綴-
  • double僅由數字組成,最多只有一個. 並可選擇以-為前綴
  • 一個string可以是任何東西,因此是我們的全部

然后我們可以編寫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

隨着猜測邏輯一起,最后解析來了:

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

唷!

那么? 差不多了。 現在我們需要確定如何比較兩個輸入。 如果它們都具有相同的類型很容易,如果不是,則需要確定任意邏輯。 你可以很容易地在輸入Double中轉換輸入Int,但是對於字符串來說它有點怪異。

// 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<

最后,該計划:

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;
}

當然,因為我不知道你想要處理的類型..我已經決定了一個任意的集合;)但是這應該給你一個基於自己的骨架。

查看注釋中所述問題的實際要求,我建議您將所有輸入存儲在std::vector<std::string>並使用std :: sort對向量進行排序 因此,您可以根據解釋向量中的字符串來表示排序邏輯,而不是擔心不同的類型。 所以

  1. 根據字符串表示的內容實現字符串的排序函數(稍后)
  2. 將輸入存儲為向量中的字符串。
  3. 確定字符串表示的類型
  4. 根據此類型選擇排序功能
  5. 使用std::sort和相應的排序函數對向量進行排序。

關於排序函數, std::sort接受一個二元仿函數或函數,它將兩個elelemts的“小於”比較應用,所以你的函子或函數應該看起來像

bool foo(const std::string& rhs, const std::string& lhs) {
  // implement the logic
}

編輯 :查看更近期的評論,似乎主要目的是練習可能是為不同類型實現排序算法。 在這種情況下,我建議由C ++標准庫所采取的做法如下,即,為了實現在術語分類或兩種類型之間的小於比較,從而去耦從類型分類邏輯進行排序。 所以你需要一個模板排序函數,模板化迭代器類型和比較函數/仿函數。

如果您知道用戶可以輸入的類型,您可以使用模板和繼承:

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
}

這只是一個想法( Input不能像那樣實現,你需要用從用戶那里得到東西的邏輯來改變那個部分並確定它的類型),但是它具有將類型掩蓋到泛型類中的好處,所以你可以使您的代碼圍繞Generic提供的接口。

另一個想法(可能更容易)是使用std::vector<void*>和一個enum ,它告訴你存儲在向量中的數據類型是什么。 當您需要在將來某處處理該數據時,可以打開枚舉以適當地將向量元素轉換為正確的類型並將它們分派給相應的代碼。

編輯 :另一個想法是定義一個模板化函數,它接受輸入並使用標准比較器對數組進行排序:

#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;
}

確定第一個輸入類型的邏輯取決於您(也許您可以打開另一個問題);)

這個問題有兩個方面:解析和排序。

  • 您可以使用正則表達式來檢查用戶輸入的數據類型。
  • 您可以使用cin來解析數據。

第一:意識到在收到所有用戶輸入之前你不一定知道用戶輸入的類型 〜例如:考慮一個用戶名列表:

728278243
390349346
495045594
elizabeth

因此,最好不要假設最好地了解傳入的數據( 可能導致令人沮喪的用戶體驗 ),而是傾向於將所有內容視為潛在的字符串。 將所有原始輸入存儲為字符串,以便輸出與輸入格式相同的格式。 你可以使用say,枚舉類型來切換排序比較器考慮使用mutliset/multimap 在這里,您將構建一個有序集。 所以沒有必要排序。 注意:構造N個元素的有序集合的復雜性, 或者對於N個未排序的列表元素的單個排序,大致相當於〜> NlogN

對於您的任務而言,它幾乎不重要,但實際上取決於列表的使用方式,一種或另一種方法在性能方面更為合適。

如果您已經使用了std::vector那么std::multimap應該不會太可怕。 松散地,它是一個關聯的鍵值對數組。 這里的multi意味着它可以存儲具有相同鍵的多個元素(在這里,您想要)。


在這個例子中,我使用boost regex庫來確定一些時髦的輸入數據類型。
(例如: sudo apt-get install libboost-regex1.46-dev

這個正則表達式可能看起來很神秘,但在i / web上有很多關於幾乎所有可想到的模式的例子。 [ 注意:C ++ 11正則表達式幾乎是boost regex的替代品。 即:boost regex應該與新興的C ++ 11標准向前兼容 ]


blah.cpp:

#include <iostream>
#include <sstream>
#include <string>
#include <list>
#include <map>
#include <set>
#include <boost/regex.hpp>    
//NB: GNU gcc added *experimental support for regular expressions in TR1 v 4.3.0.
//    compile with:  -std=c++0x

using namespace std;
using namespace boost;

//some example input data-types (perhaps notably missing a date!) 
const regex re_char("[^0-9]", regex_constants::extended); //non numeric chars
const regex re_digit("[[:digit:]]+", regex_constants::extended); //a string of only digits in range [0..9] ~ie: Z+
const regex re_xdigit("0[xX][[:xdigit:]]+", regex_constants::extended); //support hex iff starts with '0x' or '0X'
const regex re_float("[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?", regex_constants::extended); //all kinds of numbers


int main(int argc, char** argv)
{    
    int i, countc=0;
    double d;
    string str;
    int element_count;    

    do
    {
        cout << "how many elements will there be? "; 
        if (cin >> element_count) break;
        cin.clear();
        cin >> str;
        cout << "\033[A\033[2K" << flush;
    }
    while(13);
    cin.ignore(128,'\n'); 

    multimap<double, string> list_num; 
    multimap<double, string> list_fp; 
    //NB: below, by way of example, construction using the 'greater<int>' comparison class achieves _descending_ order 
    multimap<int, string, greater<int> > list_int; 
    list<string> list_str; 

    for (int next=0; next < element_count; next++)
    {
        cout << "\033[A\033[2K" << flush;
        cout << "enter next element in list ["<< next+1 << "/" << element_count << "] : "; 
        getline (cin,str);

        if (regex_match(str, re_xdigit))
        {
            //see all about manipulators here:
            //http://www.cplusplus.com/reference/iostream/istream/operator%3E%3E/
            stringstream(str) >> hex >> i;            
            list_int.insert(pair<int, string>(i, str)); 
            list_num.insert(pair<double, string>(i, str)); 
        }
        else if (regex_match(str, re_digit))
        {
            stringstream(str) >> i;            
            list_int.insert(pair<int, string>(i, str));            
            list_num.insert(pair<double, string>(i, str)); 
        }
        else if (regex_match(str, re_float))
        {
            stringstream(str) >> d;    
            list_fp.insert(pair<double, string>(d, str));        
            list_num.insert(pair<double, string>(d, str)); 
        } 

        if (regex_match(str, re_char)) countc++;      
        list_str.push_back(str);
    }    

    cout << "\033[A\033[2K" << flush;

    cout << "input: unsorted list:" << endl;
    for (list<string>::iterator it=list_str.begin(); it!=list_str.end(); it++) 
        cout << *it << endl;

    if (list_int.size() == element_count)
    {
        cout << endl << "output: sorted list of Z+ types:" << endl;
        for (multimap<int, string>::iterator it=list_int.begin() ; it != list_int.end(); it++ )
            cout << (*it).second << endl;
    }
    else if (list_fp.size() == element_count)
    {
        cout << endl << "output: sorted list of fp types:" << endl;
        for (multimap<double, string>::iterator it=list_fp.begin() ; it != list_fp.end(); it++ )
            cout << (*it).second << endl;
    }
    else if (list_num.size() == element_count)
    {
        cout << endl << "output: sorted list of numeric types:" << endl;
        for (multimap<double, string>::iterator it=list_num.begin() ; it != list_num.end(); it++ )
            cout << (*it).second << endl;
    }
    else //output as sorted strings ~but in _descending_ order, using reverse iterator, by way of example
    {
        list_str.sort(); //but best to use list_str.sort(greater<string>()); with forward iterators
        cout << endl << "output: sorted list of " <<  (countc == element_count ? "non numeric char" : "string") << " types:" << endl;
        for (list<string>::reverse_iterator it=list_str.rbegin(); it!=list_str.rend(); ++it) 
            cout << *it << endl;        
    }   

    return 0;
}

示例在Ubuntu上編譯和運行。 命令行的東西:

$
$ lsb_release -d
Description:    Ubuntu 11.10

$ g++ --version
g++ (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1 

$ g++ --pedantic -oblah blah.cpp -lboost_regex
$ ./blah
input: unsorted list:
4.77
2.0e+2
-.3
11
0x10

output: sorted list of numeric types:
-.3
4.77
11
0x10
2.0e+2
$


注意:這是示例代碼:

  • 可以在這里進行許多優化。 很顯然你並不需要那么多stl是我使用的容器。
  • 我沒有嚴格處理排序的方向(但顯示可以實現的幾種方式)。
  • 將類型特定的功能封裝在C ++對象中也可能會很好; 有一個基類,然后為你想要支持的每種類型派生類〜但這個功課對嗎? - 那么可能不值得去船上;)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM