写在前面
刷lc从Python转向C++, 不只是语法层面, 还要改变很多的API, 这次记录一下C++和Python在字符串方面的一些区别, 供参考.
基本区别
Python字符串是不可变类型, 而C++的String为容器(本质上是一个类的别名, 说容器有点不合适, 因为容器内的元素类型已经被指定为char
), 可变类型.
C++
先说一下字符, 是指char
类型, 其本质上也是int类型(0~127
的ASCII字符, 存在隐式类型转换)
using string = std::basic_string<char>; // string其实是一个类型别名
如果不需要一般的字符串操作, 可以使用vector<char>
, 是一种比较纯粹的字符数组类型, 资源占用较少. 并且C++17新增了一种叫做string_view
的类型, 用起来比string
轻量, 下面是罗剑锋老师给出的描述.
string它确实有点“重”,大字符串的拷贝、修改代价很高,所以我们通常都尽量用 const string&,但有的时候还是无法避免(比如使用 C 字符串、获取子串)。如果你对此很在意,就有必要找一个“轻量级”的替代品。
在 C++17 里,就有这么一个完美满足所有需求的东西,叫 string_view。顾名思义,它是一个字符串的视图,成本很低,内部只保存一个指针和长度,无论是拷贝,还是修改,都非常廉价。
Python
字符串是不可变类型, 并且转换成ASCII码的话需要额外的内置函数:
chr(97) # 'a'
ord('a') # 97
字符串创建
Python
s1='1'
s2='abc'
s3="45"
s4=r"D:\Desktop"
单双引号都可以, 并且支持原生(raw)字符串.
小坑:
原生字符串不支持单字符, 即
s=r'\'
这样的写法是会报错的, 只能老老实实转义:s='\\
.
C++
string s1="123";
// string s2='1';// error
string s3{"abc"}; //initializer_list, C++11
单引号和双引号并不能混用, 需要注意单引号只能表示单一字符, char
类型, 而双引号表示字符串类型(char*
), 除非使用string
以及相关构造函数, 否则默认的类型为const char*
, 例如:
void t1() {
auto s1 = "abc";
cout << typeid(s1).name() << endl; // PKc
auto s2 = "bcd"s; // 字符串字面量C++14
cout << typeid(s2).name() << endl;
string s3 = "bcd";
cout << typeid(s3).name() << endl;
/*NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE
NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE*/
}
并且C风格的字符串中双引号之间可以合并的语法也被C++的string
延续了下来.
void t2() {
string s1 =
"abc"
"ac";
cout << s1 << endl;
/* string s2 = 'a' 'b'; // error */
}
主要操作
操作 | Python方法 | C++方法 |
---|---|---|
s1 中查找s2 |
s1.find(s2) , 找到返回索引, 否则返回-1 |
s1.find(s2) , 找到返回索引,否则返回 string::npos |
取子串[1,5] |
s[1:6] , 左闭右开区间取值 |
s.substr(1, 5) 表示从索引1开始取, 取包括1在内的5个字符 |
连接字符串s1 和s2 |
加号或者+= 连接 |
加号或者+= 连接 |
字符串比较 | 字典序比较, 使用比较运算符 | 字典序比较, 使用比较运算符 |
查找
Python比较简单, 找不到返回-1
s = "abcde"
print(s.find("a")) # 0
print(s.find("z")) # -1
print(s.rfind("a")) # 0
print(s.rfind("z")) # -1
C++就不一样了, 需要一个npos
标志
int main() {
//
string s{"abcde"s};
// substr(i, k)表示从i开始(包括i位置的字符)往后取k个字符
cout << s.find('a') << endl; // 0
cout << s.find('y') << endl; // 18446744073709551615, equal to 2^64 − 1
cout << ULONG_MAX << endl; // 18446744073709551615
cout << string::npos << endl; // 18446744073709551615
}
取子串
Python, 就像数组一样, 比较优雅(左闭右开区间)
s1 = "abcde"
# 取从头开始向后的三个字符
print(s1[0:3]) # abc
print(s1[:3]) # abc
print(s1[:-2]) # abc
# 取第2个到第四个字符
print(s1[1:4]) # bcd
print(s1[-4:-1]) # bcd
# 反转
print(s1[::-1]) # edcba
C++需要substr成员函数, 而且返回的还是字符串的拷贝, 比较耗内存:
int main() {
string s{"abcde"s};
// substr(i, k)表示从i开始(包括i位置的字符)往后取k个字符
cout << s.substr(2, 0) << endl; //
cout << s.substr(2, 1) << endl; // c
cout << s.substr(2, 2) << endl; // cd
cout << s.substr(2) << endl; // cde
}
需要注意substr方法的重载版本, 单参数情况是从参数位置到字符串末尾.
检查字符
条件 |
Python (成员函数,可针对字符串) |
C++ (全局函数,只针对字符) |
---|---|---|
是否为数字 | s.isdigit() |
isdigit(c) |
是否为字母 | s.isalpha() |
isalpha(c) |
是否为小写字母 | s.islower() |
islower(c) |
是否为大写字母 | s.isupper() |
isupper(c) |
是否为字母或数字 | s.isalnum() |
isalnum(c) |
是否为十六进制字符(0~F ) |
- | isxdigit(c) |
转换字符大小写:
转换方向 | Python(成员函数) | C++(全局函数) |
---|---|---|
大写->小写 | s.lower() |
static_cast<char>(tolower(c)) |
小写->大写 | s.upper() |
static_cast<char>(toupper(c)) |
C++
#include <cctype>
void t1() {
char c1 = 'a';
cout << islower(c1) << endl; // 1
char c2 = 'N';
cout << isupper(c2) << endl; // 1
char c3 = '0';
cout << isnumber(c3) << endl; // 1
char c4 = '0';
cout << isalnum(c4) << endl; // 1
char c5 = 'g';
cout << isxdigit(c5) << endl; // 0
}
void t2() {
char c1 = 'a';
/* cout << (char)toupper(c1) << endl; // A */
cout << static_cast<char>(toupper(c1)) << endl; // A
char c2 = 'A';
cout << (char)tolower(c2) << endl; // a
}
格式化输出
这部分不是严格的字符串方面, 但是也有值得对比的点.
C++当然要用最具C++味道的cout
了(虽然名声不好, 但是在泛型方面的支持还是比printf()
方便不少, 注意一下别和左移运算符用混了就行), 而Python就是一个printf()
吃遍天.
输出的内容 | Python | C++ |
---|---|---|
字符串 | print(s) |
cout<<s<<endl; |
二进制整数(字符串形式输出) | print(bin(x)) |
cout<<bitset<32>(x); |
八进制整数 | print(oct(x)) |
cout<<oct<<x<<endl; |
十六进制整数(地址值) | print(hex(x)) |
cout<<hex<<x<<endl; |
整数格式化(指定对齐方式和位数) | Python3.6支持的f-string print(f"{123:0<6}") |
cout << setfill('0') << setw(6) << left << 123 << endl; |
浮点数格式化输出(指定小数点后4位) | print(f"{1.2:.4f}") |
cout << fixed << setprecision(4) << 3.14159 << endl; |
C++
两种方法.
#include<sstream>
// 使用字符流. (可以定制)
void t3() {
/* basic_ostringstream<char> oss; */
ostringstream oss;
// 默认是right
oss << setfill('0') << setw(5) << left << 123;
cout << oss.str() << endl;
// 可定制
cout << oss.str().insert(3, "-").insert(5, "-") << endl;
/* 12300 */
/* 123-0-0 */
}
// 直接输出
void t4() {
// 默认是right
cout << setfill('0') << setw(5) << left << 123 << endl;
/* 12300 */
}
// 浮点数
void t5() {
cout << setprecision(3) << 3.14159 << endl; // 3.14,总位数
cout << fixed << setprecision(3) << 3.14159 << endl; // 3.142,小数点后位数,支持四舍五入
}
Python
print(f"{123:0<6}") # 123000
print(f"{1.23456:.4f}") # 支持四舍五入, 1.2346
字符流刷新
这算是一个比较小的主题, 但是也很常用. 一个比较常见的例子就是终端命令行程序中的进度条旋转, 使用字符流的输出刷新flush
就可以完成.
下面是Python和C++的实现方式, 需要知道的另外一个点就是\r
表示光标移动到开头, 这同时也是进度条旋转的关键.
from time import sleep
str_lst = ["-", "\\", "|", "/", "-"]
for i in range(100):
sleep(0.5)
print(f"{str_lst[i%4]} process: {i}%\r", flush=True, end="")
下面是C++版本的:
#include <unistd.h>
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
string str{R"(-\|/-)"s};
int n{100};
while (n--) {
usleep(200 * 1000); // 微秒
cout << str[n % 4] << " process:" << 100 - n << "%\r" << flush;
}
return 0;
}
纯 C 实现也可以:
#include <stdio.h>
#include <unistd.h> // for usleep()
int main(int argc, char *argv[]) {
char str[] = "-\\|/-";
int n = 100;
while (n--) {
usleep(200000); // in microseconds
printf("%c process:%d%%\r", str[n % 4], 100 - n);
fflush(stdout);
}
return 0;
}
类型转换
转换方向 | Python函数 | C++函数 |
---|---|---|
字符串->整数 | int() , 支持不同数制( base 参数) |
stoi() , stol() , stoll() |
字符串->浮点数 | eval() |
stof() , stod() |
浮点数/整数->字符串 | str() |
to_string() |
Python的字符串转换还是相当方便的, 并且支持浮点数
Python
int('1011', base=2) # 11
C++
void t1() {
auto a = "123"s;
auto b = "12.3"s;
cout << stoi(a) << endl;
cout << stod(b) << endl;
/* 123 */
/* 12.3 */
}
小结
暂时先总结这么多, 之后刷题遇到再说.