速通C语言第三站 一篇博客带你搞定函数

本文介绍了C语言中的函数概念,包括库函数如strcpy和memset的使用,自定义函数的创建和参数传递,以及函数调用的传值与传址。文章通过示例讲解了函数递归的基本思想,并分析了递归的必要条件,强调了递归在编程中的应用和潜在问题。
摘要由CSDN通过智能技术生成

系列文章目录

速通C语言系列

 速通C语言第一站 一篇博客带你初识C语言       http://t.csdn.cn/K43IN

 速通C语言第二站 一篇博客带你搞定分支循环   http://t.csdn.cn/O9ISr


又是你在卷是吧

 感谢佬们阅读支持!

文章目录

  • 系列文章目录
  • 前言
  • 一、函数是什么?
  • 二、库函数
  •            库函数的分类
  •            举例
  • 三、自定义函数
  •             函数的组成
  •             举例
  • 四、函数的参数
  • 五、函数调用
  •              传值调用、传址调用
  •              举例
  • 六、函数的嵌套调用和链式访问
  •               嵌套调用
  •               链式访问
  •               举例
  • 七、函数的声明及定义
  •               规则
  •               举例
  • 八、函数递归
  •               小例
  •               举例
  •               递归的两个必要条件
  • 总结


前言

上篇博客我们详细地带大家剖析了分支循环,这篇博客我们来搞另一个重要的章节----函数。


一、函数是什么?

大型程序中的部分代码,由一个/多个语句块组成。完成某项特定任务,相对独立,有输入参数、返回值。

二、库函数

库函数的分类

IO函数(输入输出函数)如printf、scanf、putchar            
字符串操作函数如strcmp、strlen
字符操作函数如toupper(小写转大写)
内存操作函数如memcmp
时间、日期函数如time
数学函数如sprt、pow
其他库函数

举例

我们举两个函数为例

strcpy

字符串拷贝函数

  

char*strcpy(char*destnation,char*source);
                //目的地        //源头

例: 

#include<stdio.h>
int main()
{
	char arr1[20] = { 0 };
	char arr2[] = { "hello yigang"};
	//将arr2中的字符放入一中
	strcpy(arr1, arr2);
	printf("%s", arr1);
}


memset

mem指的是memory,所以是对内存操作的。

memset  内存设置。

void*memset(void *ptr,int value,size_t num);
//把ptr所指的那块内存的前num个字节设置为想要的value值

例:

#include<stdio.h>
int main()
{
char arr[] = "hello yigang";
	//把hello的前5个字节换成x
	memset(arr, 'x', 5);
	printf("%s\n", arr);
return 0;
}


三、自定义函数

自定义函数与库函数一样,有函数名、返回值类型和函数参数,但不一样的是这些都需要我们自己来实现。

函数的组成

ret_type   fun_name(para1, ……);
//函数返回类型   函数名    参数   如果无需传参,直接在括号中写void
{
			 //函数体
			 statement;
}

例:写一个函数找出两个数的较大值

int get_max(int x, int y)
	{
		int z = 0;
		if (x > y)
		{
			z = x;
		}
		else
		{
			z = y;
		}
		return z;
	}
	int main()
	{
		int a = 10;
		int b = 20;
		//调用函数
		int max = get_max(a, b);
		printf("^%d ", max);
		return 0;
	}


例:写一个函数交换两个整形变量的内容。

void swap(int x, int y)
{
	//临时变量
	int z = 0;
	z = x;
	x = y;
	y = z;
}
int main()
{
	int a = 10;
	int b = 20;
	//交换前
	printf("%d %d", a, b);
	swap(a, b);
	//交换后
	printf("%d %d", a, b);

}

但是运行之后,并未得到我们想要的结果。

  

 这是为什么呢?

因为其地址没变,我们将a、b叫做实参,x、y叫做形参,x、y有各自独立的空间,所以交换x、y,并不会影响a、b

 所以为了能改变其地址,我们在传参时就直接传地址过去。

#include<stdio.h>
void swap(int* pa, int* pb)
{
	int z = 0;
	z = *pa;
	*pa = *pb;
	*pb = z;
}
int main()
{
	int a = 10;
	int b = 20;
	printf("%d %d\n", a, b);
	swap(&a, &b);
	printf("%d %d\n", a, b);
	return 0;
}

结果成功了:

     


 思考:为什么我们的第一个例子不用传地址呢?

因为get_max只是找出两个数中的最大值,并不会改变值


四 函数的参数

从上个例子我们知道,函数的参数分为实参、形参。而形参是实c参的一份临时拷贝,对形参的修改不影响实参,所以第一个swap函数没能成功;而当我们传的参数变成指针时,就成功了。

五、函数调用 

 传值调用、传址调用

我们将第一种直接传值的函数调用方式称为传值调用,将第二种传指针的函数调用方式称为

传址调用。

函数swap1为传值调用swap2为传址调用
形参、实参分别占有不同内存块可以使函数和函数外边的变量建立真正的联系,也就是函数内部可以直接操作外部的变量

示例

  用函数判断一个数是否为素数

(带注释版本)

#include<stdio.h>
int is_prime(int n)
{
	int j = 0;
	//从2至n-1当除数试
	for (j = 2; j < n; j++)
	{
		if (n % j == 0)
		{
			return 0;
		}
	}
	//找到了返回1
		return 1;
	
}
int main()
{
	int i = 0;
	//判断100-200间的素数
	for (i = 100; i <= 200; i++)
	{
		if (is_prime(i) == 1)
		{
			printf("%d", i);
		}
	}
	return 0;
}

(不带注释版本)

#include<stdio.h>
int is_prime(int n)
{
	int j = 0;
	for (j = 2; j < n; j++)
	{
		if (n % j == 0)
		{
			return 0;
		}
	}
		return 1;
	
}
int main()
{
	int i = 0;
	for (i = 100; i <= 200; i++)
	{
		if (is_prime(i) == 1)
		{
			printf("%d", i);
		}
	}
	return 0;
}

注:由于有两种情况,所以对应了两个return。


再例:写一个函数,每调用一次,num的值加一。

num的值加一,意味着num会改变,所以我们用传址调用。

void Add(int* p)
{
	(*p)++;
}

int main()
{
	int num = 0;
	Add(&num);
	return 0;
}

六 函数的嵌套调用和链式访问

嵌套调用

嵌套定义非常简单且好理解,我们直接给一个例子。

示例 

void test3()
{
	printf("hehe");
}
int test2()
{
	test3();
	return 0;
}
int main()
{
	test2();
	return 0;
}

注:函数间可嵌套调用,不可嵌套定义。

示例

int main()
{
	int ADD()
	{
		……
	}
}
//不行

链式访问

把一个函数的返回值当作另一个函数的参数

示例:求一个字符串的长度

//一般我们会这么写
int main()
{
	int len = strlen("abc");
	printf("%d", len);
	return 0;
}

其实我们可以通过链式访问直接:

printf("%d",strlen("abc));

两种写法都可得到正确答案


七 函数的声明及定义

规则

对声明及定义,我们有以下规则

 1 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么;但是具体是不是存在,无关紧要。

 2 一般为先声明,后使用。

 3 函数的声明一般放在头文件(.h)函数的定义一般放在(.c)文件中。

示例

 我们实现一个有加、减、乘、除的计算器。

先实现一下加法:我们建立3个文件:ADD.c   ADD.h  test.c

 在ADD.h中我们进行声明

int ADD(int x, int y);

在ADD.c中我们具体定义这个函数,当然,在定义之前,我们需引我们写的ADD.h。由于这个头文件是我们自己实现的,所以声明时要用双引号

#include"ADD.h"
int ADD(int x, int y)
{
	return x + y;
}

在主函数中我们调用它求加法

#include"ADD.h"
int main()
{
	int a = 10;
	int b = 20;
	int c = ADD(a, b);
	printf("%d ", c);
	return 0;
}

其他运算同理,我们可以分别建立多个.c文件和头文件来表示不同算法。

也可以在一个头文件中声明所有函数,在一个.c文件中实现这些函数。

后期我们实现的项目中,这些都是常规操作。


-八、函数递归

我们把程序调用自身的编程技巧称为递归。

特点是将大型的复杂问题层层化为一堆与原问题相似的小问题。

说起来太抽象了,我们给一个例子

小例

#include<stdio.h>
int main()
{
	printf("张一钢");
	main();
	return 0;
}

main函数调用了main函数(自己),结果就是循环打印。


小例仅仅是给大家初步介绍下递归,并未涉及到解决复杂问题的思想,接下来,我会举具体的例子带大家学习递归“大化小”的思想。

举例

接受一个整形值(无符号),依照顺序打印其每一位。如:输入1234,打印1 2 3 4.。

思考

如果是仅仅打印1234中的每一位,我们是有办法的。

就是对1234先取余10得到4,再把1234除以10得到123;

         再对123取余10得到3,再把123  除以10得到12  ;

         再对12  取余10得到2,再把12    除以10得到1

         最后1    取余10得到1.

最后确实能得到每一位,但顺序是反的,即 4 3 2 1. 


  


我们将上面的思路进行一波分解

 

 具体思路:

1 刚开始为1234 > 9, 所以1234/10=123先留下,不打印;

2 上次的 123存起来,123>9 123/10=12  留下,  不打印;

3 上次的  12 存起来, 12>9   12/10=1留下,          不打印;

4 上次的  1   存起来     1<9     所以打印1。

5 打印了1以后,再回到上一层打印2,3,4。

 代码实现

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void print(unsigned int n)
{
	if (n > 9)
	{
		print(n / 10);
	}
	printf("%d ", n % 10);
}

int main()
{
	unsigned int num = 0;
	scanf("%d", &num);
	print(num);
	return 0;
}

我们输入1234

                

示例  :求n的阶乘

n的阶乘就是从n乘n-1……一直到乘到1.所以易得出

int Fac(int n)
{
	if (n <= 1)
	{
		return 1;
	}
	else
	{
		return n * Fac(n - 1);
	}
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = Fac(n);
	printf("%d\n", ret);
	return 0;
}

 

示例

求斐波那契数

科普:斐波那契数列

由科普中的递推公式,我们很容易有思路。

 我们用代码实现一波

int Fib(int j)
{
	if (j <= 2)
	{
		return 1;
	}
	else
	{
		return Fib(j - 1) + Fib(j - 2);
	}
}
int main()
{
	int j = 0;
	scanf("%d", &j);
	int n = Fib(j);
	printf("%d", n);
	return 0;
}

 当我们输入40时,过了几秒才输出结果,如果你尝试更大的数,时间会更长。


这个时候我给大家科普一下递归对内存的使用原理。

 递归所进行的调用并不是左右同时进行的,而是调用完左边后,再开始重新调用右边。

这就导致了调用时会进行一堆重复操作,加长了时间。

递归的两个必要条件

 1 存在限制条件,当满足这个限制条件时,递归便不在继续。

 2 每次递归调用之后会越来越接近。 


即便如此,使用递归时依然可能出现错误。

void test(int n)
{
	if (n < 10000)
	{
		test(n + 1);
	}
}
int main()
{
	test(1);
	return 0;
}

这段代码看似没什么问题,但是当我们运行之后。

 程序直接退出了,这是为什么呢?

系统分配给程序的栈空间是有限的,但是如果出现了死循环/死递归,这样有可能导致一 直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。

再加上递归在调用时会进行一堆重复操作,时间太长。

所以在后期,类似功能能用循环实现的,我们一般不考虑递归。


总结

   做总结,今天这篇博客带大家简单的剖析了函数的相关要点,函数是C语言非常重要的模块,在后期会经常使用,希望大家好好学习函数模块。水平有限,还请各位大佬指正。如果觉得对你有帮助的话,还请三连关注一波。希望大家都能拿到心仪的offer哦。

 每日gitee侠:今天你交gitee了嘛?

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值