一、函数的概念

1、函数的概念

每个C程序都至少有一个函数,即main主函数 ,如果程序的任务比较简单,全部的代码都写在main函数中,但是,在实际开发中,程序的任务往往比较复杂,如果全部的代码都写在main函数中,main函数体将非常庞大臃肿,代码重复。

我们可以把程序的任务分工到不同的子函数中,main更关心业务逻辑和处理流程,需要执行某任务的时候,调用子函数就可以了。

2、函数的分类

为了方使理解,我们把函数分为库函数和自定义函数(子函数)。

库函数是C语言提供的,实现了某些基本的功能,例如scanf、printf,在程序中可以直接使用。

自定义函数是程序员为了完成某项任务而编写的函数,目的是为了实现某项的功能或让主程序更简洁。自定义函数在使用之前,必须先声明和定义。

二、自定义函数的声明

自定义函数的声明包括了返回值函数名参数列表。有些程序员把函数声明说成是函数原型,只是用词不同,意思是一样的。

C语言中的声明函数的语法如下:

 return_type function_name( parameter list );

1)返回值的数据类型return_type:函数执行完任务后的返回值,可以是int、char、double或其它自定义的数据类型。如果函数只执行任务而不返回值,return_type用关键字 void表示,如下:

  void function_name( parameter list );

2)函数名function_name:函数名是标识符,命名规则与变量相同。

3)参数列表parameter list:当函数被调用时,调用者需要向函数传递参数。参数列表包括参数的数据类型和书写顺序。参数列表是可选的,也就是说,函数可以没有参数,如下:

return_type function_name();

函数声明示例:

  // 判断超女身材函数的声明
  // 函数名:checksc
  // 参数:height,身高,单位cm。
  // 返回值:0-不合格;1-娇小;2-标准;3-高挑
  int checksc(int height);

声明了一个函数,返回值是int类型,函数名是checksc,函数只有一个参数int height,注意,函数的声明语句最后的分号不能少。

如果把自定义函数比喻成工具,函数的声明就是这个工具的设计图纸。

三、自定义函数的定义

自定义函数是工具,如果想让这个工具能被使用,光有设计图纸还不行,还要把工具制造出来,函数的定义就是这个工具的实际主体,是为了实现函数的功能而编写的代码。

C语言中的函数定义的语法如下:

  return_type function_name( parameter list )       // 注意,不要在函数定义的最后加分号。
  {
    // 实现函数功能的代码
  }

函数定义的return_type、function_name和parameter list必须与函数声明一致。

函数主体包含为了完成任务需要执行语句的集合,放在花括号内。

示例

  // 判断超女身材函数的定义
  int checksc(int height)
  {
    if ( (height>=160) && (height<165) ) return 1;  // 娇小
    if ( (height>=165) && (height<175) ) return 2;  // 标准
    if ( (height>=175) && (height<180) ) return 3;  // 高挑

    return 0;   // 不合格
  }

四、自定义函数代码的组织

1、非通用能功的函数

如果自定义函数只在调用者程序中使用,可以在调用者程序中声明和定义,声明一般为调用者程序的上部,定义一般在调用者程序的下部,这并不是C语言的规定,而是为了让程序更方便阅读,程序员约定的写法。

示例(book45.c)

#include <stdio.h> 
#include <string.h> 

// 判断超女身材函数的声明
// 函数名:checksc
// 参数:height,身高,单位cm。
// 返回值:0-不合格;1-娇小;2-标准;3-高挑
int checksc(int height);

int main() 
{ 
  int ii,jj;

  printf("请输入超女的身高(单位:厘米):");
  scanf("%d",&ii);

  jj=checksc(ii);   // 调用checksc函数判断身材

  if (jj==0) printf("不合格。\n");
  if (jj==1) printf("娇小。\n");
  if (jj==2) printf("标准。\n");
  if (jj==3) printf("高挑。\n");
}

// 判断超女身材函数的定义
int checksc(int height)
{
  if ( (height>=160) && (height<165) ) return 1;  // 娇小
  if ( (height>=165) && (height<175) ) return 2;  // 标准
  if ( (height>=175) && (height<180) ) return 3;  // 高挑

  return 0;   // 不合格
}

在book45.c中,判断超女身材的函数(checksc)不是公共的功能,只在选秀程序使用,所以它的声明和定义都写在book45.c中。

2、通用能功的函数

如果自定义函数是一个通用的功能模块,可以在公共的头文件中声明,在公共的程序文件中定义。

如果某程序需要调用公共的函数,在调用者程序中用#include指令包含公共的头文件,编译的时候把调用者程序和公共的程序文件一起编译。

在以下的示例中,函数min和max是公共的函数,在_public.h头文件中声明,在_public.c程序文件中定义,在book46.c程序中被调用。

1)公共的头文件(_public.h)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 声明min函数,用于比较两个整数的大小,取小者
int min(const int ii1,const int ii2);

// 声明max函数,用于比较两个整数的大小,取大者
int max(const int ii1,const int ii2);

2)公共的程序文件(_public.c)

#include "_public.h"  // 包含自定义函数声明的头文件

// 用于比较两个整数的大小,函数返回较小者
int min(const int ii1,const int ii2)   // min函数定义
{
  if (ii1<ii2) return ii1;

  return ii2;
}

// 用于比较两个整数的大小,函数返回较大者
int max(const int ii1,const int ii2)   // max函数定义
{
  if (ii1>ii2) return ii1;

  return ii2;
}

3)调用者程序文件(book46.c)

#include "_public.h"  // 把_public.h头文件包含进来

int main()
{
  int xx,yy,imin,imax;

  xx=50; yy=80;

  imin=min(xx,yy);
  imax=max(xx,yy);

  printf("imin=%d,imax=%d\n",imin,imax);
}

4)编译运行

在这里插入图片描述

3、注意事项

1)在book46.c的第5行#include "_public.h",这里包含头文件是用双引号,不是尖括号\<>,这两者的差别如下:

#include <> 用于包含系统提供的头文件,编译的时候,gcc在系统的头文件目录中寻找头文件。

#include "" 用于包含程序员自定义的头文件,编译的时候,gcc先在当前目录中寻找头文件,如果找不到,再到系统的头文件目录中寻找。

2)编译程序的时候,要把调用者程序和公共程序文件一起编译,否则编译器会报错。

在这里插入图片描述

3)C语言对公共函数的头文件(_public.h)和程序文件(_public.c)的命名没有规定,由程序员自己命名,为了增加程序的可读性,尽可能采用一些有意义的文件名。

五、库函数

C语言提供了很多标准函数(C standard library),简称库函数,调用这些函数可以完成一些基本的功能,例如printf、scanf、memset、strcpy等。C语言的库函数有几百个,常用的不超过30%,在以后的章节中我将详细介绍常用库函数的应用场景和使用方法。

C语言标准库函数的声明的头文件存放在/usr/include目录中,如下:

<asset.h>     <ctype.h>       <errno.h>     <float.h>    <limits.h>
<locale.h>    <math.h>     <setjmp.h>   <signal.h>     <stdarg.h>
<stddef.h>   <stdlib.h>   <stdio.h>      <string.h>      <time.h>

C语言库函数的定义的文件是/usr/lib/gcc/x86_64-redhat-linux/4.4.4/libgcc.a(不同编译器的版本目录名有所不同),这是一个打包好的库文件,把程序文件打包成库文件的方法以后再详细介绍。

我们用gcc编译程序的时候并没有把libgcc.a包含进来,那是因为gcc编译器缺省会包含它,但是,程序员自定义函数的程序文件就没有这种待遇,需要手工的包含进来与程序一起编译。

六、需要包含哪些头文件

我们在使用库函数的时候,如果不包含它的头文件,编译时会出现警告或错误,如下:

在这里插入图片描述
那么,在我们编写的程序中,应该包含哪些头文件呢?有两种方法,一是上百度上查资料,二是使用Linux系统提供的帮助,以strcpy函数为例,在命令行下输入man strcpy回车,如下:
在这里插入图片描述

man显示了函数的声明的头文件(第5行),函数的参数、使用方法和返回值。

注意了,如果程序中调用了库函数但没有包含它头文件,编译的时候不一定是警告,也可能是报错,函数无法识别等,这个要看编译器,不同C语言的编译器的处理方式不一样。

七、调用函数的方法

库函数是系统提供的工具,自定义函数是程序员自己补充的工具,对使用者来说都一样,没有区别。

在C语言中,函数的调用非常灵活,可以独占一行语句,也可以当成常量赋值给变量,也可以当成函数的参数。

如果函数的返回值是void,表示该函数的调用一定会成功,一般用单行书写,独占一条语句。

如果函数的返回值不是void,可以不关心它的返回值,也可以当成常量用于任何表达式中。

示例(book47.c)

#include "_public.h"  // 把_public.h头文件包含进来

int main()
{
  int i=max(30,45);

  max(30,20);   // 没有接收返回值,max的活白干了。

  55;      // 这是什么鬼?

  i=max(min(45,38),50);

  i=min(max(min(45,38),50),25);

  printf("30和50相比,更大的值是%d。\n",max(30,50));
}

大家可能对这个代码不理解,一句话:如果函数的返回值是整数,就可以把函数当成整数来用,如果返回值是小数,就可以把函数当成小数来用,……。

八、函数调用的过程

在程序中调用子函数时,程序的流程进入子函数的代码中,当子函数返回时(或到达函数的结束括号时),程序的流程回到调用者程序。

示例(book48.c)

#include <stdio.h> 
#include <string.h> 

// 声明func函数,无返回值无参数
void func();   

int main() 
{ 
  printf("即将调用func函数。\n");

  func();

  printf("调用func函数已完成。\n");

  return 0;
}

void func()
{
  int ii=0;

  for (ii=0;ii<5;ii++)
  {
    sleep(1);    // sleep是C语言的库函数,睡眠
    printf("已经过去了%d秒。\n",ii+1);
  }
}

运行效果

在这里插入图片描述

九、函数参数的传递

函数的参数可以理解为函数内部的变量,参数传递就是调用者程序把变量(或常量)的值复制一份给函数的参数,简单说来就是复制的过程。一旦完成数据的复制,调用者程序的变量和函数的参数就再也没有瓜葛了,所以,在函数调用的过程中,函数内部变量的值发生改变并不会影响调用者程序的变量的值。

我们在调用函数的时候,不关心函数参数的名称,函数参数的名称是函数自己的事情,是函数的内部变量,只在函数内部使用,与调用者无关。

示例(book49.c)

#include <stdio.h> 

void funcld(int b);   // 声明函数funcld,无返回值

int main() 
{ 
  int a=10;

  printf("调用funcld前a的值是%d.\n",a);
  funcld(a);
  printf("调用funcld后a的值是%d.\n",a);
}

void funcld(int b)
{
  printf("赋值前b的值是%d.\n",b);
  b=20;
  printf("赋值后b的值是%d.\n",b);
}

运行效果

在这里插入图片描述

关于函数的参数,在很多教程中有很多说法,如“形参”、“实参”、“值传递”、“地址传递”等,这些说法把简单的概念复杂化了,大家不必理会。

十、const约束

const 是 constant 的缩写,意思是“恒定不变的”,它是定义只读变量的关键字。用 const 定义只读变量的方法很简单,就在定义变量时前面加 const 即可,如:

const  double  pi = 3.1415926;

用 const 定义的变量的值是不允许改变的,不允许给它重新赋值,即使是赋相同的值也不可以。所以说它定义的是只读变量。这也就意味着必须在定义的时候就给它赋初值,如果程序中试图改变它的值,编译的时候就会报错。

在变量前加const约束,主要用于定义函数的参数,表示该参数在函数中是只读取,不允许改变,如果函数中试图改变它的值,编译的时候就会报错。

例如:

在这里插入图片描述

编译如下:

在这里插入图片描述

为什么要在函数的参数前加const?有两个目的:1)防止程序员犯错,如果程序员犯了错误,编译器就能发现;2)增加了源代码的可读性。在实际开发中,函数的参数不加const不会有任何问题,但是,程序员一般都会为只读变量加上const约束,这是一个好的习惯。

十一、应用经验

1、如何寻找函数

在我的课程中,会介绍常用库函数的使用方法,但也有漏掉的。查资料能力是程序员的基本技能,它可以提升您解决问题的能力。

例如要查找C语言复制字符串功能的函数,在百度上输入“C语言复制字符串”搜索就可以了,您想查什么就输入什么。

在这里插入图片描述

然后,打开多几个网页看看,基本上能找到答案。

注意一个问题,网上的内容太多太杂乱,大部分文章是初学者发的博客,重点关注百度百科的文章。

2、测试函数的功能

以strcpy函数为例,函数的声明如下。

char *strcpy(char *dest, const char *src);

函数功能:字符串复制。

函数名:strcpy。

dest参数:目标字符串。

src参数:待复制的内容,有const约束。

返回值:返回dest,这是提高函数可用性的常用的处理方法。

用代码测试一下:

  char strname[50];
  strcpy(strname,"西施");
  printf("=%s=\n",strname);
  printf("=%s=\n",strcpy(strname,"西施"));

然后,看看输出的结果是不是“=西施=”,用=号夹住输出的内容是为了看清楚有没有其它的字符。

十二、课后作业

1)编写示例程序,自定义一个根据超女的胸围判断身材的函数,判断标准由您定,类似book45.c。

2)C语言的库函数肯定是不够用的,程序员必须要有自己的函数库,从本章节就开始准备,创建您自己的函数库的头文件和程序文件,头文件和程序文件的命名由您定,先放两个函数进去,声明如下,然后写示例程序调用它们。

// 声明max函数,取两个数值的较大者
int max(int ii1,int ii2);

// 声明min函数,取两个数值的较小者
int min(int ii1,int ii2);

这两个自定义函数的功能虽然简单,但是,千里之行,始于足下,慢慢积累。

3)本章节的内容非常重要,建议大家看多几遍视频,用程序测试每个知识点,充分的理解它。


Learn and live!