精华内容
下载资源
问答
  • C语言模块化设计控温器的实现 版本记录表 设计思路 设计一个控温器模块由加热器、制冷器、算法器三个子模块构成。控温器负责三个模块的协同工作以及与外界交换信息(设定值、实时值、控制参数等),并根据这些数据去...

    C语言模块化设计控温器的实现

    版本记录表

    设计思路

    设计一个控温器模块由加热器、制冷器、算法器三个子模块构成。控温器负责三个模块的协同工作以及与外界交换信息(设定值、实时值、控制参数等),并根据这些数据去配置或者设定三个子模块。算法器负责根据设定值、实时值的偏差计算一个输出量,并将这个输入量分别转化为加热器与制冷器的工作功率。制冷器与加热器根据算法器计算的结果,调整自己的功率输出。通过OOP的设计方法,将控温器作为一个整体封装起来,使之成为一个独立的部分(目前算法器中的部分参数仍然直接依赖外部全局变量完成初始化)。在不同的项目应用中,需要为模块编写不同的实现函数,然后通过动态或者静态的方式注册给它们,而不必考虑它们内部如何工作的、子模块之间是如何协调的。

    控温器的接口设计(外部接口)

    • void (*Init_FP)(struct _TemperController_TypeDef *this): 初始化控温器。完成控温器的内部变量的初始化以及它的三个子模块的初始化。
    • void (*SetEn_FP)(struct _TemperController_TypeDef *this, uint8_t state): 打开或关闭控温器。设置控温器是否处于运行状态,同时设置它的三个子模块的运行状态。
    • uint8_t (*GetErr_FP)(struct _TemperController_TypeDef *this): 获取控温器错误信息。通过获取三个子模块的状态以及对控制效果的判断来设置一个错误码。
    • void (*SetPV_FP)(struct _TemperController_TypeDef *this, float pv): 设置控温器的实时值。一般会在某个任务中或者定时器中断服务函数中周期性的调用。
    • void (*SetSV_FP)(struct _TemperController_TypeDef *this, float sv): 设置控温器的设定值。一般是检测到SV数值发生改变时才会调用此函数。
    • void (*Exec_FP)(struct _TemperController_TypeDef *this): 执行一次控温动作。一般会在某个任务中或者定时器中断服务函数中周期性的调用。

    执行器的接口设计(内部接口)

    加热器与制冷器都继承于执行器,通过为同样的接口注册不同的回调函数,构建了两个不同的模块,这有点类似C++中的多态特性。同时需要注意的是,执行器输入子模块,为了维持控温器的封装性,应该尽量避免在外部直接调用这些接口,同时也应该尽量避免在内部调用外部的变量与函数

    • void (*Init_FP)(struct _Actuator_Typedef *this): 初始化执行器。
    • void (*SetEn_FP)(struct _Actuator_Typedef *this, uint8_t state): 打开或关闭执行器。
    • void (*SetPower_FP)(struct _Actuator_Typedef *this, float power): 设置执行器的工作功率。需要注意的是加热器多数为电流通过电阻丝发热的原理,一般支持功率可调输出,而制冷器中的半导体制冷片也是支持功率可调输出,但是压缩机形式的制冷器只存在开关两个状态(0% 或 100%)。这个差异性在实现不同模块的回调函数的时候应当予以注意。

    算法器的接口设计(内部接口)

    算法器是控温器的核心模块,它有着较多的参数设定与较为复杂的计算流程。控温器的输入输出主要都是与算法器交互的,然后通过算法器的计算结果去设置执行器的工作状态。

    • void (*Init_FP)(struct _TemperController_TypeDef *this): 初始化算法器。
    • void (*SetEn_FP)(struct _TemperController_TypeDef *this, uint8_t state): 打开或关闭算法器。
    • void (*SetPID_FP)(struct _Algorithm_TypeDef *this, float p, float i, float d): 设置算法器的PID参数。值得注意的是,算法器中还有一个PID类型的结构体,它里面也有一系列的变量与函数,设置参数主要通过调用PID中提供的修改函数实现的。同时,由于PID的参数众多,如果全部通过接口的方式完全将算法器封装起来,会造成性能的下降。因此在算法器初始化的时候,直接调用了部分全局变量完成了PID参数的初始化,这在一定程度破坏了封装性,也是对性能的妥协
    • void (*Update_FP)(struct _Algorithm_TypeDef *this, float sv, float pv): 执行一次算法器的计算。
    • float (*GetOutput_P_FP)(struct _Algorithm_TypeDef *this): 获取算法器的加热器功率输出结果。
    • float (*GetOutput_N_FP)(struct _Algorithm_TypeDef *this): 获取算法器的制冷器功率输出结果。

    整体架构

    图中是整个控温器的架构,从OOP的角度可以理解为:控温器类继承于制冷器、加热器、算法器三个父类,制冷器与加热器继承于执行器,算法器继承于PID类。为“虚函数”注册不同函数的过程,就是在构造不同的类,而声明并定义类型数组或者在堆上动态创建类型变量的过程就等同于同一个类实例化不同对象的过程

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xlET0IgK-1605345640260)(C%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9D%97%E5%8C%96%E8%AE%BE%E8%AE%A1%E6%8E%A7%E6%B8%A9%E5%99%A8%E7%9A%84%E5%AE%9E%E7%8E%B0%2024ee15119c054e72a84c105c39c5ee6c/Untitled.png)]

    源码实现

    头文件

    /*
     * heater.h
     *
     *  Created on: Jul 3, 2019
     *      Author: tao
     */
    
    #ifndef HARDWARE_INC_HEATER_H_
    #define HARDWARE_INC_HEATER_H_
    
    #include "stm32f10x_conf.h"
    #include "stm32f10x.h"
    
    #ifdef HEATER
    
    /**
     * 执行器的类型定义
     */
    typedef struct _Actuator_Typedef
    {
    	uint8_t State;							//0: disabled, 1: enabled
    	uint8_t IsRun;							//0: stop, 1: run
    	float Pow;								//Current power of actuator
    	void (*Init_FP)(struct _Actuator_Typedef *this);								//初始化执行器
    	void (*SetEn_FP)(struct _Actuator_Typedef *this, uint8_t state);			//0: close actuator, 1: open actuator
    	void (*SetPower_FP)(struct _Actuator_Typedef *this, float power);		//output power of actuator
    }Actuator_Typedef;
    
    /**
     * 算法器的类型定义
     */
    typedef struct _Algorithm_TypeDef
    {
    	uint8_t State;							//0: disabled, 1: enabled
    	uint8_t IsRun;
    	PID_TypeDef PidData;			//控制算法数据结构体指针
    
    	void (*Init_FP)(struct _Algorithm_TypeDef *this);													//初始化算法器
    	void (*SetEn_FP)(struct _Algorithm_TypeDef *this, uint8_t state);							//0: close algorithm, 1: open algorithm
    	void (*SetPID_FP)(struct _Algorithm_TypeDef *this, float p,  float i, float d);		//0: close algorithm, 1: open algorithm
    	void (*Update_FP)(struct _Algorithm_TypeDef *this, float sv, float pv);
    	float (*GetOutput_P_FP)(struct _Algorithm_TypeDef *this);
    	float (*GetOutput_N_FP)(struct _Algorithm_TypeDef *this);
    }Algorithm_TypeDef;
    
    typedef struct _TemperController_TypeDef
    {
    	uint8_t State;							//0: disabled, 1: enabled
    	uint8_t IsRun;
    	uint8_t Error;
    
    	float SV;
    	float PV;
    
    	Actuator_Typedef *Heater;
    	Actuator_Typedef *Cooler;
    	Algorithm_TypeDef *Algorithm;
    
    	void (*Init_FP)(struct _TemperController_TypeDef *this);										//初始化控温器
    	void (*SetEn_FP)(struct _TemperController_TypeDef *this, uint8_t state);				//0: close TC, 1: open TC
    	uint8_t (*GetErr_FP)(struct _TemperController_TypeDef *this);
    
    	void (*SetPV_FP)(struct _TemperController_TypeDef *this, float pv);
    	void (*SetSV_FP)(struct _TemperController_TypeDef *this, float sv);
    	void (*Exec_FP)(struct _TemperController_TypeDef *this);
    
    //	Void_Void_FP Init;					//重置温度控制器
    //	Void_U8_FP SetEn;					//0: 关闭控温, 1: 打开控温
    //	Void_Void_FP Exec;				//执行控温程序
    }TemperController_TypeDef;
    
    #define  TEMPER_CONTROLLER_NUM			1
    #define  HEATER_NUM									TEMPER_CONTROLLER_NUM
    #define  COOLER_NUM								TEMPER_CONTROLLER_NUM
    #define  CONTROLLER_NUM						TEMPER_CONTROLLER_NUM
    
    extern TemperController_TypeDef TemperController[TEMPER_CONTROLLER_NUM];
    
    #endif
    #endif /* HARDWARE_INC_HEATER_H_ */
    

    源文件

    /*
     * heater.c
     *
     *  Created on: Jul 3, 2019
     *      Author: tao
     */
    
    #include "tc.h"
    
    #include "user.h"
    #include "mb_user.h"
    #include "pid.h"
    #include "virtual_pwm.h"
    
    #ifdef HEATER
    
    static void Heater_Init(Actuator_Typedef *this);
    static void Heater_SetEn(Actuator_Typedef *this, uint8_t cmd);
    static void Heater_SetPower(Actuator_Typedef *this, float power);
    
    static void Cooler_Init(Actuator_Typedef *this);
    static void Cooler_SetEn(Actuator_Typedef *this, uint8_t cmd);
    static void Cooler_SetPower(Actuator_Typedef *this, float power);
    
    static void Algorithm_Init(Algorithm_TypeDef *this);
    static void Algorithm_SetEn(Algorithm_TypeDef *this, uint8_t cmd);
    static void Algorithm_SetPID(Algorithm_TypeDef *this, float p, float i, float d);
    static void Algorithm_Update(Algorithm_TypeDef *this, float sv, float pv);
    static float Algorithm_GetOutput_P(Algorithm_TypeDef *this);
    static float Algorithm_GetOutput_N(Algorithm_TypeDef *this);
    
    static void TemperController_Init(TemperController_TypeDef *this);
    static void TemperController_SetEn(TemperController_TypeDef *this, uint8_t cmd);
    static uint8_t TemperController_GetErr(TemperController_TypeDef *this);
    static void TemperController_SetPV(TemperController_TypeDef *this, float pv);
    static void TemperController_SetSV(TemperController_TypeDef *this, float sv);
    static void TemperController_Exec(TemperController_TypeDef *this);
    
    //声明加热器、制冷器、控制器
    static Actuator_Typedef HeaterArray[TEMPER_CONTROLLER_NUM] =
    {
    		{
    				.State = 1,
    				.IsRun = 0,
    				.Pow = 0,
    
    				.Init_FP = Heater_Init,
    				.SetEn_FP = Heater_SetEn,
    				.SetPower_FP = Heater_SetPower,
    		},
    };
    
    static Actuator_Typedef CoolerArray[TEMPER_CONTROLLER_NUM] =
    {
    		{
    				.State = 1,
    				.IsRun = 0,
    				.Pow = 0,
    
    				.Init_FP = Cooler_Init,
    				.SetEn_FP = Cooler_SetEn,
    				.SetPower_FP = Cooler_SetPower
    		},
    };
    
    static Algorithm_TypeDef AlgorithmArray[TEMPER_CONTROLLER_NUM] =
    {
    		{
    				.State = 1,
    				.IsRun = 0,
    				//				.SV = 30,
    				//				.PV = 0,
    
    				.Init_FP = Algorithm_Init,
    				.SetEn_FP = Algorithm_SetEn,
    				.SetPID_FP = Algorithm_SetPID,
    				.Update_FP = Algorithm_Update,
    				.GetOutput_P_FP = Algorithm_GetOutput_P,
    				.GetOutput_N_FP = Algorithm_GetOutput_N,
    		}
    };
    
    //声明温控器
    TemperController_TypeDef TemperController[TEMPER_CONTROLLER_NUM] =
    {
    		{
    				.State = 1,
    				.IsRun = 0,
    				.Error = 0,
    
    				.SV = 30,
    				.PV = 0,
    
    				//全局变量是保存在静态存储区的,因此在编译的时候只能用常量进行初始化
    				//.Heater = HeaterArray[0],
    				//.Cooler = CoolerArray[0],
    				//.Algorithm = AlgorithmArray[0],
    
    				//注册函数指针
    				.Init_FP = TemperController_Init,
    				.SetEn_FP = TemperController_SetEn,				//启动、关闭控温器的函数
    				.GetErr_FP = TemperController_GetErr,
    				.SetPV_FP = TemperController_SetPV,
    				.SetSV_FP = TemperController_SetSV,
    				.Exec_FP = TemperController_Exec,					//周期性调用此函数,完成一次控温调整
    		},
    };
    
    static void Heater_Init(Actuator_Typedef *this)
    {
    	this->State = 1;
    	this->IsRun = 0;
    	this->Pow = 0;
    
    	//打开虚拟PWM通道输出,并将占空比设置为0
    	VirPwm_Init(&VirPwmDefArray[0], 10, 0, 0);
    	VirPwm_SetStatus(&VirPwmDefArray[0], 1);
    	//关闭加热器开关
    	*HEATER_EN_ADDR = 0;
    	//关闭加热器风扇
    	*HTFAN_EN_ADDR = 0;
    }
    
    static void Heater_SetEn(Actuator_Typedef *this, uint8_t cmd)
    {
    	if(cmd != 0)
    	{
    		this->IsRun = 1;
    		//打开加热器开关
    		*HEATER_EN_ADDR = 1;
    	}
    	else
    	{
    		this->Init_FP(this);
    	}
    }
    
    static void Heater_SetPower(Actuator_Typedef *this, float power)
    {
    	//占空比设为0,即关闭该通道的PWM
    	//power的范围是0~1,由算法器提供
    	///需要注意的是power转化为对应的PWM的占空比时,要乘以一个与占空比精度相关的数,
    	/// 一般设置占空比精度为2,dutyCycle = power x 100;
    
    	this->Pow = power;
    	VirPwm_SetDutyCycle(&VirPwmDefArray[0], (uint16_t)(power*100));
    
    	//加热器功率不为0时,开启加热器风扇
    	if(power != 0)
    	{
    		*HTFAN_EN_ADDR = 1;
    	}
    	else
    	{
    		*HTFAN_EN_ADDR = 0;
    	}
    }
    
    static void Cooler_Init(Actuator_Typedef *this)
    {
    	this->State = 1;
    	this->IsRun = 0;
    	this->Pow = 0;
    
    	*CMP_EN_ADDR = 0;
    }
    
    static void Cooler_SetEn(Actuator_Typedef *this, uint8_t cmd)
    {
    	this->IsRun = cmd;
    
    	if(cmd != 0)
    	{
    		this->IsRun = 1;
    	}
    	else
    	{
    		this->Init_FP(this);
    	}
    }
    
    static void Cooler_SetPower(Actuator_Typedef *this, float power)
    {
    	if(power != 0)
    	{
    		this->Pow = 1;
    		*CMP_EN_ADDR = 1;
    	}
    	else
    	{
    		this->Pow = 0;
    		*CMP_EN_ADDR = 0;
    	}
    }
    
    static void Algorithm_Init(Algorithm_TypeDef *this)
    {
    	//初始化PID控制参数
    	PID_Init(&this->PidData);
    
    	///从保持寄存器中获取PID的设置参数
    	PID_SetPIDPara(&this->PidData, HoldingReg_GetData(11), HoldingReg_GetData(12), HoldingReg_GetData(13));
    	PID_SetThresPara(&this->PidData, HoldingReg_GetData(14), HoldingReg_GetData(15));
    
    	PID_SetIntegLimiting(&this->PidData, PID_DEFAULT_MAXINTEG, PID_DEFAULT_MININTEG);
    	PID_SetDiffLimiting(&this->PidData, PID_DEFAULT_MAXDIFF, PID_DEFAULT_MINDIFF);
    }
    
    static void Algorithm_SetEn(Algorithm_TypeDef *this, uint8_t cmd)
    {
    	if(cmd != 0)
    	{
    		this->IsRun = 1;
    	}
    	else
    	{
    		this->IsRun = 0;
    
    		this->Init_FP(this);
    	}
    }
    
    static void Algorithm_SetPID(Algorithm_TypeDef *this, float p, float i, float d)
    {
    	PID_SetPIDPara(&this->PidData, p, i, d);
    }
    
    /**
     * @brief 更新一次PID计算
     */
    static void Algorithm_Update(Algorithm_TypeDef *this, float sv, float pv)
    {
    	if (this->IsRun == 0)
    	{
    		return;
    	}
    
    	//Get PV
    //	AlgorithmArray[0].PV = InputReg_GetData(1);
    	//Get SV
    //	AlgorithmArray[0].SV = HoldingReg_GetData(8);
    
    	//Set PID
    //	PID_SetPara(&AlgorithmArray[0].PidData, HoldingReg_GetData(11), HoldingReg_GetData(12), HoldingReg_GetData(13));
    	//Set PV
    	PID_Update(&this->PidData, sv, pv);
    }
    
    /**
     * @brief (根据PID计算值)计算加热器输出功率
     * 		注意此函数依赖了两个全局变量HR_Ht_PID_UT与HR_Ht_PID_DT,
     * 		这两个变量由于一般程序中无需修改,因此没有设置专用的接口函数
     * @return 加热器输出功率
     */
    static float Algorithm_GetOutput_P(Algorithm_TypeDef *this)
    {
    //	static int try_count = 0;
    
    	if (this->IsRun == 0)
    	{
    		return 0;
    	}
    
    	return PID_GetOutput(&this->PidData);
    }
    
    /**
     * @brief (根据PID计算值)计算制冷器输出功率
      * 	注意此函数依赖了两个全局变量HR_Cmp_UT与HR_Cmp_DT,
     * 		这两个变量由于一般程序中无需修改,因此没有设置专用的接口函数
     * @return 制冷器输出功率
     */
    static float Algorithm_GetOutput_N(Algorithm_TypeDef *this)
    {
    	//	static int try_count = 0;
    
    	if (this->IsRun == 0)
    	{
    		return 0;
    	}
    
    	//	float pidOut = PID_GetOutput(&AlgorithmArray[0].PidData);
    	float error = PID_GetError(&this->PidData);
    
    	//如果PV超出SV的温度达到HR_Cmp_UT,启动压缩机
    	if (error < 0 - HoldingReg_GetData(16))
    	{
    		return 1;
    	}
    
    	return 0;
    }
    
    /**
     * @brief 初始化控温器
     */
    static void TemperController_Init(TemperController_TypeDef *this)
    {
    	this->Heater = &HeaterArray[0];
    	this->Cooler = &CoolerArray[0];
    	this->Algorithm = &AlgorithmArray[0];
    
    	this->Heater->Init_FP(this->Heater);
    	this->Cooler->Init_FP(this->Cooler);
    	this->Algorithm->Init_FP(this->Algorithm);
    }
    
    /**
     * @brief 设置控温器状态(打开或关闭)
     * @param cmd: 0 关闭控温器, 1 打开控温器
     */
    static void TemperController_SetEn(TemperController_TypeDef *this, uint8_t cmd)
    {
    	//开启控温,开启控制器、加热器、制冷器
    	if(cmd != 0)
    	{
    		this->IsRun = 1;
    
    		this->Algorithm->SetEn_FP(this->Algorithm, 1);
    		this->Heater->SetEn_FP(this->Heater, 1);
    		this->Cooler->SetEn_FP(this->Cooler, 1);
    	}
    	//关闭控温,关闭控制器、加热器、制冷器
    	else
    	{
    		this->IsRun = 0;
    
    		this->Algorithm->SetEn_FP(this->Algorithm, 0);
    		this->Heater->SetEn_FP(this->Heater, 0);
    		this->Cooler->SetEn_FP(this->Cooler, 0);
    	}
    }
    
    uint8_t TemperController_GetErr(TemperController_TypeDef *this)
    {
    	return this->Error;
    }
    
    void TemperController_SetPV(TemperController_TypeDef *this, float pv)
    {
    	this->PV = pv;
    }
    
    void TemperController_SetSV(TemperController_TypeDef *this, float sv)
    {
    	this->SV = sv;
    }
    
    /**
     * @brief  执行一次控温调整(仅在控温仪打开的时候,才能调用)
     * 		一般在定时器的更新中断中周期性的调用此函数,每调用一次就执行一次控温调整。
     * 		注意此函中依赖了与PID参数相关的三个全局变量P, I , D。
     */
    static void TemperController_Exec(TemperController_TypeDef *this)
    {
    	static float pValue = -1;
    	static float iValue = -1;
    	static float dValue = -1;
    
    	//控制参数发生变化则重新修改算法的PID参数
    	if((pValue != HoldingReg_GetData(11)) || (iValue != HoldingReg_GetData(12)) || (dValue != HoldingReg_GetData(13)))
    	{
    		pValue = HoldingReg_GetData(11);
    		iValue = HoldingReg_GetData(12);
    		dValue = HoldingReg_GetData(13);
    
    		///将PID参数的设置也加入到每次动作调整轮询中,这样可以实现参数设定后的动态调整。
    		///PID启动区间阈值与积分、微分上下限仅在初始化算法器的时候写入。
    		this->Algorithm->SetPID_FP(this->Algorithm, pValue, iValue, dValue);
    	}
    
    	this->Algorithm->Update_FP(this->Algorithm, this->SV,this->PV);
    
    	this->Heater->SetPower_FP(this->Heater, this->Algorithm->GetOutput_P_FP(this->Algorithm));
    	this->Cooler->SetPower_FP(this->Cooler, this->Algorithm->GetOutput_N_FP(this->Algorithm));
    
    	/*后面加上出错的判断,将Error改为对应的错误码值*/
    }
    
    #endif
    

    使用指南

    控温器在一定程度上实现了封装,但它在整体上就像一个逻辑框架。在实际使用的时候,需要为各个模块编写不同的实现函数,然后通过动态或者静态的方式注册给控温器、加热器、制冷器、算法器,而不必考虑它们内部如何工作的、子模块之间是如何协调的。一般情况下,控温器的实现函数主要是在生成错误码的逻辑上需要修改,加热器与制冷器则是在硬件相关的底层上需要修改(例如不同的PWM产生方式、不同的GPIO控制端口等),算法器主要是计算加热器与制冷器工作功率的地方需要修改。

    控温器构建完成后,外部使用它相对会简单很多:

    1. 在程序开始的时候,初始化控温器TemperController[0].Init_FP(&TemperController[0]);(注意需要手动传入this指针)
    2. 在需要打开控温器的地方开启控温器TemperController[0].SetEn_FP(&TemperController[0],1);

    在使用了modbus通讯协议的情况下,我们一般会使用一个线圈寄存器作为打开或者关闭控温器的信号:

    	static uint8_t coilsReg_buffer[32];
    
    	//开启温度控制
    	if(coilsReg_buffer[1] != CoilsReg_GetBit(1))
    	{
    		coilsReg_buffer[1] = CoilsReg_GetBit(1);
    
    		//打开控温器
    		TemperController[0].SetEn_FP(&TemperController[0], coilsReg_buffer[1]);
    	}
    
    1. 在定时器的更新中断服务函数或者实时操作系统的一个任务中周期性的调用TemperController[0].SetPV_FP(&TemperController[0], SenPt100Array[0].CalibValue)TemperController[0].Exec_FP(&TemperController[0]);

    一般情况下,我们需要先判断控温器的状态与设定值是否变化,然后才会调用控温器的控温调整执行函数:

    	static float heaterSV = 0;
    
    	if(TemperController[0].IsRun != 0)
    	{
    		//SV发生改变的时候才对控温器进行设定
    		if(heaterSV != HoldingReg_GetData(8))
    		{
    			heaterSV = HoldingReg_GetData(8);
    			TemperController[0].SetSV_FP(&TemperController[0], heaterSV);
    		}
    
    		//实时刷新PV
    		TemperController[0].SetPV_FP(&TemperController[0], SenPt100Array[0].CalibValue);
    		TemperController[0].Exec_FP(&TemperController[0]);
    	}
    
    展开全文
  • C语言模块化设计资料,主要描述了在做C语言开发时,要形成良好的编写习惯,包括备注、分享符号等,一方面可以提高自己的编写逻辑,另一方面可以让小伙伴看得懂自己的代码,或者3年后可以明白当初自己编程的思路
  • keil中实现C语言模块化编程.doc 在使用 KEIL 的时候,我们习惯上在一个.c 的文件中把自己要写的东西按照 自己思路的顺序进行顺序书写。这样是很普遍的写法,当程序比较短的时候比如 几十行或者一百多行,是没有什么...

     

    keil中实现C语言模块化编程.doc

    在使用 KEIL 的时候,我们习惯上在一个.c 的文件中把自己要写的东西按照
    自己思路的顺序进行顺序书写。这样是很普遍的写法,当程序比较短的时候比如
    几十行或者一百多行,是没有什么问题的。但是当程序很长的时候,比如你要用
    到 LCD 显示数据,就有几个 LCD 相关的函数,然后你想在 LCD 上显示温度,那么
    就要有 DS18B20 相关的操作,这又有几个相关的函数,  如果你还想加上去 DS1302
    的时间显示功能,那么又要多很多函数。这样的话一个程序下来几百行是很正常
    的事情,对于自己写的程序可能在自己的脑海中比较清晰,不会太乱,但是当把
    自己写的程序交给别人来看的时候,别人往往会看的云里雾里,经常会看着看着
    就不知道你写的是什么了。
    如果大家写过类似电子钟这样的比较长的程序的话,     肯定在网上下载过相关
    的程序看过,有没有觉得别人的程序看起来让自己觉得很郁闷呢?呵呵。现在我
    们来介绍一种在 KEIL 中 C 语言的模块写法。这样的写法其实也是很好用的,一
    些稍长的程序中经常见到。
    是不是看起来不陌生?这就对了。其实如果学过 PC 机的 C 语言的话,对多
    文件的编译比较熟悉那么这个就不是什么问题了,因为这基本上是相同的。如果
    您是高手对此很熟悉的话,那么请略过本文;如果您是对此不是很熟悉并对此有
    点兴趣,那么本文或许对您有所帮助。如果在本文中有讲的不对的地方请跟帖提
    出。或者在我的主页给我留言进行交流。
    这个教程不大容易用文字的形式来讲清楚,         如果用视频来做的话效果应该好
    的多,但是俺没这个条件(俺普通话不好怕吓到大家,哈哈)               。可能一帖会写不
    完,另外打字是件很痛苦的事情,所以这个请见谅。下面正式开始。
    我们主的目的是学习模块化的写法,所以功能是次要的,熟悉了这个写法以
    后功能是大家自己控制的,我们现在将以 LED 灯的控制为例子讲起。
    这样,   我们先建立三个.c 的文件,    分别命名为 main.c、delay.c 和 led_on.c,
    并将在建立文件的时候尽量做到看到文件名即能看出程序的功能,                 这样的话比较
    直观,不容易混乱。然后将这三个文件都添加进工程。            (这个不能不会吧?)
    在 delay.c 中我们加入如下代码:
    void delay1s()
    {
    unsigned int m,n;
    for(m=1000;m>0;m--)
    for(n=20;n>0;n--);
    }
    当然这个延时函数的实际延时时间并不是一秒,我们暂且不用管它,知道他
    是起延时作用的就可以了。在 led_on.c 这个文件中我们加入如下代码:
    void led_on()
    {
    P0=0x00;
    delay1s();
    P0=0xff;
    delay1s();
    }
    然后在 main.c 函数中我们添加如下代码:
    void main()
    {
    led_on();
    }
    这个程序的功能简单的很,就是实现 LED 的闪烁。
    下面问题来了,就是如何将这三个 C 文件关联起来。
    其实在单个.c 文件的程序中,我们在写程序的时候第一件事就是写上
    #include <reg52.h>,如果你是一个好学者, 你一定问过为什么要这样写一句话,
    要是你上过辅导班,老师一定跟你讲 reg52.h 是头文件,这句话的作用的把头文
    件包含进来。当然这是很正确的,你可以打开 reg52.h,看一下里面的内容,里
    面包含了关于 51 单片机的一些定义,如果在这个文件中遗漏的东西可以使用命
    令 sfr 来在 C 文件中定义,如在 STC89C52 中实用扩展 RAM 的时候会用到一个寄
    存器你可以添加到这个文件中或者在 C 文件中用 sfr 定义。进一步想一下,一个
    包含命令可以把一个文件包含进来,         那么用不同的头文件包含不就可以把更多的
    文件包含进来了吗?是不是有点思路了?
    先讲到这里,下次看一下具体如何将三个文件关联起来。
    我们接着上一次的讨论一下如何将三个 c 文件关联起来,          在单文档的程序中
    我们使用#include 这个命令将单片机的头文件与我们的程序关联起来。同理我
    们也将以头文件的形式把我们建立的源程序关联起来。
    首先,我们需要一个新文档,这个文档的建立有两种方法(以 delay1s 函数
    为例)第一种,
    。       在工程目录下建立一个 delay1s.txt 然后将其改名为 delay1s.h。
    因为都是同编码的所以不会出现乱码,然后在工程中将其打开。第二种方法是直
    接在工程中新建一个文档,然后保存的时候将名字保存为 delay1s.h 即可。如果
    是需要添加很多文件的话建议使用第一种方法,这是个人建议。
    其次,我们需要编写 delay1s.h 这个文件的内容,其内容如下:
    #ifndef _DELAY1S_H_
    #define _DELAY1S_H_
    void delay1s();//延时函数
    #endif
    这个是头文件的定义,作用是声明了 delay1s()函数,因为如果在别的函数
    中如果我们需要用到 delay1s()函数的话,若不事先声明则在编译的时候会出
    错。对于#ifndef......#define......#endif;这个结构大概的意思就是说如果没有
    定义(宏定义)一个字符串,那么我们就定义它,然后执行后面的语句,如果定
    义过了那么就跳过不执行任何语句。
    关于为什么要使用这么一个定义方法,比如在 led_on()函数中我们调用了
    delay1s()函数,然后在 main()函数中我们也调用了 delay()函数,那么,在
    led_on()函数中我就就要包含头文件 delay1s.h,  然后在 main()函数中也要包含
    delay1s.h, 若主函数中我们调用过 led_on(),那么在编译的时候,    遇到 delay1s()
    和 led_on()的时候就会对 delay1s.h 进行两次解释,那么就会出现错误。若有
    以上预处理命令的话,那么在第二次的时候这个_DELAY1S_H_已经被定义过了,
    那么就不会出现重复定义的问题。这就是它的作用。但是注意,在编译器进行编
    译的时候头文件不参与编译。
    再次,我们建立一个 led_on.h,起代码内容如下:
    #ifndef _LED_ON_H_
    #define _LED_ON_H_
    void led_on();//灯闪烁
    #endif
    作用同 delay1s.h,不理解的话可以再看一下上面的解释。
    最后,将我们上次说的三个函数补充完整。
    在 led_on()函数中,我们用到了 51 单片机的一些寄存器的定义,所以我们
    要包含 reg52.h,而且我们用到了 delay1s()函数,所以我们要包含 delay1s.h,
    故 led_on()函数的代码如下:
    #include <reg52.h>
    #include “delay1s.h” //注意这里没有分号
    void led_on()
    {
    P0=0x00;
    delay1s();
    P0=0xff;
    delay1s();
    }
    Main 函数的代码如下:
    #include <reg52.h>
    #include “delay1s.h”
    void main()
    {
    led_on();
    delay1s();//在这里其实只有第一句就可以了,这句是不必要的
    led_on();//这也是不必要的
    }
    在这个函数中,为了再次说明一下#ifndef......#define......#endif 这个结
    构的定义,大家可以把所有的.h 文件中的这个结构去掉,然后编译一下看一下
    效果。
    到这里相信大家对于这种模块化的写法就有大概的了解了,           如果我们想添加
    新功能的时候,比如我们要添加一个流水灯的功能,那么,我们只需要增加一个
    led_circle.c 和 led_circle.h,然后按照上述步骤添加进工程即可,程序的其
    他部分不需要任何改动。显然这是很方便的。其实函数的声明可以使用 extern
    关键字,C 语言中默认都是这个类型的,所以可以不用写。
    如果还有说的不清楚的请提出来,我们一起讨论。由于这些东西都是手动输
    入的所以难免会有错误,如果各位朋友在看这个教程的时候发现有哪里表达错误
    或者是不妥当的地方,欢迎指出,我会及时改正,以免误导别人,呵呵。
    PCB打样找华强 http://www.hqpcb.com 样板2天出货

    展开全文
  • keil中实现C语言模块化编程.doc在使用 KEIL 的时候,我们习惯上在一个.c 的文件中把自己要写的东西按照自己思路的顺序进行顺序书写。这样是很普遍的写法,当程序比较短的时候比如几十行或者一百多行,是没有什么问题的...

    keil中实现C语言模块化编程.doc

    在使用 KEIL 的时候,我们习惯上在一个.c 的文件中把自己要写的东西按照

    自己思路的顺序进行顺序书写。这样是很普遍的写法,当程序比较短的时候比如

    几十行或者一百多行,是没有什么问题的。但是当程序很长的时候,比如你要用

    到 LCD 显示数据,就有几个 LCD 相关的函数,然后你想在 LCD 上显示温度,那么

    就要有 DS18B20 相关的操作,这又有几个相关的函数,  如果你还想加上去 DS1302

    的时间显示功能,那么又要多很多函数。这样的话一个程序下来几百行是很正常

    的事情,对于自己写的程序可能在自己的脑海中比较清晰,不会太乱,但是当把

    自己写的程序交给别人来看的时候,别人往往会看的云里雾里,经常会看着看着

    就不知道你写的是什么了。

    如果大家写过类似电子钟这样的比较长的程序的话,     肯定在网上下载过相关

    的程序看过,有没有觉得别人的程序看起来让自己觉得很郁闷呢?。现在我

    们来介绍一种在 KEIL 中 C 语言的模块写法。这样的写法其实也是很好用的,一

    些稍长的程序中经常见到。

    是不是看起来不陌生?这就对了。其实如果学过 PC 机的 C 语言的话,对多

    文件的编译比较熟悉那么这个就不是什么问题了,因为这基本上是相同的。如果

    您是高手对此很熟悉的话,那么请略过本文;如果您是对此不是很熟悉并对此有

    点兴趣,那么本文或许对您有所帮助。如果在本文中有讲的不对的地方请跟帖提

    出。或者在我的主页给我留言进行交流。

    这个教程不大容易用文字的形式来讲清楚,         如果用视频来做的话效果应该好

    的多,但是俺没这个条件(俺普通话不好怕吓到大家,)               。可能一帖会写不

    完,另外打字是件很痛苦的事情,所以这个请见谅。下面正式开始。

    我们主的目的是学习模块化的写法,所以功能是次要的,熟悉了这个写法以

    后功能是大家自己控制的,我们现在将以 LED 灯的控制为例子讲起。

    这样,   我们先建立三个.c 的文件,    分别命名为 main.c、delay.c 和 led_on.c,

    并将在建立文件的时候尽量做到看到文件名即能看出程序的功能,                 这样的话比较

    直观,不容易混乱。然后将这三个文件都添加进工程。            (这个不能不会吧?)

    在 delay.c 中我们加入如下代码:

    void delay1s()

    {

    unsigned int m,n;

    for(m=1000;m>0;m--)

    for(n=20;n>0;n--);

    }

    当然这个延时函数的实际延时时间并不是一秒,我们暂且不用管它,知道他

    是起延时作用的就可以了。在 led_on.c 这个文件中我们加入如下代码:

    void led_on()

    {

    P0=0x00;

    delay1s();

    P0=0xff;

    delay1s();

    }

    然后在 main.c 函数中我们添加如下代码:

    void main()

    {

    led_on();

    }

    这个程序的功能简单的很,就是实现 LED 的闪烁。

    下面问题来了,就是如何将这三个 C 文件关联起来。

    其实在单个.c 文件的程序中,我们在写程序的时候第一件事就是写上

    #include ,如果你是一个好学者, 你一定问过为什么要这样写一句话,

    要是你上过辅导班,老师一定跟你讲 reg52.h 是头文件,这句话的作用的把头文

    件包含进来。当然这是很正确的,你可以打开 reg52.h,看一下里面的内容,里

    面包含了关于 51 单片机的一些定义,如果在这个文件中遗漏的东西可以使用命

    令 sfr 来在 C 文件中定义,如在 STC89C52 中实用扩展 RAM 的时候会用到一个寄

    存器你可以添加到这个文件中或者在 C 文件中用 sfr 定义。进一步想一下,一个

    包含命令可以把一个文件包含进来,         那么用不同的头文件包含不就可以把更多的

    文件包含进来了吗?是不是有点思路了?

    先讲到这里,下次看一下具体如何将三个文件关联起来。

    我们接着上一次的讨论一下如何将三个 c 文件关联起来,          在单文档的程序中

    我们使用#include 这个命令将单片机的头文件与我们的程序关联起来。同理我

    们也将以头文件的形式把我们建立的源程序关联起来。

    首先,我们需要一个新文档,这个文档的建立有两种方法(以 delay1s 函数

    为例)第一种,

    。       在工程目录下建立一个 delay1s.txt 然后将其改名为 delay1s.h。

    因为都是同编码的所以不会出现乱码,然后在工程中将其打开。第二种方法是直

    接在工程中新建一个文档,然后保存的时候将名字保存为 delay1s.h 即可。如果

    是需要添加很多文件的话建议使用第一种方法,这是个人建议。

    其次,我们需要编写 delay1s.h 这个文件的内容,其内容如下:

    #ifndef _DELAY1S_H_

    #define _DELAY1S_H_

    void delay1s();//延时函数

    #endif

    这个是头文件的定义,作用是声明了 delay1s()函数,因为如果在别的函数

    中如果我们需要用到 delay1s()函数的话,若不事先声明则在编译的时候会出

    错。对于#ifndef......#define......#endif;这个结构大概的意思就是说如果没有

    定义(宏定义)一个字符串,那么我们就定义它,然后执行后面的语句,如果定

    义过了那么就跳过不执行任何语句。

    关于为什么要使用这么一个定义方法,比如在 led_on()函数中我们调用了

    delay1s()函数,然后在 main()函数中我们也调用了 delay()函数,那么,在

    led_on()函数中我就就要包含头文件 delay1s.h,  然后在 main()函数中也要包含

    delay1s.h, 若主函数中我们调用过 led_on(),那么在编译的时候,    遇到 delay1s()

    和 led_on()的时候就会对 delay1s.h 进行两次解释,那么就会出现错误。若有

    以上预处理命令的话,那么在第二次的时候这个_DELAY1S_H_已经被定义过了,

    那么就不会出现重复定义的问题。这就是它的作用。但是注意,在编译器进行编

    译的时候头文件不参与编译。

    再次,我们建立一个 led_on.h,起代码内容如下:

    #ifndef _LED_ON_H_

    #define _LED_ON_H_

    void led_on();//灯闪烁

    #endif

    作用同 delay1s.h,不理解的话可以再看一下上面的解释。

    最后,将我们上次说的三个函数补充完整。

    在 led_on()函数中,我们用到了 51 单片机的一些寄存器的定义,所以我们

    要包含 reg52.h,而且我们用到了 delay1s()函数,所以我们要包含 delay1s.h,

    故 led_on()函数的代码如下:

    #include #include “delay1s.h” //注意这里没有分号

    void led_on()

    {

    P0=0x00;

    delay1s();

    P0=0xff;

    delay1s();

    }

    Main 函数的代码如下:

    #include #include “delay1s.h”

    void main()

    {

    led_on();

    delay1s();//在这里其实只有第一句就可以了,这句是不必要的

    led_on();//这也是不必要的

    }

    在这个函数中,为了再次说明一下#ifndef......#define......#endif 这个结

    构的定义,大家可以把所有的.h 文件中的这个结构去掉,然后编译一下看一下

    效果。

    到这里相信大家对于这种模块化的写法就有大概的了解了,           如果我们想添加

    新功能的时候,比如我们要添加一个流水灯的功能,那么,我们只需要增加一个

    led_circle.c 和 led_circle.h,然后按照上述步骤添加进工程即可,程序的其

    他部分不需要任何改动。显然这是很方便的。其实函数的声明可以使用 extern

    关键字,C 语言中默认都是这个类型的,所以可以不用写。

    如果还有说的不清楚的请提出来,我们一起讨论。由于这些东西都是手动输

    入的所以难免会有错误,如果各位朋友在看这个教程的时候发现有哪里表达错误

    或者是不妥当的地方,欢迎指出,我会及时改正,以免误导别人,。

    展开全文
  • 解题思路: 1)定义一个长度为10的数组,数组定义为整型 2)要赋的值是从0到9,可以用循环来赋值 3)用循环按下标从大到小输出这10个元素 #include<stdio.h> int main() { int a[10],i; for(i=0;i<=9;i++) ...

    文章目录

    第6章 利用数组处理批量数据

    6.1 对10个数组元素依次赋值为0,1,2,3,4,5,6,7,8,9,要求按逆序输出

    解题思路:

    1)定义一个数组类型为整型且长度为10的数组a[i]
    2)要赋的值是从0到9,可以用for循环来赋值
    3)用循环按下标从大到小输出这10个数组元素

    #include<stdio.h>
    
    int main()
    {
    	int a[10],i;
    	for(i=0;i<=9;i++)
    	{
    		a[i]=i;
    	}
    	
    	for(i=9;i>=0;i--)
    	{
    		printf("%d\t",a[i]);		
    	}
    	printf("\n");
    	return 0;
    }
    

    编译结果如下:
    在这里插入图片描述

    6.2 用数组处理求Fibonacci数列问题

    说明:我的上一篇博客(C语言基础练习(一))(https://blog.csdn.net/weixin_45870610/article/details/107289218)中的5.5也是求Fibonacci数列的,不同的是5.5中是用简单变量处理的,缺点不能在内存中保存这些数。假如想直接输出数列中第25个数,是很困难的。如果用下面的方法,即数组处理,每一个数组元素代表数列中的一个数,就可以依次求出各数并存放在相应的数组元素中。

    解题思路:

    1)先定义一个数组长度为40的整型数组a[40]
    2)由于Fibonacci数列的特殊,数组的第1、2个元素都是1,且可以直接定下来
    3)根据Fibonacci数列的递推公式用for循环求得其余的38个数组元素,在内存中保存下来
    4)接下来就是如何把这个数组好看地打印出来,当然要考虑for循环,一个个打印出来,间隔可以在打印时用%12d

    #include<stdio.h>
    
    int main()
    {
    	int a[40]={1,1},i;
    	for(i=2;i<40;i++)
    	{
    		a[i]=a[i-1]+a[i-2];
    	}
    	for(i=0;i<40;i++)
    	{
    		if(i%5==0)
    		{
    			printf("\n");
    		}
    		printf("%12d",a[i]);
    	}
    	printf("\n");
    	return 0;
    }
    

    编译结果如下:
    在这里插入图片描述

    6.3 有10个地区的面积,要求对它们按由小到大的顺序排列。

    解题思路:

    1)采用起泡法排序(起泡法和冒泡法是同一种方法,不过就冒泡法分的细点的话,还可以分好几种,但出发点一样的)
    (1)从第一个元素开始,比较相邻的元素,若是前一个比后一个大,则两者调换位置,若前一个比后一个小,则两者位置不变
    (2)每一对相邻的元素进行重复的工作,从开始对一直到结尾对,这步完成后,结尾为最大的数
    (3)针对除了最后一个元素重复进行上面的步骤
    重复1-3步骤直到完成排序

    冒泡排序动图演示:
    此动图来源于:
    https://blog.csdn.net/fengge2018/article/details/105328957

    在这里插入图片描述

    #include<stdio.h>
    
    int main()
    {
    	int a[10];   
    	int i,j,t;
    	printf("input 10 numbers :\n");
    	for (i=0;i<10;i++)
    	{
    		scanf("%d",&a[i]);
    	}
    	printf("\n");
    	for(j=0;j<9;j++)
    	{
    		for(i=0;i<9-j;i++)
    		{
    			if (a[i]>a[i+1])
    			{
    				{
    					t=a[i];
    					a[i]=a[i+1];
    					a[i+1]=t;
    				}
    			}
    		}
    	}
    	printf("the sorted numbers :\n");
    	for(i=0;i<10;i++)
    	{
    		printf("%d ",a[i]);
    	}
    	printf("\n");
    }
    
    

    编译结果如下:
    在这里插入图片描述

    以上是一维数组的相关练习


    以下是二维数组的相关练习:

    6.4将一个二维数组行和列的元素互换,存到另一个二维数组中

    解题思路:
    1)可以定义两个数组:数组a为2行3列,存放指定的6个数;数组b为3行2列,开始时未赋值
    2)将a数组中的元素a[i][j]存放到b数组中的b[j][i]元素中
    用嵌套的for循环完成
    在这里插入图片描述

    #include <stdio.h>
    
    int main()
     {  
    	 int a[2][3]={{1,2,3},{4,5,6}};
         int b[3][2],i,j;
         printf("array a:\n");
         for (i=0;i<=1;i++)
         { 
    		 for (j=0;j<=2;j++)
             {  
    			 printf("%5d",a[i][j]);   
                 b[j][i]=a[i][j];     
             }
             printf("\n");
         }
         printf("array b:\n");  
         for (i=0;i<=2;i++)
         {  
    		 for(j=0;j<=1;j++)
             printf("%5d",b[i][j]);
             printf("\n");
         }
         return 0;
    } 
    

    编译结果如下:
    在这里插入图片描述

    6.5 有一个3×4的矩阵,要求编程序求出其中值最大的那个元素的值,以及其所在的行号和列号

    解题思路:
    采用"打擂台算法"
    打擂台算法的原理:先找出任一人站在台上,第2人上去与之比武,胜者留在台上,第3人与台上的人比武,胜者留台上,败者下台,以后每一个人都是与当时留在台上的人比武,直到所有人都上台比为止,最后留在台上的是冠军

    同理:

    1)先把a[0][0]的值赋给变量max(max用来存放当前已知的最大值)
    2)a[0][1]与max比较,如果a[0][1]>max,则表示a[0][1]是已经比过的数据中值最大的,把它的值赋给max,取代了max的原值
    3)以后依此处理,最后max就是最大的值

    #include<stdio.h>
    
    int main()
    {
    	int i,j,row=0,colum=0,max;
        int a[3][4]={{1,2,3,4},{9,8,7,6},{-10,10,-5,2}}; 
        max=a[0][0];                     
        for (i=0;i<=2;i++)
        {
    		for (j=0;j<=3;j++)
            {
    		    if (a[i][j]>max) 
    			{  
    				max=a[i][j];  
    				row=i;  
    				colum=j; 
    			}
    		}
    	}
        printf("max=%d\nrow=%d\ncolum=%d\n",max,row,colum);
    }
    

    编译结果如下:
    在这里插入图片描述

    6.6 输出一个已知的字符串

    解题思路:
    1)定义一个字符数组,并用"初始化列表"对其赋以初值
    2)用for循环逐个输出此字符数组中的字符

    #include <stdio.h>
    int main()
     { 
    	char c[15]={'I',' ','a','m',' ','a',' ','s','t','u','d','e','n','t','.'};
        int i;
        for(i=0;i<15;i++)
        {
    		printf("%c",c[i]);
    	}    
        printf("\n");
        return 0;
    }
    

    编译结果如下:
    在这里插入图片描述

    6.7输出数组内所有的元素

    解题思路:
    很简单的一道题,想要依次输出数组里的元素,首先数组里的元素要有值,有值了下一步就考虑如何依次输出到屏幕上,就自然想到了for循环(可以输出了以后就可以考虑美观问题,再设置数组元素输出的间隔)

    #include<stdio.h>
    
    int main()
    {
    	int a[10]={0},i;
    	for(i=0;i<10;i++)
    	{
    		printf("%2d",a[i]);
    	}
    	printf("\n");
    }
    

    编译结果如下:
    在这里插入图片描述

    6.8输出一个菱形图

    解题思路:
    1)对于这种有多个元素要输出的,我们最好是把这些元素存放在数组里,再用for循环输出到屏幕上。
    2)菱形图,先在草稿纸上画出一个菱形,观察它的结构,下面的你就懂了哈哈!

    #include <stdio.h>
    
    int main()
    { 
    	char diamond[][5]={{' ',' ','*'},{' ','*',' ','*'},{'*',' ',' ',' ','*'},{' ','*',' ','*'},{' ',' ','*'}};
        int i,j;
        for (i=0;i<5;i++)
        {
    		for (j=0;j<5;j++)
    		{
    			printf("%c",diamond[i][j]);
    		}
            printf("\n");
        }
        return 0;
    }
    

    编译结果如下:
    在这里插入图片描述

    6.9字符串和字符串结束标志

    1)在C语言中,是将字符串作为字符数组来处理的,输出字符串时考虑的是字符串的有效长度而不是字符数组的长度

    2)为了测定字符串的实际长度,C语言规定了字符串结束标志’\0’, '\0’代表ASCII码为0的字符,从ASCII码表可以查到,ASCII码为0的字符不是一个可以显示的字符,而是一个"空操作符",即它什么也不做,用它作为字符串结束标志不会产生附加的操作或增加有效字符,只起一个供辨别的标志

    char c[]={"I  am  happy"};
    可写成
    char c[]="I  am  happy";
    相当于
    char c[11]={"I  am  happy"};
    
    #include<stdio.h>
    
    int main()
    {
    	char c[10]={"China"};
    	printf("%s\n",c);
    }
    

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    字符数组的输入输出可以有两种方法:

    1)逐个字符输入输出(%c)
    2)整个字符串一次输入输出(%s)

    注意事项:

    1)输出的字符中不包括结束符’\0’
    2)用%s输出字符串时,printf函数中的输出项是字符数组名,不是数组元素名
    3)如果一个字符数组中包含多个’\0’,则遇第一个’\0’时输出就结束
    4)可以用scanf函数输入一个字符串,scanf函数中的输入项c是已定义的字符数组名,输入的字符串应短于已定义的字符数组的长度,如下:
    例1:

    #include<stdio.h>
    
    int main()
    {
    	char c[8];
    	printf("请输入一个字符串且字符串的长度小于等于8:\n");
    	scanf("%s",&c);
    	printf("%s\n",c);
    }
    

    编译结果如下:
    在这里插入图片描述
    例2:

    #include<stdio.h>
    
    int main()
    {
    	char str1[5],str2[5],str3[5];
    	scanf("%s%s%s",str1,str2,str3);
    	printf("%s %s %s\n",str1,str2,str3);
    }
    

    编译结果如下:
    在这里插入图片描述

    6.10善于使用字符串处理函数

    1.puts函数----输出字符串的函数

    1)其一般形式为:puts (字符数组名)
    2)作用是将一个字符串输出到终端
    char str[20]=“China”;
    puts(str);

    输出China到屏幕上

    2. gets函数----输入字符串的函数

    1)其一般形式为:gets(字符数组名)
    2)作用是输入一个字符串到字符数组
    char str[20];
    gets(str);

    Computer↙即从屏幕上输入20个元素,接收到数组中,赋值给数组的20个元素

    3. strcat函数----字符串连接函数

    1)其一般形式为:strcat(字符数组1,字符数组2)
    2)其作用是把两个字符串连接起来,把字符串2接到字符串1的后面,结果放在字符数组1中
    3)使用字符串函数时,在程序开头用#include <string.h>
    如下:

    #include<stdio.h>
    #include<string.h>
    
    int main()
    {
    	char str1[30]="People";//数组长度要足够大
        char str2[]="China";
        printf("%s\n", strcat(str1,str2));  
    }
    

    在这里插入图片描述

    4. strcpy和strncpy函数-字符串复制

    1)strcpy一般形式为:strcpy(字符数组1,字符串2)
    2)作用是将字符串2复制到字符数组1中去
    char str1[10],str2[]=“China”;
    strcpy(str1,str2);
    3)可以用strncpy函数将字符串2中前面n个字符复制到字符数组1中去
    4)strncpy(str1,str2,2);
    作用是将str2中最前面2个字符复制到str1中,取代str1中原有的最前面2个字符
    5)复制的字符个数n不应多于str1中原有的字符

    5. strcmp函数----字符串比较函数

    1)其一般形式为:strcmp(字符串1,字符串2)
    2)作用是比较字符串1和字符串2
    strcmp(str1,str2);
    strcmp(“China”,“Korea”);
    strcmp(str1,“Beijing”);
    3)字符串比较的规则是:将两个字符串自左至右逐个字符相比,直到出现不同的字符或遇到’\0’为止
    4)如全部字符相同,认为两个字符串相等
    5)若出现不相同的字符,则以第一对不相同的字符的比较结果为准(实则比较ASCLL码的值)

    "A"<"B"          "a">"A"    "computer">"compare"
    "these">"that"   "1A">"$20"      "CHINA">"CANADA"
    "DOG"<"cat"       "Tsinghua">"TSINGHUA"
    

    6)比较的结果由函数值带回
    如果字符串1=字符串2,则函数值为0
    如果字符串1>字符串2,则函数值为一个正整数
    如果字符串1<字符串2,则函数值为一个负整数

    if(str1>str2)  printf("yes"); // 错误
    if(strcmp(str1,str2)>0)  printf("yes");    //正确
    

    6.strlen函数----测字符串长度的函数

    1)其一般形式为:strlen (字符数组)
    2)它是测试字符串长度的函数
    3)函数的值为字符串中的实际长度
    如下:

    char str[10]="China";
    printf("%d",strlen(str));
    输出结果是5
    也可以直接测试字符串常量的长度
    strlen("China");
    

    7.strlwr函数----转换为小写的函数

    1)其一般形式为:strlwr (字符串)
    2)函数的作用是将字符串中大写字母换成小写字母

    8. strupr函数----转换为大写的函数

    1)其一般形式为:strupr (字符串)
    2)函数的作用是将字符串中小写字母换成大写字母

    6.11输入一行字符,统计其中有多少个单词,单词之间用空格分隔开

    解题思路:
    1)问题的关键是怎样确定“出现一个新单词了”

    2)从第1个字符开始逐个字符进行检查,判断此字符是否是新单词的开头,如果是,就使变量num的值加1,最后得到的num的值就是单词总数

    3)判断是否出现新单词,可以由是否有空格出现来决定(连续的若干个空格作为出现一次空格;一行开头的空格不统计在内)

    4)如果测出某一个字符为非空格,而它的前面的字符是空格,则表示“新的单词开始了”,此时使num累加1

    5)如果当前字符为非空格而其前面的字符也是非空格,则num不应再累加1

    6)用变量word作为判别当前是否开始了一个新单词的标志,若word=0表示未出现新单词,如出现了新单词,就把word置成1

    7)前面一个字符是否空格可以从word的值看出来,若word等于0,则表示前一个字符是空格;如果word等于1,意味着前一个字符为非空格

    #include<stdio.h>
    #include<string.h>
    
    int main()
    {
    	char string[81],c;  
    	int i,num=0,word=0;
    	printf("请输入句子或多个单词:\n");
        gets(string);                           
        for(i=0;(c=string[i])!='\0';i++) 
        {    
    		if(c==' ') 
    		{
    			word=0;
    		} 
            else if(word==0) 
    		{ 
    			word=1; 
    	        num++; 
    		}
    	}
        printf("%d words\n",num); 
    }
    

    编译结果如下:
    在这里插入图片描述

    6.12有3个字符串,要求找出其中最大者

    解题思路:
    1)设一个二维的字符数组str,大小为3×10。每一行存放一个字符串 char str[3][10];
    2)可以把str[0],str[1],str[2]看作3个一维字符数组,可以把它们如同一维数组那样进行处理
    在这里插入图片描述
    3)经过三次两两比较,就可得到值最大者,把它放在一维字符数组string中

       if (strcmp(str[0],str[1])>0)
    	     strcpy(string,str[0]);        
       else
    	     strcpy(string,str[1]);  
       if (strcmp(str[2],string)>0)
            strcpy(string,str[2]);
    
    #include<stdio.h>
    #include<string.h>
    
    int main()
    {
    	char str[3][10]; 
    	char string[10]; 
    	int i;
        for(i=0;i<3;i++)    
    	{
    		gets (str[i]);  
    	} 
        if(strcmp(str[0],str[1])>0)
    	{
    		strcpy(string,str[0]);
    	} 
        else                             
    	{
    		strcpy(string,str[1]); 
    	} 
        if(strcmp(str[2],string)>0)
    	{
    		strcpy(string,str[2]);
    	}      
        printf("\nthe largest:\n%s\n",string);  
        return 0;
    }
    

    编译结果如下:
    在这里插入图片描述

    第7章 用函数实现模块化程序设计

    简单说明:
    1、一个函数并不从属于另一个函数,即函数不能嵌套定义。函数间可以互相调用,但不能调用main函数,main函数是被操作系统调用的。

    2、从用户使用的角度看,函数有两种:

    1)库函数,它是由系统提供的,用户不必自己定义而直接使用它们。应该说明,不同的C语言编译系统提供的库函数的数量和功能会有一些不同,当然许多基本的函数是共同的。

    2)用户自己定义的函数。它是用以解决用户专门需要的函数。

    3、从函数的形式看,函数分两类:

    1)无参函数。无参函数一般用来执行指定的一组操作。无参函数可以带回或不带回函数值,但一般以不带回函数值的居多。

    2)有参函数。在调用函数时,主调函数在调用被调用函数时,通过参数向被调用函数传递数据,一般情况下,执行被调用函数时会得到一个函数值,供主调函数使用。

    7.1输入两个整数,要求输出其中值较大者。要求用函数来找到大数

    #include<stdio.h>
    
    int bigger(int x,int y);
    
    int bigger(int x,int y)
    {
    	int z;
    	z=x>y?x:y;
    	return z;
    }
    
    int main()
    {
    	int a,b,c;
    	printf("请输入两个正整数,并用空格隔开:\n");
    	scanf("%d%d",&a,&b);
    	c=bigger(a,b);
    	printf("较大值为c=%d\n",c);
    	return 0;
    }
    

    编译结果如下:
    在这里插入图片描述

    简单说明:
    1、在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。在发生函数调用时,函数max的形参被临时分配内存单元

    2、调用结束,形参单元被释放,实参单元仍保留并维持原值,没有改变,如果在执行一个被调用函数时,形参的值发生改变,不会改变主调函数的实参的值

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

    1)在定义函数时指定的函数类型一般应该和return语句中的表达式类型一致

    2)如果函数值的类型和return语句中表达式的值不一致,则以函数类型为准

    如下:

    将7.1稍作改动,将在bigger函数中定义的变量z改为float型。函数返回值的类型与指定的函数类型不同,分析其处理方法。

    解题思路:如果函数返回值的类型与指定的函数类型不同,按照赋值规则处理。

    #include <stdio.h>
    
    int bigger(float x,float y);
    
    int bigger(float x,float y)
    {  
    	float z;                                                
        z=x>y?x:y;
        return z;
    }
    
    int main()
    { 
        float a,b;  
    	int c;
    	printf("请输入两个小数,并用空格隔开:\n");
        scanf("%f%f",&a,&b);
        c=bigger(a,b);
        printf("bigger is %d\n",c);
        return 0;
    }
    

    编译结果如下:
    在这里插入图片描述

    7.2输入4个整数,找出其中最大的数。用函数的嵌套调用来处理

    简单说明:C语言的函数定义是互相平行、独立的,即函数不能嵌套定义,但可以嵌套调用函数,即调用一个函数的过程中,又可以调用另一个函数

    解题思路:
    1)main中调用max4函数,找4个数中最大者
    2)max4中再调用max2,找两个数中的大者
    3)max4中多次调用max2,可找4个数中的大者,然后把它作为函数值返回main函数
    4)main函数中输出结果

    #include <stdio.h>
    
    int max4(int a,int b,int c,int d);
    int max2(int a,int b);
    
    int max2(int a,int b) 
    {  
    	return a>b?a:b; 
    }
    int max4(int a,int b,int c,int d)
    {  	 
        int m; 
        m=max2(a,b); 
        m=max2(m,c);                          
        m=max2(m,d);            
        return m; 
    }
    
    int main()
    {	
        int a,b,c,d,max;
        printf("4 interger numbers:\n");    
        scanf("%d%d%d%d",&a,&b,&c,&d);              
        max=max4(a,b,c,d); 
        printf("max=%d\n",max);   
        return 0;
    }
    

    编译结果如下:
    在这里插入图片描述


    函数的递归调用:

    7.3有5个学生坐在一起

    1)问第5个学生多少岁?他说比第4个学生大2岁
    2)问第4个学生岁数,他说比第3个学生大2岁
    3)问第3个学生,又说比第2个学生大2岁
    4)问第2个学生,说比第1个学生大2岁
    5)最后问第1个学生,他说是10岁

    请问第5个学生多大

    解题思路:
    要求第5个年龄,就必须先知道第4个年龄
    要求第4个年龄必须先知道第3个年龄
    第3个年龄又取决于第2个年龄
    第2个年龄取决于第1个年龄
    每个学生年龄都比其前1个学生的年龄大2

    即:
    age(5)=age(4)+2
    age(4)=age(3)+2
    age(3)=age(2)+2
    age(2)=age(1)+2
    age(1)=10
    即:
    在这里插入图片描述

    #include <stdio.h>
    
    int age(int n);
    
    int main()
    { 
        printf("NO.5,age:%d\n",age(5)); 
        return 0;
    }
     
    int age(int n)     
    { 
    	int c;                
        if(n==1)   
    	{
    		c=10; 
    	}
        else    
    	{
    		c=age(n-1)+2;
    	} 
        return c; 
    }
    

    编译结果如下:
    在这里插入图片描述

    7.4用递归方法求n!

    解题思路:
    1)求n!也可以用递归方法,即5!等于4!×5,而4!=3!×4…,1!=1
    2)可用下面的递归公式表示:
    在这里插入图片描述

    #include <stdio.h>
    
    int fac(int n);
    
    int main()
    { 
        int n;  int y;
        printf("input an integer number:");
        scanf("%d",&n);  
        y=fac(n);
        printf("%d!=%d\n",n,y);
        return 0;
    }
    
    int fac(int n) 
    {
        int f;
        if(n<0)     
    	{
    		printf("n<0,data error!");
    	}
        else if(n==0||n==1)
    	{
    		f=1;
    	}
        else  
    	{
    		f=fac(n-1)*n;
    	} 
        return(f);
    }
    

    编译结果如下:
    在这里插入图片描述

    7.5Hanoi(汉诺)塔问题。古代有一个梵塔,塔内有3个座A、B、C,开始时A座上有64个盘子,盘子大小不等,大的在下,小的在上。有一个老和尚想把这64个盘子从A座移到C座,但规定每次只允许移动一个盘,且在移动过程中在3个座上都始终保持大盘在下,小盘在上。在移动过程中可以利用B座。要求编程序输出移动一盘子的步骤。

    解题思路:
    1)要把64个盘子从A座移动到C座,需要移动大约264 次盘子。一般人是不可能直接确定移动盘子的每一个具体步骤的

    2)老和尚会这样想:假如有另外一个和尚能有办法将上面63个盘子从一个座移到另一座。那么,问题就解决了。此时老和尚只需这样做:

    (1) 命令第2个和尚将63个盘子从A座移到B座
    (2) 自己将1个盘子(最底下的、最大的盘子)从A座移到C座
    (3) 再命令第2个和尚将63个盘子从B座移到C座

    编写程序:
    1)用hanoi函数实现第1类操作(即模拟小和尚的任务)

    2)用move函数实现第2类操作(模拟大和尚自己移盘)

    3)函数调用hanoi(n,one,two.three)表示将n个盘从“one”座移到“three”座的过程(借助“two”座)

    4)函数调用move(x,y)表示将1个盘子从x 座移到y 座的过程。x和y是代表A、B、C座之一,根据每次不同情况分别取A、B、C代入

    #include <stdio.h>
    
    void hanoi(int n,char one,char two,char three);
    void move(char x,char y);
    
    int main()
    {    
        int m;
        printf("the number of diskes:");
        scanf("%d",&m);
        printf("move %d diskes:\n",m);
        hanoi(m,'A','B','C');
    }
    void hanoi(int n,char one,char two,char three)  
    {   
        if(n==1)
    	{
    		move(one,three);
    	}
        else
        {  
    		hanoi(n-1,one,three,two);
            move(one,three);
            hanoi(n-1,two,one,three);
        }
    }
    void move(char x,char y)  
    {
        printf("%c-->%c\n",x,y);
    }
    

    编译结果如下:
    在这里插入图片描述


    数组元素作函数实参

    7.6输入10个数,要求输出其中值最大的元素和该数是第几个数

    解题思路:
    1)定义数组a,用来存放10个数
    2)设计函数max,用来求两个数中的大者
    3)在主函数中定义变量m,初值为a[0],每次调用max函数后的返回值存放在m中
    4)用“打擂台”算法,依次将数组元素a[1]到a[9]与m比较,最后得到的m值就是10个数中的最大者

    #include <stdio.h>
    
    int max(int x,int y);
    
    int main()
    {   
        int a[10],m,n,i;
        printf("10 integer numbers:\n");
    
        for(i=0;i<10;i++) 
    	{
    		scanf("%d",&a[i]);
    	}
        printf("\n");
    
    	for(i=1,m=a[0],n=0;i<10;i++)
    	{
    		if (max(m,a[i])>m) 
    		{  
    			m=max(m,a[i]); 
    	        n=i; 
    	    }
        }
        printf("largest number is %d\n",m);
        printf("%dth number.\n",n+1);
    }
    int max(int x,int y) 
    {  
    	return x>y?x:y;  
    }
    
    

    编译结果如下:

    在这里插入图片描述


    数组名作函数参数

    7.7有一个一维数组score,内放10个学生成绩,求平均成绩

    解题思路:
    1)用函数average求平均成绩,用数组名作为函数实参,形参也用数组名
    2)在average函数中引用各数组元素,求平均成绩并返回main函数

    #include <stdio.h>
    
    float average(float array[10]);
    
    int main()
    {	
        float score[10],aver;  
    	int i;
        printf("input 10 scores:\n");
        for(i=0;i<10;i++)
    	{
    		scanf("%f",&score[i]);
    	}
        printf("\n");
        aver=average(score); 
        printf("%5.2f\n",aver);
        return 0;
    } 
    float average(float array[10])
    {  
    	int i;
        float aver,sum=array[0];
        for(i=1;i<10;i++)
    	{
    		sum=sum+array[i]; 
    	}
        aver=sum/10;
        return aver;
    }
    

    编译结果如下:
    在这里插入图片描述

    7.8有两个班级,分别有5名和10名学生,调用一个average函数,分别求这两个班的学生的平均成绩

    解题思路:
    1)需要解决怎样用同一个函数求两个不同长度的数组的平均值的问题
    2)定义average函数时不指定数组的长度,在形参表中增加一个整型变量i
    3)从主函数把数组实际长度从实参传递给形参i
    4)这个i用来在average函数中控制循环的次数

    #include <stdio.h>
    
    float average(float array[],int n);
    
    int main()
    {
        float score1[5]={98.5,97,91.5,60,55};                        
        float score2[10]={67.5,89.5,99,69.5,77,89.5,76.5,54,60,99.5}; 
        printf("%6.2f\n",average(score1,5)); 
        printf("%6.2f\n",average(score2,10)); 
        return 0;
    }
    float average(float array[],int n) 
    { 
    	int i;
        float aver,sum=array[0];
        for(i=1;i<n;i++)
    	{
    		sum=sum+array[i];
    	}  
        aver=sum/n;
        return(aver);
    }
    

    编译结果如下:
    在这里插入图片描述

    7.9用选择法对数组中10个整数按由小到大排序

    解题思路:
    1)所谓选择法就是先将10个数中最小的数与a[0]对换;再将a[1]到a[9]中最小的数与a[1]对换……每比较一轮,找出一个未经排序的数中最小的一个
    2)共比较9轮

    #include <stdio.h>
    
    void sort(int array[],int n);
    
    int main()
    {
        int a[10],i;
        printf("enter array:\n");
        for(i=0;i<10;i++)  
    	{
    		scanf("%d",&a[i]);
    	}
        sort(a,10); 
        printf("The sorted array:\n");
        for(i=0;i<10;i++)   
    	{
    		printf("%d ",a[i]);
    	}
        printf("\n");
        return 0;
    } 
    void sort(int array[],int n)
    { 
    	int i,j,k,t;
        for(i=0;i<n-1;i++)
        { 
    		k=i;
            for(j=i+1;j<n;j++)
    		{
    			if(array[j]<array[k])
    			{
    				k=j;
    			}
    		}    
    	    t=array[k];
            array[k]=array[i];
            array[i]=t;
    	}
     }
    
    

    编译结果如下:
    在这里插入图片描述


    多维数组名作函数参数

    7.10有一个3×4的矩阵,求所有元素中的最大值

    解题思路:
    1)先使变量max的初值等于矩阵中第一个元素的值
    2)然后将矩阵中各个元素的值与max相比,每次比较后都把“大者”存放在max中,全部元素比较完后,max 的值就是所有元素的最大值。

    #include <stdio.h>
    
    int max_value(int array[][4]);
    
    int main()
    {
        int a[3][4]={{1,3,5,7},{2,4,6,8},{15,17,34,12}}; 
        printf("Max value is %d\n",max_value(a)); 
        return 0;
    }
    int max_value(int array[][4])
    {   
    	int i,j,max;
        max = array[0][0];
        for (i=0;i<3;i++)
        {
    		for(j=0;j<4;j++)
    		{
    			if (array[i][j]>max) 
    			{
    				max = array[i][j];
    			}
    		}
    	}    
    	return max;
    }
    

    编译结果如下:
    在这里插入图片描述


    局部变量和全局变量

    7.11有一个一维数组,内放10个学生成绩,写一个函数,当主函数调用此函数后,能求出平均分、最高分和最低分

    解题思路:
    1)调用一个函数可以得到一个函数返回值,现在希望通过函数调用能得到3个结果。
    2)可以利用全局变量来达到此目的。

    #include <stdio.h>
    
    float average(float array[],int n);
    
    float Max=0,Min=0;
                                           
    int main()
    {
        float ave,score[10];  int i;
        printf("Please enter 10 scores:\n");
        for(i=0;i<10;i++)
    	{
    		scanf("%f",&score[i]);
    	}
        ave=average(score,10);
        printf("max=%6.2f\nmin=%6.2f\naverage=%6.2f\n",Max,Min,ave);
        return 0;
     }
    float average(float array[],int n) 
    { 
    	int i;  
    	float aver,sum=array[0];
        Max=Min=array[0];
        for(i=1;i<n;i++)
        { 
    		if(array[i]>Max) 
    		{
    			Max=array[i];
    		}
            else if(array[i]<Min) 
    		{
    			Min=array[i];
    		}
           sum=sum+array[i]; 
        }
        aver=sum/n;
        return aver;
     }
    

    编译结果如下:

    在这里插入图片描述

    在这里插入图片描述
    说明:建议不在必要时不要使用全局变量

    若外部变量与局部变量同名,分析结果

    #include <stdio.h>
    
    int max(int a,int b);
    
    int a=3,b=5; 
    
    int main()
    {
        int a=8; 
        printf("max=%d\n",max(a,b)); //b为全局变量,a为局部变量,仅在此函数内有效
        return 0;   
    } 
    int max(int a,int b)        
    { 
    	int c;     
        c=a>b?a:b;
        return c; 
    }
    

    在这里插入图片描述

    7.12变量的存储方式:动态存储方式与静态存储方式

    从变量值存在的时间(即生存期)观察,变量的存储有两种不同的方式:静态存储方式和动态存储方式
    1)静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式
    2)动态存储方式是在程序运行期间根据需要进行动态的分配存储空间的方式

    在这里插入图片描述
    在这里插入图片描述
    静态存储区
    1)全局变量全部存放在静态存储区中

    2)程序开始执行时给全局变量分配存储区,程序执行完毕就释放。在程序执行过程中占据固定的存储单元

    动态存储区
    1)①函数形式参数②函数中定义的没有用关键字static声明的变量③函数调用时的现场保护和返回地址等存放在动态存储区

    2)函数调用开始时分配,函数结束时释放。在程序执行过程中,这种分配和释放是动态的
    简单说明:数据都存放在静态存储区和动态存储区


    每一个变量和函数都有两个属性:数据类型和数据的存储类别
    1)数据类型:如整型、浮点型等

    2)存储类别指的是数据在内存中存储的方式(如静态存储和动态存储)
    存储类别包括:自动的、静态的、寄存器的、外部的

    3)根据变量的存储类别,可以知道变量的作用域和生存期


    7.13局部变量的存储类别

    1.自动变量(auto变量)
    1)局部变量,如果不专门声明存储类别,都是动态地分配存储空间的

    2)调用函数时,系统会给局部变量分配存储空间,调用结束时就自动释放空间。因此这类局部变量称为自动变量

    3)自动变量用关键字auto作存储类别的声明
    在这里插入图片描述
    2.静态局部变量(static局部变量)
    1)函数中的局部变量在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次再调用该函数时,该变量已有值(就是上一次函数调用结束时的值),这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明

    2)考察静态局部变量的值:

    #include <stdio.h>
    
    int f(int);
    
    int main()
    {            
        int a=2,i;            
        for(i=0;i<3;i++)
    	{
    		printf("%d\n",f(a));
    	}
        return 0;
    }
    int f(int a)
    { 
    	auto int b=0; 
        static c=3;      
        b=b+1;
        c=c+1;
        return a+b+c;
    }
    

    编译结果如下:
    在这里插入图片描述

    3)输出1到5的阶乘值:
    解题思路:
    可以设计一个函数用来进行连乘,如第1次调用时进行1乘1,第2次调用时再乘以2,第3次调用时再乘以3,依此规律进行下去

    #include <stdio.h>
    
    int fac(int n);
    
    int main()
    {
        int i;
        for(i=1;i<=5;i++) 
    	{
    		printf("%d!=%d\n",i,fac(i));
    	} 
        return 0;
    }
    int fac(int n)
    { 
    	static int f=1; 
        f=f*n; 
        return f;   
    }
    
    

    编译结果如下:
    在这里插入图片描述
    3. 寄存器变量(register变量)
    1)一般情况下,变量(包括静态存储方式和动态存储方式)的值是存放在内存中的

    2)寄存器变量允许将局部变量的值放在CPU中的寄存器中

    3)现在的计算机能够识别使用频繁的变量,从而自动地将这些变量放在寄存器中,而不需要程序设计者指定


    7.14全局变量的存储类别

    ·全局变量都是存放在静态存储区中的。因此它们的生存期是固定的,存在于程序的整个运行过程

    ·一般来说,外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。

    1、 在一个文件内扩展外部变量的作用域
    1)外部变量有效的作用范围只限于定义处到本文件结束。

    2)如果用关键字extern对某变量作“外部变量声明”,则可以从“声明”处起,合法地使用该外部变量

    3)调用函数,求3个整数中的大者:
    解题思路:
    用extern声明外部变量,扩展外部变量在程序文件中的作用域。

    #include <stdio.h>
    
    int A,B,C;
    
    int max();
    
    int main()
    { 	     
        extern int A,B,C;  
    	printf("Please input 3 integers:\n");
        scanf("%d %d %d",&A,&B,&C); 
        printf("max is %d\n",max());
        return 0;
    }
    
    int max()    
    { 
    	int m;
        m=A>B?A:B; 
        if (C>m) 
        {
        	m=C; 
        }
        return m; 
    }
    

    编译结果如下:
    在这里插入图片描述

    2、将外部变量的作用域扩展到其他文件
    1)如果一个程序包含两个文件,在两个文件中都要用到同一个外部变量Num,不能分别在两个文件中各自定义一个外部变量Num

    2)应在任一个文件中定义外部变量Num,而在另一文件中用extern对Num作“外部变量声明”

    3)在编译和连接时,系统会由此知道Num有“外部链接”,可以从别处找到已定义的外部变量Num,并将在另一文件中定义的外部变量num的作用域扩展到本文件

    4)给定b的值,输入a和m,求a*b和a的m次方的值
    解题思路:
    (1)分别编写两个文件模块,其中文件file1包含主函数,另一个文件file2包含求am的函数。

    (2)在file1文件中定义外部变量A,在file2中用extern声明外部变量A,把A的作用域扩展到file2文件。

    #include <stdio.h>
    
    int a; 
    
    int main()                          
    { 
    	int power(int); 
        int b=3,c,d,m;  
    	printf("Please input a and m:");
        scanf("%d%d",&a,&m);
        c=a*b;
        printf("%d*%d=%d\n",a,b,c);
        d=power(m);
        printf("%d**%d=%d\n",a,m,d);
        return 0;
    }
    
    extern a; 
    
    int power(int n)
    { 
    	int i,y=1;
        for(i=1;i<=n;i++)
    	{
    		y*=a;
    	}
        return y;
    }
    

    编译结果如下:
    在这里插入图片描述
    3.将外部变量的作用域限制在本文件中
    有时在程序设计中希望某些外部变量只限于被本文件引用。这时可以在定义外部变量时加一个static声明。
    在这里插入图片描述
    简单说明:
    1)不要误认为对外部变量加static声明后才采取静态存储方式,而不加static的是采取动态存储

    2)声明局部变量的存储类型和声明全局变量的存储类型的含义是不同的

    3)对于局部变量来说,声明存储类型的作用是指定变量存储的区域以及由此产生的生存期的问题,而对于全局变量来说,声明存储类型的作用是变量作用域的扩展问题

    4)用static 声明一个变量的作用是:
    (1) 对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。
    (2) 对全局变量用static声明,则该变量的作用域只限于本文件模块(即被声明的文件中)。


    7.15存储类别小结

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    特殊:

    static对局部变量和全局变量的作用不同
    1)局部变量使变量由动态存储方式改变为静态存储方式

    2)全局变量使变量局部化(局部于本文件),但仍为静态存储方式

    3)从作用域角度看,凡有static声明的,其作用域都是局限的,或者是局限于本函数内(静态局部变量),或者局限于本文件内(静态外部变量)


    7.16关于变量的声明和定义

    1)把建立存储空间的变量声明称定义,而把不需要建立存储空间的声明称为声明
    2)在函数中出现的对变量的声明(除了用extern声明的以外)都是定义
    3)在函数中对其他函数的声明不是函数的定义

    内部函数

    1)如果一个函数只能被本文件中其他函数所调用,它称为内部函数

    2)在定义内部函数时,在函数名和函数类型的前面加static,即: static 类型名 函数名(形参表)

    3)内部函数又称静态函数,因为它是用static声明的

    4)通常把只能由本文件使用的函数和外部变量放在文件的开头,前面都冠以static使之局部化,其他文件不能引用

    外部函数

    1)如果在定义函数时,在函数首部的最左端加关键字extern,则此函数是外部函数,可供其他文件调用

    2)如函数首部可以为extern int fun (int a, int b)

    3)如果在定义函数时省略extern,则默认为外部函数

    4)有一个字符串,内有若干个字符,今输入一个字符,要求程序将字符串中该字符删去。用外部函数实现

    解题思路:
    1)分别定义3个函数用来输入字符串、删除字符、输出字符串
    2)按题目要求把以上3个函数分别放在3个文件中。main函数在另一文件中,main函数调用以上3个函数,实现题目的要求

    #include <stdio.h>
    
    extern void enter_string(char str[]);
    extern void delete_string(char str[],char ch);
    extern void print_string(char str[]); 
    
    int main()
    {
        char c,str[80];
        enter_string(str);  
    	printf("Please enter the character to delete:\n");
        scanf("%c",&c);	
        delete_string(str,c);  
        print_string(str);      
        return 0;     
    }
    
    void enter_string(char str[80]) 
    {
    	printf("Please input string:\n");
    	gets(str);   
    }
    
    void delete_string(char str[],char ch)
    { 
    	int i,j;
        for(i=j=0;str[i]!='\0';i++)
    	{
    		if(str[i]!=ch)
    		{
    			str[j++]=str[i];
    		}
    	}   
        str[j]='\0';
    } 
    
    void print_string(char str[]) 
    { 
    	printf("The string after deletion is:%s\n",str); 
    }
    

    编译结果如下:
    在这里插入图片描述

    展开全文
  • 一、本次课主要内容: ...最后,强调函数的优点,即结构化程序设计的思路,包括按自顶向下的方法对问题进行分析、模块化设计和结构化编码3 个步骤。 (2)以复数运算为例,介绍变量与函数的关...
  • 用函数实现模块化程序设计(二) ------- android培训、IOS培训、期待与您交流! ---------- ***一、函数的嵌套调用*** C语言的函数定义是互相平行、独立的。即函数不能嵌套定义,但可以嵌套调用函数。即调用...
  • 基本思路:把一个复杂问题的求解过程分阶段进行 ...(3)模块化设计(函数来实现,一般不超过50行) 划分子模块要注意模块的独立性,即使用一个模块完成一项功能,耦合性 越少越好。 (4)结构化编码 ...
  • c语言期中项目实战二—简易扫雷,思路分析加代码详细注释游戏介绍项目步骤模块化编程设置菜单设置棋盘存放雷的棋盘比真实所放雷的格子行和列都多二创建两个数组布置雷排查雷总结 游戏介绍 扫雷这个经典游戏,直到...
  • 设计思路 2.1 歌曲信息结构体 结构体内有五个数据 其中前四个是字符串类型的歌曲的各种数据,分别是歌曲名、作者、演唱者、发行年月 第五个定义了一个整形变量_exist,初始有效的结构体的时候会给这个变量...
  • 设计思路 2.1 歌曲信息结构体 结构体内有五个数据 其中前四个是字符串类型的歌曲的各种数据,分别是歌曲名、作者、演唱者、发行年月 第五个定义了一个整形变量_exist,初始有效的结构体的时候会给这个变量...
  • 做这个项目一个人大概用了一天的时间来完成,整体将近700行,量不是很多,所以也没用多文件什么的,当然也是采用了模块化设计思路,在代码中写了几个函数来实现特定的功能。 以下是这个项目的一些要求和全部源码...
  • C语言 车辆出租管理系统

    千次阅读 多人点赞 2018-06-13 22:10:35
    做这个项目一个人大概用了一天的时间来完成,整体将近700行,量不是很多,所以也没用多文件什么的,当然也是采用了模块化设计思路,在代码中写了几个函数来实现特定的功能。 以下是这个项目的一些要求和全部源码...
  • 该文档按照人的思维习惯和模块化程序设计思路,对C语言如何编写WINDOW服务程序做了深入探讨,并且贴出全部源代码。
  • C语言学习-函数

    千次阅读 2016-07-18 22:19:47
    程序设计思路: 自顶向下、逐步求精 程序结构按功能划分为若干个基本模块,形成一个树状结构 各模块间的关系尽可能简单,功能上相对独立 每一模块内部均是由顺序、选择和循环三种基本结构组成 其模块化实现的具体...
  • C语言学生信息管理系统终极版

    千次阅读 2021-01-09 16:06:14
    1.模块化设计C语言的特点 2.防止特殊情况导致程序崩溃——空文件,所以重要函数都设有保护机制 3.UI方面实在无能为力,C语言知识就储备那一点,没办法 · · · 运行结果: · · · 注意: 照搬源码可能会出错,...
  • DSP技术目前已经被广泛的应用在了集成芯片的设计研发之中,且其应用范围在最近几年中明显扩大。...在这一应用了DSP技术集成芯片的正弦波信号发生器方案中,为了简化程序,本系统的软件可以按照模块化设计思想来...
  • C语言入门之函数

    2019-06-16 23:35:59
    为什么要用函数 问题: 如果程序的功能比较多,规模比较大... 解决的方法:用模块化程序设计思路 采用“组装”的办法简化程序设计的过程 事先编好一批实现各种不同功能的函数 把它们保存在函数库中,需...
  • 第一天之C语言到C++

    2019-11-04 08:31:29
    设计思路 – 自顶向下、逐步求精。采用模块分解与功能抽象,分而治之。 程序结构 – 按功能划分为若干个基本模块,形成一个树状结构。 – 各个模块之间的关系尽可能简单,功能上相对独立;每一个模块的内部都是...
  • 1. 模块化程序设计 基本思想:将一个大的程序按功能分割成一些小模块 要求:各模块相对独立、功能单一(一个函数完成一个任务)、结构清晰、接口简单 设计思路: 将所要实现的目标分解成一个个功能单一的小模块(先...
  • C语言--函数嵌套

    2018-01-01 22:16:00
    2.要求用模块化方式组织程序结构,合理设计各自定义函数。同时,程序能够进行异常处理,检查用户输入数据的有效性,用户输入数据有错误,如类型错误或无效不会中断程序执行,具有一定的健壮性。 1.1 PTA题目 十进制...

空空如也

空空如也

1 2 3 4 5
收藏数 95
精华内容 38
关键字:

c语言模块化设计思路

c语言 订阅