C语言程序与设计——函数(一)

news2024/12/28 21:34:45

函数式编程

在编程范式中有很多分类,面向对象式,命令式编程,声明式编程等,而函数式编程也是一种重要的编程范式。

函数式编程(Functional Programming),FP是一种将计算视为函数求值过程的编程范式,并强调使用纯函数、不可变数据和函数组合来构建软件系统。函数式编程强调将程序分解成若干独立的函数,并通过函数之间的组合和组合操作来解决问题。
特点:

  1. 纯函数:函数式编程强调使用纯函数,即没有副作用、只依赖于输入参数并返回结果的函数。
  2. 不可变数据:函数式编程鼓励使用不可变数据,避免修改已有数据,而是通过创建新的数据来实>现状态的改变。
  3. 函数组合:函数式编程支持函数的组合,可以将多个函数组合成一个更复杂的函数,提高代码的复用性和可读性。
  4. 延迟计算:函数式编程中的操作通常是延迟计算的,只有在需要结果时才会进行计算,这提供了>更高的灵活性和效率。

为什么使用函数式编程

请看以下两端功能相同的代码:

程序要求:
1.写一个程序,输入一个n(n>=0),判断n是否为素数
2.若为素数则输出一2 * n, 若不是素数则输出n / 2

#include<stdio.h>

int isprime(int n){
    for(int i = 2; i <= (n >> 1); i++){
        if(!(n % i)) return 0;
    }
    return 1;
}

int func(int n){
    if (isprime(n)){
        return n * 2;
    }
    else return n / 2;
}

int main(){
    int n;
    while(~scanf("%d", &n)){
        if(isprime(n)){
            printf("%d is prime!\n", n);
        }
        else{
            printf("%d is not prime\n", n);
        }
        printf("And output a %d\n", func(n));
    }

}

/*************************************************************************
        > File Name:1-example-about-why-use-function.c
        > Author:
        > Mail:
        > Created Time: Sun 03 Mar 2024 01:16:24 PM CST
    	> content: input a number "n"(n > 0), and output the prime of the range from 1 to the number.
 ************************************************************************/

#include<stdio.h>
int main(){
    int n, flag;
    while(~scanf("%d", &n)){
        flag = 1;
        for(int i = 2 ; i < (n >> 1); i++){
            if(!(n % i)) flag = 0;
        }

        if (flag){
            printf("%d is prime!\n", n);
            printf("And output a %d\n", n * 2);

        }
        else{
            printf("%d is not prime\n", n);
            printf("And output a %d\n", n / 2);
        }
    }


从两段代码不难看出过程式的编程更加繁重,且可扩展性很差,若是在添加一个新功能,函数式编程可以通过再加一个函数来实现,主函数的总体框架不会变,但是若是以第二种方式的话,很有可能需要产生大的变动,而且从阅读上来讲,函数式的编程所呈现的更加直观,不需要关心怎么实现的上一步,若想要知道具体的实现方法,也只需查看相关的功能函数即可,对于debug来讲也就更方便。

函数说明

# 函数的结构
int isprime(int n){   #int 为返回值  #isprime为函数名  #int n 参数声明列表(其中int 是限制传输参数的限制类型)
    for(int i = 2; i <= (n >> 1); i++){
        if(!(n % i)) return 0;
    }
    return 1;
}

函数分为两个部分:1. 函数声明 2. 函数定义
上面展示的就是函数定义的部分,函数的声明和变量声明相同,在使用之前需要先声明,告诉编译器变量的存在,因为C语言是顺序执行的,若是函数在使用之前,就先定义了,那么编译器就已经知道了该函数的存在,所以就不需要声明了,反之则需要声明。

# 需要声明的情况
#include<stdio.h>
int funb();
int funa(int x){
	return funb(x);
}
int funb(int x){
	return x;
}
K&R风格定义
int isprime(x)
int x;
{
    for (int i = 2; i * i <= x; i++){
        if(!(x % i)) return 0;
    }
    return 1;
}

不建议使用这种方式,这是一种已经过时的函数定义风格,但是现在的编译器依然可以编译通过,但是只做了解,极其不建议使用。

递归

在使用函数时,个人认为最重要的就是理解递归这种编程方法的使用,理解递归思维。

递归实际上就是程序调用自身的编程技巧
递归程序的组成:

  1. 边界条件处理
  2. 针对于问题的处理过程递归过程
  3. 结果返回

以实际问题为例,写一个程序实现输入一个大于0的数字n,输出n(n < 30)的阶乘

/*************************************************************************
        > File Name: 3-factorial.c
        > Author:
        > Mail:
        > Created Time: Sun 03 Mar 2024 02:28:28 PM CST
 ************************************************************************/

#include<stdio.h>

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

int main(){
    int n;
    while(~scanf("%d", &n)){
        printf("%d! = %d\n", n, factorial(n));
    }
}

实现思路:
0!和1!等于1,这个条件可以作为出口,除此之外可知计算阶乘 n ! = n ∗ ( n − 1 ) ! n! =n * (n-1)! n!=n(n1)!,这个就是函数的处理过程。

递归:1. 向上递推 2. 向下回归(回溯)
f a c ( 5 ) = 5 ∗ f a c ( 4 ) = 5 ∗ ( 4 ∗ f a c ( 3 ) ) = 5 ∗ ( 4 ∗ ( 3 ∗ f a c ( 2 ) ) ) = 5 ∗ ( 4 ∗ ( 3 ∗ ( 2 ∗ f a c ( 1 ) ) ) ) = 5 ∗ ( 4 ∗ ( 3 ∗ ( 2 ∗ ( 1 ) ) ) ) fac(5) = 5 * fac(4) = 5 * (4 * fac(3)) = 5 * (4 * (3 * fac(2))) = 5 * (4 *( 3 *( 2 *fac(1)))) = 5 * (4 *( 3 *( 2 *(1)))) fac(5)=5fac(4)=5(4fac(3))=5(4(3fac(2)))=5(4(3(2fac(1))))=5(4(3(2(1))))
运算过程由内括号向外运算,即是递归的运算过程,就像是小时候传纸条一样,依次传到你的目标人物,停止传递,而后纸条传回来的一个过程。

如何判断递归过程是正确的:数学归纳法
f ( 1 ) = 1 f ( 2 ) = f ( 1 ) ∗ 2 = 1 ∗ 2 f ( 3 ) = f ( 2 ) ∗ 3 = f ( 1 ) ∗ 2 ∗ 3 = 1 ∗ 2 ∗ 3 . . . . . . f ( k ) = f ( k − 1 ) ∗ k = . . . = 1 ∗ 2 ∗ . . . ∗ ( k − 1 ) ∗ k f(1) = 1\\f(2) = f(1) * 2 = 1 * 2\\f(3 ) =f(2) * 3 = f(1) * 2 * 3 = 1 * 2 * 3\\...\\...\\f(k) =f(k-1) * k = ...= 1 * 2 * ...*(k - 1)* k f(1)=1f(2)=f(1)2=12f(3)=f(2)3=f(1)23=123......f(k)=f(k1)k=...=12...(k1)k

系统栈

在递归过程中,函数进行自我调用时,需要借助的系统栈。根据以上的演示可知,递归实际上是层层调用自己的过程,而对于函数的执行顺序,却是反过来的,求解f(5)的过程是 f ( 5 ) − > f ( 4 ) − > f ( 3 ) − > f ( 2 ) − > f ( 1 ) f(5)->f(4)->f(3)->f(2)->f(1) f(5)>f(4)>f(3)>f(2)>f(1)只有当运行到f(1)的时候,触发了出口条件,得到f(1)的值,返回到f(2)进而依次到f(5)得出结果。也就是说最后调用的是f(1),最先运算的也是f(1)也就是后进先出,在这个递推过程中,未进行运算的函数则存放在栈中,等待进行运算。在windows系统中系统栈的大小为2MB,Linux系统中为8MB(我所进行代码演示的系统)。而每层压入栈中的数据大小为一个函数指针8bytes(64bit 操作系统) + 一个整型变量4bytes = 12bytes(以演示的阶乘程序为例)。随着变量的的增多压入栈中的大小也会变化,所以当压入栈中的数据大小超过栈大小时,就会爆栈(栈溢出)
在这里插入图片描述

函数指针

函数指针实际上就是一个地址,及变量,所以可以当作参数传入函数,即可以把函数当作函数。

/*************************************************************************
        > File Name: 4-fun-address.c
        > Author:
        > Mail:
        > Created Time: Sun 03 Mar 2024 08:16:28 PM CST
 ************************************************************************/

#include<stdio.h>

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

int substract(int a, int b){
    return a - b;
}

int calculate(int (*f1)(int, int), int (*f2)(int, int), int a, int b){
    if(a > b){
        return f1(a, b);
    }
    else if(a < b){
        return f2(a, b);
    }
    return a;
}
int main(){
    int a, b;
    while(~scanf("%d%d", &a, &b)){
        printf("the result :%d\n", calculate(substract,add,a, b));
    }
}



在这里插入图片描述
上面我们说过,函数运行呢是把函数指针以及参数压入到系统栈中,运行时弹栈,也就说,函数的运行实际上是通过指针找到所在地址来运行,指针变量也是变量,所以我们同样可以作为参数传入到函数当中。我们通过函数传参的方式可以则可以轻易的实现出分段函数。

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

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

相关文章

volatile关键字的作用 以及 单例模式(饿汉模式与懒汉模式的区别及改进)

文章目录 &#x1f4a1;volatile保证内存可见性&#x1f4a1;单例模式&#x1f4a1;饿汉模式&#x1f4a1;懒汉模式&#x1f4a1;懒汉模式多线程版&#x1f4a1;volatile防止指令重排序 &#x1f4a1;volatile保证内存可见性 Volatile 修饰的变量能够保证“内存可见性”以及防…

【LeetCode】升级打怪之路 Day 11:栈的应用、单调栈

今日题目&#xff1a; Problem 1: 栈的应用 155. 最小栈 | LeetCode20. 有效的括号 | LeetCode150. 逆波兰表达式求值 | LeetCode Problem 2: 单调栈 496. 下一个更大元素 I739. 每日温度503. 下一个更大元素 II 目录 Problem 1&#xff1a;栈 - “先进后出”的应用LC 155. 最…

【Tomcat】The CATALINA_HOME environment variable is not defined correctly

文章目录 一、问题二、解决办法三、优化 一、问题 运行绿色版Tomcat时&#xff0c;单击apache-tomcat-9.0.27\bin\startup.bat时窗口一闪而过。 检查JAVA_HOME环境变量&#xff0c;可以发现并没有问题。 为了检查错误&#xff0c;将startup.bat程序使用文本编辑器打开&#x…

Vue2+ElementUI列表、表格组件的封装

Vue2ElementUI列表组件的封装&#xff1a;引言 在日常开发中&#xff0c;我们经常会遇到需要展示列表数据的场景。ElementUI 提供的 el-table 组件是一个功能强大的表格组件&#xff0c;可以满足大部分的需求。但是&#xff0c;在实际应用中&#xff0c;我们往往需要根据业务需…

Python | Conda安装包报错:PackagesNotFoundError

Conda在下载安装包时报错&#xff1a; PackagesNotFoundError: The following packages are not available from current channels:- XXXXXX&#xff08;包名&#xff09;有如下两种解决方法&#xff1a; 方法一&#xff1a;将conda-forge添加到搜索路径上 在命令行运行下方指令…

spring cloud 之 Netflix Eureka

1、Eureka 简介 Eureka是Spring Cloud Netflix 微服务套件中的一个服务发现组件&#xff0c;本质上是一个基于REST的服务&#xff0c;主要用于AWS云来定位服务以实现中间层服务的负载均衡和故障转移,它的设计理念就是“注册中心”。 你可以认为它是一个存储服务地址信息的大本…

14-RPC-自研微服务框架

RPC RPC 框架是分布式领域核心组件&#xff0c;也是微服务的基础。 RPC &#xff08;Remote Procedure Call&#xff09;全称是远程过程调用&#xff0c;相对于本地方法调用&#xff0c;在同一内存空间可以直接通过方法栈实现调用&#xff0c;远程调用则跨了不同的服务终端&a…

游戏框架搭建

使用框架的目标&#xff1a;低耦合&#xff0c;高内聚&#xff0c;表现和数据分离 耦合&#xff1a;对象&#xff0c;类的双向引用&#xff0c;循环引用 内聚&#xff1a;相同类型的代码放在一起 表现和数据分离&#xff1a;需要共享的数据放在Model里 对象之间的交互一般有三…

XUbuntu22.04之显示实时网速(二百一十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

【字符串】马拉车(Manacher)算法

本篇文章参考&#xff1a;比较易懂的 Manacher&#xff08;马拉车&#xff09;算法配图详解 马拉车算法可以求出一个字符串中的最长回文子串&#xff0c;时间复杂度 O ( n ) O(n) O(n) 因为字符串长度的奇偶性&#xff0c;回文子串的中心可能是一个字符&#xff0c;也可能是…

智慧草莓基地:Java与SpringBoot的技术革新

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

ue4.27 发现 getRandomReachedLocation 返回 false

把这个玩意儿删掉&#xff0c;重启工程&#xff0c;即可 如果还不行 保证运动物体在 volum 内部&#xff0c;也就是绿色范围内确保 project setting 里面的 navigation system 中 auto create navigation data 是打开的(看到过博客说关掉&#xff0c;不知道为啥) 如果还不行&…

STM32学习和实践笔记(1): 装好了的keil μVision 5

2019年3月在淘宝上买了这块STM32的开发板&#xff0c;学了一段时间后就丢下了&#xff0c;今天重新捡起来&#xff0c;决定好好学习、天天向上。 对照教程&#xff0c;今天先把keil5装上了。 装的过程有以下几点值得记录下&#xff1a; 1&#xff09;用注册机时&#xff0c;…

【数据结构】B树

1 B树介绍 B树&#xff08;英语&#xff1a;B-tree&#xff09;&#xff0c;是一种在计算机科学自平衡的树&#xff0c;能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作&#xff0c;都在对数时间内完成。B树&#xff0c;概括来说是一个一般化的…

波斯猫 6页面 宠物动物 长毛猫 HTML5 带背景音乐 JS图片轮播特效 滚动文字 鼠标经过图片 JS时间代码

波斯猫 6页面 宠物动物 长毛猫 HTML5 带背景音乐 JS图片轮播特效 滚动文字 鼠标经过图片 JS时间代码 注册表单 宠物网页成品 海量学生网页成品 个人博客 人物明星 城市家乡 旅游景点 美食特产 购物电商 公司企业 学校大学 科普教育 宠物动物 鲜花花卉 植物水果 茶叶咖啡 健康生…

【前端寻宝之路】学习如何使用HTML实现简历展示和填写

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-iJ3Ou0qMGFVaqVQq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

Google Dremel和parquet的复杂嵌套数据结构表征方法解析

转载请注明出处。作者&#xff1a;archimekai 核心参考文献&#xff1a; Dremel: Interactive Analysis of Web-Scale Datasets 文章目录 引言复杂嵌套数据结构的无损表征问题Dremel论文中提出的表征方法parquet备注 引言 Dremel是Google的交互式分析系统。Google大量采用prot…

LabVIEW石油钻机提升系统数字孪生技术

LabVIEW石油钻机提升系统数字孪生技术 随着数字化、信息化、智能化的发展&#xff0c;石油钻采过程中的石油钻机数字化技术提升成为了提高钻井效率、降低生产成本的重要途径。基于中石油云平台提供的数据&#xff0c;采用数字孪生技术&#xff0c;对石油钻机提升系统进行数字化…

设计模式(十三)抽象工厂模式

请直接看原文:设计模式&#xff08;十三&#xff09;抽象工厂模式_抽象工厂模式告诉我们,要针对接口而不是实现进行设计。( )-CSDN博客 -------------------------------------------------------------------------------------------------------------------------------- …

Some collections -- 2024.3

一、TensorFlow Android (dataset: Mnist) We used TensorFlow to define and train our machine learning model, which can recognize handwritten numbers, called a number classifier model in machine learning terminology. We transform the trained TensorFlow mod…