C语言教程---函数

1.函数的分类

C语言程序是由函数组成的,我们写的代码都是从主函数 main()开始执行的。

函数是 C 程序的基本模块,是用于完成特定任务的程序代码单元。

从函数定义的角度看,函数可分为系统函数和用户定义函数两种:

  • 系统函数,即库函数:这是由编译系统提供的,用户不必自己定义这些函数,可以直接使用它们,如我们常用的打印函数printf()。

  • 用户定义函数:用以解决用户的专门需要。

2.函数的基本结构

函数的定义就是函数体的实现,函数体就是一个代码块,它在函数调用时执行。

语法

函数返回类型 函数名(形式参数列表){
    函数体
    执行任务
    return 返回值;//如果返回类型不是void就有返回值
}

  • 函数返回类型:指定函数执行后返回的值的类型。它可以是基本数据类型,如 int、float、char 等,或者是 void,表示函数不返回任何值。

  • 函数名:标识函数的名称,必须遵循标识符的命名规则。

  • 参数列表:函数接受的输入参数,函数内部通过这些参数来执行任务。参数列表由一对圆括号 () 包围,可以为空,也可以包含多个参数,参数类型之间用逗号分隔。

  • 函数体:函数的实际执行代码块,位于一对花括号 {} 之间。函数体包含执行任务的语句。

3.函数的声明、定义和调用

函数声明(或叫做原型):告诉编译器函数的返回类型、函数名以及参数类型。通常,函数声明放在文件的顶部,或者在头文件中进行声明。

返回类型 函数名(参数类型1, 参数类型2, ...);

例如:

int add(int, int);

函数定义:函数的实际实现,它包含了函数的功能代码。

int add(int a, int b) {
    return a + b;
}

函数调用:通过函数名和传递给函数的参数来执行该函数。

int result = add(5, 3);

整体来举个例子

#include<stdio.h>

// 函数的声明
int sum(int, int);

// 函数的使用可以省去重复代码的编写,降低代码重复率

// 求两个数之和
// 函数定义
int sum(int a, int b) {
	return a + b;
}

int main() {

	int x = 10, y = 20;
    // 函数调用
	int result = sum(x,y);
	printf("两数之和为:%d\n",result);

	return 0;

}

4.全局变量和局部变量

全局变量

在函数外部定义的变量叫做全局变量

特点:

  • 作用于---在本文件中的任何函数中都可以使用

  • 声明周期---在定义变量的时候,系统会申请内存空间,当main()函数结束的时候,系统会回收全局变量对应的内存空间

  • 全局变量未初始化的时候值默认为0

局部变量

在函数内部定义的变量叫做局部变量

特点:

  • 作用域---只能在定义它的那个函数内部中使用,其他位置不能使用

  • 生命周期---在定义变量的时候,系统会申请内存空间,当定义该变量的函数调用结束之后,系统会回收局部变量对应的内存空间

  • 局部变量未初始化的时候值默认为随机值

#include<stdio.h>

// 全局变量
int global = 20;

void do_global() {

	global++;
	printf("gloabl = %d\n",global);

	//printf("local = %d\n",local);// 未定义标识符,因为local是局部变量,只能在定义它的那个函数内部中使用

	return;

}

void do_local() {

	// 局部变量
	int local = 10;
	local++;
	printf("local = %d\n",local);
	return;

}

int main() {

	do_global();
	do_global();
	do_global();

	printf("=========================================\n");

	do_local();
	do_local();
	do_local();

	return 0;
}

静态局部变量

格式:

static 数据类型 变量名;

如:

static int c;

特点:

  • 作用域---只能在定义它的函数内部使用,其他位置不能使用

  • 生命周期---静态局部变量,可以改变原来变量的生命周期,当定义该变量的函数调用结束后,系统不会回收对应的内存空间,当main()函数结束后,内存空间才会被回收

  • 静态局部变量未初始化的时候值默认为0

#include<stdio.h>

void do_stalocal() {

	// 静态局部变量
	static int sl = 10;
	sl++;

	printf("sl = %d\n",sl);

	return;

}

int main() {

	do_stalocal();
	do_stalocal();
	do_stalocal();

	return 0;
}

5.形参和实参

形参:

形参是函数定义时的参数,它们仅在函数内部有效。

形参是函数用来接收传入数据的变量,定义时只是一个占位符,不包含实际值。

例如

int add(int a, int b) {  // a 和 b 是形参
    return a + b;
}

实参:

实参是函数调用时传递给函数的实际值或变量。它们在调用函数时被传递给形参,函数根据这些值来执行操作。

例如

int result = add(3, 5);  // 3 和 5 是实参

int result = add(3, 5);  // 3 和 5 是实参

传递方式:

  • 值传递:实参的值被传递到形参,函数内的修改不会影响外部的实参。

  • 引用传递:通过指针传递实参的地址,这样函数内的修改会直接影响外部的实参。

下面来介绍参数的传递方式

6.函数的参数传递方式

参数传递的本质就是变量之间的赋值操作。

6.1值传递

传递给函数的参数是实参的副本,函数内部的修改不会影响到外部变量。

#include<stdio.h>

void func(int a) {
	a = 20;
	printf("函数中的形参a的值为:%d\n",a);
}

int main() {

	int a = 10;
	printf("实参a为:%d\n",a);
	func(a);
	printf("调用函数之后a的值为:%d\n",a);
	
	return 0;

}

经典例子:交换两个变量的值

使用值传递看看啥情况,要和下面的地址传递来做个对比

#include<stdio.h>

void swap_value(int a,int b) {

	// 先定义一个临时变量temp来保存a的值
	int temp = a;
	// 然后将b的值赋给变量a
	a = b;
	// 然后将temp的值(也就是刚才a的值给b实现交换)
	b = temp;

}

int main() {

	int x = 10, y = 20;
	swap_value(x,y);
	// 看一下有没有将两个变量的值进行交换
	printf("x = %d,y = %d\n",x,y);
	
	return 0;

}

6.2地址传递

地址传递(通过指针,指针在上一个笔记中已经介绍了,特别重要的知识点)通过传递参数的地址来允许函数修改外部变量的值。

#include<stdio.h>

void func1(int *a) {
	*a = 20;
	printf("函数中的形参a的值为:%d\n", *a);
}

int main() {

	int a = 10;
	printf("实参a为:%d\n", a);
	func1(&a);//&是取地址符,取变量a的所在地址的值
	printf("调用函数之后a的值为:%d\n", a);

	return 0;

}

经典例子:交换两个变量的值

使用地址传递看看啥情况,要和上面的值传递来做个对比

#include<stdio.h>

void swap_addr(int *a, int *b) {

	// 先定义一个临时变量temp来保存a的值
	int temp = *a;
	// 然后将b的值赋给变量a
	*a = *b;
	// 然后将temp的值(也就是刚才a的值给b实现交换)
	*b = temp;

}

int main() {

	int x = 10, y = 20;
	swap_addr(&x, &y);
	// 看一下有没有将两个变量的值进行交换
	printf("x = %d,y = %d\n", x, y);

	return 0;

}

6.3区别

  • 值传递是将实参的值传递给函数的形参。函数接收到的是实参的副本,也就是说,函数操作的是形参的副本,而不是实参本身。就意味着函数内部对形参的修改不会影响外部实参。

  • 引用传递是通过指针传递实参的地址给函数。通过传递地址,函数可以直接修改实参的值,因为函数操作的是实参的直接内存地址,而不是它的副本。

结合前面学习的指针应该会比较好理解。

7.指针函数

本质:是一个函数,只不过返回值是一个地址

如何定义:

返回值类型 * 函数名称(类型1 参数1,类型2 参数2){
    
}

例如:
    int* fun(int a,int b){
        
    }
    也就是说,指针函数其实和普通的函数一样,只不过返回值是一个指针类型的,并且它必须用同类型的指针变量来接收

举个例子

#include<stdio.h>

// 设计一个数组,返回数组首地址
int* des_array(int *plen) {

    // 定义局部数组
	int a[] = { 1,2,3,4,5 };
	int len = sizeof(a) / sizeof(a[0]);

	// 通过指针传参的方式,把数组长度传回去
	*plen = len;

	 // 返回数组首地址(注意这里有问题,a是局部变量,函数结束就释放了,看下面解决方案)
	return a;
}

// 输出数组
void output_array(int *p,int plen) {
	for (int i = 0;i < plen;i++) {
		printf("%d ", p[i]);
	}
	printf("\n");
}

int main() {

	int num = 0, i = 0;
	int* p = NULL;
	p = des_array(&num);
	output_array(p,num);

	return 0;
}

运行结果如下

#include<stdio.h>

// 设计一个数组,返回数	组首地址
int* des_array(int *plen) {

    // 定义静态局部数组
	static int a[] = { 1,2,3,4,5 };
	int len = sizeof(a) / sizeof(a[0]);

	// 通过指针传参的方式,把数组长度传回去
	*plen = len;

	 // 返回数组首地址
	return a;
}

// 输出数组
void output_array(int *p,int plen) {
	for (int i = 0;i < plen;i++) {
		printf("%d ", p[i]);
	}
	printf("\n");
}

int main() {

	int num = 0, i = 0;
	int* p = NULL;
	p = des_array(&num);
	output_array(p,num);

	return 0;
}

8.函数指针

本质:函数指针本质是一个指针,只是用来保存函数的地址的,通过函数指针来调用我们需要的函数。

定义方法:

数据类型 (*指针变量名)(类型1 参数1,类型2 参数2){

}
注意:
    函数名代表函数的首地址

#include<stdio.h>

// 函数名就代表了函数的地址
int add(int a, int b) {
	return a + b;
}
int sub(int a, int b) {
	return a - b;
}

int main() {

	int ret = 0;
	int a = 10, b = 20;
	ret = add(a, b);
	printf("a + b = %d\n",ret);

	printf("add = %p\n",add);

	printf("==============================\n");

	int (*funp)(int, int) = add;

	printf("funp = %p\n", funp);

	ret = funp(a, b);

	printf("a + b = %d\n",ret);

	funp = sub;
	ret = funp(a, b);

	printf("a - b = %d\n",ret);


	return 0;
}

上面的例子演示的是函数指针来存储函数的地址的,下面来介绍一下使用函数指针来调用函数的功能,也就是回调函数的概念。

回调函数

回调函数是一个通过函数指针调用的函数。如果将某个函数的地址作为参数传递给另一个函数,在另一个函数中用指针来接收,通过指针来调用其函数,我们就说这个是回调函数。回调函数通常用于在特定事件或条件发生时,由另一个函数“回调”执行。

概念可能不是很好理解,下面来举个例子理解一下

在上面的例子中,使用的是先使用函数指针来保存函数的地址然后调用函数,不是很方便,这时候使用回调函数就挺好的,通过一个回调函数将所有的函数都调用了,下面的代码如下

#include<stdio.h>

// 函数名就代表了函数的地址
int add1(int a, int b) {
	return a + b;
}
int sub1(int a, int b) {
	return a - b;
}

// int a = 10;
// int b = 20;
// int (*funp)(int,int) = add1;
// 或者
// int (*funp)(int,int) = sub1;
// 
// 将一个函数的地址(函数指针)作为参数传递给另一个函数
int calc(int x, int y, int(*funp)(int,int)) {
	int ret = 0;
	// 接收函数指针的函数在内部通过函数指针调用传入的函数
	ret = funp(x,y);
	return ret;
}

int main() {

	int ret = 0;
	int a = 10, b = 20;

	// 把add函数的地址给calc函数,在calc函数中调用add1函数来实现两个数的相加	
	ret = calc(a, b, add1); 
	printf("a + b = %d\n",ret);

	// 把sub函数的地址给calc函数,在calc函数中调用sub1函数来实现两个数的相减
	ret = calc(a, b, sub1);
	printf("a - b = %d\n", ret);

	return 0;
}

9.递归函数

概念:在函数体内,自己不断地调用自己,直到某个条件满足才结束调用,这样的函数称为递归函数。

递归的一般结构包括:

void recursion() {
    // 基本条件(结束条件)
    if (终止条件) {
        return;
    } else {
        // 递归调用
        recursion();
    }
}

两个要素:

  • (1) 基本条件 确保递归有退出的条件,否则会导致 无限递归,最终程序崩溃(栈溢出)。

  • (2) 递归关系(递推公式) 每次递归 缩小问题规模,最终趋近基本情况。

例子:求n的阶乘

涉及到数学知识了,阶乘的定义为:n!=n×(n−1)×(n−2)×⋯×1

比如说5的阶乘,

5! = 5 * 4! = 5 * 4 * 3! = 5 * 4 * 3 * 2! = 5 * 4 * 3 * 2 * 1! = 5*4*3*2*1

可以发现一个规律就是:n!=n×(n−1)!

代码如下

#include<stdio.h>

int factorial(int n) {

	if (n == 0 || n == 1) {
		return 1;
	}
	else {
		return n * factorial(n - 1);
	}
}

int main() {

	int num = 0;
	printf("请输入要求几的阶乘:");
	scanf("%d",&num);
	int sum = factorial(num);
	printf("%d的阶乘为:%d\n",num,sum);

	return 0;
}

经典例子:求斐波那契数列

斐波那契数列的规律如下:

  • 第 0 项:F(0)=0F(0)=0

  • 第 1 项:F(1)=1F(1)=1

  • 从第 2 项开始,每一项都等于前两项之和:

    F(n)=F(n−1)+F(n−2)F(n)=F(n−1)+F(n−2)

根据定义,斐波那契数列的前几项为:

0,1,1,2,3,5,8,13,21,34,55,89,144,…

#include<stdio.h>

// 求斐波那契数列
int fibonacci(int n) {
	if (n == 0) {
		return 0;
	}
	else if (n == 1) {
		return 1;
	}else{
		return fibonacci(n - 1) + fibonacci(n - 2);
	}
}

int main() {

	int num = 0;

	printf("请输入要计算的斐波那契数列的项:");
	scanf("%d", &num);
	printf("斐波那契数列的第%d项是:%d\n",num,fibonacci(num));

	return 0;
}

10.函数的执行流程

先来看一个函数实例来学习执行过程

#include <stdio.h>

void print_test()
{
	printf("测试函数的执行过程\n");
}

int main()
{
	print_test();	// print_test函数的调用

	return 0;
}
  1. 进入main函数

  2. 调用print_test()函数

    a. 它会在main()函数的前寻找有没有一个名字叫“print_test”的函数定义;

    b. 如果找到,接着检查函数的参数,这里调用函数时没有传参,函数定义也没有形参,参数类型匹配;

    c. 开始执行print_test()函数,这时候,main()函数里面的执行会阻塞( 停 )在print_test()这一行代码,等待print_test()函数的执行。

  3. print_test()函数执行完( 这里打印一句话 ),main()才会继续往下执行,执行到return 0, 程序执行完毕。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值