Skip to content

std::string的工具函数 #9

@jamesfancy

Description

@jamesfancy

一般来说,在处理字符串的时候通常会用到如下一些函数/方法:length, substring, find, charAt, toLowerCase, toUpperCase, trim, equalsIgnoreCase, startsWith, endsWith, parseInt, toString, split等。

如果使用 STL 中的 std::string,它已经提供了如下一些比较有用的方法:

  • length(),取得字符串的长度。
  • substr(),从字符串中取出一个子串。
  • at() / operator [],取得字符串中指定位置的字符。
  • find / rfind(),从前往后/从后往前在字符串中查找一个子串的位置。
  • find_first_of(),在字符串中找到第一个在指定字符集中的字符位置。
  • find_first_not_of(),在字符串中找到第一次人不在指定字符集中的字符位置。
  • find_last_of(),在字符串中找到最后一个在指定字符集中的字符位置。
  • find_last_not_of(),在字符串中找到最后一个不在字符集中的字符位置。

关于 std::string 的其它方法,请参阅它的文档(在MSDN中可以找到)。

很容易发现,std::string 并没有提供所有需要方法。所以,需要用STL提供了算法库、字符串流以及现存的 std::string 的方法来实现它们。

将字符串转换为大写/小写

std::transform(str.begin(), str.end(), str.begin(), tolower);
std::transform(str.begin(), str.end(), str.begin(), toupper);

去掉字符串两端的空格

去掉左边的空格

str.erase(0, str.find_first_not_of(" /t/n/r"));

去掉右边的空格

str.erase(str.find_last_not_of(" /t/n/r") + 1);

去掉两边的空格

str.erase(0, str.find_first_not_of(" /t/n/r")).erase(str.find_last_not_of(" /t/n/r") + 1);

忽略大小写比较字符串

这一功能的实现比较简单,只需要先将用于比较的两个字符串各自拷贝一个复本,并将这两个复本转换为小写,然后比较转换为小写之后的两个字符串即可。

StartsWith和EndsWith

StartsWith

str.find(substr) == 0;

如果返回值为 true,则 str 是以 substr 开始的

EndsWith

str.rfind(substr) == (str.length() - substr.length());

如果返回值为 true ,则 str 是以 substr 结束的

还有另一个方法可以实现这两个函数。就是将 str 从头/尾截取 substr 长度的子串,再将这个子串也 substr 进行比较。不过这种方法需要判断 str 的长度是否足够,所以建议用 findrfind 来实现。

从字符串解析出 int 和 bool 等类型的值

说到将字符串解析成 int ,首先想到的一定是 atoiatol 等 C 函数。如果用 C++ 来完成这些工具函数,那就要用到 std::istringstream

除了解析 bool 值之外,下面这个函数可以解析大部分的类型的数值:

template<class T> parseString(const std::string& str) {
    T value;
    std::istringstream iss(str);
    iss >> value;
    return value;
}

上面这个模板可以将 0 解析成 boolfalse ,将非 0 解析成 true 。但它不能将字符串 "false" 解析成 false ,将 "true"解析成 true 。因此要用一个特别的函数来解析 bool 型的值:

template<bool>
bool parseString(const std::string& str) {
    bool value;
    std::istringstream iss(str);
    iss >> boolalpha >> value;
    return value;
}

上面的函数中,向输入流传入一个 std::boolalpha 标记,输入流就能认识字符形式的 "true""false" 了。

使用与之类似的办法解析十六进制字符串,需要传入的标记是 std::hex

template<class T> parseHexString(const std::string& str) {
    T value;
    std::istringstream iss(str);
    iss >> hex >> value;
    return value;
}

将各种数值类型转换成字符串(toString)

与解析字符串类似,使用 std::ostringstream 来将各种数值类型的数值转换成字符串,与上面对应的3个函数如下:

template<class T> std::string toString(const T& value) {
    std::ostringstream oss;
    oss << value;
    return oss.str();
}
string toString(const bool& value) {
    ostringstream oss;
    oss << boolalpha << value;
    return oss.str();
}
template<class T> std::string toHexString(const T& value, int width) {
    std::ostringstream oss;
    oss << hex;
    if (width > 0) {
        oss << setw(width) << setfill('0');
    }
    oss << value;
    return oss.str();
}

注意到上面函数中用到的 setwsetfill 没有?它们也是一种标记,使用的时候需要一个参数。std::setw 规定了向流输出的内容占用的宽度,如果输出内容的宽度不够,默认就用空格填位。std::setfill 则是用来设置占位符。如果还需要控制输出内容的对齐方式,可以使用 std::leftstd::right 来实现。

拆分字符串和Tokenizer

拆分字符串恐怕得用 Tokenizer 来实现。C 提供了 strtok 来实现 Tokenizer ,在 STL 中,用 std::stringfind_first_offind_first_not_of 来实现。下面就是 Tokenizer 类的 nextToken 方法:

bool Tokenizer::nextToken(const std::string& delimiters) {
    // find the start character of the next token.
    size_t i = m_String.find_first_not_of(delimiters, m_Offset);
    if (i == string::npos) {
        m_Offset = m_String.length();
        return false;
    }

    // find the end of the token.
    size_t j = m_String.find_first_of(delimiters, i);
    if (j == string::npos) {
        m_Token = m_String.substr(i);
        m_Offset = m_String.length();
        return true;
    }

    // to intercept the token and save current position
    m_Token = m_String.substr(i, j - i);
    m_Offset = j;
    return true;
}

源代码

最后,关于上述的一些方法,都已经实现在 strutil.hstrutil.cpp 中,所以现在附上这两个文件的内容:

√ Header file: strutil.h

////////////////////////////////////////////////////////////////////////////////
//
// Utilities for std::string
// defined in namespace strutil
// by James Fancy
//
////////////////////////////////////////////////////////////////////////////////

#pragma once

#include <string>
#include <vector>
#include <sstream>
#include <iomanip>

// declaration
namespace strutil {

    std::string trimLeft(const std::string& str);
    std::string trimRight(const std::string& str);
    std::string trim(const std::string& str);

    std::string toLower(const std::string& str);
    std::string toUpper(const std::string& str);

    bool startsWith(const std::string& str, const std::string& substr);
    bool endsWith(const std::string& str, const std::string& substr);
    bool equalsIgnoreCase(const std::string& str1, const std::string& str2);

    template<class T> T parseString(const std::string& str);
    template<class T> T parseHexString(const std::string& str);
    template<bool> bool parseString(const std::string& str);

    template<class T> std::string toString(const T& value);
    template<class T> std::string toHexString(const T& value, int width = 0);
    std::string toString(const bool& value);

    std::vector<std::string> split(const std::string& str, const std::string& delimiters);
}

// Tokenizer class
namespace strutil {
    class Tokenizer
    {
    public:
        static const std::string DEFAULT_DELIMITERS;
        Tokenizer(const std::string& str);
        Tokenizer(const std::string& str, const std::string& delimiters);

        bool nextToken();
        bool nextToken(const std::string& delimiters);
        const std::string getToken() const;

        /**
        * to reset the tokenizer. After reset it, the tokenizer can get
        * the tokens from the first token.
        */
        void reset();

    protected:
        size_t m_Offset;
        const std::string m_String;
        std::string m_Token;
        std::string m_Delimiters;
    };

}

// implementation of template functions
namespace strutil {

    template<class T> T parseString(const std::string& str) {
        T value;
        std::istringstream iss(str);
        iss >> value;
        return value;
    }

    template<class T> T parseHexString(const std::string& str) {
        T value;
        std::istringstream iss(str);
        iss >> hex >> value;
        return value;
    }

    template<class T> std::string toString(const T& value) {
        std::ostringstream oss;
        oss << value;
        return oss.str();
    }

    template<class T> std::string toHexString(const T& value, int width) {
        std::ostringstream oss;
        oss << hex;
        if (width > 0) {
            oss << setw(width) << setfill('0');
        }
        oss << value;
        return oss.str();
    }

}

√ Source file: strutil.cpp

////////////////////////////////////////////////////////////////////////////////
//
// Utilities for std::string
// defined in namespace strutil
// by James Fancy
//
////////////////////////////////////////////////////////////////////////////////

#include "strutil.h"

#include <algorithm>

namespace strutil {

    using namespace std;

    string trimLeft(const string& str) {
        string t = str;
        t.erase(0, t.find_first_not_of(" /t/n/r"));
        return t;
    }

    string trimRight(const string& str) {
        string t = str;
        t.erase(t.find_last_not_of(" /t/n/r") + 1);
        return t;
    }

    string trim(const string& str) {
        string t = str;
        t.erase(0, t.find_first_not_of(" /t/n/r"));
        t.erase(t.find_last_not_of(" /t/n/r") + 1);
        return t;
    }

    string toLower(const string& str) {
        string t = str;
        transform(t.begin(), t.end(), t.begin(), tolower);
        return t;
    }

    string toUpper(const string& str) {
        string t = str;
        transform(t.begin(), t.end(), t.begin(), toupper);
        return t;
    }

    bool startsWith(const string& str, const string& substr) {
        return str.find(substr) == 0;
    }

    bool endsWith(const string& str, const string& substr) {
        return str.rfind(substr) == (str.length() - substr.length());
    }

    bool equalsIgnoreCase(const string& str1, const string& str2) {
        return toLower(str1) == toLower(str2);
    }

    template<bool>
    bool parseString(const std::string& str) {
        bool value;
        std::istringstream iss(str);
        iss >> boolalpha >> value;
        return value;
    }

    string toString(const bool& value) {
        ostringstream oss;
        oss << boolalpha << value;
        return oss.str();
    }

    vector<string> split(const string& str, const string& delimiters) {
        vector<string> ss;

        Tokenizer tokenizer(str, delimiters);
        while (tokenizer.nextToken()) {
            ss.push_back(tokenizer.getToken());
        }

        return ss;
    }

}

namespace strutil {

    const string Tokenizer::DEFAULT_DELIMITERS("  ");

    Tokenizer::Tokenizer(const std::string& str)
        : m_String(str), m_Offset(0), m_Delimiters(DEFAULT_DELIMITERS) {}

    Tokenizer::Tokenizer(const std::string& str, const std::string& delimiters)
        : m_String(str), m_Offset(0), m_Delimiters(delimiters) {}

    bool Tokenizer::nextToken() {
        return nextToken(m_Delimiters);
    }

    bool Tokenizer::nextToken(const std::string& delimiters) {
        // find the start charater of the next token.
        size_t i = m_String.find_first_not_of(delimiters, m_Offset);
        if (i == string::npos) {
            m_Offset = m_String.length();
            return false;
        }

        // find the end of the token.
        size_t j = m_String.find_first_of(delimiters, i);
        if (j == string::npos) {
            m_Token = m_String.substr(i);
            m_Offset = m_String.length();
            return true;
        }

        // to intercept the token and save current position
        m_Token = m_String.substr(i, j - i);
        m_Offset = j;
        return true;
    }

    const string Tokenizer::getToken() const {
        return m_Token;
    }

    void Tokenizer::reset() {
        m_Offset = 0;
    }

}

此文英文版在CodeProject上发表

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions