【C】带你复习有趣的函数

news2025/1/11 21:01:06

 作者 :会敲代码的Steve

墓志铭:博学笃志,切问静思。

前言:本文旨在总结C语言函数章节的知识点、分为以下九个模块、分别是:

1.函数是什么   2.库函数  3.自定义函数  4.函数参数   5.函数调用 

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

7.函数的声明和定义

8.函数递归

  尾递归

9.函数作用域规则

正文开始:

目录

1.函数是什么

  2.库函数

3.自定义函数

4.函数参数

5.函数调用

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

7.函数的声明和定义

8.函数递归

  尾递归

9.函数作用域规则


1.函数是什么

    我们在学习数学的时候,经常会用到函数的概念。但是你了解C语言中的函数吗?

  1. 在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method, subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组 成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。
  2. 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软 件库。ps:维基百科对于函数的定义

 C语言对于函数的分类

2.库函数

       为什么会有库函数? 

  1. 我们知道在刚开始学习C语言的时候、总是在一段代码编写完成时、想迫不及待的知道它的结果的时候;于是就会使用printf();函数按照一定的格式来打印输出的结果到控制台窗口。
  2. 在编程的过程中、我们需要频繁的完成一些输入工作(scanf)。
  3. 在编程的过程中、我们总是要完成n^k的计算(pow)。

像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到, 为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员 进行软件开发。


那么如何学习库函数呢?

  可以参照:https://cplusplus.com/reference/

 简单的总结,C语言常用的库函数都有:

  1.   IO函数
  2.   字符串操作函数
  3.  字符操作函数
  4.   内存操作函数
  5.  时间/日期函数
  6. 数学函数
  7. 其他库函数

我们参照官方文档来学习几个库函数

  strcat

char * strcat ( char * destination, const char * source );

strcmp 

int strcmp ( const char * str1, const char * str2 );

需要注意的地方:

 在使用某一个库函数时、必须包含#include对应的头文件。

 这里对照文档来学习库函数、目的是学会它的用法。 

如何学习库函数?3

需要全部记住吗?No

需要学会查询工具的使用:

  1. MSDN(Microsoft Developer Network)
  2. www.cplusplus.com http://en.cppreference.com(英文版)
  3. http://zh.cppreference.com(中文版)

英文很重要。最起码得看懂文献。

3.自定义函数

   如果库函数能完成所有的任务,那还要程序员干什么?

所以更加重要的是自定义函数。

自定义函数和库函数一样、有函数名、返回值类型、函数参数。

但不同的是,函数的功能都由我们自己来进行总体的设计、这给了程序员一个很大的发挥空间、

函数的组成部分:

int func(int x,int y)
{

return x+y;

}
int //返回值类型
func//函数名
int x,int y//函数参数

我们举一个例子

求两个数的最小公倍数 

#include <stdio.h>
int gcd(int a, int b) {
    if(!b) {
        return a;           // (1)
    }
    return gcd(b, a % b);   // (2)
}

int main() {
    int a, b;
    while(scanf("%d %d", &a, &b) != EOF) {
        printf("%d\n", gcd(a, b));
    } 
    return 0;
}

 再举一个栗子、计算函数的阶乘和

//1+2!+3!+...+N!的和 (结果为整数形式)
#include<stdio.h>
long long func(int x);
int main()
{
	int a = 0;
	long long sum = 0;
	scanf("%d",&a);
	for(int b = 1;b<=a;b++)
	{
        sum+=func(b);
	}
	printf("%lld",sum);
	return 0;
}
long long func(int x)
{
    if(x==0)
  return 1;
    else
     return func(x-1)*x;

}

4.函数参数

  4.1 实际参数

 真实传给函数的参数叫做实参、参数类型可以是:常量、变量、表达式、函数等。

无论实参是个怎样的值,在进行函数调用的时候、它们必须具有确定的值、以便把这些值传给形参。

  4.2形式参数

形式参数是指函数括号内的变量、因为函数只有在调用的时候才会被实例化(分配内存空间)。所以叫做形式参数、而且形参在函数调用完了之后才会销毁,因此形式参数只有在函数中才有效。

上面的gcd和func函数中的参数、a、b、x、都是形式参数、在main函数中传递的参数都是给gcd函数和func函数的实参。给gcd函数的&a、&b和func函数的&a、都是实际参数

我们对函数的实参和形参做一下分析:

这里可以看到 func 函数在调用的时候, x 拥有自己的空间,同时拥有了和实参一模一样的内容。 所以我们可以简单的认为:形参实例化之后其实相当于实参的一份临时拷贝

5.函数调用

   5.2传值调用

  • 函数的形参和实参分别占据着不同的内存空间,对形参的修改不会影响实参的值。

   5.2 传址调用

  • 传址调用是把函数外部创建的变量的内存地址传递给函数参数的一种调用函数的方式
  • 这种传参方式使函数内部和外部建立起真正的联系,也就是函数内部就可以直接操作外部的变量

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

函数与函数之间是可以按实际的需求进行合作的、也就是说可以嵌套调用的

   6.1嵌套调用

#include <stdio.h>
#pragma warning(disable : 4996
int gcd(int x, int y)
{
    if (!y)
    {
        return x;
    }
    else return gcd(y,x % y);
 
}
int my_we(int x, int y)
{
    int sum = 0;
    sum = (x * y) / gcd(x, y);
    return sum;
}
int main()
{
    int x, y;
    scanf("%d %d", &x, &y);
    printf("%d %d", gcd(x, y), my_we(x, y));
    return 0;
}
 
/* 你的代码将被嵌在这里 */

 6.2 链式访问

把一个函数的返回值做另一个函数的参数

#include <stdio.h>
int main()
{
    printf("%d", printf("%d", printf("%d", 43)));
    //结果是啥?
    //注:printf函数的返回值是打印在屏幕上字符的个数
    return 0;
}

7.函数的声明和定义

 7.1函数声明

  • 告诉编译器函数名字叫什么、要传入几个参数、返回值类型。但是存不存在这个问题、编译器是决定不了的
  • 函数的声明应该放在函数使用之前,需要满足先声明再使用的条件
  • 函数的声明一般要放在头文件中的。

7.2 函数定义

  • 函数的具体实现,交代函数的功能实现。

add.h中的声明:

#pragma once
int add(int x, int y);
int func(int a);

add.c中的实现 :

int add(int x, int y)
{
	return x + y;

}
int func(int a)//递归函数举例
{
	//递推公式
	if (a == 1) return 1;
	return func(a - 1) + a;
}

main函数调用: 

#include<stdio.h>
#include"add.h"
int main()
{
	int a;
	int b;
	int i;
	int sum = 0;
	scanf_s("%d%d%d", &i,&a,&b);
	printf("%d\n", func(i));
	printf("%d\n", add(a, b));
	//在这里调用的哦

}

         多文件编程的好处是在实现某一个功能的时候、可以把它封装成一个模块。等再次要使用的使用的时候直接调用这个接口即可,在调试的时候也方便了不少,节省了大量的时间重构一个项目,这便叫做高内聚、低耦合

8.函数递归

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

                                   递归的核心思想就是:大事化小

   递归的两个必要条件:

  • 存在限制条件、当满足这个限制条件时便不会再继续下去
  • 每次递归调用之后越来越接近于这个限制条件。

案例1 使用递归求1-100的和  例如 :输入 100  输出5050

 参考代码:

#include<stdio.h>
int func(int x);
int main()
{
  int a;
  int sum = 0;
  scanf("%d",&a);
   sum = func(a);
   printf("%d",sum);
  return 0;
}
int func(int x)
{
  if(x==1)
    return 1;
  else
   return func(x-1)+x;

}

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

#incude <stdio.h>
int Strlen(const char*str)
{
 if(*str == '\0')
 return 0;
 else
        return 1+Strlen(str+1);
}
int main()
{
 char *p = "abcdef";
 int len = Strlen(p);
 printf("%d\n", len);
 return 0;
}

8.1 递归与迭代

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

int func(int x)
{
  if(x==1)
    return 1;
  else
   return x*func(x-1);

}

 求第m个斐波那契数列(不考虑溢出)

int func(int x)
{
  if(x<=2)
    return 1;
  else
   return func(x-1)+func(x-2);

}

递归算法虽然简洁,但是发现了一个问题;

  •   在使用func()这个函数的时候,计算特别大的数时;特别耗时间。
  •   使用func()这个函数求10000这个数的阶乘(不考虑溢出),程序会挂掉。 

 原因在哪里?

  func函数在调用时做了很多的重复计算

  如果把代码修改一下?


 if(n == 3)
 count++;
 if (n <= 2)         
 return 1;
    else
    return fib(n - 1) + fib(n - 2);

最后输出count,是一个很大的数字

那我们如何改进呢?

  • 在调试func函数时,如果你的数字比较大,就会报错:: Stack overflow (堆栈溢出)这样的信息
  • 因为系统给栈分配的内存是有限的,如果出现了死循环或(死递归),就有可能导致一直开辟空间,最终产生了栈空间耗尽的情况,这样的情况我们称为栈溢出。

那么如何解决上面的问题?

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

 留个坑在这(你们自己写哈)

  8.2 尾递归

简单来讲,尾递归是指在一个方法内部,递归调用后直接return,没有任何多余的指令了。

比如,一个递归实现的累加函数。

int static func(int x)
{
  if(x==1)
    return 1;
  else
   return func(x-1)+func(x-2);

}

 

    请问这个是尾递归吗?答案是错误的!

可能有的人会说,最后一个步骤就是调用func,为什么不是尾递归?

实际上,你看到的最后一个步骤不代表从指令层面来讲的最后一步。这个方法的return先拿到acc(n-1)的值,然后再将n与其相加,所以求func(n-1)并不是最后一步,因为最后还有一个add操作。

如果我们把上面的代码做一个等价替换?

int static func(int x)
{
  if(x==1)
    return 1;
   int k = func(x-1);
   return x+k;

}

看,是不是还隐含一个add操作?

累加的尾递归写法是下面这样子的:

int static func(int x,int sum)
{
  if(x==1)
  {
      return x+sum;
  }
    
   return func(x-1,sum+x);

}

递归调用后就直接返回了,这是真正的尾递归。

递归调用的缺点是方法嵌套比较多,每个内部的递归都需要对应的生成一个独立的栈帧,然后将栈帧都入栈后再调用返回,这样相当于浪费了很多的栈空间.

9.函数作用域规则

      9.1 局部变量

    定义在函数内部的变量称为局部变量(Local Variable),它的作用域仅限于函数内部, 离开该函数后就是无效的,再使用就会报错。例如:

int main()
{
    int a,b;//a,b仅在main函数内有效
    scanf("%d%d",&a,&b);

}
int func(int x,int y)
{
     int m,n;//m,n仅在func函数内有效
return x+y;

}

 说明:

  1. 1.在main函数中定义的局部变量只能在main函数内进行使用,同时main函数并不能使用其他函数内定义的变量,同时main也是一个函数,与其他函数的地位平等。
  2. 2.形参变量  函数体内定义的变量都是局部变量,形参传递实参的过程等于给局部变量的赋值操作。
  3. 3.可以在不同的函数中使用相同的变量名,它们表示不同的数据,分配不同的内存,互不干扰,也不会发生混淆。
  4. 在语句块中也可定义变量,它的作用域只限于当前语句块,用完即销毁

      9.2  全局变量

在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的源文件,包括 .c 和 .h 文件。例如:

#include<stdio.h>
int func(int x,int y);
double m,k;//全局变量
int main()
{
    int d,c;
    scanf("%d%d",&d,&c);

}
int a,b;//全局变量
int func(int x,int y)
{
     int m,n;
return x+y;

}

   a,b,m,k.都是在函数外部定义的全局变量。C语言代码都是依次从前往后执行的,由于函数func定义在a,b前面所以在func函数内无效,而m,k、都定义在开头、所以在main(),func()两个函数内都是有效的。

全局变量的综合举例:

#include <stdio.h>

int n = 10;  //全局变量

void func1(){
    int n = 20;  //局部变量
    printf("func1 n: %d\n", n);
}

void func2(int n){
    printf("func2 n: %d\n", n);
}

void func3(){
    printf("func3 n: %d\n", n);
}

int main(){
    int n = 30;  //局部变量
    func1();
    func2(n);
    func3();
    //代码块由{}包围
    {
        int n = 40;  //局部变量
        printf("block n: %d\n", n);
    }
    printf("main n: %d\n", n);

    return 0;
}

运行结果:
func1 n: 20
func2 n: 30
func3 n: 10
block n: 40
main n: 30

代码中虽然定义了多个同名变量 n,但它们的作用域不同,在内存中的位置(地址)也不同,所以是相互独立的变量,互不影响,不会产生重复定义(Redefinition)错误。

  1. 1) 对于 func1(),输出结果为 20,显然使用的是函数内部的 n,而不是外部的 n;func2() 也是相同的情况。
  2. 当全局变量和局部变量同名时,在局部范围内全局变量被“屏蔽”,不再起作用。或者说,变量的使用遵循就近原则,如果在当前作用域中存在同名变量,就不会向更大的作用域中去寻找变量。
  3. func3() 输出 10,使用的是全局变量,因为在 func3() 函数中不存在局部变量 n,所以编译器只能到函数外部,也就是全局作用域中去寻找变量 n。由{ }包围的代码块也拥有独立的作用域,printf() 使用它自己内部的变量 n,输出 40。
  4. C语言规定,只能从小的作用域向大的作用域中去寻找变量,而不能反过来,使用更小的作用域中的变量。对于 main() 函数,即使代码块中的 n 离输出语句更近,但它仍然会使用 main() 函数开头定义的 n,所以输出结果是 30。
     

案例二   

 输入一行字符,统计出其中数字字符的个数。

#include<stdio.h>  
#include<ctype.h>  
#include<string.h>  
  
//定义全局变量  
int number = 0;  //数字  
   
//定义统计函数  
void len_txt(char s[])  
{  
    int c;  
    int i;  
   
    c = strlen(s);  //获取字符串的长度  
   
    //分别判断  
    for (i = 0;i < c;i++){  
        if (isdigit(s[i]))  
        {  
            number++;  
        }  
    }  
   
}  
   
//主函数  
int main()  
{  
   
    char s[100];  //定义字符串最大长度  
    gets(s);  //获取字符串  
    len_txt(s);  //调用函数  
    printf("%d ",number);  
   
    return 0;  
   
}

输入  Peking University is set up at 1898    输出   4

根据题意,我们希望借助一个函数返回数字字符的个数,为了方便输出结果。我们定义了一个全局变量number来计数,因为函数是void类型没有返回值。全局变量的作用域是整个程序,在函数len_txt中修改变量的值,,能够影响到包括 main() 在内的其它函数。

都看到这了,还不赶紧收藏起来!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/94669.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue3计算属性和侦听与script setup区别使用

一、计算属性computed 计算属性&#xff0c;只要依赖值不变&#xff0c;那么不会重新计算计算属性将基于它们的反应依赖关系缓存&#xff0c;提高性能对于任何包含响应式数据的复杂逻辑&#xff0c;应该使用计算属性 <template><!--重复使用--><p>{{ msg.s…

Redis——好友关注、共同关注、Feed流推送

1. 好友关注 在探店图文的详情页面中&#xff0c;可以关注发布笔记的作者&#xff1a; 进到探店笔记详情页&#xff0c;会发出两个请求&#xff0c;1是判断是否已经关注&#xff0c;2是尝试关注用户的请求。 关注是User之间的关系&#xff0c;是博主与粉丝的关系&#xff0c;…

你用过猿如意吗?猿如意可以使用ChatGPT哦,这里详细介绍了猿如意的功能,为什么我建议你使用猿如意,来看看吧

文章内容介绍 你是否还在为为每次安装IDE&#xff08;集成开发工具&#xff09;要去各种网站找教程而烦恼&#xff1f;你是否还在为各种文本格式转换而头痛&#xff1f;你是否在为斗图都不过兄弟们而卑微&#xff1f;你是否在为互联网中庞大冗杂却低效的教程文档而崩溃&#x…

SpringMVC:SpringMVC响应结果(7)

响应结果1. 环境准备2. 响应页面3. 响应文本数据4. 响应JSON数据&#xff08;掌握&#xff09;4.1 响应POJO对象4.2 响应POJO集合对象1. 环境准备 项目结构 pom.xml添加Spring依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"…

Mathtype / Word OMML / Latex 公式相互转换

文章目录Part.I 快应用Part.II 详细操作Chap.I Mathtype ↔ WordChap.II Mathtype ↔ LatexChap.III Latex ↔ WordPart.I 快应用 本文介绍了除了使用 Word 和 Mathtype 之外不使用其他任何辅助软件来实现三者相互转换的方法。 项目操作方法Mathtype 转成 Word OMMLWord菜单栏→…

信息化课堂怎么控屏教学的

现在的很多学校都在建设机房进行互动教学&#xff0c;相比于传统的教学方法&#xff0c;计算机的教学方式能够直观的表达每堂课的知识点&#xff0c;过程更为新颖&#xff0c;有利于吸引学生的注意力&#xff0c;提高在学习过程中的专注力。 但是&#xff0c;在提高增加课堂中学…

redo log 和binlog的相关问题及其衍生

目录 下在两阶段提交的不同时刻&#xff0c;MySQL异常重启会出现什么现象。 那么&#xff0c; MySQL怎么知道binlog是完整的? redo log 和 binlog是怎么关联起来的? 处于prepare阶段的redo log加上完整binlog&#xff0c;重启就能恢复&#xff0c;MySQL为什么要这么设计? …

ensp环境 AC+AP组网及ACweb界面配置

1.进入交换机划分Vlan并且配置好每条链路 The device is running! <Huawei>system-view [Huawei]sysname SW1 [SW1]vlan batch 100 101 [SW1]interface GigabitEthernet 0/0/2 [SW1-GigabitEthernet0/0/2]port link-type trunk [SW1-GigabitEthernet0/0/2]port trunk…

C++:类和对象:对象的初始化和清理

1 前言&#xff1a; 构造和析构的背景 1&#xff1a;C中的面向对象来源于生活&#xff0c;每个对象都会有初始值以及对象销毁前的清理数据设置 2&#xff1a;对象的初始化和清理是两个非常重要的安全问题&#xff0c;一个对象或者变量没有初始状态&#xff0c;对其使用后果是未…

左旋咪唑大单层/青蒿素长循环/酒石酸长春瑞滨热敏/棕榈酰五肽-4柔性/Anti-HER2免疫脂质体的研究

小编今天为大家分享了左旋咪唑大单层/青蒿素长循环/酒石酸长春瑞滨热敏/棕榈酰五肽-4柔性/Anti-HER2免疫脂质体的制备研究。 青蒿素长循环脂质体的制备&#xff1a; 青蒿素(artemisinin,ART)由于溶解度差,稳定性低,限制了其应用.因此,本研究采用长循环脂质体包裹青蒿素,增强其…

RDD中groupByKey和reduceByKey区别

groupByKey和reduceByKey区别 groupByKey 每个分区不聚合&#xff0c;等最终分组完成后调用Reduce再聚合 适用于求平均数、中位数等情况 reduceByKey 每个分区并行计算先实现分区内部聚合&#xff0c;然后再将每个分区的结果做最终的聚合实现分区间聚合 等同于MR中Combin…

电商之收单系统的webhook推送重试机制

文章目录1 问题背景2 前言3 解决方案3.1 核心思路3.2 数据库设计3.3 下一次发送webhook的时间算法3.3 详细设计4 延申思考1 问题背景 作为一个收单系统&#xff0c;当获取到一笔交易的支付结果时&#xff0c;就需要发送一个webhook消息给电商系统。电商系统收到webhook消息后&a…

4. Bean的生命周期

Bean的生命周期 1.生命周期相关概念介绍 生命周期&#xff1a;从创建到消亡的完整过程bean生命周期&#xff1a;bean从创建到销毁的整体过程bean生命周期控制&#xff1a;在bean创建后到销毁前做一些事情 2. Bean销毁时机 容器关闭前触发bean的销毁 关闭容器方式&#xff…

前端基础—Ajax和XML

Ajax和XML 说到这里&#xff0c;就不得不提到另一个概念&#xff1a;Ajax&#xff08;Asynchronous JavaScript&#xff09;&#xff0c;中文可以称之为“js的异步请求”&#xff0c;国内统一称为Ajax。 Ajax的概念是每次打开新的网页时&#xff0c;不要让页面整体刷新&#…

Java学习笔记 --- MySQL-常用数据类型

一、Mysql常用数据类型 二、数值型(整数)的基本使用 使用规范&#xff1a;在能够满足需求的情况下&#xff0c; 尽量选择占用空间小的 # 演示整形的使用 # 使用tinyint来演示范围 有符号 -128 ~ 127 如果没有符号 0-255 # 1. 如果没有指定 unsigned&#xff0c;则TINYINT就是…

卡塔尔世界杯门线技术(GOAL LINE TECHNOLOGY)背后的黑科技

现代职业足球运动员踢球时足球的行进速度&#xff0c;据国际足联统计数据&#xff0c;平均速度可达 60 英里/小时。极少数爆发力超强的职业球员&#xff0c;可以将这个速度刷新到超过 100 英里/小时。比如里斯本竞技队的巴西左后卫罗尼赫伯森在 2006 年以 131.82 英里/小时的速…

HACKTHEBOX——Sunday

nmap 第一次没有进行全端口扫描&#xff0c;只发现了79和111端口&#xff0c;79端口运行着finger程序&#xff0c;111则是rpcbind。 重新扫描一次&#xff0c;这次针对全部端口进行扫描。 nmap -p- -oA nmap 10.10.10.76 然后在扫描端口详细信息 可以发现22022端口运行着ssh…

数据结构——查找最全总结(期末复习必备)

目录 查找的基本概念 线性表的查找 顺序查找 折半查找&#xff08;二分或对分查找&#xff09; 分块查找&#xff08;索引顺序查找&#xff09; 树表的查找 二叉排序树 定义&#xff1a; 二叉排序树的查找&#xff1a; 二叉排序树的插入&#xff1a; 二叉排序树的创建&…

【缺陷识别】SVM金属表面缺陷分类与测量【含GUI Matlab源码 682期】

⛄一、简介&#xff08;附lunwen、答辩PPT&#xff09; 1 题目内容 金属板广泛应用在工业生产与生产生活的各方面。由于金属板制造过程涉及到的设备、工艺等多因素的影响&#xff0c;金属板表面容易出现种类较多、形态各异的缺陷&#xff0c;这些缺陷对金属板的耐磨性、抗腐蚀…

取整的四种方式

取整的四种方式一.基本认识二.四种取整方案1.零向取整2.地板取整3.向右取整4.四舍五入一.基本认识 这里按理说5/2应该为2.5啊&#xff0c;怎么为2呢&#xff1f;按照我们曾经的理解&#xff0c;其实知道符号/其实是取整。但它究竟是如何取整呢&#xff1f; 二.四种取整方案 1.…