【Rust每周一知】Rust为什么会有String和&str?!长文预警!
- 2020 年 3 月 6 日
- 筆記
本文是Amos博客文章“Working with strings in Rust”的翻译。
原文地址:https://fasterthanli.me/blog/2020/working-with-strings-in-rust/
人们选择Rust编程语言时总会遇到一个问题:为什么会有两种字符串类型?为什么会出现String和&str?
Amos在其另一篇文章"declarative-memory-management"中部分回答了这个问题。但是在本文中又进行了一些实验,看看是否可以为Rust的做法“辩护”。文章主要分为C和Rust两大部分。
C语言部分:
- print程序示例
- UTF-8编码
- print程序处理UTF-8编码
- 传递字符串
C语言的print程序示例
让我们从简单C程序开始,打印参数。
// in `print.c` #include <stdio.h> // for printf int main(int argc, char **argv) { for (int i = 0; i < argc; i++) { char *arg = argv[i]; printf("%sn", arg); } return 0; }
$ gcc print.c -o print $ ./print "ready" "set" "go" ./print ready set go
好的!很简单。程序使用的是标准的C11
主函数签名,该签名用int
定义参数个数(argc
,参数计数),和用char**
或char *[]
“字符串数组”定义参数(argv
,参数向量)。然后,使用printf
格式说明符%s
将每个参数打印为字符串,其后跟n
换行符。确实,它将每个参数打印在自己的行上。
在继续之前,请确保我们对正在发生的事情有正确的了解。修改以上的程序,使用%p
格式说明符打印指针!
// in `print.c` int main(int argc, char **argv) { printf("argv = %pn", argv); // new! for (int i = 0; i < argc; i++) { char *arg = argv[i]; printf("argv[%d] = %pn", i, argv[i]); // new! printf("%sn", arg); } return 0; }
$ gcc print.c -o print $ ./print "ready" "set" "go" argv = 0x7ffcc35d84a8 argv[0] = 0x7ffcc35d9039 ./print argv[1] = 0x7ffcc35d9041 ready argv[2] = 0x7ffcc35d9047 set argv[3] = 0x7ffcc35d904b go
好的,argv
是一个地址数组,在这些地址上有字符串数据。像这样:

printf
的%s
格式符怎么知道什么时候停止打印?因为它只获得一个地址,而不是起始地址和结束地址,或者起始地址和长度。让我们尝试自己打印每个参数:
// in `print.c` #include <stdio.h> // printf int main(int argc, char **argv) { for (int i = 0; i < argc; i++) { char *arg = argv[i]; // we don't know where to stop, so let's just print 15 characters. for (int j = 0; j < 15; j++) { char character = arg[j]; // the %c specifier is for characters printf("%c", character); } printf("n"); } return 0; }
$ gcc print.c -o print $ ./print "ready" "set" "go" ./printreadys readysetgoCD setgoCDPATH=. goCDPATH=.:/ho
哦哦~我们的命令行参数相互“渗入”。让我们尝试将我们的程序通过管道xxd
传输到一个十六进制的转储程序中,以查看发生了什么事:
$ # note: "-g 1" means "show groups of one byte", $ # xxd defaults to "-g 2". $ ./print "ready" "set" "go" | xxd -g 1 00000000: 2e 2f 70 72 69 6e 74 00 72 65 61 64 79 00 73 0a ./print.ready.s. 00000010: 72 65 61 64 79 00 73 65 74 00 67 6f 00 43 44 0a ready.set.go.CD. 00000020: 73 65 74 00 67 6f 00 43 44 50 41 54 48 3d 2e 0a set.go.CDPATH=.. 00000030: 67 6f 00 43 44 50 41 54 48 3d 2e 3a 2f 68 6f 0a go.CDPATH=.:/ho.
啊啊!它们确实彼此跟随,但是两者之间有一些区别:这是相同的输出,用^^
进行注释的位置是分隔符:
00000000: 2e 2f 70 72 69 6e 74 00 72 65 61 64 79 00 73 0a ./print.ready.s. . / p r i n t ^^ r e a d y ^^
似乎每个参数都由值0来终止。确实,C具有以null终止的字符串。因此,我们可以“修复”我们的打印程序:
#include <stdio.h> // printf int main(int argc, char **argv) { for (int i = 0; i < argc; i++) { char *arg = argv[i]; // note: the loop condition is gone, we just loop forever. // well, until a 'break' at least. for (int j = 0;; j++) { char character = arg[j]; // technically, we ought to use '