草庐IT

C语言学习入门之函数7000字超详解【下】

YoLo♪ 2023-06-21 原文

作者:敲代码の流川枫

博客主页:流川枫的博客

专栏:C语言从入门到进阶

语录:Stay hungry stay foolish

工欲善其事必先利其器,给大家介绍一款超牛的斩获大厂offer利器——牛客网

点击免费注册来和我一起刷题吧

目录

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

函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的

1.1 嵌套调用

代码演示:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
	void new_line()
	{
		printf("hehe\n");
	}
	void three_line()
	{
		int i = 0;
		for (i = 0; i < 3; i++)
		{
				new_line();
		}
	}
int main()
{		
	three_line();
	return 0;
}

注意:函数可以嵌套调用,但是不能嵌套定义,每个函数地位都是相等的,并列的关系

1.2 链式访问

把一个函数的返回值作为另外一个函数的参数
链式访问的条件:函数必须有返回值

代码演示:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{		
	int len = strlen("abcdef");
	printf("%d\n", len);

	//链式访问
	printf("%d\n", strlen("abcdef"));

	printf("%d", printf("%d", printf("%d", 43)));
	return 0;
}

运行结果:

分析:printf函数的返回值是打印的字符的个数

2. 函数的声明和定义

2.1函数声明:

写一个加法函数:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int Add(int x, int y);//函数的声明
int main()
{		
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	//加法
	int sum = Add(a, b);
	printf("%d\n", sum);

	return 0;
}
//函数的定义
int Add(int x, int y)
{
	return x + y;
}

如果不进行函数声明,程序可以编译运行,但是会出现警告
注意:1. 函数声明是告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了
2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用
3. 函数的声明一般要放在头文件中的

2.2函数定义:

函数的定义是指函数的具体实现,交待函数的功能实现

举个栗子:

实现一个计算器
1,加法——A程序猿
add.h add.c

2,减法——B程序猿
sub.h sub.c

3,乘法——C程序猿
mul.h mul.c

4,除法——D程序猿
div.h div.c
代码演示:
add.h

#pragma once
//防止头文件被重复包含
int Add(int x, int y);//函数的声明

add.c

#define _CRT_SECURE_NO_WARNINGS
int Add(int x, int y)
{
	return x + y;
}

sub.h

#pragma once
int Sub(int x, int y);

sub.c

#define _CRT_SECURE_NO_WARNINGS
int Sub(int x, int y)
{
	return x - y;
}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include"add.h"
#include"sub.h"
int main()
{		
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);//20 30
	//加法
	int sum = Add(a, b);
	printf("%d\n", sum);//50
	//减法
	int ret = Sub(a, b);
	printf("%d\n", ret);//-10

return 0;
}

包含头文件的本质是将头文件的内容全部拷贝过来

在初学编程时,我们习惯把所有的代码都写到一个文件中,但是在公司中要考虑分工协作问题,模块化设计

再举个栗子:

公司B想完成一个加法功能test.c,但是他不会,程序猿A写了一个程序可以完成这个任务并且卖给B公司,后期代码的维护维修也是A来完成。为了防止公司复刻源代码,程序猿A将 add.c 编译成静态库文件add.lib,然后将头文件add.h提供给B公司,B公司就知道怎样使用,功能的实现是在add.lib中实现的,add.lib文件都是一些乱码了,无法看懂,这样就在一定程度上保护了源文件

我们看一下怎样将add.c文件编译成静态库:

add>右击鼠标>属性

配置属性>常规>配置类型>静态库(.lib)

add.c配置完成后会生成add.lib,它是无法直接执行的,所以会报错

add.hadd.lib交给B公司


代码演示:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include"add.h"
#pragma comment(lib,"add.lib")
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);//20 30
	//加法
	int sum = Add(a, b);
	printf("%d\n", sum);//50

	return 0;
}

注意:B公司买add.lib回来之后要导入静态库文件才能使用

#pragma comment(lib,"add.lib")

add.lib在vs中打开是这个样子的:

此时,add.c所实现的功能都在add.lib中,B公司是看不到add.c的真是实现的

如果我们将函数的声明和定义放在一起,就不能单独剥离出来让别人使用了

3. 函数递归

3.1 什么是递归?

程序调用自身的编程技巧称为递归( recursion)

递归做为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量

递归的主要思考方式在于:把大事化小

3.2 递归的必要两个条件

1.存在限制条件,当满足这个限制条件的时候,递归便不再继续
2.每次递归调用之后越来越接近这个限制条件

3.2.1 练习1:

接受一个整型值(无符号),按照顺序打印它的每一位 。例如:输入:1234 输出 1 2 3 4

代码演示:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
//%d 是打印有符号的整数(会有正负数)
//%u 是打印无符号的整数
//接受一个整型值(无符号),按照顺序打印它的每一位。
//例如:输入:1234  输出 1 2 3 4

void print(unsigned int n)
{
	if (n > 9)
	{
		print(n/10);
	}
	printf("%d ", n % 10);
}
int main()
{
	unsigned int num=0;
	scanf("%u", &num);
	print(num);//接受一个整型值(无符号),按照顺序打印它的每一位。
	return 0;
}

运行结果:

图解:

注意:1. 存在限制条件,当满足这个限制条件的时候,递归便不再继续,没有限制条件会出现死递归
2. 每次递归调用之后越来越接近这个限制条件

3.2.2 练习2:

编写函数不允许创建临时变量,求字符串的长度

也就是让我们模拟strlen函数功能功能

代码演示:创建了临时变量count来计数

#define _CRT_SECURE_NO_WARNINGS
//模拟实现strlen
#include<stdio.h>
//int my_strlen(char str[])  参数部分写成数组形式
int my_strlen(char* str)//参数部分写成指针形式
{ 
	int count = 0;
	while (*str != '\0')//解引用找到字符
	{
		count++;
		str++;//找下一个字符,每个字符一个字节
	}
	return count;
}
int main()
{
	char arr[] = "abc";
	//char*
	int len = my_strlen(arr);

	printf("%d\n", len);
	return 0;
}

不允许创建临时变量,我们考虑用 递归
递归求解:我们求’abc\0’的长度,如果第一个字符不是’\0’,那我们就可以求1+’bc\0‘的长度,即:第一个字符不是’\0’,就可以拆开
my_strlen(“abc”)
1+my_strlen(“bc”)
1+1+my_strlen(“c”)
1+1+1+my_strlen(" ")
1+1+1+0

代码演示:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int my_strlen(char* str)//参数部分写成指针形式
{ 
	if (*str != '\0')
		return 1 + my_strlen(str + 1);//str++不可以,传进去的还是a地址
	else
		return 0;
}
int main()
{
	char arr[] = "abc";
	//char*
	int len = my_strlen(arr);

	printf("%d\n", len);//3
	return 0;
}

图解:

4. 递归与迭代

4.1练习:

求n的阶乘。(不考虑溢出)

代码演示:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
递归实现
//int fac(int n)
//{ 
//	if (n<=1)
//		return 1;
//	else
//		return n*fac(n-1);
//}
//迭代实现
int fac(int n)
{
	int i = 0;
	int ret = 1;
	for (i = 1; i <= n; i++)
	{
		ret *= i;
	}
	return ret;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = fac(n);
	printf("ret = %d\n", ret);
	return 0;
}

4.2练习:

求第n个斐波那契数。(不考虑溢出)

递归解决代码演示:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
//递归实现
int count = 0;
int Fib(int n)
{ 
	if (n == 3)
		count++;
	if (n<=2)
		return 1;
	else
		return Fib(n-1)+Fib(n-2);
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = Fib(n);
	printf("ret = %d\n", ret);
	printf("%d\n", count);
	return 0;
}

运行结果:

分析:使用递归会重复大量的运算,求第40个斐波那契数的时候,第3个斐波那契数就被重复了39088169次,效率很低,不推荐用递归解决

那如何解决上述的问题?

  1. 将递归改写成非递归
  2. 使用static对象替代nonstatic局部对象。在递归函数设计中,可以使用static对象替代nonstatic局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问

迭代解决代码演示:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
//迭代实现
int Fib(int n)
{ 
	int a = 1;
	int b = 1;
	int c = 0;
	 
	while (n >= 3)
	{
		c = a + b;
		a = b;
		b = c;
		n--;
	}
	return c;//n=1 n=2时返回c
	
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = Fib(n);
	printf("ret = %d\n", ret);
	return 0;
}

分析:1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰
2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些
3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销

有关C语言学习入门之函数7000字超详解【下】的更多相关文章

  1. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  2. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  3. ruby-on-rails - 在 ruby​​ 中使用 gsub 函数替换单词 - 2

    我正在尝试用ruby​​中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了

  4. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  5. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  6. ruby - 在 Ruby 中按名称传递函数 - 2

    如何在Ruby中按名称传递函数?(我使用Ruby才几个小时,所以我还在想办法。)nums=[1,2,3,4]#Thisworks,butismoreverbosethanI'dlikenums.eachdo|i|putsiend#InJS,Icouldjustdosomethinglike:#nums.forEach(console.log)#InF#,itwouldbesomethinglike:#List.iternums(printf"%A")#InRuby,IwishIcoulddosomethinglike:nums.eachputs在Ruby中能不能做到类似的简洁?我可以只

  7. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  8. 7个大一C语言必学的程序 / C语言经典代码大全 - 2

    嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来

  9. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  10. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

随机推荐