图源:
虽然一毕业就从事PHP的开发工作,但老实说都是借着以前学习Java或C++的经验来进行开发,从来没有系统性学习过这门语言,现在有空闲,决定系统性学习一下,这个系列笔记将记录相关的学习总结。
本系列的学习笔记针对的是PHP8,这也是当前的最新版本。
安装
PHP开发环境搭建推荐使用XAMPP,相对来说更方便,具体可以阅读。当前最新版的XAMPP附带的PHP版本是8。
基本语法
PHP标记
PHP是从模版语言发展来的,虽然现在已经具备完善的OOP相关语法,可以像Java那样开发大型应用,但依然保留了模版语言的特色,即所有的PHP代码需要用PHP标记<?php ?>
包裹才能被正常解析和执行:
<?php
$a = array(
"name" => "jack chen",
"age" => 16,
"career" => "actor"
);
foreach ($a as $key => $value) {
# code...
echo $key . ": " . $value . "\r\n";
}
如果编写的是纯代码,这样可能不是很方便,但实际上小型的PHP网站往往是直接使用PHP嵌入Html代码的方式来编写页面的:
某些IDE,比如Zend Studio,会自动在新建
.php
的文件时添加上<?php
这个起始标记。
<html>
<h1> echo "hello"; </h1>
</html>
这样做的好处是无需使用复杂的模版引擎和相应语法,缺点是后端语言和前端代码混合在一起,不利于进行扩展和维护。但无论如何,比一些“笨重”的语言(如Java)多了一种选择。
一般来说,PHP解析器只会解析被PHP标记包裹部分的PHP代码,并用相应的输出替代,然后将文档中其余的文本部分原样输出,但也有一些例外,比如如果含有条件或循环语句:
$students = array(
"Li lei" => array(
"name" => "Li lei",
"age" => 10,
),
"Han Meimei" => array(
"name" => "Han Meimei",
"age" => 20,
),
"Xiao Li" => array(
"name" => "Xiao Li",
"age" => 15,
)
);
<html>
<h1> echo "hello"; </h1>
<ul>
foreach ($students as $key => $student){
<li>name: echo $student["name"] age: echo $student["age"] </li>
} </ul>
</html>
如果使用过某些模版引擎和模版语言,就会发现很类似,因为PHP原本就是一种模版语言。
最后要说明的是,对于纯PHP的后端代码,只写起始标记,故意忽略掉终止标记?>
是一个良好习惯,这样做可以降低某些情况下在终止标记之外出现空白符导致的意外输出。
注释
PHP的注释风格和C++、Java的类似:
<?php
/**
* 求和
* @param int $a 一个整数
* @param int $b 另一个整数
* @return int 结果
*/
function add($a,$b){
return $a+$b; //求和逻辑
}
支持//
或#
的单行注释,以及/** ... **/
的多行注释。
如果注释符合某些注释标准,如上边的@param
,通过使用类似的注释标签可以在某些IDE中实现智能关联或者类型提示,会更友好一些。当然本质上来说,PHP任然是一种弱类型语言,这并非强制性规定,这和Python的类型注解是类似的。
结束符号
不同的语言对于在单行代码后是否添加结束标识的规定差别很大,比如Go是可加和不加,Python则不能添加,而PHP和Java、C++类似,使用;
作为单行代码的结束标识:
$num = 1;
如果是纯PHP代码,必须遵守这种规定,但如果是Html+PHP的混合文本,则可以在简短的PHP标记内省略最后一个;
:
<html>
<h1> echo "Hello";echo " World!" </h1>
</html>
数据类型
PHP与Python类似,同为弱类型语言,但这并非意味着变量是没有类型的,只不过万能变量可以存储任意类型,而某一时刻,某一个变量的真实类型取决于其存储的值的类型。
PHP支持的类型有以下几种:
基础类型:
-
bool
-
int
-
float(或double)
-
string
其中float和double没有实际的区别,这只是历史遗留的结果。
符合类型:
-
array
-
object
-
callable
-
iterable
特殊类型:
-
resource
-
NULL
要查看变量的实际类型,最常见的是在调试时使用var_dump()
函数:
$num = 1;
var_dump($num);
echo "<br/>";
// int(1)
var_dump
直接会将结果输出,如果需要将类型保存到字符串:
$type = gettype($num);
echo $type;
echo "<br/>";
// integer
但这仅应当作为调试的参考依据,不能直接用于类型检测,如果需要在代码中进行类型检测,应当使用is_xxx
函数:
if (is_array($num)){
echo '$num'." is array";
}
else if (is_integer($num)){
echo '$num'." is int";
}
else{
echo '$num'." is unknown type";
}
echo "<br/>";
// $type is int
类型转换
如果需要转换不同的类型,可以通过强制类型转换实现,其写法与Java类似:
<?php
$number = 1;
var_dump($number);
// int(1)
echo '<br/>';
$number = (string)$number;
var_dump($number);
// string(1) "1"
echo '<br/>';
$number = (array)$number;
var_dump($number);
// array(1) { [0]=> string(1) "1" }
echo '<br/>';
$number = (object)$number;
var_dump($number);
// object(stdClass)#1 (1) { ["0"]=> string(1) "1" }
echo '<br/>';
和其它语言不同,php会尽量将类型进行转换,可以看到,即使是整型,也可以转换为数组或对象,这在其它语言中肯定是行不通的,唯一的结果就是报错。
这种设计在某些情况下会让转换变得轻松,但某些情况下可能会产生潜在的bug且无法察觉。
对于其它类型转换为字符串,php提供一种更便捷的方式:
<?php
$intNum = 1024;
$strNum = "$intNum";
var_dump($intNum);
// int(1024)
echo '<br/>';
var_dump($strNum);
// string(4) "1024"
echo '<br/>';
这实际上是利用了php中""
包裹的字符串可以直接解析其中的变量的特性,通常我们会利用这种特性来构建SQL查询语句:
$uid = 100;
$sql = "
SELECT * FROM users
WHERE user_id='${uid}'
";
var_dump($sql);
echo '<br/>';
// string(44) " SELECT * FROM users WHERE user_id='100' "
隐式类型转换
除了像上面那样显式地转换类型外,代码中还会发生一些隐式的类型转换,比如一个表达式中包含多种类型:
<?php
$a = 1;
$b = 2.5;
$c = "10 a number will be used";
$result = $a*$b+$c;
echo $result;
// Warning: A non-numeric value encountered in D:\workspace\http\myweb.com\ch1\type_exchange2.php on line 5
// 12.5
这里$a
是一个整形,$b
是一个浮点型,$a*$b
的结果是一个浮点型,这点很好理解,很多程序语言都符合这样的结果,但$c
居然也可以进行运算,这或许会让人诧异。
事实上一个字符串形式的变量,在参与数学运算时会被“尽可能”转换为数字,如果是某个数字开头,就会被转换为相应的数字后参与运算,不过可能会出现warning
,就像上面示例中那样。关于这点可以参考官方文档中的相关说明。
bool类型
bool类型由true和false两种值组成,在php中是不区分大小写的:
$isTrue = true;
$isTrue = True;
$isTrue = TRUE;
php允许其他类型转换为bool类型:
$zeroNum = 0;
$nonZeroNum = 1;
var_dump((bool)$zeroNum);
// bool(false)
echo '<br/>';
var_dump((bool)$nonZeroNum);
// bool(true)
echo '<br/>';
$emptyStr = '';
$nonEmptyStr = 'abc';
var_dump((bool)$emptyStr);
// bool(false)
echo '<br/>';
var_dump((bool)$nonEmptyStr);
// bool(true)
echo '<br/>';
$emptyArray = array();
$nonEmptyArray = array("1"=>"1");
var_dump((bool)$emptyArray);
// bool(false)
echo '<br/>';
var_dump((bool)$nonEmptyArray);
// bool(true)
echo '<br/>';
$null = null;
var_dump((bool)$null);
// bool(false)
echo '<br/>';
事实上如果将变量置于条件语句中,php会进行隐式转换,尝试将其转换为bool类型后执行条件语句:
if ($nonEmptyArray){
echo "array is not empty<br/>";
// array is not empty
}
但并不推荐这么使用,因为这样的代码可读性并不强,我们可以使用其它函数来进行判断某些类型是否非空,比如empty
:
if (!empty($nonEmptyArray)){
echo "array is not empty<br/>";
// array is not empty
}
整型
php中的整形可以用十进制、八进制、十六进制、二进制来表示:
$nums = array();
$nums[] = 123;
$nums[] = 0123; //1*8^2+2*8^1+3=83
$nums[] = 0x123; //1*16^2+2*16+3=291
$nums[] = 0b110; //1*2^2+1*2+0=6
foreach ($nums as $num){
echo "${num}<br/>";
}
// 123
// 83
// 291
// 6
这里八进制用0
开头的数字表示,十六进制用0x
开头的数字表示,二进制用0b
开头的数字表示。
7.4.0之后,php还支持一种下划线表示的整形值:
$num = 12_345_678;
echo "${num}<br/>";
// 12345678
这种表示方法和记账中常用的12,345,678
这种类似,是为了方便快速确定位数便于查看的计数方式。
如果需要将其它类型转换为整形,可以通过类型强制转换,此外还可以使用intval()
函数。
关于
intval()
的详细用法,可以阅读官方手册。
需要注意的是,计算机存储的浮点数都并非精确值,而是近似值,尤其是数学表达式运算后的结果,所以如果试图将一个不确定的浮点数运算结果转换为整形,可能出现预料外的结果:
$num2 = intval((0.7+0.1)*10);
echo "${num2}<br/>";
// 7
这里的运算结果应该是8,但实际上计算机中对浮点数的运算结果只是一个近似值,可能是7.99999999
这样的,取整后的结果就是7,而非8。
这也是为什么我们不应当直接比较两个浮点数是否相等,而应当在某个精度内确定是否相等:
$float1 = 1.5;
$float2 = 1.523333;
if (abs($float1-$float2)<0.1){
echo "float1 == float2<br/>";
// float1 == float2
}
浮点型
浮点型可以用多种形式表示:
$a = 1.234;
var_dump($a);
// float(1.234)
$b = 1.2e3;
var_dump($b);
// float(1200)
$c = 7E-10;
var_dump($c);
// float(7.0E-10)
$d = 1_234.567; // 从 PHP 7.4.0 开始支持
var_dump($d);
// float(1234.567)
其中带下划线_
的形式从7.4版本开始生效。
关于浮点数的精度和比较问题,可阅读整形部分的内容。
字符串
字符串的字面量可以由4种不同的方式组成:
-
单引号
-
双引号
-
heredoc
-
nowdoc
单引号
单引号组成的字符串中,所有字符都将原样输出,如果要输出单引号自身,可以使用转义符\'
,如果要输出转义符自身,可以\\
:
$str = 'hello world';
$str2 = 'I\'m fine';
$str3 = 'http:\\\\www.baidu.com';
var_dump($str);
// string(11) "hello world"
var_dump($str2);
// string(8) "I'm fine"
var_dump($str3);
// string(20) "http:\\www.baidu.com"
双引号
双引号中的特殊字符可以被正常输出:
$header = "name\t\tage\n";
$lines = array();
$lines[] = "Li Lei\t\t20\n";
$lines[] = "Han Mei\t\t10\n";
$lines[] = "Xiao Li\t\t15\n";
echo $header;
foreach ($lines as $line){
echo $line;
}
// name age
// Li Lei 20
// Han Mei 10
// Xiao Li 15
此外,双引号字符串中的变量也会被解析为字符串后输出。这种解析方式分两种:简单形式和复杂形式。
简单形式很好理解,能正常识别的变量都会被直接解析:
class Student{
var $name;
var $age;
function __construct($name,$age)
{
$this->name = $name;
$this->age = $age;
}
}
$std1 = new Student("Li lei", 20);
echo "name:$std1->name age:$std1->age".PHP_EOL;
// name:Li lei age:20
$std2 = array("name"=>"Li lei","age"=>20);
echo "name:$std2[name] age:$std2[age]".PHP_EOL;
// name:Li lei age:20
$name = "Li lei";
$age = 20;
echo "name:$name age:$age".PHP_EOL;
// name:Li lei age:20
就像上面展示的那样,数组、对象、普通变量都可以正常解析,但前提是变量名称要“清晰”,如果某些情况下无法正常辨别变量名,就可能解析错误:
echo "name:$names age:$ages".PHP_EOL;
// name: age:
此时解析到的两个变量是$names
和$ages
,而非$name
和$age
,前者是未定义变量,所以就出现空白的情况,同时可能会输出变量没有定义的相关warming
。
要处理上边的问题也很简单,只要使用{}
明确变量名称即可:
echo "name:${name}s age:${age}s".PHP_EOL;
// name:Li leis age:20s
php有一种其它语言中几乎没有的特性:可以用字符串变量的值作为变量名来使用。比如:
$strName = 'name';
echo $std1->$strName.PHP_EOL;
// Li lei
这里的$std1->$strName
实质上就相当于$std1->name
。
如果将上边的语法代入双引号字符串,就会出现问题:
echo "$std1->$strName".PHP_EOL;
// PHP Fatal error: Uncaught Error: Object of class Student could not be converted to string in ...
查看错误信息可以知道,这里对类Student
对象无法被转换为字符串,所以报错。但明明我们访问的是对象的属性啊?
其实IDE的高亮提示已经表现的很明显了,本质上"$std1->$strName"
是被解析为两个变量:$std1
和$strName
,而->
操作符则被认为是一个普通字符,所以才会出现上面的错误提示。
如果要让这个表达式在双引号下也能正常工作,需要用到复杂变量解析,通常写为"{$...}"
的形式:
echo "{$std1->$strName}".PHP_EOL;
// Li lei
需要注意的是,{}
必须与$
符号紧密“接触”,中间不能有空白符:
echo "{ $std1->$strName }".PHP_EOL;
// PHP Fatal error: Uncaught Error: Object of class Student could not be converted to string...
{}
也可以进行嵌套,内层{}
解析的结果可以作为外层{}
内的变量名:
echo "{$std1->{$strName}}".PHP_EOL;
// Li lei
虽然"${...}"
和"{$...}"
形式上挺像的,但他们差别很大,前者是变量的简单解析,后者是复杂解析。
heredoc
heredoc是在编程领域普遍使用的一种文档标注用途的字符串,在C++或者多种Shell语言中都能看到相同定义的字符串,通常也会被叫做“文档串”。
其定义的形式是:
$docstring = <<<EOF
This is a doc description:
This doc is used to...
Points:
1. ...
2. ...
3. ...
The End.
EOF;
echo $docstring;
// This is a doc description:
// This doc is used to...
// Points:
// 1. ...
// 2. ...
// 3. ...
// The End.
<<<
之后跟的是一个开始和结束标记,这个标记是可以自行定义的任何字符串,这里使用EOF
只是一种习惯写法。开始和结束标记之间可以是任意的字符串。最后以单独的一个结束标记和;
为结尾。
就像上面展示的那样,某些情况下可能需要写多行的、内容复杂的说明性文字,此时可以使用heredoc来构建字符串。
heredoc会解析所包含的变量,且可以输出特殊字符,这点和双引号字符串类似:
$name = 'Li lei';
$age = 20;
echo <<<EOF
\nname:$name\tage:$age
EOF;
// name:Li lei age:20
更详细的变量解析规则请参考双引号的部分。
nowdoc
heredoc相当于双引号字符串,nowdoc则相当于单引号字符串,nowdoc中的字符会原样输出,且不会解析变量:
echo PHP_EOL;
echo <<<'EOF'
\nname:$name\tage:$age
EOF;
// \nname:$name\tage:$age
字节数组
在很多编程语言中,字符串是不可修改的,但php并非如此。
php字符串的本质是字节数组,所以可以使用[]
来访问字符串中的字符:
$str = "Hello World!";
$str[0] = "h";
echo $str.PHP_EOL;
// hello World!
echo $str[strlen("Hello ")];
// W
需要注意的是,通过[]
结合下标的方式访问的是字节,而非字符,所以这种方式读取和改写字符都只能针对ASCII编码的字符串,而非更普遍的UTF-8编码:
$str = "你好";
$str[0] = "h";
echo $str.PHP_EOL;
// h��好
像上边展示的那样,尝试对一个UTF-8编码的字符串使用上边的方式修改字符会发生难以预料的结果。如果有类似的需要,你应当使用字符串函数:
$str = "你好";
$str = str_replace('你','h',$str);
echo $str.PHP_EOL;
// h好
特别的,如果在通过[]
修改字符时,指定的下标超过了合法的范围,依然会添加,并且中间缺失的部分用空格填充:
$str = "Hello World!";
echo strlen($str).PHP_EOL;
$str[15] = 'E';
var_dump($str);
// 12
// string(16) "Hello World! E"
连接字符串
可以使用.
操作符连接字符串,这或许有点奇怪,因为大多数语言都是使用+
:
echo "你"."好".PHP_EOL;
// 你好
类型转换
可以通过类型强制转(string)
或strval()
函数将其它类型转换为字符串,并且在某些需要转换为字符串的地方发生隐式的类型转换,比如echo
或print
函数。
此外,类可以通过设置__toString()
魔术方法来定义字符串转换行为,这和Java中的toString()
方法类似:
class Student{
var $name;
var $age;
function __construct($name, $age)
{
$this->name = $name;
$this->age = $age;
}
function __toString()
{
return "student(name:{$this->name},age:{$this->age})";
}
}
$std1 = new Student('Li Lei',20);
echo $std1.PHP_EOL;
// student(name:Li Lei,age:20)
基础部分的内容就总结到这里,其它的复合数据类型放在下一篇笔记。
谢谢阅读。
本系列所有文章的相关代码都收录在。
文章评论