函数模块化:企业级项目高效之道

函数与模块化编程

学习目标

01 函数的定义

02 函数的调用(先声明后使用)

03 函数的参数传递

04 变量类型和存储方式

05 物流管理系统输入封装案例

PART.01 函数的定义

函数

  • 独立的程序段
  • 执行一个具体的、明确定义的任务
  • 易于编写、理解、调试和维护
  • 将需要多次执行的一组指令定义为函数

函数的定义结构

c 复制代码
type_specifier function_name ( arguments ) {
    执行部分
}
  • type_specifier:返回值的数据类型
  • function_name:有效的函数名
  • arguments:形式参数

函数的参数

函数的参数分为形参实参两种,作用是实现数据传送。

  • 形参出现在函数定义中,只能在该函数体内使用。发生函数调用时,调用函数把实参的值复制1份,传送给被调用函数的形参,从而实现调用函数向被调用函数的数据传送。

其它说明:

  1. 实参可以是常量、变量、表达式、函数等。
  2. 形参变量只有在被调用时,才分配内存单元;调用结束时,即刻释放所分配的内存单元。
  3. 实参和形参占用不同的内存单元,即使同名互不影响。
  4. 在被定义的函数中,必须指定形参的类型。
  5. 实参与形参的类型应相同或赋值兼容。
形参和实参示例代码
c 复制代码
int squarer(int x);
void main(){
    //实际参数
    int i;
    for(i =1; i <=5; i++)
        printf(" %d * %d = %d\n ", i,squarer (i));
}
int squarer(int x)
{
    //形式参数
    int iResult = 0;
    iResult = x * x;
    return iResult;
}

实参向形参传值示意图:

注意:实参值a为3.5,而形参x为整型,则将实数3.5转换成整数3,然后送到形参b。字符型与整型可以互相通用。

函数的返回值

通常,希望通过函数调用使主调函数能得到一个确定的值,这就是函数的返回值。例如,max(2,3)的值是3。

  • 将控制权从函数传回主调程序
  • 把 return 语句中的内容返回给主调程序

示例:

c 复制代码
int squarer( int x )
{
    int iResult = 0;
    iResult = x * x;
    return iResult;
}

函数的数据类型

  • 在定义函数时,对函数类型的说明,应与return语句中、返回值表达式的类型一致。
  • 如果返回的是一个整型值或者没有返回值,可以省略类型标识符。
  • 为了避免出现不一致,最好指定数据类型。

良好的程序设计习惯:为了使程序具有良好的可读性并减少出错,凡不要求返回值的函数都应定义为空类型;即使函数类型为整型,也不使用系统的缺省处理。

函数返回值说明

  1. 函数的返回值是通过函数中的return语句获得的。
    一个函数中可以有一个以上的return语句,执行到哪一个return语句,哪一个语句起作用。return语句后面的括弧也可以不要,如:return z; 等价于 return (z);
  2. 函数的返回值应当属于某一个确定的类型,在定义函数时指定函数返回值的类型。在C语言中,凡不加类型说明的函数,自动按整型处理。
  3. 在定义函数时指定的函数类型一般应该和return语句中的表达式类型匹配。 如果函数值的类型和return语句中表达式的值不一致,则以函数类型为准。对数值型数据,可以自动进行类型转换。
  4. 如果被调用函数中没有return,返回一个不确定值。对于不带回值的函数,应当用void定义函数为"无类型"(或称"空类型")。此时在函数体中不得出现return语句。

PART.02 函数的调用

函数原型和函数声明

在定义之前使用函数,必须声明,否则,一些编译器会返回错误。

采用函数原型方式,对被调用函数进行说明,其一般格式如下:

复制代码
函数类型 函数名(数据类型[ 参数名][, 数据类型[ 参数名2]...]);

错误示例(调用在前,无声明):

c 复制代码
#include <stdio.h>
void main(){
    swap();
}
void swap()
{ }
//错误:标示符未声明

正确示例(提前函数声明):

c 复制代码
#include <stdio.h>
void swap();
void main(){
    swap();//编译通过
}
void swap()
{}

函数调用

  • 函数调用的一般形式为:函数名([实际参数表]);
  • 调用函数时,函数名称必须与具有该功能的自定义函数名称完全一致。
  • 实参在个数、类型和顺序与被调用函数的形参,必须一一对应和匹配。 如果类型不匹配,C编译程序将按赋值兼容的规则进行转换。

函数调用条件

一个函数中调用另一函数(即被调用函数)需要具备哪些条件:

  1. 首先被调用的函数必须是已经存在的函数(是库函数或用户自己定义的函数)。
  2. 如果使用库函数,还应该在本文件开头用#include命令将调用有关库函数时所需用到的信息"包含"到本文件中来。
  3. 如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面(在同一个文件中),应该在主调函数中对被调用的函数作声明。

文件包含

  • 文件包含:是指一个源文件可以将另外一个源文件的全部内容包含进来。
  • 作用:使编译系统把指定的被包含文件嵌入到带有#include的源文件中。

文件包含示意图:

定义和声明

注意:函数的"定义"和"声明"不是一回事。

函数的定义 是指对函数功能的确立,包括指定函数名,函数值类型、形参及其类型、函数体等,它是一个完整的、独立的函数单位。

函数的声明的作用则是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时,编译系统能正确识别函数并检查调用是否合法。(例如函数名是否正确,实参与形参的类型和个数是否一致)。

注:系统不会检查参数名。

函数调用流程:

函数调用的嵌套

函数的嵌套调用是指,在执行被调用函数时,被调用函数又调用了其它函数。

c 复制代码
f2(){...}
f1(){
    f2();
    ...//后续语句
}
main()
{
    f1();
    ...//后续语句
}

函数的递归调用

一个函数在它的函数体内调用它自身称为递归调用。这种函数称为递归函数。C语言允许函数的递归调用。在递归调用中,主调函数又是被调函数。

  • 直接递归
  • 间接递归

为了防止递归调用无终止地进行,必须在函数内有终止递归调用的手段。常用的办法是加条件判断,满足某种条件后就不再作递归调用,然后逐层返回。

直接递归:

间接递归:

利用递归求阶乘
c 复制代码
#include <stdio.h>
int factorial( int iNum);
int main() {
    int n=10;
    printf("i=%d factorial: %d\n",n, factorial(n));//计算10的阶乘
    return 0;
}
int factorial( int iNum)
{
    if ( iNum <= 1 )
        return 1;
    else
        return (iNum*factorial(iNum-1));
}

PART.03 函数的参数传递

调用一个函数时,调用函数和被调用函数之间会发生数据传递,有两种数据传递方式

  1. 按值传递
  2. 按地址传递

按值传递

  • 变量的值被传递给被调函数,并且对这个值的任何改变都不会影响被传变量的原始值
  • 默认情况下,函数参数都是按值传递的
  • 参数值是通过临时变量传递的
  • 所有操作都针对这些临时变量

数组作为函数参数

  1. 一维数组元素作函数实参
    数组元素作为函数的实参,与用变量作实参一样,是单向传递,即"值传送"方式。
c 复制代码
void swap(int x,int y);
swap(a[0],a[1]);
  1. 一维数组作为参数例子
c 复制代码
void f(int aiNO[],int nNum)
{
    aiNO[0] = 1;
    aiNO[1] = 2;
    nNum = 3;
}
void main()
{
    int aiNO[10]={0},nNumber=5;
    f(aiNO, nNumber);
    printf("%d,%d,%d", aiNO[0], aiNO[1], nNumber);
}
数组作为函数形参注意点
  1. 实参数组名代表数组元素的首地址。而形参是用来接收从实参传递过来的数组首元素地址。
  2. 在被调用函数中声明形参数组的大小为10,但在实际上,指定大小是不起作用的,C编译器对形参数组大小不做检查,只是将实参数组的首地址传给形参数组。

PART.04 数据传递方式和内存存储区域

数据传递的几种方式

主调函数与被调函数进行数据传递的方式有以下几种:

  1. 参数传递
  2. 返回值
  3. 全局变量
    • 使用全局变量不符合程序设计中要求模块间"高内聚性、低藕合性"的原则,使这些函数依赖这些全局变量,因而使得这些函数的独立性降低。
    • 使用全局变量过多,会降低程序的可读性和可维护性。

局部变量和全局变量

  • 局部变量
    • 在函数内部声明
    • 进入一个程序块时创建,退出该程序块时销毁
  • 形式参数
    • 在函数定义中被声明为参数
    • 在函数内部像局部变量一样使用
  • 全局变量(extern)
    • 在所有函数以外声明
    • 在程序的整个执行期间都拥有值

静态存储和动态存储

变量从变量值存在的时间(即生存期)角度分:静态存储方式和动态存储方式。

  • 静态:由系统分配固定的存储空间,全局变量就存储在静态存储区。
  • 动态:系统根据需要进行动态的分配存储空间。

在动态存储区存放以下数据:

  1. 函数的形式参数
  2. 自动变量(未加static声明)
  3. 函数调用时的现场保护和返回地址

用户区内存划分:程序区、静态存储区、动态存储区。

存储类别

内存中的存储区域包括下面几个部分:

  1. 程序代码区:存放函数体的二进制代码
  2. 静态区/全局区(static):全局变量和静态变量的存储区域
  3. 堆区(heap):程序员分配释放。
  4. 栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值

存储类别是数据在内存中的存储形式,是变量的属性,使用的一般语法:

复制代码
存储类别 数据类型 变量名称;

示例:

c 复制代码
auto int a;    //自动变量
static int b;  //静态
register int i;//寄存器变量
extern int A;  //引入外部变量

存储类别定义了:

  • 生命周期:保留特定值的时间长度
  • 可见性:程序的哪些部分将能够"看见"该变量
存储类别介绍
  1. 自动的(auto):auto可以省略。
  2. 外部的(extern)
c 复制代码
int A=10;
// 在另一个c文件使用extern int A; //引入外部变量
  1. 静态的(static)
    在静态存储区分配存储单元,在编译时赋初值,而且只赋初值一次。如果没有赋初值,系统就会自动赋0或空字符。
  2. 寄存器的(register)
    • 对寄存器存取远高于对内存的存取
    • 只有局部自动变量和形式参数可以作为寄存器变量
    • 寄存器数目是有限的
静态的局部变量
  • static局部变量的作用域与auto变量相同
  • static局部变量的生存期与全局变量相同

示例代码:

c 复制代码
#include <stdio.h>
void Add(){
    static int i=10; //初始化工作在编译时进行,且只进行一次
    i++;
    printf("i=%d\n",i);
}
void main() {
    Add();
    Add();
}

PART.05 物流管理系统界面和输入封装

需求

物流管理系统登录界面

c 复制代码
用户名:admin
密码:***

物流管理系统主界面

复制代码
1.添加货物
2.查询货物
3.修改货物
5.创建订单
6.跟踪订单
7.更新订单状态
8.查看所有订单
0.退出系统
请选择:

输入控制函数封装

c 复制代码
/*************************
函数名:input
函数功能:输入控制函数
参数:
maxlen表示: 允许最大输入字符数
type表示: 输入的数据类型:
    0 (表示无控制,任意都可以)
    1(只能输入数字)
    2(只能输入字母)
    3 (只能输入数字和字母)
mode表示:显示明文还是密文
    1表示明文,0表示密文
str表示:保存输入数据的字符串
返回值:无
*******************/
int input(char str[], int maxLen, int type, int mode);

输入函数流程图:

物流管理系统 C 语言代码汇总

运行环境:Windows,依赖 windows.hconio.h,仅可在 Windows 下编译运行。

main.c(主程序源文件)

说明:程序入口文件,定义全局用户数组,循环调用登录函数,登录成功后进入管理员菜单界面。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <windows.h> 
#include <string.h>
#include <conio.h> 
#include "view.h"
#include "login.h"
#include "app.h"
int g_count=0;
char userName[5][10]={"admin","lili"};
char userPasswd[5][10]={"111","222"};
int count=2;
int main(int argc, char *argv[]) {
	int r=0;
	
	while(!r){
		system("cls");
		r=login();
	}
	adminVeiw();
	return 0;
}
view.h(视图头文件)

说明:视图模块头文件,声明绘制窗口、光标定位、输入控制函数。

c 复制代码
//# 宏定义作用:1. 符号常量 2. 代码某些简单的函数 3. 写在头文件预防重复定义
#ifndef _VIEW_H_
#define _VIEW_H_
/*
函数名:drawWin
函数功能:绘制窗口
参数:row-行,col-列
返回值:无
*/
//绘制窗口函数
void drawWin(int row,int col);
//光标定位函数 
void gotoxy(int x, int y);
/*************************
  函数名:input
  函数功能:输入控制函数
  参数:
  maxlen表示: 允许最大输入字符数
  type表示: 输入的数据类型:
  0 (表示无控制,任意都可以)
  1(只能输入数字)
  2(只能输入字母)
  3 (只能输入数字和字母)
  mode表示:显示明文还是密文
  1表示明文,0表示密文
  str表示:保存输入数据的字符串
  返回值:无
 *******************/
int input(char str[], int maxLen, int type, int mode);
#endif
view.c(视图源文件)

说明:视图功能实现文件,包含光标定位、绘制边框窗口、带校验+密文的键盘输入函数。

c 复制代码
#include <stdio.h>
#include <windows.h> 
#include <conio.h>
extern int g_count;
//光标定位函数 
void gotoxy(int x, int y) {
	COORD coord;
	coord.X = x;
	coord.Y = y; 
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord); 
}
/*
函数名:drawWin
函数功能:绘制窗口
参数:row-行,col-列
返回值:无
*/
//绘制窗口函数
void drawWin(int row,int col){
	system("cls");
	//画首行
	int i=0,j=0;
	printf("+");
	for(j=0;j<col;j++){
		printf("--");
	}
	printf("+\n");
	
	//画中间行
	for(i=0;i<row;i++){
		printf("|");
		for(j=0;j<col;j++){
			printf("  ");
		}
		printf("|\n");		
	}
	
	//画尾行
	printf("+");
	for(j=0;j<col;j++){
		printf("--");
	}
	printf("+\n");
}
/*************************
  函数名:input
  函数功能:输入控制函数
  参数:
  maxlen表示: 允许最大输入字符数
  type表示: 输入的数据类型:
  0 (表示无控制,任意都可以)
  1(只能输入数字)
  2(只能输入字母)
  3 (只能输入数字和字母)
  mode表示:显示明文还是密文
  1表示明文,0表示密文
  str表示:保存输入数据的字符串
  返回值:无
 *******************/
void input(char str[], int maxLen, int type, int mode){
	char ch=0;
	while(1){
		ch=getch();
		if(ch=='\r'){//回车
			break;
		}
		else if(ch>='0' && ch<='9'){//数字
			if(strlen(str)<maxLen){
				if(type==0 || type==1 || type==3){
					if(mode==1){//明文
						putchar(ch);
					}else if(mode==0){//密文
						putchar('*');
					}
					str[strlen(str)]=ch;
				}
			}
		}
		else if((ch>='a' && ch<='z')||(ch>='A' && ch<='Z')){//英文
			if(strlen(str)<maxLen){
				if(type==0 || type==1 || type==3){
					if(mode==1){//明文
						putchar(ch);
					}else if(mode==0){//密文
						putchar('*');
					}
					str[strlen(str)]=ch;
				}
			}
		}
		else if(ch=='\b'){//回删
			if(strlen(str)>0){
				printf("\b \b");
				str[strlen(str)-1]='\0';
			}
		}
		else{
			if(strlen(str)<maxLen){
				if(type==0){
					if(mode==1){//明文
						putchar(ch);
					}else if(mode==0){//密文
						putchar('*');
					}
					str[strlen(str)]=ch;
				}
			}
		}
	}
}
login.h(登录模块头文件)

说明:登录模块头文件,声明登录函数 login()

c 复制代码
#ifndef _LOGIN_H_
#define _LOGIN_H_
/*
函数名:login
函数功能:登录
参数:无
返回值:1-成功 0-失败
*/
int login();
#endif
login.c(登录模块源文件)

说明:登录逻辑实现,绘制登录界面,获取用户名密码,校验全局用户名密码数组,返回登录结果。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <windows.h> 
#include <string.h>
#include <conio.h> 
#include "view.h"
#include "global.h"
/*
函数名:login
函数功能:登录
参数:无
返回值:1-成功 0-失败
*/
int login(){
	char name[10]={0};
	char passwd[10]={0};
	int i=0;
	
	//1. 绘制窗口
	drawWin(12,15);
	
	//2. 显示窗口元素
	gotoxy(5,3);
	printf("物流管理系统管理界面");
	gotoxy(3,5);
	printf("用户名:");
	gotoxy(3,7);
	printf("密码:");
	
	//3. 光标定位用户名位置,让用户输入用户名,回车	
	gotoxy(12,5);
	input(name,8,3,1);
//	puts(name);
	
	//4. 光标定位密码输入的位置,让用户输入密码,密码显示*,按下回车
	gotoxy(12,7);
	input(passwd,8,3,0);
	
	
	//5. 登录校验
	for(int i=0;i<count;i++){
		if(strcmp(name,userName[i])==0){
			//判断密码
			if(strcmp(passwd,userPasswd[i])==0){
				gotoxy(10,9);
				printf("登录成功!\n");
				Sleep(1500);
				return 1;
			}
			else{
				gotoxy(10,9);
				printf("密码错误!\n");
				Sleep(1500);
				return 0;
			}
		}
	}
	if(i==count){
		gotoxy(10,9);
		printf("用户名错了!");
		Sleep(1500);
		return 0;
	}
	return 0;
}
app.h(管理员菜单头文件)

说明:管理员功能头文件,声明管理员主菜单函数 adminVeiw()

c 复制代码
#ifndef _APP_H_
#define _APP_H_
void adminVeiw();
#endif
app.c(管理员菜单源文件)

说明:管理员主菜单实现,绘制功能选择界面,打印货物、订单相关菜单选项,接收用户选择输入。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <windows.h> 
#include <string.h>
#include <conio.h> 
#include "view.h"
void adminVeiw(){
	char choose[2]={0};
	//1. 绘制窗口
	drawWin(15,15);
	
	//2. 显示窗口元素
	gotoxy(3,5);
	printf("1.添加货物");
	
	gotoxy(3,6);
	printf("2.查询货物");
	
	gotoxy(3,7);
	printf("3.修改货物");
	
	gotoxy(3,8);
	printf("5.创建订单");
	
	gotoxy(3,9);
	printf("6.跟踪订单");
	
	gotoxy(3,10);
	printf("7.更新订单状态");
	
	gotoxy(3,11);
	printf("8.查看所有订单");
	
	gotoxy(3,12);
	printf("0.退出系统");
	
	gotoxy(3,14);
	printf("请选择:");
	
	input(choose,1,1,1);
}
global.h(全局变量头文件)

说明:全局变量头文件,对 main.c 中定义的用户名、密码、用户数量做 extern 外部声明,供 login.c 使用。

c 复制代码
#ifndef _GLOBAL_H_
#define _GLOBAL_H_
extern char userName[5][10];
extern char userPasswd[5][10];
extern int count;
#endif
相关推荐
一只小阿乐11 分钟前
java 语法学习 1
java·开发语言·学习
QQ142207844911 分钟前
知识库管理和工作学习系统--guada_ai
学习
码匠许师傅16 分钟前
【设计模式精讲】13.装饰器模式(Decorator)
c++·设计模式·uml·装饰器模式
2601_9620739717 分钟前
苍穹外卖-day07(Spring Cache & 购物车业务逻辑)
java·后端·spring
夕除30 分钟前
redis--010
笔记·分布式·学习
andrsted32 分钟前
在线刷小程序推荐
学习·微信小程序·小程序·学习方法
weixin_4462608544 分钟前
基于熵的选择性智能体引导:从非完美视觉语言模型教师学习自主策略
人工智能·学习·语言模型
Sincerelyplz1 小时前
【Pipecat】基于Pipecat的voice agent实践
前端·后端·agent
OPEN-F1 小时前
C++综合实战:面向对象图书管理系统
开发语言·c++