在 Rust 中使用字符串

First Post:

Last Update:

机翻摘抄自 Working with strings in Rust

从C开始


当人们学习Rust编程语言时,总会出现一个问题:为什么有两种字符串类型?为什么有String&str ?

作者先从一个C语言程序入手:

1
2
3
4
5
6
7
8
9
10
11
12
_// in `print.c`_

_int_ _main_(_int_ _argc_, _char_ _*__*__argv_) {
_printf_(_"argv = %p\n"_, _argv_); _// new!_
_for_ (_int_ _i_ _=_ _0_; _i_ _<_ _argc_; _i__++_) {
_char_ _*__arg_ _=_ _argv_[_i_];
_printf_(_"argv[%d] = %p\n"_, _i_, _argv_[_i_]); _// new!_
_printf_(_"%s\n"_, _arg_);
}

_return_ _0_;
}

看看结果,看起来不错!

1
2
3
4
5
6
$ gcc print.c -o print
$ ./print "ready" "set" "go"
./print
ready
set
go

现在看看我们到底在做什么

1
2
3
4
5
6
7
8
9
10
11
12
_// in `print.c`_

_int_ _main_(_int_ _argc_, _char_ _*__*__argv_) {
_printf_(_"argv = %p\n"_, _argv_); _// new!_
_for_ (_int_ _i_ _=_ _0_; _i_ _<_ _argc_; _i__++_) {
_char_ _*__arg_ _=_ _argv_[_i_];
_printf_(_"argv[%d] = %p\n"_, _i_, _argv_[_i_]); _// new!_
_printf_(_"%s\n"_, _arg_);
}

_return_ _0_;
}

Now we’re using the %p format specifier, which prints.. pointers!
现在我们使用%p格式说明符,它打印..指针!

1
2
3
4
5
6
7
8
9
10
11
$ 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是一个地址数组
image.png
那么%s如何知道自己何时停止打印?
作者的代码copy过来不太方便,于是直接上图

image.png

让我们尝试将我们的程序通过管道传输到像xxd这样的十六进制转储器中,以准确查看发生了什么:

1
2
3
4
5
6
7
$ # 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.
1
2
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 ^^

我们发现他用00分割了每个参数,事实上,C具有以null(0)结尾的字符串。
所以%s等价于一个在0上break的%c打印程序。

==You may have noticed that when our print program went beyond the end of our arguments, it showed CDPATH=.:/ho too.
That was (part of) an environment variable! Those are stored right next to the program’s arguments in glibc, the GNU C library.
But the specifics are out of scope for this article, you may want to check out the Making our own executable packer series instead.
您可能已经注意到,当我们的打印程序_超出_参数末尾时,它也显示CDPATH=.:/ho 。
那是环境变量(的一部分)!它们存储在 GNU C 库 glibc 中程序参数的旁边。
但具体细节超出了本文的范围,您可能想查看“制作我们自己的可执行加壳器”系列 ==

现在进行最后一个测试

1
2
3
$ gcc print.c -o print
$ ./print "élément"
éLéMENT

好像出现了一点问题,实际我们想要的是“ÉLÉMENT”,好吧,让我们试试更简单的事情——打印每个字符%c并用空格隔开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
_// in `print.c`_

_#include_ _<stdio.h>_ _// printf_

_int_ _main_(_int_ _argc_, _char_ _*__*__argv_) {
_for_ (_int_ _i_ _=_ _1_; _i_ _<_ _argc_; _i__++_) {
_char_ _*__arg_ _=_ _argv_[_i_];
_for_ (_int_ _j_ _=_ _0_;; _j__++_) {
_char_ _character_ _=_ _arg_[_j_];
_if_ (_character_ _==_ _0_) {
_break_;
}
_// notice the space following `%c`_
_printf_(_"%c "_, _character_);
}
_printf_(_"\n"_);
}

_return_ _0_;
}
1
2
3
$ gcc print.c -o print
$ ./print "élément"
l m e n t

不太行……还是再看看xxd好吗?

gcc print.c -o print
1
2
3
$ ./print "élément" | xxd -g 1
00000000: c3 a9 6c c3 a9 6d 65 6e 74 0a ..l..ment.
^^^^^ ^^^^^

问题就在于,É实际上是两个bytec3 a9,即使我们使用nodejs书写一样如此,为什么他的编码是c3 a9?是时候学习一下utf-8了。

快速UTF-8入门