PHP学习笔记6:表达式和运算符

PHP学习笔记6:表达式和运算符

image-20211129162010327

图源:php.net

表达式

php官方手册对表达式的定义是“任何有值的东西”。不同的语言对于表达式的定义是区别很大的,比如Python中很著名的“lamda表达式”,很多人觉得其本质就是个匿名函数,用处不大,这是因为他们不明白匿名函数无法嵌入表达式中使用,而lamda表达式可以。

基础数据

基础类型的数据本身就是一个表达式:

echo 'hello'.PHP_EOL;
echo (1).PHP_EOL;
// hello
// 1

需要注意的是,echo 1.PHP_EOL;会产生语法错误,因为整形值1是不能和字符串连接的,但如果用()将其包围,则(1)就是一个明确的表达式,而表达式的结果虽然依然是整形值,但php解释器会将其隐式转换为字符串后进行字符串连接。

赋值语句

php中赋值语句也是表达式,换句话说,赋值语句的作用不仅是将一个值赋给一个变量,同时作为表达式,它本身还会产生一个值:

echo ($a=123).PHP_EOL;
echo $a.PHP_EOL;
// 123
// 123

并非所有语言的赋值语句都可以作为表达式使用,比如Python就不行,因此Python还在之前的某个版本加入了新特性——“赋值表达式”,有兴趣的可以阅读PEP 572 – Assignment Expressions

因为赋值语句是个表达式,所以可以在赋值语句中嵌入赋值语句:

$a = ($b = 123);
echo $a.PHP_EOL;
echo $b.PHP_EOL;
// 123
// 123

其实示例中的()是不必要的:

$a = $b = 123;
echo $a.PHP_EOL;
echo $b.PHP_EOL;
// 123
// 123

这是因为表达式解析是从左到右的,且=操作符是右结合的,所以php解释器自然而然会先执行$b=123,然后将值代入表达式,执行$a=123

$a = $b = 123这种写法也可以被称作“级联赋值”。

运算赋值

运算赋值是对赋值语句的一种简略写法,这广泛存在于几乎所有的编程语言:

<?php
$a = 1;
$a += 5;
echo $a.PHP_EOL;
// 6

示例中$a += 5本质上等同于$a = $a + 5,不过在写法上更为简洁。

赋值语句是表达式,同样的,运算赋值语句也是表达式:

$a = 5;
echo ($a += 6) . PHP_EOL;
// 11

函数调用

函数调用也是表达式,其值是返回值:

function test(){
    return 5;
}
echo test().PHP_EOL;
// 5

自增、自减

可以看得出,php在语法上很多地方都参考了C/C++/Java这一系语言。php中存在自增和自减语句,且同样有前后的区别:

<?php
$num = 0;
echo (++$num).PHP_EOL;
// 1
echo ($num++).PHP_EOL;
// 1
echo $num.PHP_EOL;
// 2

用表达式解释前自增和后自增的区别就是:前自增是在表达式执行前让变量完成自增,后自增则是让自增发生在表达式执行之后。

三元运算符

类C语言中有一种奇怪的东西——“三元运算符”:

<?php
$a = 10;
$b = $a > 5 ? 1 : 0;
echo $b;
// 1

三元运算符?:构成的其实也是一个表达式:

echo ($a > 5 ? 1 : 0) . PHP_EOL;
// 1

所以也会将其称之为“条件表达式”。

有些文章会将包含比较操作符(>/<=等)的表达式称作条件表达式,但我更喜欢将其称作"bool表达式",将三元运算符构成的表达式称作条件表达式。

运算符

从概念上说,运算符是一种将多个表达式结合并构成一个更大表达式的符号。可以写作:

exp : [exp1 + exp2]

也就是说加运算符+将两个子表达式exp1exp2连接起来,构成了一个更大的表达式exp

运算符根据连接的表达式数量的不同,被划分为“一元运算符”、“二元运算符”、“三元运算符”。其中一元和二元运算符广泛存在于所有编程语言中,而三元运算符部分语言中并不存在。

优先级和结合律

运算符的优先级决定了那些运算符先执行哪些运算符后执行,当然基本的算术运算符的优先级是兼容数学领域的,这样符合直觉:

$a = 1 + 2 * 5;
echo $a . PHP_EOL;
// 11

和数学中一样,使用()可以人为指定优先级:

$a = (1 + 2) * 5;
echo $a . PHP_EOL;
// 15

运算符的结合律则决定了使用同一运算符的情况下,要先执行运算符左边还是右边的运算:

$a = $b = 2;
echo $a.PHP_EOL;
echo $b.PHP_EOL;
// 2
// 2

这是一个前边举过的例子,赋值表达式是右结合律,所以其等价于$a = ($b = 2),即先执行=右侧的部分。

结合优先级和结合律的规则,就可以知道一个表达式中应该先执行哪些部分,后执行哪些部分。

并不是所有运算符都有明确的结合律,有的是没有的,比如从php8.0.0开始,三元运算符的结合律从左结合变成了无,在这种情况下使用嵌套的多个三元运算符是不被允许的:

$a = true ? 0 : true ? 1 : 2; // (true ? 0 : true) ? 1 : 2 = 2 (PHP 8.0.0 前可用)
// Fatal error: Unparenthesized `a ? b : c ? d : e` is not supported. 

因为php解释器不知道要如何分组这种表达式,即将表达式解析为true ? 0 : (true ? 1 : 2)还是(true ? 0 : true) ? 1 : 2

当然这可以使用()来避免语法错误:

$a = (true ? 0 : true) ? 1 : 2; 
echo $a.PHP_EOL;
// 2

就像上面展示的那样,最好不要依赖于运算符的优先级和结合律来构建复杂的表达式,那样会产生一些潜在风险,更别说php随着版本更新多次改变了运算符的优先级和表达式定义。这样做最大的问题是代码的可读性很差。使用()来明确表达式的优先级是个良好习惯。

完整的优先级和结合律列表可以查阅官方手册运算符优先级

算术运算符

完整的算术运算符列表见算术运算符

和C++相比,php的除法运算符/并不区分整数和浮点数,即使除数和被除数都是整数,运算也是除法而非C++中的整除:

$a = 1;
$b = 5;
echo ($a / $b) . PHP_EOL;
// 0.2

所以php中一般的除法运算结果都是浮点数,除非两个操作数都是整数,且结果恰好也是整数:

$a = 6;
$b = 2;
echo ($a / $b) . PHP_EOL;
// 3

如果需要在php中进行整除,应当使用 intdiv() 函数。

如果对浮点数进行模运算,会将操作数转换为整数后进行运算:

$a = 7.5;
$b = 2.2;
echo ($a % $b) . PHP_EOL;
// Deprecated: Implicit conversion from float 7.5 to int loses precision in ...
// Deprecated: Implicit conversion from float 2.2 to int loses precision in ...
// 1

可以看到,虽然会正常执行并产生结果,但会产生两条Deprecated提醒,这意味着这种特性已经废弃,会在未来的php版本中删除,所以应当避免这种方式。如果要对浮点数取余,可以使用 fmod() 函数。

此外,模运算中如果涉及负数,结果的正负号是与被除数的正负号一致的:

$a = -7;
$b = 2;
echo ($a % $b) . PHP_EOL;
// -1
$a = 7;
$b = -2;
echo ($a % $b) . PHP_EOL;
// 1

赋值运算符

特别的,因为php的字符串连接符是.而非常见的+,所以php的运算赋值符号中有一个独特的.=

$a = 'hello';
$a .= ' world';
$a .= '!';
$a .= PHP_EOL;
echo $a;

当然,这同样存在字符串连接中产生“中间字符串”过多,造成性能浪费的问题,所以构建长字符串通常使用下边的方式:

$longStr = array();
$longStr[] = 'hello';
$longStr[] = ' world';
$longStr[] = '!';
$longStr[] = PHP_EOL;
$a = implode('', $longStr);
echo $a;
// hello world!

php中没有指针,但存在引用赋值。对于对象以外的类型赋值,都是值拷贝:

<?php
require_once "../util/array.php";
$arr1 = array(1, 2, 3);
$arr2 = $arr1;
$arr2[0] = 99;
print_arr($arr1);
print_arr($arr2);
// [0:1, 1:2, 2:3]
// [0:99, 1:2, 2:3]

这里的工具函数print_arr是我为了缩减数组打印结果编写的一个函数:

<?php
function print_arr(array $arr)
{
    $ls = array();
    $ls[] = '[';
    $index = 0;
    $len = count($arr);
    foreach ($arr as $key => $value) {
        $ls[] = "{$key}:{$value}";
        if ($index < $len - 1) {
            $ls[] = ', ';
        }
        $index++;
    }
    $ls[] = ']';
    echo implode('', $ls) . PHP_EOL;
}

如果要在赋值后可以修改原始内容,则需要拷贝数据的引用,而不是值:

require_once "../util/array.php";
$arr1 = array(1, 2, 3);
$arr2 = &$arr1;
$arr2[0] = 99;
print_arr($arr1);
print_arr($arr2);
// [0:99, 1:2, 2:3]
// [0:99, 1:2, 2:3]

特别的,对于对象的拷贝都是引用,而非值:

<?php
class Student{
    public $name;
    public $age;
}
$std = new Student;
$std->name = 'Jack Chen';
$std->age = 20;
$std2 = $std;
$std2->name = 'Brus Lee';
echo "name:{$std->name}".PHP_EOL;
echo "name:{$std2->name}".PHP_EOL;

更多的赋值运算符说明见官方文档赋值运算符

位运算

位运算,顾名思义就是对比特位进行的运算,也就是针对二进制整数的运算。

在php中,最常见的就是错误级别的相关内容:

$consts = get_defined_constants();
foreach ($consts as $constName => $constValue){
    if (strpos($constName, 'E_')===0){
        echo "name:{$constName} value:{$constValue}".PHP_EOL;
    }
}
// name:E_ERROR value:1
// name:E_RECOVERABLE_ERROR value:4096
// name:E_WARNING value:2
// name:E_PARSE value:4
// name:E_NOTICE value:8
// name:E_STRICT value:2048
// ...

仔细观察预定义的错误级别相关常量,E_ERROR1E_WARNING2E_PARSE4E_NOTICE8,它们都是二的倍数。之所以这么设计,是为了可以利用位运算来表示一些错误级别的“集合”,这也是位运算最实用的用途之一。

如果要让php程序只显示某种级别的错误,可以实用error_reporting

error_reporting(E_ERROR);

如果要显示多种级别的错误:

error_reporting(E_ERROR | E_NOTICE);

之所以可以这样,是因为位运算E_ERROR | E_NOTICE的结果本身包含了两个二进制位,分别表示E_ERRORE_NOTICE这两个错误级别:

<?php
require_once "../util/int.php";
print_binary(E_ERROR);
print_binary(E_NOTICE);
print_binary(E_ERROR | E_NOTICE);
// 0000000001
// 0000001000
// 0000001001

这里的print_binary是我编写的一个用于输出二进制形式数字的辅助函数:

/**
 * 以二进制形式打印数字(10位,0填充)
 * @param int $binaryNum
 */
function print_binary(int $binaryNum): void
{
    echo sprintf("%'.010b", $binaryNum) . PHP_EOL;
}

将结果用二进制表示就很容易理解了,E_ERRORb0001E_NOTICEb1000,经过位或运算后的结果是b1001,其第四位1表示结果中包含E_NOTICE,第一位1表示包含E_ERROR。这种用不同进位的0和1来表示多种信息的效果是相当巧妙的。

事实上这属于离散数学领域的内容,想详细了解理论方面知识的可以查阅相关资料。

同样,我们可以利用位运算的原理从一个包含多个信息的二进制数中"还原"信息:

<?php
require_once "../util/error.php";
test_error_include(E_ERROR | E_NOTICE);
test_error_include(E_NOTICE | E_ERROR | E_STRICT);
// E_ERROR,E_NOTICE
// E_ERROR,E_NOTICE,E_STRICT

这里的test_error_include是一个检测错误级别经过位运算后包含的错误级别信息的工具函数:

<?php
function test_error_include(int $multiErros)
{
    $errors = array();
    if ($multiErros & E_ERROR) {
        $errors[] = "E_ERROR";
    }
    if ($multiErros & E_PARSE) {
        $errors[] = "E_PARSE";
    }
    if ($multiErros & E_NOTICE) {
        $errors[] = "E_NOTICE";
    }
    if ($multiErros & E_STRICT) {
        $errors[] = "E_STRICT";
    }
    if ($multiErros & E_WARNING) {
        $errors[] = "E_WARNING";
    }
    if ($multiErros & E_CORE_ERROR) {
        $errors[] = "E_CORE_ERROR";
    }
    if ($multiErros & E_USER_ERROR) {
        $errors[] = "E_USER_ERROR";
    }
    echo implode(',', $errors) . PHP_EOL;
}

需要说明的是,这里仅包含了大多数常见错误级别,并非所有。还注意的是这里的多个if可能同时满足,所以它们之间的关系是顺序的,而非if..else if...

可以看出,利用二进制位运算,原本可能需要传递一个数组到test_error_include中,现在只需要直接传递位运算结果就能表示多个错误级别的集合。而内建函数error_reporting采用的是相同的原理。

除了使用位或,还可以使用其它位运算符表示各种含义,比如:

// E_ERROR,E_NOTICE,E_STRICT
test_error_include(E_ALL & ~E_NOTICE); //E_NOTICE以外的错误级别
// E_ERROR,E_PARSE,E_STRICT,E_WARNING,E_CORE_ERROR,E_USER_ERROR
test_error_include(E_ALL ^ E_NOTICE); //E_NOTICE以外的错误级别
// E_ERROR,E_PARSE,E_STRICT,E_WARNING,E_CORE_ERROR,E_USER_ERROR

示例中的两种位运算其实是等价的,具体可以参考位运算原理和离散数学,这里不再详细说明。

按理来说其实也可以用下面来表示:

test_error_include(~E_NOTICE); //不推荐
// E_ERROR,E_PARSE,E_STRICT,E_WARNING,E_CORE_ERROR,E_USER_ERROR

但这里的~E_NOTICEE_ALL ^ E_NOTICE的结果可能并不完全相同:

require_once "../util/int.php";
print_binary(~E_NOTICE);
// 1111111111111111111111111111111111111111111111111111111111110111
print_binary(E_ALL & ~E_NOTICE);
// 111111111110111

这是因为E_ALL的高位会用0填充,它只会将“有意义”的低位(对应所有的错误级别)设置为1。

位运算还包含左移<<和右移>>运算,这里不再说明,具体可以阅读官方手册位运算符

比较运算符

如果你是先学习了其它语言,尤其是强类型语言,使用php进行比较运算时需要注意,php会在“需要”时进行一些类型转换后进行比较,所以某些其它语言中不可能相等的结果在php中却是相等的:

var_dump(0 == "a");
// bool(false)
var_dump("1" == "01");
// bool(true)
var_dump("10" == "1e1");
// bool(true)
var_dump(100 == "1e2");
// bool(true)

因为这里的"1""01""1e1"等都会被当做数字字符串来对待,在比较时会先转化为数字,然后比较。

而且这种结果还会因php版本的不同而不同,所以只要类型确定,应当使用===而非==进行比较:

var_dump(0 === "a");
// bool(false)
var_dump("1" === "01");
// bool(false)
var_dump("10" === "1e1");
// bool(false)
var_dump(100 === "1e2");
// bool(false)

很多开发者喜欢“引战”,会比较不同语言的差异评论优劣,其实语言特性往往是开发团队因为某些原因取舍的结果,这只是语言的一种“特性”,作为语言的使用者而言,其实是不存在优劣的。

就上面的相等运算来说,你可以认为这是一种php的缺陷,但也可以认为这是php灵活性的体现,可以实现一些别的语言中无法实现的效果。

这其中的重点在于你需要搞懂其中的细微差别,并时刻清楚你的代码会产生哪些后果。

如果你足够谨慎,还可以使用strcmp函数来比较字符串:

var_dump(strcmp("1", "01") == 0);
// bool(false)
var_dump(strcmp("10", "1e1") == 0);
// bool(false)

php还有一个相当“花哨”的比较运算符<=>,叫做“太空船”运算符,这个名字和“海象运算符”有的一拼。该运算符的描述是对于$a<=>$b,如果$a大于$b,会返回一个大于0的数,如果$a等于$b,会返回一个小于0的数,如果$a等于$b,则会返回0。

其实这种规则和一些用于比较的函数的返回值很像,比如我们可以利用它来创建一个用于比较对象的函数:

<?php
class Student
{
    public $name = "";
    public $age = 0;
}
/**
 * 比较两个student的大小
 * @param Student $std1
 * @param Student $std2
 * @return int 如果std1大于std2,返回值大于0,如果...
 */
function compare_student(Student $std1, Student $std2): int
{
    return $std1->age <=> $std2->age;
}
$std1 = new Student;
$std2 = new Student;
$std1->age = 20;
$std2->age = 15;
var_dump(compare_student($std1, $std2));
// int(1)

当然,对待“花哨”的运算符的最好方式是——不要使用。

同样的,想了解完整比较运算符列表,请移步官方手册比较运算符

错误控制运算符

php存在一个错误控制运算符@,其作用是添加在表达式头部后,可以屏蔽其后表达式可能出现的错误提示。

这里用打开文件作为示例,当试图打开一个不存在的文件时:

<?php
$lines = file("file_no_exists");
// Warning: file(file_no_exists): Failed to open stream: No such file or directory in ...

使用@屏蔽warning信息,并在失败时打印自定义信息并退出:

<?php
$lines = @file("file_no_exists") || die("file is not exists, exit.");
// file is not exists, exit.

在php8.0以前,使用@可能会屏蔽一些导致程序中断运行的致命错误提示,在8.0之后不会了。

需要注意的是,如果你设置了自定义的错误处理程序,@依然会触发:

<?php
function my_error_handle($err_no, $err_msg, $filename, $linenum){
    echo $err_msg.PHP_EOL;
}
set_error_handler("my_error_handle");
$lines = @file("file_no_exists") || die("file is not exists, exit.");
// file(file_no_exists): Failed to open stream: No such file or directory
// file is not exists, exit.

要想让@对自定义错误处理程序同样生效,需要加入相应的判断:

function my_error_handle($err_no, $err_msg, $filename, $linenum){
    if (!(error_reporting() & $err_no)){
        return false;
    }
    echo $err_msg.PHP_EOL;
}

对于@,我个人建议是,不要使用,不要使用,不要使用。

执行运算符

执行运算符"``"的作用是执行shell命令:

<?php
$result = `dir`;
echo $result . PHP_EOL;
// Volume in drive D is WorkSpace
// Volume Serial Number is EE20-29FD

// Directory of d:\workspace\http\php-notes\ch6

// 2021/12/04  15:26    <DIR>          .
// 2021/12/04  15:26    <DIR>          ..
// 2021/12/04  10:16               203 assignment.php
// ...
// 2021/12/04  11:25               317 priority.php
// 2021/12/04  11:26                63 priority2.php
// 2021/12/04  10:38               103 three.php
//              25 File(s)          5,203 bytes
//               2 Dir(s)  52,707,540,992 bytes free

这在某些时候很有用,比如编写一些运维使用的批处理脚本。但在大多数正规的Web开发中应当避免使用,因为shell命令都是平台相关的,不同的平台有不同的shell命令,且就算是同一平台,某些shell命令能否执行也取决于是否安装了某些必要的环境。

此外执行运算符也不是必须的,可以使用shell_exec函数代替:

$result = shell_exec("dir");

我个人更推荐使用后者,过多“花哨”的运算符只会降低代码的可读性。

递增、递减运算符

前面已经讨论过递增、递减运算符,这里不再进行介绍。唯一需要说明的是,因为前自增后和自增结合复杂的表达式使用,往往会引起一些歧义和阅读困难,比如:

<?php
$a = 1;
$b = $a + ++$a + $a++;
echo $a.PHP_EOL;
echo $b.PHP_EOL;
// 3
// 6

编写这样的代码完全是自寻烦恼,应当尽量避免:

<?php
$a = 1;
$a++;
$b = $a + $a + $a;
$a++;
echo $a.PHP_EOL;
echo $b.PHP_EOL;
// 3
// 6

这大概是为什么有些语言(如Go)有后自增,但没有前自增。而另一些语言(如Python),没有任何自增运算符。

逻辑运算符

有的语言(如Python)使用单词作为逻辑运算符,有的语言(如C++)则使用符号,php都支持:

<?php
$a = 1;
$b = 5;
if ($a < $b && is_int($a)) {
    echo "a < b" . PHP_EOL;
    // a < b
}
if ($a < $b and is_int($a)) {
    echo "a < b" . PHP_EOL;
    // a < b
}

所以在php中&&and是等价的,||or是等价的。不过逻辑异或xor并没有对应的符号表示的逻辑操作符:

var_dump(true xor true);
// bool(false)
var_dump(false xor false);
// bool(false)
var_dump(true xor false);
// bool(true)
var_dump(false xor true);
// bool(true)

完整的逻辑运算符列表见官方手册逻辑运算符

字符串运算符

用于字符串连接的运算符有两个:..=。在本文的前边已经有说明,所以不再阐述。

数组运算符

+运算符和+=运算符可以将两个数组进行合并:

<?php
require_once "../util/array.php";
$a = ['a1', 'a2', 'a3'];
$b = ['b1', 'b2'];
print_arr($a + $b);
// [0:a1, 1:a2, 2:a3]
print_arr($b + $a);
// [0:b1, 1:b2, 2:a3]

对于非关联数组,合并结果只会包含“多出来的索引对应的键值对”。事实上这个特征并不仅仅限于非关联数组,队员关联数组同样适用:

$a = ["banana" => "banana", "apple" => "apple"];
$b = ["banana" => "banana", "orange" => "orange"];
print_arr($a + $b);
// [banana:banana, apple:apple, orange:orange]
print_arr($b + $a);
// [banana:banana, orange:orange, apple:apple]

一般来说,很少在实际开发中使用+操作符来合并数组,更常见的是使用数组函数:

print_arr(array_unique(array_merge($a, $b)));
// [banana:banana, apple:apple, orange:orange]

这种情况下可以不用考虑前后数组的key是否存在重叠的问题。

==可以比较两个数组是否相等:

$a = ["banana" => "banana", "apple" => "apple"];
$b = ["apple" => "apple", "banana" => "banana"];
var_dump($a == $b);
// bool(true)
$a = [1,2];
$b = ["1","02"];
var_dump($a == $b);
// bool(true)

像上面展示的那样,键值对顺序不对也会被认为相等,此外特别要注意的是,在比较数组时,php解释器同样会进行某些隐式转换后比较,比如上边的数字字符串"1""02"就被转换为数字后进行比较,所以两个数组也是相等的。

如果要严格比较两个数组是否相等,需要使用===

$a = ["banana" => "banana", "apple" => "apple"];
$b = ["apple" => "apple", "banana" => "banana"];
var_dump($a === $b);
// bool(false)
$a = [1,2];
$b = ["1","02"];
var_dump($a === $b);
// bool(false)

在这种情况下,数组的键值对不仅要内容一致、类型一致,同时顺序也要一致才会被认为是相等的。

此外,==的反面是!=,而===的反面是!==

类型运算符

使用类型运算符instanceof可以判断对象是否为类的实例:

<?php
class MyClass
{
}

class NotMyClass
{
}
$a = new MyClass;

var_dump($a instanceof MyClass);
var_dump($a instanceof NotMyClass);
// bool(true)
// bool(false)

对继承关系也能很好的判断:

<?php
class ParentClass
{
}
class MyClass extends ParentClass
{
}
$a = new MyClass;
var_dump($a instanceof MyClass);
var_dump($a instanceof ParentClass);
// bool(true)
// bool(true)

因为判断的结果是bool值,所以可以结合逻辑运算符使用:

var_dump(!($a instanceof MyClass));
// bool(false)

也能判断接口:

<?php
interface MyInterface{

}
class MyClass implements MyInterface{

}
$a = new MyClass;
var_dump($a instanceof MyClass);
var_dump($a instanceof MyInterface);
// bool(true)
// bool(true)

如果instanceof左边的操作数不是对象,结果是false

<?php
var_dump(1 instanceof stdClass);
var_dump("hello" instanceof stdClass);
var_dump(false instanceof stdClass);
// bool(false)
// bool(false)
// bool(false)

以上就是php表达式和运算符的全部内容,谢谢阅读。

往期内容

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值