Pike Tutorial Study
Pike是一门解释型的、面向对象的编程语言,与C和C++有点像,但是更加容易学习和使用。它能够被用于小脚本或者是大型系统的编写。
Pike的主要特性有:
高级并强力、面向对象、解释性、最快的脚本语言、垃圾收集机制、易于扩展(C或者C++)
Pike的安装
1、去Pike官网上去下载安装包。
2、解压缩,直接make && make install
3、新建一个文件filename.pike
,输入:
int main()
{
write("Hello world!\n");
return 0;
}
在命令行输入pike filename.pike
,可以看到运行结果。
运行在窗口中
运行代码:
int main()
{
GTK.setup_gtk();
GTK.Alert("Hello world!")
-> signal_connect("destroy", lambda(){ exit(0); });
return -1;
}
效果如下:
Pike的模块
Pike和大多数其他语言一样,提供了丰富的API给我们开发丰富的应用。比如说,当我们需要访问一个网页的时候,可以使用Protocols.HTTP
模块下的Query对象。
形如:
void handle_url(string this_url){
write("Fetching URL '" + this_url + "'...");
Protocols.HTTP.Query web_page;
web_page = Protocols.HTTP.get_url(this_url);
if(web_page == 0) {
write(" Failed!\n");
return;
}
write(" Done.\n");
}
// handle_url
当然,pike提供了import关键字完整导入一个模块。这样使用模块内部的类时就不用写上前缀了:
import Protocols.HTTP;
Query web_page;
当用web_page访问了一个网站之后,如果想要将网站的内容显示出来,则使用web_page提供的一个data()方法。
web_page->data()
基本数据类型
pike的基本数据类型包括:int float和string。
容器类型包括:array, mapping,multiset等
容器类型
最简单的容器类型是array。你可以往这个容器里面放入数据项,array是一个连续的大小的盒子。
array是一个形如这样的list:
({ "geegle", "boogle", "fnordle blordle" })
({ 12, 17, -3, 8 })
({ })
({ 12, 17, 17, "foo", ({ "gleep", "gleep" }) })
可以看到,除了基本类型,array还能嵌套其他容器类型。
能够这样子定义一个变量:
array a; // Array of anything
array(string) b; // Array of strings
array(array(string)) c; // Array of arrays of strings
array(mixed) d; // Array of anything
可以看到最后一行的array(mixed) d
,这其实是一个多态容器,能够存放任意类型的数据。
然后就是array的赋值了:
a = ({ 17, "hello", 3.6 });
b = ({ "foo", "bar", "fum" });
c = ({ ({ "John", "Sherlock" }), ({ "Holmes" }) });
d = c;
我们可以访问数组一样通过下标访问array:
write(a[0]); // Writes 17
b[1] = "bloo"; // Replaces "bar" with "bloo"
c[1][0] = b[2]; // Replaces "Holmes" with "fum"
有一些有意思的操作可以运用于array,比如说加法:
({ 7, 1 }) + ({ 3, 1, 6 })
({ 7, 1, 3, 1, 6 })
除了array还有两种容器类型:mapping和multiset。
set用来判断一个值是不是一个集合的成员。multiset则运行集合里面存在一样的值。
mapping有时被称为字典,通过键值对来存储数据。后面还会进一步解释。
方法也是对象
方法也是对象,这完全符合面向对象里面万物皆对象的理念。因此有时我们可以把方法像一个对象一样传递参数给另一个方法。比如说:
void write_one(int x)
{
write("Number: " + x + "\n");
}
int main()
{
array(int) all_of_them = ({ 1, 5, -1, 17, 17 });
map(all_of_them, write_one);
return 0;
}
Python里面也有这种特性,对于编程来说非常方便。
0是一个特别的东西
0这个值是一个特别的东西,不单单只是int类型的0,它可以代表所有的类型的“NULL”,如果我们创造了一个变量但是我们不想赋值它,默认会被赋值为0。
举个栗子:
string accumulate_answer()
{
string result;
// Probably some code here
result += "Hi!\n";
// More code goes here
return result;
}
``
程序会返回一个`0Hi!\n`,前面多了一个0,这是我们常见的错误。因此,为了避免犯错,应该在变量声明的时候赋初值,哪怕是这样:`string result = ""`也好。
pike支持一个任意数据类型mixed,这意味着用mixed声明的变量可以存储任意的类型:
```pike
mixed x;
array(mixed) build_array(mixed x) { return ({ x }); }
pike还很神奇的支持或语句来声明变量类型,形如:
int|string w;
array(int|string|float) make_array(int|string|float x)
{
return ({ x });
}
比较好的做好是声明一个明确的变量类型,这样编译器会帮助做类型检查,使得类型错误在编译时就被发现。
基本类型和引用类型
熟悉C++的话自然就知道引用时啥了。
基本数据类型都不是引用类型。int i1=5;i2=i1;
在内存中像这样:
非基本数据类型比如说array就是引用类型,比如说下面的代码:
array(int) a1;
a1 = ({1,2,3})
array(int) a2;
a2 = a1;
在内存中会是像这样:
当然如果不想要引用,而是值复制的话(这种方式更加推荐,避免数据同步问题。)
a2 = copy_value(a1)
内存中是这样的:
如果想要复制但是又不想用copy_value函数的话,可以这样子写:
a2 = a1 + ({})
返回的是一个a1的拷贝,与copy_value效果相同。
引用类型到底有哪些呢?下面列出来:
array
mapping
multiset
program
object
function
创建方法
pike在这方面的语法和c很像,一个简单的例子:
float average(float x1, float x2)
{
return (x1 + x2) / 2;
}
方法的调用:
float x = average(19.0 + 11.0, 10.0);
average(27.13, x + 27.15);
float y = average(1.0, 2.0) + average(6.0, 7.1);
float z = average(1.0, average(2.0, 3.0));
两个更加复杂的例子:
int getDex()
{
int oldDex = Dex;
Dex = 0;
return oldDex;
}
private void
show_user(int|string id, void|string full_name)
{
write("Id: " + id + "\n");
if(full_name)
write("Full name: " + full_name + "\n");
}
如果想要调用:
getDex();
show_user(19);
show_user("john");
show_user(19, "John Doe");
show_user("john", "John Doe");
pike的多态
对象的使用
pike声明并初始化一个对象是这样的:
animal some_animal;
some_animal = animal();
animal my_dog = animal();
通过->调用对象的内部变量:
my_dog->name = "Fido";
my_dog->weight = 10.0;
some_animal->name = "Glorbie";
write("My dog is called " + my_dog->name + ".\n");
write("Its weight is " + my_dog->weight + ".\n");
write("That animal is called " + some_animal->name + ".\n");
初始化一个对象时可以调用有参构造函数:
animal piglet = animal("Piglet", 6.3);
my_dog->eat("quiche"); // Real dogs eat quiche.
write("Its weight is now " + my_dog->weight + ".\n");
从上面可以看到如果想要调用对象方法,依然是使用->。
类定义
话不多说,先上代码:
class animal
{
string name;
float weight;
void create(string n, float w)
{
name = n;
weight = w;
}
void eat(string food)
{
write(name + " eats some " + food + ".\n");
weight += 0.5;
}
}
分析一下,基本上与C++的类定义差不多,主要的区别在于没有声明访问控制(public和private)。
当然也可以把类当做结构体来用:
class customer
{
int number;
string name;
array(string) phone_numbers;
}
调用代码:
array(customer) all_customers = ({ });
customer c = customer();
c->number = 18;
c->name = "Ellen Ripley";
c->phone_numbers = ({ "555-8767", "555-4001" });
all_customers += ({ c });
奇特用法
一个pike文件就是一个类。这是pike比较特殊的地方。
比如说我们创建一个"animal.pike"文件并输入:
string name;
float weight;
void create(string n, float w)
{
name = n;
weight = w;
}
void eat(string food)
{
write(name + " eats some " + food + ".\n");
weight += 0.5;
}
通过下面的方式把上面的这个文件当做一个类来用:
constant animal = (program)"animal.pike";
animal piglet = animal("Piglet", 6.3);
继承
多态的基础就是继承。继承能够带来代码重用,多态能够在运行时确定代码段的调用,解除代码耦合并带来更好的扩展性和灵活性。
比如说我们前面定义的动物类,可以作为父类衍生出下面这些子类:
class bird
{
inherit animal;
float max_altitude;
void fly()
{
write(name + " flies.\n");
}
void eat(string food)
{
write(name + " flutters its wings.\n");
::eat(food);
}
}
class fish
{
inherit animal;
float max_depth;
void swim()
{
write(name + " swims.\n");
}
}
可以像这样调用两个新的子类:
bird tweety = bird("Tweety", 0.13);
tweety->eat("corn");
tweety->fly();
tweety->max_altitude = 180.0;
fish b = fish("Bubbles", 1.13);
b->eat("fish food");
b->swim();
animal w = fish("Willy", 4000.0);
w->eat("tourists");
w->swim();
访问控制
pike类的成员变量默认是public的,这并不是一个好的做法。比较好的做法是,隐藏不该公开的所有信息。
值得注意的是:pike的static跟C++不一样,pike的static意味着成员只能被自己或者子类访问。这其实类似于protect。local变量声明的方法,意味着尽管被子类重载了,但是在这个类中还是使用这个本地的方法。
语句
语句包括有if、switch、for、while、do-while等。
基本语法与C相同,感觉无需赘述了。
写代码时,三元表达式是一个不错的选择,max_value = (a > b) ? a : b;
非常用,而且理解上也不难。
foreach语句是一个不错的工具,可以用来遍历一组数据进行操作。形如:
foreach( container , loop-variable)
statement
容器放前面,迭代器放后面,这似乎与其他语言的顺序是相反的。
有三种方法提前离开循环:
使用return
使用throw
使用exit终止程序
特殊语句
空语句
while(!finish())
;
catch语句
形如:
mixed result = catch
{
i = klooble() + 2;
fnooble();
j = 1/i;
};
if(result == 0)
write("Everything was ok.\n");
else
write("Oops. There was an error.\n");
后面的错误处理模块会更多的讨论catch语句。
更多关于数据类型的事情
int
如果想要判断变量是否为int,用intp(??)。
如果想要一个随机数,用random(limit)。
如果想要翻转bit,那么使用reverse(int)。需要注意的是,int是有符号的32位整数,因此reverse一个正数会转变为一个负数。
float
同理:
floatp -- 判断类型
四舍五入:
floor -- 下舍入,去掉小数点后面的数字。配合(int)f,转换为正数。
ceil -- 上舍入,也就是说不管小数点后面是啥都往前进位。
string
注意pike的string都是双引号的。
pike的string使用了著名的慢拷贝技术,剩下的你懂的。
同理:
stringp -- 判断类型
array
前面对于array讨论了很多。看看例子差不多了:
array(string) b; // Array of strings
b = ({ "foo", "bar", "fum" });
b[1] = "bloo"; // Replaces "bar" with "bloo"
array(string) a1; // a1 contains 0
a1 = ({ }); // Now a1 contains an empty array
array(int) a2 = ({ }); // a2 contains an empty array
write(a[0]);
b[1] = "bloo";
c[1] = b[2];
array(array(int)) aai = ({
({ 7, 9, 8 }),
({ -4, 9 }),
({ 100, 1, 2, 4, 17 })
});
array(string) a = ({ "foo", "bar" });
array(string) b = a;
array(string) c = ({ "foo", "bar" });
同理:
arrayp -- 判断类型
截取:({ 1, 7, 3, 3, 7 })[ 1..3 ] = ({ 7, 3, 3 })
array(int) a = ({ 7, 1 }); a == b;
会返回0,及时a和b是相等的,但是由于使用的是完全不同的两块内存,也就是地址不同,所以其实不相等。
真正比较两个array看起来是否相等用equal。
支持集合操作:
| -- 联合
& -- 交集
- -- A排除B的元素
^ -- 排除两方都有的元素
/ -- 用B把A切分成array的array,比如:
({ 7, 1, 2, 3, 4, 1, 2, 1, 2, 77 }) / ({ 1, 2 }) gives the result ({ ({ 7 }), ({ 3, 4 }), ({ }), ({ 77 }) }) .
size 数量
allocate(size) 预分配
reverse 翻转
search 返回第一个找到的元素位置,如果不存在返回-1
has_value 这个只判断是否存在这个元素
replace(array, old, new) 替换(用==)
mapping
先来看一些代码熟悉一下mapping:
([ "beer" : "cerveza", "cat" : "gato", "dog" : "perro" ])
mapping(string:string) m;
mapping(int:float) mif = ([ 1:3.6, -19:73.0 ]);
mapping(string:string) english2spanish = ([
"beer" : "cerveza",
"cat" : "gato",
"dog" : "perro"
]);
mapping(string:float) m1; // m1 contains 0
m1 = ([ ]); // Now m1 contains an empty mapping
mapping(int:int) m2 = ([ ]);
// m2 contains an empty mapping
write(english2spanish["cat"]); // Prints "gato"
english2spanish["dog"] = "gato";
// Now, english2spanish["dog"] is "gato" too
english2spanish["beer"] = english2spanish["cat"];
// Now, all values are "gato"
看起来其实就是Python里面的字典类型吧。通过键值对来存放数据。当然C++的STL里面也有mapping,大致和这个是一样的,区别在于pike的mapping定义时需要用([])扩住,有点麻烦。
mapping里面的顺序是无关系的,估计主要原因是以B树的形式存储,进入容器后会被重新排序。
如果想要查找一个mapping中不存在的key,那么value会返回0:
english2spanish["cat"] // Gives "gato"
english2spanish["glurble"] // Gives 0
注意如果放入mapping的是一个对象的话,会有一些问题:
mapping(array(int) : int) m = ([ ]);
array(int) a = ({ 1, 2 });
m[a] = 3; //ok
write(m[({ 1, 2 })]) //error
mapping也提供了一些函数:
mappingp(some) :检查是否mapping
== 和 equal:==判断两个是否指向同一个东西,equal判断两个是否元素都相同。
indices():返回一个包含所有key的array
values():返回一个包含所有value的array
mkmapping(index-array, value-array):通过一个现成的index array和value array来创建mapping。
mapping1|mapping2:就是数学里的“联合”。如果两边都有的话(可能value不同),右手边优先。"+"是"union"的同义词啦。
mapping1 & mapping2:两边都有的才会返回,且如果value不一致,右手边优先。
mapping1 - mapping2:将mapping1中不在mapping中的元素给挑出来
mapping1 ^ mapping2:异或的作用是找到两个mapping的不同点。
sizeof(mapping):返回mapping的size
search(haystack, needle):逆向查找value的key,如果有多个也只返回第一个。如果不存在会返回编译error。
replace(mapping, old, new):替换旧的value为新的value
zero_type(mapping[index]):查询一个index是否存在于mapping当中。如果存在则返回一个0。不存在返回一个非0。
multiset
set就是集合,包含一堆数据,且数据不重复。如果需要重复数据的话,就要用multiset,这种数据类型用 (< >)来包裹。
还是那样的,如果只是这样multiset(string) m1;
,m1就只是个0而已。如果想要的是空集合的话,应该这样才对:m1 = (< >);
。
直接看代码了解一下multiset的基本使用先:
if(dogs["Fido"])
write("Fido is one of my dogs.\n");
if(!dogs["Dirk"])
write("Dirk is not one of my dogs.\n");
dogs["Kicker"] = 1; // Add Kicker to the set
dogs["Buster"] = 0; // Remove Buster
multiset支持集合并和减操作,比如说:
dogs |= (< "Kicker" >); // Add Kicker to the set
dogs -= (< "Buster" >); // Remove Buster
multiset跟mapping一样,也是没有顺序的,不同顺序的multiset使用equal的比较是一样的。
multiset提供的一些操作:
multisetp(multiset):判断是否是一个multiset
== 和 equal:和mapping相同啦
集合的操作:| - & ^ 和前面的mapping都差不多,不再赘述了。
其他的参数类型
program
感觉看不太懂,比较有用的可能就是通过类名来创建对象:
program-name() or program-name(arguments)
判断是不是一个程序:
programp(something)
object
感觉没有太好说的,看函数吧:
objectp(some):判断some是否为一个object
根据类名创建一个对象:program-name() or program-name(arguments)
destruct(object):析构一个对象,如果对象里面有一个destroy方法,会被优先调用。一般来说不用显示的destroy一个对象,pike有自动的垃圾回收器。
访问一个对象:和C++访问一个指针是一样的。
function
看代码:
void write_one(int x)
{
write("Number: " + x + "\n");
}
int main()
{
array(int) all_of_them = ({ 1, 5, -1, 17, 17 });
map(all_of_them, write_one);
return 0;
}
function w = write;
w("Hello!\n");
提供的客户端:
functionp:判断是否是一个函数
function_name(funciton_object):得到方法对象的名字。
字符串
字符串的操作方法
==/+/-/连接/index/切片(({ 1, 7, 3, 3, 7 }) [ 1..3 ]
)
除法
比较特别的是string的除法:
"abcdfoofoo x" / "foo" == ({ "abcd", "", " x" });
abcdfoofoo x" / 5 == ({ "abcdf", "oofoo" });
"abcdfoofoo x" / 5.0 == ({ "abcdf", "oofoo", " x" });
关于除以int和float的区别,上面的代码一眼就能看出来。
取模
看代码一眼就懂了:
"abcdfoofoo x" % 5 == " x";
乘法
看代码:
({ "7", "1", "foo" }) ":" == "7:1:foo";
string 的内置函数
stringp(something):判断一个对象是否是字符串
sizeof/replace/lower_case/upper_case/String.capitalize(string)/
search(haystack, needle):比如search("sortohum", "orto") == 1;
sprintf格式化生成字符串
用到直接看连接:sprintf格式化字符串
sscanf格式化提取字符串
用到直接看连接:sscanf提取字符串
宽字符串
就是除了8bit字符串之外的都叫宽字符串啦(中文编码这些)。
String.width(string):检测是否包含宽字符串
string_to_utf8(string data):转换编码为utf8
utf8_to_string(string utf8_encoded_data):转为原生的pike编码
表达式
基本的加减乘除取模不再赘述了。
可查通过查询得到隐式类型变换:隐式类型变换表
对于位操作符,可通过查表:位操作符表格
切片操作:
array(int) iii = ({1,2,3,4,5});
iii[1..3] = ({2,3,4})
赋值
值得留意的是群体赋值:
int i;
string s;
float f1, f2;
[ i, s, f1, f2 ] = ({ 3, "hi", 2.2, 3.14 });
类型转换
跟c差不多:
(float)14 // Gives the float 14.0
(int)6.9 // Gives the int 6
(string)6.7 // Gives the string "6.7"
(float)"6.7" // Gives the float 6.7
pike支持一个特殊的类型:mixed
的。这个类型能够存储任何类型的数据。
如果需要对于mixed类型做类型转换:
mixed m = 8;
int i = [int]m;
逗号操作符
总是返回逗号右边的那个值:
7 + 3, 6 * 2 // Gives 12
3,14 // Gives 14 (and not pi)
call and splice
如果我想把一个array里面的元素作为参数传给函数怎么做?(好奇怪的想法..)
array a = ({ 17, -0.3, "foo" });
koogle(@a);
equivalent
koogle(17, -0.3, "foo");
优先级
运算符的优先级可以通过查询表格:运算符优先级
预处理
c++推荐使用const完全代替macro的使用。我觉得pike也应该是这样的。
#define MAX_TEMP 100
当然可以用,但是并不推荐。
const MAX_TEMP = 100
是更好的选择。
当然,预处理也可以写一些简单的函数:
#define square(x) x * x
但是这样子写函数要注意,macro是直接替换的,而不是调用函数栈,所以容易存在算数优先级不明确的问题。
最好不要用include。用真正的继承比较好(这里不太明白???):
inherit .somefile;
macro比较能用的地方时声明源文件的字符编码:
#charset <*charset-name*>
可能这也是为什么没有废掉macro的原因所在吧。
默认的字符集是 iso8859-1,可以换成utf-8或者iso2022.
神奇的预处理常量
LINE:表示现在处于哪一行
DATA:变成month day year的时间
TIME:变成24小时制的时间:hh:mm:ss
模块
模块是一系列的插件。甚至可以用c或者c++来写。
pike提供了一系列的模块:
Stdio:标准输入输出
GTK:图形图像
Image:图片处理
Protocols:网络络协议
MIME:用来编码和解码MIME
Crypto:加密(我喜欢)
Calendar:日历
Sql:数据库操作相关
Thread:多线程
Process:多进程
Getopt:命令行参数
LR:语法分析器
Yp:网络信息系统的支持
Gz:解压缩文件
Regexp:正则表达式的匹配
如何使用这些模块
比如说标准输入:
string name = Stdio.stdin->gets();
比如说网络:
Protocols.HTTP.Query web_page;
如果我们不想写前缀:
import Stdio;
import Protocols.HTTP;
如果两个模块的函数名有冲突,会使用最新import的模块,谨记,这种错误很难被发现,所以要特别小心!!!
创建模块
用c和c++来创建模块不再这个讨论范围内,让我们来定义一个简单的模块吧:
创建一个trig.pmod
文件。
输入:
float cos2(float f)
{
return pow(cos(f), 2.0);
}
constant PI = 3.1415926535;
如何使用这些自定义模块呢?
int main()
{
write("The square of cos(pi/4) is " +
.trig.cos2(.trig.PI/4) + ".\n");
return 0;
}
当然也可以导入后再用:
import .trig; // Import the module from this directory
int main()
{
write("The square of cos(pi/4) is " +
cos2(PI/4) + ".\n");
return 0;
}
pike是如何找模块的?
大概遵循这个步骤:
如果前缀有.表示在本地目录下查找
如果有路径,包含路径名
在add_module_path中的路径
在命令行中用-M 指定的路径
在环境变量中的PIKE_MODULE_PATH
内置模块目录中的:/usr/local/pike/7.4.1/lib/modules/ or C:\Program Files\Pike\lib\pike\modules
pike是如何找到这个文件的呢?
查找到module-name.pmod文件,拷贝
查找到module-name.pmod目录,创建一个包含目录下所有文件的文件出来
查找到module-name.pmod.pike文件,拷贝
查找到module-name.so,文件会动态的链接c和c++编译的库。
错误处理
pike包括warning和error两种类型的警告。
waring通常不会显示出来,通过声明#pragma strict_types
才能显示warning。
或者也可以再运行pike的时候这样:
pike -rT myprogram.pike
效果一样的。
pike里面,空对象会默认赋值为0。所以使用对象前都需要判断一下是否为0.
处理错误的方式:
1、终止程序并打印错误
if(result_of_something == 0)
{
werror("Failed to open the file called '" +
file_name + "'\n");
exit(1);
}
2、输出错误到文件里面
webbrowser.pike cod.ida.liu.se > output.txt
错误码
不同类型的错误有不同的错误码。
比如说文件打开失败的错误:
Stdio.File localfile = Stdio.File();
if(!localfile->open(file_name, "r"))
{
werror("Couldn't open '" + file_name + "'.\n");
werror(strerror(localfile->errno()) +
" (errno = " + localfile->errno() + ").\n");
exit(1);
}
通过localfile->errno()
来获取错误码。
catch-throw
这部分可能无需赘述,直接看代码吧:
if(result_of_something == 0)
throw("Failed to open the file called '" +
file_name + "'\n");
mixed result = catch {
i = klooble() + 2;
fnooble();
j = 1/i;
};
if(result == 0)
write("Everything was ok.\n");
else
write("Oops. There was an error.\n");
void drink_coffee()
{
if(coffe_pot == 0)
throw("No coffe-pot.");
}
void eat_dinner()
{
eat_main_course();
eat_dessert();
drink_coffee();
}
int main()
{
mixed result = catch {
eat_dinner();
};
if(result == 0)
write("Everything was ok.\n");
else
write("There was an error: " + result + "\n");
return 0;
}
int eat_dinner()
{
if(eat_main_course() == 0)
return 0;
if(eat_dessert() == 0)
return 0;
if(drink_coffee() == 0)
return 0;
return 1;
}
mixed result = catch {
koogle(0, 3.0, "foo");
};
if(result == 0)
write("Everything was ok.\n");
else if(objectp(result))
{
write("There was an error.\n");
write("Type of error: " + result->error_type + "\n");
write("Description of the error:\n");
write(result->describe());
write("Done.\n");
}
else {
write("There was some other type of error.\n");
}