18.1.2~18.1.4
[TOC]
第五章:C函数
数学库函数
下图给出一些常用的数学库函数,x和y的数据类型都是double
。double
型的数据与float
型的数据一样都可用转换说明符%f
来输出。
函数<math.h> | 说明 |
---|---|
sqrt(x) |
x的平方根 |
exp(x) |
指数函数ex |
log(x) |
x的自然对数(以e为底) |
log10(x) |
x的对数(以10位底) |
fabs(x) |
x的绝对值 |
ceil(x) |
对x向上取整 |
floor(x) |
对x向下取整 |
pow(x,y) |
x的y次幂(xy) |
fmod(x,y) |
以浮点数表示x/y的余数 |
sin(x) cos(x) tan(x)
|
x的正弦、余弦、正切值(x以弧度表示) |
定义函数
//定义求平方函数square
#include <stdio.h>
int square(int y); //函数原型,以分号结束
int main(void) {
int x;
for (x = 1; x <= 10; x++)
printf("%d ", square(x)); //square调用
printf("\n");
return 0;
} //end main
int square(int y) //定义square
{
return y*y; //将计算结果返回给主调函数
}
- 17行中,定义函数的形参是一个整数,函数也将返回一个整数型的结果;
- 5行中,圆括号内
int
通知编译器:square
函数期望从主调函数接收一个整数;最左边的int将通知编译器:函数square讲一个整数结果返回给主调函数; - 函数定义的一般格式为
返回值类型 函数名(形参列表) //函数头(function header)
{
变量定义
语句
}
若声明返回值类型为void,则表示函数不返回任何值。在形参列表中,每一个形参前,都应该分别定义出其类型,如(double x , double y)
。
将控制从被调函数返回到主调函数的方法有三种:(1)若无返回值,则执行到最右花括号时将自动返回主调函数;(2)执行 return;
将控制返回主调函数;(3)执行 return expression;
将表达式的值返回给主调函数,同时将控制返回给主调函数。
数据类型及其转换说明符
数据类型 | prinft | scanf |
---|---|---|
long double (长双精度浮点数) |
%Lf |
%Lf |
double (双精度浮点数) |
%f |
%lf |
float (浮点数) |
%f |
%f |
unsigned long int (无符号长整数) |
%lu |
%lu |
long int (长整数) |
%ld |
%ld |
unsigned int (无符号整数) |
%u |
%u |
int (整数) |
%d |
%d |
unsigned short (无符号短整数) |
%hu |
%hu |
short (短整数) |
%hd |
%hd |
char (字符) |
%c |
%c |
随机数的生成
通过头文件<stdlib.h>
中定义的C标准库函数rand()
,可以将机会元素引入计算机应用中。
函数rank
将产生一个0
到RAND_MAX
之间的整数值,其中RAND_MAX
是头文件<stdio.h>
中定义的符号常量,其值至少是32767。
掷六面体骰子
//掷六面体骰子20次
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int i;
for (i = 1; i <= 20; i++) {
printf("%20d", 1 + rand() % 6);
if (i % 5 == 0) //每5个数据换行
printf("\n");
} //end for
return 0;
}
- 为了随机的结果只产生6个值,采用
rand() % 6
, 得到结果0~5,称为比例缩放(scaling)。 - 重复运行程序,将得到同样的结果,这正是
rand
函数的重要特点。严格来说,函数rand
产生的是伪随机数。程序可通过使用标准库函数srand
来实现随机化。调用srand
函数需要一个无符号整数作为种子(seed),控制函数rand
在每次被调用时产生不同结果。函数srand
的函数原型也是保存在<stdlib.h>
中。
//掷六面体骰子20次
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int i;
unsigned int seed; //定义无符号整数
printf("Enter seed:");
scanf_s("%u", &seed); //转换说明符%u
srand(seed);
for (i = 1; i <= 20; i++) {
printf("%20d", 1 + rand() % 6);
if (i % 5 == 0) //每5个数据换行
printf("\n");
} //end for
return 0;
}
为了无须每次都输入一个种子就可以实现随机化,可使用语句:
srand(time(NULL));
函数time
返回的是以秒为单位的,从1970.1.1到现在所经历的时间。time
函数的返回值在被转换成一个无符号整数后传递给srand
函数。这里time
的实参是NULL
(time
函数的功能是将返回的时钟值以字符串的形式显示出来,但是NULL
屏蔽了这个功能)。time
函数的函数原型在头文件<time.h>
中给出。
通用随机数公式
给出一个产生随机数的通用公式:n = a + rand() % b
。其中,a
是平移值,b
是比例因子(等于期望的连续整数区间的宽度)。
程序案例:双骰子赌博Craps
//Craps游戏
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
enum Status {CONTINUE, WON, LOSE}; //引入枚举类型
int rollDice(void); //rollDice函数原型
int main(void) {
int sum, myPoint;
enum Status gameStatus; //定义变量gameStatus为枚举型
srand(time(NULL));
sum = rollDice();
switch (sum) {
case 7:
case 11:
gameStatus = WON;
break;
case 2:
case 3:
case 12:
gameStatus = LOSE;
break;
default:
myPoint = sum;
gameStatus = CONTINUE;
printf("Point is %d\n", myPoint);
break;
} //end switch
while (gameStatus == CONTINUE) {
sum = rollDice();
if (myPoint == sum)
gameStatus = WON;
else if (sum == 7)
gameStatus = LOSE;
else
gameStatus = CONTINUE;
} //end while
if (gameStatus == WON)
printf("Player wins!\n");
else
printf("Player loses\n");
return 0;
}
int rollDice(void) //函数定义
{
int dice1 = 1 + rand() % 6;
int dice2 = 1 + rand() % 6;
int sumWork = dice1 + dice2;
printf("Player rolled %d + %d = %d\n", dice1, dice2, sumWork);
return sumWork;
}
- 定义函数
rollDice
来模拟投掷两个骰子并计算点数之和,由于rollDice
函数不需要实参,故在其形参列表注明void
,该函数返回值为整数,故函数头标明返回类型为int
; - 引入新的数据类型
enum Status
,并用其定义表示当前游戏状态的变量gameStatus
; - 枚举类型用关键字
enum
来定义,它是一组用标识符表示的整型常量的集合; - 枚举类型中的元素被称为枚举常量或符号常量;
- 一个枚举类型中的值是从0开始,逐个增1;
- 枚举类型中的每一个标识符必须是唯一的,但它们的值是可重复的;
- 引入新的数据类型
全部用大写字母来为枚举常量命名,既可以使之醒目,又同时表示其为常量。
储存类型
- 标识符的属性:标识名称、储存类型、储存周期、作用域、链接;
- C语言的储存类型:
auto
、register
、extern
、static
; - C语言的四种储存类型可以按照其对应的储存周期,分成两类:自动储存周期、静态储存周期;关键字
auto
和register
用于声明对应于自动储存周期的变量;
局部变量(local variable)
只有变量才可以具有自动存储周期。一个函数的局部变量在默认情况下都被认为是自动存储周期,因此声明语句中的auto
常被省略。
寄存器变量(register variable)
储存类型说明符register
可放在自动变量的声明语句前,用来建议编译器讲这个变量驻留在一个计算机硬件内部可高速访问的寄存器中。关键字register
只能用于具有自动存储周期的变量上。
事实上,
register
声明常常是多余的,编译器自动就会找出被频繁访问的变量并将其驻留在寄存器中。
静态存储类(static storage class)
- 关键字
extern
和static
用于声明具有静态存储周期的变量名和函数名的标识符; - 具有静态存储周期的标识符可以分为两类:外部标识符和利用
static
声明的局部变量; - 默认情况下,全局变量(global variable)和函数名都属于外部存储类
extern
; - 将一个变量定义为全局变量的方法是将其声明语句写在任何一个函数体之外,全局变量在程序运行期间始终存在。
将一个变量定义为全局变量会导致副作用的出现。使不需要访问这个变量的函数有意无意的修改该变量。通常情况下不要使用全局变量。
- 用关键字static声明的局部变量仍只能在定义它的函数中被访问,但在函数执行结束后仍然保留。
标识符作用域的规定
C语言有四种不同的标识符作用域:函数作用域(function scope)、文件作用域(file scope)、程序块作用域(block scope)、函数原型作用域(function-prototype scope)。
- 标号,是具有函数作用域的唯一标识符,可以在定义它的函数中的任何位置被访问到,但在定义它的函数外不能访问。
#include <stdio.h>
void useLocal(void); //函数原型
void useStaticLocal(void); //函数原型
void useGlobal(void); //函数原型
int x = 1; //声明全局变量x
int main(void)
{
int x = 5;
printf("函数main的外部块中局部变量x的值为:%d\n", x);
{ //start new scope
int x = 7;
printf("函数main的内部块中局部变量x的值为:%d\n", x);
} //end new scope
printf("函数main的外部块中局部变量x的值为:%d\n", x);
useLocal();
useStaticLocal();
useGlobal();
useLocal();
useStaticLocal();
useGlobal();
printf("\n函数main中局部变量x的值为:%d\n", x);
return 0;
} //end main
void useLocal(void) //函数定义
{
int x = 25;
printf("\n运行useLocal后,useLocal中的局部变量x值为:%d\n", x);
x++;
printf("结束useLocal之前,useLocal中的局部变量x值为:%d\n", x);
} //end function useLocal
void useStaticLocal(void) //函数定义
{
static int x = 50;
printf("\n运行useStaticLocal时,useStaticLocal中的局部变量x值为:%d\n", x);
x++;
printf("结束useStaticLocal时,useStaticLocal中的局部变量x值为:%d\n", x);
} //end function useStaticLocal
void useGlobal(void) //函数定义
{
printf("\n运行useGlobal时,全局变量x的值为:%d\n", x);
x *= 10;
printf("结束useGlobal时,全局变量x的值为:%d\n", x);
} //end function useGlobal
函数useStaticLocal
第一次运行后,x的值会保留为51,第二次函数useStaticLocal
运行时,x的初始值即为51。
递归
调用递归函数必须事先知道基线条件(base case)下的解。为了保证递归调用最终终止,递归函数每次都是用规模更小的原始问题去调用它自己,而这些逐渐变小的问题最终必须收敛于基线条件。程序执行到这一点时,递归函数将识别出基线条件,将基线条件的解返回到调用它的上一个函数,然后就是一连串的直线返回式操作,直到最终由函数的原始调用将原始问题的解返回给main
函数。
递归地计算阶乘
#include <stdio.h>
long int factorial(long number); //递归函数原型
int main(void) { //使用递归函数打印0到10的阶乘
int i;
for (i = 0; i <= 10; i++)
printf("%2d的阶乘为:%ld\n", i, factorial(i));
return 0;
} //end main
long factorial(long number) //递归函数定义
{
if (number <= 1)
return 1;
else
return number * factorial(number - 1);
}
递归函数一定需要返回值,否则将引起无穷递归直将内存耗尽。
使用递归的例子:Fibonacci数列
#include <stdio.h>
long fibonacci(long n); //函数fibonacci原型
int main(void)
{
long number;
printf("Enter a integer:");
scanf_s("%ld", &number);
printf("Fibonacci(%ld) is :%ld\n", number, fibonacci(number));
return 0;
} //end main
long fibonacci(long n) //定义函数fibonacci
{
if (n == 1)
return 0;
else if (n == 2)
return 1;
else
return fibonacci(n - 2) + fibonacci(n - 1);
}
计算同一个运算符对应的不同操作数的顺序
在众多C运算符中,C标准只对4个运算符规定了操作数的定值顺序:与运算符&&
、或运算符||
、逗号运算符,
和条件运算符?:
。
递归与迭代
- 递归与迭代都分别以一种控制结构为基础:迭代基于循环结构,递归基于选择结构;
- 递归与迭代都需要循环地执行:迭代是显式地使用一个循环结构,递归通过重复地进行函数调用来实现循环;
- 递归和迭代都需要进行终止测试:当循环调节为假时,迭代结束;递归是在遇到基线条件时终止;
- 基于计数控制循环的迭代和递归都是逐渐接近终止点的:迭代是不断地改变计数器变量直至循环继续条件为假,递归总是不断将问题的规模逐渐变小直至到达基线条件;
- 递归和迭代都会出现无限执行的情况;
- 递归有很多副作用:不断地执行函数调用,每次都要创建函数的一个副本,耗费存储器;而迭代通常发生在一个函数内部,反复执行的开销和额外占用的存储空间可以忽略不计;
- 递归方法比迭代更受欢迎是在于递归更自然地反映了问题的本质,更易于理解,易于排错。
作业
5.9 停车收费
#include<stdio.h>
float calculateCharges(float time);
int main(void)
{
float time1, time2, time3;
printf("输入3辆车的停车时间:\n");
scanf_s("%f%f%f", &time1, &time2, &time3);
printf("%-5s%9s%10s\n", "Car", "Hours", "Charge");
printf("%-5d%9.1f%10.2f\n", 1, time1, calculateCharges(time1));
printf("%-5d%9.1f%10.2f\n", 2, time2, calculateCharges(time2));
printf("%-5d%9.1f%10.2f\n", 3, time3, calculateCharges(time3));
printf("%-5s%9.1f%10.2f\n", "TOTAL", time1 + time2 + time3, calculateCharges(time1) + calculateCharges(time2) + calculateCharges(time3));
return 0;
}
float calculateCharges(float time)
{
if (time <= 3)
return 2.00;
else if (3 < time && time <= 19)
return 2 + (time - 3)* 0.5;
else if (time > 19)
return 10.00;
}
5.10 数据的舍入
#include<stdio.h>
#include<math.h>
int main(void) {
float x;
int y;
printf("输入数以求舍入:\n");
scanf_s("%f", &x);
for (;;) {
printf("原始值:%.1f\t舍入值:%d\n", x, y = floor(x + 0.5));
scanf_s("%f", &x);
}
return 0;
}
for(;;)
可以制造无条件循环。
5.16 取幂
#include<stdio.h>
long integerPower(int base, int exponent);
int main(void) {
int x, y;
printf("分别输入整数x和y以计算x的y次幂:\n");
scanf_s("%d%d", &x, &y);
for (;;) {
printf("计算%d的%d次幂结果为:%ld\n",x,y,integerPower(x,y));
scanf_s("%d%d", &x, &y);
}
return 0;
}
long integerPower(int base, int exponent)
{
if (exponent == 0)
return 1;
else if (exponent == 1)
return base;
else
return base * integerPower(base, (exponent - 1));
}
5.17 倍数
#include<stdio.h>
int multiple(int num1,int num2);
int main(void) {
int num1, num2;
printf("分别输入两个整数来比较第二个数是否为第一个数的倍数:\n");
scanf_s("%d%d", &num1, &num2);
for (;;) {
printf("判断结果为:%d\n", multiple(num1, num2));
scanf_s("%d%d", &num1, &num2);
}
return 0;
}
int multiple(int num1, int num2)
{
if (num2 % num1 == 0)
return 1;
else
return 0;
}
显示一个由任意字符组成的方阵
#include<stdio.h>
void row(int x, char z);
void line(int x, int y, char z);
int main(void) {
int a, b, d;
char c;
printf("输入方框长、宽:");
scanf_s("%d%d %c",&a, &b, &c); //注意空格
line(a, b, c);
return 0;
}
void row(int x, char z)
{
for (; x != 0; x--)
printf("%c", z);
}
void line(int x, int y, char z)
{
for (; y != 0; y--) {
row(x, z);
printf("\n");
}
}
- 11行
%c
前需要插入空格以确保a,b的值正常录入后c也能正常录入,因为%c
会接收空格和换行的录入。或写为scanf("%d%d\n%c",&a, &b, &c);
a,b,c之间以换行逐个输入。
5.26 完数
#include<stdio.h>
int perfect(int x);
int main(void) {
int num;
printf("输入整数:");
scanf_s("%d", &num);
if (perfect(num) == 1)
printf("\n%d是完数\n", num);
else
printf("\n%d不是完数\n", num);
return 0;
}
int perfect(int x)
{
int y; //作为x的副本
int sum = 0;
for (y = x - 1; y != 0; y--) {
if (x % y == 0)
sum += y;
}
if (sum == x)
return 1;
else
return 0;
}
5.27 素数
#include<stdio.h>
int prime(int x);
int main(void) {
int num;
printf("素数有:");
for (num = 1; num <= 1000000; num++) {
if (prime(num) != 0)
printf("%d ",num);
} //end for
printf("\n");
return 0;
}
int prime(int x)
{
int y;
if (x == 1)
return 0;
else {
for (y = x - 1; y != 1; y--) {
if (x % y == 0)
return 0;
}
}
}
5.32 猜数游戏
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main(void)
{
int randEnd;
int guess;
srand(time(NULL));
randEnd = 1 + rand() % 1000;
printf("I have a number between 1 and 1000.\nCan you gurss my number?\nPlease type your first guess.\n");
scanf_s("%d", &guess);
for (; guess != randEnd;) {
if (guess < randEnd) {
printf("Too Low. Try again.\n");
scanf_s("%d", &guess);
}
else {
printf("Too high. Try again.\n");
scanf_s("%d", &guess);
} //end if
} //end for
printf("Excellent! You guessed the number!\n");
return 0;
}
5.36 汉诺塔
#include<stdio.h>
void hanoi(int n, int a, int b, int c);
int main(void) {
int n;
int a = 1;
int b = 2;
int c = 3;
printf("输入汉诺塔的盘数:");
scanf_s("%d", &n);
hanoi(n, a, b, c);
return 0;
}
void hanoi(int n, int a, int b, int c)
{
if (n == 1)
printf("%d to %d\n", a, c);
else
{
hanoi(n - 1,a,c,b); //把a的n-1个盘子通过c移动到b
printf("%d to %d\n", a, c); //把a的最后1个盘(最大的盘)移动到c
hanoi(n - 1, b, a, c); //把b上面的n-1个盘通过a移动到c
}
}