R语言编程艺术# 矩阵(matrix)和数组(array)
矩阵(matrix)是一种特殊的向量,包含两个附加的属性:行数和列数。所以矩阵也是和向量一样,有模式(数据类型)的概念。(但反过来,向量却不能看作是只有一列或一行的矩阵。
数组(array)是R里更一般的对象,矩阵是数组的一个特殊情形。数组可以是多维的。例如:一个三维数组可以包含行、列和层(layer),而一个矩阵只有行和列两个维度
1、创建矩阵
矩阵的行和列的下标都是从1开始,如:矩阵a左上角的元素记作a[1,1]。矩阵在R中是按列存储的,也就是说先存储第一列,再存储第二列,以此类推。
> y <- matrix(c(1,2,3,4),nrow=2,ncol=2)
> y
[,1] [,2]
[1,] 1 3
[2,] 2 4
> y <- matrix(c(1,2,3,4),nrow=2)
> y
[,1] [,2]
[1,] 1 3
[2,] 2 4
> #按列输出
> y[,2] #输出第二列
[1] 3 4
>
为矩阵中的元素赋值
> y <- matrix(nrow = 2,ncol = 2)
> y
[,1] [,2]
[1,] NA NA
[2,] NA NA
> y[1,1] <- 1
> y[2,1] <- 2
> y[1,2] <- 3
> y[2,2] <-4
> y
[,1] [,2]
[1,] 1 3
[2,] 2 4
>
>#与上面的代码效果相同
> y <- matrix(c(1,2,3,4),nrow = 2)
> y
[,1] [,2]
[1,] 1 3
[2,] 2 4
>
默认在R中矩阵是以列进行存储的,但通过byrow = T,参数可以将矩阵进行按行存储
> y <- matrix(c(1,2,3,4),nrow = 2, byrow = T)
> y
[,1] [,2]
[1,] 1 2
[2,] 3 4
>
2、一般矩阵运算
常用的矩阵运算:线性代数运算、矩阵索引、矩阵元素筛选
#线性代数运算
线性代数运算包括:矩阵相乘、矩阵数量乘法、矩阵加法等
> y <- matrix(c(1,2,3,4),nrow = 2)
> y
[,1] [,2]
[1,] 1 3
[2,] 2 4
> y %*% y #矩阵相乘
[,1] [,2]
[1,] 7 15
[2,] 10 22
> 3*y #矩阵数量乘法
[,1] [,2]
[1,] 3 9
[2,] 6 12
> y+y #矩阵加法
[,1] [,2]
[1,] 2 6
[2,] 4 8
>
#矩阵索引
> z <- matrix(c(1,2,3,4,1,1,0,0,1,0,1,0),nrow = 4)
> z
[,1] [,2] [,3]
[1,] 1 1 1
[2,] 2 1 0
[3,] 3 0 1
[4,] 4 0 0
> z[,2:3] #提取z中第2、3更
[,1] [,2]
[1,] 1 1
[2,] 1 0
[3,] 0 1
[4,] 0 0
>
给矩阵赋值
> z
[,1] [,2] [,3]
[1,] 1 1 1
[2,] 2 1 0
[3,] 3 0 1
[4,] 4 0 0
> z[c(1,3),] <-matrix(c(1,1,8,12,16,20),nrow = 2) #给z1,3行进行赋新值
> z
[,1] [,2] [,3]
[1,] 1 8 16
[2,] 2 1 0
[3,] 1 12 20
[4,] 4 0 0
>
利用行号负值,移除行或列
> y <- matrix(c(1,2,3,4,5,6),nrow = 3)
> y
[,1] [,2]
[1,] 1 4
[2,] 2 5
[3,] 3 6
> y[-2,] #移除第2行
[,1] [,2]
[1,] 1 4
[2,] 3 6
> y[,-2] #移除第2列
[1] 1 2 3
>
#矩阵元素筛选
矩阵跟向量样也可以进行筛选,只是语法上不同而已
> x <-matrix(c(1,2,3,2,3,4),nrow = 3)
> x
[,1] [,2]
[1,] 1 2
[2,] 2 3
[3,] 3 4
> x[x[,2]>=3,] #x中第2列所有大于等于3的行
[,1] [,2]
[1,] 2 3
[2,] 3 4
>
矩阵筛选规则可以基于除被筛选变量这外的变量
> x
[,1] [,2]
[1,] 1 2
[2,] 2 3
[3,] 3 4
> z <- c(5,12,13)
> x[z %% 2 == 1,]
[,1] [,2]
[1,] 1 2
[2,] 3 4
>
运算符:& and && 前者是向量的逻辑“与”运算,后者是用于if语句的标量逻辑“与”运算
> m <- matrix(c(1,2,3,4,5,6),nrow = 3)
> m
[,1] [,2]
[1,] 1 4
[2,] 2 5
[3,] 3 6
> m[m[,1]>1 & m[,2]>5] #m中第1列中大于1,第2列中大于5的行
[1] 3 6
>
#扩展案例:生成协方差矩阵
n元正态分布,协方差矩阵有n行n列,要求n个随机变量方差都为1,每两个变量间的相关性都是rho,如:当n=3,rho=0.2时,需要的矩阵如下:
> makecov <- function(rho,n){
m<-matrix(nrow = n,ncol = n)
m<-ifelse(row(m)==col(m),1,rho)
return(m)
}
> makecov(0.2,3)
[,1] [,2] [,3]
[1,] 1.0 0.2 0.2
[2,] 0.2 1.0 0.2
[3,] 0.2 0.2 1.0
>
3、对矩阵的行和列调用函数
apply()函数,是R中最常用的函数,其中包括apply()、tapply()、lapply(),apply()函数允许用户在矩阵和各行或各列上调用指的函数。
apply()函数一般形式:apply(m,dimcode,f,fargs)
参数解释:
m:是一个矩阵
dimcode:是维度编号,若取值为1代表对一行应用函数,若取值为2代表对每一列应用函数
f:是应用在行或列上的函数
fargs:是f的可选参数
>######对z变量列进行mean()函数操作,做平均数计算
> z <- matrix(c(1,2,3,4,5,6),ncol = 2)
> z
[,1] [,2]
[1,] 1 4
[2,] 2 5
[3,] 3 6
> apply(z,2,mean)
[1] 2 5
>
>######当然上面的代码也可以有更简便的代码
> colMeans(z)
[1] 2 5
> 其它语法参考如下:
colSums (x, na.rm = FALSE, dims = 1)
rowSums (x, na.rm = FALSE, dims = 1)
colMeans(x, na.rm = FALSE, dims = 1)
rowMeans(x, na.rm = FALSE, dims = 1) .colSums(x, m, n, na.rm = FALSE)
.rowSums(x, m, n, na.rm = FALSE)
.colMeans(x, m, n, na.rm = FALSE)
.rowMeans(x, m, n, na.rm = FALSE)
#当然在R中apply()函数还可以使用自定义函数
> z
[,1] [,2]
[1,] 1 4
[2,] 2 5
[3,] 3 6
> f <- function(x) x/c(2,8)
> y <- apply(z,1,f) #对z变量的行进行f函数操作
> y
[,1] [,2] [,3]
[1,] 0.5 1.000 1.50
[2,] 0.5 0.625 0.75
>
上面的代码输出的结果有两个重要的知识点:
1、如果向量x的长度大于2,那么(2,8)就会循环补齐,apply()对z的每行分别调用f(),形参x对应用的实参是(1,4)。
2、y输出的结果是一个2x3的矩阵而不是z一样的3x2的矩阵,因为R中的矩阵默认是以列进行存储的,所以当第一行输出的结果自然也是按列进行存储,如果调用f()返回有k个元素向量,那么apply()的结果就有k行。但是可以通过t()函数进行行列转置。
> t(apply(z,1,f))
[,1] [,2]
[1,] 0.5 0.500
[2,] 1.0 0.625
[3,] 1.5 0.750
>
#所调用的函数只返回一个标量(即单个元素向量),那么apply()的结果就是一个向量,而非矩阵,在使用apply()函数时调用的函数至少需一个参数,在上例中的形参对应的实参就是z矩阵中的一行(或一列),有时待调用的函数需要多个参数,在调用这类函数时,调用的函数的参数写在函数名称的后面用逗号隔开。
> copymaj <- function(rw,d) {
+ maj <- sum(rw[1:d]) / d
+ return(ifelse(maj > 0.5,1,0))
+ }
> x <- matrix(c(1,1,1,0,0,1,0,1,1,1,0,1,1,1,1,1,0,0,1,0),nrow = 4)
> x
[,1] [,2] [,3] [,4] [,5]
[1,] 1 0 1 1 0
[2,] 1 1 1 1 0
[3,] 1 0 0 1 1
[4,] 0 1 1 1 0
> apply(x,1,copymaj,3)
[1] 1 1 0 1
> apply(x,1,copymaj,2)
[1] 0 1 0 0
>
在R中使用apply()函数不能使程序运行速度加快,其优点是使代码更紧凑,便于阅读和修改,避免产生使用循环语句时可能带来的bug。此外并行运算是R目前发展的方向之一,apply()这类函数会变得越来越重要。如:在sonw包中的clusterApplay()函数能够把子矩阵的数据分配到多个网络节点上,在每个网络节点上对子矩阵调用给定的函数,达到并行计算的目的。
#扩展案例:寻找异常值
在统计学中,“异常值”(outlier)指的是哪些和大多数观测值离得很远的少数点。所以异常值要么是有问题(例如数字写错了),要么是不具有代表性(例如比尔盖茨的收入和华盛顿居民的收入相比),通常用到median()函数(中位数函数)
中位数(又称中值,英语:Median),统计学中的专有名词,代表一个样本、种群或概率分布中的一个数值,其可将数值集合划分为相等的上下两部分。对于有限的数集,可以通过把所有观察值高低排序后找出正中间的一个作为中位数。如果观察值有偶数个,通常取最中间的两个数值的平均数作为中位数。
> findols
function(x){
findol <- function(xrow){
mdn <- median(xrow)
devs <- abs(xrow - mdn)
return(which.max(devs))
}
return(apply(x,1,findol))
}
> x
[,1] [,2] [,3] [,4] [,5] [,6] [,7]
[1,] 1 8 9 20 20 24 25
[2,] 2 13 13 17 18 19 26
[3,] 6 10 11 12 19 20 31
[4,] 4 5 12 13 24 24 28
[5,] 5 6 17 17 21 22 23
[6,] 10 10 14 15 16 23 24
[7,] 7 8 9 16 17 28 28
[8,] 2 9 10 21 21 25 26
[9,] 3 14 14 18 19 20 27
[10,] 7 11 12 13 20 21 32
[11,] 5 6 13 14 25 25 29
[12,] 6 7 18 18 22 23 24
[13,] 11 11 15 16 17 24 25
[14,] 8 9 10 17 18 29 29
[15,] 3 10 11 22 22 26 27
[16,] 4 15 15 19 20 21 28
[17,] 8 12 13 14 21 22 33
[18,] 6 7 14 15 26 26 30
[19,] 7 8 19 19 23 24 25
[20,] 12 12 16 17 18 25 26
[21,] 9 10 11 18 19 30 30
[22,] 4 11 12 23 23 27 28
[23,] 5 16 16 20 21 22 29
[24,] 9 13 14 15 22 23 34
[25,] 7 8 15 16 27 27 31
> findols(x)
[1] 1 1 7 7 1 7 6 1 1 7 7 1 7 6 1 1 7 7 1 7 6 1 1 7 7 #输出为异常数的位置
>
4、增加或删除矩阵的行或列
严格来说,矩阵的长度和维度是固定的,因此不能增加或删除行或列,但是可以给矩阵重新赋值,这样可以得到和增加删除一样的效果
#改变矩阵的大小
>#####向量的增、插、删
> x <- c(1,2,3,4)
> x
[1] 1 2 3 4
> x <- c(x,99) #增加
> x
[1] 1 2 3 4 99
> x <- c(x[1:4],88,x[5]) #插入
> x
[1] 1 2 3 4 88 99
> x <- x[-4:-5] #删除第4:5个元素
> x
[1] 1 2 3 99
>
改变矩阵常用到的函数rbind()、cbind(),可以给矩阵增加行或列
语法:
cbind(..., deparse.level = 1)
rbind(..., deparse.level = 1)
## S3 method for class 'data.frame'
rbind(..., deparse.level = 1, make.row.names = TRUE, stringsAsFactors = default.stringsAsFactors())
> x <- c(1,1,1)
> x
[1] 1 1 1
> z <- matrix(c(1,2,3,4,5,6,7,8,9), nrow = 3)
> z
[,1] [,2] [,3]
[1,] 1 4 7
[2,] 2 5 8
[3,] 3 6 9
> cbind(x,z)
x
[1,] 1 1 4 7
[2,] 1 2 5 8
[3,] 1 3 6 9
> cbind(z,x)
x
[1,] 1 4 7 1
[2,] 2 5 8 1
[3,] 3 6 9 1
> cbind(9,z)
[,1] [,2] [,3] [,4]
[1,] 9 1 4 7
[2,] 9 2 5 8
[3,] 9 3 6 9
>
函数cbind()、rbind()还可以用来快速生成一些小的矩阵
> q <- cbind(c(1,2),c(3,4))
> q
[,1] [,2]
[1,] 1 3
[2,] 2 4
>
不过!不要高兴太早了,以会有了cbind,rbind对矩阵增、删就方便了,但事实你将要付出更大的资源,和创建向量一样,创建一个新的矩阵是很耗时间的(毕竟矩阵也属于向量),假如要在矩阵中插入10w+条记录,相当于将矩阵进行了10w+的增、删。
不过不要悲观,我们可以预先创建一个足够大的矩阵(按需),最开始矩阵是空的(NA)然后在循环过程中逐行或逐列进行赋值,这样做法避免了循环过程中每次进行耗时的矩阵内存分配。
> m <- matrix(nrow = 3,ncol = 2)
> m
[,1] [,2]
[1,] NA NA
[2,] NA NA
[3,] NA NA
> m[,] <- c(c(1:3),c(4:6))
> m
[,1] [,2]
[1,] 1 4
[2,] 2 5
[3,] 3 6
> m <- m[c(1,3),]
> m
[,1] [,2]
[1,] 1 4
[2,] 3 6
>
#扩展案例:找到图中距离最近的一对端点
计算图中多个端点之间距离是计算机或统计学中常见的例子,这类问题在聚类算法和基因问题中经常出现。
我们以计算城市之间的距离为例,这比计算DNA链间距离更直观。
假设有一个距离矩阵,其第i行第j列的元素代表城市i和城市j间的距离。我们需要写一个函数,输入城市距离矩阵,输出城市间最短的距离,以及对应的两个城市。
mind <- function(d){
n <- nrow(d)
dd <- cbind(d,1:n)
wmins <- apply(dd[-n,],1,imin)
i <- which.min(wmins[2,])
j <- wmins[1,i]
return(c(d[i,j],i,j))
} imin <-function(x) {
lx <- length(x)
i <- x[lx]
j <- which.min(x[(i+1):(lx-1)])
k <- i+j
return(c(k,x[k]))
}
q <- matrix(c(0,12,13,8,20,12,0,15,28,88,13,15,0,6,9,8,28,6,0,33,20,88,9,33,0),nrow = 5) > q
[,1] [,2] [,3] [,4] [,5]
[1,] 0 12 13 8 20
[2,] 12 0 15 28 88
[3,] 13 15 0 6 9
[4,] 8 28 6 0 33
[5,] 20 88 9 33 0
> mind(q)
[1] 6 3 4 #最小值是6,位于在第3行第4列
>
5、向量与矩阵的差异
矩阵就是一个向量,只是多了两个属性:行娄和列数
从面向对象编程的角度来说,矩阵类(matrix class)是实际存在的,R中的大部分类都是S3类,用$符号就可以访问其各组件。矩阵类有一个dim属性,是一个由矩阵的行数和列数组成的向量,可以用dim()函数访问dim属性。
> z <-matrix(1:8,nrow = 4)
> z
[,1] [,2]
[1,] 1 5
[2,] 2 6
[3,] 3 7
[4,] 4 8
> length(z)
[1] 8
> class(z)
[1] "matrix"
> attributes(z)
$dim
[1] 4 2 > y <-c(1:8)
> y
[1] 1 2 3 4 5 6 7 8
> length(y)
[1] 8
> class(y)
[1] "integer"
> attributes(y)
NULL
>
> z
[,1] [,2]
[1,] 1 5
[2,] 2 6
[3,] 3 7
[4,] 4 8
> dim(z)
[1] 4 2
> nrow(z)
[1] 4
> ncol(z)
[1] 2
> nrow
function (x)
dim(x)[1L]
<bytecode: 0x07b80efc>
<environment: namespace:base>
> x <- c(1:12) ;dim(x)<-c(3,4)
> x
[,1] [,2] [,3] [,4]
[1,] 1 4 7 10
[2,] 2 5 8 11
[3,] 3 6 9 12
>
6、避免意外降维
在统计学领域,“降维”(dimension reduction)是有益的,也存在很多降维的统计学方法。假设我们需要处理10个变量,如果能把变量个数降到3个,却还能保留数据的主要信息,何乐而不为呢?
在R中,降维指的是完全另外一件事情,而且通常要避免。
> z
[,1] [,2]
[1,] 1 5
[2,] 2 6
[3,] 3 7
[4,] 4 8
> r <- z[2,]
> r
[1] 2 6
> attributes(z)
$dim
[1] 4 2 > attributes(r)
NULL
> str(z)
int [1:4, 1:2] 1 2 3 4 5 6 7 8
> str(r)
int [1:2] 2 6
>
从上面的代码可以看出,r的结果显示的是向量格式,而非矩阵的格式,也就是说,r是一个长度为2的向量,而不是一个1*2的矩阵
在R中可以使用drop参数,禁止矩阵自动减少维度。
> r <- z[2,,drop = FALSE]
> r
[,1] [,2]
[1,] 2 6
> dim(r)
[1] 1 2
>
对原本就是向量的对象,可以使用as.matrix()函数将其转化成矩阵
> u <- c(1:12)
> u
[1] 1 2 3 4 5 6 7 8 9 10 11 12
> v <- as.matrix(u)
> v
[,1]
[1,] 1
[2,] 2
[3,] 3
[4,] 4
[5,] 5
[6,] 6
[7,] 7
[8,] 8
[9,] 9
[10,] 10
[11,] 11
[12,] 12
> attributes(v)
$dim
[1] 12 1
7、矩阵的行和列的命名问题
访问矩阵元素最直接的方法就是通过行号和列号,但也可以使用行名与列名
> z
[,1] [,2]
[1,] 1 5
[2,] 2 6
[3,] 3 7
[4,] 4 8
> colnames(z)
NULL
> colnames(z) <- c("a","b")
> z
a b
[1,] 1 5
[2,] 2 6
[3,] 3 7
[4,] 4 8
> colnames(z)
[1] "a" "b"
> z[,"a"]
[1] 1 2 3 4
>
8、高维数组
在统计学领域,R语言中典型的矩阵用行表示不同的观测,比如不同的人,而用列表示不同变量,比如体重血压等。因此矩阵一般都是二维的数据结构。但是假如我们的数据采集自不同的时间,也就是每个人每个变量每个时刻记录一个数。时间就成为除了行和列之外的第三个维度,在R中,这样的数据称为数组(arrays)。
> firsttest <- matrix(c(46,21,50,30,25,50), nrow = 3)
> firsttest
[,1] [,2]
[1,] 46 30
[2,] 21 25
[3,] 50 50
> secondtest <- matrix(c(46,41,50,43,35,50), nrow = 3)
> secondtest
[,1] [,2]
[1,] 46 43
[2,] 41 35
[3,] 50 50
> tests <- array(data = c(firsttest,secondtest),dim = c(3,2,2))
> attributes(tests)
$dim
[1] 3 2 2 > tests[3,2,1] #第3行,第2列,第1个表
[1] 50
> tests[2,2,1] #第2行,第2列,第1个表
[1] 25
> tests[2,2,2] #第2行,第2列,第2个表
[1] 35
>
tests共分为两个数据层(layer),一层对应一次考试,每层都是3*2的矩阵
> tests
, , 1 [,1] [,2]
[1,] 46 30
[2,] 21 25
[3,] 50 50 , , 2 [,1] [,2]
[1,] 46 43
[2,] 41 35
[3,] 50 50 >
R语言编程艺术# 矩阵(matrix)和数组(array)的更多相关文章
- <R语言编程艺术>的一个错误以及矩阵相加
R语言编程艺术讲矩阵这节时,举了个随机噪声模糊罗斯福总统画像的例子.但是里面似乎有个错误,例子本意是区域外的值保持不变,而选定区域的值加一个随机值,但是实际情况是两个行列不相等的矩阵相加,会报错,如果 ...
- R语言编程艺术(2)R中的数据结构
本文对应<R语言编程艺术>第2章:向量:第3章:矩阵和数组:第4章:列表:第5章:数据框:第6章:因子和表 ======================================== ...
- R语言编程艺术(5)R语言编程进阶
本文对应<R语言编程艺术> 第14章:性能提升:速度和内存: 第15章:R与其他语言的接口: 第16章:R语言并行计算 ================================== ...
- R语言编程艺术(4)R对数据、文件、字符串以及图形的处理
本文对应<R语言编程艺术> 第8章:数学运算与模拟: 第10章:输入与输出: 第11章:字符串操作: 第12章:绘图 =================================== ...
- R语言编程艺术(3)R语言编程基础
本文对应<R语言编程艺术> 第7章:R语言编程结构: 第9章:面向对象的编程: 第13章:调试 ============================================== ...
- R语言编程艺术(1)快速入门
这本书与手上其他的R语言参考书不同,主要从编程角度阐释R语言,而不是从统计角度.因为之前并没有深刻考虑这些,因此写出的代码往往是一条条命令的集合,并不像是“程序”,因此,希望通过学习这本书,能提高编程 ...
- R语言编程艺术#02#矩阵(matrix)和数组(array)
矩阵(matrix)是一种特殊的向量,包含两个附加的属性:行数和列数.所以矩阵也是和向量一样,有模式(数据类型)的概念.(但反过来,向量却不能看作是只有一列或一行的矩阵. 数组(array)是R里更一 ...
- R语言编程艺术# 数据类型向量(vector)
R语言最基本的数据类型-向量(vector) 1.插入向量元素,同一向量中的所有的元素必须是相同的模式(数据类型),如整型.数值型(浮点数).字符型(字符串).逻辑型.复数型等.查看变量的类型可以用t ...
- R语言编程艺术#01#数据类型向量(vector)
R语言最基本的数据类型-向量(vector) 1.插入向量元素,同一向量中的所有的元素必须是相同的模式(数据类型),如整型.数值型(浮点数).字符型(字符串).逻辑型.复数型等.查看变量的类型可以用t ...
随机推荐
- Linux Shell脚本编程--sed命令详解
简介 sed 是一种在线编辑器,它一次处理一行内容.处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的 ...
- jquery的节点查询
jQuery.parent(expr) //找父元素 jQuery.parents(expr) //找到所有祖先元素,不限于父元素 jQuery.children ...
- IE6-BUG-CSS兼容详解
1.IE6背景闪烁 如果你给链接.按钮用CSS sprites作为背景,你可能会发现在IE6下会有背景图闪烁的现象.造成这个的原因是由于IE6没有将背景图缓存,每次触发hover的时候都会重新加载,可 ...
- phpstorm 2016.1注册码
phper 享受生产PHP Web开发phpStorm.利用深代码理解,一流的编码的援助,并支持所有主要的工具和框架. 先看看 phpstorm 2016.1 带来那些新变化呢? 1,更好的PHP语言 ...
- PHP微信红包的算法实现探讨
header("Content-Type: text/html;charset=utf-8");//输出不乱码,你懂的 $total=10;//红包总额 $num=8;// 分成8 ...
- php生成table表格
function getTable($arrTh, $arrTr){ $s = '<table class="tbData">'; $s .= '<tr>' ...
- 08 - 删除vtkDataObject中的SetWholeExtent() 方法 VTK 6.0 迁移
VTK6 引入了许多不兼容的变.其中之一是删除vtkDataObject中所有有关管道的方法.其中之一就是SetWholeExtent().SetWholeExtent()方法先前被用来管理结构话数据 ...
- Android监听事件
ListView事件监听: setOnItemSelectedListener 鼠标滚动时触发 setOnItemClickListener 点击时触发 EditText事件监听: setOnKeyL ...
- Ubuntu命令行下安装,卸载软件包的过程[转]
一.Ubuntu中软件安装方法 1.APT方式 (1)普通安装:apt-get install softname1 softname2 …; (2)修复安装:apt-get -f install so ...
- 转:ASCII码表_全_完整版
ASCII码表 ASCII值 控制字符 ASCII值 控制字符 ASCII值 控制字符 ASCII值 控制字符 0 NUL 32 (space) 64 @ 96 . 1 SOH 33 ! 65 A 9 ...