第9章 函数

关键字:returen

运算符:*(一元)、&(一元)

函数及其定义方式

如何使用参数和返回值

如何把指针变量用作函数参数

函数类型

ANSI C原型

递归

 

如何组成程序:C的设计思想,把函数用作构建块

目录

9.1 复习函数

9.1.1 创建并使用简单函数

9.1.2 分析程序

9.1.3 函数参数

9.1.4 定义带形式参数的函数

9.1.5 声明带形式参数函数的原型

9.1.6 调用带实际参数的函数

9.1.7 黑盒视角

9.1.8 使用return从函数中返回值

9.1.9 函数类型

9.2 ANSI C函数原型

9.2.1 问题所在

9.2.2 ANSI的解决方案

9.2.3 无参数和未指定参数

9.2.4 函数原型的优点

9.3 递归

9.3.1 演示递归

9.3.2 递归的基本原理

9.3.3 尾递归

9.3.4 递归和倒序计算

9.3.5 递归的优缺点

9.4 编译多源代码文件的程序

9.4.1 UNIX

9.4.2 Linux

9.4.3 DOS命令行编译器

9.4.4 Windows和苹果的IDE编译器

9.4.5 使用头文件

9.5 查找地址:&运算符

9.6 更改主调函数中的变量

9.7 指针简介

9.7.1 间接运算符:*

9.7.2 声明指针

9.7.3 使用指针在函数间通信

9.8 关键概念

9.9 本章小结


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 本章小结

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值