欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  php教程

PHP: chr和pack、unpack那些事

程序员文章站 2022-05-06 09:16:08
...

引子 我之前有篇文详细介绍过pack和unpack:PHP: 深入pack/unpack,如果有不明白的地方,建议再回过头去看多几遍。现在应该能够写出以下代码: ?php echo pack( C , 97 ). \n ; $ php-ftest.phpa 但是,为什么会输出'a'呢?虽然我们知道字符'a'的ASCII码就是

引子

我之前有篇文详细介绍过pack和unpack:PHP: 深入pack/unpack,如果有不明白的地方,建议再回过头去看多几遍。现在应该能够写出以下代码:

echo pack("C", 97) . "\n";
$ php -f test.php
a

但是,为什么会输出'a'呢?虽然我们知道字符'a'的ASCII码就是97,但是pack方法返回的是二进制字符串,为什么不是输出一段二进制而是'a'?为了确认pack方法返回的是一段二进制字符串,这里我对官方的pack的描述截了个图:

PHP: chr和pack、unpack那些事

确实如此,pack返回包含二进制字符串的数据,接下来详细进行分析。

程序是如何显示字符的

这里所说的'程序',其实是个宏观的概念。

对于在控制台中执行脚本(这里是指PHP作为cli脚本来执行),脚本的输出会写入标准输出(stdin)或标准错误(stderr),当然也有可能会重定向到某个文件描述符。拿标准输出来说,暂且忽略它是行缓冲、全缓冲或者是无缓冲。脚本进程执行完毕后如果有输出则会在控制台上输出字符串。那这里的控制台就是所说的'程序'。

对于Web来说(这里是指PHP作为Web的服务器端语言),程序执行完后会将结果响应给浏览器或其它UserAgent,为了方便描述,这里统一称为UserAgent。这里的UserAgent就是所说的'程序'。

当然还有其它情况,比如在GUI窗口中的输出,编辑器打开一个文件等等,这都涉及到如何显示字符串的问题。

在控制台中执行

控制台通过shell命令来执行脚本,它会fork一个子进程,之后通过exec替换子进程的地址空间,因为这个子进程不是会话首进程,所以它可以关联到终端。脚本输出执行完毕后退出,回到控制台。来看下面的例子:

$str = '回';
echo $str . "\n";
$ php -f test.php
回

test.php是UTF-8格式的文件,我的Linux系统的Locales是zh_CN.UTF-8。

$ locale
LANG=zh_CN.UTF-8
LANGUAGE=
LC_CTYPE="zh_CN.UTF-8"
LC_NUMERIC="zh_CN.UTF-8"
LC_TIME="zh_CN.UTF-8"
LC_COLLATE="zh_CN.UTF-8"
LC_MONETARY="zh_CN.UTF-8"
LC_MESSAGES="zh_CN.UTF-8"
LC_PAPER="zh_CN.UTF-8"
LC_NAME="zh_CN.UTF-8"
LC_ADDRESS="zh_CN.UTF-8"
LC_TELEPHONE="zh_CN.UTF-8"
LC_MEASUREMENT="zh_CN.UTF-8"
LC_IDENTIFICATION="zh_CN.UTF-8"
LC_ALL=

回到刚才的代码,test.php是UTF-8编码的文件,汉字'回'是三个字节表示的UTF8字符(如果不明白,可以看我的另一篇文章: JavaScript: 详解Base64编码和解码 ),所以test.php文件的内容保存在硬盘上的数据就是4个字节('\n'是ASCII字符,用1个字节表示)。将test.php输出时,将这4个字节发送到标准输出,之后被冲洗(这里忽略掉被flush的时机),由控制台来显示。回想一下Linux系统上的locale设置,很显然是采用UTF8的机制来显示字符,所以前三个字节被当成一个UTF8字符,它被组合在一起转成Unicode码然后查表,再显示出来。

$str = '回';
echo $str . "\n";
echo $str{0} . $str{1} . $str{2} . "\n";
$ php -f test.php
回
回

可以看到,不管是整个字符输出,还是三个字节连在一起输出,结果是一样的。我们接下来看看不同平台上同一个字符的Unicode编码和UTF-8编码是否一样:

PHP测试:

$str = '回';
$bin = pack("C3", ord($str{0}), ord($str{1}), ord($str{2}));
$hex = strtoupper(bin2hex($bin));
echo "UTF-8编码: " . $hex . "\n";
/**
* 1110xxxx 10xxxxxx 10xxxxxx
*/
$byte1 = ord($str{0});
$byte2 = ord($str{1});
$byte3 = ord($str{2});
$c1    = (($byte1 & 0x0F) 4) | (($byte2 & 0x3F) >> 2);
$c2    = (($byte2 & 0x03) 6