初阶C语言-函数(下)

这篇具有很好参考价值的文章主要介绍了初阶C语言-函数(下)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

“追光的人,终会光芒万丈!” 今天我们继续一起来学习一下函数的相关知识点。

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

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

5.1嵌套调用

#define _CRT_SECURE_NO_WARNINGS 1
//函数的嵌套调用
#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;
}

初阶C语言-函数(下),c语言,开发语言
注:函数可以嵌套调用,但是不能嵌套定义!

//函数不能嵌套定义
void test()
{
	void new_line()
	{
		printf("haha\n");
	}
}

初阶C语言-函数(下),c语言,开发语言

5.2链式访问

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

#include <stdio.h>
#include <string.h>
int main()
{
	printf("%d\n", strlen("abc"));//链式访问
	printf("%d\n", sizeof("abc"));
	return 0;
}

初阶C语言-函数(下),c语言,开发语言

注:sizeof就是一个计算数据类型所占空间大小的单目运算符,在计算字符串的空间大小时,包含了结束符\0的位置;而strlen是一个计算字符串长度的函数,使用时需要引用头文件#include <string.h>,不包含\0,即计算\0之前的字符串长度。

#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[20] = { 0 };
	char arr2[] = "abc";
	printf("%d", strlen(strcpy(arr1, arr2)));
	return 0;
}

初阶C语言-函数(下),c语言,开发语言

#include <stdio.h>
#include <string.h>
int main()
{
	printf("%d", printf("%d", printf("43")));
	return 0;
}

通过查阅资料,我们可以发现printf函数的返回值是打印在屏幕上字符的个数。
初阶C语言-函数(下),c语言,开发语言
初阶C语言-函数(下),c语言,开发语言

6.函数的声明和定义

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//写一个函数实现两个数相加
int main()
{
	int num1 = 0;
	int num2 = 0;
	scanf("%d%d", &num1, &num2);
	//函数使用之前需要添加函数的声明
	int add(int x, int y);
	//函数的调用,传值调用
	int ret = add(num1, num2);
	printf("%d\n", ret);
	return 0;
}
//函数的定义
int add(int x, int y)
{
	return x + y;
}

初阶C语言-函数(下),c语言,开发语言
注:函数在使用之前一定要先声明,不然会出现警告!
初阶C语言-函数(下),c语言,开发语言
当然,有的同学可能会想到之前我们在使用函数的时候,也没有声明,为什么没有出现警告呢?

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//写一个函数实现两个数相加
//函数的定义
int add(int x, int y)
{
	return x + y;
}
int main()
{
	int num1 = 0;
	int num2 = 0;
	scanf("%d%d", &num1, &num2);
	//函数的调用,传值调用
	int ret = add(num1, num2);
	printf("%d\n", ret);
	return 0;
}

初阶C语言-函数(下),c语言,开发语言
我们可以发现这时函数的定义是在函数的使用之前,我们需要注意的是函数的定义也是一种特殊的声明。

6.1函数声明

1.告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
2.函数的声明一般出现在函数的使用之前。要满足先声明后使用
3.函数的声明一般放在头文件中的。

6.2函数定义

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

未来在工程中代码比较多,函数一般放在.h文件中声明,在.c文件中实现。

//add.h文件
#pragma once
//函数的声明
int add(int x, int y);
//add.c文件
#define _CRT_SECURE_NO_WARNINGS 1
//函数的定义
//函数具有外部链接属性
int add(int x, int y)
{
	return x + y;
}
//test.c文件
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "add.h"//通过#include" "引入自定义.h头文件
int main()
{
	int num1 = 0;
	int num2 = 0;
	scanf("%d%d", &num1, &num2);
	//函数的调用,传值调用
	int ret = add(num1, num2);
	printf("%d\n", ret);
	return 0;
}

分文件书写形式的优点:
1.方便多人协作
2.保护代码

7.函数递归

7.1什么是递归?

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

7.2递归的两个必要条件

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

7.2.1 练习1

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

//递归
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void Print(int n)
{
	if (n > 9)
	{
		Print(n / 10);
	}
	printf("%d ", n % 10);
}
int main()
{
	int num = 0;
	scanf("%d", &num);
	Print(num);
	return 0;
}

初阶C语言-函数(下),c语言,开发语言
递归的本质就是递推+回归,那么,这段代码是如何实现的呢?
初阶C语言-函数(下),c语言,开发语言

函数之所以能实现调用,能实现递归都是因为函数在调用的时候会维护一个函数栈帧(内存上的一块区域)。函数调用开始,函数栈帧创建,函数调用结束后,栈帧销毁。

这里假设n=123(数值小好画图)给大家演示一下栈区存储情况:
初阶C语言-函数(下),c语言,开发语言

7.2.2 练习2

练习2:编写函数不允许创建临时变量,求字符串的长度。
如果允许用临时变量,相信我们大家都会想到strlen函数来求字符串长度。

#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[] = "abc";
	int len = strlen(arr1);
	printf("%d\n", len);
	return 0;
}

初阶C语言-函数(下),c语言,开发语言
包括下面这段代码,也是用到了临时变量,不符合题目要求。

#include <stdio.h>
size_t my_strlen(char* str)
{
	size_t count = 0;//创建了临时变量,不满足题目要求
	while (*str != '\0')
	{
		str++;
		count++;
	}
	return count;
}
int main()
{
	char arr[] = "abc";
	size_t len = my_strlen(arr);
	printf("%zd\n", len);
	return 0;
}

那么,现在题目中不允许使用临时变量,我们该怎么编写这段代码呢?这里,我们可以利用递归来解决这个问题。

#include <stdio.h>
size_t my_strlen(char* str)
{
	if (*str == '\0')
		return 0;
	else
		return 1 + my_strlen(str + 1);
}
int main()
{
	char arr[] = "abc";
	size_t len = my_strlen(arr);//传递的是数组元素的首地址
	printf("%zd\n", len);
	return 0;
}

初阶C语言-函数(下),c语言,开发语言
初阶C语言-函数(下),c语言,开发语言

7.3递归和迭代

7.3.1练习三

练习三:求n的阶乘(不考虑溢出)

//求n的阶乘
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
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;
}

初阶C语言-函数(下),c语言,开发语言
初阶C语言-函数(下),c语言,开发语言

但是,这里存在着栈溢出的问题,如果我们按照下面这种方法编写程序就可以避免这个问题。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int Fac(int n)//将递归代码改为循环的方式
{
	int i = 0;
	int r = 1;
	for (i = 1; i <= n; i++)
	{
		r = r * i;
	}
	return r;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = Fac(n);
	printf("%d\n", ret);
	return 0;
}

初阶C语言-函数(下),c语言,开发语言

7.3.2练习四

练习四:求第n个斐波那契数。(不考虑溢出)
斐波那契数列(Fibonacci sequence),又称黄金分割数列,因意大利数学家莱昂纳多·斐波那契(Leonardo Fibonacci)1202年以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:
初阶C语言-函数(下),c语言,开发语言
这个数列从第 3项开始,每一项都等于前两项之和。

//斐波那契数列
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int Fib(int n)
{
	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("%d\n", ret);
	return 0;
}

初阶C语言-函数(下),c语言,开发语言

但是,我们发现这样有问题:

  • 在使用Fib这个函数的时候,如果我们要计算第50个斐波那契数字的时候特别耗费时间。
  • 使用factorial函数求10000的阶乘(不考虑程序的正确性),程序会崩溃。

为什么呢?我们发现Fib函数在调用的过程中很多计算其实在一直重复。
那么我们该如何改进呢?

  • 在调试factorial函数的时候,如果你的参数比较大,那就会报错:stack overflow(栈溢出)这样的信息。系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者四递归,这样有可能导致一直开辟空间,最终产生栈空间耗尽的情况,这样的现象我们就称为栈溢出

这里,我们就可以将递归改为非递归:

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

初阶C语言-函数(下),c语言,开发语言

注:如果使用递归很容易想到,写出来的代码没有明显的缺陷,那就可以使用递归。但是如果写出的递归代码,有明显的问题,比如:栈溢出、效率低等,我们还是要使用迭代(循环)的方式来解决。

解决栈溢出的问题的方法:

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

好啦,关于函数的知识点到这里就结束啦,后期会继续更新C语言相关知识点,欢迎大家持续点赞、关注和评论!大家多多支持团子呀!文章来源地址https://www.toymoban.com/news/detail-628685.html

到了这里,关于初阶C语言-函数(下)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 初阶C语言——特别详细地介绍函数

     💓作者简介: 加油,旭杏,目前大二,正在学习 C++ , 数据结构 等👀 💓作者主页:加油,旭杏的主页👀 ⏩本文收录在:再识C进阶的专栏👀 🚚代码仓库:旭日东升 1👀 🌹欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖        从标题也能看出来,我们有要进行 超详细

    2024年02月14日
    浏览(33)
  • 『C语言初阶』第四章-函数

    🔥 博客主页 : 小羊失眠啦. 🔖 系列专栏 : C语言 🌥️ 每日语录 : 世上无难事,只怕有心人。 ❤️ 感谢大家点赞👍收藏⭐评论✍️ 各位小伙伴们,时隔多日,小羊今天又来和大家一起学习C语言新的知识:函数,好的,废话不多说,咱们直接进入主题! 通常大家第一眼

    2024年02月14日
    浏览(28)
  • 【初阶C语言3】特别详细地介绍函数以及在初阶中重要的算法——递归

     💓作者简介: 加油,旭杏,目前大二,正在学习 C++ , 数据结构 等👀 💓作者主页:加油,旭杏的主页👀 ⏩本文收录在:再识C进阶的专栏👀 🚚代码仓库:旭日东升 1👀 🌹欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖        从标题也能看出来,我们有要进行 超详细

    2024年02月08日
    浏览(45)
  • 【初阶C语言】学会使用库函数getchar和putchar

    目录 一、getchar函数 1.作用: 2.认识函数 3.运用 二、putchar函数 1.作用 2.定义 3.运用 三、getchar与putchar的运用 四、getchar的应用 1.作用:       该函数的作用是 接收一个字符 ,然后把该字符 转化对应的ASCII值 2.认识函数  参数类型:括号里面的void意思是 不接受参数 ,就是括

    2024年02月15日
    浏览(40)
  • 【初阶C语言】关于scanf函数的超详细介绍和多组输入

    感谢各位可以光临娥子的作品   我们学习一个函数,要围绕三个点 :1.函数是什么  2.函数的用法   3.注意的细节 认识一个 函数,我们需要了解他的功能、返回值、接收的参数三个方面 函数原型  (1)功能介绍          scanf函数是一个输入函数 ,所以接收的参数类型需

    2024年02月08日
    浏览(53)
  • 以太坊智能合约开发:Solidity语言中的构造函数

    Solidity语言中关于构造函数的定义: 构造函数是使用 constructor 声明的一个可选函数; 构造函数只在合约部署时调用一次,并用于初始化合约的状态变量; 如果没有显式定义的构造函数,则由编译器创建默认构造函数。 构造函数声明语法如下: 其中: ** constructor :

    2024年02月01日
    浏览(51)
  • 【C++初阶】构造函数和析构函数

    📖 默认成员函数 用户没有显式实现,编译器会自动生成的成员函数,称为默认成员函数。 构造函数 :完成对象的初始化工作。 析构函数 :完成对象空间的清理工作。 拷贝构造 :使用同类对象初始化创建对象。 赋值重载 :把一个对象赋值给另外一个对象(该对象已存在

    2024年02月17日
    浏览(46)
  • 【C++初阶】:内联函数

    在c语言中,如果一个函数较短并且会被重复使用,那么我们就可以把该函数换成一个宏函数。 贴个小知识,宏函数有许多的坑也是面试官喜欢考的点,如果对行函数不太了解可以看看这篇关于宏的部分传送门 宏函数的优点:不会建立栈帧,提高使用效率。 宏函数的缺点:复

    2023年04月25日
    浏览(31)
  • 【C++初阶】类和对象——构造函数&&析构函数&&拷贝构造函数

    ========================================================================= 个人主页点击直达: 小白不是程序媛 C++系列专栏: C++头疼记 ========================================================================= 目录 前言 类的6个默认成员函数 构造函数 概念 构造函数的特性 析构函数 概念 析构函数特性 拷贝构

    2024年02月06日
    浏览(56)
  • 【C++】初阶 --- 内联函数(inline)

    🥰 用C语言先来实现普通的Add函数看一下 👇 转到反汇编来看一下: 可以看到,编译器为了实现一个简单的相加函数,Add函数体内需要执行的汇编指令要很多,而且为了调用函数还要执行指令跳转 (并且要在栈区上为函数开辟栈帧空间) ,如果 Add函数被重复大量地使用,则会

    2024年02月14日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包