【C语言基础知识06】函数知识点阐述

  • Post author:
  • Post category:其他


本文大纲:


6.1


函数概述

C语言中,模块用函数来实现

函数分类以下两种:

1)

标准库函数

:用户不需要定义可直接使用。如scanf()、printf()等

2)

用户自己定义的函数

例子:

从键盘输入两个正整数m和n,求m!/(m-n)!的值

函数的作用:

1)可方便地使用其他人已经编写的代码,就像调用系统提供的库函数

2)可以在后续程序中使用自己编写的代码

3)实现结构化程序设计的基本思想

结论:

1)一个完整的C程序可以由

若干个函数

组成,其中必须有一个且只能有

一个主函数



从主函数开始执行,而其他函数只能被调用

2)完整的C程序中的所有函数可以放在一个文件中,也可以放在多个文件中


6.2


函数的定义与声明


先定义


–>


后声明


–>


再使用

首先定义函数的数据类型、存储类型、函数体

然后才使用

3个概念:函数定义、函数调用、函数声明


函数定义

:定义函数的功能(未经定义的函数不能使用)。可分为库函数和用户自定义函数两种。


函数调用

:执行一个函数;调用函数时,如果有参数首先传参数,程序由主调函数跳转到被调函数体内的第一条语句开始执行,

执行完被调用函数体内的最后一条语句或中途遇到return语句时,又返回到主调函数继续向下执行


函数声明:

通知编译系统该函数已经定义过了;库函数不需要写出函数声明,只需要在程序前面用#include包含具有该库函数原型的头文件即可;用户自定义函数,如果函数定义的位置在函数调用之后,则前面必须有函数声明;如果函数定义放在函数调用之前,则可省略函数声明

6.2.1

函数定义:

函数定义的一般形式:

[函数类型] 函数名字([形式参数表])

{

[声明部分]

[执行语句]

}

说明:一个函数(定义)由

函数头

(函数首部)和

函数体

两部分组成。

(1)函数头(首部):说明

函数类型、函数名称、参数

1)函数类型:函数返回值的数据类型;基本类型或构造类型;

默认int

;不返回值则定义为

void


类型

2)函数名:给函数取的名字,以后用这个名字调用;命名规则与标识符相同

3)函数名后面是形式参数表,也可以是没有参数,但()不能省略,这是格式的规定。形式参数表是说明形式参数的类型和形式参数的名称,各个形式参数之前用,分隔。

(2)函数体:函数头下方用一对{}括起来的部分。如果函数体内有多个{},最外层是函数体的范围。

函数体一般包括声明部分和执行部分


1


)声明部分

:定义本函数所使用的变量和进行的有关声明(如函数声明)


2


)执行部分

:程序段,即由若干条语句组成的命令序列(可以在其中调用其他函数)

函数不能单独运行,函数可以被主函数或其他函数调用,也可以调用其他函数,但不能调用主函数

6.2.2 函数的参数和返回值

函数的参数分为

形式参数



实际参数


形式参数

(形参,形参的本质就是变量):函数定义时设定的参数。

例:int max3(int x,int y,int z)中的x,y,z就是形参,都是整型


实际参数

(实参):调用函数时所使用的实际的参数。

例:主函数中调用max3函数的语句namx=max3(n1,n2,n3)中,n1,n2,n3就是实际参数,都是整型

形参和实参的功能:

数据传递

只有发送函数调用时,主调函数把实参的值传递给被调函数的形参(也就是

实参给形参赋值

),从而实现主调函数向被调函数的数据传递。

保证实参与形参的类型一致、个数一致、顺序一致

C语言可以从函数(被调函数)返回值给调用函数。通过return语句返回值,使用return语句能够返回一个值或不返回值(此时函数类型是 void)。

return语句格式

return [表达式];

return(表达式)

注意:

1)

main


是函数,必须有返回值

,默认返回int类型。exit(0)或return(0)

2)函数的类型就是返回值的类型,return语句中表达式的类型与函数类型一致,如果不一致,一函数类型为准

3)如果函数没有返回值,函数类型应该说明void(空类型)

函数声明:

函数定义的位置随意。

1)函数定义位置在前,函数调用在后,不必声明,编译程序产生正确的调用格式

2)函数定义在调用它的函数之后或函数在其他源程序模块中,且函数类型不是整型,在函数使用前对函数进行声明

函数声明格式:

函数类型 函数名([形式参数表]);


6.3


函数的调用

一个函数调用另外一个函数称为函数调用,其调用者称为主调函数,被调用者称为被调函数。

三种方式:

1)函数语句形式

只进行某些操作而不返回函数值,这时的函数调用可作为一条独立的语句

2)函数表达式形式

函数作为表达式的一项,出现在表达式中,以函数返回值参与表达式的运算,这种方式要求函数

必须有返回值

3)函数实参形式

函数作为另一个函数调用的实际参数出现。这种情况是把函数的返回值作为实参进行传送,因此要求该函数

必须有返回值

函数语句形式:

函数表达式形式:

函数实参形式:

6.3.2 函数参数的传递形式

实参与形参的传递方式有两种:

值传递和地址传递

值传递:参数传递的是数据本身;

数值只能由实参传递给形参,形参不能反过来传递给

实参,即传值是

单向

的。

形参的任何变化不会影响到实参

过程:

1)发生函数调用时,系统临时创建形参变量

2)实参将其数值复制一份给形参变量

3)函数调用过程中,形参的任何改变只发生在被调函数内部,不会影响到实参

4)当被调函数运行结束返回主调函数时,形参的存储空间

被自动释放

例:swap()函数

#include "stdio.h"
void swap(int a,int b)    //形参a、b
{
	int t;
	printf("(2)子函数开始时:a=%d,b=%d\n",a,b);  //输出子函数中交换操作前的数值
	t=a;
	a=b;
	b=t;
	printf("(3)子函数结束时:a=%d,b=%d\n",a,b);  //输出子函数中交换操作后的数值 
} 
main()
{
	int x=2,y=4;
	printf("(1)子函数调用前:x=%d,y=%d\n",x,y);  //输出swap()函数调用前的数值
	swap(x,y);
	printf("(4)子函数调用后:x=%d,y=%d\n",x,y);  //输出swap()函数调用后的数值
	return 0; 
}

–实参向形参传值是单向传递

例:定义函数,求极值

#include "stdio.h"
main()
{
	int n;
	void s(int n);
	printf("请输入n的值:\n");
	scanf("%d",&n);
	s(n);
	printf("n=%d\n",n);    //实参的值不随形参的变化而变化 
}
void s(int n)
{
	int i;
	for(i=n-1;i>=1;i--)
	{
		n=n+i;
	}
	printf("n=%d\n",n);
}

6.3.3 函数的嵌套调用

C语言中函数定义都是互相平行、独立的,也就是说在定义函数时,

一个函数内不能包含另外一个函数。

C语言不能嵌套定义函数,

但可以嵌套调用函数

,也就是说,在调用一个函数的过程中,又调用另外一个函数。

例:计算机平方阶层

#include "stdio.h"
long f1(int p)
{
	int k;
	long r;
	long f2(int q);   //声明 
	k=p*p;
	r=f2(k);
	return r;
}
long f2(int q)
{
	long c=1;
	int i;
	for(i=1;i<=q;i++)
	{
	    c=c*i;
	}
	return c;
}
main()
{
	int i;     //i是数字 
	long s=0;
	for(i=2;i<=3;i++)
	{
	    s=s+f1(i);
	}
	printf("s=%ld\n",s);
}

6.3.4 函数的递归调用


一个函数在它的函数体内调用它自身的过程称为递归调用。

直接递归调用或间接递归调用

例:有5个人做在一起,问第5个人多少岁?

#include "stdio.h"
main()
{
	int age(int n);
	printf("%d\n",age(5));
	return 0;
}
int age(int n)
{
	int c;    //用c作为存放函数的返回值的变量
	if(n==1)
	{
	    c=10;
	}
	else
	{
	    c=age(n-1)+2;
	} 
	printf("\t%d\n",c);
	return(c);
}

例:求n!

#include "stdio.h"
main()
{
	int n;
	long ff(int n);
	long y;
	printf("请输入n的值:\n");
	scanf("%d",&n);
	if(n<0)
	{
		printf("n<0!input error!");
	}
	else
	{
		y=ff(n);
		printf("%d!=%ld",n,y);
	}
	return 0;
}
long ff(int n)
{
	long f;
	if(n==0||n==1)
	{
		f=1;
	}
	else
	{
		f=n*ff(n-1);
	}
	return (f);
}

一个递归问题可分为:

回溯



递推

两个阶段;且必须要有一个

递归过程的边界条件

两个阶段:

1)递推阶段:将原问题不断地分解成为新的

子问题

,逐渐从未知的向已知的方向推测,最终达到自己已知的条件,即递归结束条件,这时递推阶段结束。

2)回归阶段:从已知条件出发,按照“递推”的逆过程,逐一求值回归,最终到达“递推”的开始处,结束回归阶段,完成递归调用

要有递归的终止条件!

递归=递归方式+递归条件


6.4


局部变量和全局变量

函数中:形参变量只在被调用期间才分配内存单元,

调用结束后立即释放

。表明形参变量只有在函数内才有效,离开该函数就不能再使用了。

这种变量有效性地范围称为变量地作用域。变量说明方式不同,作用域也不同。

按作用域范围可分为:局部变量 和 全局变量。

6.4.1 局部变量

局部变量也称为内部变量。局部变量是在函数内作定义和说明。作用域仅限于函数内,离开该函数后再使用这种变量就是非法的。

注意:


  1. 主函数中定义的变量只能在主函数中使用

    ,不能在其他函数中使用。同时,主函数也不能使用其他函数中定义的变量。主函数也是一个函数,与其他函数是平行关系。
  2. 形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量
  3. 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆
  4. 在复合语句中也可以定义变量,其作用域只在复合语句范围内。

6.4.2 全局变量

全局变量也称为外部变量,

在函数外部定义的变量

不属于某一个函数,而

属于一个源程序文件

,其作用域是从定义变量的位置开始到本源文件结束

如果全局变量在文件的开头定义,则在整个文件范围内均可以使用该全局变量;如果不在文件的开头定义,又想在定义点之前使用该全局变量,需要用exterm进行声明。


变量的声明:说明变量的性质,并不分配存储空间


变量的定义:即为变量分配存储空间

例:输入正方体的长、宽、高,分别为l,w,h,求体积及三个面的面积。

#include "stdio.h"
int s1,s2,s3;     //作用域为整个程序
main()
{
	int v,l,w,h;
	int vs(int a,int b,int c);
	printf("请输入长,宽,高的值:\n");
	scanf("%d %d %d",&l,&w,&h);
	v=vs(l,w,h);
	printf("v=%d,s1=%d,s2=%d,s3=%d\n",v,s1,s2,s3);
	return 0;
}
int vs(int a,int b,int c)
{
	int v;
	v=a*b*c;
	s1=a*b;
	s2=b*c;
	s3=a*c;
	return v;
}

说明:

  1. 对于局部变量的定义和说明,可以不加区分。全局变量定义必须在所有的函数之外,

    且只能定义一次。

全局变量的定义:

类型说明符   变量名,变量名……

全局变量的声明:

extern 说明符  变量名,变量名……

全局变量在定义时就分配了内存单元,全局变量在定义时也可作初始赋值,但不能在声明时赋初始值,全部变量的声明只是表明在函数内要使用该全局变量。

例:编写程序,输入两个数,调用函数找出最大值

#include "stdio.h"
int a,b;    //全局变量的定义 
main()
{
	extern int a,b;    //全局变量的声明 
	int mymax(int x,int y);
	printf("请输入a,b的值:\n");
	scanf("%d %d",&a,&b);	
	printf("max=%d\n",mymax(a,b));
	return 0;
}
int mymax(int x,int y)
{
	int z;
	if(x>y)
	{
		z=x;
	}
	else
	{
		z=y;
	}
	return z;
} 
  1. 全局变量只在所有函数之外定义一次;全局变量可以声明多次,哪个函数内要用到在其后面定义的变量,就需要在该函数内对该全局变量进行声明。

在同一个源文件中,允许全局变量与局部变量同名。在局部变量的作用范围内,全局变量被屏蔽,即它不起作用。

例:全局变量与局部变量同名。

在局部变量的作用范围内,全局变量被“屏蔽”,即全局变量不起作用

  1. 如果没有全局变量,函数只能通过参数与外界发生数据关系,有了全局变量以后,增加了一条与外界传递数据的渠道。

联系太多,降低模块的独立性


6.5


变量的存储属性

1. 用户程序的存储分配

程序区

静态存储区

动态存储区

程序区:存放程序;

静态存储区:在程序开始执行时就分配的固定存储单元,如全局变量

动态存储区:在函数调用过程中进行动态分配的存储单元,如函数形参、自动变量

2. 变量的存储类型

变量和函数有两个属性:

操作属性



存储属性

操作属性:数据类型

存储属性:变量的存储类型、变量的生存期和变量的作用域

变量划分:空间角度,全局变量和局部变量;生存期角度,永久存储和动态存储;

永久存储:从程序的开始到结束;动态存储:在程序的过程中

按存储属性四个分类:register(寄存器)、auto(主存)、static(主存)、extern(主存)

在定义一个变量时,除了指定其数据类型外,还可以执行其存储属性。

6.5.1 自动变量(auto)

自动变量为

局部变量

。说明符:auto

当程序的一个局部要使用某些自动变量时,说明形式:

[auto] 数据类型 变量名[= 初值表达式],…;

[]表示可以省略。

说明:


  1. 自动变量是局部变量

    。自动变量的作用域仅限于定义该变量的个体内。在函数中定义的自动变量在函数内有效,复合语句中的在复合语句中有效
  2. 自动变量属于动态存储,使用时,定义该变量的函数被调用才分配存储单元,函数调用结束,释放存储单元。函数调用结束后,自动变量的值不能保留
  3. 自动变量的作用域和生存期都局限于定义它的个体内(函数和复合语句内);同名变量不会混淆
  4. 使用未赋初值的自动变量
#include "stdio.h"
main()
{
	int x=1;
	{
		int prt();  //声明 
		int x=3;
		prt();
		printf("2nd x=%d\n",x); 
	}
	printf("1st x=%d\n",x);
	return 0;
}
int prt()
{
	int x=5;
	printf("3th x=%d\n",x);
	return 0;
}

6.5.2 寄存变量(register)

寄存器变量具有与自动变量完全相同的性质。为了提高效率,C语言允许将局部变量的值放在CPU寄存器中,这种变量叫“寄存器变量”,用关键字register表示。

例:求阶层

说明:

只有局部自动变量和形式参数可以作为寄存器变量

一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量

#include "stdio.h"
main()
{
	int fac(int n);
	int i;
	for(i=0;i<=5;i++)
	{
		printf("%d!=%d\n",i,fac(i));
	}
	return 0;
}
int fac(int n)    //将实参i给形参n 
{
	register int i,f=1;  //形参-由于频繁使用变量i,故将它放在寄存器中 
	for(i=1;i<=n;i++)
	{
		f=f*i;
	}
	return (f);
}

6.5.3 静态变量(static)

函数中的局部变量的值在函数调用

结束后不消失而保留原值

,这时就应该指定局部变量为“静态局部变量”,关键字static声明

格式:

static 数据类型 变量名[=初始化常量表达式].…;

说明:

  1. 静态变量的存储空间在程序的整个运行期间是固定的。一个变量被指定为静态,在编译时就为其分配存储空间,程序一开始执行便被建立,直到该程序执行结束都是存在的

  2. 静态变量的初始化是在编译时进行

    ,在定义时只能使用常量或常量表达式进行显示初始化。未显示初始化时,编译时将它们初始化为0(int)或0.0(float)

自动变量没有初始化的问题

,只有静态变量和外部变量有初始化问题。

对自动变量称为“赋初值”

对静态变量和外部变量称为“初始化”

  1. 函数多次被调用的过程中,静态局部变量的值具有可继承性
#include "stdio.h"
main()
{
	int f(int a);
	int a=2,i;
	for(i=0;i<3;i++)
	{
		printf("%d\n",f(a));
	}
	return 0;
} 
int f(int a)
{
	auto int b=0;
	static int c=3; //值可以继承;静态局部变量 
	b=b+1;
	c=c+1;
	return(a+b+c);
}

6.5.4 外部变量

外部变量即为全局变量是在函数的外部定义的,其作用域为从变量定义处开始,到程序文件末尾。

如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件的末尾。

如果在

定义点之前的函数想引用外部变量

,则应该在引用之前用关键字extern对该变量作“外部变量声明”,表示该变量是应该已经定义的外部变量。

例:外部变量代码

  1. 可将外部变量的作用域扩充到其他文件,这时在需要用到这些外部变量的文件中,对变量用extern作声明即可。
  2. 限定本文件的外部变量只在本文件中使用。如果有的外部变量只允许本文件使用而不允许其他文件使用,则可以在此外部变量前加一个static,使其有限局部化,称为静态外部变量
#include "stdio.h"
main()
{
	int mymax(int x,int y);
	extern a,b;   //外部变量声明从而合法的引用 
	printf("%d\n",mymax(a,b));
	return 0;
}
int mymax(int x,int y)
{
	int z;
	z=x>y?x:y;
	return (z);
}
int a=13,b=1;
//在本程序中最后一行定义了外部变量a,b;外部变量定义的位置在main函数之后 
 


6.6


编译预处理

编译预处理是在编译前对源程序进行的一些预处理。预处理由编译系统中的预处理程序按源程序中的预处理命令进行。

C语言预处理命令均已“#”开头,末尾不加分号,以来区别C语句

可出现在程序中的任何位置,作用域是自出现点到所在源程序的末尾。如:#define和#include

6.6.1 宏定义

C语言允许用一个标识符来表示一个字符串,称为“宏”。

被宏定义的标识符称为“宏名”。

在编译预处理时,对程序中出现的“宏名”。

都用宏定义中的字符串去代换,这称为“宏代换”、“宏展开”

宏定义是由源程序中的宏定义命令完成的,宏代换是预处理程序自动完成的。

“宏”分为有参数和无参数两类。

1.无参数宏定义

无参宏的宏名后不带参数,一般形式为:#define 宏名 字符串

宏名的标识符习惯上用有意义且容易理解的大写字母来表示。

“字符串”可以是常数、表达式、格式串等

一般写在文件开头函数体的外面,有效范围是从定义宏命令之后到遇到终止宏命令#undef为止,否则其作用域将一直到源文件结束。

例:#define PI 3.14

定义了宏名PI代表3.14,在预编译处理时,系统将把该命令之后作用之内的所有PI都自动用3.14代换,即进行宏展开。

减少字符串的重复书写、修改重复使用的字符串的工作变得简单

注意:

  1. 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一个简单的

    替换

    ,字符串中可以含任何字符,常数、表达式,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时所发现。
  2. 如果在一行中写不下整个宏定义,需要用两行或更多行来书写时,只需要在每行的最后一个字符的后面加上反斜杠“\”,并在下一行的最开始接着写即可
  3. 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用

    #undef


    命令
  4. 宏名在源程序中若用引号括起来,则预处理程序不对其作宏代换

例:printf(“PI=”,PI);

在预处理时,将只对第二个PI进行代换,对第一个双引号中的PI,系统不对其作代换

  1. 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时,由预处理程序层层代换

例子:

#define R 5.6

#define PI 3.14

#define L 2*PI*R

#define S PI*R*R   //PI、R是已经定义的宏名

2.带参数宏定义

C语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。

参数宏:调用中,不仅要进行宏展开,

而且要用实参去代换形参

形式:

#define 宏名(形参表列) 字符串

在字符串中含有各个形参

带参宏调用的一般形式为:

宏名(实参表列);

例如:

#define M(y) y*y+3*y  //宏定义

k=M(5)      //宏调用

例:求两个数中较大者

#include "stdio.h"
#define MAX(a,b)(a>b)?a:b 
main()
{
	int x,y,mymax;
	printf("请输入两个数:");
	scanf("%d %d",&x,&y);
	mymax=MAX(x,y);   //宏调用 
	printf("max=%d\n",mymax);
	return 0;
}
  1. 带参宏定义中,宏名和形参表之间不能有空格出现
  2. 在带参数宏定义中,形式参数不分配内存单元,因此不必作类型定义。而宏调用中的实参有具体的值,要用它们去代换形参,因此必须作类型说明,与函数的情况不同。函数调用中,形参与实参是两个不同的量,作用域不同,存在值传递;而带宏参数中只是符号代换,不存在值传递的问题。
  3. 宏定义中形参是标识符,宏调用中的实参可以是表达式
  4. 宏定义中,字符串内的形参通常要用括号括起来以避免出错。除了将宏定义字符串中的参数都用括号括起来,还需要将整个字符串部分也用括号括起来。
  5. 同一表达式用函数处理的结果与宏定义处理的结果不一定相同

例如:求函数n的平方

#include "stdio.h"
#define SQ(y)((y)*(y))
main()
{
	int i=1;
	while(i<5)
	{
		printf("%d\n",SQ(i++));
	}
}

6.6.2 文件包含

文件包含是C语言预处理程序的另一个重要功能。

形式:

#include “文件名”

例如:

#include “stdio.h”

文件包含命令的功能就是把指定的文件插入该命令位置取代该命令,从而把指定的文件和当前的源程序文件连成一个源文件。

说明:

  1. 文件包含命令中的文件名可以用双引号括起来,也可以用尖括号括起来


尖括号

:表示在包含文件目录中查找(包含目录是由用户在设置环境时设置的),而不再源文件目录查找;


双引号

:首先在当前的源文件目录中查找,若未找到才到包含目录中查找

  1. 一个include命令只能指定一个被包含文件,若有多个文件要包含,则需用多个include命令
  2. 文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件

预处理程序提供了条件编译的功能。可以按不同的条件按去编译不同的程序部分,因而产生不同的目标代码文件。

1)形式1:

#ifdef 标识符

程序段1

#else

程序段2

#endif

标识符是指已用宏命令#define定义的宏名,而程序段可以是编译预处理命令行,也可以是C语言组。如果标识符已被#define命令定义过则对程序段1进行编译;否则对程序段2进行编译。

#include "stdio.h"
#define REAL float
main()
{
	#ifdef REAL
		REAL a;
		printf("输入一个实数: ");
		scanf("%f",&a);
		printf("这个实数是:%f\n",a);
	#else
		float a;
		printf("输入一个单精度浮点数: ");
		scanf("%f",&a);
		printf("这个单精度浮点数是:%f\n",a);
	#endif
} 

2)形式2:

#ifndef 标识符

程序段1

#else

程序段2

#endif

功能:如果标识符未被#define命令定义过则对程序段1进行编译,否则对程序段2进行编译。

3)形式3:

#if 表达式

程序段1

#else

程序段2

#endif

功能:如果表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。


6.7


应用举例

例子:编写一个程序完成“菜单”功能:提供三种选择路径。


代码太长,望读者谅解;关于C代码实例可进我主页下载。嘻嘻嘻嘻嘻~~~~


谢谢各位~~~~


print—是函数,可以返回一个值,只能有一个参数;


println—与print唯一的区别是println换行输出;


printf—函数,把文字格式化后输出,直接调用系统调用进行IO,非缓冲



版权声明:本文为qq_42603841原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。