在C++中如何逐个字符读取字符串?

6

我需要逐个字符地读取一个字符串,以便对其进行一些控制。这种操作是否可行?我是否必须将其转换为一个字符数组? 我尝试使用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
请将您的代码添加到问题中。 - Evgeny
小的改进建议:_1_:如果你坚持使用 if,在第二个 if 之前加上 else。_2_:没有理由将其作为类成员函数。改为自由函数即可。 - Ted Lyngmo
你能否使用 c_str()std::string 转换为 C 字符串?请阅读此链接:https://dev59.com/g2s05IYBdhLWcg3wJurp - Soumya Kanti
7个回答

6

遍历字符串中的每个字符最简单的方法是使用“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++; }
    }
    //...
};

1
使用std::string::at()可能更安全,而不是使用std::string::operator[],对于C++11之前的版本,可以考虑使用std::string::begin()/end()迭代器。 - Clifford
@Clifford,我不认为在这里使用at()而不是operator[]会带来任何安全性的提升。这只会使它潜在地变得更慢。如果size()for谓词和if之间可能会发生变化,那么这意味着另一个线程进行了更改,并且使用at()也无济于事。 - Ted Lyngmo

4
如果您只是想计算开放和关闭括号,请看这个例子:

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

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

1
我认为如果operator[]的参数等于字符串的长度,则返回空字符并且不被视为越界访问。因此,while (cont[k] != '\0')不应该导致未定义的行为。 - Lukas-T
你说得对,自从c++11后,字符串保证始终以空字符结尾: https://en.cppreference.com/w/cpp/string/basic_string/operator_at - Alan Birtles
@churill:假设代码永远不会在旧编译器上编译可能不是明智的选择。我猜测C++11的更改是为了支持已经出现此错误的旧代码,并规范添加空字符以避免毫无必要地破坏这样的代码实现,也许是为了使string::c_str()更容易实现。这仍然是一种不优雅的检测std::string结尾的方式。 - Clifford
1
@Clifford 实际上,对于常量 [] 重载,这一直被支持。 - Alan Birtles

2
如果您不想使用迭代器,std::string也重载了operator[],因此您可以像使用char[]一样访问字符。例如,cont[i]将返回索引为i的字符,然后您可以使用==将其与另一个字符进行比较。请注意保留HTML标签。
bool Class::func(const string& cont){
    int k = 0;

    while (k < cont.length()) {
        if (cont[k] == '(') { ap++; };
        if (cont[k] == ')') { ch++; };
        k++;
    };
};

2

std::string::operator[] 重载允许表达式如 cont[k]。你的代码把 p 当作一个 std::string 数组而不是你想要的字符数组。可以通过以下方式进行更正:

const string &p = cont;

但是这是不必要的,因为您已经可以直接访问cont

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

cont[k] == '('` 

你还需要知道,在C++11之前,std::string的结尾不像C字符串那样以\0为分隔符(字符串数据后可能会有一个NUL,但这只是凭运气)。C++11确保了这一点,但可能只是为了“修复”做出这种假设的旧代码。
如果你使用std::string::at而不是std::string::operator[],则在超出范围时会抛出异常。但你应该使用基于范围的forstd::string::iteratorstd::string::length()来迭代字符串到末尾。

2

要计算括号数量,您可以使用标准库中的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, '(', ')');

1
我基于你的版本,使用迭代器(https://godbolt.org/z/dpb55x)制作了一个单遍历版本,这也可能会很有用。 - Ted Lyngmo

-5

首先将字符串转换为字符数组,方法如下:

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

3
你尝试编译你的代码了吗?为什么要把一个完好的字符串转换成字符数组才能访问其中的字符? - Alan Birtles
1
另外,char p[...] 是一个VLA,这不是标准的C++。 - Lukas-T
1
代码行 char p[cont.size() + 1]; 是不正确的:编译器提示 '函数调用在常量表达式中必须具有常量值'。 - A O
1
@AnnaOriglia 因为它不是标准的一部分,一些编译器将可变长度数组作为非标准扩展接受。 - Lukas-T
2
[]已经为std::string重载了,将字符串复制到char[]是毫无意义的。 - Clifford

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接