实用字符串操作指南
字符串解析不难,但容易手忙脚乱,所以这里整理一下实用的字符串操作。
宽字节这里不做讨论。
C 风格
atoi, atol, atoll
函数定义如下:
// 定义于头文件 <cstdlib>
int atoi(const char *str);
long atol(const char *str);
long long atoll(const char *str);
功能是转译 str
所指的字节字符串中的整数值。
舍弃任何空白字符,直至找到首个非空白字符,然后接收尽可能多的字符以组成合法的整数表示,并将其转换为整数值。
可以消除前导零,正负号必须在前导零之前,否则,转译的结果是 0。
若转换值超出范围,则行为未定义;若不能进行转换,则返回 0。
atoi
是 “ASCII to integer” 的缩写。
atof
函数定义如下:
// 定义于头文件 <cstdlib>
double atof(const char* str);
转译 str
所指向的字节字符串中的浮点值。
函数会舍弃所有由 isspace()
确定的空白字符,直至找到首个非空白字符。然后它会尽可能多地取字符,以构成合法的浮点数表示,并将它们转换成浮点值。合法的浮点数如下:
- 十进制浮点数,由下列部分组成:
- (可选)正负号
- 非空的十进制数字序列,可选地包含一个小数点字符(定义有效数字)
- (可选)e 或 E,并跟随可选的正或符号,以及非空十进制数字序列(以 10 为底定义指数)
- 十六进制浮点数,由下列部分组成:
- (可选)正负号
- 0x 或 0X,二进制的标志
- 非空的十六进制数字序列,以及非空十进制数字序列(以 2 为底定义指数)
- 无穷大,它由下列部分组成
- (可选)正负号
- INF 或 INFINITY,忽略大小写
- 非数(NaN)表达式,它由下列部分组成:
- (可选)正负号
- NAN 或 NAN(字符序列),忽略 NAN 大小写
以下遇到的合法的浮点数表示都是这个。
若转换值超出范围,返回值未定义;若不能转换,则返回 0.0
。
strtol, strtoll
函数定义如下:
// 定义于头文件 <cstdlib>
long strtol(const char *str, char **str_end, int base);
long long strtol(const char *str, char **str_end, int base);
转译 str
所指向的字节字符串中的整数值。功能同 atoi
。
不同的是,函数设置的 str_end
在完成转译之后会指向最后被转译的后一个字符。若 str_end
为空,则可以忽略它。此外,还可以通过设置参数 base
(底数)设置数值进制,例如 2、8、10、16等,底数的合法集是 {0, 2, 3, ..., 36}。底数 2 对应的合法数字集是 {0, 1},底数 3 对应的合法数字集是 {0, 1, 2},以此类推。对于大于 10 的底数,合法数字包含字母字符,从对于底数 11 的 Aa 到对于底数 36 的 Zz。
如果底数 base
为 0,那么会自动检测数值进制:若前缀为 0,则为八进制;若前缀为 0x 或 0X,则为十六进制;否则为十进制。
若转换值超出对应类型的范围外,则发生值域错误;若不能进行转换,则返回 0
。
简单应用,利用 str_end
指向最后一个被转译字符的后一个字符的特性截取 IP 地址。
#include <cstdlib>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
const char *const ipAddress = "192.168.0.1";
// 存储 IP 地址的 4 个段.
vector<long> segments(4);
// 先定义一个指针.
char *pEnd;
segments[0] = strtol(ipAddress, &pEnd, 10);
cout << "pEnd " << &pEnd << " point to next element "
<< "\"" << *pEnd << "\"\t"
<< " pEnd stand for " << pEnd << endl;
// 此时 pEnd 指向分隔符 '.', 令其自增 1, 指向下一个数字段的开始.
segments[1] = strtol(++pEnd, &pEnd, 10);
cout << "pEnd " << &pEnd << " point to next element "
<< "\"" << *pEnd << "\"\t"
<< " pEnd stand for " << pEnd << endl;
segments[2] = strtol(++pEnd, &pEnd, 10);
cout << "pEnd " << &pEnd << " point to next element "
<< "\"" << *pEnd << "\"\t"
<< " pEnd stand for " << pEnd << endl;
segments[3] = strtol(++pEnd, &pEnd, 10);
cout << "pEnd " << &pEnd << " point to next element "
<< "\"" << *pEnd << "\"\t"
<< " pEnd stand for " << pEnd << endl;
return 0;
}
上述程序输出为:
pEnd 0x7fffea6f56b0 point to next element "." pEnd stand for .168.0.1
pEnd 0x7fffea6f56b0 point to next element "." pEnd stand for .0.1
pEnd 0x7fffea6f56b0 point to next element "." pEnd stand for .1
pEnd 0x7fffea6f56b0 point to next element "" pEnd stand for
strtoul, strtoull
strtoul
和 strtoull
同 strtol
和 strtoull
,虽然转译的是无符号整数值,但也能转译负数。
strtof, strtod, strtold
函数定义如下:
// 定义于头文件 <cstdlib>
float strtof(const char *str, char **str_end);
double strtod(const char *str, char **str_end);
long double strtold(const char *str, char **str_end);
和 strtol
等一样,但需要注意的是,参数中没有底数 base
,不过规定了合法的浮点数,和 atof
相同。
C++ 风格
stoi, stol, stoll
函数定义如下:
// 定义于头文件 <string>
int stoi(const std::string& str, std::size_t* pos = 0, int base = 10);
long stol(const std::string& str, std::size_t* pos = 0, int base = 10);
long long stoll(const std::string& str, std::size_t* pos = 0, int base = 10);
转译字符串 str
中的有符号整数值。其上三个内部分别调用 strtol
、strtol
和 strtoll
。
同样,还是舍弃空白符,尽可能多地组成底数为 base
的整数表示。合法的整数值由下列部分组成:
- (可选)正负号
- (可选)指示八进制底数的前缀(0)(仅当底数为
8
或0
时使用) - (可选)指示十六进制底数的前缀(0x 或 0X)(仅当底数为
16
或0
时使用) - 一个数字序列
底数的合法集同样是 {0,2,3,...,36},若 base
为 0,则自动检测数值进制。
注意 stoi
中 pos
和 strtol
中的 str_end
不同,它表示的是已处理字符的个数的地址,已处理字符的个数保留在 *pos
中,这点和 strtol
不同。
可以将上述的分割 IP 地址的例子改为由 stoi
实现。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
const string ipAddress = "172.31.255.12";
// 存储 IP 地址的 4 个段.
vector<int> segments(4);
// 先定义一个指针并分配空间. 不同于 strtol, 只需定义一个 char 指针,
// 无需分配空间.
size_t *pos = new size_t();
// 截取子串的开始位置.
int start = 0;
for (int i = 0; i < 4; ++i)
{
segments[i] = stoi(ipAddress.substr(start), pos, 10);
// 使用每次转译处理的字符数更新子串开始的位置, 注意加 1 是为了跳过
// 分隔符 '.'.
start += *pos + 1;
}
return 0;
}
stoul, stoull
基本同 stol
和 stoll
。
stof, stod, stold
函数定义如下:
// 定义于头文件 <string>
float stof(const std::string& str, std::size_t* pos = 0);
double stod(const std::string& str, std::size_t* pos = 0);
long double stold(const std::string& str, std::size_t* pos = 0);
注意合法的浮点值。
to_string
函数定义如下:
// 定义于头文件 <string>
std::string to_string(int value);
std::string to_string(long value);
std::string to_string(long long value);
std::string to_string(unsigned value);
std::string to_string(unsigned long value);
std::string to_string(unsigned long long value);
std::string to_string(float value);
std::string to_string(double value);
std::string to_string(long double value);
把给定的十进制数转换为字符串,返回转换后的字符串。
空白字符检测
除了 atoi
没有明确使用 isspace()
检测空白字符,其余所有数值转换函数都使用 isspace()
检测空白字符。所谓空白字符包括:
- 空格 (
0x20
, ' ') - 换页 (
0x0c
, '\f') - 换行 (
0x0a
, '\n') - 回车 (
0x0d
, '\r') - 水平制表符 (
0x09
, '\t') - 垂直制表符 (
0x0b
, '\v')
总结
C 风格字符串
C 风格字符串 | 符号 | 参数 | 进制 |
---|---|---|---|
atoi,atol,atoll | 有符号 | 1 个,要转换的字节字符 | |
atof | 有符号 | 1 个,要转换的字节字符。 | 进制自动判断,支持 8、10、16 进制 |
strtol,strtoll | 有符号 | 3 个,要转换的字节字符,转换后的下一个位置的指针,底数。 | 支持 0、2、3、……36 进制,8、10、16 进制可自动判断 |
strtoul,strtoull | 无符号 | 3 个,要转换的字节字符,转换后的下一个位置的指针,底数。 | 支持 0、2、3、……36 进制,8、10、16 进制可自动判断 |
strtof,strtod,strtold | 有符号 | 2个,要转换的字节字符,转换后的下一个位置的指针。 | 进制自动判断,支持 8、10、16 进制 |
C++ 风格字符串
C++ 风格字符串 | 符号 | 参数 | 进制 |
---|---|---|---|
stoi, stol, stoll | 有 | 3 个,要转换的字符串,保存已处理字符的数量的地址,底数 | 支持 0、2、3、……、36 进制,底数为 0 时自动判断八进制和十六进制 |
stoul, stoull | 无 | 3 个,要转换的字符串,保存已处理字符的数量的地址,底数 | 同上 |
stof, stod, stold | 有 | 3 个,要转换的字符串,保存已处理字符的数量的地址 | 同 strtof |