C语言从零探索函数的知识

 更新时间:2022年4月21日 16:36  点击:231 作者:清风自在 流水潺潺

一、初探程序中的函数

函数的概念

  • 函数是具有特定功能的程序部件(可当黑盒使用)
  • 函数有明确的使用方式(固定输入对应固定输出)
  • 函数在程序中可重复使用(程序中的工具)

函数的类型

  • 数据处理(数据→数据)

通过某种规则将 x处理成 y,如: y = 2x +1

  • 过程定义(数据→功能)

(根据数据)执行一系列动作,进而完成某种功能,如:屏幕打印

C语言中函数的组成部分

函数名:函数的唯一标识

函数参数定义:数据输入(数据→数据,数据→动作)

函数返回类型:

  • 数据输出(数据→数据)
  • 无返回值(数据→动作)

广义函数示例:

返回类型 函数名(参数1,参数2)
{
    程序语句1;
    程序语句2;
    ......;
    程序语句n;
}

C语言中的函数示例

函数的调用

  • 通过函数名调用已经定义好的函数
  • 函数调用时需要依次指定函数参数的具体值
  • 函数调用的结果(返回值)可保存在同类型的变量中

下面看一段函数调用的代码:

#include <stdio.h>
int func_demo( int x )
{
    int y = 0;
    y = 2 * x  - 1;
    return y;
}
int main()
{
    int r1 = func_demo(1);
    int r2 = func_demo(5);
    int r3 = func_demo(10);
    printf("r1 = %d\n", r1);
    printf("r2 = %d\n", r2);
    printf("r3 = %d\n", r3);
    return 0;
}

下面为输出结果:

下面再看一段编写函数计算累加和的代码:

#include <stdio.h>
int sum (int n)
{
    int r = 0;
    int i = 0;
    for(i=1; i<=n; i++)
    {
        r += i;
    }
    return r;
}
int main()
{
    int o[10] = {10, 20, 30, 40, 50, 100};
    int r[10];
    int i = 0;
    for(i=0; i<10; i++)
    {
        r[i] = sum(o[i]);
    }
    for(i=0; i<10; i++)
    {
        printf("sum(%d) = %d\n", o[i], r[i]);
    }
    return 0;
}

下面为输出结果:

采用数组可以便捷的求出从1加到指定的数。

小结

  • 函数是具有特定功能的程序部件
  • 函数由函数名,参数,返回类型以及函数体组成
  • 通过函数名调用已经定义好的函数,并同时传入参数值
  • 函数的本质就是可重复利用的代码段

二、深入浅出函数调用

再论C语言程序的入口

  • 一般情况下,C语言程序从main()开始执行

深入理解main()

  • main() 是应用程序与操作系统的一个“约定”
  • 当操作系统运行应用程序时,首先调用的就是 main() 函数
  • 应用程序必须运行于操作系统,接受操作系统管理

应用程序的运行

应用程序运行流程

下面看一段代码,实际感受一下吧:

#include<stdio.h>
#include<stdlib.h>
int main()
{
    printf("Hello World!\n");
    system("pause");
    return 0;
}

没错,就是这个简单的不能再简单的代码,打开Test.exe,得到下图:

下面来证明一下 返回值 0 是返回给操作系统,首先打开命令提示符,如下:

然后在命令提示符上切换到这个目录,采用 cd 命令,如下:

然后运行 Test.exe,如下:

按下回车,输入echo %errorlevel%,如下:

可以看到输出 0 ,这是 main 函数中的返回值。如果把 return 0那里换成 return 666,那么运行 echo %errorlevel% 会输出 666。这就说明返回值成功返回给操作系统。

核心本质

  • C程序由一系列不同功能的函数构成
  • 函数之间通过相互调用组合“小功能”构成“大功能”
  • 整个C程序的功能由函数的组合调用完成

工具包的本质

  • 工具包就是函数集,包含了一系列定义好的函数
  • #include 语句用于声明需要使用工具包中的函数
  • 工具包中的函数由其它开发者通过C语言编写
  • 也可根据项目需要自行编写私有工具包

小结

  • main() 是C程序中的入口函数(第一个被调用的函数)
  • main() 函数被操作系统调用(返回值也传给操作系统)
  • 工具包的本质是一个函数集合
  • 可以根据需要自行定义工具包(函数集)

三、函数定义细节剖析

函数定义与函数调用

函数在被调用前必须完整定义

函数可以先被声明,再被定义

  • 声明时,必须给出函数三要素(函数名,参数列表,返回类型)
  • 定义时,必须完整给出函数体定义

特殊的基础类型

  • C语言中存在空类型(void),这种类型表示“空”
  • void 不能用于定义具体变量(没有任何数据属于空类型)
  • void 常用于函数定义,表示无返回值或无参数

void 深入理解

  • void 是基础类型,但不是基础数据类型,无法定义变量
  • void可用于函数参数,表示函数无参数
  • void 可用于函数返回类型,表示函数无返回值

所以说,下面程序的写法就是错误的。

#include <stdio.h>
void demo(void i)
{
    return i;
}
int main()
{
    void v;
    void x = v;
    demo(x);
    return 0;
}

注意事项

C语言中的函数如果确定不需要参数,那么用 void 定义参数,而不是不写参数。

#include <stdio.h>
void f( )
{
    printf("void f() \n");
}
void g(void)
{
    printf("void g() \n");
}
int main()
{
    f();
    f(1, 2);
    g();
//    g(1);   // ERROR
    return 0;
}

下面为输出结果:

可以看出,f 函数的输入参数是没有限制的,而g 函数没有输入参数,这点要特别注意。

关于函数返回

return 语句直接返回主调函数,后续代码不再执行

对于无返回值函数

  • return 可以直接使用,无需跟上返回值
  • 当函数体中没有 return 语句时,最后一条语句执行后自动返回

对于有返回值的函数

  • return 必须跟上一个合法返回值,所有执行分支都必须显示返回值
  • return 语句必须出现在函数体中,并且必须被执行

小结

  • 函数可以先被声明,再被定义(调用前必须给出完整定义)
  • C语言中存在空类型(void) ,这种类型表示“空”
  • void是基础类型,但不是基础数据类型
  • return语句直接返回主调函数,后续代码不再执行
  • 函数中的所有执行分支必须存在return语句

四、函数参数深度剖析

深入函数参数

  • 函数参数在函数定义时并没有确定的值(形参)
  • 函数参数的具体值在函数调用时指定(实参)
  • 函数参数的本质是变量
  • 函数调用时指定的实参用于对形参进行初始化,初始化之后形参在函数内部等同于普通变量。

下面看一段代码:

#include <stdio.h>
int test(int n);
int main()
{
    int i = 3;
    int j = test(i);
    printf("i = %d, j = %d\n", i, j);
    return 0;
}
int test(int n)
{
    n = n * 10;
    return n;
}

下面为输出结果:

特殊的数组参数

  • 可以在定义函数时使用数组形参(如: int f( int a[5] );)
  • 数组形参需要使用同类型数组作为实参
  • 在C语言中,数组作为函数参数传递时大小信息丢失
  • 在函数内部修改数组形参,将影响数组实参

注意事项

  • 数组形参已经发生退化,不包含数组大小信息
  • 示例:void func (int a[ ]) 等价于void func (int a[1]) 等价于void func (int a[10]) 等价于void func (int a[100])

下面看一段代码,加深印象:

#include <stdio.h>
void demo(int a[3])
{
    a[0] = 50;
}
int sum(int a[], int len)
{
    int ret = 0;
    int i = 0;
    while( i < len )
    {
        ret += a[i];
        i++;
    }
    return ret;
}
int main()
{
    int arr1[5] = {0, 1, 2, 3, 4};      // arr1[0] -> 0
    int arr2[10] = {0, 10, 20, 30, 40}; // arr2[0] -> 0
    demo(arr1);
    demo(arr2);
    printf("arr1[0] = %d\n", arr1[0]);
    printf("arr2[0] = %d\n", arr2[0]);
    printf("sum(arr1) = %d\n", sum(arr1, 5));
    printf("sum(arr2) = %d\n", sum(arr2, 10));
    return 0;
}

下面为输出结果:

这里注意一下这句话:在函数内部修改数组形参,将影响数组实参,所以当调用 demo 函数后,实参的 arr1[0] 和 arr2[0] 都变成了50。

小结

  • 函数定义时参数没有具体值,函数调用时指定参数初始值
  • 函数参数在函数内部等同于普通变量
  • 在C语言中,数组作为函数参数传递时大小信息丢失
  • 在函数内部修改数组形参,将影响数组实参
  • 在C语言中,数组作为函数参数传递时大小信息丢失在函数内部修改数组形参,将影响数组实参

五、编写函数对数组排序

排序的一般定义

  • 排序是计算机内经常进行的一种操作,其目的是将一组“无序”的数据元素调整为“有序”的数据元素。

排序中的关键操作

比较

  • 任意两个数据元素通过比较操作确定先后次序

交换

  • 数据元素之间需要交换才能得到预期结果

核心思想

  • 每次(例如第i次,i = O,1,..., n-2) 从后面 n-i 个待排的数据元素中选出最小元素,作为第 i 个元素。

解决方案

编写函数 int Min(int a[], int b, int e) 选择最小元素

  • 功能定义:在数组 a 的 b ...e ] 范围寻找最小元素
  • 返回值:最小元素在数组中的下标

循环遍历数组,将每次找到的最小元素交换就位

下面看一下示例代码:

#include <stdio.h>
int Min(int a[], int b, int e)
{
    int r = b;
    int i = 0;
    for(i=b; i<=e; i++)
        if( a[r] > a[i] )
            r = i;
    return r;
}
void Sort(int a[], int n)
{
    int i = 0;
    int j = 0;
    int k = 0;
    for(i=0; i<n; i++)
    {
        j = Min(a, i, n-1);
        if( i != j )
        {
            k = a[i];
            a[i] = a[j];
            a[j] = k;
        }
    }
}
void Print(int a[], int n)
{
    int i = 0;
    while( i < n )
        printf("%d ", a[i++]);
    printf("\n");
}
int main()
{
    int a[5] = {20, 30, 10, 40, 50};
    printf("Origin: \n");
    Print(a, 5);
    Sort(a, 5);
    printf("After: \n");
    Print(a, 5);
    return 0;
}

下面为输出结果:

小结

  • 排序是将一组“无序”的数据调整为“有序”的数据
  • 排序中的关键操作为比较和交换
  • 排序时每次选择未排序数据中的最小值,并交换就位
  • 每次选择交换使得数据逐渐有序,最终完全有序

六、变量的作用域与生命期(上)

C语言中变量的分类

局部变量

  • 函数内部定义的变量(隶属于当前函数)
  • 只能在当前函数中访问使用

全局变量

  • 全局范围内的变量(不特定隶属于任意一个函数)
  • 可以在任意函数中访问使用

同名变量的问题

  • 不同函数中的局部变量可以同名(不会产生冲突)
  • 全局变量不能同名(会产生命名冲突)
  • 当局部变量和全局变量同名时,优先使用局部变量

同名变量规则

  • 存在多个同名变量时,优先使用最近定义的变量。

下面看一段代码,感受一下:

#include <stdio.h>
int var = 100;  // 全局变量
void f(int var) // var <==> 局部变量
{
    var++;
    printf("var = %d\n", var);
}
int main()
{
    int var = 10;  // 局部变量
    f(var);  // f(10);
    printf("var = %d\n", var);  // var = 10;
    return 0;
}

下面为输出结果:

变量的作用域

  • 变量的作用域指的是变量定义后的可访问范围
  • 不同变量的作用域可以有重叠
  1. 不同名变量在重叠作用域内可分别访问
  2. 在重叠作用域内,只可访问最近定义的同名变量

局部变量的作用域

  • 代码块:从 { 开始到 } 结束的一段代码
  • 变量只能定义在代码块的开始处,即: { 之后,执行语句之前
  • 变量的作用域从定义开始到当前代码块结束
  • 当变量的作用域结束后,变量不可用 (无法直接访问)

全局变量的作用域

  • 全局作用域:可在程序的各个角落访问并使用
  • 文件作用域:只能在当前代码文件中访问并使用
  • 全局作用域:可在程序的各个角落访问并使用一文件作用域:只能在当前代码文件中访问并使用
  • 工程开发中,全局变量通常以 g_ 作为前缀命名(工程约定)

下面看一段代码,感受一下:

#include <stdio.h>
int var = 100;  // 全局变量
int main()
{
    int var = 10;  // 局部变量
    {
        int var = 1;  // 局部变量
        printf("var = %d\n", var);
    }
    printf("var = %d\n", var);  // var = 10;
    return 0;
}

下面为输出结果:

注意:存在多个同名变量时,优先使用最近定义的变量

小结

  • 局部变量只能在当前函数中使用,全局变量可在任何地方使用
  • 当局部变量和全局变量同名时,优先使用局部变量
  • 变量的作用域可重叠,内层作用域覆盖外层作用域
  • 离开作用域后变量不可访问(无法继续使用)

七、变量的作用域与生命期(下)

不同变量的物理存储区域

  • 在现代计算机系统中,物理内存被分为不同区域
  • 区域不同,用途不同,不同种类的变量位于不同区域
  1. 全局数据区:存放全局变量,静态变量
  2. 栈空间:存放函数参数,局部变量
  3. 堆空间:用于动态创建变量

生命期:变量从创建到销毁的时间(即:合法可用的时间)

不同变量的生命期

全局数据区中的变量

  • 程序开始运行时创建,程序结束时被销毁,整个程序运行期合法可用

栈空间中的变量

  • 进入作用域时创建,离开作用域时销毁(自动销毁)

局部变量在函数调用返回后销毁

下面看一段代码,感受一下变量生命期:

#include <stdio.h>
int var = 1;
void func()
{
    printf("var = %d\n", var);
}
int main()
{
    int var = 2;
    int i = 0;
    for(i=0; i<5; i++)
    {
        int var = 4;
        var += i;
        printf("var = %d\n", var);
    }
    func();
    printf("var = %d\n", var);
    return 0;
}

下面为输出结果:

这个例子充分展示了变量的生命周期,值得仔细体会。

作用域与生命期无本质联系

作用域规则是语法层面对变量是否可访问的规定

生命期是二进制层面上变量存在于内存中的时间

可能的情况

  • 作用域外无法访问的变量,可能在其生命期中(静态局部变量)
  • 作用域内可访问的变量,可能已经被销毁(堆变量)
  • 生命期中的变量,可能无法访问(文件作用域全局变量)

静态变量

  • static 是C语言中的关键字
  • static 修饰的局部变量创建于全局数据区(拥有程序生命期)
  • static 修饰的全局变量只有文件作用域(文件之外无法访问)
  • static 局部变量只会初始化一次,作用域与普通变量无异

​​​​​​​变量的生命期由变量存储位置决定 ​​​​​​​

  • static 将变量存储于全局数据区,默认初始化为0
  • auto 将变量存储于栈空间,默认初始化为随机值
  • register 将变量存储于寄存器,默认初始化为随机值​​​​​​​​​​​​​​

不同类型变量示例​​​​​​​​​​​​​​

#include <stdio.h>
int g_var = 1;
static int g_sVar = 2;
int main()
{
    static int s_var = 3;
    auto int v = 4;
    register int rv = 5;
    printf("g_var = %d\n", g_var);
    printf("g_sVar = %d\n", g_sVar);
    printf("s_var = %d\n", s_var);
    printf("v     = %d\n", v);
    printf("rv    = %d\n", rv);
    return 0;
}

下面为输出结果:

下面看一段代码,感受一下 static 关键词:

#include <stdio.h>
int global;
int func(int x)
{
    static int s_var;   // 全局数据区中的变量,默认初始化为 0
                        // 并且,只做一次初始化
    s_var += x;
    return s_var;
}
int main()
{
    int i = 0;
    for(i=1; i<=5; i++)
    {
        printf("func(%d) = %d\n", i, func(i));
    }
    printf("func(0) = %d\n", func(0));
    printf("global = %d\n", global);
    return 0;
}

下面为输出结果:

这里注意:全局数据区中的变量,默认初始化为 0 ,并且,只做一次初始化

小结

  • 变量生命期指变量合法可用的时间
  • 生命期是变量存在于内存中的时间
  • 作用域与生命期无本质联系
  • 作用域和生命期用于判断变量是否可访问
 staticauto(默认)register
局部变量全局数据区栈空间寄存器(可能)
全局变量全局数据区------

八、函数专题练习

题目:编写函数,将字符串转换为整型数

函数原型:int str2int(char s[]);

参数:可以代表整型数的字符串

返回值:整型值

注意事项:

  • 整型数可以存在符号位,如: "-12345"
  • 字符串本身可能不是一个合法整型数,如:"123xyz45"

算法流程

上代码:

#include <stdio.h>
int getNumber(char c)
{
    int ret = -1;
    if( ('0' <= c) && (c <= '9') )
        ret = c - '0';
    return ret;
}
int str2int(char str[])
{
    int ret = 0;
    int sign = 0;
    int i = 0;
    if( getNumber(str[0]) != -1 )
    {
        sign = 1;
        i = 0;
    }
    else if( str[0] == '+' )
    {
        sign = 1;
        i = 1;
    }
    else if( str[0] == '-' )
    {
        sign = -1;
        i = 1;
    }
    while( sign && str[i] )
    {
        int n = getNumber(str[i]);
        if( n != -1 )
            ret = ret * 10 + n;
        else
            break;
        i++;
    }
    ret = sign * ret;
    return ret;
}
int main()
{
    printf("%d\n", str2int("123"));
    printf("%d\n", str2int("-12345"));
    printf("%d\n", str2int("567xyz89"));
    printf("%d\n", str2int("abc"));
    printf("%d\n", str2int("-xyz"));
    return 0;
}

下面为输出结果:

九、递归函数简介

在程序设计中,将函数自调用称为递归调用

递归是一种数学上分而自治的思想 ​​​​​​​

  • 将原问题分解为规模较小的问题进行处理
  • 问题的分解是有限的(递归不能无限进行)

递归模型的一般表示法

递归在程序设计中的应用

递归函数

  • 函数体中存在自我调用的函数
  • 递归函数必须有递归出口(边界条件)
  • 函数的无限递归将导致程序崩溃​​​​​​​​​​​​​​

递归思想的应用:​​​​​​​​​​​​​

自然数列求和:sum( n ) = 1 +2 +3 + ... + n​​​​​​

斐波拉契数列:1,1,2,3,5,8,13,21,...

上代码:

#include <stdio.h>
int sum(int n)
{
    int ret = 0;
    if( n == 1 )
        ret = 1;
    else
        ret = n + sum(n-1);
    return ret;
}
int fac(int n)
{
    int ret = 0;
    if( n == 1 )
        ret = 1;
    else if( n == 2 )
        ret = 1;
    else if( n >= 3 )
        ret = fac(n-1) + fac(n-2);
    else
        ret = -1;
    return ret;
}
int main()
{
    int i = 0;
    printf("sum(1) = %d\n", sum(1));
    printf("sum(10) = %d\n", sum(10));
    printf("sum(100) = %d\n", sum(100));
    for(i=1; i<=10; i++)
    {
        printf("%d, ", fac(i));
    }
    printf("\n");
    return 0;
}

下面为输出结果:

小结

  • 递归是一种数学上分而自治的思想
  • 递归程序中的表现为函数自调用
  • 递归解法必须要有边界条件,否则无解
  • 编写递归函数时不要陷入函数的执行细节

到此这篇关于C语言从零探索函数的知识的文章就介绍到这了,更多相关C语言函数内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

原文出处:https://blog.csdn.net/weixin_43129713/article/details/122051

[!--infotagslink--]

相关文章

  • C语言实现放烟花的程序

    这篇文章主要为大家详细介绍了C语言实现放烟花的程序,有音乐播放,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-02-23
  • C语言中的字符(char)详细讲解

    本篇文章主要介绍C语言中char的知识,并附有代码实例,以便大家在学习的时候更好的理解,有需要的可以看一下...2020-04-25
  • php正确禁用eval函数与误区介绍

    eval函数在php中是一个函数并不是系统组件函数,我们在php.ini中的disable_functions是无法禁止它的,因这他不是一个php_function哦。 eval()针对php安全来说具有很...2016-11-25
  • php中eval()函数操作数组的方法

    在php中eval是一个函数并且不能直接禁用了,但eval函数又相当的危险了经常会出现一些问题了,今天我们就一起来看看eval函数对数组的操作 例子, <?php $data="array...2016-11-25
  • Python astype(np.float)函数使用方法解析

    这篇文章主要介绍了Python astype(np.float)函数使用方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-06-08
  • Python中的imread()函数用法说明

    这篇文章主要介绍了Python中的imread()函数用法说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-16
  • C# 中如何取绝对值函数

    本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
  • C#学习笔记- 随机函数Random()的用法详解

    下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • 详解如何将c语言文件打包成exe可执行程序

    这篇文章主要介绍了详解如何将c语言文件打包成exe可执行程序,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-25
  • C#创建自定义控件及添加自定义属性和事件使用实例详解

    这篇文章主要给大家介绍了关于C#创建自定义控件及添加自定义属性和事件使用的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用C#具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧...2020-06-25
  • JS实现自定义简单网页软键盘效果代码

    本文实例讲述了JS实现自定义简单网页软键盘效果。分享给大家供大家参考,具体如下:这是一款自定义的简单点的网页软键盘,没有使用任何控件,仅是为了练习JavaScript编写水平,安全性方面没有过多考虑,有顾虑的可以不用,目的是学...2015-11-08
  • 金额阿拉伯数字转换为中文的自定义函数

    CREATE FUNCTION ChangeBigSmall (@ChangeMoney money) RETURNS VarChar(100) AS BEGIN Declare @String1 char(20) Declare @String2 char...2016-11-25
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • C++中 Sort函数详细解析

    这篇文章主要介绍了C++中Sort函数详细解析,sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变...2022-08-18
  • PHP用strstr()函数阻止垃圾评论(通过判断a标记)

    strstr() 函数搜索一个字符串在另一个字符串中的第一次出现。该函数返回字符串的其余部分(从匹配点)。如果未找到所搜索的字符串,则返回 false。语法:strstr(string,search)参数string,必需。规定被搜索的字符串。 参数sea...2013-10-04
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • PHP函数分享之curl方式取得数据、模拟登陆、POST数据

    废话不多说直接上代码复制代码 代码如下:/********************** curl 系列 ***********************///直接通过curl方式取得数据(包含POST、HEADER等)/* * $url: 如果非数组,则为http;如是数组,则为https * $header:...2014-06-07
  • php中的foreach函数的2种用法

    Foreach 函数(PHP4/PHP5)foreach 语法结构提供了遍历数组的简单方式。foreach 仅能够应用于数组和对象,如果尝试应用于其他数据类型的变量,或者未初始化的变量将发出错误信息。...2013-09-28
  • C语言中free函数的使用详解

    free函数是释放之前某一次malloc函数申请的空间,而且只是释放空间,并不改变指针的值。下面我们就来详细探讨下...2020-04-25
  • Android自定义WebView网络视频播放控件例子

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02