关键字:returen
运算符:*(一元)、&(一元)
函数及其定义方式
如何使用参数和返回值
如何把指针变量用作函数参数
函数类型
ANSI C原型
递归
如何组成程序:C的设计思想,把函数用作构建块
目录
9.1 复习函数
什么是函数?函数(function)是完成特定任务的独立程序代码单元。语法规则定义了函数的结构和使用方式。
为什么要使用函数?首先使用函数可以省去编写重复代码的苦差。让程序更加模块化,从而提高了程序代码的可读性。
如果不是自己编写函数,根本不用关心黑盒的内部行为。例如使用printf()时,只需知道该函数传入格式字符串或一些参数以及printf()生成的输出,无需了解printf()的内部代码。
9.1.1 创建并使用简单函数
/* lethead1.c */
#include <stdio.h>
#define NAME "GIGATHINK, INC."
#define ADDRESS "101 Megabuck Plaza"
#define PLACE "Megapolis, CA 94904"
#define WIDTH 40
void starbar(void); /* prototype the function */
int main(void)
{
starbar();
printf("%s\n", NAME);
printf("%s\n", ADDRESS);
printf("%s\n", PLACE);
starbar(); /* use the function */
return 0;
}
void starbar(void) /* define the function */
{
int count;
for (count = 1; count <= WIDTH; count++)
putchar('*');
putchar('\n');
}
[wlsh@wlsh-MacbookPro] ch09$ clang lethead1.c
[wlsh@wlsh-MacbookPro] ch09$ ./a.out
****************************************
GIGATHINK, INC.
101 Megabuck Plaza
Megapolis, CA 94904
****************************************
9.1.2 分析程序
函数原型(function prototype):告诉编译器函数的签名(signature)即函数的返回值类型和函数接受的参数类型。
函数调用(function call):表明在此处执行函数
函数定义(function definition):明确指出函数做什么
9.1.3 函数参数
/* lethead2.c */
#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);
}
[wlsh@wlsh-MacbookPro] ch09$ clang lethead2.c
[wlsh@wlsh-MacbookPro] ch09$ ./a.out
****************************************
GIGATHINK, INC.
101 Megabuck Plaza
Megapolis, CA 94904
****************************************
9.1.4 定义带形式参数的函数
形式参数(formal argument/formal parameter)
1.局部变量,属该函数私有
2.每个变量前都声明其类型。
void dibs(int x, y ,z)
void dibs(int x, int y, int z)
9.1.5 声明带形式参数函数的原型
void show_n_char (char ch, int num);
void show_n_char (char , int)
void shor_n_char ();
9.1.6 调用带实际参数的函数
实际参数(actual argument)
9.1.7 黑盒视角
从黑盒的视角看show_n_char(),待显示的字符和显示的次数是输入。执行后的结果是打印指定数量的字符。输入以参数的形式传递给函数。这些信息清楚地表明了如何在main()中使用该函数,可以作为编写函数的设计说明。
9.1.8 使用return从函数中返回值
函数的返回值可以把信息从被调函数传回主调函数。
被设计用于测试函数的程序有时被称为驱动程序(drive)
/* lesser.c -- finds the lesser of two evils */
#include <stdio.h>
int imin(int, int);
int main(void)
{
int evil1, evil2;
printf("Enter a pair of integers (q to quit):\n");
while (scanf("%d %d", &evil1, &evil2) == 2)
{
printf("The lesser of %d and %d is %d.\n",
evil1, evil2, imin(evil1,evil2));
printf("Enter a pair of integers (q to quit):\n");
}
printf("Bye.\n");
return 0;
}
int imin(int n,int m)
{
int min;
if (n < m)
min = n;
else
min = m;
return min;
//return (n < M) ? n : m;
}
[wlsh@wlsh-MacbookPro] ch09$ clang lesser.c
[wlsh@wlsh-MacbookPro] ch09$ ./a.out
Enter a pair of integers (q to quit):
509 333
The lesser of 509 and 333 is 333.
Enter a pair of integers (q to quit):
q
int what_if(int n)
{
double z = 100.0 / (double)n
return z;
}
//把函数中指定的返回值赋给与函数类型相同的变量多得到的值
result = what_if(64);
//z = 1.5625,但是returen 语句返回int类型为1.
imin(int n,int m)
{
if(n < m)
return n;
else
return m;
printf("lalalal.\n");
}
//print()语句用于不会被执行,因为return语句另一个作用是,终止函数并把控制返回给主调函数的下一条语句。
9.1.9 函数类型
声明函数时必须声明函数的类型,带返回值的函数类型应该与其返回值类型相同,没有返回值的函数应声明为void。
要正确的使用函数,程序在第1次使用函数之前必须知道函数的类型。
函数原型都在声明使用函数之前。
ANSI C标准库中,函数被分成多个系列,每一个系列都有各自的头文件。这些头文件除了其他内容,还包含了本系列所有函数的声明。例如stdio.h头文件包含了标准I/O库函数的声明,math.h头文件包含了各种数学函数的声明。
9.2 ANSI C函数原型
9.2.1 问题所在
/* misuse.c -- uses a function incorrectly */
#include <stdio.h>
int imax(); /* old-style declaration */
int main(void)
{
printf("The maximum of %d and %d is %d.\n",
3, 5, imax(3));
printf("The maximum of %d and %d is %d.\n",
3, 5, imax(3.0, 5.0));
return 0;
}
int imax(n, m)
int n, m;
{
return (n > m ? n : m);
}
[wlsh@wlsh-MacbookPro] ch09$ clang misuse.c
[wlsh@wlsh-MacbookPro] ch09$ ./a.out
The maximum of 3 and 5 is 3.
The maximum of 3 and 5 is 73896.
为什么会出现这样的情况?
主调函数把它的参数存储在被称为栈(stack)的临时存储区,被调函数从栈中读取这些参数。对于该例,两个过程并未相互协调。
函数调用imax(3)把一个整数放在栈中,当imax()函数开始执行时,它从栈中读取两个整数,而实际上栈中只存放一个待读取的整数,所以读取的第2个值时恰好在栈中的其他值。
第2次使用imax()函数时,它传递的是float类型的值,这次把两个double类型的值放在栈中(当float类型出作为参数传递时会被升级为double类型),两个double类型的值是两个64位的值,所以128位的数据被放在栈中,当imax()从栈中读取两个int类型的值时,他从栈中读取前64位。而系统中每个int类型的变量占用32位。
9.2.2 ANSI的解决方案
int max(int , int);
int max(int a, int b);//变量名是假名,不必与函数定义的形式参数名一致
有了这些信息,编译器可以检查函数调用是否与函数原型匹配,参数的数量是否正确?参数的类型是否匹配?如果两个参数都是数字,但是类型不匹配,编译器会把实际参数的类型转换成形式参数的类型。
imax (3.0,5.0)->imax(3,5)
/* proto.c -- uses a function prototype */
#include <stdio.h>
int imax(int, int); /* prototype */
int main(void)
{
printf("The maximum of %d and %d is %d.\n",
3, 5, imax(3));
printf("The maximum of %d and %d is %d.\n",
3, 5, imax(3.0, 5.0));
return 0;
}
int imax(int n, int m)
{
return (n > m ? n : m);
}
[wlsh@wlsh-MacbookPro] ch09$ clang proto.c
proto.c:7:24: error: too few arguments to function call, expected 2, have 1
3, 5, imax(3));
~~~~ ^
proto.c:3:1: note: 'imax' declared here
int imax(int, int); /* prototype */
^
1 error generated.
clang..很不同
9.2.3 无参数和未指定参数
原型 void print_name()
声明 void print_name(void)
9.2.4 函数原型的优点
函数原型是C语言的强有力的工具,让编译器捕获在使用函数时啃根出现的许多错误或疏漏。
//即是函数定义,也是函数原型。
int imax(int a,int b){return a>b ? a : b;}
int main()
{
int x,z;
z = imax(x,50);
}
9.3 递归
C允许函数调用自己,这种调用过程称为递归(recursion),结束递归的是使用递归的难点,如果递归代码来没有终止递归的条件测试部分,一个调用自己的函数会无限递归。
可以使用循环的地方都可以使用递归。
9.3.1 演示递归
/* recur.c -- recursion illustration */
#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); // 1
if (n < 4)
up_and_down(n+1);
printf("LEVEL %d: n location %p\n", n, &n); // 2
}
[wlsh@wlsh-MacbookPro] ch09$ clang recur.c
[wlsh@wlsh-MacbookPro] ch09$ ./a.out
Level 1: n location 0x7ffee4c15acc
Level 2: n location 0x7ffee4c15aac
Level 3: n location 0x7ffee4c15a8c
Level 4: n location 0x7ffee4c15a6c
LEVEL 4: n location 0x7ffee4c15a6c
LEVEL 3: n location 0x7ffee4c15a8c
LEVEL 2: n location 0x7ffee4c15aac
LEVEL 1: n location 0x7ffee4c15acc
9.3.2 递归的基本原理
9.3.3 尾递归
// factor.c -- uses loops and recursion to calculate factorials
#include <stdio.h>
long fact(int n);
long rfact(int n);
int main(void)
{
int num;
printf("This program calculates factorials.\n");
printf("Enter a value in the range 0-12 (q to quit):\n");
while (scanf("%d", &num) == 1)
{
if (num < 0)
printf("No negative numbers, please.\n");
else if (num > 12)
printf("Keep input under 13.\n");
else
{
printf("loop: %d factorial = %ld\n",
num, fact(num));
printf("recursion: %d factorial = %ld\n",
num, rfact(num));
}
printf("Enter a value in the range 0-12 (q to quit):\n");
}
printf("Bye.\n");
return 0;
}
long fact(int n) // loop-based function
{
long ans;
for (ans = 1; n > 1; n--)
ans *= n;
return ans;
}
long rfact(int n) // recursive version
{
long ans;
if (n > 0)
ans= n * rfact(n-1);
else
ans = 1;
return ans;
}
[wlsh@wlsh-MacbookPro] ch09$ clang factor.c
[wlsh@wlsh-MacbookPro] ch09$ ./a.out
This program calculates factorials.
Enter a value in the range 0-12 (q to quit):
4
loop: 4 factorial = 24
recursion: 4 factorial = 24
Enter a value in the range 0-12 (q to quit):
q
Bye.
尾递归(tail recursion)是最简单的递归形式,相当于循环。
递归和循环应该选择哪一个?一般而言,循环比较好,每次递归都会创建一组变量,所以递归用的内存更多,而且每次递归都会把创建的一组新变量放在栈中。递归调用的数量受限与内存空间,每次函数调用要话费一定的时间,所以递归的执行效率慢
9.3.4 递归和倒序计算
/* binary.c -- prints integer in binary form */
#include <stdio.h>
void to_binary(unsigned long n);
int main(void)
{
unsigned long number;
printf("Enter an integer (q to quit):\n");
while (scanf("%lu", &number) == 1)
{
printf("Binary equivalent: ");
to_binary(number);
putchar('\n');
printf("Enter an integer (q to quit):\n");
}
printf("Done.\n");
return 0;
}
void to_binary(unsigned long n) /* recursive function */
{
int r;
r = n % 2;
if (n >= 2)
to_binary(n / 2);
putchar(r == 0 ? '0' : '1');
return;
}
[wlsh@wlsh-MacbookPro] ch09$ clang binary.c
[wlsh@wlsh-MacbookPro] ch09$ ./a.out
Enter an integer (q to quit):
9
Binary equivalent: 1001
Enter an integer (q to quit):
255
Binary equivalent: 11111111
Enter an integer (q to quit):
q
Done.
9.3.5 递归的优缺点
优点:为某些编程问题提供最简单的解决方案
缺点:快速消耗计算机的内存单元
9.4 编译多源代码文件的程序
9.4.1 UNIX
9.4.2 Linux
gcc file1.c file2.c
改动file1.c,而file2.c不变,编译命令如下
gcc file1.c file2.o
9.4.3 DOS命令行编译器
9.4.4 Windows和苹果的IDE编译器
9.4.5 使用头文件
clang: error: linker command failed with exit code 1 (use -v to see invocation)
[wlsh@wlsh-MacbookPro] ch09$ clang usehotel.c hotel.c hotel.h
[wlsh@wlsh-MacbookPro] ch09$ ./a.out
********************************************************************
Enter the number of the desired hotel:
1) Fairfield Arms 2) Hotel Olympic
3) Chertworthy Plaza 4) The Stockton
5) quit
********************************************************************
9.5 查找地址:&运算符
一元运算符会求出变量的地址
pooh是变量名,&pooh是变量的地址
/* 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);
}
[wlsh@wlsh-MacbookPro] ch09$ clang loccheck.c
[wlsh@wlsh-MacbookPro] ch09$ ./a.out
In main(), pooh = 2 and &pooh = 0x7ffee7606ae8
In main(), bah = 5 and &bah = 0x7ffee7606ae4
In mikado(), pooh = 10 and &pooh = 0x7ffee7606ab8
In mikado(), bah = 2 and &bah = 0x7ffee7606abc
9.6 更改主调函数中的变量
/* swap2.c -- researching swap1.c */
#include <stdio.h>
void interchange(int u, int v);
int main(void)
{
int x = 5, y = 10;
printf("Originally x = %d and y = %d.\n", x , y);
interchange(x, y);
printf("Now x = %d and y = %d.\n", x, y);
return 0;
}
void interchange(int u, int v)
{
int temp;
printf("Originally u = %d and v = %d.\n", u , v);
temp = u;
u = v;
v = temp;
printf("Now u = %d and v = %d.\n", u, v);
}
[wlsh@wlsh-MacbookPro] ch09$ clang swap1.c
[wlsh@wlsh-MacbookPro] ch09$ ./a.out
Originally x = 5 and y = 10.
Now x = 5 and y = 10.
仍然没有没有改变,如何改变?指针
9.7 指针简介
指针(pointre)是一个值为内存地址的变量,
ptr = &pooh;
ptr是变量,&pooh是常量。
9.7.1 间接运算符:*
间接运算符*(indirection operator)求出存储在指针或变地址上的值,也称解引用运算符(dereferencing operator)
9.7.2 声明指针
声明指针变量必须指定指针所指向变量的类型,因为不同变量类型占用不同的存储空间。
类型说明符表明了指针所指向对象的类型。
星号*表明声明的变量是一个指针。
int * pi; pi是指向int类型变量的指针。
char * pc; pc是指向char类型变量的指针
*和指针名之间的空格,通常在声明时使用空格,在解引用变量时省略空格。
指针是新类型,不是整数类型。
9.7.3 使用指针在函数间通信
#include <stdio.h>
void interchange(int* u, int* v);
int main(void)
{
int x = 5, y = 10;
printf("Originally x = %d and y = %d.\n", x , y);
interchange(&x, &y); //传递地址
printf("Now x = %d and y = %d.\n", x, y);
return 0;
}
void interchange(int* u, int* v)
{
int temp;
printf("Originally u = %p and v = %p.\n", u , v);
temp = *u; //解引用,temp获得u所指向对象的值
*u = *v;
*v = temp;
printf("Now u = %p and v = %p.\n", u, v);
}
[wlsh@wlsh-MacbookPro] ch09$ ./a.out
Originally x = 5 and y = 10.
Originally u = 0x7ffeec497ae8 and v = 0x7ffeec497ae4.
Now u = 0x7ffeec497ae8 and v = 0x7ffeec497ae4.
Now x = 10 and y = 5.
要在被调函数中改变主调函数的变量,用第2种形式
9.8 关键概念
9.9 本章小结