ESP32上实现面向对象的C++ OOP——头文件、源文件、构造与析构函数
前言回顾
在之前我们已经充分了解过ESP32是如何利用面向对象的思想和方法进行点灯的,包括面向对象的类、对象、类方法的调用等等都进行了详细的说明,但是不少同学反映呀,功能也就实现那么几个,但是代码看起来就臃肿不堪了,再写下去看都得看糊涂了!!!
因此,今天这篇博客我们聊聊如何让你的面向对象开发更加灵活、方便、顺畅!!!
PREVIOUS CODE:
class LED{
private:
/*
* 属性:
* pin 引脚
*/
byte pin;
public:
/*
* 行为(方法):
* setPin(设置引脚) & getPin(获取引脚)
* on(开灯) & off(关灯) & toggle(切换)
*/
void setPin(byte pin_param);
byte getPin();
void on();
void off();
void toggle();
};
void LED::setPin(byte pin_param){
if ((pin_param < 40) && (pin_param >= 0)){
pin = pin_param;
} else {
pin = 2;
Serial.println("invalid pin number,use default pin 2");
}
pinMode(pin, OUTPUT);
}
byte LED::getPin(){
return pin;
}
// 格式:方法类型 类名::方法名
void LED::on(){
digitalWrite(pin, HIGH);
delay(1000);
}
void LED::off(){
digitalWrite(pin, LOW);
delay(1000);
}
void LED::toggle(){
digitalWrite(pin,!digitalRead(pin));
delay(100);
}
// 实例化类的对象
LED ledGreen,ledRed;
void setup() {
Serial.begin(115200);
ledRed.setPin(33);
ledRed.on();
ledRed.off();
}
void loop() {
ledRed.toggle();
}
拆分!让你的代码不再臃肿
将代码拆分成主程序、头文件、源文件;
拆分头文件
.h 头文件 接口文件
存放代码的头文件*(头文件是给大家看的)*
需要与arduino链接,即加载arduino头文件:#include “Arduino.h”
#include "Arduino.h"
// LED.h 头文件
class LED{
private:
/*
* 属性:
* pin 引脚
*/
byte pin;
public:
/*
* 行为(方法):
* setPin(设置引脚) & getPin(获取引脚)
* on(开灯) & off(关灯) & toggle(切换)
*/
void setPin(byte pin_param);
byte getPin();
void on();
void off();
void toggle();
};
拆分源文件
.cpp 源文件 实现文件
主要存放类实现的具体源代码*(源文件是给自己看的)*
需要与头文件链接,即加载头文件:#include “LED.h”
#include “LED.h”
// LED.cpp 源文件
void LED::setPin(byte pin_param){
if ((pin_param < 40) && (pin_param >= 0)){
pin = pin_param;
} else {
pin = 2;
Serial.println("invalid pin number,use default pin 2");
}
pinMode(pin, OUTPUT);
}
byte LED::getPin(){
return pin;
}
// 格式:方法类型 类名::方法名
void LED::on(){
digitalWrite(pin, HIGH);
delay(1000);
}
void LED::off(){
digitalWrite(pin, LOW);
delay(1000);
}
void LED::toggle(){
digitalWrite(pin,!digitalRead(pin));
delay(100);
}
通过预处理来防止多次导入相同代码
有时候我们在引入文件的时候会出现重复引入的现象,这时候程序将会报错,所以我们在引入文件的时候通常会采用预处理的方式来防止多次导入相同的代码。
ifndef是 if not define 的缩写,一种宏定义。它是预处理功能中三种(宏定义,文件包含和条件编译)中的第三种–条件编译。
其使用方式是:
#define X
...
#endif
c语言在对程序进行编译时,会先根据预处理命令进行预处理,C语言编译系统包括预处理,编译和链接等部分。
#ifndef X //先测试是否被宏定义过
#define X
程序段1//如果X没有被宏定义过,定义X,并编译程序段1;
#else
程序段2 //如果X已经定义过了则编译程序段2 的语句,忽视程序段1.
#endif
条件指示符#ifndef的最主要目的是防止头文件的重复包含和编译。
语句1 #ifndef 标识1
语句2 #define 标识1
语句3 #endif
语句4 ……
语句5 ……
该段代码意思是:如果标识1没有被定义,则重定义标识1,即执行语句2、语句3;如果标识1已经被定义,则直接跳过语句2、语句3,直接执行语句4、语句5、……
ifndef和endif要一起使用,不能存在丢失。
假如你有一个C源文件,它包含了多个头文件,比如头文件A和头文件B,头文件B又包含了头文件A,则最终该源文件包含了两次头文件A。如果你在头文件A里定义了结构体或者类类型(这是最常见的情况),那么问题来了,编译时会报大量的重复定义错误。
例如要编写头文件a.h,需要在头文件开头写上两行:
#ifndef _A_H
#define _A_H//一般是文件名的大写
头文件结尾写上一行:
#endif
这样一个工程文件里同时包含两个test.h时,就不会出现重定义的错误了。
当第一次包含test.h时,由于没有定义_TEST_H,条件为真,这样就会包含(执行)#ifndef _TEST_H 和 #endif之间的代码,当第二次包含test.h时前面一次已经定义了_TEST_H,条件为假,#ifndef _TEST_H和 #endif之间的代码也就不会再次被包含,这样就避免了重定义了。这样我们把头文件的内容都放在#ifndef和#endif中。不管你的头文件会不会被多个文件引用,你最好是都加上这个。
拿本示例代码举例,如下:
#ifndef LED_H
#define LED_H
#include "Arduino.h"
// LED.h 头文件
class LED{
private:
/*
* 属性:
* pin 引脚
*/
byte pin;
public:
/*
* 行为(方法):
* setPin(设置引脚) & getPin(获取引脚)
* on(开灯) & off(关灯) & toggle(切换)
*/
void setPin(byte pin_param);
byte getPin();
void on();
void off();
void toggle();
};
#endif
构造函数和析构函数
构造函数
在我们实例化类对象时,计算机会做两件事儿:
- 第一步:给对象分配内存空间地址;
- 第二步:执行构造函数对“对象”进行初始化;
第一步我们不需要太过考虑,第二步我们可以仔细看看!
首先什么是构造函数?
你可以理解为在实例化时会自动执行的函数,并且我用了一个词是默认,即每个类实际上都会有个默认的构造函数,就算你没有定义,它也会被默认创建,默认执行。
当然我们也可以自己定义构造函数,以格式如下**(与类名一致)**:
// **以LED类为例** **.h 头文件 接口文件**
class LED{
// LED():默认构造函数 & LED(byte pin_param):重载构造函数
LED();
LED(byte pin_param);
}
// **以LED类为例 .cpp 源文件 实现文件
// 默认构造函数
LED::LED(){
Serial.println("默认构造函数");
}
// 重载构造函数
LED::LED(**byte pin_param**){**
pin = pin_param;
**Serial.println("重载构造函数");
}**
当然,你可以利用构造函数做很多事情,包括给私有变量赋值,更加灵活实例化对象等
另外一种重载构造函数赋值的表示:
// **以LED类为例 .cpp 源文件 实现文件
// 重载构造函数
LED::LED(**byte pin_param**) : pin(pin_param){
Serial.println("重载构造函数");
}**
析构函数
刚才说了“对象”在创建时有构造函数,在“对象”被抛弃的时候,也做一件事儿:
- 运行析构函数来对资源进行回收
首先什么是析构函数?
你可以理解为在对象资源被回收释放的时候执行的函数。
当然我们也可以自己定义析构函数,以格式如下**(~类名)**:
// **以LED类为例** **.h 头文件 接口文件**
class LED{
// ~LED():析构函数
~LED();
}
// **以LED类为例 .cpp 源文件 实现文件
// 析构函数
LED::~LED(){
Serial.println("执行析构函数");
}