C语言中的匕首-C风格字符串(续)
https://m.toutiao.com/is/JwmYg8w/
本文接续《C语言中的匕首 - C风格字符串》。
让我们展开聊聊C风格字符串的常见操作。
字符串比较
比较两个字符串,凭借C标准库里的strcmp函数,这项任务易如反掌。strcmp的函数原型如下。
int strcmp(const char * s1, const char * s2)
从首字符开始,依次比较。
返回值等于0,则两字符串相等。
返回值大于0, 则字符串s1大于s2。
返回值小于0, 则字符串s1小于s2。
例如,
assert(strcmp('hello', 'Hello') > 0); // 因为字符h大于字符Hassert(strcmp('hello', 'hello ') < 0); assert(strcmp('hello', 'hello') == 0);
取子字符串
最常见的字符串操作之一。例如,从一串字符串中取出前五个字符。
熟悉C++的朋友可能知道C++的std::string类有一个取子字符串的方法: std::string substr(size_t pos = 0, size_t len = std::string::npos)。没必要羡慕,我们自造一个类似的C函数。请看示例代码。
#include <string.h>#include <stdio.h>#include <assert.h>// 目的:取指定的子字符串存入目标字符// 返回:指向目标字符串的指针// 目标字符串的分配内存空间必须大于或者等于源头字符串的分配内存空间// 否则,后果不可预测char* substr(char* dest, // 目标字符串 const char source[], // 源头字符串 size_t start_index, size_t end_index){ assert(dest != NULL); assert(start_index < end_index); assert(end_index <= strlen(source)); size_t len = end_index - start_index; strncpy(dest, source + start_index, len); dest[len] = '\0'; return dest;}int main(){ char s[] = 'hello, world!'; char dest[strlen(s) + 1]; substr(dest, s, 0, 5); assert(strcmp('hello', dest) == 0); assert(strcmp('ello,', substr(dest, s, 1, 6)) == 0); assert(strcmp('h', substr(dest, s, 0, 1)) == 0); return 0;}
拷贝
C语言标准库提供与字符串拷贝相关的库函数。凭借这些函数,拷贝字符串相当简单。
char* strcpy(char* source, const char* dest);char* strncopy(char source, const char* dest, size_t len);
#include <assert.h>#inlcude <string.h> int main(){ char s[] = 'hello, world!'; // 使用strcpy函数拷贝字符串 char cp[strlen(s)+1]; strcpy(cp, s); if (cp) assert(strcmp(cp, s) == 0); // 使用strncpy函数拷贝字符串 char cp2[strlen(s)+1]; strncpy(cp2, s, strlen(s)); cp2[strlen(s)] = '\0'; // 这一步不可省略。否则后果严重! if (cp2) assert(strcmp(cp2, s) == 0); return 0;}
熟悉C++的朋友或许在想,拷贝一个字符串,用C++多简单啊,C语言太费事了。而且,你看,C++有很多种办法拷贝一个字符串。
#include <string>#include <cassert>int main(){ char c_str[] = 'hello'; std::string str {'hello'}; std::string str1 {str}; // 方法1 std::string str2 = str; // 方法2 std::string str3 {str.cbegin(), str.cend()}; // 方法3 std::string str4 {c_str, c_str + strlen(c_str)}; // 方法4 assert(str == str1); assert(str == str2); assert(str == str3); assert(str == str4); return 0;}
这彰显了C和C++,Java等语言的不同的设计理念和价值观。
C语言用尽量精简的语言核心,竭尽全力使经常性开支(overhead costs)为零。例如,拷贝字符串,用库函数完成,不是发明一种操作符或者重用某种操作符来完成。这是很多高级C程序员热爱C的源泉,这也是C语言长盛不衰的原因之一。
C++引入各种语法,尽量使经常性开支(overhead costs)降为零。例如,拷贝用重载操作符“=”等实现。C++由此走上不停扩张语言的道路,越来越庞大。C++程序员和C++编译器不断升级学习新内容和避免新旧知识发生冲突。
类似C++, Java引入各种各样的语法和其他语言花式,把设计焦点放在安全性和移植性上,而经常性开支不是重点,只要试图控制在电脑和程序员可以忍受的范围内就好了。
拼接
用C如何拼接字符串?C语言标准库已有了相关库函数。
char* strcat(char* dest, const char* src);char* strncat(char* dest, const char* src, size_t n);
例如,我们拼接两个C风格字符串s1和s2,形成一个新的C风格字符串s3。如何编程?请看。
char s1[] = 'hello,';char s2[] = ' world!';char s3[strlen(s1) + strlen(s2) + 1];s3[0] = '\0'; // 注意!这一步不能省略strcat(s3, s1);strcat(s3, s2);assert(strcmp('hello, world!', s3) == 0);
总结
至此,介绍了C风格字符串的常用操作。从例子代码看得出,其实操作C风格字符串并不难。C语言的标准库string.h是C语言程序员的好朋友,内含很多库函数供调用。用好标准库中的库函数一举两得,一避免重复造轮子,二获得良好的代码可移植性。
然而,有没有更好地使用C风格字符串的策略呢?能不能取C和C++二者最好的部分,用来处理字符串呢?且看下篇文章详解。