函数在现在的编程语言教材里,占的分量不大,尤其是java语言的编程书籍,但我认为函数是程序开发的重要基石,真正弄懂了函数,才算真正的会编程,函数还没有真正弄明白,就先不要去搞什么io,gui,线程那些玩意,有些同学总说C++难学,其实是看见各种千奇百怪的函数原型,不知该如何调用,把函数真正搞明白了,再看联机手册,肯定不会再像天书难懂了。
先从c++说起,(c++和c语言语法上是有区别的,但也不大,但我是不太爱区分的,后缀名一般用cpp,仍用printf,少用cout,cin),也可以说先从C语言中的函数说起。如果说java代码是由类构成的,那么C语言就是由函数构成的。
在我给学生讲解C语言的函数时,让学生记住3句话:
1.函数的参数就是函数内部的变量(我上课时把变量叫做空间,目前正在编一本用空间讲述C,JAVA的教材,这篇文章的内容就是教材的一部分)。
2.在调用的时候要给参数(这里可以换成变量或空间)赋值,记住是给函数内部的变量赋“值”,没有什么所谓“传址”,“传引用”,(c++中有引用,始终理解不好,所以不提倡使用,有指针足够用了,看了很多代码,引用很少有人用)
3.在传递数组的时候,只是把数组的首地址传进去,不能把数组的每个值都传进去。传多维数组的时候除了第一维,其它维的长度一定要写在参数里。
有函数如下:
void exampleFunc(int a,int b,int *c ,char d[],char e[][4])
{
int m=0;
int n=0;
m=a;
b=n; //不会修改下面调用中传入的k
*c=m; //会修改下面调用中的k的值,
d[0]=(char)a;
e[1][0]=n;
}
如果调用的话有如下代码:
void testExampleFunc1()
{
int k=4;
char e[][6]={"abc","edf"};
char h[]=“abcdef”;
exampleFunc1(2,k,&k,h,e);
int *p;
p=&k;
exampleFunc1(2,k,p,h,e);//如果你有一个指针p,当然可以把p的值直接传进去
p=h;
exampleFunc1(2,k,p,p,e);//指针p可以当数组传进去
}
解释一下:
1.参数里面的a,b,c,d,e和函数里的m,n一样都是函数内部的变量,在一个作用域里面,可以互相赋值,数组的写法和在函数里的写法不一样,这个第三条讲。这条虽然简单,但能消除你对参数的陌生和恐惧。
2.在调用的时候,按照函数的类型依次传值进去,可以直接传入常量2到参数a,可以把k里的值复制到参数b里,c是指针型变量,需要一个地址,&k正好表示k的地址,如果你有一个指针p,当然可以把p的值直接传进去。剩下两个是数组,第三条讲。看似很简单,但是要注意“复制”这两个字,把一个变量传入到参数中,是把值复制进去,参数中的值无论怎么修改都不会改变传入的变量,要想通过函数修改变量怎么办,很简单把变量的地址传进去,变量的地址用什么存,指针,指针的内容能不能改呢,也能,把指针的地址传进去,用什么存,二阶指针,不管怎么说,都是复制值,只不过是不同的值,相同阶数的不同类型变量可以互相转换赋值,不同阶数的变量严格禁止转换赋值,所以传递参数时一定保证阶数匹配。
3.d和e是两个数组型参数,传递时传首地址就行,h不用下标单独使用,就表示首元素的地址,和&h[0]的意思完全一样,参数里的数组和函数里定义的数组是完全不一样的,一定要主意,当然指针p也可以当数组传进去,想传递数组的时候,用个指针变量做参数也是完全可以的。参数里char d[]和char *d其实是一样的。注意二维数组的写法。
会了这些内容,尤其是数组的传递,已经能看懂大多数函数原型了,但是还会遇见一些很古怪写法的参数,不要害怕,那一定是函数指针,传递个函数名进去就可以了,当然这个函数的原型要匹配,看着挺复杂,传起来却简单。函数指针也是一种空间,用来存函数的地址。
最后,一般还会出道题,看是不是真的掌握了:
有5个函数
void func1(char c);
void func2(char *c);
void func3(char c[]);
void func4(char *c[]);
void func5(char **c);
现有两个数组
char m[5];
char *n[5];
写出用m,n做传入变量调用5个函数的写法,有几种写几种。先不公布答案,大家试着写一下。
说完了c,就可以说说java了,对于java而言,函数要简单一些,只用记住前两句话就行了。
class Example
{
private int a=0;
public int b=0;
void func(int m,int n[],Example l)
{
a=m;
n[0]=a;
l.b=a+1;
l=new Integer(45);
}
}
调用
class TestExample
{
void testFunc(int m,int n[])
{
Example example=new example();
Example m=new example();
int a[]=new int[5];
int k=3;
example.func(k,a,m);
}
}
在java里,数组是一种对象,直接把数组对象传进去就可以了,关键还是要把数组弄明白了,这里不多讲,主要讲一点,参数Example l,l是对象句柄,其实就是指针,JAVA里所有复合类型变量都是指针,所有基本类型变量都是非指针,调用的时候k的值怎么都不会改变,数组a的第0个元素会改变,l在未重新赋值之前,存的是和m相同的值,m和 l指向同一对象,l.b=a+1修改了m的成员变量b的值, l=new Integer(45); 之后,l存入了新对象的地址,和m不会再有任何瓜葛,说来说去,不明白指针想把java学明白是很不容易的,把指针弄明白了,搞任何语言都很容易,下面说javascript还会说指针,另外还要注意的是函数里可以使用成员变量, 这个问题也不是函数的问题,主要是静态成员变量,成员变量和静态函数和成员函数之间的调用关系,也不再这里讨论了。
最后说说javascript,这是一门解释性语言,也是一门面向对象的语言,php,perl.ruby,python等都是解释性语言,解释性语言基本上都是弱类型语言,和c,java 等有较大的差别,主要特点是变量不用定义类型,不同类型之间随便赋值,写起来很随意,不好的地方就是类型匹配的错误无法检查,只会在运行时发生,其实实现原理并不复杂,在c语言里,指针虽然有类型,但所占有的空间大小还是一样的,所有指针都是可以互相赋值的,在弱类型语言里,所有存储数据,对象的空间的地址都有指针保存,都必须通过指针进行访问,所有的变量其实都是指针。
看代码
function jsFunc(a,b,c)
{
a.name="I was changed!";
b="Test is changed";
c[3]=5;
}
var myObj = new Object();
myObj.name = "Fred";
myObj.age = 42;
n="Test";
k=new Array();
k[5]=23;
jsFunc(myObj,n,k);
alert(myObj.name);
alert(n);
alert(k);
有三个参数a,b,c,调用的时候,分别传入了3个地址,myObj 的值传给了参数a,a和myObj指向同一个对象,a修改了属性name,myObj的属性name被修改,n的值传给了参数b,b的值被重新赋值,b指向了新的对象,n的值不会有任何变动,这里数组也是一个对象,k存储了这个对象的地址,k的值传给了参数c,c就可以访问数组中元素,并进行修改,javascript里面,String,Number,Date等对象和java里的String类很像,只能通过新建对象来修改值,没有成员函数可以修改值。
在3种语言里,函数参数的传递都是一样的,都是值传递,值可以是数据,也可以是地址,参数是函数内部的变量,修改参数的值不会引起函数外变量的改变,但是通过参数的值可以修改参数指向的空间,也就是函数外的空间。