zoukankan      html  css  js  c++  java
  • Perl 笔记

    Perl 学习

    目录

    常用记录

    chop与chomp的区别

    • chop()函数,只是去除最后一个字符
    • chomp()函数,就要首先判断最后一个字符是不是为" ",他才去除。

    变量初值

    对于没有赋初值的scaler,默认初始为空,但若参与运算,则当作0处理。

    $x = $ff + 1;
    print("$x
    ");
    print("$ff
    ");
    1
    
    

    这里没有对ff定义,但x的值print出来是1,说明ff在参与运算时是0;但运算后其值并没有改变,还是空。

    多行注释

    =pod
    codes to comment
    =cut
    

    正则匹配注意事项

    1. =~ 匹配时,等号和~符号之间不能有空格!,否则输出一串怪异数字。

    lc和uc

    $side = uc $attrs[0];  把attrs[0]转换成大写,然后给side变量赋值。
    $gender = lc $attrs[1]; 把attrs[1]转换成小写,然后给gender赋值。
    

    split 产生空元素

    在按照/ /或者/s+/来split字符串时,常会遇到莫名其妙多出来一个空元素的问题。

    这是因为如果字符串开头就是空格,split会把开头的前导空白符(一个空字符)也算作一个元素。

    如果要按照空格来split,有几种方法:

    1. split ' '或者直接用默认形式split,不加任何东西
      • split ' '是split的特殊情况,该格式是模拟awk的默认行为,所以在分割行为开始之前,会把字符串中的前导空格全部删除,然后再使用split /s+/处理。
    2. 删除前导空白符,再用split(/s+/,$_);
      • $_ =~ s/^s+//; ## 丢弃前导空白符 $_ =~ s/s+$//; ## 丢弃末尾空白符

    基础

    1. 运行perl

    • 文件顶部加 #!/usr/bin/perl
    • 加权限 chmod u+x file.pl 或者chmod 0755 file.pl
    • ./file.pl

    2. 字符串

    字符串连接符

    $str = "hello" . "world";       # 字符串连接
    $num = 5 + 10;                  # 两数相加
    $mix = $str . $num;             # 连接字符串和数字
    

    转义符

    转义字符 含义
    水平制表符(4空格)
    u 强制下一个字符为大写
    l 强制下一个字符为小写
    U 强制将所有字符转换为大写
    L 强制将所有的字符转换为小写
    Q 将到E为止的非单词(non-word)字符加上反斜线
    E 结束L、U、Q

    qq

    • qq(string in qq):相当于”string in qq”
    • q(string in q): 相当于’string in q’
    • ( )可换为<>,{ },[ ]等配对字符 qq{string in qq};

    here文档

    示例:

    #!/usr/bin/perl
     
    $a = 10;
    $var = <<"EOF";
    这是一个 Here 文档实例,使用双引号。
    可以在这输如字符串和变量。
    例如:a = $a
    EOF
    print "$var
    ";
    

    输出:

    这是一个 Here 文档实例,使用双引号。
    可以在这输如字符串和变量。
    例如:a = 10
    

    $a=<< “EOF” 的意思就是说:下一行开始,直到遇见“EOF”为止,所有的字符都按照指定的格式存入变量a中。你可以用EEE,MAMA等等其他的名字都可以。

    多行字符串

    #!/usr/bin/perl
    $string = '
    菜鸟教程
        —— 学的不仅是技术,更是梦想!
    ';
    print "$string
    ";
    

    也可以用here文档

    特殊字符

    # __FILE__, __LINE__, 和 __PACKAGE__ 分别表示当前执行脚本的文件名,行号,包名
    文件名 test.pl
    行号 4
    包名 main
    

    v 字符串

    一个以 v 开头,后面跟着一个或多个用句点分隔的整数,会被当作一个字串文本。将整数转换为对应的ASCII码字符。

    $foo    = v102.111.111; # 代表foo
    $martin = v77.97.114.116.105.110; # 代表Martin
    

    3. 变量

    1. 标量:$myfirst=123; 

    2. 数组:@arr=(1,2,3) ,索引用$arr[0]

    3. 哈希:%h=('a'=>1,'b'=>2); 索引用$h{'a'}

    数据类型
    1. 整形:$x = 12345; 实际是浮点数的特例
    • 8 进制和 16 进制数:8 进制以 0 开始,16 进制以 0x 开始。
    1. 浮点数:11.4 、 -0.3 、.3 、 3. 、 54.1e+02 、 5.41e03。

      • 浮点寄存器通常不能精确地存贮浮点数,从而产生误差,在运算和比较中要特别注意。指数的范围通常为 -309 到 +308
    2. 字符串: 见上面

    变量上下文

    Perl 解释器会根据上下文来决定变量的类型。实例如下:

    #!/usr/bin/perl
     
    @names = ('google', 'runoob', 'taobao');
     
    @copy = @names;   # 复制数组
    $size = @names;   # 数组赋值给标量,返回数组元素个数
    
    数组
    创建数组:括号或者qw
    @array = (1, 2, 'Hello');
    @array = qw/这是 一个 数组/;
    @days = qw/google #qw支持多行定义数组
    taobao
    runoob/;
    # 可以按索引给数组赋值
    $array[6] = 'Sunday'; # 允许索引超出定义的长度
    
    数组序列号
    @var_10 = (1..10);
    @var_20 = (10..20);
    @var_abc = (a..z);
     
    print "@var_10
    ";   # 输出 1 到 10
    print "@var_20
    ";   # 输出 10 到 20
    print "@var_abc
    ";  # 输出 a 到 z
    
    数组大小

    $size = @array;

    返回的是数组长度,不是元素个数。

    添加和删除数组元素
    序号 类型和描述
    1 push @ARRAY, LIST 将列表的值放到数组的末尾
    2 pop @ARRAY 删除数组的最后一个值
    3 shift @ARRAY 弹出数组第一个值,并返回它。数组的索引值也依次减一。
    4 unshift @ARRAY, LIST 将列表放在数组前面,并返回新数组的元素个数。
    #!/usr/bin/perl
    # 创建一个简单是数组
    @sites = ("google","runoob","taobao");
    $new_size = @sites ;
    print "1. @sites  = @sites
    "."原数组长度 :$new_size
    ";
    # 在数组结尾添加一个元素
    $new_size = push(@sites, "baidu");
    print "2. @sites  = @sites
    "."新数组长度 :$new_size
    ";
     
    # 在数组开头添加一个元素
    $new_size = unshift(@sites, "weibo");
    print "3. @sites  = @sites
    "."新数组长度 :$new_size
    ";
     
    # 删除数组末尾的元素
    $new_byte = pop(@sites);
    print "4. @sites  = @sites
    "."弹出元素为 :$new_byte
    ";
     
    # 移除数组开头的元素
    $new_byte = shift(@sites);
    print "5. @sites  = @sites
    "."弹出元素为 :$new_byte
    ";
    
    切割索引
    #!/usr/bin/perl
    @sites = qw/google taobao runoob weibo qq facebook 网易/;
    @sites2 = @sites[3,4,5];
    # 输出:weibo qq facebook
    # 连续索引也可以用..:
    @sites2 = @sites[3..5];
    
    替换数组元素

    Perl 中数组元素替换使用 splice() 函数:

    splice @ARRAY, OFFSET [ , LENGTH [ , LIST ] ]

    @nums = (1..20);
    print "替换前 - @nums
    ";
    #从第6个元素开始替换数组中的5个元素:
    splice(@nums, 5, 5, 21..25); 
    print "替换后 - @nums
    ";
    
    #替换前 - 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
    #替换后 - 1 2 3 4 5 21 22 23 24 25 11 12 13 14 15 16 17 18 19 20
    
    将字符串转换为数组

    split [ PATTERN [ , EXPR [ , LIMIT ] ] ]

    • PATTERN:分隔符,默认为空格。
    • EXPR:指定字符串数。
    • LIMIT:如果指定该参数,则返回该数组的元素个数。
    # 定义字符串
    $var_test = "runoob";
    $var_string = "www-runoob-com";
    $var_names = "google,taobao,runoob,weibo";
     
    # 字符串转为数组
    @test = split('', $var_test);
    @string = split('-', $var_string);
    @names  = split(',', $var_names);
     
    print "$test[3]
    ";  # 输出 o
    print "$string[2]
    ";  # 输出 com
    print "$names[3]
    ";   # 输出 weibo
    
    将数组转换为字符串

    Perl 中将数组转换为字符串使用 join() 函数,语法格式如下:

    • join EXPR, LIST
    # 数组转为字符串
    $string1 = join( '-', @string );
    $string2 = join( ',', @names );
    
    数组排序

    Perl 中数组排序使用 sort() 函数,语法格式如下:

    • sort [ SUBROUTINE ] LIST
    # 定义数组
    @sites = qw(google taobao runoob facebook);
    print "排序前: @sites
    ";
     
    # 对数组进行排序
    @sites = sort(@sites);
    # 排序后: facebook google runoob taobao
    
    特殊变量: $[

    特殊变量 ([** 表示数组的第一索引值,一般都为 0 ,如果我们将 **)[ 设置为 1,则数组的第一个索引值即为 1,第二个为 2,以此类推。不推荐,新版中已经被废弃

    合并数组
    @numbers = (1,3,(4,5,6)); 
    print "numbers = @numbers
    "; #numbers = 1 3 4 5 6
    
    哈希
    创建hash
    1. $data{'google'} = 'google.com';

    2. 通过列表设置, 列表中第一个元素为 key,第二个为 value。

    %data = ('google', 'google.com', 'runoob', 'runoob.com', 'taobao', 'taobao.com');
    
    %data = ('google'=>'google.com', 'runoob'=>'runoob.com', 'taobao'=>'taobao.com');
    

    或者用-代替'':

    %data = (-google=>'google.com', -runoob=>'runoob.com', -taobao=>'taobao.com');
    

    但这种方式的key不能出现空格,读取时:

    $val = $data{-google}
    $val = $data{-runoob}
    
    读取哈希值

    @array = @data{-taobao, -runoob};

    读取所有key

    @names = keys %data;

    读取所有value

    @urls = values %data;

    检测元素是否存在

    如果你在哈希中读取不存在的 key/value 对 ,会返回 undefined 值,且在执行时会有警告提醒。为了避免这种情况,我们可以使用 exists 函数来判断key是否存在

    if( exists($data{'facebook'} ) ){}
    else{}
    
    哈希中添加或删除元素
    # 添加元素
    $data{'facebook'} = 'facebook.com';
    # 删除哈希中的元素
    delete $data{'taobao'};
    

    4. 条件

    1. if() {} elsif {} else {}

    2. use Switch;
      $var = 10;
      @array = (10, 20, 30);
      %hash = ('key1' => 10, 'key2' => 20);
      switch($var){
         case 10    { print "数字 10
      " }
         case "a"   { print "字符串 a" }
         case [1..10] { print "数字在列表中" }
         case (@array){ print "数字在数组中" }
         case (\%hash) { print "在哈希中" }
         else        { print "没有匹配的条件" }
      }
      

      其中@array 和%hash可以判断var的值是否在列表或者hash内。

      另外,还可以在case中加上next, case还会匹配下面其他的是否满足:

      2. case 10    { print "数字 10
      "; next; }  # 匹配后继续执行
      
    3. 三元运算符

      Exp1 ? Exp2 : Exp3;

      $status = ($favorite > 60 )? "热门网站" : "不是热门网站";
      

    5. 循环

    while () {}

    条件为真时循环

    until () {}

    条件为假时循环

    for( ; ; )

    for( $a = 0; $a < 10; $a = $a + 1 ){
        print "a 的值为: $a
    ";
    }
    

    foreach

    循环列表或者集合变量值

    @list = (2, 12, 36, 42, 51);
    # 执行foreach 循环
    foreach $a (@list){
        print "a 的值为: $a
    ";
    }
    

    do...while...

    do{
       statement(s);
    }while( condition );
    

    循环控制

    next

    next 语句用于停止执行从next语句的下一语句开始到循环体结束标识符之间的语句,转去执行continue语句块,然后再返回到循环体的起始处开始执行下一次循环。

    $a = 10;
    while( $a < 20 ){
       if( $a == 15)
       {
           # 跳出迭代
           $a = $a + 1;
           next;
       }
       print "a 的值为: $a
    ";
       $a = $a + 1;
    }
    # 输出结果会跳过15
    
    last

    Perl last 语句用于退出循环语句块,从而结束循环,last语句之后的语句不再执行,continue语句块也不再执行。

    continue

    Perl continue 块通常在条件语句再次判断前执行。continue 语句可用在 while 和 foreach 循环中。

    $a = 0;
    while($a < 3){
       print "a = $a
    ";
    }continue{
       $a = $a + 1;
    }
    # 输出
    a = 0
    a = 1
    a = 2
    
    @list = (1, 2, 3, 4, 5);
    foreach $a (@list){
       print "a = $a
    ";
    }continue{
       last if $a == 4;
    }
    # 输出
    a = 1
    a = 2
    a = 3
    a = 4
    
    redo

    Perl redo 语句直接转到循环体的第一行开始重复执行本次循环,redo语句之后的语句不再执行,continue语句块也不再执行。

    $a = 0;
    while($a < 10){
       if( $a == 5 ){
          $a = $a + 1;
          redo;
       }
       print "a = $a
    ";
    }continue{
       $a = $a + 1;
    }
    # 输出
    a = 0
    a = 1
    a = 2
    a = 3
    a = 4
    a = 6
    a = 7
    a = 8
    a = 9
    
    goto

    Perl 有三种 goto 形式:got LABLE,goto EXPR,和 goto &NAME.

    goto label:

    $a = 10;
    LOOP:do
    {
        if( $a == 15){
           # 跳过迭代
           $a = $a + 1;
           # 使用 goto LABEL 形式
           print "跳出输出 
    ";
           goto LOOP;
           print "这一句不会被执行 
    ";
        }
        print "a = $a
    ";
        $a = $a + 1;
    }while( $a < 20 );
    

    goto expr:

    $a = 10;
    $str1 = "LO";
    $str2 = "OP";
     
    LOOP:do
    {
        if( $a == 15){
           # 跳过迭代
           $a = $a + 1;
           # 使用 goto EXPR 形式
           goto $str1.$str2;    # 类似 goto LOOP
        }
        print "a = $a
    ";
        $a = $a + 1;
    }while( $a < 20 );
    
    

    6. 运算符

    基本运算符

    数字的比较: $a=10,$b=20

    运算符 描述 实例
    <=> 检查两个操作数的值是否相等, 如果左边的数小于右边的数返回 -1,如果相等返回 0, 如果左边的数大于右边的数返回 1 。 ($a <=> $b) 返回 -1。

    字符串比较: $a="abc", $b = "xyz"

    运算符 描述 实例
    lt 检查左边的字符串是否小于右边的字符串,如果是返回 true,否则返回 false。 ($a lt $b) 返回 true。
    gt 检查左边的字符串是否大于右边的字符串,如果是返回 true,否则返回 false。
    le 检查左边的字符串是否小于或等于右边的字符串,如果是返回 true,否则返回 false。 ($a le ​$b) 返回 true
    ge 检查左边的字符串是否大于或等于右边的字符串,如果是返回 true,否则返回 false。 ($a ge ​$b) 返回 false。
    eq 检查左边的字符串是否等于右边的字符串,如果是返回 true,否则返回 false。 ($a eq ​$b) 返回 false。
    ne 检查左边的字符串是否不等于右边的字符串,如果是返回 true,否则返回 false。 ($a ne ​$b) 返回 true
    cmp 如果左边的字符串大于右边的字符串返回 1,如果相等返回 0,如果左边的字符串小于右边的字符串返回 -1。 ($a cmp $b) 返回 -1。

    支持:

    • +=
    • 位运算符
    • 逻辑运算符,and ,&& ,or ,|| ,not

    引号运算符

    运算符 描述 实例
    q{ } 为字符串添加单引号 q{abcd} 结果为 'abcd'
    qq{ } 为字符串添加双引号 qq{abcd} 结果为 "abcd"
    qx{ } 为字符串添加反引号 qx{abcd} 结果为 abcd

    其他运算符

    运算符 描述 实例
    . 点号 (.) 用于连接两个字符串。 如果 $a="run", $b="oob" , (a.)b 结果为 "runoob"
    x x 运算符返回字符串重复的次数。 ('-' x 3) 输出为 ---。
    .. .. 为范围运算符。 (2..5) 输出结果为 (2, 3, 4, 5)
    ++ 自增运算符,整数值增加 1 $a =10, $a++ will 输出为 11
    -- 自减运算符,整数值减少 1 $a =10, $a-- 输出为 9
    -> 箭号用于指定一个类的方法 (obj->)a 表示对象 $obj 的 $a 方法。

    运算符优先级

    运算符符 结合性
    ++, --
    -, ~, ! 从右到左
    ** 从右到左
    =~, !~ 从左到右
    *, /, %, x 从左到右
    +, -, . 从左到右
    <<, >> 从左到右
    -e, -r,
    <, <=, >, >=, lt, le, gt, ge 从左到右
    ==, !=, <=>, eq, ne, cmp 从左到右
    & 从左到右
    |, ^ 从左到右
    && 从左到右
    || 从左到右
    .. 从左到右
    ? and : 从右到左
    =, +=, -=, *=, 从右到左
    其他
    , 从左到右
    not 从左到右
    and 从左到右
    or, xor 从左到右

    7. 时间日期

    Perl中处理时间的函数有如下几种:

    • 1、time() 函数:返回从1970年1月1日起累计的秒数
    • 2、localtime() 函数:获取本地时区时间
    • 3、gmtime() 函数: 获取格林威治时间
    @months = qw( 一月 二月 三月 四月 五月 六月 七月 八月 九月 十月 十一月 十二月 );
    @days = qw(星期天 星期一 星期二 星期三 星期四 星期五 星期六);
     
    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
    print "$mday $months[$mon] $days[$wday]
    ";
    #输出
    12 六月 星期天
    
    $datestring = localtime();
    print "时间日期为:$datestring
    ";
    #输出
    时间日期为:Sun Jun 12 11:27:31 2016
    

    求运行时间

    新纪元时间(Epoch Time)

    我们可以使用 time() 函数来获取新纪元时间,该函数返回从1970年1月1日起累计的秒数。

    一般可以用来求程序运行的时间。

    $epoc = time();
    ...
    $epoc1 = time();
    $run_time = $epoc1-$epoc;
    

    POSIX 函数 strftime()

    函数 strftime() 可以将时间格式化为我们想要的格式。

    use POSIX qw(strftime);
     
    $datestring = strftime "%Y-%m-%d %H:%M:%S", localtime;
    printf("时间日期 - $datestring
    ");
     
    #  GMT 格式化时间日期
    $datestring = strftime "%Y-%m-%d %H:%M:%S", gmtime;
    printf("时间日期 - $datestring
    ");
    
    时间日期 - 2016-06-12 12:15:13
    时间日期 - 2016-06-12 04:15:13
    

    8. 子程序(函数)

    子程序传参

    Perl 子程序可以和其他编程一样接受多个参数,子程序参数使用特殊数组 @_ 标明。因此子程序第一个参数为 $_[0], 第二个参数为 ​$_[1], 以此类推。

     
    # 定义求平均值函数
    sub Average{
       # 获取所有传入的参数
       $n = scalar(@_);
       $sum = 0;
     
       foreach $item (@_){
          $sum += $item;
       }
       $average = $sum / $n;
       print '传入的参数为 : ',"@_
    ";           # 打印整个数组
       print "第一个参数值为 : $_[0]
    ";         # 打印第一个参数
       print "传入参数的平均值为 : $average
    ";  # 打印平均值
    }
     
    # 调用函数
    Average(10, 20, 30);
    

    向函数传递列表

    # 定义函数
    sub PrintList{
       my @list = @_;
       print "列表为 : @list
    ";
    }
    $a = 10;
    @b = (1, 2, 3, 4);
     
    # 列表参数
    PrintList($a, @b);
    
    列表为 : 10 1 2 3 4
    

    相函数传递哈希

    # 方法定义
    sub PrintHash{
       my (%hash) = @_;
     
       foreach my $key ( keys %hash ){
          my $value = $hash{$key};
          print "$key : $value
    ";
       }
    }
    %hash = ('name' => 'runoob', 'age' => 3);
     
    # 传递哈希
    PrintHash(%hash);
    

    子程序返回值

    如果没有使用 return 语句,则子程序的最后一行语句将作为返回值。

    # 方法定义
    sub add_a_b{
       # 不使用 return
       $_[0]+$_[1];  
     
       # 使用 return
       # return $_[0]+$_[1];  
    }
    print add_a_b(1, 2)
    

    子程序的私有变量

    • 默认情况下,Perl 中所有的变量都是全局变量,这就是说变量在程序的任何地方都可以调用。如果我们需要设置私有变量,可以使用 my 操作符来设置。

    • my 操作符用于创建词法作用域变量,通过 my 创建的变量,存活于声明开始的地方,直到闭合作用域的结尾。

    • 闭合作用域指的可以是一对花括号中的区域,可以是一个文件,也可以是一个 if, while, for, foreach, eval字符串。

    # 全局变量
    $string = "Hello, World!";
     
    # 函数定义
    sub PrintHello{
       # PrintHello 函数的私有变量
       my $string;
       $string = "Hello, Runoob!";
       print "函数内字符串:$string
    ";
    }
    # 调用函数
    PrintHello();
    print "函数外字符串:$string
    ";
    
    函数内字符串:Hello, Runoob!
    函数外字符串:Hello, World!
    

    变量的临时赋值

    我们可以使用 local 为全局变量提供临时的值,在退出作用域后将原来的值还回去。local 定义的变量不存在于主程序中,但存在于该子程序和该子程序调用的子程序中。定义时可以给其赋值

    # 全局变量
    $string = "Hello, World!";
     
    sub PrintRunoob{
       # PrintHello 函数私有变量
       local $string;
       $string = "Hello, Runoob!";
       # 子程序调用的子程序
       PrintMe();
       print "PrintRunoob 函数内字符串值:$string
    ";
    }
    sub PrintMe{
       print "PrintMe 函数内字符串值:$string
    ";
    }
     
    sub PrintHello{
       print "PrintHello 函数内字符串值:$string
    ";
    }
     
    # 函数调用
    PrintRunoob();
    PrintHello();
    print "函数外部字符串值:$string
    ";
    
    PrintMe 函数内字符串值:Hello, Runoob!
    PrintRunoob 函数内字符串值:Hello, Runoob!
    PrintHello 函数内字符串值:Hello, World!
    函数外部字符串值:Hello, World!
    

    静态变量

    use feature 'state';
     
    sub PrintCount{
       state $count = 0; # 初始化变量
     
       print "counter 值为:$count
    ";
       $count++;
    }
     
    for (1..5){
       PrintCount();
    }
    
    counter 值为:0
    counter 值为:1
    counter 值为:2
    counter 值为:3
    counter 值为:4
    

    注1:state仅能创建闭合作用域为子程序内部的变量。

    注2:state是从Perl 5.9.4开始引入的,所以使用前必须加上 use。

    注3:state可以声明标量、数组、哈希。但在声明数组和哈希时,不能对其初始化(至少Perl 5.14不支持)。

    子程序调用上下文

    子程序调用过程中,会根据上下文来返回不同类型的值,比如以下 localtime() 子程序,在标量上下文返回字符串,在列表上下文返回列表:

    # 标量上下文
    my $datestring = localtime( time );
    print $datestring;
     
    print "
    ";
     
    # 列表上下文
    ($sec,$min,$hour,$mday,$mon, $year,$wday,$yday,$isdst) = localtime(time);
    printf("%d-%d-%d %d:%d:%d",$year+1990,$mon+1,$mday,$hour,$min,$sec);
     
    print "
    ";
    
    Sun Jun 12 15:58:09 2016
    2106-6-12 15:58:9
    

    my 和 local 的区别

    内部 -> 外部:

    • (1)my 和 local 都只在一个 block 里有效,出去就失效;
    • (2)但是 local 的变量可以继续在这个 block 中调用的子程序中存在;
    • (3)如果有与外界同名的变量,两者在 block 退出后都不影响外界同名变量;

    外部 -> 内部:

    (1)外部设置 my、local、缺省均队内有效,但是同名变量外部 my,在 block 内部 local 是不允许的。因为二者在 block 中调用的子程序中均有效,会冲突。

    (2)如果在一个 block 中有一个 my 修饰的变量和外界的一个变量同名,而且又需要在这个 block 中使用外界变量时,两个办法:

    • 第一个办法,用 main 的 package 修饰这个变量名 $main::global。
    • 第二个办法,用 our 修饰 our $global,那么该 block 中接下来出现的所有 $global 都是外界的 global。

    (3)编写脚本时,注意作用域,防止外部影响内部。

    9. 引用

    引用就是指针,Perl 引用是一个标量类型可以指向变量、数组、哈希表(也叫关联数组)甚至子程序,可以应用在程序的任何地方。

    定义变量的时候,在变量名前面加个,就得到了这个变量的一个引用

    $scalarref = $foo;     # 标量变量引用
    $arrayref  = @ARGV;    # 列表的引用
    $hashref   = \%ENV;     # 哈希的引用
    $coderef   = &handler; # 子过程引用
    $globref   = *foo;     # GLOB句柄引用
    

    在数组中我们可以用匿名数组引用,使用 [] 定义:

    $aref= [ 1,"foo",undef,13 ];
    

    匿名数组的元素仍然可以是匿名数组,所以我们可以用这种方法构造数组的数组,可以构造任意维度的数组。

    my $aref = [
            [1, 2, 3],
            [4, 5, 6],
            [7, 8, 9],
    ]
    

    访问的时候用引用访问:

    $aref->[0]->[0] 或者

    $aref->[0][0]"

    哈希中我们可以用匿名哈希引用,使用 {} 定义:

    $href= { APR =>4, AUG =>8 };
    

    我们也可以创建一个没有子程序名的匿名子程序引用:

    $coderef = sub { print "Runoob!
    " };
    

    取消引用

    取消引用可以根据不同的类型使用 $, @ 或 % 来取消,实例如下:

    $var = 10;
     
    # $r 引用 $var 标量
    $r = $var;
     
    # 输出本地存储的 $r 的变量值
    print "$var 为 : ", $$r, "
    ";
     
    @var = (1, 2, 3);
    # $r 引用  @var 数组
    $r = @var;
    # 输出本地存储的 $r 的变量值
    print "@var 为: ",  @$r, "
    ";
     
    %var = ('key1' => 10, 'key2' => 20);
    # $r 引用  %var 数组
    $r = \%var;
    # 输出本地存储的 $r 的变量值
    print "\%var 为 : ", %$r, "
    ";
    
    10 为 : 10
    1 2 3 为: 123
    \%var 为 : key110key220
    

    判断变量类型

    ref 来判断: ref($r)

    引用函数

    • 函数引用格式: &

    • 调用引用函数格式: & + 创建的引用名。

    # 函数定义
    sub PrintHash{
       my (%hash) = @_;
       
       foreach $item (%hash){
          print "元素 : $item
    ";
       }
    }
    %hash = ('name' => 'runoob', 'age' => 3);
     
    # 创建函数的引用
    $cref = &PrintHash;
     
    # 使用引用调用函数
    &$cref(%hash);
    

    10. 格式化输出

    Perl 中可以使用 format 来定义一个模板,然后使用 write 按指定模板输出数据。

    format FormatName =
    fieldline
    value_one, value_two, value_three
    fieldline
    value_one, value_two
    .
    

    参数解析:

    • FormatName :格式化名称。
    • fieldline :一个格式行,用来定义一个输出行的格式,类似 @,^,<,>,| 这样的字符。
    • value_one,value_two…… :数据行,用来向前面的格式行中插入值,都是perl的变量。
    • . :结束符号。
    $text = "google runoob taobao";
    format STDOUT =
    first: ^<<<<<  # 左边对齐,字符长度为6
        $text
    second: ^<<<<< # 左边对齐,字符长度为6
        $text
    third: ^<<<< # 左边对齐,字符长度为5,taobao 最后一个 o 被截断
        $text  
    .
    write
    
    first: google
    second: runoob
    third: taoba
    
    • 格式行以 @ 或者 ^ 开头,这些行不作任何形式的变量代换。
    • @ 字段(不要同数组符号 @ 相混淆)是普通的字段。
    • @,^ 后的 <, >,| 长度决定了字段的长度,如果变量超出定义的长度,那么它将被截断。
    • <, >,| 还分别表示,左对齐,右对齐,居中对齐。
    • ^ 字段用于多行文本块填充。
    格式 值域含义
    @<<< 左对齐输出
    @>>> 右对齐输出
    @||| 中对齐输出
    @##.## 固定精度数字
    @* 多行文本

    在上表中,除了多行值域@*,域宽都等于其指定的包含字符@在内的字符个数

    format EMPLOYEE =
    ===================================
    @<<<<<<<<<<<<<<<<<<<<<< @<< 
    $name, $age
    @#####.##
    $salary
    ===================================
    .
     
    select(STDOUT);
    $~ = EMPLOYEE;
     
    @n = ("Ali", "Runoob", "Jaffer");
    @a  = (20,30, 40);
    @s = (2000.00, 2500.00, 4000.000);
     
    $i = 0;
    foreach (@n){
        $name = $_;
        $age = $a[$i];
        $salary = $s[$i++];
        write;
    }
    
    ===================================
    Ali                     20
      2000.00
    ===================================
    ===================================
    Runoob                  30
      2500.00
    ===================================
    ===================================
    Jaffer                  40
      4000.00
    ===================================
    

    格式变量

    • $~ ($FORMAT_NAME) :格式名字 (^ ()FORMAT_TOP_NAME) :当前的表头格式名字存储在
    • $% ($FORMAT_PAGE_NUMBER) :当前输出的页号
    • $= ($FORMAT_LINES_PER_PAGE) :每页中的行数
    • $| ($FORMAT_AUTOFLUSH) :是否自动刷新输出缓冲区存储
    • $^L ($FORMAT_FORMFEED) :在每一页(除了第一页)表头之前需要输出的字符串存储在
    $~ = "MYFORMAT"; # 指定默认文件变量下所使用的格式
    write;           # 输出 $~ 所指定的格式
     
    format MYFORMAT = # 定义格式 MYFORMAT 
    =================================
          Text # 菜鸟教程
    =================================
    .
    write;
    
    =================================
          Text # 菜鸟教程
    =================================
    =================================
          Text # 菜鸟教程
    =================================
    

    如果不指定$~的情况下,会输出名为STDOUT的格式:

    write;         # 不指定$~的情况下会寻找名为STDOUT的格式
     
    format STDOUT =
    ~用~号指定的文字不会被输出
    ----------------
      STDOUT格式
    ----------------
    .
    

    如果STDOUT也没有,会报错。

    此外我们通过添加报表头部信息来演示 $^ 或 $FORMAT_TOP_NAME 变量的使用

    format EMPLOYEE =
    ===================================
    @<<<<<<<<<<<<<<<<<<<<<< @<< 
    $name, $age
    @#####.##
    $salary
    ===================================
    .
     
    format EMPLOYEE_TOP =
    ===================================
    Name                    Age
    ===================================
    .
     
    select(STDOUT);
    $~ = EMPLOYEE;
    $^ = EMPLOYEE_TOP;
     
    @n = ("Ali", "Runoob", "Jaffer");
    @a  = (20,30, 40);
    @s = (2000.00, 2500.00, 4000.000);
     
    $i = 0;
    foreach (@n){
       $name = $_;
       $age = $a[$i];
       $salary = $s[$i++];
       write;
    }
    
    ===================================
    Name                    Age
    ===================================
    ===================================
    Ali                     20
      2000.00
    ===================================
    ===================================
    Runoob                  30
      2500.00
    ===================================
    ===================================
    Jaffer                  40
      4000.00
    ===================================
    

    也可以使用 $% 或$FORMAT_PAGE_NUMBER 为报表设置分页:

    # 添加分页 $% 
    format EMPLOYEE_TOP =
    ===================================
    Name                    Age Page @<
                                     $%
    =================================== 
    .
    
    ===================================
    Name                    Age Page 1
    ===================================
    

    输出到文件

    默认情况下函数write将结果输出到标准输出文件STDOUT,我们也可以使它将结果输出到任意其它的文件中。最简单的方法就是把文件变量作为参数传递给write,如:

    write(MYFILE);
    

    但是这样就不能用$~变量来改变所使用的打印格式。系统变量$~只对默认文件变量起作用,

    if (open(MYFILE, ">tmp")) {
    $~ = "MYFORMAT";
    write MYFILE; # 含文件变量的输出,此时会打印与变量同名的格式,即MYFILE。$~里指定的值被忽略。
     
    format MYFILE = # 与文件变量同名 
    =================================
          输入到文件中
    =================================
    .
    close MYFILE;
    }
    

    我们可以使用select改变默认文件变量时,它返回当前默认文件变量的内部表示,这样我们就可以创建子程序,按自己的想法输出,又不影响程序的其它部分。

    if (open(MYFILE, ">>tmp")) {
    select (MYFILE); # 使得默认文件变量的打印输出到MYFILE中
    $~ = "OTHER";
    write;           # 默认文件变量,打印到select指定的文件中,必使用$~指定的格式 OTHER
     
    format OTHER =
    =================================
      使用定义的格式输入到文件中
    =================================
    . 
    close MYFILE;
    }
    
    

    11. 文件操作

    open(DATA, "<file.txt") or die "file.txt 文件无法打开, $!";
     
    while(<DATA>){
       print "$_";
    }
    

    <表示只读方式。

    模式 描述
    < 或 r 只读方式打开,将文件指针指向文件头。
    > 或 w 写入方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。
    >> 或 a 写入方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
    +< 或 r+ 读写方式打开,将文件指针指向文件头。
    +> 或 w+ 读写方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。
    +>> 或 a+ 读写方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。

    Sysopen函数

    sysopen 函数类似于 open 函数,只是它们的参数形式不一样。

    <FILEHANDL> 操作符

    我们使用 <FILEHANDLE> 操作符时,它会返回文件句柄中每一行的列表,例如我们可以导入所有的行到数组中。

    读取 import.txt 并将每一行放到 @lines 数组中:

    open(DATA,"<import.txt") or die "无法打开数据";
    @lines = <DATA>; #<>操作符
    print @lines;    # 输出数组内容
    close(DATA);
    

    getc 函数

    getc 函数从指定的 FILEHANDLE 返回单一的字符,如果没指定返回 STDIN。如果发生错误,或在文件句柄在文件末尾,则返回 undef。

    read 函数

    read 函数用于从缓冲区的文件句柄读取信息。这个函数用于从文件读取二进制数据

    read FILEHANDLE, SCALAR, LENGTH, OFFSET
    read FILEHANDLE, SCALAR, LENGTH
    

    如果读取成功返回读取的字节数,如果在文件结尾返回 0,如果发生错误返回 undef。

    对于所有从文件句柄中读取信息的函数,在后端主要的写入函数为 print:

    print FILEHANDLE LIST
    print LIST
    print
    

    文件拷贝

     # 只读方式打开文件
    open(DATA1, "<file1.txt");
     
    # 打开新文件并写入
    open(DATA2, ">file2.txt");
     
    # 拷贝数据
    while(<DATA1>)
    {
       print DATA2 $_;
    }
    close( DATA1 );
    close( DATA2 );
    

    文件重命名

    rename ("/usr/runoob/test/file1.txt", "/usr/runoob/test/file2.txt" ); #file1命名为file2
    

    删除文件

    unlink ("/usr/runoob/test/file1.txt");
    

    指定文件读写指针位置

    你可以使用 tell 函数来获取文件的位置,并通过使用 seek 函数来指定文件内的的位置:

    tell 函数用于获取文件读写指针位置:

    tell FILEHANDLE
    

    seek()函数是通过文件句柄来移动文件读写指针的方式来读取或写入文件的,以字节为单位进行读取和写入:

    seek FILEHANDLE, POSITION, WHENCE

    • FILEHANDLE:文件句柄,用于存放一个文件唯一标识符。
    • POSITION:表示文件句柄(读写位置指针)要移动的字节数。
    • WHENCE:表示文件句柄(读写位置指针)开始移动时的起始位置,可以取的值为0、1、2;分别表示文件开头、当前位置和文件尾。
    open(DATA,"<a.txt") or die "出错!";
    #seek DATA,5,0;
    $position = tell DATA;
    print "$position
    ";
    
    while(<DATA>)
    {
        print "$_";
    }
    close (DATA);
    

    输出:0
    dadad

    0表示指针位置。

    如果用seek进行指针偏移:

    open(DATA,"<a.txt") or die "出错!";
    $ss = seek DATA,5,0;
    print "$ss
    ";
    $position = tell DATA;
    print "$position
    ";
    
    while(<DATA>)
    {
        print "$_";
    }
    close (DATA);
    

    输出 1

    ​ 5

    返回1表示seek成功,后面没输出文件内容因为文件里五个字符后没内容了。

    文件信息

    测试文件是否存在,是否可读写等

    常用的是-e,文件或目录是否存在;-d ,是否位目录。

    my $file = "a.txt";
    my (@description, $size);
    if (-e $file)
    {
        push @description, '是一个二进制文件' if (-B _);
        push @description, '是一个socket(套接字)' if (-S _);
        push @description, '是一个文本文件' if (-T _);
        push @description, '是一个特殊块文件' if (-b _);
        push @description, '是一个特殊字符文件' if (-c _);
        push @description, '是一个目录' if (-d _);
        push @description, '文件存在' if (-x _);
        push @description, (($size = -s _)) ? "$size 字节" : '空';
        print "$file 信息:", join(', ',@description),"
    ";
    }
    

    输出:a.txt 信息:是一个文本文件, 6 字节

    文件测试操作符如下表所示:

    操作符 描述
    -A 文件上一次被访问的时间(单位:天)
    -B 是否为二进制文件
    -C 文件的(inode)索引节点修改时间(单位:天)
    -M 文件上一次被修改的时间(单位:天)
    -O 文件被真实的UID所有
    -R 文件或目录可以被真实的UID/GID读取
    -S 为socket(套接字)
    -T 是否为文本文件
    -W 文件或目录可以被真实的UID/GID写入
    -X 文件或目录可以被真实的UID/GID执行
    -b 为block-special (特殊块)文件(如挂载磁盘)
    -c 为character-special (特殊字符)文件(如I/O 设备)
    -d 为目录
    -e 文件或目录名存在
    -f 为普通文件
    -g 文件或目录具有setgid属性
    -k 文件或目录设置了sticky位
    -l 为符号链接
    -o 文件被有效UID所有
    -p 文件是命名管道(FIFO)
    -r 文件可以被有效的UID/GID读取
    -s 文件或目录存在且不为0(返回字节数)
    -t 文件句柄为TTY(系统函数isatty()的返回结果;不能对文件名使用这个测试)
    -u 文件或目录具有setuid属性
    -w 文件可以被有效的UID/GID写入
    -x 文件可以被有效的UID/GID执行
    -z 文件存在,大小为0(目录恒为false),即是否为空文件,

    12. 目录操作

    opendir DIRHANDLE, EXPR  # 打开目录
    readdir DIRHANDLE        # 读取目录
    rewinddir DIRHANDLE      # 定位指针到开头
    telldir DIRHANDLE        # 返回目录的当前位置
    seekdir DIRHANDLE, POS   # 定位指定到目录的 POS 位置
    closedir DIRHANDLE       # 关闭目录
    

    显示所有的文件

    使用glob函数

    # 显示 /tmp 目录下的所有文件
    $dir = "/tmp/*";
    my @files = glob( $dir );
     
    foreach (@files ){
       print $_ . "
    ";
    }
     
    # 显示 /tmp 目录下所有以 .c 结尾的文件
    $dir = "/tmp/*.c";
    @files = glob( $dir );
     
    foreach (@files ){
       print $_ . "
    ";
    }
     
    # 显示所有隐藏文件
    $dir = "/tmp/.*";
    @files = glob( $dir );
    foreach (@files ){
       print $_ . "
    ";
    }
     
    # 显示 /tmp 和 /home 目录下的所有文件
    $dir = "/tmp/* /home/*";
    @files = glob( $dir );
     
    foreach (@files ){
       print $_ . "
    ";
    }
    

    或者:

    opendir (DIR, '.') or die "无法打开目录, $!";
    while ($file = readdir DIR) {
      print "$file
    ";
    }
    closedir DIR;
    
    # 如果你要显示 /tmp 目录下所有以 .c 结尾的文件,可以使用以下代码:
    opendir(DIR, '.') or die "无法打开目录, $!";
    foreach (sort grep(/^.*.c$/,readdir(DIR))){
       print "$_
    ";
    }
    closedir DIR;
    

    创建一个新目录

    $dir = "/tmp/perl";
     
    # 在 /tmp 目录下创建 perl 目录
    mkdir( $dir ) or die "无法创建 $dir 目录, $!";
    print "目录创建成功
    ";
    

    删除目录

    $dir = "/tmp/perl";
     
    # 删除 /tmp 目录下的 perl 目录
    rmdir( $dir ) or die "无法删除 $dir 目录, $!";
    print "目录删除成功
    ";
    

    切换目录

    $dir = "/home";
     
    # 将当期目录移动到 /home 目录下
    chdir( $dir ) or die "无法切换目录到 $dir , $!";
    print "你现在所在的目录为 $dir
    ";
    

    13. 错误处理

    14. 特殊变量

    • Perl 语言中定义了一些特殊的变量,通常以 $, @, 或 % 作为前缀,例如:$_。

    • 很多特殊的变量有一个很长的英文名,操作系统变量 $! 可以写为 ​$OS_ERROR。

    • 如果你想使用英文名的特殊变量需要在程序头部添加 use English;。这样就可以使用具有描述性的英文特殊变量。

    • 最常用的特殊变量为 $_,该变量包含了默认输入和模式匹配内容。

    foreach ('Google','Runoob','Taobao') {
        print $_;
        print "
    ";
    }
    
    foreach ('Google','Runoob','Taobao') {
        print;
        print "
    ";
    }
    

    上面两个的输出结果都是一样的。因为print 没有指定输出变量时默认用$_

    以下是几处即使没有写明 Perl 也会假定使用 $_ 的地方:

    • 各种单目函数,包括像 ord() 和 int() 这样的函数以及除 "-t"以外所有的文件 测试操作 ("-f","-d"),"-t" 默认操作 STDIN。
    • 各种列表函数,例如 print() 和 unlink()。
    • 没有使用 "=~" 运算符时的模式匹配操作 "m//"、"s///" 和"tr///"。
    • 在没有给出其他变量时是 "foreach" 循环的默认迭代变量。
    • grep() 和 map() 函数的隐含迭代变量。
    • 当 "while" 仅有唯一条件,且该条件是对 ""操作的结果进行测试时,$_ 就是存放输入记录的默认位置。除了"while" 测试条件之外不会发生这种情况。(助记:下划线在特定操作中是可以省略的。)

    特殊变量列举表

    https://www.runoob.com/perl/perl-special-variables.html

    15. 正则表达式

    Perl语言的正则表达式功能非常强大,基本上是常用语言中最强大的,很多语言设计正则式支持的时候都参考Perl的正则表达式。

    Perl的正则表达式的三种形式,分别是匹配,替换和转化:

    • 匹配:m//(还可以简写为//,略去m)
    • 替换:s///
    • 转化:tr///

    这三种形式一般都和 =~!~ 搭配使用, =~ 表示相匹配,!~ 表示不匹配。

    匹配操作符

    匹配操作符 m// 用于匹配一个字符串语句或者一个正则表达式,例如,要匹配 标量 $bar 中的 "run",代码如下所示:

    $bar = "I am runoob site. welcome to runoob site.";
    if ($bar =~ /run/){
       print "第一次匹配
    ";
    }else{
       print "第一次不匹配
    ";
    }
     
    $bar = "run";
    if ($bar =~ /run/){
       print "第二次匹配
    ";
    }else{
       print "第二次不匹配
    ";
    }
    
    第一次匹配
    第二次匹配
    
    模式匹配修饰符
    修饰符 描述
    i 忽略模式中的大小写
    m 多行模式
    o 仅赋值一次
    s 单行模式,"."匹配" "(默认不匹配)
    x 忽略模式中的空白
    g 全局匹配
    cg 全局匹配失败后,允许再次查找匹配串

    正则表达式变量

    perl处理完后会给匹配到的值存在三个特殊变量名:

    • $`: 匹配部分的前一部分字符串
    • $&: 匹配的字符串
    • $': 还没有匹配的剩余字符串

    如果将这三个变量放在一起,你将得到原始字符串。

    $string = "welcome to runoob site.";
    $string =~ m/run/;
    print "匹配前的字符串: $`
    ";
    print "匹配的字符串: $&
    ";
    print "匹配后的字符串: $'
    ";
    

    替换操作符

    替换操作符 s/// 是匹配操作符的扩展,使用新的字符串替换指定的字符串。基本格式如下:

    s/PATTERN/REPLACEMENT/修饰符;
    
    $string = "welcome to google site.";
    $string =~ s/google/runoob/;
     
    print "$string
    ";
    
    替换操作修饰符
    修饰符 描述
    i 如果在修饰符中加上"i",则正则将会取消大小写敏感性,即"a"和"A" 是一样的。
    m 默认的正则开始"^"和结束"$"只是对于正则字符串如果在修饰符中加上"m",那么开始和结束将会指字符串的每一行:每一行的开头就是"^",结尾就是"​$"。
    o 表达式只执行一次。
    s 如果在修饰符中加入"s",那么默认的"."代表除了换行符以外的任何字符将会变成任意字符,也就是包括换行符!
    x 如果加上该修饰符,表达式中的空白字符将会被忽略,除非它已经被转义。
    g 替换所有匹配的字符串。
    e 替换字符串作为表达式

    转化操作符

    修饰符 描述
    c 转化所有未指定字符
    d 删除所有指定字符
    s 把多个连续相同的输出字符缩成一个

    以下实例将变量 $string 中的所有小写字母转化为大写字母:

    $string = 'welcome to runoob site.';
    $string =~ tr/a-z/A-z/;
    
    print "$string
    ";
    

    以下实例使用 /s 将变量 $string 重复的字符删除:

    $string = 'runoob';
    $string =~ tr/a-z/a-z/s;
     
    print "$string
    ";
    
    $string =~ tr/d/ /c;     # 把所有非数字字符替换为空格
    $string =~ tr/	 //d;     # 删除tab和空格
    $string =~ tr/0-9/ /cs    # 把数字间的其它字符替换为一个空格。
    

    更多正则表达式规则

    表达式 描述
    . 匹配除换行符以外的所有字符
    x? 匹配 0 次或一次 x 字符串
    x* 匹配 0 次或多次 x 字符串,但匹配可能的最少次数
    x+ 匹配 1 次或多次 x 字符串,但匹配可能的最少次数
    .* 匹配 0 次或多次的任何字符
    .+ 匹配 1 次或多次的任何字符
    {m} 匹配刚好是 m 个 的指定字符串
    {m,n} 匹配在 m个 以上 n个 以下 的指定字符串
    {m,} 匹配 m个 以上 的指定字符串
    [] 匹配符合 [] 内的字符
    [^] 匹配不符合 [] 内的字符
    [0-9] 匹配所有数字字符
    [a-z] 匹配所有小写字母字符
    [^0-9] 匹配所有非数字字符
    [^a-z] 匹配所有非小写字母字符
    ^ 匹配字符开头的字符
    $ 匹配字符结尾的字符
    d 匹配一个数字的字符,和 [0-9] 语法一样
    d+ 匹配多个数字字符串,和 [0-9]+ 语法一样
    D 非数字,其他同 d
    D+ 非数字,其他同 d+
    w 英文字母或数字的字符串,和 [a-zA-Z0-9_] 语法一样
    w+ 和 [a-zA-Z0-9_]+ 语法一样
    W 非英文字母或数字的字符串,和 [^a-zA-Z0-9_] 语法一样
    W+ 和 [^a-zA-Z0-9_]+ 语法一样
    s 空格,和 [ f] 语法一样
    s+ 和 [ f]+ 一样
    S 非空格,和 [^ f] 语法一样
    S+ 和 [^ f]+ 语法一样
     匹配以英文字母,数字为边界的字符串
    B 匹配不以英文字母,数值为边界的字符串
    a|b|c 匹配符合a字符 或是b字符 或是c字符 的字符串
    abc 匹配含有 abc 的字符串 (pattern) () 这个符号会记住所找寻到的字符串,是一个很实用的语法.第一个 () 内所找到的字符串变成 $1 这个变量或是 1 变量,第二个 () 内所找到的字符串变成 $2 这个变量或是 2 变量,以此类推下去.
    /pattern/i i 这个参数表示忽略英文大小写,也就是在匹配字符串的时候,不考虑英文的大小写问题. 如果要在 pattern 模式中找寻一个特殊字符,如 "*",则要在这个字符前加上 符号,这样才会让特殊字符失效
  • 相关阅读:
    shell中单引号、双引号、反斜杠简说
    shell脚本
    求素数
    SqlBulkCopy高效写入数据库Demo
    地图面面观之百望山
    FileUpload控件客户端验证
    如何将shapefile进行拆分
    Python 字符串操作
    如何重装oracle
    资料
  • 原文地址:https://www.cnblogs.com/lyc-seu/p/12377166.html
Copyright © 2011-2022 走看看