【M5Stack物联网开发】第三章 屏幕模块
https://github.com/oursrabbit/M5StackIoT
1 使用屏幕模块
1.1 屏幕属性
M5Stack Core2 v1.1 屏幕是一款IPS LCD屏幕,尺寸为2.0英寸,分辨率为320x240。该屏幕使用了ILI9342C驱动器,还配备了FT6336U型号的触摸屏。
屏幕的左上角是原点(0, 0),其他位置的坐标会随着屏幕旋转而变化。
坐标系详解
-
X轴:水平方向,从左到右。最小值为0(屏幕最左边),最大值为319(屏幕最右边)。
-
Y轴:垂直方向,从上到下。最小值为0(屏幕最顶部),最大值为239(屏幕最底部)。
屏幕旋转与坐标系
M5Stack Core2 的显示屏支持旋转功能,这意味着你可以通过编程改变屏幕的方向,这将影响坐标系的定位。例如,如果将屏幕旋转90度,原点(0, 0)将移动到原来屏幕的左下角,X轴和Y轴的方向也会相应调整。
触摸坐标
如果使用屏幕的触摸功能,触摸坐标也会遵循同样的规则。即触摸点的位置将以像素为单位返回,根据屏幕的当前旋转状态,坐标可能会有所变化。
使用M5Unified API中的Display模块可以对屏幕进行各种操作。Display模块中的函数主要分为以下几类:
初始化和配置:
- 这类函数用于初始化显示屏,如设置屏幕背景色
- 这类函数通常命名为setXXXX(....)
示例函数:setColor(),setRotation()
,setBrightness()
图形绘制:
- 提供了绘制各种图形的函数,如点、线、圆、矩形等。
- 这类函数通常命名为drawXXXX(....)以及fillXXXX(....)
- draw函数用来画空心的图形,fill函数用来画实心的图形
- 示例函数:
drawPixel()
,drawLine()
,drawRect()
,drawCircle()
,fillRect()
,fillCircle()
文本显示:
- 用于在屏幕上显示文本,可以设置字体、大小、颜色等。
- 这类函数通常命名为setTextXXXX(....)、drawXXXX(....)、printXXXX(.....)
- setTextXXXX函数可以修改文字的外观
- drawString函数可以绘制自定义的文字,默认情况下只能打印英文字符和数字
- printXXXX函数可以对字符串进行格式化,并输出在屏幕上,默认只能打印英文字符和数字
- 示例函数:
drawString()
,setTextFont()
,setTextSize()
,setTextColor(),printf(),println()
图片显示:
- 这类函数允许用户在屏幕上显示图像。
- 示例函数:
drawBitmap()
,drawPngFile(),drawJpegUrl()
屏幕控制:
- 包括清屏、调整屏幕亮度、睡眠模式等控制功能。
- 示例函数:
clear()
,sleep()
,wakeup()
触摸功能:
- 如果屏幕支持触摸功能,这类函数可以用来检测和获取触摸事件。
- 示例函数:
getTouch()
1.2 打印文字
// 使用M5Unified API时需要引入该API的头文件 #include <M5Unified.h> // 系统初始化函数,只在程序启动时执行一次 void setup() { // 初始化M5系统 auto cfg = M5.config(); M5.begin(cfg); // 清屏,将背景设定为绿色 M5.Display.clear(GREEN); // 将输出文字设定为红色 M5.Display.setTextColor(RED); // 将输出文字的大小设定为5号 M5.Display.setTextSize(5); // 将文字光标移动到屏幕像素(100,100)的位置 M5.Display.setCursor(100, 100); // 输出单词Hello M5.Display.print("Hello"); } // 程序的主循环函数,会被不停的调用并执行里面的内容 void loop() { }
1.3 打印图形
#include <M5Unified.h> void setup() { auto cfg = M5.config(); M5.begin(cfg); M5.Display.clear(GREEN); // 用红色填充一个圆形 // 圆心坐标为(50,50),直径为20 M5.Display.fillCircle(50, 50, 20, RED); // 用深灰色画一个空心矩形 // 矩形左上角坐标为(150,150),宽为80,高为30 M5.Display.drawRect(150, 150, 80, 30, DARKGREY); // 用黄色画一条贝塞尔曲线 // 四个控制点为(100,50),(150,20),(200,70),(250,50) M5.Display.drawBezier(100, 50, 150, 20, 200, 70, 250, 50, YELLOW); } void loop() { }
1.4 打印图片
// 使用SD卡,需要引入SD头文件 #include <SD.h> #include <M5Unified.h> void setup() { auto cfg = M5.config(); M5.begin(cfg); // 初始化SD库 SD.begin(GPIO_NUM_4, SPI, 25000000); M5.Display.clear(GREEN); // 从SD卡中,显示文件名为cat.png的图片 M5.Display.drawPngFile(SD, "/cat.png"); } void loop() { }
1.5 格式化文本
在C++开发中,可以使用C风格的printf
函数来进行格式化输出。printf
函数是从C语言继承来的,包含在M5.Display模块中。使用printf
可以提供更多的格式控制,并且在某些情况下执行速度比C++风格的输出更快。
基本语法
printf
的基本语法是:
printf("格式字符串、转义字符串", 参数列表...);
格式字符串
格式字符串包含文本和格式说明符。格式说明符用于指定如何格式化一个或多个传递给printf
的值。格式说明符通常以%
字符开始。
常用的格式说明符
%d
或%i
:输出十进制整数。%u
:输出无符号十进制整数。%f
:输出浮点数(默认情况下输出6位小数)。%e
或%E
:以科学计数法输出浮点数。%g
或%G
:自动选择%f
或%e
(或%E
),取决于数值的大小。%x
或%X
:输出十六进制数,%x
输出小写字母,%X
输出大写字母。%o
:输出八进制数。%s
:输出字符串。%c
:输出单个字符。%%
:输出%
字符。
格式控制选项
- 宽度和精度:
%5d
表示输出的整数至少占5个字符宽,如果不足5个字符将在前面填充空格。%.2f
表示输出的浮点数小数部分占2位。 - 左对齐:
%-5d
表示输出的整数至少占5个字符宽,并且是左对齐。 - 正负号:
%+d
表示输出的整数总是显示符号(正数显示+
,负数显示-
)。
转移字符串
在使用printf
函数时,可以通过转义字符来控制输出的格式或表示一些特殊的字符。转义字符通常由反斜杠\
后跟一个或多个字符组成。下面是一些常用的转义字符。
常用的格式说明符
\n
:换行符,将光标移至下一行的开始。\t
:水平制表符,用于在文本中添加一个制表位,通常用于对齐输出。\b
:退格符,将光标向前移动一格(向左移动)。\r
:回车符,将光标移动到当前行的开头。\\
:表示一个反斜杠字符\
。\'
:表示一个单引号字符'
。\"
:表示一个双引号字符"
。\0
:空字符,用于字符串的结束标志,在C语言中字符串以\0
作为结束。\a
:响铃符,通常会使系统发出警告声。\f
:换页符,用于打印文档时,将光标移至下一页的开始。\v
:垂直制表符,用于在文本输出中垂直跳跃到下一个制表位。
示例代码
#include <M5Unified.h> void setup() { auto cfg = M5.config(); M5.begin(cfg); int a = 255; double pi = 3.14159; String str = "Hello, world!"; // 转义字符和格式化字符可以混合使用 // 转义字符例子 // \n,表示new line,开始新的一行 M5.Display.printf("Hello, World!\n"); // \",表示打印" M5.Display.printf("He said, \"Hello, I'm your teacher.\"\n"); // 一个printf函数中可以有多个格式化字符,但是格式化字符的数量必须和参数一致 // 格式化字符例子 // %d,以整数为格式输出a M5.Display.printf("Integer: %d\n", a); // %x,以十六进制的形式输出数字 // #,在十六进制数字前显示0x M5.Display.printf("Unsigned hexadecimal: %#x\n", a); // %f,以小数的形式输出数字 // .3,保留小数点后,前三位 M5.Display.printf("Floating point: %.3f\n", pi); // %e,以科学计数法,输出数字 M5.Display.printf("Scientific: %e\n", pi); // %s,以字符串形式输出内容 // %c,以单个字符的形式输出内容 M5.Display.printf("String: %s\nCharacter: %c", str, 'A'); } void loop() { }
2 小程序2:倒计时交通灯
2.1 功能分析
在当今快速发展的城市环境中,交通管理系统的有效性对于确保行人和车辆安全至关重要。其中,交通信号灯作为调控交通流的基本设施,其智能化和可视化设计显得尤为重要。本小节将详细探讨如何在屏幕上展示一个包含红黄绿三色灯、中间倒计时以及右侧行人动画的交通信号灯系统。这种设计不仅能提高交通信号的直观性,还能通过动态交互增强行人的遵守意识,从而提升整个交通系统的效率和安全性。
2.2.1 设计界面布局
信号灯部分
在设计界面的左侧,我们需要垂直排列红、黄、绿三色灯。每个灯应采用圆形图形表示,以符合公众对传统交通信号灯的普遍认知。为了增强视觉效果,每个灯圈可以设计成内部填充鲜明的颜色,当相应的灯亮起时,其他两个灯则保持灰色状态,以突出当前的信号指示。
倒计时显示
倒计时的显示位置应位于屏幕中央,以确保其在视觉上的主导地位。倒计时数字需要设计得大而清晰,背景与数字颜色对比明显,确保在不同的光照环境下都能清楚地显示。此外,倒计时的字体与大小应考虑易读性,避免过于花哨的字体可能带来的阅读障碍。
行人动画
屏幕右侧应展示行人走路的动画。这个动画可以从简单的行人图标在屏幕上水平移动,逐步演变为更复杂的帧动画,如行人行走的各个动作逐一展示。动画的设计应简洁明了,颜色与背景对比度高,确保其在各种设备上的兼容性和清晰度。
2.2.2 功能分析
信号灯控制
- 红灯:亮起时表示停止,所有车辆和行人均不得通行。
- 黄灯:亮起时表示预备,即将变为红灯或绿灯,提醒司机和行人注意即将变化的信号。
- 绿灯:亮起时表示可以通行,车辆和行人可以安全通过路口。
灯光的切换应基于设定的时间间隔,这个间隔可以根据实际交通流量和时间段进行调整,以优化交通流。
倒计时功能
倒计时应与信号灯同步进行。例如,当红灯亮起时,倒计时开始,持续到下一个信号灯亮起。倒计时结束时,系统应自动切换到下一个信号灯,从而保持交通信号的连续性和流畅性。
行人动画
行人动画的启动应与绿灯信号同步。当绿灯亮起时,行人动画开始,模拟行人过马路的过程。动画的持续时间应与绿灯的倒计时相匹配,确保在红灯亮起前行人已经安全地走完过马路。这种同步设计不仅增加了交通信号的互动性,还能有效提醒行人注意剩余的安全通行时间。
2.2 伪代码
2.2.1 伪代码是什么
伪代码(Pseudocode)是一种非正式的编程语言,用来描述算法的结构而不依赖于具体的编程语言语法。伪代码主要用于算法设计和编程问题的解决方案规划阶段,它帮助程序员理解和展示算法的逻辑流程,而不需要关心代码的具体实现细节。
特点
- 语言无关性:伪代码不依赖于任何具体的编程语言,它使用自然语言和简单的控制结构(如果-那么,当满足条件时,循环)来描述算法。
- 易读性:伪代码的目的是使算法易于理解,通常比实际的编程代码更容易读懂。
- 结构化:虽然伪代码不遵循具体的语法规则,但它保持一定的结构性,使得转换成实际的编程语言代码变得相对直接。
示例
假设我们需要编写一个算法,用于查找数组中的最大值。以下是用伪代码描述的算法:
算法:查找最大值 输入:一个数值列表 nums 输出:列表中的最大值 开始 如果 nums 为空 返回 "列表为空" 否则 设 max_value 为 nums 的第一个元素 对于 nums 中的每一个元素 element 如果 element 大于 max_value max_value 赋值为 element 返回 max_value 结束
int findMaxValue(int arr[], int length) { if (length == 0) { return -1; // 如果数组为空,返回-1代表错误 } int max_value = arr[0]; // 将最大值初始化为数组的第一个元素 for (int i = 1; i < length; i++) { // 遍历数组 if (arr[i] > max_value) { max_value = arr[i]; // 更新最大值 } } return max_value; }
2.2.2 伪代码
函数 drawLight
开始
如果 交通状态是 红灯
在显示屏上绘制红灯位置的红色圆圈
在显示屏上绘制黄灯位置的灰色圆圈
在显示屏上绘制绿灯位置的灰色圆圈
否则如果 交通状态是 黄灯
在显示屏上绘制红灯位置的灰色圆圈
在显示屏上绘制黄灯位置的黄色圆圈
在显示屏上绘制绿灯位置的灰色圆圈
否则
在显示屏上绘制红灯位置的灰色圆圈
在显示屏上绘制黄灯位置的灰色圆圈
在显示屏上绘制绿灯位置的绿色圆圈
结束
函数 drawSecond
开始
如果 交通状态是 红灯
设置显示倒计时文本的颜色为红色
否则如果 交通状态是 黄灯
设置显示倒计时文本的颜色为黄色
否则
设置显示倒计时文本的颜色为绿色
设置文本大小为
设置显示光标位置
显示倒计时
结束
函数 drawAnim 开始 如果绘制需要绘制动画的第0帧 设置绘制的动画图片为第0帧对应的图片文件 "/w1.png" 否则如果绘制需要绘制动画的第1帧 设置绘制的动画图片为第1帧对应的图片文件 "/w2.png" 否则 设置绘制的动画图片为第3帧对应的图片文件 "/w3.png" 在显示设备上从 SD 卡读取并显示文件 更新下一次需要显示的动画图片帧数 结束
2.3 功能实现
// 首先定义一些常量,并加载需要的头文件 #include <SD.h> #include <M5Unified.h> // 当前交通信号灯的状态 #define RedLight 0 #define YelloLight 1 #define GreenLight 2 int trafficStatus = 0; // 倒计时文字在屏幕显示的位置与大小 #define countDownTimePositionX 140 #define countDownTimePositionY 80 #define countDownTimeSize 5 // 每种颜色信号灯倒计时的时间,单位为毫秒 #define redTime 3000; #define yellowTime 3000; #define greenTime 3000; // 当前颜色信号灯还剩的倒计时时长 int countdownTime = 0; // 信号灯绘制的位置 #define RedLightPositionX 70 #define RedLightPositionY 50 #define RedLightRadius 25 #define YelloLightPositionX 70 #define YelloLightPositionY 120 #define YelloLightRadius 25 #define GreenLightPositionX 70 #define GreenLightPositionY 190 #define GreenLightRadius 25 // 动画绘制的位置,与当前动画的帧数 #define AnimPositionX 210 #define AnimPositoinY 80 int animIndex = 0;
// 使用fillCircle绘制实心信号灯 // fillCircle需要传递圆心的X、Y坐标,直径,以及填充色作为参数 void drawLight() { if (trafficStatus == RedLight) { M5.Display.fillCircle(RedLightPositionX, RedLightPositionY, RedLightRadius, RED); M5.Display.fillCircle(YelloLightPositionX, YelloLightPositionY, YelloLightRadius, DARKGREY); M5.Display.fillCircle(GreenLightPositionX, GreenLightPositionY, GreenLightRadius, DARKGREY); } else if (trafficStatus == YelloLight) { M5.Display.fillCircle(RedLightPositionX, RedLightPositionY, RedLightRadius, DARKGREY); M5.Display.fillCircle(YelloLightPositionX, YelloLightPositionY, YelloLightRadius, YELLOW); M5.Display.fillCircle(GreenLightPositionX, GreenLightPositionY, GreenLightRadius, DARKGREY); } else { M5.Display.fillCircle(RedLightPositionX, RedLightPositionY, RedLightRadius, DARKGREY); M5.Display.fillCircle(YelloLightPositionX, YelloLightPositionY, YelloLightRadius, DARKGREY); M5.Display.fillCircle(GreenLightPositionX, GreenLightPositionY, GreenLightRadius, GREEN); } }
void drawSecond() { if (trafficStatus == RedLight) M5.Display.setTextColor(RED); else if (trafficStatus == YelloLight) M5.Display.setTextColor(YELLOW); else M5.Display.setTextColor(GREEN); M5.Display.setTextSize(countDownTimeSize); M5.Display.setCursor(countDownTimePositionX, countDownTimePositionY); // 程序中计时器使用的单位是毫秒 // 在屏幕显示的时候使用的单位是秒 int displayTime = countdownTime / 1000; M5.Display.print(displayTime); }
void drawAnim() { auto filePath = ""; if (animIndex == 0) filePath = "/w1.png"; else if (animIndex == 1) filePath = "/w2.png"; else filePath = "/w3.png"; // 在屏幕上输出一个图片 // 参数为,图片所在的存储器,图片文件地址 // 图片左上角在屏幕上显示的X,Y坐标 // 图片可以最大显示的宽、高 M5.Display.drawPngFile(SD, filePath, AnimPositionX, AnimPositoinY, M5.Display.width(), M5.Display.height()); // %,取余符号 // 0%2=0,1%2=1,2%2=0,3%2=1 // 10%100=10,115%100=15,12345%100=45 animIndex = (animIndex + 1) % 3; }
void switchTrafficLight() { trafficStatus = (trafficStatus + 1) % 3; switch (trafficStatus) { case RedLight: countdownTime = redTime; break; case YelloLight: countdownTime = yellowTime; break; case GreenLight: countdownTime = greenTime; break; } }
void drawTrafficLight() { M5.Display.clear(BLACK); drawSecond(); drawLight(); drawAnim(); } void setup() { SD.begin(GPIO_NUM_4, SPI, 25000000); auto cfg = M5.config(); M5.begin(cfg); // green,初始化为绿灯 trafficStatus = GreenLight; countdownTime = greenTime; } void loop() { drawTrafficLight(); // 每一秒绘制一次 countdownTime = countdownTime - 1000; // 如果倒计时为0,就切换红绿灯 if (countdownTime <= 0) { switchTrafficLight(); } // 让系统等待一秒后,在执行下一次loop delay(1000); }
2.4 C++:控制流
2.4.1 布尔运算
布尔运算(Boolean operations)是基于布尔代数的运算,主要用于逻辑运算,其操作数和结果都是布尔值(true
或 false
)。布尔运算在计算机科学、电子工程、数学逻辑以及日常编程中非常重要,它们是控制逻辑和条件判断的基础。
常用的布尔运算符
- 逻辑与(AND,
&&
):只有当所有操作数都为true
时,结果才为true
。否则,结果为false
。 - 逻辑或(OR,
||
):只要至少有一个操作数为true
,结果就为true
。如果所有操作数都为false
,结果为false
。 - 逻辑非(NOT,
!
):如果操作数为true
,结果为false
;如果操作数为false
,结果为true
。
真值表
- 逻辑非(NOT)真值表
逻辑非运算符只有一个输入。它的作用是反转输入的布尔值。
输入 | 输出 |
---|---|
true | false |
false | true |
- 逻辑与(AND)真值表
逻辑与运算符需要两个输入。只有当两个输入都为 true
时,输出才为 true
;否则,输出为 false
。
输入 A | 输入 B | 输出 |
---|---|---|
false | false | false |
false | true | false |
true | false | false |
true | true | true |
- 逻辑或(OR)真值表
逻辑或运算符也需要两个输入。只要至少有一个输入为 true
,输出就为 true
;如果两个输入都为 false
,输出为 false
。
输入 A | 输入 B | 输出 |
---|---|---|
false | false | false |
false | true | true |
true | false | true |
true | true | true |
布尔运算的例子
假设有两个布尔变量 A
和 B
,其中 A
的值为 true
,B
的值为 false
。下面是使用这些布尔运算符的一些基本示例:
-
逻辑与(AND):
bool result = A && B; // 结果为 false,因为 B 是 false
-
逻辑或(OR):
bool result = A || B; // 结果为 true,因为 A 是 true
-
逻辑非(NOT):
bool result = !A; // 结果为 false,因为 A 是 true
2.4.2 逻辑运算符
符号 | 意义 | 运算结果 |
&& | 与 | T && T = T; T && F = F; F && F =F |
|| | 或 | T || T = T; T || F = F; F || F = F |
! | 非 | !T = F; !F = T |
C++中的逻辑运算符主要用于执行布尔逻辑运算,即基于真 (true
) 或假 (false
) 的条件判断。这些运算符对于控制程序流程、实现条件语句和循环非常重要。在C++中,true和1(或非0)相同,false和0相同
#include <M5Unified.h> void setup() { // 初始化M5 auto cfg = M5.config(); M5.begin(cfg); int a = 12; int b = 3; // 使用逻辑与运算符 if (a > 10 && b < 5) { M5.Display.printf("Both conditions are true.\n"); } // 使用逻辑或运算符 if (a > 10 || b > 5) { M5.Display.printf("At least one condition is true.\n"); } // 使用逻辑非运算符 if (!b) { M5.Display.printf("B is zero.\n"); } else { M5.Display.printf("B is not zero.\n"); } } void loop() {}
2.4.3 关系运算符
在C++中,关系运算符用于比较两个值之间的关系,这些运算符的结果通常用于控制流语句(如if语句、while循环等)中,以决定程序的执行流程。关系运算符返回的结果是布尔值,即true
(非零)或false
(零)。以下是C++中常用的关系运算符:
符号 | 意义 | 运算结果 |
> | 大于 | 5 > 5 = false |
>= | 大于等于 | 5 >=5 = true |
< | 小于 | 5 < 5 = false |
<= | 小于等于 | 5 <= 5 = true |
== | 等于 | 5 == 5 = true |
!= | 不等于 | 5 != 5 = false |
注意:== 才是进行等于判断, = 是进行赋值操作,赋值操作的结果永远都是 true。
#include <M5Unified.h> void setup() { auto cfg = M5.config(); M5.begin(cfg); int a = 12; int b = 3; // 使用大于运算符 if (a > b) { M5.Display.printf("a is greater than b\n"); } else { M5.Display.printf("a is not greater than b\n"); } // 使用小于运算符 if (a < b) { M5.Display.printf("a is less than b\n"); } else { M5.Display.printf("a is not less than b\n"); } // 使用等于运算符 if (a == b) { M5.Display.printf("a is equal to b\n"); } else { M5.Display.printf("a is not equal to b\n"); } // 使用不等于运算符 if (a != b) { M5.Display.printf("a is not equal to b\n"); } else { M5.Display.printf("a is equal to b\n"); } // 使用大于等于运算符 if (a >= b) { M5.Display.printf("a is greater than or equal to b\n"); } else { M5.Display.printf("a is not greater than or equal to b\n"); } // 使用小于等于运算符 if (a <= b) { M5.Display.printf("a is less than or equal to b\n"); } else { M5.Display.printf("a is not less than or equal to b\n"); } } void loop() {}
2.4.4 if条件判断
在C++编程语言中,if
语句是用来执行基于某个条件的决策控制的基本语句。它允许程序在满足指定的条件时执行特定代码块,如果条件不满足,则可以选择执行另一个代码块或不执行任何操作。
基本语法
if
语句的基本语法如下:
if (condition) { // code to be executed if condition is true }
这里,condition
是一个布尔表达式(即结果为 true
或 false
的表达式)。如果 condition
为 true
,则执行大括号 {}
中的代码块。
if-else
语句
如果你想在条件不成立时执行另一段代码,可以使用 if-else
语句:
if (condition) { // code to be executed if condition is true } else { // code to be executed if condition is false }
if-else if-else
语句
有时你需要根据多个条件执行不同的代码块。这可以通过 if-else if-else
结构实现:
if (condition1) { // code to be executed if condition1 is true } else if (condition2) { // code to be executed if condition2 is true } // 更多的else if else { // code to be executed if both condition1 and condition2 are false }
嵌套 if
语句
if
语句可以嵌套使用,即一个 if
或 else
代码块内部可以包含另一个 if
或 else
语句:
if (condition1) { if (condition2) { // code to be executed if both condition1 and condition2 are true } } // 与下面的代码相同 if (condition1 && condition2) { // code to be executed if both condition1 and condition2 are true }
示例
下面是一个简单的例子,展示如何使用 if
语句:
#include <M5Unified.h> void setup() { auto cfg = M5.config(); M5.begin(cfg); int number = 10; // 如果条件内的语句块只包含一行 // 则可以省略大括号 if (number > 0) M5.Display.printf("Number is positive."); else if (number < 0) M5.Display.printf("Number is negative."); else M5.Display.printf("Number is zero."); } void loop() {}
这个程序检查一个整数(number
)是正数、负数还是零,并相应地打印出不同的消息。
注意事项
- 条件表达式:确保条件表达式的结果是布尔类型,即
true
或false
。 - 代码块:如果
if
或else
后面只有一条语句执行,可以省略大括号{}
。但为了代码的清晰性和可维护性,建议始终使用大括号。 - 逻辑错误:在复杂的条件判断中,逻辑错误很常见。
三元运算符
在C++中,条件运算符(也称为三元运算符)是一种简洁的方式来代替简单的 if-else
语句。它是唯一的一个接受三个操作数的运算符,这也是为什么它被称为三元运算符。它的一般形式是:
条件表达式 ? 表达式1 : 表达式2
工作原理
- 条件表达式:这是一个布尔表达式,其结果决定了整个条件运算符的结果。如果条件为真(非零),则计算并返回
表达式1
的结果;如果条件为假(零),则计算并返回表达式2
的结果。
示例
假设我们需要根据用户的年龄来判断他们是否成年(18岁及以上为成年):
#include <M5Unified.h> void setup() { auto cfg = M5.config(); M5.begin(cfg); int age = 28; String result = (age >= 18) ? "You are an adult." : "You are not an adult."; M5.Display.println(result); } void loop() {}
在这个例子中,条件运算符检查 age >= 18
是否为真。如果为真,result
变量将被赋值为 "You are an adult."
;如果为假,将被赋值为 "You are not an adult."
。
注意事项
- 条件运算符的两个结果表达式(
表达式1
和表达式2
)应具有相兼容的类型,或者至少能够被隐式转换为一个公共类型。 - 在使用条件运算符时,应注意不要嵌套太多层次的条件运算符,以避免代码可读性降低。
2.4.5 switch条件判断
在C++中,switch
语句是一种多路分支选择结构,它可以基于一个变量或表达式的值选择执行不同的代码块。这种语句通常用于替代长的 if-else
链,使代码更加清晰和易于管理。
基本语法
switch
语句的基本语法如下:
switch (expression) { case constant1: // 代码块 1 break; case constant2: // 代码块 2 break; ... default: // 默认代码块 }
- expression: 这是一个表达式,通常是一个变量,
switch
语句会根据这个表达式的值来执行相应的代码块。 - case constant: 这里的
constant
是一个常量表达式,用于与switch
表达式的值进行比较。如果匹配,执行该case
下的代码。 - break:
break
语句用于退出switch
结构。如果没有break
,程序会继续执行下一个case
的代码,即使表达式的值与其不匹配。 - default: 这是一个可选的部分,如果没有任何
case
与表达式的值匹配,将执行default
部分的代码。
示例
下面是一个使用 switch
语句的简单例子,根据交通信号灯状态,打印信号灯的颜色:
#include <M5Unified.h> void setup() { auto cfg = M5.config(); M5.begin(cfg); int trafficLight = 0; switch (trafficLight) { case 0: M5.Display.printf("Red Light"); break; case 1: M5.Display.printf("Yellow Light"); break; case 2: M5.Display.printf("Green Light"); break; default: M5.Display.printf("Broken Light"); } } void loop() {}
注意事项
- 数据类型限制:
switch
的表达式必须是整型或枚举类型。 - 唯一性:每个
case
标签必须是唯一的常量表达式。 - 防止穿透:不要忘记在每个
case
之后使用break
,除非你有意要使用穿透(fall through)的特性。
2.4.6 for循环控制
在C++中,for
循环是一种常用的循环控制结构,它允许你以一种简洁的方式重复执行代码块。for
循环的语法如下:
for (初始化表达式; 循环条件表达式; 更新表达式) { // 循环体(需要重复执行的代码) }
组件解释
- 初始化表达式:在循环开始前执行,通常用于设置计数器的初始值。这个表达式只在循环开始时执行一次。
- 循环条件表达式:在每次循环迭代之前评估。如果表达式结果为真(非零),则执行循环体。如果为假(零),则退出循环。
- 更新表达式:每次循环迭代后执行,通常用于更新计数器。
- 循环体:包含要重复执行的代码块。
示例
下面是一个简单的示例,展示如何使用for
循环打印数字1到10:
#include <M5Unified.h> void setup() { auto cfg = M5.config(); M5.begin(cfg); for (int i = 1; i <= 10; i++) { M5.Display.printf("%d\n", i); } } void loop() {}
注意事项
- 在
for
循环中,初始化表达式、循环条件表达式和更新表达式都可以省略,但是必须保留两个分号(;)。 - 如果循环条件表达式始终为真(例如,使用
for(;;)
),则会创建一个无限循环,除非在循环体内部有一个break
语句或其他退出机制。
2.4.7 while循环控制
在C++中,while
循环和 do...while
循环都是用于重复执行代码块的控制结构,但它们在何时检查循环条件上有所不同。
while 循环
while
循环是一种先测试条件后执行循环体的结构。如果条件一开始就不满足,循环体内的代码甚至可能一次都不会执行。其基本语法如下:
while (条件表达式) { // 循环体 }
do...while 循环
与 while
循环相比,do...while
循环是一种后测试循环结构,这意味着循环体至少会被执行一次,之后才检查循环条件。其基本语法如下:
do { // 循环体 } while (条件表达式);
示例
while 循环示例
打印1到5的数字:
#include <M5Unified.h> void setup() { auto cfg = M5.config(); M5.begin(cfg); int i = 1; while (i <= 5) { M5.Display.printf("%d\n", i); i++; } // 即使初始条件为假,也至少执行一次循环体: i = 6; do { M5.Display.printf("%d\n", i); i++; } while (i <= 5); } void loop() {}
在这个 do...while
示例中,尽管 i
的初始值是6,不满足循环条件 i <= 5
,循环体仍然执行了一次,打印出数字6。
使用场景
- while 循环:当你不知道循环需要执行多少次,但循环开始前需要先检查条件时,使用
while
循环。 - do...while 循环:当你至少需要执行循环体一次时,使用
do...while
循环。这在需要先执行操作然后再评估条件的场景中非常有用,如至少读取一次用户输入然后决定是否继续。
2.4.8 控制关键字
在C语言中,continue
和 break
是两种用于控制循环结构的关键字,它们通常用在 for
、while
和 do-while
循环中。这两个关键字都用于改变程序的控制流程,但它们的作用和用法有所不同。
continue
continue
关键字用于立即跳过当前循环的剩余部分,并开始下一次迭代。它只会影响包含它的最内层循环。使用 continue
时,循环的增量表达式仍然会被执行(在 for
循环中),然后控制回到循环的条件判断部分。
break
break
关键字用于立即退出包含它的最内层循环或 switch
语句。在循环中,一旦执行到 break
,循环将完全停止,控制流程会跳转到循环后面的第一条语句。
示例代码:
#include <M5Unified.h> void setup() { auto cfg = M5.config(); M5.begin(cfg); for (int i = 0; i < 10; i++) { if (i % 2 == 0) continue; // 跳过偶数 M5.Display.printf("%d ", i); } // 输出结果:1 3 5 7 9 // 在这个例子中,当 i 是偶数时,continue 会被执行 // 当前循环的剩余部分(即 printf 调用)被跳过,循环直接进入下一次迭代。 for (int i = 0; i < 10; i++) { if (i == 5) break; // 当i等于5时退出循环 M5.Display.printf("%d ", i); } // 输出结果:0 1 2 3 4 // 在这个例子中,当 i 等于 5 时,break 被执行 // 循环立即停止,控制流程挑出循环。 } void loop() {}
总结
continue
用于跳过当前迭代的剩余部分,继续下一次迭代。break
用于完全退出循环,继续执行循环后的代码。
这两个关键字在控制复杂循环逻辑时非常有用,可以使代码更加清晰和易于管理。
2.5 练习
之前的例子代码中还有一些问题,比如从红灯切换回绿灯时,没有经过黄灯。并且小人的动画在任何时候都会显示。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具