字符串
在这里梳理关于字符串的一切。
字符串字面量
所谓字符串字面量,就是我们在代码里面写的 "asdf" 这种东西。在代码中直接可以看见的,直接表达数值的,就是字面量,就像它名字一样,字面上量。
而字符串字面量是字面量里面非常特殊的一类,字符串的字面量,本质是就是一个 char 字符数组,而 "asdf" 就是它的数组名,我们之间使用字符串字面量,就相当于创建一个临时的已经赋好值的字符数组。
因为它本质上是一个数组名,而我们却把它当成了真正的字符串,因此导致了很多 “想当然” 的错误,下面列举几个常见的错误:
- 字符串字面量 + 字符导致无法输出或者输出乱码,即
"asdf" + 'a'。我们可能认为这是对的但是在 C++ 看来,这就相当于"asdf" + 97,从数组名的角度看,这就是给字符串位移了 \(97\) 位,然后输出,这一方面是容易导致数组越界,另一方面导致输出字符串不正确。 - 字符串字面量 + 字符串字面量,无法输出,以前我们会认为,这是 C++ 太懒,没加入这个特性,然而当做数组名看,这是很可笑的数组名 + 数组名,自然不会正常链接。(这里特别说一下,字符串字面量是有一个特性的,即字符串字面量 字符串字面量,即
"asdf" "asdf"这样可以直接拼接成一个字符串)。
这里说一下,像 string 的 substr 函数切割下来的字符串都 string 类,天生带有加号拼接的方法。能看出,string 更符合时代。对比起来,char 数组离散的字符表示字符串的方法,有些落伍了。
这里顺便说一下,除了上述的 char 型字符串字面量以外,还有 string 型字符串字面量,即 "asdf"s,它和 char 型的区别是字符串后面多了个 s, 但拥有 string 一样的性质,从实现原理上看,它是个重载函数即:inline std::string std::literals::string_literals::operator ""s(const char *__str, std::size_t __len) 也就是说,这玩意本质上输出的是一个临时 string 变量,因此,无法读取地址,但是 char 型是真实创造了一个字符串是正常的变量,这算是一些区别。在复杂字符串处理时,可以尝试使用 string 字面量,可能有奇效。
char 字符数组
其实一般处理字符串使用 string 比较多,char 处理字符串有些零散了,无论是删除也好,还是插入也好,都不如 string 来的方便快捷。
string 字符串
拼接字符串
拼接字符串的效率一般都为 \(\operatorname O(n)\),只不过常数较小,在非 for 下,可以少一个 \(10\) 的常数。我们一般是使用 += 其他字符串。(这里的 \(n\) 是指拼接的字符串长度,拼接时间和原字符串长度无关)。
这里只要是字符串即可,无论是字面量,还是字符,还是字符串,只要是字符就可以加入。
提取字符串
一般使用 substr 直接提取部分字符串。如 s.substr(i,len) 就是返回 \(s\) 中从下标 \(i\) 开始 \(len\) 长度的字符串,即返回 中下标 \([i, i + len - 1]\) 的字符串。第一个参数可省且默认为 0,即你可以直接 s.substr(len),提取前面 \(len\) 长的字符串。
插入字符串
即使用 string 的成员函数 \(insert\),即:s1.insert(pos,s2); 在 \(pos\) 的前一个位置,插入字符串 \(s2\)。
如:
string s = "0123456789";
s.insert(2, "asdf");
cout << s << endl;
输出:
012asdf3456789
删除字符串
删除 string 字符串一般就两个方式:erase 和 pop_back,erase 适合任意删除,pop_back 适合删除最后一个字符。因为 pop_back 少用,下面就主要说 erase 的删除方式。
删除特定区段字符
这实际上就是 erase 的正常用法,即 s.earse(i, len),表示从 \(s\) 字符串的 \(i\) 下标开始,删除 \(len\) 个字符,即删除 \([i,i + len - 1]\) 内的字符。如果 \(len\) 超过了剩余长度那就直接删完。特别的,如果不写 \(len\) 这个参数,将会直接删除从 \(i\) 往后的所有字节,包括 \(i\) 本身,即删完。
注意,erase 返回的是删除后的字符串
erase 的时间复杂度就是删多少花多少时间。这点还算良心。
删除特定字符
即同时使用 erase 和 remove,形成删除特定字符的效果。注意,remove 是 \(\operatorname O(n)\) 的。remove 和 unique 在使用和返回上很像。
如删除 \(s\) 里面的 a 字符,我们可以直接使用 s.erase(remove(s.begin(), s.end(), a), s.end()); 删去 \(s\) 里面的 a 字符。
这里需要说明一点,remove 有两个,一个是 STL 的 remove 也就是这里说的这个,还有一个是 cstdio 里面删除文件的 remove。如果直接引入 std 的话,remove 会直接冲突,导致错误。需要用局部命名域。
恶心的字符串
字符和字符串是一个很恶心的东西,恶心的点就在于我们太容易落入想当然。
字面量之争
见最上面 [[字符串#字符串字面量]]

浙公网安备 33010602011771号