您的位置:首页 > 编程语言

Racket编程指南——3 内置的数据类型

2018-02-17 10:43 501 查看


3 内置的数据类型

上一章介绍了Racket的内置数据类型:数字、布尔值、字符串、列表、和程序。本节提供了简单的数据表的内置数据表更完整的覆盖。
3.1 布尔值(Boolean)
3.2 数值(Number)
3.3 字符(Character)
3.4 字符串(Unicode
Strings)
3.5 字节(Byte)和字节字符串(Byte
String)
3.6 符号(Symbol)
3.7 关键字(Keyword)
3.8 配对(Pair)和列表(List)
3.9 向量(Vector)
3.10 哈希表(Hash
Table)
3.11 格子(Box)
3.12 空值(Void)和未定义值(Undefined)


3.1 布尔值(Boolean)

Racket有表示布尔值的两个常数:#t表示真,#f表示假。大写的#T和#F在语法上描述为同样的值,但小写形式是首选。

boolean?过程识别两个布尔常量。然而,在对if、cond、 and、or等等的测试表达式的结果里,除了#f外,任何值都是记为真。

Examples:

>
(= 2 (+ 1 1))
#t
>
(boolean? #t)
#t
>
(boolean? #f)
#t
>
(boolean? "no")
#f
>
(if "no" 1 0)
1


3.2 数值(Number)

Racket的数值(number)可以是精确的也可以是不精确的:

一个精确的数字是:

一个任意大的或小的整数,比如:5,99999999999999999或-17;

一个有理数,即精确的两个任意小的或大的整数比,比如:1/2,99999999999999999/2或-3/4;

一个复数,带有精确的实部和虚部(即虚部不为零),比如:1+2i或1/2+3/4i。

一个 不精确的数字是:

一个数的一个IEEE浮点表示,比如:2.0或3.14e+87,其中IEEE无穷大和非数书写为:+inf.0,-inf.0和+nan.0(或-nan.0);

一个带有实部和虚部配对的复数的IEEE浮点表示,比如:2.0+3.0i或-inf.0+nan.0i;一种特殊情况是,一个不精确的复数可以有一个精确的零实部和一个不精确的虚部。

对一个小数点或指数的说明符进行不精确数字打印,对整数和分数进行精确数字打印。用同样的约定申请读入数值常数,但#e或#i可以前缀数值以解析一个精确的或不精确的数值。前缀#b、#o和#x指定二进制、八进制和十六进制数值的解释。

Examples:

>
0.5
0.5
>
#e0.5
1/2
>
#x03BB
955
计算涉及到精确的数值产生不精确的结果,这样的情况对数据造成一种污染。注意,然而,Racket没有提供"不精确的布尔值",所以对不精确的数字的比较分支计算却能产生精确的结果。exact->inexact和inexact->exact程序在两种类型的数值之间转换。

Examples:

>
(/ 1 2)
1/2
>
(/ 1 2.0)
0.5
>
(if (= 3.0 2.999) 1 2)
2
>
(inexact->exact 0.1)
3602879701896397/36028797018963968
当精确的结果需要表达实际的非有理数数值,不精确的结果也由像sqrt、log和sin这样的程序产生。Racket只能代表有理数和有理数配对的复数。

Examples:

>
(sin 0)
; 有理数...
0
>
(sin 1/2)
; 非有理数...
0.479425538604203
在性能方面,小整数的计算通常是最快的,其中“小”意味着这个数字比有符号数值的机器字长要小一点。具有非常大的精确整数或非整精确数的计算要比不精确数的计算代价要高昂得多。

(define (sigma f a b)
(if (= a b)
0
(+ (f a) (sigma f (+ a 1) b))))
>
(time (round (sigma (lambda (x) (/ 1 x)) 1 2000)))
cpu time: 88 real time: 89 gc time: 0
8
>
(time (round (sigma (lambda (x) (/ 1.0 x)) 1 2000)))
cpu time: 0 real time: 0 gc time: 0
8.0
在针对通常的number?的加法中,整数类(integer)、有理数类(rational)、实类(real)(总是有理数)和复数都以通常的方式定义,并被程序 integer?、rational?、real?和complex?所识别。一些数学过程只接受实数,但大多数实现了对复数的标准扩展。

Examples:

>
(integer? 5)
#t
>
(complex? 5)
#t
>
(integer? 5.0)
#t
>
(integer? 1+2i)
#f
>
(complex? 1+2i)
#t
>
(complex? 1.0+2.0i)
#t
>
(abs -5)
5
>
(abs -5+2i)
abs: contract violation
expected:
real?
given:
-5+2i
>
(sin -5+2i)
3.6076607742131563+1.0288031496599335i
=过程比较数值相等的数值。如果给定不精确和精确的数字进行比较,它实际上会在比较之前将不精确数字转换为精确数字。eqv?(乃至 equal?)程序,相反,程序比较既是精确数而且数值上相等的数值。

Examples:

>
(= 1 1.0)
#t
>
(eqv? 1 1.0)
#f
当心涉及不精确的数字比较,由于其性质会有令人惊讶的行为。即使是简单的不精确的数字也许并不意味着你能想到他们的意思;例如,当一个二进制IEEE浮点数可以表示为1/2精确数,它只能近似于1/10:

Examples:

>
(= 1/2 0.5)
#t
>
(= 1/10 0.1)
#f
>
(inexact->exact 0.1)
3602879701896397/36028797018963968


3.3 字符(Character)

Racket 字符(character)对应于Unicode标量值(scalar
value)。粗略地说,一个标量值是一个无符号整数,它的表示适合21位,并且映射到某种自然语言字符或字符块的某些概念。从技术上讲,标量值是比Unicode标准中的“字符”概念更简单的概念,但它是一种用于许多目的的近似值。例如,任何重音罗马字母都可以表示为一个标量值,就像任何普通的汉字一样。

虽然每个Racket字符对应一个整数,但字符数据类型和数值是有区别的。char->integer和integer->char程序在标量值和相应字符之间转换。

一个可打印字符通常在以#\作为代表字符后打印。一个不可打印字符通常在以#\u开始十六进制数的标量值打印。几个字符特殊打印;例如,空格和换行字符分别打印为#\space和#\newline。

Examples:

>
(integer->char 65)
#\A
>
(char->integer #\A)
65
>
#\λ
#\λ
>
#\u03BB
#\λ
>
(integer->char 17)
#\u0011
>
(char->integer #\space)
32
display程序直接将字符写入当前输出端口(见《输入和输出》部分)(
Input and Output),与用于打印字符结果的字符常量语法形成对照。

Examples:

>
#\A
#\A
>
(display #\A)
A
Racket提供了几种分类和转换字符的程序。注意,然而,某些Unicode字符要如人类希望的那样转换只有在一个字符串中才行(例如,”ß”的大写转换或者”Σ”的小写转换)。

Examples:

>
(char-alphabetic? #\A)
#t
>
(char-numeric? #\0)
#t
>
(char-whitespace? #\newline)
#t
>
(char-downcase #\A)
#\a
>
(char-upcase #\ß)
#\ß
char=?程序比较两个或多个字符,char-ci=?比较忽略字符。eqv?和equal?程序的行为与 char=?在字符方面的表现一样;当更具体地声明正在比较的值是字符时使用 char=?。

Examples:

>
(char=? #\a #\A)
#f
>
(char-ci=? #\a #\A)
#t
>
(eqv? #\a #\A)
#f


3.4 字符串(Unicode Strings)

字符串(string)是固定长度的字符(characters)数组。它使用双引号打印,双引号和反斜杠字符在字符串中是用反斜杠转义。其他常见的字符串转义是支持的,包括\n换行, \r回车,使用\后边跟随三个八进制数字实现八进制转义,使用\u(达四位数)实现十六进制转义。在打印字符串时通常用\u显示字符串中的不可打印字符。

display过程直接将字符串的字符写入当前输出端口(见《输入和输出》)( 输入和输出),与打印字符串结果的字符串常量语法形成对照。

Examples:

>
"Apple"
"Apple"
>
"\u03BB"
"λ"
>
(display "Apple")
Apple
>
(display "a
\"quoted\" thing")
a "quoted" thing
>
(display "two\nlines")
two
lines
>
(display "\u03BB")
λ
字符串可以是可变的,也可以是不可变的;作为表达式直接写入的字符串是不可变的,但大多数其他字符串是可变的。 make-string过程创建一个给定长度和可选填充字符的可变字符串。string-ref程序从字符串(用0字符串集索引)存取一个字符。string-set!过程更改可变字符串中的一个字符。

Examples:

>
(string-ref "Apple" 0)
#\A
>
(define s (make-string 5 #\.))
>
s
"....."
>
(string-set! s 2 #\λ)
>
s
"..λ.."
字符串排序和状态操作通常是区域无关(locale-independent)的,也就是说,它们对所有用户都是相同的。提供了一些与区域相关(locale-dependent)的操作,允许字符串折叠和排序的方式取决于最终用户的区域设置。如果你在排序字符串,例如,如果排序结果应该在机器和用户之间保持一致,使用string<?或者string-ci<?,但如果排序纯粹是为最终用户订购字符串,使用string-locale<?或者string-locale-ci<?。

Examples:

>
(string<? "apple" "Banana")
#f
>
(string-ci<? "apple" "Banana")
#t
>
(string-upcase "Straße")
"STRASSE"
>
(parameterize ([current-locale "C"])
(string-locale-upcase "Straße"))
"STRAßE"
对于使用纯ASCII、处理原始字节、或将Unicode字符串编码/解码为字节,使用字节字符串(byte strings)。


3.5 字节(Byte)和字节字符串(Byte String)

字节(byte)是包含0到255之间的精确整数。byte?判断表示字节的数字。

Examples:

>
(byte? 0)
#t
>
(byte? 256)
#f
字节字符串(byte
string)类似于字符串——参见《字符串(Unicode)》(字符串(Unicode Strings))部分,但它的内容是字节序列而不是字符。字节字符串可用于处理纯ASCII而不是Unicode文本的应用程序中。一个字节的字符串打印形式特别支持这样的用途,因为一个字节的字符串打印的ASCII的字节字符串解码,但有一个#前缀。在字节字符串不可打印的ASCII字符或非ASCII字节用八进制表示法。

Examples:

>
#"Apple"
#"Apple"
>
(bytes-ref #"Apple" 0)
65
>
(make-bytes 3 65)
#"AAA"
>
(define b (make-bytes 2 0))
>
b
#"\0\0"
>
(bytes-set! b 0 1)
>
(bytes-set! b 1 255)
>
b
#"\1\377"
一个字节字符串的display表写入其原始字节的电流输出端口(看《输入和输出》(输入和输出)部分)。从技术上讲,一个正常的display(即,字符编码的字符串)字符串打印到当前输出端口的UTF-8,因为产出的最终依据字节的定义;然而一个字节字符串的display,没有编码写入原始字节。同样,当这个文件显示输出,技术上显示输出的utf-8编码格式。

Examples:

>
(display #"Apple")
Apple
>
(display "\316\273") ; same
as "λ"
λ
>
(display #"\316\273") ; UTF-8
encoding of λ
λ
字符串和字节字符串之间的显式转换,Racket直接支持三种编码:UTF-8,Latin-1,和当前的本地编码。字节到字节的通用转换器(特别是从UTF-8)弥合了支持任意字符串编码的差异分歧。

Examples:

>
(bytes->string/utf-8 #"\316\273")
"λ"
>
(bytes->string/latin-1 #"\316\273")
"λ"
>
(parameterize ([current-locale "C"]) ; C
locale supports ASCII,
(bytes->string/locale #"\316\273")) ; only,
so...
bytes->string/locale: byte string is not a valid encoding
for the current locale
byte
string: #"\316\273"
>
(let ([cvt (bytes-open-converter "cp1253" ; Greek
code page
"UTF-8")]
[dest (make-bytes 2)])
(bytes-convert cvt #"\353" 0 1 dest)
(bytes-close-converter cvt)
(bytes->string/utf-8 dest))
"λ"


3.6 符号(Symbol)

符号(symbol)是一个原子值,它像前面的标识符那样以'前缀打印。一个表达式以'开始并以标识符继续表达式产生一个符号值。

Examples:

>
'a
'a
>
(symbol? 'a)
#t
对于任何字符序列,一个相应的符号被保留(interned);调用string->symbol程序,或read一个语法标识,产生一个保留符号。由于互联网的符号可以方便地用eq?(或这样:eqv?或equal?)进行比较,所以他们作为一个易于使用的标签和枚举值提供。

符号是区分大小写的。通过使用一个#ci前缀或其它方式,在读者保留默认情况下,读者可以将大小写字符序列生成一个符号。

Examples:

>
(eq? 'a 'a)
#t
>
(eq? 'a (string->symbol "a"))
#t
>
(eq? 'a 'b)
#f
>
(eq? 'a 'A)
#f
>
#ci'A
'a
任何字符串(即,任何字符序列)都可以提供给string->symbol以获得相应的符号。读者输入任何字符都可以直接出现在一个标识符里,除了空白和以下特殊字符:

( ) [ ] { } " , ' ` ; # | \

实际上,#只有在一个符号开始是不允许的,或者仅仅如果随后是%;然而,#也被允许。同样。.它本身不是一个符号。

空格或特殊字符可以通过用|或\引用包含标识符。这些引用机制用于包含特殊字符或可能看起来像数字的标识符的打印形式中。

Examples:

>
(string->symbol "one,
two")
'|one, two|
>
(string->symbol "6")
'|6|
write函数打印一个没有'前缀的符号。一个符号的display表与相应的字符串相同。

Examples:

>
(write 'Apple)
Apple
>
(display 'Apple)
Apple
>
(write '|6|)
|6|
20000
>
(display '|6|)
6
gensym和string->uninterned-symbol过程产生新的非保留(uninterned)符号,那不等同于(比照eq?)任何先前的保留或非保留符号。非保留符号是可用的新标签,不能与任何其它值混淆。

Examples:

>
(define s (gensym))
>
s
'g42
>
(eq? s 'g42)
#f
>
(eq? 'a (string->uninterned-symbol "a"))
#f


3.7 关键字(Keyword)

一个关键字(keyword)值类似于一个符号(见《符号(Symbols)》(符号(Symbol))),但它的打印形式是用前缀#:。

Examples:

>
(string->keyword "apple")
'#:apple
>
'#:apple
'#:apple
>
(eq? '#:apple (string->keyword "apple"))
#t
更确切地说,关键字类似于标识符;以同样的方式,可以引用标识符来生成符号,可以引用关键字来生成值。在这两种情况下都使用同一术语“关键字”,但有时我们使用关键字值(keyword value)更具体地引用引号关键字表达式或使用string->keyword过程的结果。一个不带引号的关键字不是表达式,只是作为一个不带引号的标识符不产生符号:

Examples:

>
not-a-symbol-expression
not-a-symbol-expression: undefined;
cannot
reference undefined identifier
>
#:not-a-keyword-expression
eval:2:0: #%datum: keyword used as an expression
in:
#:not-a-keyword-expression
尽管它们有相似之处,但关键字的使用方式不同于标识符或符号。关键字是为了使用(不带引号)作为参数列表和在特定的句法形式的特殊标记。运行时的标记和枚举,而不是关键字用符号。下面的示例说明了关键字和符号的不同角色。

Examples:

>
(define dir (find-system-path 'temp-dir)) ; not
'#:temp-dir
>
(with-output-to-file (build-path dir "stuff.txt")
(lambda () (printf "example\n"))
; optional
#:mode
argument can be 'text
or 'binary
#:mode 'text
; optional
#:exists
argument can be 'replace,
'truncate,
...
#:exists 'replace)


3.8 配对(Pair)和列表(List)

一个配对(pair)把两个任意值结合。cons过程构建配对,car和cdr过程分别提取配对的第一和第二个成员。pair?判断确认配对。

一些配对通过圆括号包围两个配对元素的打印表来打印,在开始位置放置',在元素之间放置.。

Examples:

>
(cons 1 2)
'(1 . 2)
>
(cons (cons 1 2) 3)
'((1 . 2) . 3)
>
(car (cons 1 2))
1
>
(cdr (cons 1 2))
2
>
(pair? (cons 1 2))
#t
一个列表(list)是创建链表的配对的组合。更确切地说,一个列表要么是空列表null,要么是个配对(其第一个元素是列表元素,第二个元素是一个列表)。list?判断识别列表。null?判断识别空列表。

一个列表通常打印为一个'后跟一对括号括在列表元素周围。

Examples:

>
null
'()
>
(cons 0 (cons 1 (cons 2 null)))
'(0 1 2)
>
(list? null)
#t
>
(list? (cons 1 (cons 2 null)))
#t
>
(list? (cons 1 2))
#f
当一个列表或配对的一个元素不能写成一个quote(引用)值时,使用list或cons打印。例如,一个用srcloc构建的值不能使用quote来写,应该使用srcloc来写:

>
(srcloc "file.rkt" 1 0 1 (+ 4 4))
(srcloc "file.rkt" 1 0 1 8)
>
(list 'here (srcloc "file.rkt" 1 0 1 8) 'there)
(list 'here (srcloc "file.rkt" 1 0 1 8) 'there)
>
(cons 1 (srcloc "file.rkt" 1 0 1 8))
(cons 1 (srcloc "file.rkt" 1 0 1 8))
>
(cons 1 (cons 2 (srcloc "file.rkt" 1 0 1 8)))
(list* 1 2 (srcloc "file.rkt" 1 0 1 8))
如最后一个例子所示,list*是用来缩略一系列的不能使用list缩略的cons。

write和display函数不带前导'、cons、list或list*打印一个配对或一个列表。一个配对或列表的write和display没有区别,除非它们运用于列表元素:

Examples:

>
(write (cons 1 2))
(1 . 2)
>
(display (cons 1 2))
(1 . 2)
>
(write null)
()
>
(display null)
()
>
(write (list 1 2 "3"))
(1 2 "3")
>
(display (list 1 2 "3"))
(1 2 3)
列表中最重要的预定义程序是遍历列表元素的那些程序:

>
(map (lambda (i) (/ 1 i))
'(1 2 3))
'(1 1/2 1/3)
>
(andmap (lambda (i) (i .
<
. 3))
'(1 2 3))
#f
>
(ormap (lambda (i) (i .
<
. 3))
'(1 2 3))
#t
>
(filter (lambda (i) (i .
<
. 3))
'(1 2 3))
'(1 2)
>
(foldl (lambda (v i) (+ v i))
10
'(1 2 3))
16
>
(for-each (lambda (i) (display i))
'(1 2 3))
123
>
(member "Keys"
'("Florida" "Keys" "U.S.A."))
'("Keys" "U.S.A.")
>
(assoc 'where
'((when "3:30") (where "Florida") (who "Mickey")))
'(where "Florida")
配对是不可变的(与Lisp传统相反),pair?、list?仅识别不可变的配对和列表。mcons过程创建一个可变的配对,用set-mcar!和set-mcdr!,及mcar和mcdr进行操作。一个可变的配对用mcons打印,而write和display使用{和}打印可变配对:

Examples:

>
(define p (mcons 1 2))
>
p
(mcons 1 2)
>
(pair? p)
#f
>
(mpair? p)
#t
>
(set-mcar! p 0)
>
p
(mcons 0 2)
>
(write p)
{0 . 2}


3.9 向量(Vector)

一个向量(vector)是任意值的固定长度数组。与列表不同,向量支持常量时间访问和元素更新。

向量打印类似列表——作为其元素的括号序列——但向量要在'之后加前缀#,或如果某个元素不能用引号则使用vector表示。

向量作为表达式,可以提供可选长度。同时,一个向量作为一个隐式quote(引用)的表表达的内容,这意味着在一个矢量常数标识符和括号表表示的符号和列表。

Examples:

>
#("a" "b" "c")
'#("a" "b" "c")
>
#(name (that tune))
'#(name (that tune))
>
#4(baldwin bruce)
'#(baldwin bruce bruce bruce)
>
(vector-ref #("a" "b" "c") 1)
"b"
>
(vector-ref #(name (that tune)) 1)
'(that tune)
像字符串一样,向量要么是可变的,要么是不可变的,直接作为表达式编写的向量是不可变的。

向量可以通过vector->list和list->vector转换成列表,反之亦然。这种转换与列表中预定义的程序相结合特别有用。当分配额外的列表似乎太昂贵时,考虑使用像for/fold的循环形式,它像列表一样识别向量。

Example:

>
(list->vector (map string-titlecase
(vector->list #("three" "blind" "mice"))))
'#("Three" "Blind" "Mice")


3.10 哈希表(Hash Table)

一个哈希表(hash table)实现了从键到值的映射,其中键和值可以是任意的Racket值,而对表的访问和更新通常是常量时间操作。键的比较用equal?、eqv?或eq?,取决于哈希表的键创建方式为make-hash、make-hasheqv或make-hasheq。

Examples:

>
(define ht (make-hash))
>
(hash-set! ht "apple" '(red round))
>
(hash-set! ht "banana" '(yellow long))
>
(hash-ref ht "apple")
'(red round)
>
(hash-ref ht "coconut")
hash-ref: no value found for key
key:
"coconut"
>
(hash-ref ht "coconut" "not
there")
"not there"
hash、hasheqv和hasheq函数创建不可变的哈希表的键和值的初始设置,其中每个值在键后提供一个参数。不可变的哈希表可通过hash-set扩展,在恒定的时间里产生一个新的不可变的哈希表。

Examples:

>
(define ht (hash "apple" 'red "banana" 'yellow))
>
(hash-ref ht "apple")
'red
>
(define ht2 (hash-set ht "coconut" 'brown))
>
(hash-ref ht "coconut")
hash-ref: no value found for key
key:
"coconut"
>
(hash-ref ht2 "coconut")
'brown
一个原意的不可变哈希表可以写为一个表达式,使用#hash(以equal?为基础的表)、#hasheqv(以eqv?为基础的表)或#hasheq(以eq?为基础的表)。一个括号序列必须紧跟#hash、#hasheq或#hasheqv,其中每个元素是一个点的键–值对。这个#hash等其它表都暗含quote它们的键和值的子表。

Examples:

>
(define ht #hash(("apple" .
red)
("banana" .
yellow)))
>
(hash-ref ht "apple")
'red
可变和不可变的哈希表都像不可变的哈希表一样打印,如果所有的键和值可以通过引用或使用别的#hash、#hasheqv或#hasheq,那么使用一个被引用的hash、hasheq或hasheqv表:

Examples:

>
#hash(("apple" .
red)
("banana" .
yellow))
'#hash(("apple" . red) ("banana" . yellow))
>
(hash 1 (srcloc "file.rkt" 1 0 1 (+ 4 4)))
(hash 1 (srcloc "file.rkt" 1 0 1 8))
可变哈希表可以选择性地弱方式(weakly)保留其键,因此只要保留在其它地方的键,每个映射都被保留。

Examples:

>
(define ht (make-weak-hasheq))
>
(hash-set! ht (gensym) "can
you see me?")
>
(collect-garbage)
>
(hash-count ht)
0
请注意,即使是弱哈希表,只要对应的键是可访问的,它的值也很强健。当一个值指回到它的键,就造成了一个两难的依赖,以致这个映射永久保持。要打破这个循环,映射一个键到一个暂存值(ephemeron),配对它的键和值(除这个隐配对的哈希表之外)。

Examples:

>
(define ht (make-weak-hasheq))
>
(let ([g (gensym)])
(hash-set! ht g (list g)))
>
(collect-garbage)
>
(hash-count ht)
1
>
(define ht (make-weak-hasheq))
>
(let ([g (gensym)])
(hash-set! ht g (make-ephemeron g (list g))))
>
(collect-garbage)
>
(hash-count ht)
0


3.11 格子(Box)

一个 格子(box)是一个单元素向量。它可以打印成一个引用#&后边跟随这个格子值的打印表。一个#&表也可以用来作为一种表达,但由于作为结果的格子是常量,它实际上没有使用。

Examples:

>
(define b (box "apple"))
>
b
'#&"apple"
>
(unbox b)
"apple"
>
(set-box! b '(banana boat))
>
b
'#&(banana boat)


3.12 空值(Void)和未定义值(Undefined)

某些过程或表达式形式不需要结果值。例如,display程序仅调用输出的副作用。在这样的情况下,得到的值通常是一个特殊的常量,打印为#<void>。当一个表达式的结果是简单的#<void>,REPL不打印任何东西。

void程序接受任意数量的参数并返回#<void>。(即,void标识符绑定到一个返回#<void>的过程,而不是直接绑定到#<void>。)

Examples:

>
(void)
>
(void 1 2 3)
>
(list (void))
'(#<void>)
undefined常量,它打印为#<undefined>,有时是作为一个参考的结果,其值是不可用的。在Racket以前的版本(6.1以前的版本),过早参照一个局部绑定会产生#<undefined>;而不是像太早的参照现在会引发一个异常。

(define (fails)
(define x x)
x)
>
(fails)
x: undefined;
cannot
use before initialization
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: