zoukankan      html  css  js  c++  java
  • php面试题

    一:PHP
    1:isset() 和 empty()的区别:
    答:PHP的isset()函数 一般用来检测变量是否设置,PHP的empty()函数 判断值为否为空

    2:php有哪些魔术方法
    答: __construct(), __destruct(), __call(), __callStatic(), __get(),
    __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(),
    __invoke(), __set_state(), __clone() 和 __debugInfo() 等方法在 PHP 中
    被称为魔术方法(Magic methods)。在命名自己的类方法时不能使用这些方法名,除非是想
    使用其魔术功能

    3:echo,print_r,print,var_dump有什么区别
    答: echo()函数:输出一个或多个字符串。实际上它并不是一个函数,所以不必对它使用括号,直接用echo就行

    print()函数:输出一个或多个字符串。同echo一样,实际上它并不是一个函数。print有返回值,而echo没有,
    当其执行失败时返回false,成功则返回true,速度比echo稍慢。只能打印出简单类型变量的值,如:int、string。

    print_r()函数:能打印出复杂类型变量的值。利用print_r()可以打印出整个数组内容及结构,按照一定格式显示键
    和元素,事实上,它不仅仅用于打印,而是用于打印关于变量的易于理解的信息

    var_dump()函数:判断一个变量的类型与长度,并输出变量的数值,如果变量有值,输出的是变量的值,并返回数据类型。
    此函数显示关于一个或多个表达式的结构信息,包括表达式的类型和值。数组将递归展开值,通过缩进显示其结构

    4:如何判断一个函数是否存在
    答:函数检测用function_exists,注意待检测的函数名也需要使用引号,如:
    if (function_exists('imap_open')) {
    echo "存在函数imag_openn";
    } else {
    echo "函数imag_open不存在n";
    }

    5:@这个符号表示什么意思
    答:阻止警告输出。 有些函数,在遇到入参不正确时,会提示警告,但程序也可以正常运行。其实只要把警告去掉就可以,所以
    就有@这个符号

    6:PHP获取客户端和服务器端IP
    答:客户端的ip变量:
    $_SERVER['REMOTE_ADDR'] :客户端IP,也有可能是代理IP
    $_SERVER['HTTP_CLIENT_IP']:代理端的IP,可能存在,也可能伪造
    $_SERVER['HTTP_X_FORWARD_FOR'] :用户在哪个ip上使用的id,可能存在,也可能伪造

    服务端的ip变量:
    $_SERVER['SERVER_NAME'] :需要使用函数gethostname()来获得,这个无论是服务器端还是客户端都能正常显示。
    $_SERVER['SERVER_ADDR']:服务器端的ip地址,在服务器端测试ip地址,在客户端测试正常
    getnev 获得系统的环境变量:

    7:preg_replace()和str_ireplace()两个函数在使用上有什么不同
    答:str_replace() 函数定义:使用一个字符串替换字符串中的另一些字符,对大小写敏感的搜索语法:
    str_replace(find,replace,string,count)

    str_ireplace() 函数
    定义:使用一个字符串替换字符串中的另一些字符,对大小写不敏感的搜索
    语法:str_ireplace(find,replace,string,count)

    二:MYSQL
    1:MyISAM和InnoDB的基础区别
    答:InnoDB:
    支持事务处理等
    不加锁读取
    支持外键
    支持行锁
    不支持FULLTEXT类型的索引
    不保存表的具体行数,扫描表来计算有多少行
    DELETE 表时,是一行一行的删除
    InnoDB 把数据和索引存放在表空间里面
    跨平台可直接拷贝使用
    InnoDB中必须包含AUTO_INCREMENT类型字段的索引
    表格很难被压缩

    MyISAM:
    不支持事务,回滚将造成不完全回滚,不具有原子性
    不支持外键
    不支持外键
    支持全文搜索
    保存表的具体行数,不带where时,直接返回保存的行数
    DELETE 表时,先drop表,然后重建表
    MyISAM 表被存放在三个文件 。frm 文件存放表格定义。 数据文件是MYD (MYData) 。 索引文件是MYI (MYIndex)引伸
    跨平台很难直接拷贝
    MyISAM中可以使AUTO_INCREMENT类型字段建立联合索引
    表格可以被压缩

    选择:
    因为MyISAM相对简单所以在效率上要优于InnoDB.如果系统读多,写少。对原子性要求低。那么MyISAM最好的选择。且MyISAM恢复速度快。可直接用备份覆盖恢复。
    如果系统读少,写多的时候,尤其是并发写入高的时候。InnoDB就是首选了。
    两种类型都有自己优缺点,选择那个完全要看自己的实际类弄。

    2:Mysql经常写的语句
    答:1.创建数据库 CREATE DATABASE 数据库名;

    2.删除数据库 drop database 数据库名;

    3.选择数据库 mysql> use RUNOOB;

    4.删除表
    mysql> DROP TABLE runoob_tbl
    update students settel=default where id=5;
    将手机号为 13288097888 的姓名改为 “小明”, 年龄改为 19:
    update students setname="小明", age=19 wheretel="13288097888";

    5.删除
    mysql> DELETE FROM runoob_tbl WHERE runoob_id=3;

    6.like子句
    mysql> SELECT * from runoob_tbl WHERE runoob_author LIKE '%COM';

    7.排序
    你可以使用 ASC 或 DESC 关键字来设置查询结果是按升序或降序排列。 默认情况下,它是按升序排列。
    这是升序
    mysql> SELECT * from runoob_tbl ORDER BY submission_date ASC;
    order by code,name desc等同于order by code asc, name desc
    这是降序
    select * from a order by code desc, name desc;
    1
    2
    3
    4
    5
    6
    7

    8.常用聚合函数
    1 count 2 sum 3 avg 4 max 5 min

    9.多表查询
    - as关键字可用来做别名识别
    - 默认连接的是内连接(也叫做等值连接)
    select * from book b inner join category c
    on b.category_id = c.category_id;
    1
    2
    相当于:
    select * from book b ,category c
    where b.category_id = c.category_id;
    1
    2
    外连接有左右连接(如下):
    - 左连接(以左边为基准):
    查询出所有的书籍分类,及每个分类下的书籍信息;

    select * from category c left (outer) join book b on c.category_id = b.category_id;
    右连接(以右边为基准):
    查询出所有的书籍分类,及每个分类下的书籍信息;
    select c.*,b.* from book b right (outer) join category c on c.category_id = b.category_id;

    10.NULL的处理
    查找数据表中 runoob_test_tbl 列是否为 NULL
    - 必须使用 IS NULL 和 IS NOT NULL

    11.分页查询
    查询第11到第15条数据
    select * from table_name limit 10,5
    1
    limit关键字的用法:
    LIMIT [offset,] rows
    offset指定要返回的第一行的偏移量,rows第二个指定返回行的最大数目。
    初始行的偏移量是0(不是1)。
    1
    2
    3

    12.创建视图
    create view demo_view as select * from demo_table;
    好处1:减少数据的冗余,方便对数据操作
    数据库虽然可以存储海量数据,但是在数据表设计上却不可能每种关系创建数据表
    例如,对于学生表,存储了学生信息,学生的属性包括学号、姓名、年龄、家庭地址等信息;而学生成绩表只存储了学生学号、
    科目、成绩等信息。现获得学生姓名和成绩信息,那么就需要创建一个关系,该关系需要包含学生的姓名、科目、成绩。但是为
    了该关系创建一个新的数据表,并利用实际信息进行填充,以备查询使用,是不合适的,这样会造成了数据库中数据的大量冗余。
    视图就是解决这个问题的最佳策略,因此视图可以存储查询定义,一旦使用视图存储了查询定义,就如同存储了一个新的关系,用
    户就可以直接对视图中所存储的关系进行各种操作,就如同面对的是真实的数据表。
    好处2:数据的安全和保密
    一个数据表可能包含很多列,但是这些列的信息,对于不同的角色来说,肯定不是全部公开的,对于员工表来说吧,一个普通的员
    工只能看见这个员工表中的姓名和年龄这些信息,但是对于高层来说,他们要看见员工表中更多信息,不仅仅是上面的两列还有其他的
    信息,包括员工的住址和员工的薪资待遇,这个时候都是同一张表,怎么办?视图可以解决呀,首先建立一个视图只有员工的姓名和年
    龄,再建一个视图包含地址和薪资待遇的信息。这样就可以根据不同的角色分配两个视图的查询权限,与实际表隔离开来。这样就可以
    提高数据访问的安全性了。

    13.创建存储过程
    DELIMITER //
    CREATE PROCEDURE myproc(OUT s int)
    BEGIN
    SELECT COUNT(*) INTO s FROM students;
    END
    //
    DELIMITER ;
    1
    2
    3
    4
    5
    6
    7

    14.触发器
    CREATE TRIGGER trigger_name trigger_time trigger_event ON tb_name FOR EACH ROW trigger_stmt
    trigger_name:触发器的名称
    tirgger_time:触发时机,为BEFORE或者AFTER
    trigger_event:触发事件,为INSERT、DELETE或者UPDATE
    tb_name:表示建立触发器的表明,就是在哪张表上建立触发器
    trigger_stmt:触发器的程序体,可以是一条SQL语句或者是用BEGIN和END包含的多条语句
    1
    2
    3
    4
    5
    6
    所以可以说MySQL创建以下六种触发器:
    BEFORE INSERT,BEFORE DELETE,BEFORE UPDATE
    AFTER INSERT,AFTER DELETE,AFTER UPDATE
    1
    2
    tips:一般情况下,mysql默认是以 ; 作为结束执行语句,与触发器中需要的分行起冲突为解决此问题可用DELIMITER,如:
    DELIMITER ||,可以将结束符号变成||当触发器创建完成后,可以用DELIMITER ;来将结束符号变成;
    mysql> DELIMITER ||
    mysql> CREATE TRIGGER demo BEFORE DELETE
    -> ON users FOR EACH ROW
    -> BEGIN
    -> INSERT INTO logs VALUES(NOW());
    -> INSERT INTO logs VALUES(NOW());
    -> END
    -> ||
    Query OK, 0 rows affected (0.06 sec)
    mysql> CREATE TABLE account (acct_num INT, amount DECIMAL(10,2));
    mysql> INSERT INTO account VALUES(137,14.98),(141,1937.50),(97,-100.00);
    mysql> delimiter $$
    mysql> CREATE TRIGGER upd_check BEFORE UPDATE ON account
    -> FOR EACH ROW
    -> BEGIN
    ->   IF NEW.amount < 0 THEN
    ->     SET NEW.amount = 0;
    ->   ELSEIF NEW.amount > 100 THEN
    ->     SET NEW.amount = 100;
    ->   END IF;
    -> END$$
    mysql> delimiter;

    3:mysql explain的意义
    答:模拟Mysql优化器是如何执行SQL查询语句的,从而知道Mysql是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈
    (粗略的了解)

    4:写出mysqldump的用法
    答:(1)导出整个数据库(包括数据库中的数据)
    mysqldump -u username -p dbname > dbname.sql

    (2)导出数据库结构(不含数据)
    mysqldump -u username -p -d dbname > dbname.sql

    (3)导出数据库中的某张数据表(包含数据)
    mysqldump -u username -p dbname tablename > tablename.sql

    (4)导出数据库中的某张数据表的表结构(不含数据)
    mysqldump -u username -p -d dbname tablename > tablename.sql

    mysqldump常用参数说明:
    --all-databases , -A 导出全部数据库
    mysqldump -uroot -p --all-databases

    --all-tablespaces , -Y导出全部表空间。
    mysqldump -uroot -p --all-databases --all-tablespaces --no-tablespaces , -y不导出任何表空间信息。
    mysqldump -uroot -p --all-databases --no-tablespaces

    --add-drop-database每个数据库创建之前添加drop数据库语句。
    mysqldump -uroot -p --all-databases --add-drop-database

    --add-drop-table每个数据表创建之前添加drop数据表语句。(默认为打开状态,使用--skip-add-drop-table取消选项)
    mysqldump -uroot -p --all-databases (默认添加drop语句)
    mysqldump -uroot -p --all-databases --skip-add-drop-table (取消drop语句)

    --add-locks在每个表导出之前增加LOCK TABLES并且之后UNLOCK TABLE。(默认为打开状态,使用--skip-add-locks取消选项)
    mysqldump -uroot -p --all-databases (默认添加LOCK语句)
    mysqldump -uroot -p --all-databases --skip-add-locks (取消LOCK语句)

    --comments附加注释信息。默认为打开,可以用--skip-comments取消
    mysqldump -uroot -p --all-databases (默认记录注释)
    mysqldump -uroot -p --all-databases --skip-comments (取消注释)

    --compact导出更少的输出信息(用于调试)。去掉注释和头尾等结构。可以使用选项:--skip-add-drop-table

    --skip-add-locks --skip-comments --skip-disable-keysmysqldump -uroot -p --all-databases --compact

    --complete-insert, -c使用完整的insert语句(包含列名称)。这么做能提高插入效率,但是可能会受到max_allowed_packet参数
    的影响而导致插入失败。
    mysqldump -uroot -p --all-databases --complete-insert

    --compress, -C在客户端和服务器之间启用压缩传递所有信息
    mysqldump -uroot -p --all-databases --compress

    --databases, -B导出几个数据库。参数后面所有名字参量都被看作数据库名。
    mysqldump -uroot -p --databases test mysql

    --debug输出debug信息,用于调试。默认值为:d:t:o,/tmp/mysqldump.tracemysqldump -uroot -p --all-databases
    --debugmysqldump -uroot -p --all-databases --debug=” d:t:o,/tmp/debug.trace”
    --debug-info输出调试信息并退出
    mysqldump -uroot -p --all-databases --debug-info

    --default-character-set设置默认字符集,默认值为utf8
    mysqldump -uroot -p --all-databases --default-character-set=latin1

    --delayed-insert采用延时插入方式(INSERT DELAYED)导出数据
    mysqldump -uroot -p --all-databases --delayed-insert

    --events, -E导出事件。
    mysqldump -uroot -p --all-databases --events

    --flush-logs开始导出之前刷新日志。请注意:假如一次导出多个数据库(使用选项--databases或者--all-databases),
    将会逐个数据库刷新日志。除使用--lock-all-tables或者--master-data外。在这种情况下,日志将会被刷新一次,相应的
    所以表同时被锁定。因此,如果打算同时导出和刷新日志应该使用--lock-all-tables 或者--master-data 和--flush-logs。
    mysqldump -uroot -p --all-databases --flush-logs

    --flush-privileges在导出mysql数据库之后,发出一条FLUSH PRIVILEGES 语句。为了正确恢复,该选项应该用于导出mysql
    数据库和依赖mysql数据库数据的任何时候。
    mysqldump -uroot -p --all-databases --flush-privileges

    --force在导出过程中忽略出现的SQL错误。
    mysqldump -uroot -p --all-databases --force

    --host, -h需要导出的主机信息
    mysqldump -uroot -p --host=localhost --all-databases

    --ignore-table不导出指定表。指定忽略多个表时,需要重复多次,每次一个表。每个表必须同时指定数据库和表名。例如:
    --ignore-table=database.table1 --ignore-table=database.table2 ……
    mysqldump -uroot -p --host=localhost --all-databases --ignore-table=mysql.user

    --lock-all-tables, -x提交请求锁定所有数据库中的所有表,以保证数据的一致性。这是一个全局读锁,
    并且自动关闭--single-transaction 和--lock-tables 选项。
    mysqldump -uroot -p --host=localhost --all-databases --lock-all-tables

    --lock-tables, -l开始导出前,锁定所有表。用READ LOCAL锁定表以允许MyISAM表并行插入。对于支持事务
    的表例如InnoDB和BDB,--single-transaction是一个更好的选择,因为它根本不需要锁定表。请注意当导出多个数据库时,--lock-tables
    分别为每个数据库锁定表。因此,该选项不能保证导出文件中的表在数据库之间的逻辑一致性。不同数据库表的导出状态可以完全不同。
    mysqldump -uroot -p --host=localhost --all-databases --lock-tables

    --no-create-db, -n只导出数据,而不添加CREATE DATABASE 语句。
    mysqldump -uroot -p --host=localhost --all-databases --no-create-db

    --no-create-info, -t只导出数据,而不添加CREATE TABLE 语句。
    mysqldump -uroot -p --host=localhost --all-databases --no-create-info

    --no-data, -d不导出任何数据,只导出数据库表结构。
    mysqldump -uroot -p --host=localhost --all-databases --no-data

    --password, -p连接数据库密码
    --port, -P连接数据库端口号
    --user, -u指定连接的用户名。

    mysqldump常用实例:
    mysqldump常用于数据库的备份与还原,在备份的过程中我们可以根据自己的实际情况添加以上任何参数,假设有数据库test_db,
    执行以下命令,即可完成对整个数据库的备份:
    mysqldump -u root -p test_db > test_db.sql

    如要对数据进行还原,可执行如下命令:
    mysql -u username -p test_db < test_db.sql

    还原数据库操作还可以使用以下方法:
    mysql> source test_db.sql

    5:说说inner join与left join的区别
    答:inner join 必须两边对应才能查处结果

    left join 用主表关联副表,关联不出来依然显示结果

    三,Thinkphp5
    1:关联模型有哪些使用方法
    答:一对多关联,例如一个用户有多个订单则需要在数据库的订单表中添加用户ID
    //在订单model中使用 定义与用户表user的一对多关联
    public function user()
    {
    return $this->belongsTo('user');
    }
    // 在用户model 中定义关联方法
    public function advinfo()
    {
    return $this->hasMany('advinfo');//hasmany一对多关联,详细看开发手册
    }

    在订单方法中,直接通过foreach,动态的为每条数据新增用户姓名
    foreach ($list as $value) {
    $value -> user = $value -> user -> name;
    }

    2:数据进行增删查改操作有哪些方法
    答: 2 // 1.thinkphp5添加记录
    // 第一种方法
    $result=Db::execute('insert into think_data (name,status) values ("thinkphp",1');
    //第二种
    Db::table('think_data')
    ->insert(['name'=>'thinkphp','status'=>1]);
    // 第三种
    Db::name('data')
    ->insert(['name'=>'thinkphp','status'=>1]);
    // 第四种
    $db=db('data');
    $result=$db->insertGetId(['name'=>'thinkphp5']);
    //返回id的方法
    $data=['name'=>'thinkphp'];
    $res=Db::name('data')->insertGetId($data);
    //插入多条记录
    $data1=[
    ['name'=>'1'],
    ['name'=>'2']
    ];
    $res=$db->insertAll($data1);
    // 2.thinkphp5更新记录
    // 第一种方法
    $result=Db::execute('update think_data set name="thinkphp" where id= 1');
    // 第二种方法
    Db::table('think_data')
    ->where('id',10)
    ->update(['name'=>'thinkphp']);
    // 第三种方法
    Db::name('data')
    ->where('id',10)
    ->update(['name'=>'thinkphp']);
    //第四种方法db函数
    $db=db('data');
    $db->where('id',20)->update(['name'=>"thinkphp5.0"]);
    // 3.查找记录
    // 第一种方法
    $res=Db::query('select * from think_data ');
    // 第二种方法
    $res=Db::table('think_data')
    ->where('id',10)
    ->select();
    // 第三种方法
    $res=Db::name('think_data')
    ->where('id',10)
    ->select();
    //第四种方法
    $db=db('data');
    $res=$db->where('id',22)->select();
    // 4.删除记录
    // 第一种方法
    $res=Db::execute('delete from think_data where id=3');
    // 第二种方法
    Db::table('think_data')
    ->where('id',20)
    ->delete();
    // 第三种方法
    Db::name('data')
    ->where('id',20)
    ->delete();
    // 第四种方法
    $db=db('data');
    $db->where('id','<',10)
    ->delete();
    //第五种方法
    db('data')->delete(1);
    db('data')->delete([1,2,3]);

    3:闭包查询如何传入变量
    答:普通闭包查询:
    $items = ItemModel::all(function($query){$query->order('sort', 'asc');});

    带参数的闭包查询:
    $items = ItemModel::all(function($query)use($type){$query->where('type',$type)->order('sort', 'asc');});

    通过代码我们可以发现,在ThinkPHP5闭包查询中传参使用的是use传递。

    4:thinkphp5使用什么类进行数据验证
    答:1:namespace appvalidate;
    /**
    * 用户验证类User.php
    */
    use thinkValidate;
    class User extends Validate
    {
    protected $rule = [
    'name' => [
    'require' => 'require',
    'min' => 5,
    'max' => 20,
    ],
    'email' => [
    'require' => 'require',
    'email' => 'email',
    ],
    'pass' => [
    'require' => 'require',
    'min' => 3,
    'max' => 12,
    'alphaNum'=> 'alphaNum',
    ],
    'mobile' => [
    'require' => 'require',
    'mobile' => 'mobile',
    'max' => 12,
    ],
    ];
    }

    ----------

    namespace appindexcontroller;
    use thinkController;
    use appvalidateUser;
    //Demo9.php
    class Demo9 extends Controller
    {

    public function test()
    {
    # 要验证的数据
    $data = [
    'name' => 'Sam567',
    'email' => 'sam@163.com',
    'pass' => 'd123456ok',
    'mobile' => '18521311599'
    ];
    $validate = new User;
    if(!$validate->check($data)){
    return $validate->getError();
    }
    return '验证通过!';

    }
    }

    5:如何进行分页参数的传递
    答:paginate(15,false,[‘query’=>request()->param() ]);
    说明:paginate有3个参数,第一个是一页显示的数量;第二个是简单模式或传入总计入数,默认为true,
    简单模式为false;第三个就是分页传入的参数.
    $name = input('get.searchKey/s');
    if($name != ""){
    $this->assign('searchKey', $name);
    $map['name'] = ['like','%'.$name.'%'];
    }
    $map['delete'] = 0;
    $list = Db::name('goods')->where($map)
    ->paginate(10,false,['query'=>request()->param()]);
    $this->assign('list', $list)
    ->assign('empty', '<td colspan="9">暂无数据</td>');
    return $this->fetch();

    四;综合题
    1:如何在网页之间传递变量
    答:POST传值:
    post传值是用于html的<form>表单跳转的方法,很方便使用。例如:
    <html>
    <form action='' method=''>
    <input type='text' name='name1'>
    <input type='hidden' name='name2' value='value'>
    <input type='submit' value='提交'>
    </form>
    </html>

    form中的action填入的是跳转页面的url路径,method填入post方法。form表单中的提交按钮按下后,
    就会把form中有name的内容都传到填入的url中,可以通过$_POST['name']获取,例如:
    $a=$_POST['name1'];
    $b=$_POST['name2'];

    这里有个很方便的小技巧,在input标签中把type选为'hidden'时,这个input标签会隐藏起来,不在页
    面显示,但这input标签在 form中,并且有name值和value值,同样会跟随提交按钮传递过去,这种隐藏
    标签可以传递一些不想显示出来的内容

    GET传值:
    GET传值是通过跟随url传递的,在页面跳转时,跟着url跳转。常用于<a>标签的使用。例如:
    <a href='delete.php?id=value'>点我跳转</a>
    跳转进入xxx.php后,就能通过$_GET['id']获取传递的值。GET方法常用于URL的目的是删除或读取某个id的php文件。

    SESSION传值:
    SESSION是全局变量的一种,经常用于用户登陆后保存用户id之类的常用数据。一旦保存到SESSION中,其他页面都可以
    通过SESSION获取,SESSION的使用要开启session:
    //session赋值
    session_start();
    $_SESSION['one']=value1;
    $_SESSION['two']=value2;

    //session值的读取:
    $one = $_SESSION['one'];

    //session值的销毁
    unset($_SESSION['one']);

    2:API接口中,针对数据传输安全性都有哪些做法
    答:1、当用户登录APP时,使用https协议调用后台相关接口,服务器端根据用户名和密码时生成一个access_key,并将
    access_key保存在session中,将生成的access_key和session_id返回给APP端。

    2、APP端将接收到的access_key和session_id保存起来

    3、当APP端调用接口传输数据时,将所传数据和access_key使用加密算法生成签名signature,并将signature和
    session_id一起发送给服务器端。

    4、服务器端接收到数据时,使用session_id从session中获取对应的access_key,将access_key和接收到的数
    据使用同一加密算法生成对应signature,如果生成的签名和接收到的signature相同时,则表明数据合法



  • 相关阅读:
    Web服务器推送技术【转】
    [转]vs2010 中文版下载地址及可用CDKEY
    [php] sae上的一个应用框架申请通过了
    [linux] ssh WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! 问题解决
    [php] 调试利器
    [javascript] 邮箱&&电话正则
    [erlang] Erlang比较运算符 (Term Comparisons)
    [vim] gvim 折行
    [linux] 查看内存型号
    [linux] mtu查看&&设置
  • 原文地址:https://www.cnblogs.com/sunny20/p/11355253.html
Copyright © 2011-2022 走看看