第4章函数
函数是构成GeoGeo的主体,通过一系列的函数调用实现程序的功能。
GeoGeo目前有4类函数,它们是:
1. 普通函数普通函数即通常意义上调用的函数,可以声明为有或者没有返回值。
2. 线程函数由thread关键字声明的函数,这种函数运行时另起线程独立运行。
3. 进程函数由 process关键字声明,这种函数在本机或者其它节点机上另起进程独立运行。
4. 事件函数由event关键字声明,响应事件的函数。
4.1普通函数的声明和定义
GeoGeo在执行前进行预扫描,并且不需要编译成二进制代码,因此GeoGeo函数无需前向声明。也可以认为GeoGeo只有定义,没有声明。
这样的写法是合法的:
main(){
func1();
}
func1(){
…
}
函数声明和定义格式如下:
[<返回类型>] <函数名> ( [<类型名> <参数名>[,…]])<代码块>
4.2函数的返回值
GeoGeo函数的可以有或者没有返回值。没有返回值的函数直接写函数名(如上一节例子),有返回值的函数在定义时开头写上返回类型。如:
double func1(){
…
return 0.0;
}
表明该函数返回一个双精度浮点数据。一个返回字符串类型的例子如下:
程序清单 4.1 4-1-函数.c
main(){
//无返回值函数
func1();
double db = func2();
Print("%f",db);
STRING str = func3();
Print("%s",str);
double x = 20.0;
int a=10;
funcX(x,a);
Print("函数调用后x值为%f。",x);
return 1;
}
//无返回值函数
func1(){
Print("func called !");
//这里的 return可以写或者不写。
return;
}
//返回值一个浮点数据的函数
float func2(){
return 3.14;
}
//返回值不可以是数组,但是可以使用字符串类型
STRING func3(){
return"这是一个字符串返回类型";
}
funcX(double x1,int x2){
x1 = x1+x2;
return;
}
代码运行的结果为:
func called !
3.140000
这是一个字符串返回类型
函数调用后x的值为30.000000。
第一个函数func1没有返回值,return语句可以写也可以不写,在函数func1中输出“func called !”。第二个函数func2返回类型为4字节浮点型,函数调用返回后,将这个4字节浮点数赋值给一个8字节双精度浮点类型,函数返回值对数值类型变量的赋值自动进行。第三个函数func3返回一个字符串类型,字符串类型的返回值如果用于对数值类型变量赋值会引起类型错误。
4.3函数的参数传递
GeoGeo函数的参数可以使用预定义数据类型或者自定义类型,也可以使用数组。GeoGeo函数在调用时传递的实元有两种:
①使用已定义的变量名传递,这时参数是引用方式传递。
②使用表达式传递,这时参数是传值的。
4.3.1函数参数的引用传递方式
GeoGeo是设计用来处理大数据集的,是一种围绕数据的加工方式。假如你有一个非常庞大的数据体需要处理,将这样庞大的数据在函数间进行传输、复制显然是非常不合理的。这时可以保持数据不动,将数据的接口暴露给函数,函数围绕这个数据体进行加工处理。GeoGeo函数参数的引用可以满足这种加工方式。
下面是1个引用方式传递函数参数的例子.
程序清单 4.2 4-2-函数参数-引用.c
1 main(){
2 double x = 20.0;
3 int a=10;
4 func(x,a);
5 Print("函数调用后x值为%f。",x);
6 }
7 func(double x1,int x2){
8 x1 = x1+x2;
9 }
代码运行的结果为:
函数调用后x值为30.000000。
上述代码在第2行申明了一个变量x,赋初始值为20。在第4行作为参数传递给函数func的哑元x1。
对于某些语言传值的函数参数传递的情况(除声明为引用),x将20传递给x1,在函数func中对x1进行运算操作,函数返回时x的值仍然为20。(参见c/c++函数参数的传值)。
GeoGeo的函数参数均为引用(参见c/c++函数参数的引用),参数x在传递到函数func时,仅为变量x取了一个别名x1,对两个名称中任何一个的操作都是对原来的变量x的操作,因此,在函数func中进行了 x1 = x1+x2操作后,函数返回时x值是30而不是20。
4.3.2函数参数的类型匹配
函数定义时的参数列表声明的变量类型必须与调用时使用的参数列表一致,否则会引起类型错误。这是因为2个列表实际使用的是一套同样的变量,因此必须有一样的数据类型。下面的例子是非法的:
double x = 20.0;
int a=10;
func(x,a);
…
func(float x1,int x2){
…
}
调用函数的使用的参数与函数声明时参数不一致。
4.3.3用表达式传值的函数参数
如果调用函数向下传递的参数是一个表达式(非独立变量的表达式),则创建一个临时变量向下传递,并且是传值的。将上述进行简单改动。将4.3.2中调用函数的一行
func(x,a);
改为
func(x+1,a);
这时函数的第1个参数由变量名x改成了一个表达式x+1,这时这样的写法就是合法的。并且函数调用结束后不会改变x的值。
程序清单 4.3 4-3-函数参数-传值.c
main(){
double x = 20.0;
int a=10;
func(x+1,a);
Print("函数调用后x值为%f。",x);
}
func(float x1,int x2){
Print("函数参数值为%f。",x1);
x1 = x1+x2;
}
代码运行的结果为:
函数参数值为21.000000。
函数调用后x值为20.000000。
可见函数调用后未对x值进行更改。
4.3.4自定义类型的函数参数
自定义数据类型也可以作为函数参数传递,下例:
程序清单 4.4 4-4-自定义类型函数参数.c
struct Struc{
ULONGLONG ull;
STRING name;
};
STRING func(Struc ms){
ms.name = "这是函数返回后参数值";
return"这是函数返回值";
}
main(){
Struc strc;
STRING str;
str = func(strc);
Print("%s",str);
Print("%s",strc.name);
return;
}
运行代码,输出结果为:
这是函数返回值
这是函数返回后参数值
使用结构成员做函数参数时相当于表达式参数,是传值的,将上例代码加以改动:
程序清单 4.5 4-5-自定义类型函数参数2.c
main(){
Struc strc;
strc.ull = 1234;
double a = func2(strc.ull);
Print("函数返回值:%f",a);
Print("函数返回后参数值:%d",strc.ull);
return;
}
double func2(ULONGLONG x){
x = x*2;
return x;
}
运行代码,输出结果为:
函数返回值:2468.000000
函数返回后参数值:1234
由于函数参数使用的是结构成员,等同于表达式,所以函数返回后结构成员strc.ull的值仍然是原来的1234。
4.3.5数组函数参数
各种数据类型的数组也可以作为函数参数传递,也包括自定义数据类型数组,如:
程序清单 4.6 4-6-数组函数参数.c
struct Struc{
ULONGLONG ull;
STRING name;
};
main(){
Struc strc[4];
func(strc);
int i;
STRING str;
for(i=0; i<4; i=i+1){
Format(str,"%d %s",strc[i].ull,strc[i].name);
Print(str);
}
return;
}
func(Struc s){
STRING str;
int i;
for(i=0; i<4; i=i+1){
s[i].ull = i+1;
Format(str,"第%d个数组元素",i+1);
s[i].name = str;
}
}
运行代码,输出结果为:
1 第1个数组元素
2 第2个数组元素
3 第3个数组元素
4 第4个数组元素
4.4线程函数
线程函数是一种由 thread关键字声明的函数。如
thread func(){
…
}
这类函数在被调用时,GeoGeo为其开辟1个独立的线程,并在该线程中执行该函数的代码。函数的调用方式与调用其它普通函数相同。
线程函数的参数传递总是传值的,不对调用程序的参数原值进行修改。
线程函数的返回值只返回线程的ID号。
有关线程函数与多线程方面内容在第5章专门讲述。
4.5进程函数
进程函数是将函数本身由一个独立的进程来执行。这个进程可以在本机或者其它的网络节点机上运行,以实现GeoGeo多机并行计算的目的。
这里所说的进程是指GeoGeo脚本的一个独立执行单元。严格来讲GeoGeo脚本的执行进程是GeoGeo解释程序,脚本执行单元都是脚本解释程序的子线程。在节点机上,主进程是GeoGeo脚本解释服务,脚本执行单元也是服务程序的子线程。
进程函数由 process关键字声明。如
process func(){
…
}
函数在被调用时,GeoGeo为其在节点机上(或者本机)开辟1个独立的执行单元,并在该执行单元中执行该函数的代码。函数的调用方式与调用其它普通函数相同。
进程函数的参数传递也是传值的,不对调用程序的参数原值进行修改。进程函数也没有返回值。
有关进程函数与多机并行计算方面内容在第6章讲述。
4.6事件函数
相应一系列来自用户或者系统事件的函数称为事件函数,例如用户对键盘和鼠标的操作以及窗口之间的消息传递等。
事件函数由 event关键字声明,如
event func(){
…
}
当有定义的或者内建事件发生时,事件函数被调用,以实现GeoGeo的事件驱动机制。内建事件有固定的函数参数,自定义事件可以有任意的函数参数。事件函数也没有返回值。
事件函数与事件驱动机制在第9章讲述。
4.7本章小结
1. GeoGeo有多种类型的函数,本章讲述普通的GeoGeo函数。GeoGeo函数与C函数类似,有返回类型、函数名和参数表以及由语句块组成的函数体。GeoGeo函数无需前向声明。
2. 调用函数时的实参可以是变量或者表达式。使用变量作为函数参数时该变量作为引用传递,函数执行结束后将变量的新值返回给调用函数。
3. 使用表达式作为函数的实参时将表达式求值,然后以传值方式传递给函数。此时函数形参的计算结果不被返回到调用者。
4. 其它多种类型的函数在分别的章节讲述。