C Program Ⅱ 函数

什么是函数?函数(function)是完成特定任务的独立程序代码单元。

为什么要使用函数?首先,使用函数可以省去编写重复代码,提高效率。其次,让程序更加模块化,从而提高了程序代码的可读性,更方便后期修改、完善。如果不是自己编写函数,根本不用关心黑盒的内部行为;以这种方式看待函数有助于把注意力集中在程序的整体设计,而不是函数的实现细节上

以下面程序为例分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <string.h> /* for strlen() */
#define NAME "GIGATHINK, INC."
#define ADDRESS "101 Megabuck Plaza"
#define PLACE "Megapolis, CA 94904"
#define WIDTH 40
#define SPACE ' '

void show_n_char(char ch, int num);

int main(void)
{
int spaces;

show_n_char('*', WIDTH); /* using constants as arguments */
putchar('\n');
show_n_char(SPACE, 12); /* using constants as arguments */
printf("%s\n", NAME);
spaces = (WIDTH - strlen(ADDRESS)) / 2;
/* Let the program calculate */
/* how many spaces to skip */
show_n_char(SPACE, spaces);/* use a variable as argument */
printf("%s\n", ADDRESS);
show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2);
/* an expression as argument */
printf("%s\n", PLACE);
show_n_char('*', WIDTH);
putchar('\n');

return 0;
}

/* show_n_char() definition */
void show_n_char(char ch, int num)
{
int count;

for (count = 1; count <= num; count++)
putchar(ch);
}

程序在3处使用了show_n_char()标识符:函数原型(function prototype)告诉编译器函数show_n_char()的类型;函数调用(function call)表明在此处执行函数;函数定义(function definition)明确地指定了函数要做什么。

函数和变量一样,有多种类型。任何程序在使用函数之前都要声明该函数的类型。因此,在main()函数定义的前面

1.对于函数声明

出现了下面的ANSI C风格的函数原型:

1
void show_n_char(char ch, int num);

圆括号表明show_n_char()是一个函数名。第1个void是函数类型,void类型表明函数没有返回值。后面的char,int(在圆括号中)表明该函数带有两个参数,一个为char类型,一个为int类型。分号表明这是在声明函数,不是定义函数。函数原型指明了函数的返回值类型和函数接受的参数类型。这些信息称为该函数的签名(signature)。

程序把 show_n_char()原型置于main()的前面。当然,也可以放在 main()里面的声明变量处。放在哪个位置都可以。

2.对于函数参数

还是以这个有ANSI C风格的函数头为例:

1
void show_n_char(char ch, int num)

该行告知编译器show_n_char()使用两个参数ch和num,ch是char类型,num是int类型。这两个变量被称为形式参数(formal argument,但是最近的标准推荐使用formal parameter),简称形参。和定义在函数中变量一样,形式参数也是局部变量,属该函数私有。

注意,ANSI C要求在每个变量前都声明其类型。也就是说,不能像普通变量声明那样使用同一类型的变量列表:void dibs(int x, y, z)     / 无效的函数头 /

当函数接受参数时,函数原型用逗号分隔的列表指明参数的数量和类型。根据个人喜好,也可以省略变量名:

1
void show_n_char(char, int);

在函数调用中,实际参数(actual argument,简称实参)提供了ch和num的值。考虑上例中第1次调用show_n_char()

1
show_n_char(SPACE, 12);

实际参数是空格字符和12。这两个值被赋给show_n_char()中相应的形式参数:变量ch和num。简而言之,形式参数是被调函数(called function)中的变量,实际参数是主调函数(calling function)赋给被调函数的具体值。

1.注意 实际参数和形式参数:实际参数是出现在函数调用圆括号中的表达式。形式参数是函数定义的函数头中声明的变量。

2.为了表明函数确实没有参数,应该在圆括号中使用void关键字:

1
2
> void print_name(void);
>

3.关于return从函数中返回值

函数的返回值可以把信息从被调函数传回主调函数。关键字return后面的表达式的值就是函数的返回值。

问题:如果函数返回值的类型与函数声明的类型不匹配会怎样?

1
2
3
4
5
int what_if(int n)
{
double z = 100.0 / (double) n;
return z; // 会发生什么?
}

实际得到的返回值相当于把函数中指定的返回值赋给与函数类型相同的变量所得到的值。因此在本例中,相当于把z的值赋给int类型的变量,然后返回int类型变量的值。例如,假设有下面的函数调用:

1
result = what_if(64);

虽然在what_if()函数中赋给z的值是1.5625,但是return语句返回确实int类型的值1。

4.查找地址:&运算符

指针(pointer)是 C 语言最重要的(有时也是最复杂的)概念之一,用于储存变量的地址。前面使用的scanf()函数中就使用地址作为参数。概括地说,如果主调函数不使用return返回的值,则必须通过地址才能修改主调函数中的值。一元&运算符给出变量的存储地址。如果pooh是变量名,那么&pooh是变量的地址。

下面例子中使用了这个运算符查看不同函数中的同名变量分别储存在什么位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* loccheck.c  -- checks to see where variables are stored  */
#include <stdio.h>
void mikado(int); /* declare function */
int main(void)
{
int pooh = 2, bah = 5; /* local to main() */

printf("In main(), pooh = %d and &pooh = %p\n",
pooh, &pooh);
printf("In main(), bah = %d and &bah = %p\n",
bah, &bah);
mikado(pooh);

return 0;
}

void mikado(int bah) /* define function */
{
int pooh = 10; /* local to mikado() */

printf("In mikado(), pooh = %d and &pooh = %p\n",
pooh, &pooh);
printf("In mikado(), bah = %d and &bah = %p\n",
bah, &bah);
}

运行结果:

首先,两个pooh的地址不同,两个bah的地址也不同。因此,和前面介绍的一样,计算机把它们看成4个独立的变量。其次,函数调用mikado(pooh)把实际参数(main()中的pooh)的值 2 传递给形式参数(mikado()中的bah)。注意,这种传递只传递了值。涉及的两个变量(main()中的pooh和mikado()中的bah)并未改变。

5.使用指针在函数间通信

指针?什么是指针?从根本上看,指针(pointer)是一个值为内存地址的变量(或数据对象)。正如char类型变量的值是字符,int类型变量的值是整数,指针变量的值是地址。

声明指针变量时必须指定指针所指向变量的类型,因为不同的变量类型占用不同的存储空间,一些指针操作要求知道操作对象的大小。另外,程序必须知道储存在指定地址上的数据类型。longfloat可能占用相同的存储空间,但是它们储存数字却大相径庭。

1
2
3
int * pi;   // pi是指向int类型变量的指针
char * pc;    // pc是指向char类型变量的指针
float * pf, * pg; // pf、pg都是指向float类型变量的指针

类型说明符表明了指针所指向对象的类型,星号()表明声明的变量是一个指针。int pi; 声明的意思是pi是一个指针,*piint类型

pc指向的值(pc)是char类型。pc本身是什么类型?我们描述它的类型是“指向char类型的指针。pc 的值是一个地址,在大部分系统内部,该地址由一个无符号整数表示。但是,不要把指针认为是整数类型。一些处理整数的操作不能用来处理指针,反之亦然。例如,可以把两个整数相乘,*但是不能把两个指针相乘。所以,指针实际上是一个新类型,不是整数类型。因此,如前所述,ANSI C专门为指针提供了%p格式的转换说明。

下面例子就介绍了指针在函数间通信:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* swap2.c -- researching swap1.c */
#include <stdio.h>
void interchange(int *u, int *v);

int main(void)
{
int x = 5, y = 10;

printf("In main Originally x = %d and y = %d.\n", x , y);
interchange(&x, &y);
printf("In main Now x = %d and y = %d.\n", x, y);

return 0;
}

void interchange(int *u, int *v)
{
int temp;

printf("In interchange Originally u = %d and v = %d.\n", *u, *v);
temp = *u;
*u = *v;
*v = temp;
printf("In interchange Now u = %d and v = %d.\n", *u, *v);
}

运行结果:

该函数传递的不是x和y的值,而是它们的地址。这意味着出现在interchange()原型和定义中的形式参数u和v将把 地址作为它们的值。因此,应把它们声明为指针。

interchange(int *u, int *v)函数中 * u = x, * v = y;即 u = &x, v = &y;

*u = *v;——>把 v 的地址赋值给 u; *v = temp;——>把temp的地址赋值给v。

uv 互换了地址。

猜想:如果直接int * temp;是否也能改变值?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
void interchange(int *u, int *v);

int main(void)
{
int x = 5, y = 10;

printf("In main Originally x = %d and y = %d.\n", x , y);
interchange(&x, &y);
printf("In main Now x = %d and y = %d.\n", x, y);

return 0;
}

void interchange(int * u, int * v)
{
int *temp;

printf("In interchange Originally u = %d and v = %d.\n", *u , *v);
temp = u;
u = v;
v = temp;
printf("In interchangeo Now u = %d and v = %d.\n", *u, *v);
}

实践证明是不行的,因为定义指针temp后的交换与定义int temp,然后interchange(int u, int v)这样交换是一样的,都是同类型的交换。

对于指针与地址的实验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
int main(){
int n = 5;
int *p;
p = &n;
int temp = *p;
int *v;
v = &temp;
printf("n = %d,the loacl of n = %p\n",n,&n);
printf("*p = %d,the loacl of p = %p\n",*p,p);
printf("temp = %d,the loacl of temp = %p\n",temp,&temp);
printf("*v = %d,the loacl of v = %p\n",*v,v);
return 0;
}

为什么temp的地址与* v的地址不一样呢,因为temp是新定义的变量,编译时给temp新分配了一个地址。

0%