【问题标题】:How do I read a string char by char in C++?如何在 C++ 中逐字符读取字符串?
【发布时间】:2020-01-18 08:49:04
【问题描述】:

我需要逐个字符地读取字符串,以便对其执行一些控制。有可能这样做吗?我是否必须将其转换为 char 数组? 我试图用string_to_control[i] 指向单个字符,然后增加 i 来移动,但这似乎不起作用。 举个例子,我贴一段括号控制的代码。

bool Class::func(const string& cont){
    const string *p = &cont;
    int k = 0;
    //control for parenthesis
    while (p[k].compare('\0') != 0) {
        if (p[k].compare("(") == 0) { ap++; };
        if (p[k].compare(")") == 0) { ch++; };
        k++;
    };
    //...
};

字符串被复制好了,但是当我尝试第一次比较时,就会抛出异常。

编辑:我补充说,我想拥有初始字符串 cont 的不同副本(并继续它们,而不是直接在 cont 上)以便操纵它们(稍后在代码中,我需要验证某些单词在正确的位置)。

【问题讨论】:

  • 将您的代码添加到问题中。
  • 小改进建议:1:如果你坚持做ifs:在第二个if之前添加else2:没有理由让它成为类成员函数。改为免费功能。
  • 你能用c_str()std::string转换成C字符串吗?阅读:stackoverflow.com/questions/7416445/…

标签: c++ string char


【解决方案1】:

逐个字符遍历字符串的最简单方法是 range-for:

bool Class::func(const string& cont){
    for (char c : cont) {
        if (c == '(') { ap++; }
        if (c == ')') { ch++; }
    }
    //...
};

在 C++11 中添加了 range-for 语法。如果由于某种原因,您使用的是不支持 C++11 的旧编译器,则可以很好地按索引进行迭代,而无需任何强制转换或复制:

bool Class::func(const string& cont){
    for (size_t i = 0; i < cont.size(); ++i) {
        if (cont[i] == '(') { ap++; }
        if (cont[i] == ')') { ch++; }
    }
    //...
};

【讨论】:

  • 使用std::string::at() 可能比std::string::operator[] 更安全,并且可以说是std::string::begin()/end() 用于C++ 11 之前的版本的迭代器。
  • @Clifford 我看不到使用at() 代替operator[] 的任何安全收益。它只会使它可能变慢。如果size() 可能在for 谓词和if 之间发生变化,则意味着另一个线程进行了更改,使用at() 将无济于事。
【解决方案2】:

如果您只想计算左括号和右括号,请查看以下内容:

bool Class::func(const string& cont) {
    for (const auto c : cont) {
        switch (c) {
            case '(': ++ap; break;
            case ')': ++ch; break;
        }
    }
    // ...
}

【讨论】:

    【解决方案3】:
    const string *p = &cont;
    int k = 0;
    while (p[k].compare('\0') != 0)
    

    p 视为一个数组,因为p 仅指向单个值,当k 为非零时,您的代码具有未定义的行为。我假设你真正想写的是:

    bool Class::func(const string& cont){
        while (cont[k] != '\0') {
            if (cont[k] == '(') { ap++; };
            if (cont[k] == ') { ch++; };
            k++;
        };
    };
    

    更简单的方法是使用 begin()end() 迭代 std::string,或者更简单地使用范围 for 循环:

    bool Class::func(const string& cont){
        for (char ch : cont) {
            if (ch == '(') { ap++; };
            if (ch == ')') { ch++; };
        };
    };
    

    如果你想复制你的字符串,只需声明一个新字符串:

    std::string copy = cont;
    

    【讨论】:

    • 我认为如果operator[] 的参数等于字符串的长度,则返回空字符并且它不被视为越界访问。所以while (cont[k] != '\0') 不应该导致未定义的行为。
    • 你是对的,它在 c++11 中发生了变化,现在保证字符串始终为空终止:en.cppreference.com/w/cpp/string/basic_string/operator_at
    • @churill :假设代码永远不会在较旧的编译器上编译可能是不明智的。我怀疑 C++11 的更改是为了支持已经出现此错误的旧代码,并正式化添加 nul 以避免无缘无故破坏此类代码的实现,并可能使string::c_str() 更易于实现。它仍然是检测std::string 结尾的一种不优雅的方式。
    • @Clifford 实际上对于常量 [] 重载,这始终得到支持
    【解决方案4】:

    std::string::operator[] 重载允许使用 cont[k] 等表达式。您的代码将p 视为std::string 的数组,而不是您想要的字符数组。这可以通过以下方式纠正:

    const string &p = cont;
    

    但没有必要,因为您已经可以直接访问cont

    cont[k] 的类型为 char,因此调用 std::string::compare() 无效。可以正常比较chars:

    cont[k] == '('` 
    

    您还应该注意,在 C++11 之前,std::string 的结尾不像 C 字符串那样由 \0 分隔(可能碰巧有一个 NUL after字符串数据,但这是相信运气)。 C++11 确实保证了这一点,但可能只是为了“修复”假设它是的旧代码。

    如果您使用std::string::at 而不是std::string::operator[],则如果超出范围将引发异常。但是您应该使用基于范围的forstd::string::iteratorstd::string::length() 将字符串迭代到末尾。

    【讨论】:

      【解决方案5】:

      如果您不想使用迭代器 std::string 也会重载 operator[],因此您可以像使用 char[] 一样访问字符。

      cont[i] 将返回例如索引i 处的字符,然后您可以使用== 将其与另一个字符进行比较:

      bool Class::func(const string& cont){
          int k = 0;
      
          while (k < cont.length()) {
              if (cont[k] == '(') { ap++; };
              if (cont[k] == ')') { ch++; };
              k++;
          };
      };
      

      【讨论】:

        【解决方案6】:

        要计算括号,可以使用标准库中的std::count 算法:

        /* const */ auto ap = std::count(cont.begin(), cont.end(), '(');
        /* const */ auto ch = std::count(cont.begin(), cont.end(), ')');
        

        字符串将被遍历两次。

        对于单次遍历,您可以实现一个通用函数(需要 C++17):

        template<class C, typename... Ts>
        auto count(const C& c, const Ts&... values) {
            std::array<typename C::difference_type, sizeof...(Ts)> counts{};
            for (auto& value : c) {
                auto it = counts.begin();
                ((*it++ += (value == values)), ...);
            }
            return counts;
        }
        

        然后写

        /* const */ auto [ap, ch] = count(cont, '(', ')');
        

        【讨论】:

        • 我根据你的使用迭代器 (godbolt.org/z/dpb55x) 制作了一个单一的遍历版本,它也可能派上用场。
        【解决方案7】:

        首先将字符串转换为 char 数组,如下所示:

        bool Class::func(const string& cont){
        
            char p[cont.size() + 1];
            strcpy(p, cont.c_str());
        
            int k = 0;
            //control for parenthesis
            while (p[k].compare('\0') != 0) {
                if (p[k].compare("(") == 0) { ap++; };
                if (p[k].compare(")") == 0) { ch++; };
                k++;
            };
            //...
        };
        

        你可以用算法做你想做的事,这意味着你可以避免数组转换:

        #include <iostream>
        #include <string>
        #include <cstring>
        #include <algorithm>    // std::count
        
        int main()
        {
            std::string s = "hi(there),(now))";
        
            int ap = std::count (s.c_str(), s.c_str()+s.size(), '(');
            int ch = std::count (s.c_str(), s.c_str()+s.size(), ')');
        
            std::cout << ap <<  "," <<  ch << '\n'; // prints 2,3
        
            return 0;
        }
        

        【讨论】:

        • 您是否尝试编译您的代码?为什么将一个非常好的字符串转换为 char 数组只是为了访问字符?
        • 另外char p[...] 是一个VLA,它不是标准的C++。
        • char p[cont.size() + 1]; 行不正确:编译器说“函数调用必须在常量表达式中具有常量值”。
        • @AnnaOriglia 因为它不是标准的一部分,所以一些编译器接受可变长度数组作为非标准扩展。
        • [] 已经为std::string 重载,将字符串复制到char[] 是没有意义的。
        猜你喜欢
        • 2014-11-22
        • 1970-01-01
        • 1970-01-01
        • 2010-11-08
        • 1970-01-01
        • 2011-07-19
        • 1970-01-01
        • 2011-06-16
        相关资源
        最近更新 更多