第五章

18.1.2~18.1.4

[TOC]

第五章:C函数

数学库函数

下图给出一些常用的数学库函数,x和y的数据类型都是doubledouble型的数据与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将产生一个0RAND_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的实参是NULLtime函数的功能是将返回的时钟值以字符串的形式显示出来,但是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语言的储存类型:autoregisterexternstatic
  • C语言的四种储存类型可以按照其对应的储存周期,分成两类:自动储存周期、静态储存周期;关键字autoregister用于声明对应于自动储存周期的变量;

局部变量(local variable)

只有变量才可以具有自动存储周期。一个函数的局部变量在默认情况下都被认为是自动存储周期,因此声明语句中的auto常被省略。

寄存器变量(register variable)

储存类型说明符register可放在自动变量的声明语句前,用来建议编译器讲这个变量驻留在一个计算机硬件内部可高速访问的寄存器中。关键字register只能用于具有自动存储周期的变量上。

事实上,register声明常常是多余的,编译器自动就会找出被频繁访问的变量并将其驻留在寄存器中。

静态存储类(static storage class)

  • 关键字externstatic用于声明具有静态存储周期的变量名和函数名的标识符;
  • 具有静态存储周期的标识符可以分为两类:外部标识符和利用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
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341

推荐阅读更多精彩内容

  • 第五章 序列和协程 来源:Chapter 5: Sequences and Coroutines 译者:飞龙 协议...
    布客飞龙阅读 638评论 0 37
  • Perl中,函数(又称子程序)是一个封装的行为单元。函数可以有自己的名字,可以接受输入,可以产生输出,它是Perl...
    可以没名字吗阅读 3,473评论 0 8
  • 特别说明,为便于查阅,文章转自https://github.com/getify/You-Dont-Know-JS...
    杀破狼real阅读 455评论 0 0
  • 引用类型的值时引用类型的一个实例。在ECMAScript中,引用类型是一种数据结构,用于将数据和功能组织在一起。有...
    cooore阅读 276评论 0 1
  • 窗外雨微微, 辗转难入睡。 心念郎君面, 暗洒相思泪。
    聽雨書生阅读 168评论 0 1