c++ - 在 vector 中查找字符串的索引

标签 c++ arrays vector indexing

我正在移植我的“加密器/解密器”程序,该程序使用 key ( like this one ) 来触发文本。加密部分已完成,现在我正在研究解密器:

  • 在数组
  • 中查找 128 个字符的字符串
  • 获取该字符串的索引
  • chars[index_of_long_string] 获取字符
  • 将该字符连接到字符串 pass

  • 通过一些谷歌搜索,我发现在 C++ 中使用 vector 会更好。我编写了以下代码以从我的数组中形成一个 vector :
    vector<string> arrayAllReplaceDecryptVector(std::begin(arrayAllReplaceDecrypt), end(arrayAllReplaceDecrypt));
    

    然后使用此代码;获取 key_string 的索引,将字符串与 chars[index] 连接起来,然后删除 key_string 的前 128 个字符:
    size_t index = std::distance(arrayAllReplaceDecryptVector.begin(), find(arrayAllReplaceDecryptVector.begin(), arrayAllReplaceDecryptVector.end(), pass.substr(0,128)));
            output = output + chars[index];
            pass = pass.substr(128, pass.length());
    

    下图是第 127 行的当前故障,其中 cin >> pass传递给函数时被切断。这似乎发生在输入中第一次出现空格字符时。这从上面的索引代码中产生了 95。显示的输入是加密后的“Hello”,索引号应该是14、9、22、22,然后是28。最终的输出应该是“Hello”。

    enter image description here

    此代码将重现错误(但是请注意,您需要与编译代码位于同一目录中的这两个 .txt 文件才能运行程序 - [1][2] ):
    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <stdlib.h>
    #include <time.h>
    #include <string>
    
    using namespace std;
    
    void fillChars(char(&chars)[95]);
    string decrypt(char(chars)[95], string pass, string& lastJob);
    
    char chars[95];
    int runs = 0, lastJobID = 0;
    bool keyCreated = false;
    bool charsFilled = false;
    
    int main()
    {
        string pass, lastJob;
        //One time fills the character array
        if (charsFilled == false)
        {
            fillChars(chars);
        }
        cout << "\nEnter a message to decrypt:\n";
        cin >> pass;
        lastJob = decrypt(chars, pass, lastJob);
        lastJobID = 2;
    }
    void fillChars(char(&chars)[95])
    {
        //Opens chars.txt
        ifstream characters("chars.txt");
        for (int i = 0; i <= 94; i++)
        {
            if (i == 94)
            {
                chars[i] = ' ';
            }
            else
            {
                //Puts characters into array
                characters >> chars[i];
            }
        }
        characters.close();
    }
    
    string arrayAllReplaceDecrypt[95];
    string decrypt(char(chars)[95], string pass, string& lastJob)
    {
        cout << "\n" << pass << "\n";
        //Encrypted string
        string output;
        //Opens key
        ifstream ifs("Key.txt");
        //Reads entire key to string
        string content((std::istreambuf_iterator<char>(ifs)),
            (std::istreambuf_iterator<char>()));
        //Loops to add chars to
        for (int i = 0; i < 94; i++)
        {
            //Adds 128 chars to replace array
            arrayAllReplaceDecrypt[i] = content.substr(1, 128);
            try
            {
                //Deletes old chars
                content = content.substr(130, content.size());
            }
            catch (const exception& e)
            {
                //content.clear();
            }
        }
        ifs.close();
        vector<string> arrayAllReplaceDecryptVector(std::begin(arrayAllReplaceDecrypt), end(arrayAllReplaceDecrypt));
        while (pass.length() > 0)
        {
            size_t index = std::distance(arrayAllReplaceDecryptVector.begin(), find(arrayAllReplaceDecryptVector.begin(), arrayAllReplaceDecryptVector.end(), pass.substr(0, 128)));
            cout << "\n" << index << "\n";
            output = output + chars[index];
            pass = pass.substr(129, (pass.length() - 128));
        }
        return(output);
    }
    

    最佳答案

    很有意思。你的程序随着时间的推移而发展,你发现了一些问题并逐步修补它们。有时通过两次犯同样的错误来消除错误。

    尽管如此,仍有一些必须修复的严重错误。这些大多是越界错误。然后是样式错误,例如全局变量和错误数据类型的使用。
    所以,软件设计的一个大问题,必须重构

    最后但并非最不重要的一点是,您如何加密的想法是行不通的,因为您将第一个字母和 key 一起存储在 Key.txt”中。

    sb = sb + (chars[(sb.length()) / 128]);
    

    您始终添加后跟 128 字节 key 的明文字符。所以你的方法的安全性是 0。

    那么,接下来。您必须了解数组的索引和边界。数组索引从 0 开始,最后一个有效索引是数组的大小减去 1。对于数组 { 1,2,3,4 } ,大小为 4,第一个索引为 0,最后一个索引为 3 !

    您必须始终检查您使用的索引是否有效。例子。在你的声明中:
    chars[rand() % 95 + 1]
    

    你创造了许多越界问题。 rand可以产生介于 0 和非常大的数字。如果这么大的数字的余数是 94,那么你在索引上加 1,得到无效的 95。因为,在变量定义中
    char chars[95];
    int runs = 0;
    

    运行在字符之后定义,您访问变量的低字节 runs .你可以测试一下。在第一次创建 key 文件时,您会看到许多 0 字符。在第二次运行时,您将创建 '\001' 字符。

    最好是使用现代 C++ 容器来帮助您进行此类检查。

    接下来,阅读您帖子的人无法理解您为什么要做某些事情。所以,我们想知道你为什么写 substr(1,128)然后 content = content.substr(130, content.size()); .这样,您消除了第一个清除字符,然后读取了 128 个字节。然后您跳过附加到 key 文件的“\n”。所有这些都应该重构。

    另一个问题。用于阅读您的“chars.txt”。您发现您没有在循环中读取空格 ' ' 字符。但是您没有检查 IO 流的“skipws”属性,而是通过添加一个空格作为最后一个字符来修补它。您可以使用函数 getgetline来克服这个问题。

    现在回答你的问题。在解密你使用
    for (int i = 0; i < 94; i++)
    

    所以,你阅读了除最后一行之外的所有内容。结束然后你做
    pass = pass.substr(129, pass.length());
    

    129是错误的。它必须是 128。你已经去掉了第一个清晰的字符和“\n”。然后它起作用了,我在调试器中对其进行了测试。

    但是,所有这些都不起作用,因为您为小输入字符串创建了巨大的加密输出。因此,对于 3 个字符“abc”,您将创建 384 个字节的输出。谁应该输入,尤其是错误地创建 0 个字符。因此,您甚至无法测试代码。您应该将加密后的数据存储在一个文件中,并且为了解密,再次从文件中读取它。

    顺便说一句,您正在调用您的函数以递归方式运行,这是错误的。

    还有更多的发现。因此建议重构它。并请添加大量错误检查。特别是对于任何 IO 功能。您必须测试这是否有效。

    如果您有更多问题,请提问。

    为了向您展示如何进行错误检查,我为您创建了一个完整可行的解决方案。请参见:
    #include <iostream>
    #include <vector>
    #include <fstream>
    #include <string>
    #include <random>
    #include <algorithm>
    #include <iterator>
    #include <numeric>
    
    // Definitions ---------------------------------------------------------------------------------------------------------------------
    // Filenames that we want to use in our software
    const std::string fileNamePrintableCharacters{ "chars.txt" };
    const std::string fileNameKey{ "Key.txt" };
    
    // That many printable characters we will use. We will use the ASCII and hence have characters in the range 32-126, and that are 95
    constexpr size_t NumberOfUsedPrintableCharacters = 95U;
    // One character will be encrypted with this number of bytes
    constexpr size_t NumberOfEncryptionCharacters = 128U;
    
    // This is datatype for our key. For each printable character we use NumberOfEncryptionCharacters. 
    using KeyData = std::vector<std::string>;
    
    // ----------------------------------------------------------------------------------------------------------------------------------
    // Read the printable characters from the definition file
    std::string getPrintableCharacters() {
    
        // Here we sill store the result. Initialize all values with 0
        std::string printableCharacters{};
    
        // Open the file with the printable characters and check, if the file could be opened
        if (std::ifstream printableCharactersFileStream{ fileNamePrintableCharacters }; printableCharactersFileStream) {
    
            // Read the printable characters and check, if that worked
            if (!std::getline(printableCharactersFileStream, printableCharacters) || (printableCharacters.size() != NumberOfUsedPrintableCharacters)) {
                std::cerr << "\n\n*** Error: Could not read the expected data from ('" << fileNamePrintableCharacters << "')\n";
                printableCharacters = {};
            }
        }
        else {   // In case that we could not open the file, show error message
            std::cerr << "\n\n*** Error: File with printable characters ('" << fileNamePrintableCharacters << "') could not be opened\n";
        }
        // Return the array with printable characters to the caller using Return Value Optimization / Copy Elision
        return printableCharacters;
    }
    
    // ----------------------------------------------------------------------------------------------------------------------------------
    std::string generateNewPrintableCharactersFile() {
    
        // Here we will stor the resulting printabl characters string
        std::string printableCharacters(NumberOfUsedPrintableCharacters, '\0');
    
        // Open output file and chcke, if it could be opened
        if (std::ofstream printableCharactersFileStream{ fileNamePrintableCharacters }; printableCharactersFileStream) {
    
            // Create all printable characters
            std::iota(printableCharacters.begin(), printableCharacters.end(), ' ');
    
            if (!(printableCharactersFileStream << printableCharacters)) {
                std::cerr << "\n\n*** Error: Could not create printable character file\n";
                printableCharacters.clear();
            }
        }
        return printableCharacters;
    }
    
    // ----------------------------------------------------------------------------------------------------------------------------------
    KeyData getKeyData() {
    
        // Here we will store all key data
        KeyData keyData{};
    
        // indicator for error. In case of error, we delete all key data
        bool thereWasAnError = false;
    
        // Open the file with the key data and check, if the file could be opened
        if (std::ifstream keyDataFileStream{ fileNameKey }; keyDataFileStream) {
    
            // Read NumberOfUsedPrintableCharacters lines from the key data file
            for (size_t i{}; (i < NumberOfUsedPrintableCharacters) && keyDataFileStream && !thereWasAnError; ++i) {
    
                // Read a line, and check, if that worked
                if (std::string line{}; std::getline(keyDataFileStream, line)) {
    
                    // Sanity check. Check, if there were the expected number of characters in the key file
                    if (line.size() != NumberOfEncryptionCharacters) {
                        std::cerr << "\n\n*** Error: Invalid data in keyfile line " << i + 1 << "\n";
                        thereWasAnError = true;
                    }
                    else { // Save new read line with encryption characters
                        keyData.push_back(line);
                    }
                }
            }
            // Next sanity check. Check, if we have read enough lines
            if (keyData.size() != NumberOfUsedPrintableCharacters) {
                std::cerr << "\n\n*** Error: Not enough data in key file\n";
                thereWasAnError = true;
            }
        }
        else {   // In case that we could not open the file, show error message
            std::cerr << "\n\n*** Error: File with key data ('" << fileNameKey << "') could not be opened\n";
            thereWasAnError = true;
        }
        // In case of any error, reset the key data
        if (thereWasAnError) keyData.clear();
    
        // Return the array with printable characters to the caller using Return Value Optimization / Copy Elision
        return keyData;
    }
    
    // ----------------------------------------------------------------------------------------------------------------------------------
    KeyData generateNewKeyFile(const std::string& printableCharacters) {
    
        // Here we will store all key data
        KeyData keyData{};
    
        // Sanity Check. Do we have the expected number of printable characters
        if (NumberOfUsedPrintableCharacters == printableCharacters.size()) {
    
            // Open already now the output file, and check, if it could be opened.
            if (std::ofstream keyDataFileStream{ fileNameKey }; keyDataFileStream) {
    
                // Initialize random number generation. Lambda for generating randim values
                std::random_device rd;  //Will be used to obtain a seed for the random number engine
                std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
                std::uniform_int_distribution<size_t> distribution(0U, NumberOfUsedPrintableCharacters - 1);
    
                auto randomCharacter = [&]() { return printableCharacters[distribution(gen)]; };
    
                // We want to create a key string for all printable characters
                for (size_t i{}; i < NumberOfUsedPrintableCharacters && keyDataFileStream; ++i) {
    
                    // Temporary for string with random chars
                    std::string key(NumberOfEncryptionCharacters, '\0');
    
                    // Genrate string with random content
                    std::generate_n(key.begin(), NumberOfEncryptionCharacters, randomCharacter);
    
                    // Write data to file and check for possible error
                    if (!(keyDataFileStream << key << "\n")) {
                        std::cerr << "\n\n*** Error: Could not write line to key file\n";
                        keyData = {}; break;
                    }
                    else {
                        // Store the next line in the file in in the internal vector
                        keyData.push_back(std::move(key));
                    }
                }
                if (keyData.size() != NumberOfUsedPrintableCharacters) std::cerr << "\n*** Geeneral error during creation of key file\n";
            }
            else {
               // In case that we could not open the file, show error message
                std::cerr << "\n\n*** Error: File with key data ('" << fileNameKey << "') could not be opened for writing\n";
            }
        }
        return keyData;
    }
    // ----------------------------------------------------------------------------------------------------------------------------------
    std::string encrypt(const std::string& toBeEncrypted, const KeyData& keyData, const std::string& printableCharacters, const std::string& outputFileName) {
    
        // Here we will store the encrypted result
        std::string encryptedString{};
    
        // First open output file, if this does not work, no need to do something else
        if (std::ofstream outFileStream{ outputFileName }; outFileStream) {
    
            // Then make some sanity checks
            if (((keyData.size() != NumberOfUsedPrintableCharacters)) || (printableCharacters.size() != NumberOfUsedPrintableCharacters)) {
                std::cerr << "\n\n*** Error: Preconditions for encryption not met\n";
            }
            else {
                // Go through all data in source string that shall be encrypter
                for (const char c : toBeEncrypted) {
                    // Search for the character in our printable character string and check, if it is available and if it is in the key data
                    if (size_t index{ printableCharacters.find(c) }; (index != std::string::npos) && (index < NumberOfUsedPrintableCharacters)) {
    
                        // Prepare output
                        encryptedString += keyData[index] + "\n";
                        outFileStream << keyData[index] << "\n";
                    }
                    else {
                        std::cerr << "\n\n*** Error: Invalid character '" << c << "' in input string\n";
                    }
                }
            }
        }
        else {
            // In case that we could not open the file, show error message
            std::cerr << "\n\n*** Error: output file ('" << outputFileName << "') for encrypted data could not be opened\n";
        }
        return encryptedString;
    }
    // ----------------------------------------------------------------------------------------------------------------------------------
    std::string decrypt(const KeyData& keyData, const std::string& printableCharacters, const std::string& inputFileName) {
        // Here we will store the decrypted result
        std::string decryptedString{};
    
        // First open input file, if this does not work, no need to do something else
        if (std::ifstream inFileStream{ inputFileName }; inFileStream) {
    
            // Then make some sanity checks
            if (((keyData.size() != NumberOfUsedPrintableCharacters)) || (printableCharacters.size() != NumberOfUsedPrintableCharacters)) {
                std::cerr << "\n\n*** Error: Preconditions for encryption not met\n";
            }
            else {
    
                // Read all lines of the file
                for (std::string line{}; std::getline(inFileStream, line); ) {
    
                    // Search this line in the keydata
                    if (KeyData::const_iterator searchResult = std::find(keyData.begin(), keyData.end(), line); searchResult != keyData.cend()) {
    
                        // Calculate the distance
                        if (const int index{ std::distance(keyData.begin() , searchResult) }; index < printableCharacters.size()) {
                            decryptedString += printableCharacters[index];
                        }
                        else { // With all the checks, this error should not occur
                            std::cerr << "\n\n*** Unexpected error while decrypting data\n";
                        }
                    }
                    else {
                        std::cerr << "\n\n*** Error: Could not decrypt data\n";
                    }
                }
            }
        }
        else {
            // In case that we could not open the file, show error message
            std::cerr << "\n\n*** Error: input file ('" << inputFileName << "') with encrypted data could not be opened\n";
        }
        // Last sanity check
        if (decryptedString.empty()) std::cerr << "\nGeneral Error during decryption\n";
    
        return decryptedString;
    }
    
    int main() {
    
        // First read the printable character string. Check, if that was successfull
        if (std::string printableCharacters{ getPrintableCharacters() }; printableCharacters.length() == NumberOfUsedPrintableCharacters ||
            (printableCharacters = generateNewPrintableCharactersFile()).size() == NumberOfUsedPrintableCharacters) {
    
            // Next, try to read a KeyData file
            if (KeyData keyData{ getKeyData() }; keyData.size() == NumberOfUsedPrintableCharacters ||
                (keyData = generateNewKeyFile(printableCharacters)).size() == NumberOfUsedPrintableCharacters) {
    
                // main programm loop 
                for (bool doRunMainLoop{ true }; doRunMainLoop; ) {
    
                    // Show menu to the user
                    std::cout << "\n\n\nPlease select, what to do. Press\n\n\t1\tfor encryption\n\t2\tfor decryption\n\t3\tto generate new key file\n\t4\tto exit\n\nSelection: ";
    
                    // Get input from user and check, if that worked
                    if (unsigned int selection{}; std::cin >> selection) {
    
                        switch (selection) {
                        case 1U:
                            // Encryption
                            std::cout << "\nEncryption\n\nEnter first a filename and then the word that you want to encrypt:\n";
    
                            if (std::string filename{}, word{}; std::cin >> filename >> word) {
                                std::cout << "\nEncrypted Data:\n\n" << encrypt(word, keyData, printableCharacters, filename) << "\n";
                            }
                            else std::cout << "\n\nError while reading your data. Please try again\n";
                            break;
                        case 2U:
                            // Decryption
                            std::cout << "\nDecryption\n\nEnter first a filename with the ecrypted information:\n";
    
                            if (std::string filename{}; std::cin >> filename) {
                                std::cout << "\nDecrypted Data:\n\n" << decrypt(keyData, printableCharacters, filename) << "\n";
                            }
                            else std::cout << "\n\nError while reading your data. Please try again\n";
                            break;
                        case 3U:
                            // Generate new keyfile
                            if ((keyData = generateNewKeyFile(printableCharacters)).size() != NumberOfUsedPrintableCharacters) {
                                std::cerr << "\n\n*** Error: New keyfile could not be generated. Exit\n";
                                doRunMainLoop = false;
                            }
                            break;
                        case 4U:
                            doRunMainLoop = false;
                            break;
                        default:
                            std::cout << "\nInvalid selection. Pleas try again\n";
                        } // End select
                    }
                    else std::cerr << "\n\n\nError: Unexpected error while reading from console\n";
                } // End for
            }
            else std::cerr << "\n\n*** Error: Problem with key data generation\n";
        }
        else std::cerr << "\n\n*** Error: No printable character data present\n";
    
        return 0;
    }
    

    关于c++ - 在 vector 中查找字符串的索引,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61167570/

    相关文章:

    c++ - 如何查看 vector 中的内容?

    C++ 揭秘第 13 章 (2004) Jeff Kent : Ifstream program does not return expected output

    c++ - 全局键盘 Hook 不起作用

    c++ - 我应该在 C++ 字符串文字上使用 _T 还是 _TEXT ?

    python:找到两个数组中两点之间的最小距离

    scala - 如何将 Array[Long] 转换为 Scala 数据帧中的 Vector 类型?

    c++ - 更改派生类的模板参数

    javascript - 将 Json 对象数组值转换为单个数组

    javascript - 如何使用 JavaScript 动态构建路点数组?

    java - 进程 fork 、子进程等[Java]