从程序员到数据科学家:SAS 编程基础 (06)- DATA步与PDV
来源:互联网 发布:怎么给域名续费 编辑:程序博客网 时间:2024/04/29 04:26
在BASE SAS 中,DATA 语句用于开始一个数据步, 后续为若干DATA步特定的语句;SAS数据步结束于下一个 DATA 步或 PROC 步开始之处,或者结束于后续显式指定的RUN语句。
注意:DATA 步是SAS编程语言的基础,它可以执行在多种执行环境中。本章要讲的是传统意义上的SAS DATA步,后面我们会讲到下一代DATA步以及DATA步在SAS 云分析服务环境中执行的情况。千里之行,始于足下!我们还是从传统 DS 开始吧…
DATA 语句最常见的调用方式有如下几种:
1)DATA 语句可不指定任何参数,则DATA 步将自动创建一个目标数据集 DATAn,其中 n为从1开始不断增长的唯一整数。
比如下面的SAS代码将在临时逻辑库 WORK中创建数据集 Data1,包含1行5列数据。再次运行该代码时将生成 Data2…依次类推。
DATA;
Name="Leon"; Sex="M"; Age=30; Weight=83.5; Height=175;
run;
注意:这种方式一般用来生成不在乎输出数据集名称的时候,但由于每次执行都会在 WORK 中生成一份新的数据,一般不建议这种用法。通常情况下会显式指定输出数据集的名字会更好一些。
对于这种临时数据,我们依然可以使用系统宏变量 &SYSLAST来跟踪,比如下面的代码用来显示上一次生成的临时数据集的名字,并从WORK中删除它。
data _null_;
put "&SYSLAST";
run;
proc datasets nolist ;
delete %scan(&SYSLAST,2);
quit;
2)DATA 语句也可以指定数据集名称 _NULL_ ,表示不输出任何目标数据集,通常用于纯粹的计算逻辑处理。也经常用于调试SAS代码。
3)DATA 语句也可以指定多个输出数据集,从而实现在一个DATA步里输出各种需要的数据。比如下面的代码将同时生成两份内容相同的数据集 data1和data2。
DATA data1 data2;
Name="Leon"; Sex="M"; Age=30; Weight=83.5; Height=175;
run;
一般情况下,我们会显式指定生成一个目标数据集名称,并且希望放在特定的目录中。SAS DATA语句在数据处理中可以展现出令人惊异的行为,比如我们希望把 sashelp.class 中两种性别的数据分成两个数据集 classF和clasM,可使用下面的代码简洁地实现。
proc print data=sashelp.class;run;
DATA classF classM;
set sashelp.class;
if sex="F" then output classF; /*Split F to ClassF*/
else output classM; /*Split M to ClassM*/
run;
proc print data=classF; run;
proc print data=classM; run;
上一章的最后,我提到数据集中的观测(记录)有各种生成方式,下面我们就一起看看在SAS中如何做到。
l 利用内嵌数据行创建SAS数据集
一般情况下,如果我们要生成的数据量比较小,且不希望有独立的数据文件,我们可以直接将数据嵌在SAS代码中,内嵌数据行有几种不同的方式:
1) 最常见的情况是我们有一系列的数据行,数据项之间用空格分隔。这种情况我们可以使用datalines 语句配合input 语句直接生成。datalines语句表示下一行将包含数据行。由于数据行并非语句,所以它不需要分号。另外,除了 datalines 语句外,我们也可使用 lines(datalines 语句的别名)或 cards 语句,三者等价。
libname mylib "c:\temp";
DATA mylib.myclass;
input Name $ Sex $ Age Height Weight; /*$ 表示字符型变量*/
datalines;
Alfred M 14 112.5 69
Alice F 13 84 56.5
Barbara F 13 98 65.3
Carol F 14 102.5 62.8
Henry M 14 102.5 63.5
James M 12 83 57.3
Jane F 12 84.5 59.8
Janet F 15 112.5 62.5
Jeffrey M 13 84 62.5
John M 12 99.5 59
Joyce F 11 50.5 51.3
Judy F 14 90 64.3
Louise F 12 77 56.3
Mary F 15 112 66.5
Philip M 16 150 72
Robert M 12 128 64.8
Ronald M 15 133 67
Thomas M 11 85 57.5
William M 15 112 66.5
RUN;
系统会生成如下数据集,存放在 C:\temp 目录中。与利用临时库 WORK不同,该数据集在你SAS 会话结束后依然存在,存放在 C:\temp\class.sas7bdat 文件中。
注意:生成到磁盘上的文件名依赖于操作系统的文件系统,Windows平台的FAT/NTFS不区分大小写,而Linux/Unix 则区分大小写,SAS一律使用小写字母生成数据集文件,在Unix/Linux平台上,如果我们要读取包含大写字母的文件名(即数据集名)时,需要启用系统选项VALIDMEMNAME=EXTEND,并且数据集的名字必须与磁盘上的文件名大小写完全匹配。
2) 如果数据行包含特定的分隔符,我们可以利用 infile 语句来指向特殊的文件引用 datalines,并且使用参数 delimiter=’<分隔符>’ 来指定分隔符。比如:
libname mylib "c:\temp";
DATA mylib.myclass;
infile datalines delimiter=',';
input Name $ Sex $ Age Height Weight;
datalines;
Alfred,M,14,112.5,69
Alice,F,13,84,56.5
Barbara,F,13,98,65.3
Carol,F,14,102.5,62.8
Henry,M,14,102.5,63.5
James,M,12,83,57.3
Jane,F,12,84.5,59.8
Janet,F,15,112.5,62.5
Jeffrey,M,13,84,62.5
John,M,12,99.5,59
Joyce,F,11,50.5,51.3
Judy,F,14,90,64.3
Louise,F,12,77,56.3
Mary,F,15,112,66.5
Philip,M,16,150,72
Robert,M,12,128,64.8
Ronald,M,15,133,67
Thomas,M,11,85,57.5
William,M,15,112,66.5
RUN;
3) 如果数据行本身包含分号(注:由于分号是SAS语句的结束符,因此很特别),我们该如何处理呢?我们可以使用 datalines4 语句来完成。也可以使用该语句的别名为 cards4 或lines4。datalines4 语句必须使用4个连续的分号开始的一行来标记数据行结束。下面的例子中,字符型列 Name 包含分号,则我们必须以datalines4 来输入数据。
libname mylib "c:\temp";
DATA mylib.myclass;
input Name $ Sex $ Age Height Weight;
datalines4;
Alfred; M 14 112.5 69
Alice; F 13 84 56.5
Barbara; F 13 98 65.3
Carol; F 14 102.5 62.8
Henry; M 14 102.5 63.5
James; M 12 83 57.3
Jane; F 12 84.5 59.8
Janet; F 15 112.5 62.5
Jeffrey; M 13 84 62.5
John; M 12 99.5 59
Joyce; F 11 50.5 51.3
Judy; F 14 90 64.3
Louise; F 12 77 56.3
Mary; F 15 112 66.5
Philip; M 16 150 72
Robert; M 12 128 64.8
Ronald; M 15 133 67
Thomas; M 11 85 57.5
William; M 15 112 66.5
;;;;
RUN;
4) 如果数据行本身是变长(比如字符串变量包含空格字符),也就是说数据行参差不齐,那我们如何输入呢?我们可以使用SAS提供的列指针,用来明确指定数据行中变量的读取的起止位置,从而正确读取变长的字符串。比如:
libname mylib "c:\temp";
DATA mylib.myclass;
input Name $1-15 Sex $ Age Height Weight;
datalines;
Alfred Liu M 14 112.5 69
Alice Wang F 13 84 56.5
Barbara Deng F 13 98 65.3
Carol Zhang F 14 102.5 62.8
Henry Kissinger M 14 102.5 63.5
James Michalle M 12 83 57.3
Jane Xu F 12 84.5 59.8
Janet Quin F 15 112.5 62.5
Jeffrey Smith M 13 84 62.5
John Albert M 12 99.5 59
Joyce Betty F 11 50.5 51.3
Judy Yang F 14 90 64.3
Louise Bernard F 12 77 56.3
Mary Kushiner F 15 112 66.5
Philip Pebble M 16 150 72
Robert Chu M 12 128 64.8
Ronald Wiese M 15 133 67
Thomas Berryman M 11 85 57.5
William Wu M 15 112 66.5
RUN;
5) 有的同学会问,如果我们在一个数据行上包括多个观测(记录),我们该如何读取?SAS在 input 语句上设计了一个特殊的参数 @@,用来告诉SAS 从数据行完整读取一个观测后,不要马上读入下一个数据行,而是继续从当前行的缓冲区中读取数据填充观测。这为我们节省代码文件的行数非常有用。比如:
libname mylib "c:\temp";
DATA mylib.myclass;
input Name $ Sex $ Age Height Weight @@;
datalines;
Alfred M 14 112.5 69
Alice F 13 84 56.5 Barbara F 13 98 65.3
Carol F 14 102.5 62.8 Henry M 14 102.5 63.5 James M 12 83 57.3
Jane F 12 84.5 59.8 Janet F 15 112.5 62.5 Jeffrey M 13 84 62.5
John M 12 99.5 59 Joyce F 11 50.5 51.3 Judy F 14 90 64.3
Louise F 12 77 56.3 Mary F 15 112 66.5 Philip M 16 150 72
Robert M 12 128 64.8 Ronald M 15 133 67
Thomas M 11 85 57.5 William M 15 112 66.5
RUN;
l 基于外部文件创建SAS数据集
大部分情况下,数据来自于磁盘上的某个外部文件,而且通常不是一系列的文件。比如在C:\temp目录中有如下文本文件: myclass.txt, 我们怎么用 DATA 步来读取呢?
我们不需要 datalines语句,而是在 DATA 步内利用 infile 语句指定该外部文件(相当于我们将datalines语句下的数据行移入了外部文件)。然后再用 input语句读入。比如:
libname mylib "c:\temp";
DATA mylib.MyClass;
infile 'c:\temp\myclass.txt';
input Name $ Sex $ Age Height Weight;
RUN;
系统将创建一个完整的数据集 mylib.MyClass。
还有一种更加标准的做法是,我们先用 filename 语句定义一个文件引用 myfile,然后再在infile 语句中使用该文件引用。代码如下:
libname mylib "c:\temp";
filename myfile 'c:\temp\myclass.txt';
DATA mylib.MyClass;
infile myfile ;
input Name $ Sex $ Age Height Weight;
RUN;
就像前面已经提到的一样,数据行中可能包含说明文字,或者数据的表头什么的。这种情况下我们可以在 infile 语句上指定开始读取观测的行 firstobs=,同时也可以指定结束读取观测的行obs= 用来限定读入的数据量,通常结果集的总行数为 obs-firstobs+1 行。比如下面的代码读入第二行开始的10行数据。
infile myfile delimiter=',' firstobs=2 obs=11;
l 简单验证生成的SAS数据集
为了验证我们自己创建的数据集 MyLib.myclass 和系统 SASHELP.CLASS 数据集的差异,我们可以调用 PROC COMPARE 来比较两个数据集的异同。
proc compare base=sashelp.class compare=mylib.myclass;
run;
系统显示两个数据集基本相同,除了 sashelp.class 有数据集Label信息,Sex 列宽度为8字节外,两个数据集完全一样。
l 通过已有的SAS 数据集生成数据
很多时候我们都是操作已有的SAS数据集,来进行各种操作生成目标数据集。比如对数据集中数据的增删改查,数据集的排序、合并、分离、转置等。
1) 增加数据行:在数据集尾部增加数据行
DATA OneRow;
Name="Leon"; Sex="M"; Age=30; Weight=83.5; Height=175;
run;
data Class2;
set sashelp.class OneRow;
run;
也可以在数据的头部增加数据行,只需要改变 SET 语句中的数据集顺序即可。
DATA OneRow;
Name="Leon"; Sex="M"; Age=30; Weight=83.5; Height=175;
run;
data myclass;
set OneRow sashelp.class;
run;
proc print data=myclass;
run;
注意:细心的读者可能会发现,输出的数据集中Name有截断错误,原因是在 SET语句时 PDV的初始结构来自于我们创建的临时数据集 OneRow,而该数据集中变量Name的长度定义不够,可以通过增加临时数据集宽度定义来修正。
DATA OneRow;
length Name $8;
Name="Leon"; Sex="M"; Age=30; Weight=83.5; Height=175;
run;
如果需要在特定行处插入数据,可以使用内部计数器 _N_ 作条件实现:
DATA myclass;
set sashelp.class;
if _N_ = 1 then do; /*在第一行后面插入数据*/
output;
Name="Leon"; Sex="M"; Age=30; Weight=83.5; Height=175;
end;
output;
run;
option obs=3; /*列出前三行*/
proc print data=myclass;
run;
2) 删除数据行:删除第三行数据
data myclass;
set sashelp.class;
if _N_ = 3 then return;
else output;
run;
proc summary data=myclass print; run;
当然你也可以删除满足指定条件的数据行
data myclass;
set sashelp.class;
if Sex = 'M' then return;
else output;
run;
proc print data=myclass;
run;
2) 修改数据行: 满足特定条件时修改变量的值,比如把第三行的Name改为 "Baby".
Data myclass;
set sashelp.class;
if _N_ = 3 then name="Baby";
run;
options obs=3;
proc print ; run;
2) 查询特定数据行:仅输出满足特定条件的数据行
libname mylib "c:\temp";
data mylib.myclass;
set sashelp.class;
if name="Alfred" then output;
run;
proc print data=mylib.myclass;run;
l 通过 PROC IMPORT 或PROC SQL 生成
SAS 提供PROC IMPORT 和 PROC EXPORT 来将数据导入/导出 SAS 运行环境,常用的数据文件格式为逗号分隔的 CSV 和微软的电子表格 EXCEL 文件,我们可以使用如下代码简单完成。
proc import
datafile='c:\temp\class.csv' dbms=csv out=class replace;
getnames=YES;
mixed=NO;
run;
proc print data=class;run;
proc import
datafile='c:\temp\class.xlsx' dbms=xlsx out=class replace;
getnames=YES;
mixed=NO;
run;
注意:以上例子需要数据文件 c:\temp\class.csv 和 class.xlsx,你可以用 proc export 进行生成。
PROC EXPORT data=sashelp.class outfile='c:\temp\class.xlsx' dbms=xlsx replace;
run;
PROC EXPORT data=sashelp.class outfile='c:\temp\class.csv' dbms=csv replace;
run;
注意:很多人根据帮助文档使用 dbms=EXCEL 导入 EXCEL 文件时会出现如下错误:
ERROR: Connect: 没有注册类
ERROR: Error in the LIBNAME statement.
根本的原因是 dbms=EXCEL 和 dbms=xlsx 在 SAS 里访问机制不同,前者需要安装 ACE 引擎才能工作,而默认情况下我们并没有安装它。这是一个令很多程序员困惑的技术陷阱。
PROC SQL 让 SAS 可以调用结构化查询语言 SQL 进行数据操作,广泛用于关系数据库管理系统的数据表和视图的增删改查,但SAS 技术更高一筹,也可以对 SAS 数据集进行标准的 SQL操作。主要功能包括:创建数据表和数据视图,对数据列作索引;查询存储在数据表和数据视图中的数据;增删改数据行和增删改数据列本身;将数据库支持的 SQL 语句到数据库管理系统中进行数据查询。另外 SAS 也支持将 SQL 查询结果置入 SAS 宏变量中,进行数据传递功能。PROC SQL 功能非常强大,下面仅列出两个简单的例子:创建/查询数据表。
1)基于已有的数据集创建新的数据集,没有使用 DATA 步。
libname mylib 'c:\temp';
proc sql;
create table mylib.myclass as
select Name, Sex, Age, Height, Weight format=best.
from sashelp.class;
proc print data=mylib.myclass; run;
2)使用标准的 SQL 语言创建数据集,如果 mylib 指向某个数据库管理系统,则会在该数据库中创建对应的数据表。
libname mylib 'c:\temp';
proc sql;
create table mylib.myclass( Name char(8), Sex char(1), Age num, Height num, Weightnum informat=best. format=best.);
insert into mylib.myclass
values('Leon','M',31,175,80)
values('Jim', 'M',30,173,75);
title 'Table mylib.myclass';
select * from mylib.myclass;
proc printto; run;
DATA 步的运行机制
前面的例子让我们看到SAS在处理数据非常方便,但这依然不够,我们需要深入探索SAS的DATA步是怎么工作的——即我们需要深刻理解SAS DATA 步的运行机制,这是SAS编程的核心内容之一。
首先需要指出的是,SAS语言是按步进行编译执行的,所以SAS程序与大多数编译型计算机语言程序一样,总体上要经过编译和执行两个阶段。简要流程如下图所示:
编译阶段
编译阶段SAS 主要做两件事:
1)扫描DATA 步内的每一行语句,a) 执行语法检查:扫描代码片段,检查是否存在于语法错误。常见的语法错误包括关键字缺失或拼写错误、无效变量名称、标点符缺失或者无效、以及无效的参数或选项等。b)标识每一个变量的名称,类型和长度等信息,并且判断是否需要为后续变量引用作类型转换等。
2)为程序执行创建必要的数据结构,包括输入缓冲区IB(INPUT BUFFER)、程序数据向量PDV(Program Data Vector)和输出数据集描述信息 DI(Descriptor Information),其中输入缓冲区 IB只在从外部读取原始数据文件时才是创建。
a) 输入缓冲区IB:当DATA 步内执行INPUT语句是从原始数据文件(Raw Data,比如前面例子中的外部文本文件)中读取观测(记录)时,SAS会在内存中分配一块逻辑区域作为缓冲区,作为将数据放入PDV之前的临时缓冲区存在。如果是使用 SET语句来读取SAS数据集时,SAS则将数据直接拷贝到PDV中,而不需要所谓的输入缓冲区IB。
b) 程序数据向量PDV:DATA步每读入一行数据时,都需要在内存中分配一个逻辑区域,用于存放数据集的变量和计算变量(即计算列)信息。其数据来自于输入缓冲区IB或SAS 执行语句。
另外,PDV中还包含2个仅用于系统处理阶段的临时变量,它们不会被写入目标数据集:
l 行计数器 _N_:用来对DATA 步的每次处理进行循环计数,从1开始;
l 错误标志 _ERROR_:用来标记执行过程中由于数据错误引起的错误;默认值是0,表示没有错误,否则为1,表示有一个或者多个错误。
c) 输出数据集描述符信息
SAS为每一个输出数据集创建和维护的元数据信息,包括数据集属性和变量属性。比如数据集名字,成员类型,创建日期,创建时间,观测数,变量名称,类型(字符型/数值型)等。
让我们考察如下代码的编译过程:
/*利用系统 SASHELP.CLASS数据,创建体质指数 BMI,俗称肥胖指数*/
data myclass;
set sashelp.class;
where age> 12 and sex='男';
BMI=(weight * 0.4535924) / ((height*2.54/100) **2);
format BMI 4.1;
drop weight height;
run;
对于如上代码,编译时 PDV 的变化依次如下图右侧所示。从上到下各行语句编译时会修改PDV,比如DROP 语句的作用是告诉SAS PDV中哪些变量不需要输出到输出数据集,对那些变量设置“删除”标志,以免执行时输出到目标数据集中。
编译到 RUN 语句时,DATA步编译宣告结束。SAS会为需要输出到目标数据集的那些变量,创建必要的描述符信息——包括数据集中各列的名称、类型、长度、输出格式、标签等。
运行阶段
一旦编译成功,执行阶段开始。SAS 的执行主要有如下步骤:
1)SAS 首先会从DATA语句处开始执行,如果是第一次执行,SAS会设置内部变量_N_=1和_ERROR_=0,否则会对内部计数器 _N_自动加1。
2)SAS会以缺失值对程序数据向量(PDV)中那些由INPUT语句和赋值语句创建的变量进行初始化。注意:那些以 SET, MERGE, MODIFY或 UPDATE语句读取的变量并不会被重置为缺失值。
3)默认情况下,SAS从外部原始数据文件中读入一条数据记录到输入缓冲区IB中,然后创建对应的PDV,或者直接从SAS数据集中读入一个观测到程序数据向量PDV中。SAS语句 INPUT和 SET、 MERGE、 MODIFY、UPDATE都可以用来读入一条记录。
4)对当前记录,执行后续的SAS程序语句,包括赋值、计算和更新等等。
5)当执行到 DATA 步的最后时,隐含的SAS语句 OUTPUT,RETURN和RESET 自动触发。SAS将当前记录作为一个观测写入到SAS数据集中。执行自动返回到DATA 步的开始处进入下一循环。如果SAS 读取外部文件结束或SAS数据集结束,则整个 DATA 步执行终止,进入后续的DATA或PROC步的编译执行。
让我们考察前面代码的运行过程,其中DATA 步中的新变量(如体质指数BMI)首先也会以缺失值 . 进行初始化,随后在执行赋值语句时对表达式重新求值,赋给那些新变量;然后执行下一行语句…在 DATA 步的最后,SAS会将PDV中没有删除标志的非临时变量(除了 height, weight)和值写入到目标数据集中。然后控制流程再返回到 DATA 步的开始处进入下一个循环。正是SAS DATA步这种独特的隐性循环设计,为用户在进行数据处理时提供了自然的循环概念,从而让用户对具体文件的I/O读写细节不必过于关注。
当SAS进入下一次循环时,临时变量 _N_ 计数器会自动加1,对于非INPUT语句读入的情况,SAS 也会保留上次读入到 PDV中的变量值,直到被新读入的观测所覆盖;对于DATA步中的新变量(如体质指数 BMI),SAS会重新使用缺失值 (.) 进行初始化。当数据被再次读入时(比如从上面的SET语句所指定的源),SAS会将源数据集中的第二个观测读入到 PDV中,并重新计算程序中的新变量 BMI。然后在DATA步的最后,使用 PDV中的值作为第二个观测(记录)写入到输出数据集中。然后控制再次回到DATA步开始,一直循环执行,直到源数据集中的所有观测都被处理完毕。
结语:本章我们学习了利用SAS创建数据集的几种灵活方法,然后介绍了SAS DATA 步运行机制的奥秘。深刻理解DATA步的编译运行机制对掌握SAS 编程至关重要,灵活使用SAS DATA步可以为分析准备任何形式的待分析数据。
下面笔者就以如何生成前20个黄金分割数列的简单SAS程序来结束本章的学习! 黄金分割数列即斐波那契数列,该数列中后一个数与前一个数的比例越往后越接近于黄金比例(1+√5)/2 ,此数列分布表现出极致的均衡与和谐之美;其前8个数为:1 1 2 3 5 8 13 21…
/*生成前20个黄金分割数列到数据集 WORK.FbNC 中*/
data Fbnc;
do n=1 to 20;
if n=1 or n=2 then y=1;
else y= y1 + y2;
y2=y1; y1=y;
output;
put n= y=; /*打印到SAS日志*/
end;
drop n y1 y2;
run;
proc print data=fbnc;run;
输出:
n=0 y=1
n=1 y=1
n=2 y=2
n=3 y=3
n=4 y=5
n=5 y=8
n=6 y=13
n=7 y=21
- 从程序员到数据科学家:SAS 编程基础 (06)- DATA步与PDV
- 从程序员到数据科学家:SAS 编程基础 (01)
- 从程序员到数据科学家:SAS 编程基础 (02)
- 从程序员到数据科学家:SAS 编程基础 (03)
- 从程序员到数据科学家:SAS 编程基础 (04)
- 从程序员到数据科学家:SAS 编程基础 (05)
- 从程序员到数据科学家:SAS 编程基础 (07)- 常量与变量
- 从程序员到数据科学家:SAS 编程基础 (08)- 表达式
- SAS学习笔记之《SAS编程与数据挖掘商业案例》(4)DATA步循环与控制、常用全程语句、输出控制
- SAS学习笔记之《SAS编程与数据挖掘商业案例》(1)系统简介和编程基础
- SAS进阶《深入解析SAS》之Base SAS基础、读取外部数据到SAS数据集
- 从菜鸟成为数据科学家的 9步养成方案
- 《程序员第二步-从程序员到项目经理》
- SAS学习笔记之《SAS编程与数据挖掘商业案例》(5)SAS宏语言、SQL过程
- SAS数据步常用语句
- SAS学习笔记之《SAS编程与数据挖掘商业案例》(2)数据获取与数据集操作
- SAS中关于PDV的总结
- (2016)[SAS数据处理] 利用二分法思想加速DATA步数据截取操作 [v1.02]
- 前端名词解释(持续更新)
- 从程序员到数据科学家:SAS 编程基础 (05)
- Octave 介绍与学习
- vagrant系列(2):使用Vagrantfile实现集成预安装
- c语言函数参数传地址时容易发生的错误
- 从程序员到数据科学家:SAS 编程基础 (06)- DATA步与PDV
- ubuntu gnome3 下eclipse dialog界面显示不全
- 从PHP5到PHP7自我封装MongoDB以及平滑升级
- MySQL数据库(日结)
- phper在mac上安装composer
- Unit2.5 文档
- C语言实现字符串拷贝函数有几种方式
- session的一些摘要
- 从程序员到数据科学家:SAS 编程基础 (07)- 常量与变量