C语言函数详解

函数详解

在结构化程序设计中,函数是将任务进行模块划分的基本单位。通过函数,可以把一个复杂的任务分解成若干个易于解决的小任务。充分体现结构化程序设计由粗到精,逐步细化的设计思想,一个大的程序一般应分成若干个程序模块,每个模块实现一个特定的功能,这些模块称之为子程序,在C语言中子程序用函数实现。

在这里插入图片描述

如上,我们设计一个图书管理系统,可以按照功能划分,分为多个模块,再将多个模块继续划分,直到划分成实现单一功能的模块。将这些模块一一封装成函数,再通过函数调用,即可实现该系统。


1. 函数的分类

函数分为两类:自定义函数库函数

1.1 库函数

C语言库提供给用户的,已经封装好的函数,使用时包含相应的头文件。
举个例子:
c语言库中的#include< ctype.h>头文件
用来确定包含于字符数据中的类型的函数,如是否是数字,是否是英文字母,是否是大写等等。

int main()
{
    char ch;
    ch = getchar();
    if (iscntrl(ch)) // 是否是控制字符,是返回非0值,否返回0值
    {
        printf("this is a control character\n");
    }
    else if (isdigit(ch)) // 是否是数字型字符
    {
        printf("this is a digit character\n");
    }
    else if (islower(ch)) // 是否是小写字符
    {
        printf("this is a small character\n");
    }
    else if (isupper(ch)) // 是否是大写字符
    {
        printf("this is a capital character\n");
    }
    else
    {
        printf("this is a other character\n");
    }
    return 0;
}

上述代码,调用了一些判断字符的库函数。

1.2 自定义函数

编程者自己根据需求,将某个具有相对独立功能的程序封装成的函数。
返回值类型 函数名(形参列表){ 函数体 }

int add(int a, int b); // 函数声明

int main()
{
    int a = 10, b = 20;
    int sum = add(a, b); // 函数调用
}

int add(int a, int b) // 函数定义
{
    return a + b;
}

注意:

  • 在标准C语言中,函数可以嵌套使用,但不能嵌套定义。
  • 若函数被定义在该函数调用的代码之后,在调用函数之前一定要声明函数
  • 函数声明:返回值类型 函数名(形参列表 / 形参类型列表);
  • 函数声明给出了函数名、返回值类型、参数列表(重点是参数类型)等与该函数有关的信息,称为函数原型。函数原型的作用是告诉编译器与该函数有关的信息,让编译器知道函数的存在,以及存在形式,即使函数暂时没有定义,编译器也知道如何使用它。
  • 有了函数声明,函数定义就可以在任何地方了,甚至是其他文件、静态链接库、动态链接库等。

2. 编译和链接

2.1 文件

  • 文件:文件是一个外存的概念,文件只存在于外存(硬盘,U盘,网盘)中,文件由两部分构成,文件名文件主体
  • 文件的分类:可执行文件不可执行文件
    • 可执行文件:Windows中,扩展名为:.exe、.bat、.com 等文件都是可执行文件。可执行文件是由指令和数据构成的,在Linux中,是靠文件属性来判断是否可执行。
    • 不可执行文件:只有数据构成。
  • C/C++中,(.c)(.cpp) 源文件(文本); (.h)(.hpp) 头文件(文本);(.i)预编译文件(文本);(.s)汇编文件;(.o / .obj)二进制目标文件;(.exe)可执行文件(二进制文件)。

2.2 编译链接过程 (X86)

在这里插入图片描述

我们所写的文件是不可执行文件,当我们启动编译器后,编译器会将我们源文件经过预编译、编译、链接,最后形成.exe可执行文件,我们执行这个文件时,文件会被加载到内存中执行。


3. 可见性(作用域)和生命周期

3.1 作用域(可见性) 针对于编译链接过程

作用域:标识符能够被识别的范围,只有在作用域内标识符才能被使用。

  1. 函数中定义的标识符,包括形参和函数体中定义的局部变量,作用域都在该函数内,也称为函数域
  2. 文件作用域也叫全局作用域,定义在所有的函数之外的标识符,具有文件作用域,作用域从定义到整个源文件结束。文件中定义的全局变量和函数都具有文件作用域。

3.2 生命周期 针对于程序执行过程

生命周期:标识符从程序开始运行时被创建,具有存储空间,到程序运行结束时消亡,释放存储空间的时间段。

  1. 局部变量生命周期:函数被调用,分配存储空间,到函数执行结束,释放存储空间。存储在 .stack区。

  2. 全局变量的生命周期:从程序开始执行,到执行结束。存储在 .data区。

  3. 动态生命周期:由特定的函数调用或运算符来创建或释放,如调用 malloc() 为变量分配内存空间时变量的生命周期开始,到调用 free() 释放空间或程序结束时,生命周期结束。具有动态生命周期。存储在 .heap 区。

    int mian()
    {
        {
            static int a=10;    //作用域在这个块作用域中,生命周期是整个文件。
            a+=1;
        }
        return 0;
    }
    

4. 函数调用分析

在这里插入图片描述

当函数调用时,系统会给该函数分配栈帧,用来存放该函数中定义的局部变量以及形参,当函数调用完毕,退出函数时,这块栈帧会被回收。函数的返回值会被放到一个临时空间中。局部变量生命周期结束。
当函数调用时,实参传递给形参时,是从参数列表从右向左依次传递的。

4.1 总结函数调用机制

局部变量占用的内存是在程序执行过程中动态建立和释放的。这种动态是通过栈由系统自动管理进行的。当任何一个函数调用发生时,系统都要做以下工作:

  1. 建立栈空间。
  2. 为被调用函数中的局部变量分配空间,完成参数传递。
  3. 保护现场:主调函数运行状态和返回地址入栈。
  4. 执行被调函数的函数体。
  5. 被调函数执行结束,释放被调函数中局部变量占用的栈空间。
  6. 回复现场:取主函数运行状态及返回地址,释放栈空间。
  7. 继续主调函数的后续语句。

5. 传值调用与传址调用

5.1 传值调用

void Swap_i(int a, int b) // 经典交换函数
{
    int tmp = a;
    a = b;
    b = tmp;
}

int main()
{
    int x = 10, y = 20;
    Swap_i(x, y);
    return 0;
}

在此,我们顺便讨论一下,形参与实参

  • 实参:实际参数,就是函数在调用时,我们传递给函数的值,如上述代码中,x,y都是实参。

  • 形参:函数被调用时,会跟据传递的实参,进行拷贝,形参就是实参的临时拷贝。在调用函数结束后,形参的生命周期结束。上述代码中,a,b是形参。

  • 实参和形参在数量上、类型上、顺序上,必须一致。

    在这里插入图片描述

该函数只能将形参a,b的值进行交换,函数调用完毕,栈帧回收,a,b也随之消失。无法实现x,y的值交换。

5.2 传址调用

void Swap(int* ap, int* bp)
{
    int tmp = *ap;
    *ap = *bp;
    *bp = tmp;
}

int main()
{
    int x = 10, y = 20;
    Swap(&x, &y);
    return 0;
}

在这里插入图片描述

通过指针,交换了x,y的值,函数调用完毕,栈帧回收,ap,bp随之消失。


6. 利用函数实现模块化程序设计

函数就是功能,每个函数用来实现一个特定的功能,函数的名字反应其代表的功能。

在设计一个较大的程序时,往往把它们分成若干个程序模块(模块可以是一个函数,也可能是一个.c文件),每个模块包含一个或多个函数,每个函数实现一个特定的功能。一个C程序可有一个主函数和若干个其他函数构成,由主函数调用其他函数,其他函数也可以相互调用。

有些功能可能往往需要在不同地方多次实现,因此通过调用封装的函数来实现做到事半功倍的效果。

6.1 函数设计的基本原则

  1. 封装:把代码封装进去,对调用者来说的话能隐藏我们的实现功能。
    a)调用者(外界)对函数的影响——仅限于入口参数。
    b)函数对调用者的影响——函数的返回值,指针参数。
  2. 检查函数的入口参数的有效合法,检查函数是否调用成功。(对于参数进行断言/if判断,特别时对指针判空)
  3. 函数的规模要小。不超过80行。
  4. 函数功能单一,只实现特定功能。
  5. 函数接口定义清晰,利于调用者使用(函数名,返回值,形参)。

6.2 函数的编写步骤

  1. 需求分析:明确我们要处理什么问题(完成什么功能)。即函数功能。确定我们处理问题所需要的资源。即函数参数列表信息。
  2. 算法设计(如何做一件事):根据所需的功能和拥有的资源,理清思路,确定具体步骤(算法)。
  3. 起函数名(见名知意),确定形参,确定返回值。
  4. 编写函数。

以上是编写函数的步骤,也是编写程序的步骤,理清步骤,才能做到编码行如流水,一气呵成。

6.3 函数设计的要求

  1. 可复用性:任何一个提供服务的模块都有可能在其他程序中复用。由于通常很难预测模块的未来使用,因此最好将模块设计成可复用的。
  2. 可维护性(最重要):将程序模块化后,程序中的错误通常只影响一个模块的实现。因此更容易找到并修正错误,在修正错误之后,重建程序只需要重新编译模块链接程序实现。更广泛的说,为了提高性能或将程序移植到另一个平台上,我们甚至可以替换整个模块的实现。
  3. 可读性:设计好程序逻辑。函数名、变量名要见名知意,参数设计不要过多,必要时写注释或文档。
  4. 健壮性:检查参数合法性、输入值的合法性,检查函数的返回值(明确返回值的意思)。
  5. 高内聚性:模块中的元素应该彼此紧密相关,我们可以认为它们是为了同一目标相互合作的,高内聚性会使模块更易于使用。
  6. 低耦合性:模块之间应该相互独立,低耦合性可以使程序更便于修改,并方便模块复用。

6.4 C语言函数接口

函数的调用者和其实现者之间存在一个协议,在函数调用之前,调用者要为实现者提供某些条件,在函数返回时,实现者完成调用者需要的功能。
函数接口通过函数名、参数和返回值来描述这个协议,只要函数名和参数名命名合理,参数和返回值的类型定义的准确,调用者仅通过函数接口就知道函数的用法,当函数接口不能表达全部语义时,文档就起了补充作用。

. 低耦合性:模块之间应该相互独立,低耦合性可以使程序更便于修改,并方便模块复用。

6.4 C语言函数接口

函数的调用者和其实现者之间存在一个协议,在函数调用之前,调用者要为实现者提供某些条件,在函数返回时,实现者完成调用者需要的功能。
函数接口通过函数名、参数和返回值来描述这个协议,只要函数名和参数名命名合理,参数和返回值的类型定义的准确,调用者仅通过函数接口就知道函数的用法,当函数接口不能表达全部语义时,文档就起了补充作用。

第六部分属于软件工程的内容,这部分内容有助于我们规范实现模块化设计,编写出高效率、简洁易读的代码。

数据集概述 本数据集用于情感分析,主要针对Yelp评论,通过比较两种先进的模型——Hugging Face的bert-base-multilingual-uncased和cardiffnlp/twitter-roberta-base-sentiment-latest来分析评论中的情感表达。 模型使用 BERT Multilingual Uncased: 适用于理解多种语言,特别适合处理Yelp评论中多样化的语言特性。 Twitter RoBERTa: 专门针对情感分析进行微调,擅长理解英语情感的细微差别。 构建方式 Yelp Reviews Dataset的构建基于Yelp平台上用户提交的评论数据。该数据集通过爬虫技术从Yelp网站上抓取,涵盖了多个国家和地区的餐厅、服务和商品的评论。数据收集过程中,确保了评论的完整性和真实性,同时对文本进行了预处理,包括去除HTML标签、特殊字符和停用词,以保证数据的质量和可用性。 特点 Yelp Reviews Dataset的特点在于其广泛的地理覆盖和多样化的评论内容。数据集包含了数百万条评论,涵盖了从星级评价到详细文本反馈的多种信息形式。此外,该数据集还提供了用户、商家和评论之间的关联信息,使得研究者可以进行多维度的分析。评论的情感倾向和语言风格也为自然语言处理和情感分析提供了丰富的素材。 使用方法 Yelp Reviews Dataset可用于多种研究目的,包括但不限于情感分析、用户行为研究、推荐系统构建和市场分析。研究者可以通过分析评论文本,提取用户的情感倾向和偏好,进而优化推荐算法或改进服务质量。此外,该数据集还可用于训练和验证自然语言处理模型,如情感分类器和文本生成模型。使用时,建议根据具体研究需求选择合适的子集和特征进行分析。 背景与挑战 背景概述 Yelp Reviews Dataset,作为在线评论平台Yelp的核心
本项目旨在开发一个基于Python的卷积神经网络(CNN)人脸识别系统,用于检测驾驶员的疲劳状态并及时发出预警。该系统主要通过分析驾驶员的面部特征,如打哈欠、眨眼和点头等行为,来判断驾驶员是否处于疲劳状态,从而提高驾驶安全性。 开发环境 IDE: PyCharm 编程语言: Python 3.6 算法: 卷积神经网络(CNN) 系统功能 本系统主要分为三个部分: 打哈欠检测:通过检测驾驶员的嘴巴张合程度来判断是否打哈欠。 眨眼检测:通过分析驾驶员的眼睛开合度和眨眼频率来判断是否疲劳。 点头检测:通过检测驾驶员的头部姿态变化来判断是否疲劳。 疲劳检测原理 人在疲倦时通常会出现以下两种状态: 眨眼:正常情况下,人的眼睛每分钟大约会眨动10-15次,每次眨眼大约0.2-0.4秒。当人疲劳时,眨眼次数会增加,速度也会变慢。 打哈欠:疲劳时,人的嘴巴会张大并保持一定状态。 因此,通过检测眼睛的开合度、眨眼频率以及嘴巴的张合程度,可以判断一个人是否处于疲劳状态。 检测工具 本项目使用dlib库进行人脸检测和关键点定位。shape_predictor_68_face_landmarks.dat是一个用于人脸68个关键点检测的模型库,能够方便地进行人脸检测和应用。 眨眼计算原理 计算眼睛的宽高比(Eye Aspect Ratio, EAR)是判断眨眼状态的关键。当人眼睁开时,EAR值较大;当人眼闭合时,EAR值较小。通过实时计算EAR值的变化,可以判断驾驶员是否在眨眼。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值