带参数的函数
不能像普通变量声明那样使用同一类型的变量列表:
void f(int x, y, z) /* 无效的函数头 */
void f(int x, int y, int z) /* 有效的函数头 */
函数中的这些变量被称为形式参数。
在调用时可写成 f(1,2,3)
函数中的数值被称为实际参数。
简而言之
形式参数是被调函数(called function)中的变量,
实际参数是主调函数(calling function)赋给被调函数的具体值,
实际参数可以是常量、变量,或更复杂的表达式,
无论实际参数是何种形式都要被求值,然后该值被拷贝给被调函数相应的形式参数。
函数类型
声明函数时必须声明函数的类型;
带返回值的函数类型应该与其返回值类型相同,而没有返回值的函数应声明为void类型。
注意:函数类型指的是返回值的类型,不是函数参数的类型。
递归
//在一个函数内调用该函数本身
#include <stdio.h>
void up_and_down(int);
int main(void)
{
up_and_down(1);
return 0;
}
void up_and_down(int n)
{
printf("Level %d: n location %p\n", n, &n);
if (n < 4)
up_and_down(n + 1);
printf("LEVEL %d: n location %p\n", n, &n);
}
最简单的递归形式是把递归调用置于函数的末尾,即正好在 return 语句之前。
这种形式的递归被称为尾递归(tail recursion),因为递归调用在函数的末尾。
尾递归是最简单的递归形式,因为它相当于循环。
一般而言,选择循环比较好。
首先,每次递归都会创建一组变量,所以递归使用的内存更多,而且每次递归调用都会把创建的一组新变量放在栈中。递归调用的数量受限于内存空间。
其次,由于每次函数调用要花费一定的时间,所以递归的执行速度较慢。
递归既有优点也有缺点。
优点是递归为某些编程问题提供了最简单的解决方案。
缺点是一些递归算法会快速消耗计算机的内存资源。
另外,递归不方便阅读和维护。
编译多源代码文件的程序
使用多个函数最简单的方法是把它们都放在同一个文件中,然后像编译只有一个函数的文件那样编译该文件即可。
其他方法因操作系统而异,下面将举例说明。
1、unix
假定在UNIX系统中安装了UNIX C编译器cc(最初的cc已经停用,但是许多UNIX系统都给cc命令起了一个别名用作其他编译器命令,典型的是gcc或clang)。
假设file1.c和file2.c是两个内含C函数的文件,下面的命令将编译两个文件并生成一个名为a.out的可执行文件:
cc file1.c file2.c
另外,还生成两个名为file1.o和file2.o的目标文件。
如果后来改动了 file1.c,而file2.c不变,可以使用以下命令编译第1个文件,并与第2个文件的目标代码合并:
cc file1.c file2.o
2、linux
假定Linux系统安装了GNU C编译器GCC。
假设file1.c和file2.c是两个内含C函数的文件,下面的命令将编译两个文件并生成名为a.out的可执行文件:
gcc file1.c file2.c
另外,还生成两个名为file1.o和file2.o的目标文件。
如果后来改动了file1.c,而file2.c不变,可以使用以下命令编译第1个文件,并与第2个文件 的目标代码合并:
gcc file1.c file2.o
3、DOS命令行编译器
绝大多数DOS命令行编译器的工作原理和UNIX的cc命令类似,只不过使用不同的名称而已。
其中一个区别是,对象文件的扩展名是.obj,而不是.o。
一些编译器生成的不是目标代码文件,而是汇编语言或其他特殊代码的中间文件。
4、Windows和苹果的IDE编译器
Windows和Macintosh系统使用的集成开发环境中的编译器是面向项目的。
项目(project)描述的是特定程序使用的资源。资源包括源代码文件。
这种IDE中的编译器要创建项目来运行单文件程序。
对于多文件程序,要使用相应的菜单命令。
把源代码文件加入一个项目中。
要确保所有的源代码文件都在项目列表中列出。
许多IDE都不用在项目列表中列出头文件(即扩展名为.h的文件),因为项目只管理使用的源代码文件,源代码文件中的 #include指令管理该文件中使用的头文件。
但是,Xcode要在项目中添加头文件。
5、头文件
如果把main()放在第1个文件中,把函数定义放在第2个文件中,那么第1个文件仍然要使用函数原型。
把函数原型放在头文件中,就不用在每次使用函数文件时都写出函数的原型。
C 标准库就是这样做的,例如,把I/O函 数原型放在stdio.h中,把数学函数原型放在math.h中。
你也可以这样用自定义的函数文件。
//头文件举例
//hook.h
#include <windows.h>
//假设以下函数在同一项目的key.cpp中
extern "C" __declspec(dllexport) BOOL StartHook(const char* windName, const char* user);
extern "C" __declspec(dllexport) BOOL StopHook(const char* user);
//hook.cpp
#include "hook.h" //注意使用""而不是<>
//接下来就可以在该文件中使用key.cpp中的函数
//#define也可以写在头文件中
查找地址:&运算符
一元&运算符给出变量的存储地址。
如果pooh是变量名,那么&pooh是变量的地址。
可以把地址看作是变量在内存中的位置。
假设有下面的语句:
pooh = 24;
假设pooh的存储地址是0B76(PC地址通常用十六进制形式表示)。
那么下面的语句:
printf("%d %p\n", pooh, &pooh);
将输出如下内容(%p是输出地址的转换说明):
24 0B76
指针
从根本上看,指针(pointer)是一个值为内存地址的变量(或数据对象)。
正如char类型变量的值是字符,int类型变量的值是整数,指针变量的值是地址。
假设一个指针变量名是ptr,可以编写如下语句:
ptr = &pooh; // 把pooh的地址赋给ptr
对于这条语句,我们说ptr“指向”pooh。
ptr和&pooh的区别是ptr是变量, 而&pooh是常量。
或者,ptr是可修改的左值,而&pooh是右值。
还可以把ptr 指向别处:
ptr = &bah; // 把ptr指向bah,而不是pooh
现在ptr的值是bah的地址。
要创建指针变量,先要声明指针变量的类型。假
设想把ptr声明为储存 int类型变量地址的指针,就要使用下面介绍的新运算符。
间接运算符:*
示例:
nurse = 22;
ptr = &nurse; // 指向nurse的指针
val = *ptr; // 把ptr指向的地址上的值赋给val
不能写成 val = ptr ;
执行以上3条语句的最终结果是把22赋给val。
指针的声明示例:
int * pi; // pi是指向int类型变量的指针
char * pc; // pc是指向char类型变量的指针
float * pf, * pg; // pf、pg都是指向float类型变量的指针
//int * pi; 声明的意思是pi是一个指针,*pi是int类型
编程练习
1、
设计一个函数min(x, y),返回两个double类型值的较小值。
在一个简单的驱动程序中测试该函数。
#include <stdio.h>
double min(x,y);
int main(void)
{
double x, y;
double m;
printf("Enter first number: ");
scanf("%lf", &x);
printf("Enter second number: ");
scanf("%lf", &y);
m = min(x, y);
printf("the smaller number is %lf\n", m);
return 0;
}
double min(double x, double y)
{
if (x > y)
return y;
else
return x;
}
2、
设计一个函数chline(ch, i, j),打印指定的字符j行i列。
在一个简单的驱动程序中测试该函数。
#include <stdio.h>
void chline(ch, i, j);
int main(void)
{
int x, y;
char ch;
printf("Enter a letter: ");
ch = getchar();
printf("rows: ");
scanf("%d", &y);
printf("columns: ");
scanf("%d", &x);
chline(ch, x, y);
return 0;
}
void chline(char ch, int i, int j)
{
for (int m = 0; m < j; m++)
{
for (int n = 0; n < i; n++)
{
printf("%c ", ch);
}
printf("\n");
}
}
3、
编写一个函数,接受3个参数:一个字符和两个整数。
字符参数是待打印的字符,第1个整数指定一行中打印字符的次数,第2个整数指定打印指定字符的行数。
编写一个调用该函数的程序。
//没看出来和第二题有啥区别
4、
两数的调和平均数这样计算:先得到两数的倒数,然后计算两个倒数的平均值,最后取计算结果的倒数。
编写一个函数,接受两个double类型的参数,返回这两个参数的调和平均数。
#include <stdio.h>
double harmonic(x, y);
int main(void)
{
double x, y;
double h;
printf("Enter first number: ");
scanf("%lf", &x);
if (x == 0)
{
printf("Please re-enter!\n");
printf("Enter first number: ");
scanf("%lf", &x);
}
printf("Enter second number: ");
scanf("%lf", &y);
if (y == 0)
{
printf("Please re-enter!\n");
printf("Enter second number: ");
scanf("%lf", &y);
}
h = harmonic(x, y);
printf("the harmonic mean of the two number is %lf\n", h);
return 0;
}
double harmonic(double x, double y)
{
double a, b;
double mean;
double harmonic;
a = 1 / x;
b = 1 / y;
mean = (a + b) / 2;
harmonic = 1 / mean;
return harmonic;
}
5、
编写并测试一个函数larger_of(),该函数把两个double类型变量的值替换为较大的值。
例如, larger_of(x, y)会把x和y中较大的值重新赋给两个变 量。
#include <stdio.h>
double large_of(double x, double y);
int main(void)
{
double x, y;
double max;
printf("Enter first number: ");
scanf("%lf", &x);
printf("Enter second number: ");
scanf("%lf", &y);
x = large_of(x, y);
y = large_of(x, y);
printf("x = %lf y = %lf\n", x,y);
return 0;
}
double large_of(double x, double y)
{
double max;
if (x > y)
max = x;
else
max = y;
return max;
}
/*
Enter first number: 12
Enter second number: 26
x = 26.000000 y = 26.000000
*/
6、
编写并测试一个函数,该函数以3个double变量的地址作为参数,
把最小值放入第1个函数,中间值放入第2个变量,最大值放入第3个变量。
参考
#include <stdio.h>
void p(double * p1, double * p2, double * p3);
int main(void)
{
double x, y, z;
while ((scanf("%lf %lf %lf", &x, &y, &z)) == 3)
p(&x, &y, &z);
return 0;
}
void p(double * p1, double * p2, double * p3)
{
double t;
if (*p1 > *p2)
{
t = *p1;
*p1 = *p2;
*p2 = t;
}
if (*p1 > *p3)
{
t = *p1;
*p1 = *p3;
*p3 = t;
}
if (*p2 > *p3)
{
t = *p2;
*p2 = *p3;
*p3 = t;
}
printf("%lf %lf %lf\n", *p1, *p2, *p3);
}
7、
编写一个函数,从标准输入中读取字符,直到遇到文件结尾。
程序要报告每个字符是否是字母。
如果是,还要报告该字母在字母表中的数值位置。
例如,c和C在字母表中的位置都是3。
合并一个函数,以一个字符作为参数,
如果该字符是一个字母则返回一个数值位置,否则返回-1。
#include <stdio.h>
#include <ctype.h>
int alpha(ch);
int main(void)
{
char ch;
while ((ch = getchar()) != EOF)
alpha(ch);
return 0;
}
int alpha(char ch)
{
int i;
if (isalpha(ch))
i = toupper(ch) - 64;
else
i = -1;
printf("%d\n", i);
}
/*
1a2b3c4d5e6f7g8h
-1
1
-1
2
-1
3
-1
4
-1
5
-1
6
-1
7
-1
8
-1
^Z
*/
8、
第6章的程序清单6.20中,power()函数返回一个double类型数的正整数次幂。
改进该函数,使其能正确计算负幂。
另外,函数要处理0的任何次幂都为0,任何数的0次幂都为1(函数应报告0的0次幂未定义,因此把该值处理为1)。
要使用一个循环,并在程序中测试该函数。
//程序清单6.20 powwer.c程序
// power.c -- 计算数的整数幂
#include <stdio.h>
double power(double n, int p); // ANSI函数原型
int main(void)
{
double x, xpow;
int exp;
printf("Enter a number and the positive integer power");
printf(" to which\nthe number will be raised. Enter q");
printf(" to quit.\n");
while (scanf("%lf%d", &x, &exp) == 2)
{
xpow = power(x, exp); // 函数调用
printf("%.3g to the power %d is %.5g\n", x, exp, xpow);
printf("Enter next pair of numbers or q to quit.\n");
}
printf("Hope you enjoyed this power trip -- bye!\n");
return 0;
}
double power(double n, int p) // 函数定义
{
double pow = 1;
int i;
for (i = 1; i <= p; i++)
pow *= n;
return pow; // 返回pow的值
}
/*
运行该程序后,输出示例如下:
Enter a number and the positive integer power to which
the number will be raised.Enter q to quit.
1.2 12
1.2 to the power 12 is 8.9161
Enter next pair of numbers or q to quit.
2 16
2 to the power 16 is 65536
Enter next pair of numbers or q to quit.
q
Hope you enjoyed this power trip -- bye!
*/
//修改
#include <stdio.h>
#include <math.h>
double power(double n, int p); // ANSI函数原型
int main(void)
{
double x, xpow;
int exp;
printf("Enter a number and the positive integer power");
printf(" to which\nthe number will be raised. Enter q");
printf(" to quit.\n");
while (scanf("%lf%d", &x, &exp) == 2)
{
if (x == 0 && exp!= 0)
xpow = 0;
else if (x != 0 && exp == 0)
xpow = 1;
else if (x == 0 && exp == 0)
{
printf("undefined 0^0\n");
xpow = 1;
}
else
xpow = power(x, exp);
printf("%.3g to the power %d is %.5g\n", x, exp, xpow);
printf("Enter next pair of numbers or q to quit.\n");
}
printf("Hope you enjoyed this power trip -- bye!\n");
return 0;
}
double power(double n, int p) // 函数定义
{
double pow = 1;
int i;
if (p > 0)
{
for (i = 1; i <= p; i++)
pow *= n;
}
else
{
for (i = 1; i <= abs(p); i++)
pow *= n;
pow = 1 / pow;
}
return pow; // 返回pow的值
}
/*
运行该程序后,输出示例如下:
Enter a number and the positive integer power to which
the number will be raised. Enter q to quit.
2 -3
2 to the power -3 is 0.125
Enter next pair of numbers or q to quit.
0 0
undefined 0^0
0 to the power 0 is 1
Enter next pair of numbers or q to quit.
3 0
3 to the power 0 is 1
Enter next pair of numbers or q to quit.
0 3
0 to the power 3 is 0
Enter next pair of numbers or q to quit.
2 3
2 to the power 3 is 8
Enter next pair of numbers or q to quit.
5 -2
5 to the power -2 is 0.04
Enter next pair of numbers or q to quit.
q
Hope you enjoyed this power trip -- bye!
*/
9、
使用递归函数重写编程练习8。
#include <stdio.h>
#include <math.h>
double power(double n, int p); // ANSI函数原型
int main(void)
{
double x, xpow;
int exp;
printf("Enter a number and the positive integer power");
printf(" to which\nthe number will be raised. Enter q");
printf(" to quit.\n");
while (scanf("%lf%d", &x, &exp) == 2)
{
if (x == 0 && exp!= 0)
xpow = 0;
else if (x != 0 && exp == 0)
xpow = 1;
else if (x == 0 && exp == 0)
{
printf("undefined 0^0\n");
xpow = 1;
}
else
xpow = power(x, exp);
printf("%.3g to the power %d is %.5g\n", x, exp, xpow);
printf("Enter next pair of numbers or q to quit.\n");
}
printf("Hope you enjoyed this power trip -- bye!\n");
return 0;
}
double power(double n, int p) // 函数定义
{
double pow = 1;
int i;
if (p > 0)
{
for (i = 1; i <= p; i++)
pow *= n;
}
else
{
pow = 1 / power(n, -p);
}
return pow; // 返回pow的值
}
10、
为了让程序清单9.8中的to_binary()函数更通用,
编写一个to_base_n() 函数接受两个在2~10范围内的参数,然后以第2个参数中指定的进制打印第 1个参数的数值。
例如,to_base_n(129, 8)显示的结果为201,也就是129的八进制数。
在一个完整的程序中测试该函数。
/* Programming Exercise 9-10 */
#include <stdio.h>
void to_base_n(int x, int base);
int main(void)
{
int number;
int b;
int count;
printf("Enter an integer (q to quit):\n");
while (scanf("%d", &number) == 1)
{
printf("Enter number base (2-10): ");
while ((count = scanf("%d", &b))== 1 && (b < 2 || b > 10))
{
printf("base should be in the range 2-10: ");
}
if (count != 1)
break;
printf("Base %d equivalent: ", b);
to_base_n(number, b);
putchar('\n');
printf("Enter an integer (q to quit):\n");
}
printf("Done.\n");
return 0;
}
void to_base_n(int x, int base)
{
int r;
r = x % base;
if (x >= base)
to_base_n(x / base, base);
putchar('0' + r);
return;
}
11、
编写并测试Fibonacci()函数,该函数用循环代替递归计算斐波那契数。