【问题标题】:Finding the Index of a String in a Vector在向量中查找字符串的索引
【发布时间】:2020-07-24 19:30:15
【问题描述】:

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

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


通过谷歌搜索,我发现在 C++ 中使用向量会更好。我编写了以下代码来从我的数组中形成一个向量:

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 &gt;&gt; pass 在传递给函数时被切断。这似乎发生在输入中第一次出现空格字符时。这会从上面的索引代码中产生 95。显示的输入是加密后的“Hello”,索引号应该是14、9、22、22,然后是28。最终输出应该还是“Hello”。

此代码将重现错误(但请注意,您需要将这两个 .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);
}

【问题讨论】:

  • 在C++中字符串、数组和向量索引都是从开始的,substr的第二个参数是长度,不是位置,是可选的(默认为字符串的其余部分)。这是正确的arrayAllReplaceEncrypt[i] = content.substr(0, 128); content = content.substr(128);
  • but thankfully, it works! 我不这么认为。
  • 即使在开始时多了一个字符,您仍然会将密钥分解成 129 个字符的块。并且您以后的解密代码不起作用的事实很容易通过您的代码的加密部分不起作用来解释。显然很难判断加密代码是否有效。
  • 声明places each 128 character string into an array 完全不正确。您的字符串长度为 129 个字符,您可以自己轻松测试。
  • @David 我编辑了我的答案并为您创建了一个完整的可行解决方案。请在底部查看 - 请告诉我您对此的想法。

标签: c++ arrays vector indexing


【解决方案1】:

非常有趣。您的程序随着时间的推移而发展,您发现了一些问题并逐步修补它们。有时通过两次犯相同的错误来消除错误。

仍然存在一些必须修复的严重错误。这些大多是超出范围的错误。然后是样式错误,例如全局变量和错误数据类型的使用。 所以,软件设计的一个大问题,必须重构

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

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

您总是在 128 字节的密钥之后添加明文字符。所以你的方法的安全性是 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 在 chars 之后定义,您访问变量 runs 的低字节。你可以测试一下。在第一次创建密钥文件时,您会看到许多 0 字符。在第二次,当运行一次时,您创建 '\001' 字符。

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

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

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

现在回答你的问题。在解密你使用

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

【讨论】:

  • 虽然这段代码做了我希望我的代码做的事情,但这正是我在使用这个解决方案时遇到的问题。我正在尝试边做边学,如果我只是使用别人的代码,我就无法做到这一点。我宁愿花数周时间阅读文档也不愿放弃我的工作方式。
  • 这确实是一个很好的态度。通过复制和粘贴,您将一无所获。如果你真的想学习,那么你必须走更艰难的路,从错误中学习。拿上面的代码给你一些想法或者作为这个和那个的参考。顺便说一句,我也不会采用这种解决方案。我会使用面向对象的方法、更多的 std::algorithms 和更现代的 C++ 语言元素。至少接受一项建议。对代码进行后续和持续的重构。不断审查它,然后改进它。很好! +1
猜你喜欢
  • 2020-10-24
  • 1970-01-01
  • 2015-09-22
  • 1970-01-01
  • 2016-02-15
  • 2020-12-02
  • 2019-11-12
相关资源
最近更新 更多