【第八节】C语言的函数

目录

前言

一、函数的基础概念

二、函数的定义

三、函数的调用

四、函数的返回值

五、形式参数与实际参数

六、其他重要说明

七、关于函数代码块的变量类型

八、全局函数与静态函数


前言

        函数是 C 语言中非常重要的部分,是设计大型程序的基础。通过使用函数,可以使代码更加模块化和可复用,提高代码的可读性和可维护性,同时还可以提高程序的效率和可靠性。


一、函数的基础概念

1)什么是函数
1. 函数就是被定义的有特定功能的一小段独立程序。函数也被称之为方法。
2. 函数是一个独立的代码块,可以完成特定的任务。函数可以接受输入参数,并返回输出结果。通过使用函数,可以将复杂的任务分解为多个简单的子任务,并将这些子任务分别实现为函数。
3. 函数是C语言的基本构成单位。
4. 函数都是平等的,没有谁等级更高一些这种说法,只有 main()稍微特殊一些。

2)函数可以分为:
1. 标准C定义的标准库函数符合标准的C编译器必须提供这些函数,函数的行为也要符合标准 C的定义。
2. 第三方库函数由其它厂商自行开发的C语言函数库,不在标准范围内,能扩充C语言的功能。
3. 自定义函数自己编写的函数包装后,也可成为函数库,供别人使用。

        学习库函数的时候,我们主要学习的是函数的功能,参数的含义,今天我们主要学习函数是如何定义出来的。

二、函数的定义

函数的定义规则:
返回值类型 函数名(参数类型 形式参数名1,参数类型 形式参数名2,...)
{
        函数语句;
        return 返回值;
}

说明:
1. 返回值类型: 函数调用表达式的值类型,需和return 之后的数据类型一致。假如函数没有返回值,则可以写 void 在此处。
2. 参数类型: 形式参数的数据类型,调用函数的时候,可以给函数传递的数据类型。
3. 形式参数名: 接到外部数据的变量,可以在函数内部直接使用。假如没有参数的话,写一个 void即可。
4. return 语句: 有两个作用,第一个是用于结束函数,第二个是将返回值传递出函数注:假如函数中没有写 return,则函数在最后一个)时结束。
5. 返回值,就是函数会返回给调用者的一个数据。假如返回值类型是 void 的,那么就不用写返回值了,有没有返回值这都是由设计者决定的。

我们实现一个函数,主要考虑的就是三个问题:
1. 你定义这个函数的主要功能是什么?
2. 你实现这个函数的功能,需要外部给你传递什么参数?
3. 如何让调用者,得到最终的结果?

        比如,我要实现一个求两个整数最大值的函数,那么函数功能就确定了,求两个整数之间的最大值,那要实现这个功能,需要外部传递什么呢?当然是两个 int 型的数。别人调用这个函数的时候通过返回值得到最大值。那么函数的返回值类型,自然也是 int 型,例如下面的代码:

//比如我们现在要实现一个函数:求得两个数中比较大的数:
int GetMax(int numA, int numB)
{
    if(numA>numB)
    {
        return numA;
    }
    else
    {
        return numB;
    }
}

int main(void)
{
    printf("%d",GetMax(3, 6));
    return 0;
}

注:我们为什么会设计一个没有返回值的函数呢?它的结果如果显现出来呢?
首先,C 语言语法是支持没有返回值的。
其次,从设计的角度看,有可能结果就是直接打印到屏幕上,没有什么需要返回的。

比如下面的代码:

void fun(void) //这是一个没有返回值,也没有参数的函数
{
    printf("fun:hello world");
}

int main(void)
{
    printf("%d",4+6);
    fun(); //调用函数
    return 0;
}

三、函数的调用

        按函数在程序中出现的位置来分,可以有以下 3 种函数调用方式
1)函数语句
把函数调用作为一个语句。这时不要求函数带反回值,只要求函数完成一定的操作。

2)函数表达式
函数出现在一个表达式中,这种表达式称为函数表达式。这时要求函数带回一个确定的值以参加表达式的运算。

3)函数参数
函数调用作为一个函数的实参。

代码示例:

//当定义在调用之前时 调用者不需要申明即可调用
int print_message_a(int nNumb, int nNumbA)
{
    int nTem =nNumb;
    nNumb = nNumbA;
    nNumbA = nTem;
    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
    int nNumb=12, nNumbA = 10;

    //1.当定义在调用之前时 调用者不需要申明即可调用
    print_message_a(nNumb, nNumbA);
    //2.当函数定义在调用者之后时,需要先声明函数声明必须要与该函数定义完全相
    //同(返回值,参数类型,参数个数) 最后以分号结束

    int FUN(int n, int b, int c);
    //可以没有变量的名字,但是必须要有类型: int FUN(int, int, int);
    //调用函数:
    FUN(1, print_message_a(nNumb, nNumbA), 3);
    return 0;
}

int FUN(int n, int b, int c)
{
    return 0;
}

函数调用注意事项:
在一个函数中调用另一个函数,需要具备的条件如下。
(1)首先被调用的函数必须是已经存在的函数(是库函数或用户自己定义的函数)。但光有这一条件还不够。
(2)如果使用库函数,还应该在本文件开头用#include 命令将调用有关库函数时所需用到的信息“包含”到本文件中去。
(3)如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面(在同一个文件中),应该在主调函数中对被调用的函数作声明。
(4)即使没有实现该函数,但有声明,在写的时候没有错误,但在编译的时会出现错误.(无法解析 XXX 外部符号)。
(5) 将函数作为另一个函数的参数时该函数的返回值必须要和当前函数形参类型一致。

四、函数的返回值

通过函数调用使主调函数能得到一个确定的值,这就是函数的返回值。
1)函数的返回值是通过函数中的 return 语句获得的。
return 语句将被调用函数中的一个确定值带回主调函数中去。
2)函数值的类型。既然函数有返回值,这个值当然应属于某一个确定的类型,应当在定义函数时指定函数值的类型。
注:return 后面的语句可以是变量,常量,表达式,函数。例如:
 

int print_message(int nNumb, int nNumbA, int nNumbB)
{
    int nSum =nNumb + nNumbA + nNumbB;
    return nSum; //return 语句后面必须要有表达式或变量
}

int _tmain(int argc, _TCHAR* argv[])
{
    int print_message(int nNumb, int nNumbA, int nNumbB);
    //该函数 return 回来的值得用一个变量接收,最终得到传入的三个值的和。
    int nSumA = print_message(12, 33, 44);
    return 0;
}

3)在定义函数时指定的函数类型一般应该和return 语句中的表达式类型一致如果函数值的类型和return 语句中表达式的值不一致,则以函数类型为准。对数值型数据,可以自动进行类型转换。即函数类型决定返回值的类型。例:

float print_message(int nNumb, int nNumbA, int nNumbB)
{
    int nSum = nNumb + nNumbA + nNumbB;
    //nSum 本身的值是一个int 类型的值,
    //但返回值是一个 float 类型,那么函数的返回值最终以返回类型为准
    return nSum;
}

int print_message_a()
{
    float fNumb = 120.32, fNumbB = 78.33;
    return fNumb + fNumbB; //表达式相加的结果自动转换 int 型返回
}

int _tmain(int argc, TCHAR* argv[])
{
    int print_message(int nNumb, int nNumbA, int nNumbB);
    //fSumA 也是一个 float 类型的变量,用于接收该函数的返回值。
    float fSumA = print_message(12, 33, 44);
    int i = print_message(12, 33, 44);//精度丢失
    int nSumA = print_message_a();
    return 0;
}

4)对于不带返回值的函数,应当用“void”定义函数为“无类型”(或称“空类型”)。
这样,系统就保证不使函数带回任何值,即禁止调用函数中使用被调用函数的返回值。

五、形式参数与实际参数

1)在调用函数时,大多数情况下,主调函数和被调用函数之间有数据传递关系。在定义函数时函数名后面括号中的变量名称为“形式参数”(简称“形参”),在主调函数中调用一个函数时,函数名后面括号中的参数(可以是一个表达式)称为“实际参数”(简称“实参”)。
例:
 

float print_message(int nNumb, int nNumbA, int nNumbB)
{
    //此处的nNumb,nNumbA,nNumbB 为函数形参
    //参数值由调用者给定现在也就是个不确定的值
    int nSum =nNumb + nNumbA + nNumbB;
    return nSum;
}

int print_message_a(){
    float fNumb = 150.32, fNumbB = 78.66;
    return fNumb + fNumbB;
}

int _tmain(int argc, _TCHAR* argv[])
{
    int print_message(int nNumb, int nNumbA, int nNumbB);
    int nNumb = 12, nNumbA = 10, nNumbB= 90;
    //此处的 nNumb,nNumbA,nNumbB 为函数实参,调用函数是实际传入的参数
    //注:在函数传参时,实参可以是变量,表达式,常量,函数的返回值.
    //但是唯一要确定的是 传入参数要与函数形参的类型一致。 (这一条很重要)
    //如果不一致则通过强制转换。
    float fSumA = print_message(nNumb, nNumb, nNumb);
    float fSumb = 130.562;

    //fSumb 为 float 类型,形参为 int 型,所以强制转换,
    //在本代码中不强制转换也不会报错,最多就是警告你可能精度丢失。
    //例如:指针与地址之间的转换
    //int *p=(int*)0x12345678;//(关于指针暂时不需要掌握)
    float fSumA = print_message((int)fSumb, nNumb, nNumb);
    int nSumA = print_message_a();
    return 0;
}

2)形式参数和实际参数说明(注意事项)
1. 在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。只有在发生函数调用时,函数 print_message_a中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放。
注:因为当进入函数时,形参也相当于变量,是变量就要给他开辟空间。当调用结束后,形参所占的内存单元也被释放。
2. 实参可以是常量、变量或表达式。但要求它们有确定的值。在调用时将实参的值赋给形参。
3. 在被定义的函数中,必须指定形参的类型。
4. 实参与形参的类型应相同或赋值兼容。
5. 实参向形参的数据传递是“值传递”,单向传递,只由实参传给形参,而不能由形参专回来给实参。在内存中,实参单元与形参单元是不同的单元。在调用函数时,给形参分配存储单元,并将实参对应的值传递给形参,调用结束后,形参单元被释放,实参单元仍保留并维持原值。因此,在执行一个被调用函数时,形参的值如果发生改变,并不会改变主调函数的实参的值。

例:通过函数实现两个数的交换

//&nNumb的地址为:0x10010;
//&nNumbA的地址为:0x10006;
int print_messagez_a(int nNumb, int nNumbA)
{
    int nTem=nNumb;
    nNumb = nNumbA;
    nNumbA = nTem;
    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
    int nNumb = 12,nNumbA = 10;
    //&nNumb0x30010;
    //&nNumbA0x30006;
    print_message_a(nNumb, nNumbA); //可以不接收返回值
    //最终结果nNumb,nNumbA 值没有任何变化
    return 0;
}

六、其他重要说明

1) 一个C程序由一个或多个程序模块组成,每一个程序模块作为一个源程序文件。对较大的程序,一般不希望把所有内容全放在一个文件中,而是将它们放在若千个源文件中,再由若干个源程序文件组成一个C程序。一个源程序文件可以为多个C程序共用。

例如:
demo1.c如果想在demo2中使用,那么可以通过 #include "..demo1\\demo1.c",目录如下图所示
1. 双引号中的内容为要加载的文件的路径及文件名。
2. “..”表示当前文件的上一层文件夹(demo2)。"\\"转移字符 表示路径\
3. 所以demo1.c 可以在其自己的工程中使用,也可以在其他工程中使用
4. 相对路径:当前文件所在的文件路径。
5. 绝对路径:直接加上盘符及完整的路径 例:C:\\Program Files (x86)\\document

2) 一个源程序文件由一个或多个函数以及其他有关内容(如命令行、数据定义等)组成一个源程序是一个编译单位,在程序编译时是以源程序文件为单位进行编译的,而不是以函数为单位进行编译的。
3) C程序的用户代码执行是从 main函数开始的。如在 main 函数中调用其他函数在调用后流程返回到main 函数,在 main函数中结束整个程序的运行。
4) 所有函数都是平行的,即在定义函数时是分别进行的,是相互独立的。一个函数并不从属于另一个函数,即函数不能嵌套定义。函数间可以相互调用,但不能调用 main 函数main甬数是系统调用的。

例: 自定义的一个函数 返回值为void 参数为void

void printstar() {
    printf("***********\n");
}

void print_message() {
    printf("How do you do!\n");
    void fun(); //函数声明
    fun(); //函数使用
}

void fun()
{
    //函数之间相互独立
    printf("*********\n");
    printf("How do you do!\n");
}

int _tmain(int argc, _TCHAR* argv[])
{
    void printstar(); //函数申明
    void print_message(); //函数申明
    printstar(); //谁先调用先执行谁
    print_message();
    return 0;
}

下面是错误的代码示例:

//1.函数嵌套定义(错误)
void FUN(){
    void FUNA(){} //在函数体内定义函数
}
/
//2.错误的调用方式:

//这两个函数最终无限递归的调用下去,直到栈被填满,崩溃。
void printstar(){
    printf(”********** n");
    void print message();
    print_message(); //error
}

void print_message(){
    void printstar();
    printstar(); //error
    printf("How do you do!\n");
}

int _tmain(int argc, _TCHAR* argv[])
{
    void printstar(); //函数申明
    printstar();
    return 0;
}

5)从用户使用的角度看,函数有两种
1. 标准函数即库函数,它是由系统提供的,用户不必自己定义而直接使用它们。
例如:strlen,printf....
2. 用户自己定义的函数。它是用以解决用户专门需要的函数

6)从函数的形式看,函数分两类。
1. 无参函数。

        在调用无参函数时,主调函数不向被调用函数传递数据。无参函数一般用来执行指定的一组操作。无参函数可以带回或不带会函数值,但一般以不带回返回值的居多
例: void Fun(void);


2. 有参函数。

        在调用函数时,主调函数在调用被调用函数时,通过参数向被调用函数传递数据,一般情况下,执行被调用函数时会得到一个函数值,供主调函数使用。
例:void Fun(int i);

七、关于函数代码块的变量类型

        C语言变量的作用域分为:代码块作用域(代码块是{}之间的一段代码);函数作用域;文件作用域。关于作用域范围。变量可分为局部变量,全局变量。

1)局部变量

        它也叫auto自动变量(auto可写可不写),一般情况下代码块{}内部定义的变量都是自动变量,局部变量定义在函数内部,只对当前函数有效,如“分”内定义的局部变量与外面重名,则以内部为准。


它有如下特点:
在一个函数内定义,只在函数范围内有效;
在复合语句中定义,只在复合语句中有效;
随着函数调用的结束或复合语句的结束局部变量的声明声明周期也结束;
如果没有赋初值,内容为随机。

示例代码:

#include <stdio.h>

void test()
{
	//auto写不写是一样的
	//auto只能出现在{}内部
	auto int b = 10; 
}

int main(void)
{
	//b = 100; //err, 在main作用域中没有b

	if (1)
	{
		//在复合语句中定义,只在复合语句中有效
		int a = 10;
		printf("a = %d\n", a);
	}

	//a = 10; //err离开if()的复合语句,a已经不存在
	
	return 0;
}

2)静态局部变量

        静态变量的作用类似于于全局变量,所有的全局变量均为静态变量,而局部变量只有定义时加上类型修饰符 static,才为局部静态变量。

例如:
static int nNum;
static auto nNum =1;
静态变量的特点是在程序的整个执行过程中始终存在,但是在它作用域之外不能使用。静态变量的生存期就是整个程序的运行期,函数体内如果在定义静态变量的同时进行了初始化,则以后程序不再进行初始化操作。

static局部变量的作用域也是在定义的函数内有效。
static局部变量的生命周期和程序运行周期一样,同时staitc局部变量的值只初始化一次,但可以赋值多次。
static局部变量若未赋以初值,则由系统自动赋值:数值型变量自动赋初值0,字符型变量赋空字符。

代码示例:

#include <stdio.h>

void fun1()
{
	int i = 0;
	i++;
	printf("i = %d\n", i);
}

void fun2()
{
	//静态局部变量,没有赋值,系统赋值为0,而且只会初始化一次
	static int a;
	a++;
	printf("a = %d\n", a);
}

int main(void)
{
	fun1();
	fun1();
	fun2();
	fun2();
	
	return 0;
}

3)全局变量

        全局变量需要定义在函数外部,对所有函数有效。全局变量的作用范围是从定义变量的位置开始到源程序结束,即全局变量可以被在其定义位置之后的其它函数所共享。全局变量主要用于函数之间数据的传递。
        除此之外,函数可以将结果保存在全局变量中,这样函数得到多个执行结果,而不局限于一个返回值,由于函数可以直接使用全局变量的数据,因此可以减少函数调用时的参数。
        需要注意的是,全局变量可以在多个函数中使用,当其中一个函数改变了全局变量的值可能会影响其他函数的执行结果。在一个函数内定义了一个与全局变量名相同的局部变量(或者是形参)时,局部变量有效,而全局变量在该函数内不起作用。
        它在函数外定义,可被本文件及其它文件中的函数所共用,若其它文件中的函数调用此变量,须用extern声明。全局变量的生命周期和程序运行周期一样。不同文件的全局变量不可重名。

4)静态全局变量

在函数外定义,作用范围被限制在所定义的文件中。
不同文件静态全局变量可以重名,但作用域不冲突。
static全局变量的生命周期和程序运行周期一样,同时staitc全局变量的值只初始化一次。


如果组成这一个程序的几个文件需要用到同一个全局变量,只要在其它引用该全局变量的源程序文件中说明该全局变量为 extern 即可。
extern int nNum; // nNum 已经在其他位置定义 ,这个变量在别的文件中已经定义了,这里只是声明,而不是定义。
如果一个源程序文件中的全局变量仅限于该文件使用,只要在该全局变量定义时的类型说明前加 static 即可。
staticint nNum;  //nNum仅限于本文件使用

八、全局函数与静态函数

        在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静态,函数定义为static就意味着这个函数只能在定义这个函数的文件中使用,在其他文件中不能调用,即使在其他文件中声明这个函数都没用。

对于不同文件中的staitc函数名字可以相同。

代码示例:


注意:
1. 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰。
2.同一源文件中,允许全局变量和局部变量同名,在局部变量的作用域内,全局变量不起作用。
3.所有的函数默认都是全局的,意味着所有的函数都不能重名,但如果是staitc函数,那么作用域是文件级的,所以不同的文件static函数名是可以相同的。

关于一些关键字修饰的函数和变量的区别如下:

  • 11
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

攻城狮7号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值