关于程序员:函数探秘深入理解C语言函数实现高效模块化编程

7次阅读

共计 4710 个字符,预计需要花费 12 分钟才能阅读完成。

✨✨ 欢送大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C 语言学习
贝蒂的主页:Betty‘s blog

1. 函数的概念

在数学中咱们就晓得了函数这个概念,而 C 语言同样引入了函数这个概念,那 C 语言的函数到底是什么样的呢?

在 C 语言中,函数也叫子程序,它是一段能够重复使用的代码,用来独立地实现某个性能,它能够接管用户传递的数据,也能够不接管。

2. 函数的分类

C 语⾔的程序其实是由⽆数个⼩的函数组合⽽成的,也能够说:⼀个⼤的计算工作能够分解成若⼲个较⼩的函数(对应较⼩的工作)实现。同时⼀个函数如果能实现某项特定工作的话,这个函数也是能够复⽤的,晋升了开发软件的效率 。而 C 语言函数大抵能够分为两类: 库函数 自定义函数

2.1 库函数

C 语⾔规范中规定了 C 语⾔的各种语法规定,然而 C 语⾔并不提供库函数;C 语⾔的国际标准 ANSIC 规定了⼀些常⽤的函数的规范,被称为规范库,那不同的编译器⼚商依据 ANSI 提供的 C 语⾔规范就给出了⼀系列函数的实现。这些函数就被称为 库函数

比如说咱们后面学习的 printfscanf都是库函数,它的头文件时 <stdio.h>。然而咱们明天的主题不是解说库函数,如果大家想学习其余库函数,能够去 C /C++ 官网学习

2.2 自定义函数

自定义函数 是由程序员自主设计的函数,和库函数一样有 函数名、返回类型、形式参数 等,明天咱们的指标就是学习如何写自定义函数。

3. 自定义函数

3.1 语法

dataType functionName(形式参数)

{
//body
}

  • dataType 是 返回值类型,它能够是 C 语言中的任意数据类型,例如 int、float、char 等。
  • functionName 是 函数名 ,它是 标识符 的一种,命名规定和标识符雷同。函数名前面的括号 () 不能少。
  • body 是 函数体 ,它是函数须要执行的代码,是函数的主体局部。即便只有一个语句,函数体也要由{} 突围。
  • 如果有返回值,在函数体中应用 return 语句返回。return 进去的数据的类型要和 dataType 一样。

3.2 实例

通过函数的定义咱们能够写一个简略的加法函数:

#include<stdio.h>
int Add(int x, int y)
// 函数名 Add
// 两个形式参数 x,y,类型都是 int
// 返回类型也是 int
{
    int c = 0;
    c = x + y;return c;// 返回 x + y 的和
}
int main()
{
    int a, b;
    scanf("%d%d", &a, &b);
    int ret = Add(a, b);// 加法函数
    printf("两个数和为 %d\n", ret);
    return 0;
}

当然咱们也能够优化一下

int Add(int x, int y)
{return x + y;// 间接返回}
  • 通过函数咱们就能够节俭大量工夫,每次调用这个性能只需复用该函数即可。

3.3 作用域与生命周期

作用域 生命周期 是 C 语言中一个特地重要的概念,分明了解这个概念能帮忙咱们写出更好的程序,缩小 bug 的产生。

作⽤域(scope)是程序设计概念,通常来说,⼀段程序代码中所⽤到的名字并不总是无效(可⽤的,⽽限定这个名字的可⽤性的代码范畴就是这个名字的作⽤域。

  1. 局部变量的作⽤域是变量所在的部分范畴。
  2. 全局变量的作⽤域是整个⼯程(项⽬)。
  • 以下面加法函数以列,x,y 的作用域就是加法函数。当加法函数执行完结,它就会被销毁。

⽣命周期 指的是变量的创立 (申请内存) 到变量的销毁 (发出内存) 之间的⼀个时间段。

  1. 局部变量的⽣命周期是:进⼊作⽤域变量创立,⽣命周期开始,出作⽤域⽣命周期完结。
  2. 全局变量的⽣命周期是:整个程序的⽣命周期。
  • 以下面加法函数以列,x,y 的生命周期就是其进入函数创立到变量销毁的时间段。

4. 函数的参数

在函数使⽤的过程中,把函数的参数分为,理论参数 形式参数。上面我仍将以加法函数举例:

#include<stdio.h>
int Add(int x, int y)
// 函数名 Add
// 两个形式参数 x,y,类型都是 int
// 返回类型也是 int
{
    int c = 0;
    c = x + y;return c;// 返回 x + y 的和
}
int main()
{
    int a, b;
    scanf("%d%d", &a, &b);
    int ret = Add(a, b);// 加法函数
    printf("两个数和为 %d\n", ret);
    return 0;
}

4.1 理论参数

顾名思义,理论参数就是理论存在的参数,也就是主函数 main 调用函数时传给它的参数,也就是下面主函数中的 a,b。

4.2 形式参数

同理,形式参数其实就是模式上的参数,也就是下面加法函数 Add 中的 x,y。它并不是理论存在的,在未应用时并不会向内存申请空间,形式参数只有在函数被调⽤的过程中为了寄存实参传递过去的值,才向内存申请空间,这个过程就是模式的实例化。

4.3 实参加形参的关系

在介绍实参和形参关系时,咱们先看看上面这段代码

#include<stdio.h>
void swap(int x, int y)// 返回类型为 void 示意不返回值
{
    int temp = 0;// 定义一个长期变量
    temp = x;// 把 x 的值赋给 temp
    x = y;// 把 y 的值赋给 x
    y = temp;// 把 temp 的值赋给 y,实现替换操作
}
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    printf("替换前:a=%d,b=%d\n", a, b);
    swap(a, b);// 替换函数
    printf("替换后:a=%d,b=%d\n", a, b);
    return 0;
}

那么替换后的输入是多少呢~

为什么替换的值还是不变呢,咱们能够通过调试来察看一下

通过调试,咱们发现 a 与 x,b 与 y 只是在数值上雷同,他们地址是齐全不同的,他们在两个独立的内存空间互不影响,对 x,y 替换基本不会扭转 a,b 的值。并且函数在调用实现之后,函数所申请的内存空间会偿还零碎~

所以咱们总结实参加形参的关系是:形参只是实参的⼀份长期拷⻉,对形参扭转基本不会影响实参

4.4 数组做参数

咱们后面学过的数组天然是也能作为函数的参数来应用,上面是一个具体实例:

void print_arr(int arr[], int sz)// 打印数组
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {printf("%d", arr[i]);
    }
    printf("\n");
}
  • 其中 arr 是整个数组,sz 是数组元素的个数

5. 嵌套调用和链式拜访

5.1 函数的嵌套调用

咱们在后面曾经学习了抉择构造和循环构造的嵌套应用,那函数嵌套调用又是怎么的呢。其实,简略来说,就是函数中也能够调用函数

举个例子,让咱们看看上面这段代码

void print2()
{printf("hello world\n");
}
void print1()
{print2();// 调用 print2 函数
}
int main()
{print1();// 调用 print1 函数
         return 0;
}

  • C 语言函数反对嵌套调用,然而不反对嵌套定义。

5.2 函数的链式拜访

链式拜访简略来说就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数的链式拜访。

举例:

这是 strlen 的函数申明:size_t strlen(const char *str)

#include <stdio.h>
int main()
{printf("%d\n", strlen("abcdef"));
    // 依据 strlen 的返回值,咱们就能够间接将其作为 printf 的参数
    // 这就是一种简略的链式拜访
    return 0;
}

6. 函数的定义与申明

6.1 函数的定义

函数的定义就是指 函数具体的实现过程,交代函数具体性能的实现

比方咱们后面写的加法函数具体实现的过程就是函数的定义

int Add(int a, int b)
{return a + b;// 间接返回}

然而特地留神函数不能嵌套定义

int add(int x, int y)// 加法函数
{
    return x + y;
    void print()
    {....;// 嵌套定义 error}
}

6.2 函数的申明

不晓得大家留神到一个细节没有,就是贝蒂在写函数时候,会把函数放在主函数之前,那如果放在主函数之后会怎么样呢,让咱们试验一下吧

int main()
{
    int a, b;
    scanf("%d%d",&a,&b);
    int ret = Add(a, b);
    printf("%d", ret);
    return 0;
}
int Add(int x, int y)
{return x + y;}

一运行就会报这个正告,这个正告的意思就是说 Add 函数未定义,那为什么说 Add 未定义呢,原来是 程序从上往下进行的,在遇到 Add 函数之前,会先执行主函数中的 Add,此时程序基本不晓得 Add 的含意,所以会报正告。

那如何打消这个正告呢,其实我只须要加一个申明,让程序提前晓得有这个函数。

int Add(int x, int y);// 函数申明
int main()
{
    int a, b;
    scanf("%d%d",&a,&b);
    int ret = Add(a, b);
    printf("%d", ret);
    return 0;
}
int Add(int x, int y)
{return x + y;}
int Add(int , int);// 申明写出这样也是能够的
  • 函数的调⽤⼀定要满足,先申明后使⽤是,而函数的定义也是⼀种非凡的申明,所以如果函数定义放在调⽤之前也是能够的。

7. 函数的迭代与递归

7.1 函数的迭代

「迭代 iteration」是一种反复执行某个工作的控制结构。在迭代中,程序会在满足肯定的条件下反复执行某段代码,直到这个条件不再满足。咱们学习过的 while,for,do-while 循环构造就是一种迭代。

7.2 函数的递归

「递归 recursion」是一种算法策略,通过函数调用本身来解决问题。它次要蕴含两个阶段。

  1. 递:程序不断深入地调用本身,通常传入更小或更简化的参数,直到达到“终止条件”。
  2. 归:触发“终止条件”后,程序从最深层的递归函数开始逐层返回,汇聚每一层的后果。

递归的三个因素:

  1. 终止条件:用于决定什么时候由“递”转“归”。
  2. 递归调用:对应“递”,函数调用本身,通常输出更小或更简化的参数。
  3. 返回后果:对应“归”,将以后递归层级的后果返回至上一层。

7.3 递归问题

(1) 问题一

利用递归计算 1 +2+3+4+….+n

int recur(int n)
{
    // 终止条件
    if (n == 1)
    {return 1;}
    // 递:递归调用
    return n + recur(n - 1);
}

(2) 问题二

输⼊⼀个整数 m,打印这个依照程序打印整数的每⼀位。
⽐如:
输⼊:1234 输入:1 2 3 4
输⼊:520 输入:5 2 0

#include <stdio.h>
void print(int n)
{if (n > 9)
    {print(n / 10);
    }
    printf("%d", n % 10);
}
int main()
{
    int n = 0;
    scanf("%d", &n);
    print(n);
    return 0;
}

代码剖析:

void print(int n)// 输出 1234
{if (n > 9)//1234 大于 9
    {print(n / 10);// 将 123 传入下个递归
    }
    printf("%d", n % 10);// 归的第四步,输入 4
}
void print(int n)// 传入 123
{if (n > 9)//123 大于 9
    {print(n / 10);// 将 12 传入下个递归
    }
    printf("%d", n % 10);// 归的第二步,输入 3
}
void print(int n)// 传入 12
{if (n > 9)//12 大于 9
    {print(n / 10);// 将 1 传入下个递归
    }
    printf("%d", n % 10);// 归的第一步,输入 2
}
void print(int n)// 传入 1
{if (n > 9)// 1 不大于 9
    {print(n / 10);
    }
    printf("%d", n % 10);// 输入 1,开始‘归’}
正文完
 0