文章目录
2.6 函数
2.6.1 函数的定义
函数是一个实现一定功能的语句的集合。它可以使代码更加简洁和清晰,不必将一些语句多次重复写一遍。
函数的基本语法格式如下所示:
返回类型 函数名称(参数类型 参数){
函数主体
}
若是小括号里没有添加参数,我们就称这个函数为无参函数
,否则就为有参函数
。C语言中还会使用return来返回函数需要传回的数据,且传回参数的数据类型必须跟函数的返回类型一致。这里有一个特殊的返回类型void
,它表示空,使用它的时候我们就不需要返回参数了。
(1)全局函数
全局函数是指在定义之后的所有程序段内都有效的变量。
#include <stdio.h>
// 全局变量x
int x;
void change(){
x = x + 1;
}
int main(){
x = 10;
change();
printf("%d\n", x);
return 0;
}
// 输出结果:
// 11
所有在定义x之后的程序段都共用这个x,所以使用change()函数可以直接改变x的值。
(2)局部变量
局部变量定义在函数内部,且旨在函数内部生效。函数结束就会被销毁。
#include <stdio.h>
void change(int x){
x = x + 1;
}
int main(){
int x = 10;
change(x);
printf("%d\n", x);
return 0;
}
// 输出结果:
// 10
由于局部变量只在函数内部生效,所以change()函数中的x被赋值之后直接就被销毁了,所以main()函数中输出的x仍然是在其中定义的x的值10。
注意
,main()函数中的x和change()函数中的x不是同一个。大家可以理解为当我们在main()函数中通过change()函数传递x的值时,系统另外开辟了一个空间,也取名叫做x,我们把原先x中的值给了它,而这个x中的值进行的一系列操作,并不会影响原先的x。就像有两个人都叫小明一样。
这种传递参数的方式称为值传递
,函数定义的小括号内的参数称为形式参数
或形参
;而把实际调用时小括号内的参数称为实际参数
或实参
。
2.6.2 再谈main()函数
主函数对于程序来说只能有一个。无论它在程序的哪个位置,整个程序都是从它的第一个语句开始执行,然后在需要调用其他函数的时候才会去调用其他函数。
int main(){
…
return 0;
}
现在我们再看main()函数可以发现,main就是函数名称,由于小括号里没有参数,所以它还是一个无参函数,返回类型是int类型,并且在函数主体的最后都返回了0。这个0的意义就在于告知计算机系统程序是正常终止的。
2.6.3 以数组作为函数参数
如果是数组作为函数参数,数组的第一维不需要填写长度,实际调用时也只需填写数组名。而且数组作为参数时,在函数中对数组元素的修改就相当于对原数组元素的修改(和局部变量不一样)。不过数组不允许作为返回类型出现,所以会有下面的方法,将数组作为参数传入。
#include <stdio.h>
void change(int a[], int b[][5]){
a[0] = 1;
a[1] = 3;
a[2] = 5;
b[0][0] = 1;
}
int main(){
int a[3] = {0};
int b[5][5] = {0};
change(a, b);
for(int i=0; i<3, i++){
printf("%d\n", a[i]);
}
return 0;
}
// 输出结果
// 1
// 3
// 5
2.6.4 函数的嵌套调用
函数的嵌套调用就是指在函数中调用另一个函数,跟在main()函数中调用其他函数的方法一样。
2.6.5 函数的递归调用
函数的递归调用就是函数自己调用自己的过程。
2.7 指针
2.7.1 什么是指针
在计算机中,我们程序中的每个变量都会存放在内存中。内存中的空间就像是一个一个的小房间,变量就住在里面。我们可以把一个字节理解为一个房间,一个int型的变量就需要占用连续的4个房间。有了房间,自然要有房间号,要不然我们怎么知道到哪里去找它呢?房间号就类似于计算机中说的地址,通过地址我们就能找到变量。变量的地址一般指它占用的字节中第一个字节的地址。
在C语言中,用“指针”来表示内存地址,初学者可以简单地认为指针就是变量的地址(当然这么说不那么严谨)。怎样获得指针呢?可以用前面提到的取址符&
,只要在变量的前面加上&,就代表这个变量的地址。指针是一个unsigned类型的整数。
2.7.2 指针变量
指针变量就是用存放指针的变量,类似于int型变量就是存放int型常量。它的定义与普通变量的定义有些不同,格式如下:
int* p1; // 等价于int *p1;
double* p2; // 等价于double *p2;
char* p3; // 等价于char *p3;
这个“*”号可以添加在数据类型之后,也可以添加到变量之前,看自己的风格。但大家要牢记int*
等形式才是指针变量的类型。我们在存储地址的时候,赋值给p而不是*p。其次,这里的int、double和char又叫做变量的基类型
,基类型必须和指针变量存储的地址类型相同。
另外,如果一次有好几个同种类型的指针变量要同时定义,星号只会结合第一个变量,如:
int* p1, p2;
上面语句中的p1是int*型的,p2则是int型的。如果要定义多个int*型的变量,我们必须写成如下形式才行:
int* p1, *p2, *p3;
// 或许这样更好看
int *p1, *p2, *p3;
若是要取地址然后赋值给同种类型的指针变量,还可以这样定义:
int a;
int* p = &a;
// 或者
int a;
int* p;
p = &a;
那么,如果我们要取出地址内的东西该如何操作呢?还是使用星号,我们可以将它看成是开门的钥匙,于是*p
就代表地址内的值。注意
,我们经常会改变地址内的值,只要地址不变,其他指向这个地址的数值会跟着改变。
指针变量同时支持加减法和自增自减
操作。它的加减法的结果是整个地址的偏移。比如对于int*型的指针变量p来说,p+1指的是p所指的int型变量的下一个int型变量地址,这里的下一个跨越了一整个int型字节内存。如果p指向地址1,那么p+1就是跨越了2、3、4指向地址5。
2.7.3 指针与数组
数组是由地址上连续的若干个相同类型的数据组合而成。也是因为地址连续,只要知道数组首个数据的地址,我们就能知道数组其他数据的地址。在C语言中,数组名称也作为数组的首地址使用,对于数组a,a == &a[0]
是成立的。
前面提到过指针变量支持加减法,很容易推出对于数组a,下面几个等式也都是成立的:
a+i == &a[i];
*(a+i) == a[i];
scanf("%d", a+i); // 等价于scanf("%d", &a[i]);
printf("%d", *(a+i)); // 等价于printf("%d", a[i])
顺便提一下,两个int型的指针相减,等价于在求两个指针之间相差了几个int。
2.7.4 使用指针变量作为函数参数
此时视为将变量的地址传入函数,如果在函数中改变这个地址中的元素,那么原先的数据也会跟着改变。这种传递方式叫做地址传递
,与值传递的方式对应。总结一下就是,**只有在获取地址的情况下对元素进行操作,才能真正地修改变量。**数组也是一样。
这里总结一下两种初学者常犯的错误写法
(这里以交换a和b的值为例):
// 第一种错误写法:
void swap(int* a, int* b){
int* temp;
*temp = *a;
*a = *b;
*b = *temp;
}
这里的代码看似正确,但对于指针来说是有问题的,问题在于temp。由于没有初始化,指针变量temp中存储的地址是随机的,可能会指向系统工作区间,这样就会出错!当然,如果我们只是定义一个变量就不会出现这种问题,所以我们可以很好解决这个问题。
// 第一种错误写法修改:
void swap(int* a, int* b){
int x;
int* temp = &x;
*temp = *a;
*a = *b;
*b = *temp;
}
第二种错误写法则是想直接更换两个变量的地址:
// 第二种错误写法
void swap(int* a, int* b){
int* temp = a;
a = b;
b = temp;
}
main()函数传给swap()函数的地址其实是一个“无符号整型”的数,其本身跟普通变量一样只是“值传递”。在swap()函数中对地址本身的修改,并不会更改main()函数中的地址。
2.7.5 引用
- 引用的含义
引用是C++中一个强有力的语法,当我们想不使用指针也达到修改传入参数的目的的时候,一个很方便的方法就是使用“引用”。引用不产生副本,而是给原变量起了一个别名,对引用变量的操作就是对原变量的操作。
使用引用我们只需要在参数类型后面加个&
就可以了,至于加在类型后还是变量名前都可以,一般我们加在变量名前面。由于这是C++中的语法,所以使用时我们要将文件保存为.cpp
格式。
#include <stdio.h>
// 这里&x中的x不一定要和原来的相同,写成&a也完全可以!
// 函数的参数名和实际传入的参数名可以不同!!
void change(int &x){
x = 1;
}
int main(){
int x = 10;
change(x);
printf("%d\n", x);
return 0;
}
// 输出结果:
// 1
- 指针的引用
在2.7.4节的错误写法二中,我们无法通过直接修改传入的地址来改变两个变量,这是因为对指针变量本身的修改无法作用到原指针变量上。但是在这里,我们可以使用引用来实现上面的效果:
#include <stdio.h>
void swap(int* &p1, int* &p2){
int* temp = p1;
p1 = p2;
p2 = temp;
}
int main(){
int a=1, b=2;
// 使用指针变量存放地址,才能修改地址,否则传进去的是常量。
int *p1=&a, *p2=&b;
swap(p1, p2);
printf("a = %d, b = %d", *p1, *p2);
return 0;
}