从零开始学嵌入式技术之C语言10:函数

一:函数基本概念

(1)什么是函数

        函数是一种可重复使用的代码块,用于执行特定的任务或操作。

        函数允许我们将代码逻辑组织成独立的单元,从而提高了代码的可读性、可维护性和重用性。

        一个C程序可以由一个或多个源文件构成(C文件扩展名是“.c”),一个源文件是一个编译单位。一个源文件可以由若干个函数构成,函数之间可以相互调用。也就是说,函数是C程序基本的组成单位。

(2)函数的作用

  1. 封装功能,将一个完整的功能封装成函数,提高代码的结构化和复用性。
  2. 代码模块化,将程序按照功能拆分成若干模块单元,有助于降低复杂度。
  3. 增强可维护性,如果需要修改某项功能,只需要调整对应的函数代码。
  4. 隔离细节,通过函数调用可以隐藏实现细节,只关心输入输出。

(3)函数的分类

        C语言中,从使用的角度,函数可以分类两类。

  1. 库函数,也称为标准函数,是由C系统提供的,用户不必自己定义,可直接使用它们,使用库函数,必须包含 #include 对应的头文件。
  2. 自定义函数,解决具体需求而自己定义的函数,需先定义再使用。

 二:函数基本语法

(1)声明函数语法

返回类型 函数名(参数列表)
{
    函数体语句1;
    函数体语句2;
    … 函数体语句n;
    return 返回值;
}
  1. 函数名:函数被调用时使用的名字,函数名要符合标识符规范。
  2. 函数体:函数中所包含的代码块,用于实现函数的具体功能和操作。
  3. 参数:用于接收调用函数时传递进来的值。
  4. 返回值:函数执行完毕后,从函数传回到调用点的值,返回值的类型要与函数名前面的返回类型对应,如果没有返回值,返回类型可以写 void。
#include <stdio.h>
// 声明函数
void func(){
    printf("hello func\n");
}
// 实现两个数字相减
int minus(int m, int n){
    return m - n;
}
// 取两个数字中的最大值
int max(int a, int b){
    int c;
    c = a > b ? a : b;
    return c;
}
// 主函数
int main(){
    return 0;
}

         C程序中的所有函数都是互相独立的,一个函数并不从属于另一个函数,即函数不能嵌套声明。有些编译器的扩展允许函数嵌套声明,但这不是C标准的一部分,代码的可移植性可能会受到影响,强烈不建议。

 (2)调用函数

         函数名后面加上圆括号即是函数的调用,参数写在小括号中,函数每调用一次,函数体语句都会执行一遍。

#include <stdio.h>

// 声明函数
void func()
{
    printf("hello func\n");
}

// 实现两个数字相减
int minus(int m, int n){
    return m - n;
}
// 取两个数字中的最大值
int max(int a, int b){
    int c;
    c = a > b ? a : b;
    return c;
}
// 主函数
int main(){
    // 函数的调用
    func();
    func();
    printf("17-90的结果:%d\n", minus(17, 90));
    printf("21-180的结果:%d\n", minus(21, 180));
    printf("12和16之间较大的是:%d\n", max(12, 16));
    printf("45和31之间较大的是:%d\n", max(45, 31));
    return 0;
}

 (3)函数的返回值

        函数调用后数能得到一个确定的值,这就是函数的返回值,返回值常常是一个计算的结果,或是用来作为判断函数执行状态的标记。

        函数返回值分为以下三种情况:

  1. 无返回值类型:针对函数无返回值或明确不需返回值的情况,使用void(即空类型)表示。
  2. 有返回值类型:指明具体的类型,比如,int、float、char等。需要在函数体内与return语句搭配使用。
  3. 如果返回值类型不是void,但函数中没有return语句,则函数会返回一个不确定的值。
#include <stdio.h>
// 函数返回值:return
void fun(){
    // 判断1-10,哪些偶数
    for (int i = 1; i <= 10; i++)
    {
        if (i % 2 == 0)
        {
            printf("%d是偶数\n", i);
        }
    }
}
double fun1(){
    return 3.14 * 4 * 4;
}
// 返回值的类型与函数名前面类型不统一,有可能造成精确度缺失
int fun2(){
    return 3.14 * 4 * 4;
}
// 下面这种写法容易导致编译给你返回一个随机数值!!!
// 下面这种写法不建议这么搞!!!
int fun3()
{
    int a = 100;
    int b = 200;
    int c = a + b;
}
int main(){
    fun();
    double result = fun1();
    printf("%lf\n", result);
    int result1 = fun2();
    printf("%d\n", result1);
    int result2 = fun3();
    printf("%d\n", result2);
    return 0;
}

(4)函数的参数

        函数的参数分为形参与实参:

        形参:在定义函数时,函数名后面括号()中声明的变量称为形式参数,简称形参。

        实参: 在调用函数时,函数名后面括号()中的使用的常量、变量、表达式称为实际参数,简称实参。

#include <stdio.h>
// x 和 y 即为形参
int func(int x, int y){
    return x + y;
}
int main(){
    // 3 和 5 即为实参
    int sum = func(3, 5);
    printf("%d \n", sum); // 8;
    // 如果实参数量如形参不一致 会报错
    // func(100, 299, 300);
    // func(100);
    return 0;
}

 三:主函数

        主函数是程序的入口函数,即所有的程序一定要包含一个主函数,程序总是从这个函数开始执行,如果没有该函数,程序就无法启动。

        主函数中可以调用其它函数,但其它函数不能反过来调用主函数,主函数也不能调用自己。

         C语言约定,主函数返回值0表示运行成功,如果返回其它非零整数,就表示运行失败。默认情况下,如果主函数里面省略return 0 这一行,编译器会自动加上,即 main() 的默认返回值为0。但是为了保持统一的代码风格,不建议省略。

         主函数的声明中可以带有两个参数,格式如下:

int main(int argc, char *argv[])
{
    // 函数体
}

        其中,形参argc,全称是argument count,表示传给程序的参数个数,其值至少是1;而argv,全称是argument value,argv[]是一个指针数组(详见下一章),我们可以暂时将 argv 理解为是一个数组,数组的每个元素都是字符串。

        这种方式可以通过命令行的方式执行源代码接收指定的字符串传给参数argv。

        我们创建一个名为 demo.c 的源文件,代码如下:

#include <stdio.h>
int main(int argc, char *argv[]){
    printf("argc = %d\n", argc);
    // 函数体
    for (int i = 0; i < argc; i++)
    {
        printf("%s\n", argv[i]);
    }
    return 0;
}

         在 demo.c 所在的目录下打开命令行工具,使用 gcc 命令进行编译

 四:函数原型    

        默认情况下,函数必须先声明,后使用。由于程序总是先运行main() 函数,导致所有其它函数都必须在main() 函数之前声明。

        如果想将函数声明写在后面,可以在程序开头处给出函数原型。函数原型,就是提前告诉编译器,每个函数的返回类型和参数类型。其它信息都不需要,也不用包括函数体,具体的函数实现可以后面再补上。

#include <stdio.h>
// 函数原型:提前把函数声明,'相当于和编译器提前沟通好了'
// 函数原型花括号,函数体部分不用书写
int fun(int, int);
void fun1();
int main(){
    int result = fun(10, 20);
    printf("计算的结果:%d\n", result);
    fun1();
    return 0;
}
// 底部定义函数
int fun(int a, int b){
    return a + b;
}
void fun1(){
    printf("床前明月光!\n");
    printf("打的一拳开,免得百拳来\n");
}

 五:作用

        作用域用于确定在代码中某个标识符(如变量、标识符常量、数组等)的可见性和访问范围,它决定了在程序的哪些部分可以引用或访问该标识符。

        作用域可以分为全局作用域、局部作用域、块级作用域。

        同一个作用域中不能声明同名的标识符。

(1)全局作用域

         在函数和代码块(分支语句、循环语句等)以外定义的变量、标识符常量、数组等具有全局作用域,在程序的任何地方都可以被访问,通常称它们为全局变量、全局常量、全局数组等。

#include <stdio.h>
// 全局作用于:函数体、代码块(条件语句+循环语句)以外,全局作用域
// 全局作用域的数据可以在源文件的任意地方使用
// 全局变量:只是声明  但是没有初始化赋值--->0
// 对于全局数组,如果没有显式初始化它们,它们的元素将自动初始化为零值,字符数组,将初始化为空字符 \0。
int num; // 默认是数字零
// 全局变量
int a = 100;
double b = 6.6;
char ch = 'z';
// 全局常量
const double PI = 3.14;
// 全局数组
int arr[5] = {10, 20, 30, 40, 50};
// 函数体内部使用全局数据
void fun(){
    printf("fun函数体内部:%d", a);
    printf("fun函数体内部:%lf", PI);
    for (int i = 0; i < 5; i++)
    {
        printf("fun函数体内部:%d\n", arr[i]);
    }
    printf("fun函数体内部使用全局变量num=%d\n", num);
}
int main(){
    printf("全局变量:%d\n", a);
    printf("全局常量:%lf", PI);
    // 遍历全局数组
    for (int i = 0; i < 5; i++)
    {
        printf("数组:%d\n", arr[i]);
    }
    fun();
    printf("全局变量num=:%d", num);
    return 0;
}

        对于全局变量,如果没有显式指定初始值,它们将自动初始化为零值。

        对于全局数组,如果没有显式初始化它们,它们的元素将自动初始化为零值,字符数组,将初始化为空字符 \0。

 (2)​​​​​​​局部作用域

        在函数内定义的变量、标识符常量、数组等具有局部作用域,只有在该函数内部才能被访问,通常称它们为局部变量、局部常量、局部数组等。需要注意的是,函数的形参也是局部变量。

#include <stdio.h>
// 全局变量erha
int erha = 100;
// 函数的形参:a、b局部变量,只能在局部作用域中使用
int max(int a, int b)
{
    int result = a + b;
    printf("计算的结果:%d\n", result);
}
int main()
{
    // 局部作用域:创建变量、常量、数组、局部变量、局部常量、局部数组
    // 只能在当前局部作用域中使用
    // 函数的形参,也是局部局部变量
    int num = 12306;
    int arr[5] = {1, 2, 3, 4, 5};
    printf("主函数局部作用于:num=%d\n", num);
    max(10, 20);
    for (int i = 0; i < 5; i++)
    {
        printf("局部数组:%d\n", arr[i]);
    }
    return 0;
}

        如果局部作用域中定义了与全局作用域中同名的标识符,优先使用本作用域中定义的数据。

#include <stdio.h>
// 局部作用域与全局作用域当中,如果出现标识符重名
// 就近原则:先找所在自身作用域中是否存在,不存在找全局!!!
//  全局变量
int a = 699;
void fun()
{
    // 局部变量
    int a = 100;
    printf("a=%d\n", a);
}

void fun1(int arguments)
{
    // 局部变量
    int num;
    printf("111111111111111111111局部变量num=%d\n", num);
}

int main()
{
    fun();
    fun1(10);
    // 定义变量、常量、数组->局部数据
    // 局部数据,仅仅初始化,但是没有赋值,系统安排垃圾数值[随机的]!
    // 局部变量既然声明了,就必须要进行初始化!!!

    int sum;
    printf("系统安排上:sum=%d\n", sum);
    int arr[10];
    for (int i = 0; i < 10; i++)
    {
        // printf("%d\n", arr[i]);
    }

    return 0;
}

        与全局变量和全局数组不同,局部变量和局部数组如果没有显式初始化,它们的初始值是机器垃圾值,即系统之前分配给这块空间的值。所以不对局部变量、局部数组初始化是不安全且强烈不建议的。

(3)块级作用域

        块级作用域是C99标准引入的概念,在代码块(分支语句、循环语句等)中定义的变量、标识符常量、数组等具有块级作用域,只有在该代码块内部才能被访问,代码块通常具有花括号 {} 结构。

        这些被称为块级变量、块级常量、块级数组等,同时也可以被称为局部变量、局部常量、局部数组,且与函数中的局部变量、局部常量、局部数组具有相同的特性。

#include <stdio.h>
void fun(){
    {
        int a = 100;
        printf("娃哈哈!%d\n", a);
    }
    printf("娃哈哈!%d\n", a);//未定义不能执行
}

int main(){
    int i;
    // 块级作用域:条件语句 + 循环语句花括号----块级作用域
    if (1)
    {
        int a = 100;
        int b = 200;
        int result = a + b;
        printf("%d\n", result);
    }

    for (i = 0; i < 10; i++)
    {
        printf("%d\n", i);
    }
    printf("%d\n", i);
    fun();

    return 0;
}

 (4)​​​​​​​作用域和内存

 栈区域

        局部变量、局部数组等通常存储在栈(Stack)区,这些局部数据的内存分配和释放是自动管理的,它们的生命周期受限于其定义的函数或块级作用域,当函数返回或块级作用域结束时,这些变量的内存会被自动释放。

        函数每调用一次就创建一次局部数据,调用结束就销毁;下次调用再创建新的局部数据。

 全局静态区域

        全局变量、全局数组等存储在全局静态区,这些全局的数据在程序的整个生命周期内都存在,它们在程序启动时被分配,在程序结束时才被释放。

#include <stdio.h>
double kill = 3.14;
// 局部数据:存储在栈区【用完就销毁】
// 全局数据:存储全局静态区[知道程序全部执行完毕了,数据才会销毁]
// int a:局部变量
void fn(int a)
{
    int num = 100;
    kill += 1;
    printf("局部数据:%d\n", a + num);
    printf("全局数据:%lf\n", kill);
}

void fn1()
{
    kill += 100;
    printf("全局数据:%lf\n", kill);
}

int main()
{
    fn(10);
    fn(20);
    fn1();

    return 0;
}

思考1:下面的代码输出什么内容?

#include <stdio.h>
double price = 200.0;
void test01()
{
    printf("%.2f \n", price);
}
void test02()
{
    price = 250.0;
    printf("%.2f \n", price);
}
// main函数
int main()
{
    printf("main price=%.2f \n", price);
    test01();
    test02();
    test01();

    return 0;
}

 思考2:下面的代码输出什么内容?

#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;
}

六:static和 extern关键字

        static关键字可以声明静态变量和静态函数,以控制变量和函数的作用范围和生存周期。

(1)静态局部变量

使用static关键字修饰的局部变量,称为静态局部变量,静态局部变量与全局变量一样存储在内存中的全局静态区。静态局部变量具有如下特点:

  1. 静态局部变量只在函数第一次调用时初始化一次并将生命周期延长到整个程序的执行期间。
  2. 静态局部变量如果声明时没有初始赋值,系统会自动初始化为零,同全局变量的初始化规则一致。
#include <stdio.h>
// 静态局部变量
// 局部变量
void fn(int a)
{
    int num = 444;
    printf("fn输出结果:%d\n", a + num);
    int b;
    printf("fn局部变量:%d\n", b);
}
void fn1()
{
    // 静态的局部变量
    static int a = 100;
    a++;
    printf("fn1输出结果:%d\n", a);
    static int c;//只是声明但是为赋值,全局变量类似填充0
    printf("static C:%d\n",c);
}
int main()
{
    fn(100);
    fn(300);
    fn1();
    fn1();
    fn1();
    return 0;
}

 (2)​​​​​​​多文件编译和静态全局变量

        C 编译器可以将多个源文件编译成一个可执行文件。创建两个源文件,分别命名为file01.c和 file02.c,源文件代码如下:

file01.c

#include <stdio.h>
int num01 = 100;
const double PI01 = 3.14;
char msg01[] = "Hello msg01";
void fn01(){
    printf("function fn01 \n");
}

 file02c

#include <stdio.h>
int main(){
    printf("Hello file02");
    return 0;
}

         VS Code 默认是无法同时编译多个源文件的,我们使用命令行终端进行编译,打开VS Code 内置终端,运行如下命令:

gcc file01.c file02.c -o main.exe

运行之后,会生产 main.exe 可执行文件,再次通过命令行运行 mian.exe 即可。

./main.exe

 运行完会显示“Hello file02”。

 (3)​​​​​​​extern 关键字声明外部链接

         下面我们演示如何在 file02.c 中使用file01.c 中定义的全局变量,修改 file02.c,如下:

#include <stdio.h>
int main(){
    // 使用 file02.c 中定义的数据
    printf("%d \n", num01);
    printf("%f \n", PI01);
    printf("%s \n", msg01);
    fn01();
    
    return 0;
}

        重新使用命令编译会发现编译失败,这是因为默认无法使用外边文件中定义的变量。如果想要使用外部文件中定义的变量,我们可以使用 extern 关键字声明外部链接,我们再次修改 file02.c,如下:

#include <stdio.h>
// 外部声明 file01.c 中定义的全局变量
extern int num01;
extern const double PI01;
extern char msg01[];
extern void fn01();
int main(){
    // 使用 file01.c 中定义的数据
    printf("%d \n", num01);
    printf("%f \n", PI01);
    printf("%s \n", msg01);
    fn01();
    
    return 0;
}

重新使用命令编译:可以成功运行。

(4)​​​​​​​static 关键字声明静态全局变量

        使用 static 关键字修饰的全局变量称为静态全局变量。

        普通全局变量对整个工程可见,其他文件中,使用extern外部声明后,可以直接使用。静态全局变量仅对当前文件可见,其他文件不可访问,其他文件中可以定义与其同名的变量,两者互不影响。

        静态全局变量对于需要编译多个源代码文件的程序,能够有效地降低源文件之间的耦合,避免不同文件同名变量的冲突。

        此外static关键字还可以修饰函数(静态函数)、全局数组、全局常量等,功能作用与静态全局变量一致。

         修改 file01.c 和 file02.c,如下:

file01.c

#include <stdio.h>
int num01 = 100;
// 声明为静态全局变量
static const double PI01 = 3.14;
char msg01[] = "Hello msg01";
// 声明为静态全局变量
static void fn01(){
    printf("function fn01 \n");
}

 file02.c

#include <stdio.h>
// 外部声明 file01.c 中定义的全局变量
extern int num01;
extern const double PI01;
extern char msg01[];
extern void fn01();
int main()
{
    // 使用 file01.c 中定义的数据
    printf("%d \n", num01);
    printf("%s \n", msg01);

    // 对于静态全局变量,即使使用 extren 声明也无法使用
    // fn01();
    // printf("%f \n", PI01);

    return 0;
}

运行:我们发现,file01.c 中定义的静态全局变量,在 file02.c 中即使使用 extern 声明也无法被使用。

七:递归函数

(1)递归函数概念

        一个函数在函数体内又调用了本身,我们称为递归调用,这样的函数就是递归函数。

递归函数成功执行需满足以下两个条件:

  1. 必须有一个明显的结束条件。
  2. 必须有一个趋近于结束条件的趋势。
#include <stdio.h>
// 递归函数:本质就是函数自己调用自己
// 递归函数:递归函数务必要有一个趋近于结束的条件
void fun(int num)
{
    printf("num1=%d\n", num);
    // 条件判断
    if (num > 1)
    {
        fun(num - 1);
    }
    printf("num2=%d\n", num);
}
int main()
{
    fun(3);
    return 0;
}

(2)递归函数案例

案例一

        1,1,2,3,5,8,13... 这是一个斐波那契数列,它的规则是第1个和第2个斐波那契数是1,从第3个数开始,每个斐波那契数都是前两个数之和。编写程序,输入一个整数,计算它的斐波那契数?

#include <stdio.h>
/**
 * 斐波那契数列:8->21
 *
 * 1 1 2 3 5 8 13 21........
 *
 * feibonaqie(1) = 1
 * feibonaqie(2) = 1
 *
 * feibonaqie(2) + feibonaqie(1) = 2
 *
 */

int feibonaqie(int n)
{
    // 趋近于结束的条件
    if (n == 1 || n == 2)
    {
        return 1;
    }
    else
    {
        // 从第三位以后开始 前两个之和
        return feibonaqie(n - 1) + feibonaqie(n - 2);
    }
}

int main()
{
    int n;
    printf("请您输入要查询的这个斐波那契数字的位置:");
    scanf("%d", &n);
    int result = feibonaqie(n);
    printf("%d位置的斐波那契数列的数字是%d\n", n, result);
    return 0;
}

 案例二

        有一堆桃子,猴子第一天吃了其中的一半,并多吃一个。以后每天猴子都吃其中的一半,然后再多吃一个。当到第十天时,想再吃时(注意:此时还没吃),发现只有1个桃子了。问:最初共多少个桃子?

#include <stdio.h>
/**
 * 第十天:  1
 * 第九天:  4
 * 第八天: 10
 * 第七天:  22
 * .......
 */

int peach(int day)
{
    // 趋近于结束的条件
    if (day == 10)
    {
        return 1;
    }
    else
    {
        return (peach(day + 1) + 1) * 2;
    }
}

// peach(10) == 1
// (peach(10) + 1 )* 2
int main()
{
    // 用户输入一个天数
    int day;
    printf("请您输入一下查询的天数:");
    scanf("%d", &day);
    int result = peach(day);
    printf("第几天%d,还有%d个桃子\n", day, result);

    return 0;
}

八:常用系统函数

(1)​​​​​​​字符串相关函数

<string.h> 头文件中的函数

标准库的头文件 <string.h> 中,有三个常用的字符串函数:

函数名

描述

strlen(str)

返回str的长度,类型是 size_t

strcpy(str1,str2)

将str2中的字符串复制到str1中

strcat(str1,str2)

将 str2 中的字符串追加到 str1 后面

#include <stdio.h>
#include <string.h> // 该头文件中声明字符串相关的系统函数

int main()
{
    // 声明两个字符串
    char src[] = "abcdefg", dest[50] = "hello";

    // strlen 获取字符串长度
    printf("src长度:%d \n", strlen(src));
    printf("dest长度:%d \n", strlen(dest));
    printf("\n");

    // 将 “world” 拷贝到 src, 拷贝字符串将原来的内容覆盖
    strcpy(src, " world");
    printf("src=%s \n", src);
    printf("\n");

    // strcat 是将 src 字符串的内容连接到 dest
    strcat(dest, src);
    printf("dest=%s", dest);

    return 0;
}

 <stdio.h> 头文件中的函数

        标准库的头文件 <stdio.h> 中,也有两个字符串函数:

        sprintf(),用于将格式化数据写入字符串。相比于 printf(),多了一个参数,第一个参数是要写入的字符串,后面参数与 printf() 一致。简单地讲,sprintf() 是将内容写入字符串而不是输出。

        sscanf(),用于从一个字符串中按照指定的格式提取数据。相比于 scanf(),多了一个参数,第一个参数是要提取数据的字符串,后面参数与 scanf() 一致。简单地讲,sscanf() 是从字符串中提取数据而不是从用户输入提取数据。

#include <stdio.h>
int main()
{
    // 这个函数sprintf对于嵌入式同学很重要!!
    // 可以将其他类型的数据转换为字符串
    // int a = 100;
    // double PI = 3.14;
    // char str[100];
    // // 字符串[字符数组]
    // sprintf(str, "今天花了%d元,但是剩下%f元", a, PI);
    // printf("%s\n", str);

    // sscanf:从字符串中提取数据
    char str[] = "今天买了一个手机花了8000,但是讲完价格卖我3.4";
    int price;
    double price1;
    sscanf(str, "今天买了一个手机花了%d,但是讲完价格卖我%lf", &price, &price1);
    printf("price=%d price1=%lf\n", price, price1);
    return 0;
}

(2)日期时间相关函数

日期时间相关函数,在头文件 <time.h> 中

函数名

描述

time(&变量)

获取当前日期赋值到变量中,该变量需是 time_t 类型

ctime(&时间值)

将时间值转为字符串并返回,时间值需是 time_t 类型

difftime(时间值1,时间值2)

返回两个时间值的差,返回值是 double 类型,时间值需是 time_t 类型

注意:

        time_t 是C语言中用于表示时间值的数据类型,它通常是一个整数类型(int 或 long 或 long long),用于存储时间戳。

        时间戳是指从1970年1月1日(通常称为UNIX纪元)零时零分零秒(UTC时间)起至某一特定时刻所经过的秒数。

#include <stdio.h>
// time头文件当中
#include <time.h>
int main()
{
    // 变量
    time_t current_time;
    time(&current_time);
    printf("时间戳=%lld\n",current_time);
    printf("%s",ctime(&current_time));

    return 0;
}
#include <stdio.h>
#include <time.h>
void fn()
{
    int sum = 0;
    for (int i = 0; i < 10000000; i++)
    {
        sum = 0;
        for (int j = 0; j <= 100; j++)
        {
            sum += j;
        }
    }
}

int main()
{
    time_t start, end;
    // 开始
    time(&start);
    fn();
    // 结束
    time(&end);
    time_t diff = difftime(end, start);
    printf("这个fn函数执行了%lld秒\n", diff);

    return 0;
}

(3)数学相关函数

        数学相关函数,在头文件 <math.h> 中

函数名

描述

sqrt(x)

计算平方根

cbrt(x)

计算立方根

pow(x,y)

计算x的y次方

fabs(x)

计算x的绝对值

ceil(x)

向上取整

floor(x)

向下取整

round(x)

四舍五入取整

trunc(c)

截断小数部分

 九:本章练习

(1)第一题

        定义函数,实现求两个double数字的最大值,并返回。

#include <stdio.h>

// 声明函数:算出两个数字中最大的数值
double max(double a, double b)
{
    return a > b ? a : b;
}

int main()
{
    // 调用次函数
    double result = max(3.4, 6.9);
    printf("最大数值:%lf", result);
    printf("最大数值:%lf", max(1.2, 2.4));
    return 0;
}

(2)第二题

        定义函数,求出三个int 类型数的和,并返回。

#include <stdio.h>
// 计算三个数字之和
int sum(int a, int b, int c)
{
    return a + b + c;
}

int main()
{
    int result = sum(1, 2, 3);
    printf("%d\n",result);
    return 0;
}

(3)第三题

        定义函数,判断一个数字是否是质数。

#include <stdio.h>
#include <stdbool.h>
// 封装一个函数:函数作用是判断这个数字是不是质数!
// 如果是质数,函数返回真
// 如果不是质数,函数返回假
bool Prime(int number)
{
    // 统计能被整除的个数
    int count = 2;
    // 循环遍历
    for (int i = 2; i < number; i++)
    {
        // 判断
        if (number % i == 0)
        {
            count++;
            break;
        }
    }

    return count == 2 ? true : false;
}

int main()
{
    int a;
    // 提示
    printf("请您输入一个大于1的整数:");
    scanf("%d", &a);
    // 接受函数返回的结果
    bool isPrime = Prime(a);
    if (isPrime)
    {
        printf("%d是质数", a);
    }
    else
    {
        printf("%d不是质数", a);
    }

    return 0;
}

 (4)第四题

        函数可以没有返回值案例,编写一个函数,从终端输入一个整数打印出对应的金字塔,函数可以传入金字塔层数。

#include <stdio.h>



void star(int row)
{
    // 外层控制行数
    for (int i = 1; i <= row; i++)
    {
        // 输出空格
        for (int k = 1; k <= row - i; k++)
        {
            printf(" ");
        }

        // 输出星星
        for (int j = 1; j <= 2 * i - 1; j++)
        {
            // 保留住每一行头部与尾部的星星
            if (j == 1 || j == 2 * i - 1 || i == row)
            {
                printf("*");
            }
            else
            {
                printf(" ");
            }
        }
        // 换行
        printf("\n");
    }
}

int main()
{

    int line;
    printf("请您输入楼层的层数:");
    scanf("%d", &line);
    star(line);

    return 0;
}

 (5)第五题

        请写出下面程序的运行结果

int num = 100;
void func()
{
    num += 20;
}
int main()
{
    int num = 50;
    func();
    printf("%d", num);
    return 0;
}

答案:50

【解析】

func 函数中使用的是全局变量 num,main() 函数中输出的是局部变量 num, 两者没有关联。

(6)第六题

         请写出下面程序的运行结果

void func()
{
    int n1 = 10;
    static int n2 = 20;
    n1 ++;
    n2 ++;
    printf("%d %d \n", n1, n2);
}

int main()
{
    func();
    func();
    return 0;
}

答案:

11 21

11 22

【解析】

  1. n1是普通的局部变量,函数调用时创建,函数调用结束销毁,func() 调用两次,每次都会创建新的变量 n1, 故两次 n1 的值都是 11。
  2. n2 是静态局部变量,只在函数第一次调用时创建,函数调用结束也不会销毁,所以第二次调用 func() 的时候并没有创建新的 n2, 会继续在上次基础上累加,所以第一次值是 21, 第二次是 22。

(7)第七题 

        请写出下面程序的运行结果

void func(int n)
{
    printf("%d \n", n);
    if (n > 1) {
        func(n - 1);
    }
    printf("%d \n", n);
}

int main()
{
    func(3);
    return 0;
}

答案:

3

2

1

1

2

3

【解析】

  1. func(3) 被调用,输出 3,然后进入递归调用 func(2)。
  2. func(2) 被调用,输出 2,然后进入递归调用 func(1)。
  3. func(1) 被调用,输出 1,然后不再进行递归调用,再次输出 1。
  4. 返回到上一级的 func(2),输出 2。
  5. 返回到最初的调用 func(3),输出 3。

本章内容到此结束,下一章为指针

关注我,一起学习嵌入式 

  • 36
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值