代码写到一定程度是一定要封装,别等到写了一千多行两千多行的时候才想起来要封装,那你就完了,除非你的水平一定够!
具体步骤:
-
规划功能:明确需要封装的功能和预期的接口
打开工程目录,在主文件中创建相关文件夹,然后点开该文件夹在里面创建两个.c文件和.h文件
-
创建文件夹:
在Keil项目中创建一个新的文件夹(尽量与工程目录所创建的文件夹同名),用于存放相关的.c
和.h
文件。 -
引入相关源文件(.c文件)和头文件(配置包含路径)
-
配置包含路径(如果需要):确保新创建的文件的路径已经被包含在项目的包含路径中,这样编译器才能正确找到这些文件。
-
编写源文件:
在SystemTick.c
中定义SystemTick.h
中声明的函数和变量,并实现具体的功能逻辑。 -
编写头文件:
在SystemTick.h
中声明公共函数原型、宏定义、数据类型和使用extern
声明的全局变量。 -
编译项目:
保存所有更改,并尝试编译项目以确保没有错误。 -
测试功能:
编写测试代码或使用单元测试来验证封装的功能是否按预期工作。 -
调试:
使用Keil的调试工具进行调试,确保封装的功能正确执行。
.c文件
//.c文件
#include"SystemTick.h"
#include"gd32f30x.h"
static volatile SYSTEM_TICK_TYPE gSysTickOvfcnt=0;
static unsigned int gSysTickPerUs = 0;
void SysTick_Handler(){++gSysTickOvfcnt;}
#define SYSTICK_REAL ((~SysTick->VAL)&0xFFFFFF|(gSysTickOvfcnt)<<24)
SYSTEM_TICK_TYPE GetSystemRealTick(){return (SYSTEM_TICK_TYPE)SYSTICK_REAL;}
void delay_us(unsigned int us){
unsigned long long dest = SYSTICK_REAL + us*gSysTickPerUs;
while(SYSTICK_REAL < dest);
}
void SystemTickInit(){
SysTick_Config(0x1000000);
systick_clksource_set(SYSTICK_CLKSOURCE_HCLK_DIV8);//8分频->15M
gSysTickPerUs = rcu_clock_freq_get(CK_AHB)/8000000;//不写成8e6是因为它是double型(warning:'double'to 'int')
}
想要头文件多个地方包含里面不能放定义。
这样子写只会被编译器查看一次,加快我们的编译速度同时避免变量的重复声明,避免warning和error。
不想要别人使用你的变量,可以在不定义变量前加static,静态变量外界无法访问。
.h文件
//.h头文件
#ifndef SYSTEM_TICK_H
#define SYSTEM_TICK_H
typedef unsigned long long SYSTEM_TICK_TYPE;
extern void delay_us(unsigned int us);
extern void SystemTickInit();
extern SYSTEM_TICK_TYPE GetSystemRealTick();
#endif
函数声明:在头文件中使用
extern
关键字声明了delay_us
和SystemTickInit
函数。这意味着这些函数的定义在其他源文件中,但是在包含此头文件的源文件中可以调用这些函数。避免重复定义:由于这些函数在
.c
文件中被定义,使用extern
在头文件中声明可以避免在其他源文件中包含时重复定义这些函数。提供接口:头文件为其他源文件提供了一个接口,其他源文件可以通过包含这个头文件来使用这些函数,而不需要关心这些函数的具体实现细节。
全局访问点:
GetSystemRealTick
函数被声明为返回SYSTEM_TICK_TYPE
类型的值,它允许其他源文件通过这个接口获取系统滴答计数。静态变量声明:在
.c
文件中,gSysTickOvfcnt
和gSysTickPerUs
被声明为static
,这意味着它们的作用域限定在该源文件内。由于这些变量仅在这个源文件(.c文件)中使用,所以不需要在头文件中声明。SysTick_Handler 函数:这个函数作为中断处理函数,通常在
.c
文件中定义,不需要在头文件中声明,因为它只在当前源文件中使用,并且由编译器自动处理中断向量表。宏定义:
SYSTICK_REAL
宏定义用于简化对系统滴答计数的访问,它在.c
文件中定义,因为它依赖于.c
文件中定义的变量gSysTickOvfcnt
。总结来说,头文件
SYSTEM_TICK_H
用于声明可以在其他源文件中使用的函数和类型定义,而具体的实现(函数定义和静态变量)放在.c
文件中,这样有助于保持代码的模块化和组织性。
-
头文件(.h):
- 函数声明:声明你将在其他源文件中定义的函数。
- 数据类型定义:如使用
typedef
或struct
定义的自定义数据类型。 - 宏定义:使用
#define
预处理指令定义的宏。 - 常量定义:全局使用的常量。
- 外部变量声明:使用
extern
关键字声明的全局变量。 - 包含其他头文件:如果功能实现需要其他库的支持。
-
源文件(.c 或 .cpp):
- 函数定义:对应头文件中声明的函数的具体实现。
- 外部变量定义:对应头文件中声明的
extern
变量的实际定义。 - 静态函数和变量:只在本源文件中使用的函数和变量定义。
- 主函数(
main
):如果是可执行程序的入口点。 - 其他逻辑实现:包括条件语句、循环等实现具体功能的代码。
相关知识
在C或C++编程中,如果一个头文件被多个源文件包含,而头文件中包含了定义(如变量定义、函数定义等),这可能会导致编译错误,因为编译器会尝试多次定义相同的实体。为了避免这种情况,通常使用预处理指令来确保头文件中的内容只被包含一次。这通常通过使用包含守卫(也称为头文件保护)来实现。
以下是实现包含守卫的步骤:
-
定义包含守卫:在头文件的最开始处使用
#ifndef
、#define
和#endif
预处理指令来定义一个唯一的宏。#ifndef SYSTEM_TICK_H #define SYSTEM_TICK_H #endif
-
放置头文件内容:在
#ifndef
和#endif
之间放置你的头文件内容,包括函数声明、模板声明、类声明、宏定义等。 -
避免在头文件中定义函数:如果可能,避免在头文件中定义函数(尤其是非内联函数)。如果需要定义函数,可以使用内联函数,因为内联函数的定义在预处理阶段会被展开,不会违反一次定义规则。
-
使用
static
关键字:在头文件中定义的变量,如果只在该文件内部使用,可以使用static
关键字,这样变量就不会被暴露给其他包含此头文件的源文件。
内联函数 :通常定义在.h
文件中,并且定义时必须为static
类型,以避免在编译时出现未定义的错误。但是,这里的static
指的是内联函数本身的静态作用域,而不是指函数内部的静态变量。实际上,内联函数内部使用静态变量是不推荐的,因为这会破坏内联函数的预期行为,即在每个调用点展开为函数体代码。