词法结构
题1 php区分大小写吗?
分析:用户定义的类和函数、内置的结构以及关键字例如echo
,while
,class
是不区分大小写的,但是变量和常量区分大小写,例如$user
,$User
,$USER
是3个不同的变量。
表达式和操作符
题1.
<?php
$a = 3;
$b = 5;
if($a = 5 || $b = 7) {
$a++;
$b++;
}
echo '$a = ',$a,', $b = ',$b;
分析:该题考察php中操作符的优先级,根据 php手册,逻辑运算符||
的优先级要高于赋值运算符=
,因此if
条件实际的顺序应该是if($a = (5 || $b = 7))
,$a
的值是bool
类型的,由于5
为true
,所以会执行$a++; $b++;
,由于$a
是bool
类型的,所以$a++
的结果仍然是1
,用var_dump()
打印$a
的结果是boolean true
,$b
的结果是6
。
所以本题最后的结果是$a = 1, $b = 6
。
题2 不使用比较运算符求两个数中较大者
function max($a, $b) {
return ($a + $b + abs($a -$b)) / 2;
}
题3 求下面的程序输出什么
echo '1+5='. 1 + 5,PHP_EOL;
echo '1+5='. 5 + 1,PHP_EOL;
echo '5+1='. 5 + 1,PHP_EOL;
echo '5+1='. 1 + 5,PHP_EOL;
分析:此题隐式转换和操作符优先级。.
和+
的优先级是相同的。字符串'1+5='. 1
会转换成1
,所以第一行会输出运算结果6
,以此类推,后面3行输出2
、6
、10
变量
题1
<?php
$a = array(1);
$b = & $a[0];
$c = $a;
$c[0]++;
echo $c[0].$a[0];
分析:本题考察变量的引用。在赋值数组
的时候,如果=
右边的数据存在引用,那边赋值的新数组对应的元素也是引用,所以改变$c[0]
的值 也会同时改变$a[0]
的值,因此这段代码输出的结果是22
。
如果没有第2
行$b = & $a
,那么输出的结果是21
。
如果是$a
是普通变量,例如
<?php
$a = 1;
$b = & $a;
$c = $a;
$c++;
echo $c,$a;
输出的结果也是21
。
题2
<?php
$a = 1;
$x = &$a;
$b = $a++;
求 $b
和 $x
的值
分析:此题考察变量的引用赋值以及递增运算符(++$a, $a++, --$a, $a--
)
由于 $b = $a++;
,因此 $b
等于1
,同时$a
递增1
,即此时$a
等于2
,由于$x
是$a
的引用,即$x
是$a
的别名,指向的是同一个内容,因此$x
等于2
。
题3 以下代码输出什么
$a = 2;
$b = &$a;
unset($a);
echo $b;
分析:此题同样考察变量的引用。在引用赋值之后,$a
和$b
有相同的值但不同的名称,销毁其中任何一个变量,不会影响其值的其他别名。
题4 PHP如何管理内存,它的垃圾收集机制是怎样的
分析:
php使用引用计数
和写时复制
来管理内存。写时复制保证了变量间复制值不浪费内存,引用计数保证了不再需要时将内存释放给操作系统。
当将一个变量的值复制到另一个变量时,php没有为复制值使用更多的内存,相反,它会更新符号表
(符号表是一个将变量名映射到内存中变量值所在地址的数组)来说明这两个变量拥有相同的内存块,如果后来修改了任意一个副本,php将分配所学的内存来进行复制,例如:
$username = ['dee', 'lee', 'john];
$users = $username; // 数组不被复制
$user[1] = 'jack'; // 值改变,数组被复制
通过延迟分配和复制,php在很多情形下节省了时间和内存,这就是写时复制。符号表中每一个值都有一个引用计数器,它的数值表示获取那片内存的方式的数目。在上例中,在$username
和$users
初始化赋值后,数组指向了符号表,$username和$user的引用计数器的值为2,即那块内存有两种方式可以获得:通过$username和$users。当$users\[1\]被改变时,php为$users创建了一个新的数组,此时$username和$users的引用计数器的值各位1.
当一个变量离开作用域,例如函数参数和局部变量到达函数末尾时,它的值的引用计数减1。当一个变量在其他内存空间被赋值时,旧值的引用计数减1。当引用计数的值为0时,他的内存被释放,这就是引用计数。
题5 PHP中变量的生命周期
分析:
局部变量:为其所在函数被调用的整个过程。当局部变量所在的函数结构结束时,局部变量的生命周期也随之结束。
全局变量:为其所在“.php”脚本文件被调用的整个过程。当全局变量所在脚本文件结束调用时,全局变量的生命周期结束
静态变量:有时某个定义函数结束后,我们希望该函数内的变量仍然存在,就需要将这个变量声明为静态变量(static)。
题6 请说说你对PHP中引用类型和值类型的理解
分析:
值类型:大部分变量类型,如字符串,整型,浮点型,数组等,赋值时会在内存中创建一个新的变量。
引用类型:引用就是创建变量的别名。赋值之后两个变量拥有相同的变量但是不同的名称。对象的赋值不会创建新的变量。引用的优点是,当大的字符串或数组进行赋值时可以节省资源。
题7 交换两个变量的值,不使用新的变量
分析:
方法① 使用异或运算符
<?php
$a = 4;
$b = 3;
$a = $a ^ $b;
$b = $a ^ $b;
$a = $a ^ $b;
方法②
使用list()
函数
<?php
$a = 'memcached';
$b = 'redis';
list($a, $b) = array($b, $a);
题7 用最短的代码找出3个数字中的最大值
分析:可以使用三元运算
<?php
($a = ($a > $b ? $a : $b)) > $c ? $a : $c;
不够短?还可以使用max()
函数:
<?php
$max = max(array($a, $b, $c));
题8 以下代码会输出?
$A="Hello";
function print_A() {
$A= "php mysql !!";
global$A;
echo$A;
}
echo$A;
print_A();
分析:HelloHello
第一次执行echo $A;
,$A
的值是Hello
, 第二次在方法中赋值的$A
是局部变量, global $A
声明全局变量$A
,则在函数中echo $A
由于是全局变量所以值还是Hello
。如果本题在globle $A
后执行$A = "php mysql"
结果就是Hellophp mysql!!
了。
函数
题1 include, include_once, require, require_once这几个函数有什么区别?
分析: include
和require
这两个关键字都是包含并运行指定文件,不同的是include
在引入不存的文件时产生一个警告且脚本还会继续执行,而require
则会导致一个致命性错误且脚本停止执行 include_once
语句与include
类似,唯一的区别是如果该文件中已经被加载成功了,则后续再次加在同一个文件的操作会被忽略,require_once
与require
的区别也是如此。
题2 isset() 、empty()与is_null的区别
分析: isset()
函数检测变量是否设置,如果变量存在并且值不是 NULL 则返回 TRUE,否则返回 FALSE。 empty()
函数检查一个变量是否为空,当变量存在,并且是一个非空非零的值时返回 FALSE 否则返回 TRUE is_null()
函数检测变量是否为 NULL,如果变量是 null 则返回 TRUE,否则返回 FALSE。
题3 php中传值与传引用的区别
按值传递
:函数范围内对值的任何改变在函数外部都会被忽略 引用传递
:函数范围内对值的任何改变在函数外部也能反映出这些修改
题4 用php写出显示客户端IP、服务器端IP的代码和网页地址
客户端IP:$_SERVER["REMOTE_ADDR"]
服务器端IP:$_SERVER["SERVER_ADDR"]
网页地址:$_SERVER["REQUEST_URI"]
当前脚本的执行路径:$_SERVER["SCRIPT_FILENAME"
]或者__FILE__
当前脚本的名称:$_SERVER["PHP_SELF"]
或者$_SERVER["SERIPT_NAME"]
链接到前一页的URL地址:$_SERVER["HTTP_REFERER"]
题5 echo,print(),print_r(),printf(),sprintf(),var_dump()有什么区别
echo
是语言结构,用于输出字符串到页面 print()
函数发送一个值(它的参数)给浏览器 print_r()
函数可以智能地显示传给它的参数,可以打印字符串和数字,也可以打印数组和对象 printf()
函数可以通过替换模版中的值输出字符串 var_dump()
比print_r()
更适合调试,它用更适合阅读的格式显示所有php的值
题6 php中对数组序列化和反序列化的函数,把utf-8转换成gbk的函数
serialize
,unserialize
,iconv("utf-8","gbk",$strs)
题7 strlen()与mb_strlen的作用分别是什么?
strlen()
无法正确处理中文字符串的占位,对于gb2312
得到的是汉字个数的2
倍,utf8
得到的是汉字个数的3
倍
mb_strlen()
就很好的解决了这个问题,它的第二个参数就是设置字符编码的
题7 exit和return有什么区别
分析:
脚本执行到exit
语句时,就会停止执行 return
语句一般用于某个函数退出返回,或者囧啊本停止执行
字符串
题1 使用5种方式获取一个文件的扩展名
分析:
可以使用php内置的函数、数组函数、字符串函数来实现。
1.内置函数pathinfo()
,在返回的数组中键为extension
的值就是扩展名
<?php
$filename = 'pic.20160626.jpg';
$pathinfo = pathinfo($filename);
var_dump($pathinfo['extension']); // jpg
2.同样使用内置函数pathinfo()
,带上第二个参数PATHINFO_EXTENSION
$ext = pathinfo($filename, PATHINFO_EXTENSION);
var_dump($ext);
3.数组函数explode()
+count()
$arr = explode('.', $filename);
var_dump($arr[count($arr) - 1]); // jpg
4.字符串函数strrpos()
+substr()
:strrpos()用于计算指定字符串在目标字符串中最后一次出现的位置
$position = strrpos($filename, '.');
var_dump(substr($filename, $position + 1)); // jpg
5.字符串函数strrchr()
:strrchr()查找指定字符在字符串中的最后一次出现,并该函数返回字符串的一部分
var_dump(strrchr($filename, '.')); // .jpg
题2 给任意一段URL,取出该URL中包含的扩展名
分析:
类似于上一题。也可以使用内置函数parse_url()
+basename()
来解析URL:
<?php
$url = 'http://www.sina.cn/index.php?username=dee';
$parse_url = parse_url($url);
$basename = basename($parse_url['path']); // index.php
$arr = explode('.', $basename);
$ext = array_pop($arr); // php
题3 PHP中单引号和双引号有什么区别?哪个速度更快?
分析:
1.用单引号括起来的变量不能被解析
2.在单引号括起来的字符串中只有单引号'和反斜线܀要用反斜线转义;用双引号括起来的变量能够被解析,有双引号、换行符、回车、制表符、大括号、中括号等需要转义。
3.如果包含的字符串中含有变量,使用单引号更快,因为单引号不会解析变量,而双引号解析变量需要时间。
数组
题1 写个函数用来对二维数组排序
解析:
情况一、如果是针对二维数组中每个元素的某个值进行排序,例如
$arr = [
['name'=>'dee','age'=>28],
['name'=>'emperor', 'age'=>27],
['name'=>'Lee', 'age'=>20],
['name'=>'Arshavin', 'age'=>33],
['name'=>'Totti', 'age'=>40],
['name'=>'K6', 'age'=>27],
];
按照age
的值从小到大进行排序
解法① 可以使用usort()
函数
$arr = [
['name'=>'dee','age'=>28],
['name'=>'emperor', 'age'=>27],
['name'=>'Lee', 'age'=>20],
['name'=>'Arshavin', 'age'=>33],
['name'=>'Totti', 'age'=>40],
['name'=>'K6', 'age'=>27],
];
usort($arr, function($a, $b){
return $a['age'] >= $b['age'] ? 1 : 0;
});
var_dump($arr);
解法② 使用array_multisort()
函数
$arr = [
['name'=>'dee','age'=>28],
['name'=>'emperor', 'age'=>27],
['name'=>'Lee', 'age'=>20],
['name'=>'Arshavin', 'age'=>33],
['name'=>'Totti', 'age'=>40],
['name'=>'K6', 'age'=>27],
];
$age = [];
foreach($arr as $val) {
$age[] = $val['age'];
}
array_multisort($age, SORT_ASC, $arr);
var_dump($arr);
情况二、如果是针对二维数组中每个元素的某几个值进行排序,例如
$arr = [
['name'=>'dee','age'=>28, 'height'=>180],
['name'=>'emperor', 'age'=>27, 'height'=>181],
['name'=>'Lee', 'age'=>20, 'height'=>170],
['name'=>'Arshavin', 'age'=>33, 'height'=>173],
['name'=>'Totti', 'age'=>40, 'height'=>183],
['name'=>'Jonh', 'age'=>27, 'height'=>170],
];
要根据age
从小到到,age
相同的按height
从大到小排序:
$arr = [
['name'=>'dee','age'=>28, 'height'=>180],
['name'=>'emperor', 'age'=>27, 'height'=>181],
['name'=>'Lee', 'age'=>20, 'height'=>170],
['name'=>'Arshavin', 'age'=>33, 'height'=>173],
['name'=>'Totti', 'age'=>40, 'height'=>183],
['name'=>'Jonh', 'age'=>27, 'height'=>170],
];
$age = $height = [];
foreach($arr as $val) {
$age[] = $val['age'];
$height[] = $val['height'];
}
array_multisort($age, SORT_ASC, $height, SORT_DESC, $arr);
var_dump($arr);
情况三、如果是针对多维数组中每个元素的某几个值进行排序,例如
$arr = [
['name'=>'dee','info'=>['subinfo'=>['age'=>28, 'height'=>180]]],
['name'=>'emperor', 'info'=>['subinfo'=>['age'=>27, 'height'=>181]]],
['name'=>'Lee', 'info'=>['subinfo'=>['age'=>20, 'height'=>170]]],
['name'=>'Arshavin', 'info'=>['subinfo'=>['age'=>33, 'height'=>173]]],
['name'=>'Totti', 'info'=>['subinfo'=>['age'=>40, 'height'=>183]]],
['name'=>'Jonh', 'info'=>['subinfo'=>['age'=>27, 'height'=>170]]]
];
要根据age
从小到到,age
相同的按height
从大到小排序:
$arr = [
['name'=>'dee','info'=>['subinfo'=>['age'=>28, 'height'=>180]]],
['name'=>'emperor', 'info'=>['subinfo'=>['age'=>27, 'height'=>181]]],
['name'=>'Lee', 'info'=>['subinfo'=>['age'=>20, 'height'=>170]]],
['name'=>'Arshavin', 'info'=>['subinfo'=>['age'=>33, 'height'=>173]]],
['name'=>'Totti', 'info'=>['subinfo'=>['age'=>40, 'height'=>183]]],
['name'=>'Jonh', 'info'=>['subinfo'=>['age'=>27, 'height'=>170]]]
];
function get_key($arr, $key) {
$weight = [];
if(is_array($arr)) {
foreach ($arr as $k => $v) {
if ($key === $k) {
$weight[] = $v;
} else {
$tmp = get_key($v, $key);
if(! empty($tmp)) {
$weight[] = $tmp[0];
}
}
}
return $weight;
}
}
$age = 'age';
$age = get\_key($arr, $age);
$height = 'height';
$height = get\_key($arr, $height);
array_multisort($age, SORT\_ASC, $height, SORT_DESC, $arr);
echo '<pre>';
print_r($arr);
类和对象
题1 接口与抽象类的区别是什么?
抽象类:
抽象类是不能被实例化的类,只能作为其他类的父类来使用,抽象类是通过关键字abstract来声明
抽象类与普通类类似,都包含成员变量和成员方法,两者的区别在于,抽象类中至少包含一个抽象方法
抽象方法没有方法体,该方法天生就是要被子类重写的
抽象方法的格式为:abstract function abstractMethod()
子类继承抽象类使用extends
接口:
接口是通过interface关键字来声明,接口中的成员常量和方法都是public的,方法可以不写关键字public
接口中的方法也是没有方法体的,接口中的方法也是天生要被子类实现的
接口能实现多继承
子类实现接口使用implements
题2 以下代码会输出什么
class Dog {
public $age;
public function __construct($age) {
$this->age = $age;
}
}
function test($dog) {
$dog->age = 10;
}
$dog = new Dog(100);
test($dog);
echo $dog->age;
题3 以下代码会输出什么
class Dog {
public $age;
public function __construct($age) {
$this->age = $age;
}
}
function test($dog) {
$dog = new Dog(10);
}
$dog = new Dog(100);
test($dog);
echo $dog->age;
题4 以下代码会输出什么
class Dog {
public $age;
public function __construct($age) {
$this->age = $age;
}
}
function test($dog) {
$dog = null;
}
$dog = new Dog(100);
test($dog);
echo $dog->age;
题5 以下代码会输出什么
class Dog {
public $age;
public function __construct($age) {
$this->age = $age;
}
}
function test(&$dog) {
$dog = new Dog(10);
}
$dog = new Dog(100);
test($dog);
echo $dog->age;
错误和异常
缓存与静态化
时间日期
题1 知道一天的日期,求任意一天叮得日期
题2 用php打印出前一天的时间格式
文件
题1 写一个函数,算出两个文件的相对路径
<?php
function relative_path($a, $b) {
$a2arr = explode('/', $a);
$b2arr = explode('/', $b);
$diff = array_diff($a2arr, $b2arr);
$count = count($diff);
$relative = str_repeat('../', $count);
array_pop($b2arr);
return $relative.implode('/', array_diff($b2arr, $a2arr));
}
题2 写一个函数,能够遍历一个文件夹下的所有文件和子文件夹
<?php
function tree($dir) {
static $files;
if(is_dir($dir)) {
if($handle = opendir($dir)) {
while(($file = readdir($handle)) !== false) {
if(is_dir("$dir/$file")) {
if(! in_array($file, array('.', '..'))) {
$files[] = "$dir/$file";
tree("$dir/$file");
}
} else {
$files[] = "$dir/$file";
}
}
}
}
return $files;
}
$dir = 'D:practise/test';
题3 删除某目录下的子目录及文件
分析:需要使用到的目录函数包括is_dir()
、opendir()
、readdir()
、rmdir()
、closedir()
;需要用到的文件函数包括is_file()
、unlink()
,使用递归算法
function deldir($dir) {
if(is_dir($dir)) {
$handle = opendir($dir);
while (false !== ($file = readdir($handle))) {
if($file == '.' || $file == '..'){
continue;
}
if(is_dir($dir.'/'.$file)) {
deldir($dir.'/'.$file);
} else {
unlink($dir.'/'.$file);
}
}
closedir($handle);
rmdir($dir);
} elseif(is_file($dir)) {
unlink($dir);
} else {
return 'error dir';
}
return 'del complete';
}
题4 php中Web上传文件的原理是什么,如何限制上传文件的大小?
分析:
通过form表单使用POST方法上传,可以使用户上传文本和二进制文件
客户端html部分如下
<form action="" method="post" enctype="multipart/form-data">
<input type="file" name="userfile">
<input type="submit" value="上传">
</form>
服务端通过$\_FILES接收通过HTTP上传到服务器的文件,上传的内容存储在$_FILES'xx'中,然后再通过move_uploaded_file,将上传的文件移动到新位置:
if(! empty($_FILES)) {
if(is_uploaded_file($_FILES['userfile']['tmp_name'])) {
if($_FILES['userfile']['error'] == UPLOAD_ERR_OK) {
$upload_dir = './upload';
$tmp_name = $_FILES['userfile']['tmp_name'];
$name = $_FILES['userfile']['name'];
if(move_uploaded_file($tmp_name, $upload_dir.'/'.$name)) {
echo 'success.';
} else {
echo 'error';
}
}
}
}
有多个配置可以限制上传文件的大小,在php.ini
中: post_max_size
:(php 5.5.12)默认3M upload_max_filesize
:(php 5.5.12)默认64M max_execution_time
:必要的情况下还需要修改该配置,设置了脚本被解析器中止之前允许的最大执行时间,单位秒。(php 5.5.12)默认120 memory_limit
:(php 5.5.12)默认128M
Cookie和Session
题1 禁用COOKIE 后 SEESION 还能用吗?
分析:在默认情况下(PHP>=4.3.0),php.ini
中session.use_only_cookies
的值为1
,也就是说在不更改任何配置的情况下,禁用了浏览器的 Cookie 功能,是没有办法使用 Session 的,因为默认情况下,Session_ID 保存在 Cookie 中。如果希望在禁用了 Cookie 之后仍然可以使用 Session,至少需要更改php.ini
中的以下几个配置:session.use_cookies
改为0
、session.use_only_cookies
改为0
、·session.use_trans_id·改为1
(表示Session_ID通过url的参数进行传递)。使用url传递Session_ID相比使用Cookie传递Session_ID来说非常不安全,一是完全暴露Session_ID,二是容易遭到Session固定攻击。
题2 如何设置session的过期时间
分析:如果没有设定Session的生存周期,保存Session_ID的Cookie是保存在内存中的,关闭浏览器后该ID自动注销。如果客户端没有禁用Cookie,Cookie在启动Session时扮演的是存储Session_ID和Session生命周期的角色,可以手动设置Session的生存期:
$lifetime = 24 * 3600;
set_cookie(session_name(), session_id(), time() + $lifetime, '/');
也可以使用 session_set_cookie_params()
函数设置Session的生存期。
Session过期后,PHP会对其进行回收,因此Session并不是随着浏览器的关闭而消失。
如果浏览器禁用了Cookie,那么Session的生命周期会随着浏览器进程的结束而结束,即只要关闭了浏览器,再次请求页面就要重新注册Session。
题3 请介绍Session的原理
分析:由于HTTP协议的无状态特性,协议本身并不支持服务器端保存客户端的状态信息,为了让服务器端和客户端保持联系,引入了Session的概念,用其来保持客户端的状态信息。Session通过一个成为PHPSESSID(可以更改名称)的Cookie和服务器端联系。Session通过Session_ID判断用户。
当使用了session_start()函数,用户第一次访问站点时,PHP会为用户创建一个session ID,这就是该用户的唯一标识,每一个访问的用户都会得到一个自己唯一的session ID。session ID会存放在响应头(Response)里的cookie中,之后发送给客户端,于是客户端就有了该用户在这个站点的session ID。当用户第二次访问该站点时,浏览器会带着本地存放的cookie(里面存有上次得到的session ID)随着请求(Request)一起发送到服务器,服务端接到请求后会检测是否有session ID,如果有就会找到响应的session文件,把其中的信息读取出来;如果没有就重新创建一个Session_ID。
题4 如何注销Session
分析:Session的注销分为4个步骤:
1.开启Session,所有有关Session的操作都需要先开启Session(除非在php.ini中设置session.auto_start为1,默认为0)
session_start();
2.删除所有的session变量,即把session数组清空
$_SESSION = array();
3.如果是基于Cookie的Session,则需要把保存Session_ID的Cookie删除
if(isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time() - 1, '/');
}
4.彻底销毁Session
session_destroy();
题5 简述Session的回收机制
分析:如果用户退出网站时没有主动注销帐号,那么Session的回收将是被动的。php.ini
中和Session回收有关的配置有: session.gc_maxlifetime
:表示 Session 文件的过期时间,默认为 1440 秒即 24分钟 session.gc_probability
:默认值为1 session.gc_divisor
:默认值为1000
后面两个配置代表 session_start() 函数每调用 1000 次触发一次 Session 文件的全部扫描,把过期的 Session 文件删除。过期的 Session 文件的判断标准是:文件的修改时间和当前时间相差是否大于 session.gc_maxlifetime。当用户每进行一个操作哪怕是一个刷新页面的动作时,都会修改 Session 文件的修改时间。对于设置分级目录存储的Session,php不会自动回收,需要自己实现回收机制。
题6 session共享问题解决方案
分析:可以将Session入库(普通数据库、内存表),或者使用内存存储系统例如Redis来存储Session,达到服务器间Session的共享。通过session_save_handler()
函数实现。
题7 大型网站中Session方面应注意什么
分析:大型网站有很多衡量标准,访问量是其中一个。对于大访问量的网站,如果Session按照默认的设置存储,会影响系统性能。默认情况下,Session由文件的形式保存在指定的目录下,如果同一个目录下文件数超过10000,文件的定位将会非常耗时,可以通过修改php.ini
中session.save_path
将Session文件存储在多级目录下;也可以保存在数据库中;最好的方式是保存在key-value形式的内存数据库中,例如Redis。
另一个方面,大型网站一般有多台服务器,要做好Session的同步。如果是使用数据库或者NoSQL来存储Session,Session的同步会比较容易。如果是文件形式保存Session文件,可以使用NFS或者FastDFS等文件系统存储Session文件。
题8 Session和Cookie的联系和区别
分析:
如果不修改php的配置,则Session是基于Cookie的:Session的唯一标识Session_ID保存在Cookie中,通过Cookie中的Session_ID来维持服务器端和客户端之间的状态。
可以通过设置保存Session_ID的Cookie的过期时间来设置Session的生存周期。
Cookie通过文件或者数据库的形式保存客户端,Session通过文件、数据库等形式保存在服务器端。
题9 服务器端设置了cookie的过期时间,然后修改本地时间小于cookie过期时间,cookie会不会被删除
分析:
会。因为Cookie的过期时间依赖于客户端时钟的标准。
题10 如何设置Cookie的过期时间,关闭浏览器Cookie会消失吗
分析:
通过setcookie()
函数的第三个参数expire
设置Cookie的过期时间,单位为秒,例如
setcookie('username', 'dee', time() + 24 * 3600, '/');
这段代码表示name
为username的Cookie,会在24小时后过期。
如果要删除某个Cookie,同样使用setcookie()
函数,例如
setcookie('user', '', time() - 1, '/');
只需要把Cookie的过期时间改为小于当前的一个时间即可。
HTTP协议
题1 HTTP状态码
题2 get请求和post请求的区别
算法
题1 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
分析:可以用递归和递推两种方法实现。
① 递归
如果台阶只有 1
级,则只有 1
种跳法,如果台阶有2
级,则有2
种跳法(一次1级跳2次或一次2级)。把n
级台阶时的跳法设为f(n)
,如果第一次只跳1
级,则后面的n-1
级的跳法为f(n-1)
,如果第一次跳2
级,则后面n-2
级的跳法为f(n-2)
。当n > 2
时,n
级台阶的跳法为f(n) = f(n-1) + f(n-2)
,这就是一个斐波那契数列
的问题
<?php
function fibonacci($n) {
$result = [0, 1, 2];
if($n <= 2) {
return $result[$n]; // $n = 1,1种跳法;$n = 2,2种跳法
}
return fibonacci($n - 1) + fibonacci($n - 2);
}
② 递推
<?php
function climbstairs($n) {
$dp = [1, 1];
if($n < 2) {
return 1;
}
for($i = 2; $i <= $n; $i++) {
$dp[2] = $dp[0] + $dp[1];
$dp[0] = $dp[1];
$dp[1] = $dp[2];
}
return $dp[2];
}
题2 用一段代码实现无限级分类
分析:此题考察递归算法。
<?php
function level_layer($cate, $name = 'child', $pid = 0) {
$arr = [];
foreach($cate as $val) {
if($val['pid'] == $pid) {
$val[$name] = level_layer($cate, $name, $val['id']);
$arr[] = $val;
}
}
return $arr;
}
$data = [
['id' => 1, 'title' => '北京市', 'pid' => 0],
['id' => 2, 'title' => '朝阳区', 'pid' => 1],
['id' => 3, 'title' => '海淀区', 'pid' => 1],
['id' => 4, 'title' => '江西省', 'pid' => 0],
['id' => 5, 'title' => '九江市', 'pid' => 4],
['id' => 6, 'title' => '浔阳区', 'pid' => 5],
['id' => 7, 'title' => '庐山区', 'pid' => 5],
['id' => 8, 'title' => '南昌市', 'pid' => 4],
['id' => 9, 'title' => '河北省', 'pid' => 0],
['id' => 10, 'title' => '保定市', 'pid' => 9],
['id' => 11, 'title' => '石家庄市', 'pid' => 9],
['id' => 12, 'title' => '江苏省', 'pid' => 0],
['id' => 13, 'title' => '浙江省', 'pid' => 0],
];
echo '<pre>'; print_r(level_layer($data));
题3 一群猴子排成一圈,按 1,2,...,n 依次编号。然后从第 1 只开始数,数到第 m 只,把它踢出圈,从它后面再开始数,再数到第 m 只,在把它踢出去...,如此不停的进行下去,直到最后只剩下一只猴子为止,那只猴子就叫做大王。要求编程模拟此过程,输入 m、n,输出最后那个大王的编号。
分析:这是一个“约瑟夫环”问题,可以使用循环队列、循环链表等多种方法来解决,这里使用循环链表。
<?php
// 猴子类,链表上的每一个元素
class Monkey {
public $number;
public $name = null;
public function __construct($number, $name = null) {
$this->number = $number;
$this->name = $name;
}
}
// 初始化链表,添加元素
function init(&$first, $n) {
$current = null;
for($i = 0; $i < $n; $i++) {
$monkey = new Monkey($i + 1);
if($i == 0) {
$first = $monkey;
$first->next = $monkey; // 只有一个元素时
$current = $first;
} else {
$current->next = $monkey;
$monkey->next = $first; // $monkey 是变化的
$current = $current->next;
}
}
}
// 显示链表
function showList($first) {
$current = $first;
while($current->next != $first) {
echo '猴子的编号是:',$current->number,'<br />';
$current = $current->next;
}
echo '猴子的编号是:',$current->number,'<br />';
}
// 删除元素
function delElement($first, $m, $start = 1) {
$tail = $first; // 指向尾部节点的引用,在first节点之前
while($tail->next != $first) {
$tail = $tail->next; // 把tail节点设置在first节点之前
}
// 从第几个元素开始数
for($i = 0; $i < $start - 1; $i++) {
$tail = $tail->next;
$first = $first->next;
}
while($tail != $first) { //当$tail==$first则说明只有最后一个元素
for($i = 0; $i < $m - 1; $i++) {
$tail = $tail->next;
$first = $first->next;
}
echo '<br />删除编号 '.$first->number,' 的元素';
// 跳过被删除的节点(first指向的节点)
$first = $first->next;
$tail->next = $first;
}
echo '<br />最后剩下的编号是: '.$tail->number;
}
$n = 4; // 元素的个数(猴子的数量)
$m = 3; // 每次数几个元素
$start = 1; // 从第几个元素开始数
$first = null; // 链表头部节点的引用
init($first, $n);
showList($first);
delElement($first, $m, $start);
题4 写一段php代码实现冒泡排序
解答:
function bubble($array) {
$count = count($array);
for($i = 0; $i < $count - 1; $i++) {
for($j = 0; $j < $count - 1 - $i; $j++) {
// 递减
if($array[$j] < $array[$j + 1]) {
$tmp = $array[$j];
$array[$j] = $array[$j + 1];
$array[$j + 1] = $tmp;
}
}
}
return $array;
}
题5 写一段php代码实现快速排序
/*快速排序*/
function quick_sort($array) {
// 递归到只有一个元素
if(count($array) <= 1) {
return $array;
}
$key = $array[0];
$left_arr = [];
$right_arr = [];
// 左边数组的元素都比右边数组的元素小
for($i = 1; $i < count($array); $i++) {
if($array[$i] <= $key) {
$left_arr[] = $array[$i];
} else {
$right_arr[] = $array[$i];
}
}
// 递归对左右两个数组分别进行排序
$left_arr = quick_sort($left_arr);
$right_arr = quick_sort($right_arr);
// 合并结果
return array_merge($left_arr, array($key), $right_arr);
}
$arr = [10, 21, 1, 3, 89, 27, 75, 45, 91, 200, 451, 37, 2];
var_dump(quick_sort($arr));
题6 写一段php代码实现二分查找
<?php
function binarySearch(array $array, $needle) {
$start = 0;
$end = count($array) - 1;
while($start < $end) {
$middle = floor(($start + $end) / 2);
if($array[$middle] == $needle) {
return $middle;
}
if($needle < $array[$middle]) {
$end = $middle - 1;
}
if($needle > $array[$middle]) {
$start = $middle + 1;
}
}
return false;
}
题7 换硬币问题:如果有100元,可以使用1元、2元、5元、10元4种面额的零钱来兑换,有多少种兑换方法?
<?php
$a = 1;
$b = 2;
$c = 5;
$d = 10;
$sum = 0;
for ($i = 0; $i <= 100; $i++) {
for ($j = 0; $j <= 50; $j++) {
for ($k = 0; $k <= 20; $k++) {
for($h = 0; $h <= 10; $h++) {
if ($a * $i + $b * $j + $c * $k + $d * $h == 100) {
$sum += 1;
printf("%d个1元,%d个2元,%d个5元,%d个10元<br />", $i, $j, $k, $h);
}
}
}
}
}
printf("共%d种换法", $sum);
题8 鸡兔同笼问题
正则表达式
题1 请写一个函数验证电子邮件的格式是否正确
设计模式
题1 请用单态设计模式方法设计类满足如下需求,使用php代码编写类实现在每次对数据库连接的访问中都只能获取唯一的一个数据库连接,具体连接数据库的详细代码忽略,请写出主要逻辑代码。
MySQL
题1 where和having的区别
题2 内连接、左连接、右连接的区别
题3 mysql优化常见的方案
题4 explain的用法
题5 索引的种类和用法
题6 mysql的存储引擎Innodb和MyISAM的区别
题7
一个足球网站有一张新闻表news
,字段包括新闻编号id
、新闻分类cid
、标题title
、点击量click
,现在需要对 news 表按照 cid (分类id)进行过分组,按照每组新闻的数量进行排序,同时取出每组点击量(click)最多的两篇新闻。
分析:
创建表
CREATE TABLE `news` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '新闻编号',
`cid` int(11) NOT NULL COMMENT '分类编号 例如 1 世界足球新闻 2 英超新闻 3 西甲新闻',
`title` varchar(25) NOT NULL COMMENT '新闻标题',
`click` int(11) NOT NULL DEFAULT '0' COMMENT '点击量',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
表数据
INSERT INTO `news` VALUES ('1', '1', '法国2-0力克德国进决赛', '1888');
INSERT INTO `news` VALUES ('2', '2', '传曼联1亿英镑报价博格巴', '201');
INSERT INTO `news` VALUES ('3', '3', '队报:巴萨接近签法国国脚', '150');
INSERT INTO `news` VALUES ('4', '1', '克罗斯:德国队表现最好的一场', '100');
INSERT INTO `news` VALUES ('5', '1', '决赛对阵:法国vs葡萄牙', '205');
INSERT INTO `news` VALUES ('6', '1', '格列兹曼6场6球仅次普拉蒂尼', '211');
INSERT INTO `news` VALUES ('7', '1', '阿森纳法国双星赛后安慰厄齐尔', '188');
INSERT INTO `news` VALUES ('8', '2', '回声报:利物浦要求艾比交易中加入回购条款', '11');
INSERT INTO `news` VALUES ('9', '3', '普约尔:没有比巴萨更适合梅西的地方', '225');
INSERT INTO `news` VALUES ('10', '3', '皇家贝蒂斯有意狼堡前锋多斯特', '13');
INSERT INTO `news` VALUES ('11', '3', '哈维:我觉得梅西不会想离开巴萨的', '1000');
INSERT INTO `news` VALUES ('12', '2', '传温格已将西迪贝看作重点目标', '1200');
此题的需求是 1.按新闻分类分组 2.按照新闻分类下新闻数量给分好的组排序 3.排好序的分组还需要取出该组分类下点击量最多的两篇新闻
需求1 可以使用group by
来进行分组
需求2 可以使用count()
+group by
,取出每组分类下新闻的数量
以上2个需求可以用一条sql完成
select *, count(*) as num from news group by cid order by num desc;
查询结果
可以看到已经按照数量num进行了排序
需求3 可以cid
+click
进行排序,这个需求可以用一条sql完成,用子查询实现
select a.* from news a
where (
select count(*) from news b where a.cid = b.cid and b.click > a.click
) < 2
order by cid, click desc;
查询结果
此时每个cid下点击量最多的两篇文章已经查询出来了,只不过查询的结果没有按照每个分类下新闻数量的多少来排序
只需要最后一步,把两个sql语句进行左(右)连接即可
select * from (
select a.* from news a
where (
select count(*) from news b where a.cid = b.cid and b.click > a.click
) < 2
order by cid, click desc
) tablea RIGHT JOIN (
select cid, count(*) as num from news group by cid order by num desc
) tableb on tablea.cid = tableb.cid
order by tableb.num desc, click desc;
查询结果:
题8 一张表有id,sortid,title,如何按照sortid进行分组,按照sortid数量进行排序,输出结果。
分析:此题和题6类似。
表结构
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(25) NOT NULL,
`sortid` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
创建数据
INSERT INTO `test` VALUES ('1', 'title1', '106');
INSERT INTO `test` VALUES ('2', 'title2', '78');
INSERT INTO `test` VALUES ('3', 'title3', '56');
INSERT INTO `test` VALUES ('4', 'title4', '78');
INSERT INTO `test` VALUES ('5', 'title5', '78');
INSERT INTO `test` VALUES ('6', 'title6', '12');
INSERT INTO `test` VALUES ('7', 'title7', '56');
分三步创建sql
-- step 1
select a.* from test a
where (
select count(*) from test b where b.sortid > a.sortid
) < (select count(*) from test)
order by sortid;
-- step2
SELECT *, count(*) AS num, GROUP_CONCAT(id) AS ids FROM test GROUP BY sortid ORDER BY num;
-- final
select * from (
select a.* from test a
where (
select count(*) from test b where b.sortid > a.sortid
) < (select count(*) from test)
order by sortid
) tablea LEFT JOIN (
SELECT *, count(*) AS num, GROUP_CONCAT(id) AS ids FROM test GROUP BY sortid ORDER BY num
) tableb
on tablea.sortid = tableb.sortid
order by tableb.num asc;
此题没有要求列出每个分组的前N条数据,那就把每个分组下所有的数据都列出来,查询结果如下:
题9
有如下表及数据
name
subject
score
张三
数学
90
张三
语文
50
张三
地理
40
李四
语文
55
李四
政治
45
王五
政治
30
要求:查询出2门及2门以上不及格者的平均成绩
解析:
先查出所有人的平均分(AVG(score)),再找出每个人2门及2门以上不及格的数量(SUM(score<60)),最后筛选出数量大于等于2的人的信息(Having num>=2)
SELECT name, AVG(score) AS avg, SUM(score < 60) AS num
FROM result
GROUP BY name
HAVING num >= 2;
查询结果:
方法2:
SELECT `name`, AVG(score) AS avg FROM result WHERE `name` in (
SELECT name FROM result GROUP BY name HAVING SUM(score<60) >= 2
)
GROUP BY `name`;
查询结果:
方法3:复杂很多
SELECT tablea.name, tablea.avg, tableb.num FROM (
SELECT *,AVG(score) AS avg FROM result GROUP BY name
) tablea RIGHT JOIN (
SELECT *, COUNT(*) AS num FROM (
SELECT * FROM result WHERE score < 60
) a
GROUP BY a.name HAVING num >= 2
) tableb on tablea.name = tableb.name;
查询结果:
题10
有两张表:
表A
id
num
a
5
b
10
c
15
d
10
表B
id
num
b
5
c
15
d
20
e
99
要求查询出以下效果:
id
sum(num)
a
5
b
15
c
30
d
30
e
99
分析:
先使用UNION ALL
将两张表联合起来,再使用SUM()
+GROUP BY
SELECT id, SUM(num) FROM (
SELECT * FROM A
UNION ALL
SELECT * B
) AS tmp
GROUP BY tmp.id
;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。