【M5Stack物联网开发】第二章 智能设备开发流程概述

1 软件工程

软件工程是一个复杂而系统化的过程,其目标是开发、操作和维护高质量的软件系统。这一过程遵循被广泛认可的软件开发生命周期(SDLC),该生命周期包括需求分析、系统设计、实现(编码)、测试、部署以及维护和更新等关键步骤。随着技术的发展,特别是物联网(IoT)技术的兴起,软件工程的传统模式和方法正在逐步适应新的挑战和需求。

1.1 需求分析

在传统软件工程中,需求分析是确定软件必须满足的功能和性能需求的关键阶段。这一步骤通常涉及与客户、用户以及其他利益相关者的广泛沟通。对于物联网设备的开发,需求分析不仅要考虑软件的功能,还需评估设备的物理特性、环境适应性、以及与传感器和执行器的兼容性等因素。例如,智能温控器需要考虑室内外的温差、湿度变化等环境因素,以及如何通过网络与其他智能设备通信。

1.2 系统设计

系统设计阶段在软件工程中扮演着将需求转化为操作性技术解决方案的角色。在物联网设备开发中,这一阶段不仅包括软件架构的设计,还包括硬件设计、选择合适的网络通信协议以及数据流处理方案的制定。例如,开发一个智能照明系统不仅需要设计灯光控制软件,还要设计灯具的硬件接口以及它们如何通过Wi-Fi或蓝牙与其他设备互联。

1.3 实现(编码)

软件工程的编码阶段涉及具体实现系统设计阶段确定的架构和模块。在物联网领域,这一步骤扩展到了嵌入式系统的开发,包括固件编程和设备驱动程序的开发。例如,智能手表的开发不仅需要编写应用程序,还需要在资源受限的环境中优化操作系统的性能和电池寿命。

1.4 测试

测试是软件工程中确保产品质量的关键步骤。对于物联网设备,除了常规的软件测试(如单元测试、集成测试和系统测试),还必须进行硬件测试、耐久性和可靠性测试,以及网络通信的稳定性测试。这些测试确保设备在各种物理环境和网络条件下都能稳定运行。

1.5 部署

软件部署是将软件产品交付给最终用户的过程。在物联网设备中,部署不仅涉及软件的安装,还包括物理设备的安装、网络配置以及设备在云平台或控制中心的注册。这一阶段的复杂性远高于传统软件应用的部署。

1.6 维护和更新

软件维护和更新是确保软件长期有效运行的必要步骤。对于物联网设备,这不仅包括软件的定期更新和bug修复,还涉及硬件的维护、固件升级以及对环境变化的适应性调整。例如,智能家居系统可能需要通过远程更新来增加新的安全特性或改进能效性能。

2 编程语言的发展

编程语言的发展是计算机科学领域中一段引人入胜的历史。从最初的机器语言到现代的高级编程语言,每一步的演变都不仅仅是技术的进步,更是对人类思维方式和解决问题方法的一种革新。本文将详细探讨编程语言的发展历程,分析每个阶段的特点及其对计算机科学和工业实践的影响。

2.1 机器语言阶段(1940年代初期)

在计算机科学的早期,机器语言是唯一的编程方式。程序员需要直接使用二进制代码来编写程序,这种方式直接操作硬件,每条指令具体到操作计算机的每一个电子部件。这不仅要求程序员具有深厚的硬件知识,同时编程效率极低,错误率高。

2.2 汇编语言阶段(1950年代)

随着计算需求的增加,机器语言的局限性开始显现。汇编语言的出现标志着编程语言的第一次重大演进。通过引入助记符,汇编语言将复杂的二进制代码转换为更易理解的符号和命令。尽管汇编语言仍然依赖于特定的硬件,但它大大提高了编程的可读性和效率。

2.3 高级语言阶段(1950年代末至今)

2.3.1 早期高级语言

高级语言的出现是为了进一步抽象化,减少编程语言与硬件之间的直接关系,使得程序设计更加专注于解决问题而非机器操作。FORTRAN和COBOL等语言的开发,使得编程更加接近自然语言,极大地提升了编程的普及性和应用领域。

2.3.2 结构化编程语言

1970年代,随着C语言的诞生,结构化编程成为主流。这类语言支持复杂的数据结构和算法,使得程序不仅易于编写,更易于维护和扩展。这一阶段的语言强调程序的逻辑结构,促进了软件工程学的发展。

2.3.3 面向对象编程语言

1980年代后,面向对象的编程语言如C++、Java和C#开始流行。这些语言通过类和对象的概念,实现了数据和方法的封装,继承和多态性,极大地提高了代码的重用性和模块化程度。面向对象语言在软件开发中提供了更高的抽象层次,成为商业和工业应用的主流选择。

2.4 脚本语言和动态语言阶段(1990年代至今)

随着互联网的兴起和信息技术的快速发展,脚本语言和动态语言应运而生。这些语言如Python、Ruby和JavaScript,以其开发效率高、学习门槛低等特点,广泛应用于网页开发、系统管理、科学计算等领域。它们通常具有良好的跨平台性和强大的社区支持。

2.5 现代编程语言阶段(2000年代至今)

新世纪的编程语言如Go、Rust和Swift,是对既有语言的进一步发展或完善。这些语言在设计上更加注重效率、安全性和并发性,以适应现代计算需求的变化,如多核处理、云计算和大数据处理。它们的出现和普及,反映了编程语言不断适应新技术环境的趋势。

3 C++概述

3.1 C++发展史

C++的发展历史始于1979年,由丹麦计算机科学家比亚尼·斯特劳斯特鲁普(Bjarne Stroustrup)在美国AT&T贝尔实验室(Bell Labs)创造。最初,斯特劳斯特鲁普的目标是增强C语言的功能,使其支持类似于Simula的对象编程特性,同时保持C语言的高效性和灵活性

C++最初被称为“C with Classes”,意在表明它是C语言的一个带有类的超集。在1983年,这门语言被正式命名为C++,意指“增加一”的C,象征着这门语言在C的基础上增加了新的功能

随着时间的推移,C++引入了许多其他的编程特性,例如虚函数、模板和异常处理。这些特性使得C++成为一种功能强大的多范式编程语言,适用于系统编程、应用软件开发、设备驱动开发等多种场合。

C++的第一个官方标准化版本是在1998年发布的ISO/IEC 14882:1998标准,通常被称为C++98。此后,C++标准持续更新和改进,包括2003年的C++03修订版,2011年的重大更新C++11,以及随后的C++14、C++17和C++20等版本。这些更新引入了更多的现代特性,如自动类型推导、智能指针、lambda表达式、并行编程支持等,使得C++能够更好地适应现代软件开发的需求

3.2 C++基本结构

在C++中,程序的基本结构包括几个关键部分:

3.2.1 语句 (Statements)

在C++中,语句是程序中的基本构建块,用于执行具体的操作。一个语句可以是单行的,也可以是多行的。

单行语句:这是最简单的语句形式,通常完成一个简单的操作,比如赋值或调用一个函数。每个单行语句最后以英文分号结束。

int x = 5; // 单行语句务必要以分号结束

多行语句:当一个语句太长或者需要包含多个步骤时,它可以扩展到多行。例如,一个if语句可能会跨越多行来包含多个条件和操作。对于多行语句,通常不以英文分号结束,而是以大括号进行语句范围描述。

复制代码
// 条件判断结构多行语句
if ( i > 0 )
{
   // to do...  
}
else
{
  // to do...
}

// 循环结构多行语句
for(int i = 0; i < 10; i++)
{
  // to do
}
复制代码

3.2.2 声明和定义 (Declarations and Definitions)

声明:在C++中,声明一个变量或函数是告诉编译器某个名称的存在及其类型,但不分配内存。例如:

extern int x;

声明了一个整数类型的变量x,但没有分配内存。因为没有分配内存,你也就无法为x指定值或者进行操作。

定义:定义是声明的扩展,它除了提供名称和类型外,还分配了内存。例如:

int x;

这里不仅声明了x,还为它分配了内存。有了内存空间,就可以为x指定值,并存储在对应的内存空间中。

3.2.3 函数 (Functions)

函数是执行特定任务的代码块,可以被程序的其他部分重复调用。每个函数都有一个返回类型(也可以没有返回)、一个名称(必须有名称)、参数列表(可以没有参数)和一个函数体(可以为空函数体)。例如:

复制代码
// 返回值类型是int(也可以没有)
// 名称是add
// 具有两个整型参数a和b(也可以没有)
// 这个函数的功能就是返回a加b的和
int add(int a, int b)
{
    return a + b;
}

// 在其他地方调用add函数完成任务
int a = 10;
int b = 12;
int c = add(a, b); //调用add,并将返回值保存在变量c中
复制代码

3.2.4 API (Application Programming Interface)

API是一组预定义的函数或指令集,它允许一个程序与另一个程序或工具进行交互。在C++中,这通常指的是库提供的一组功能,如标准模板库(STL),Arduino仓库(Arduino),M5Stack库(M5Unified),音频模块库(Audio)

3.2.5 头文件 (Header Files)

要想使用别人预定义好的API,就需要将该API对应的头文件和源文件加载到当前程序中。头文件包含了C++程序中使用的声明和宏定义等。它们通常有`.h`或`.hpp`扩展名,并通过#include指令包含在其他源文件中。头文件使得函数声明、宏、常量等可以在多个源文件之间共享。

#include <iostream> // 使用标准的C++输入输出库
#include <Arduino.h> // 使用Arduino官方库
#include <M5Unified.h> // 使用M5Stack官方库
#include <Audio.h> // 使用Audio库,可以读取MP3文件等

 

3.3 C++程序开发过程概述

在C++程序编码过程中,程序的基本结构可以被理解为“程序 = 数据 + 算法”。这个概念强调了程序不仅仅是代码的集合,而是通过特定的数据结构和算法来解决问题的方法。下面将详细描述这个过程,并给出一个具体的例子。

3.3.1 声明各种数据

在C++中,数据通常是通过变量、数组、结构体、类等来声明的。这些数据结构用来存储程序运行过程中需要处理的信息。

复制代码
#include <iostream>

// 数据结构定义
// 该数据结构称为结构体(struct)
// 结构体可以将不同类型的变量组织在一起

struct Student { // 该结构体的类型名称是Student
    string name; // 以字符串类型保存name
    int age; // 以整型(整数)保存年龄
    float score; // 以单精度浮点数(小数)保存成绩
};
复制代码

3.3.2 设计函数(算法)

函数是执行特定任务的代码块,它可以接受输入参数,并返回输出数据。

复制代码
// 定义一个函数,名称为calculateGrade
// 该函数返回一个字母类型(char)数据
// 该函数具有一个单精度浮点数(小数)类型的参数
char calculateGrade(float score) {
    if (score >= 90) // if条件判断模块,多行语句
    {
        return 'A';
    }
    else if (score >= 80)
    {
        return 'B';
    }
    else if (score >= 70)
    {
        return 'C';
    }
    else
    {
        return 'F';
    }
}
复制代码

3.3.3 执行代码

在主函数或其他函数中,调用上述定义的函数来处理数据并完成具体的需求。

复制代码
// 主函数main
// 也被称为程序的入口函数,起点函数
// 任何C++程序都是从main函数的第一行开始执行的
int main() {
    // 创建数据
    Student stu = {"John", 20, 85.5};

    // 调用函数
    char grade = calculateGrade(stu.score);
    
    // 输出成绩结果
    cout << stu.name << "的成绩等级是: " << grade << endl;

    // 程序顺利执行,返回0(无错误)
    return 0;
}
复制代码

3.3.4 编译

编译是将你的C++代码转换成机器能理解的语言(机器代码)。这通常是通过一个编译器来完成的,如GCC或Clang。现代开发环境(IDE)中,已经集成了图形化的编译界面,一般不需要自行编写编译指令。

g++ -o student_program student_program.cpp

3.3.5 故障与调试

在程序开发过程中,可能会遇到各种逻辑错误或运行时错误。使用调试工具(如GDB)或插入调试语句(如`cout`语句)来检查和修正这些错误。在集成开发环境(IDE)中,也可以使用断点,查看堆栈的方式进行调试,这些调试相关的操作,会在后续内容中,逐步讲解并实践

3.3.6 上传代码至单片机

如果你的C++程序是为单片机(如Arduino、ESP32等)开发的,最后需要通过特定的IDE(如Arduino IDE)或工具将编译后的代码,通过特定硬件结构(如USB,串口Serial等)上传到单片机上。在集成开发环境(IDE)中,也已经提供了图形化界面去配置通讯接口信息,以及上传功能等。

4 安装集成开发环境(Integrated Development Environment,IDE)

4.1 安装Visual Studio Code

Visual Studio Code(简称VS Code)是一个由微软开发的免费、开源的代码编辑器,你可以从Visual Studio官网下载安装程序。它在开发者社区中非常受欢迎,主要因为其轻量级、高效和强大的功能,特别是对跨平台和多语言支持的优秀表现。

VS Code 支持多种操作系统,包括 Windows、macOS 和 Linux。这意味着无论开发者使用什么操作系统,他们都可以使用VS Code进行开发工作。这一跨平台特性使得团队成员在不同的操作系统上工作时能够共享相同的开发环境和工具,极大地提高了协作效率和项目的可移植性。

VS Code 支持多种编程语言的开发,包括但不限于 JavaScript、TypeScript、Python、PHP、C++、C#、Java、Go 和 Rust。这种广泛的语言支持是通过安装语言特定的扩展来实现的。VS Code 的市场上有成千上万的扩展,可以为不同的语言和框架提供额外的功能,如智能代码补全(IntelliSense)、代码片段、语法高亮、代码调试、以及其他高级编程功能。

IntelliSense是VS Code的一个核心特性,提供自动完成、参数信息、快速信息和成员列表等功能,帮助开发者更快地编写代码。
VS Code 支持内置调试工具,可以直接在编辑器中启动和调试代码,无需离开开发环境。
VS Code 有内置的Git支持,可以在编辑器内进行版本控制操作,如提交、拉取、推送和分支管理。
用户可以通过安装扩展来定制编辑器的功能,改变主题、添加新的功能或集成其他工具。
通过安装Remote Development扩展,可以直接在远程服务器、容器或Windows子系统(WSL)上进行代码编辑和调试。

步骤:

对于Windows系统:

1. 访问官网:

  • - 打开浏览器,访问 [Visual Studio Code 官方网站](https://code.visualstudio.com/).

2. 下载安装包:

  • 点击“Download for Windows”按钮,下载适用于Windows的安装包。

3. 运行安装程序:

  • 找到下载的安装包(通常在下载文件夹中),双击运行。
  • 按照安装向导的指示进行操作。你可以选择默认安装位置或自定义安装位置。
  • 选择安装选项,如添加“打开方式”到右键菜单,添加到PATH(这样可以从命令行启动VS Code)。

4. 完成安装并启动:

  • 完成安装向导后,启动VS Code。
  • 第一次启动时,VS Code可能会提示你安装推荐的扩展或进行其他配置。

对于macOS系统:

1. 访问官网:

  • 使用浏览器访问 [Visual Studio Code 官方网站](https://code.visualstudio.com/).

2. 下载安装包:

  • 点击“Download for Mac”按钮,下载适用于macOS的安装包。

3. 安装应用:

  • 打开下载的`.zip`文件,解压VS Code。
  • 将Visual Studio Code拖动到你的“应用程序”文件夹中,这样就安装好了。

4. 启动VS Code:

  • 打开“应用程序”文件夹,找到VS Code并双击启动。
  • 可能会看到一个安全警告,因为这是从互联网下载的应用。可以在“系统偏好设置”中允许打开。

4.2 安装PlatformIO插件

PlatformIO是一个开源的生态系统,用于物联网开发,支持跨平台代码构建和兼容多种开发板。它提供了一个强大的平台,使开发者能够使用同一代码库开发多个目标设备,这些设备可能基于不同的微控制器和处理器架构。

 

PlatformIO支持Windows、macOS、Linux操作系统,使开发者能够在不同的环境中工作。

PlatformIO支持超过1000种不同的开发板,包括Arduino、ESP8266、ESP32、STM32等流行的微控制器和开发平台。

PlatformIO可以集成到多种流行的IDE中,如Visual Studio Code、Atom、CLion等,提供便捷的编程和调试环境。

PlatformIO提供了一个强大的库管理系统,使开发者能够轻松地添加和管理项目依赖的第三方库。

PlatformIO使用先进的构建工具,可以自动化编译和上传过程,简化开发流程。

PlatformIO支持单元测试和持续集成,帮助开发者确保代码质量和功能正常。

PlatformIO拥有一个活跃的社区和丰富的文档资源,帮助开发者解决开发中的问题并分享经验。

 

PlatformIO特别适合于物联网设备和跨平台项目的开发,可以处理多种硬件的兼容性和不同设备的代码部署。

步骤:

  1. 在VS Code左侧选择Extension标签,打开插件/扩展管理器
  2. 在搜索栏输入:PlatformIO,并搜索该插件
  3. 选中PlatformIO插件后,点击安装
  4. 重启VS Code之后,就会在左侧边栏看到PlatformIO的外星人图标。

 4.3 安装M5Stack API

M5Unified API 是由 M5Stack 官方推出的一个统一的编程接口,用于简化和标准化 M5Stack 系列设备的编程过程。M5Stack 是一系列基于 ESP32 的模块化、堆叠式物联网开发平台,包括各种模块和配件,适用于快速开发和原型设计。M5Unified API 旨在提供一个统一的方法来控制和管理这些设备,无论所使用的具体模型或硬件配置如何。

M5Unified API 的特点

  • 统一性:M5Unified API 提供一套统一的函数和方法来控制不同的 M5Stack 设备,这意味着开发者可以使用相同的代码基础来操作不同的硬件,从而提高代码的可重用性和可维护性。
  • 简化编程:通过减少需要写的代码量和简化硬件控制的复杂性,M5Unified 使得开发过程更加快速和容易。
  • 跨平台支持:支持多种不同的 M5Stack 设备,包括但不限于 M5Stack Core, M5StickC, M5Paper 等。

M5Unified API 的模块:

显示屏模块:用来在显示屏上,以多种方式展示文字、形状或图片

按钮模块:对于如M5 Core2这类设备,具备三个按键,可以通过该模块进行操作

触摸模块:对于如M5 Core2这类设备,具备一个触屏,可以通过该模块进行操作

扬声器模块:对于如M5 Core2这类设备,具备一个扬声器,可以通过该模块进行操作

时钟模块:可以与Wi-Fi库联用,通过互联网时钟服务器设置内部时钟

麦克风模块:对于如M5 Core2这类设备,具备一个麦克风,可以通过该模块进行操作

IMU(惯性测试单元)模块:对于如M5 Core2这类设备,具备加速度计、陀螺仪等,可以通过该模块进行操作。

步骤:

  1. 在VS Code左侧边栏打开PlatformIO扩展
  2. 在PlatformIO的操作界面,找到Libraries库管理器
  3. 在库管理器中搜索并安装M5Unified API

4.4 使用M5Burner测试M5Stack Core2

4.4.1 固件

固件(Firmware)是嵌入在硬件设备中的一种特殊类型的软件,它控制和管理设备的低级功能。固件提供了设备所需的指令,以便它能够执行基本的任务,如启动、运行和与其他设备的通信。固件通常存储在设备的非易失性存储器中,如ROM、EPROM或闪存,这意味着即使在电源关闭后,固件也仍然存在。

固件的主要特点和功能:

  1. 控制硬件操作:固件直接控制硬件的操作,比如处理器的启动过程、输入输出控制、电源管理等。
  2. 设备启动:固件包含了设备启动时必须执行的初步程序,这些程序负责初始化硬件并加载更高级别的操作系统(如果有的话)。
  3. 提供接口:固件提供了硬件与软件之间的接口,使得软件能够通过固件与硬件交互。
  4. 更新和升级:虽然固件通常被设计为长期使用,但它可以通过更新来修复错误或添加新功能。这些更新通常由设备制造商提供。
  5. 持久性:固件是永久性的,它不会因为设备断电而消失,这与RAM中存储的数据不同。

固件与软件和硬件的关系:

  1. 固件与软件:固件可以看作是软件和硬件之间的中间层。它比应用软件更接近硬件,执行更底层的控制;但它又是通过编程实现的,因此属于软件的范畴。
  2. 固件与硬件:固件是硬件的一部分,因为它直接嵌入在硬件中,控制硬件的基本行为。

4.4.2 安装固件

https://docs.m5stack.com/zh_CN/uiflow/m5burner/export

5 小程序1:Hello World

5.1 创建第一个项目

步骤:

  1. 在VS Code中打开PlatformIO扩展
  2. 在PlatformIO Home界面中,选择Create New Project
  3. 选择New Project打开创建新项目对话框
  4. 为项目填写信息,包括项目名称:HelloWorld,项目运行的硬件环境:M5Stack Core2,项目使用的开发框架:Arduino。备注:ESP32芯片官方的开发框架是ESP-IDF,但是Arduino框架更加简单易用,所以本书都是用Arduino框架
  5. 在VS Code中再次打开PlatformIO扩展
  6. 在PlatformIO Home界面中打开库管理器
  7. 在库管理器中打开M5Unified API详情界面,该步骤与安装M5Unified API操作一致
  8. 选择Add To Project将M5Unified API添加至步骤4中创建的项目中
  9. 选择步骤4中创建的项目HelloWorld,然后点击Add
  10. 在VS Code中打开文件管理器对话框
  11. 默认情况下,会打开HelloWorld项目文件夹,如果没有打开可手动打开,选择src/main.cpp,进行编辑
  12. 将下面的代码复制到编辑器中
  13. 在VS Code最下方,是PlatformIO提供的编译与上传操作,将Core2连接至电脑后,点击Upload按钮,PlatformIO就会自动编译代码,并上传至Core2中
复制代码
// 声明使用M5Unified API,并加载该API对应的头文件
#include "M5Unified.h"

// Arduino框架是一种简化版的C++程序
// 基于Arduino框架生成程序,包含两个主要部分:Setup与Loop
// Setup表示软件启动后的初始化操作,该函数中的内容只在启动后运行一次
// 比如,初始化屏幕背景色,设定字体大小,显示软件标题等
// Loop表示软件在运行过程中,不停循环执行的任务
// 比如,不停的读取传感器的信息,不停的监测用户是否有单击按钮操作等

// 初始化函数
void setup() {

  // 初始化M5硬件及默认模块
  auto cfg = M5.config();
  M5.begin(cfg);

  // 调用M5Unified API中的屏幕模块
  // 并调用模块中的pritln函数,在屏幕上输出一行文字
  // 默认情况下不支持英文以外的字符
  M5.Display.println("Hello World");
}

// 主循环函数
void loop() {
  // 无操作,空函数
}
复制代码

 

 

5.2 Setup与Loop

在Arduino编程框架中,setup()和loop()是两个基本且必须的函数,它们构成了大多数Arduino程序的核心结构。下面将详细解释这两个函数的用途和工作方式。

5.2.1 setup()函数

setup()函数在Arduino程序中用于初始化设置。它在M5Stack Core2上电或重置后只执行一次。这个函数主要用于设置引脚模式(输入、输出)、初始化串行通信、以及启动使用到的外部库等。简单来说,任何只需要在程序开始时执行一次的代码都应该放在这个函数中。

void setup() {
    pinMode(13, OUTPUT); // 设置数字引脚13为输出模式
    Serial.begin(9600); // 启动串行通信,波特率为9600
}

5.2.2. loop()函数

loop()函数是Arduino程序中的主要部分,用于持续执行代码。当setup()函数完成后,Arduino程序会不断地重复执行loop()函数中的代码,直到板子断电。这使得它成为实现程序持续响应的理想位置,如读取传感器数据、控制电机、更新显示屏等。

void loop() {
    digitalWrite(13, HIGH); // 设置数字引脚13为高电平
    delay(1000); // 延时1000毫秒(1秒)
    digitalWrite(13, LOW); // 设置数字引脚13为低电平
    delay(1000); // 延时1000毫秒(1秒)
}

总结

  • setup()用于一次性的初始化设置。
  • loop()用于执行重复的任务和持续的操作。

这两个函数共同构成了大多数Arduino程序的骨架,通过它们可以实现从简单到复杂的各种自动化和交互功能。理解这两个函数的用途和如何使用它们是学习Arduino编程的基础。

5.3 查看调试信息

前文曾经调到过,调试并找出错误,是开发者应当具备的一项重要技能,本小结将介绍如何利用PlatformIO自带的调试信息输出,进行错误查看。

复制代码
#include "M5Unified.h"

void setup() {
  auto cfg = M5.config();
  M5.begin(cfg);
  
  M5.Display.println("Hello " + name)
}

void loop() {

}
复制代码

将上述代码复制到main.cpp中,并点击Build按钮进行编译,Build按钮在Upload按钮左侧。

编译结束后,会在VS Code下方的Terminal对话框中,看到红色字体输出的错误信息。

这个错误信息告诉我们:

  1. 在src/main.cpp文件中的函数void setup()发现了错误
  2. 在scr/main.cpp文件的第7行,第33个字符位置,出现了错误:编译器不知道name是什么,name没有被声明
  3. PlatformIO给出了一些修改建议(黄色文字部分)

这个错误的修改方式,就是声明name,或者把name删掉。关于如何声明name,会在下一小节进行介绍。现在直接把name删掉即可。

将name删掉后,重新Build(编译),会发现还有一个错误,这个错误留给读者自行解决。

如果,整个代码都正确,并且顺利编译,在输出信息的最后会出现,success而不是图中的failed。

5.4 C++:变量

前文曾经调到,一个C++程序是由数据和算法共同构成,算法就是如何处理数据的方法。C++中的数据有很多种,这一小节介绍常量与变量。

5.4.1 声明与定义

前文已经将结果声明与定义,这里再次复习一下。

声明:在C++中,声明一个变量或函数是告诉编译器某个名称的存在及其类型,但不分配内存。例如:

extern int x;

声明了一个整数类型的变量x,但没有分配内存。因为没有分配内存,你也就无法为x指定值或者进行操作。

定义:定义是声明的扩展,它除了提供名称和类型外,还分配了内存。例如:

int x;

这里不仅声明了x,还为它分配了内存。有了内存空间,就可以为x指定值,并存储在对应的内存空间中。

无论对于常量还是变量,在使用前都需要进行声明与定义,否则就会出现前面小结中,未定义name变量的错误

5.4.2 变量

在C++中,变量是用来存储数据的标识符,其值在程序执行期间可以改变。变量必须先声明后使用,声明时必须指定变量的类型。C++支持多种数据类型,可以大致分为以下几类:

  1. 基本数据类型:

    • 整型 (intshortlonglong long): 用于存储整数。
    • 字符型 (char): 用于存储单个字符。
    • 布尔型 (bool): 用于存储真(true)或假(false)。
    • 浮点型 (floatdoublelong double): 用于存储带小数的数。
  2. 派生数据类型:

    • 数组 (int arr[10]): 用于存储同类型的固定数目的元素。
    • 指针 (int* ptr): 用于存储变量地址。
    • 引用 (int& ref): 另一种形式的变量别名。
  3. 用户定义类型:

    • 结构体 (struct): 用于组合不同的数据项。
    • 联合体 (union): 允许在相同的内存位置存储不同的数据类型。
    • 枚举 (enum): 用于定义变量,其值限定在一定范围内。
  4. 类 (class): C++的核心部分,用于面向对象编程。

复制代码
// 变量在使用前必须声明和定义

// 仅声明一个变量b,并不为b分配内存空间
// 此时的变量b是无法操作的,因为其不在内存中
extern int b;

// 声明并定义一个变量a,并为a分配内存空间
// 此时的变量a是可以进行修改和操作的
int a = 5;

// 声明一个变量的格式是
// extern type name;
// 关键字extern表明,告诉编译器要声明一个东西了
// type是变量的类型
// name是变量的名字

// 定义一个变量的格式是
// type name [= value];
// type是变量的类型
// name是变量的名字
// value是变量的初始值,可以没有

// 一些例子

// int类型的变量
int number = 5;

// 单个字符类型的变量,单个字符在C++中使用单引号表示
char charactor = 'A';

// 多个字符,也叫字符串类型的变量,字符串在C++中使用双引号表示
String name = "PXQ";

// 布尔类型,值只能为TRUE或者FALSE
bool boolvalue = TRUE;

// 单精度浮点数类型,可以用来保存小数,int类型只能保存整数
float floatNumber = 0.005;
// int number = 0.005;
// 如果试图用整型变量保存小数,编译器会忽略小数部分
// 此时number的值是0
复制代码

驼峰命名法

在C++编程中,命名变量时常用的一种约定是驼峰命名法。驼峰命名法有两种形式:

  • 小驼峰命名法(lowerCamelCase):

    • 第一个单词以小写字母开始;后续每个单词的首字母大写。
    • 例如: studentNametotalVolumecurrentSpeed
  • 大驼峰命名法(UpperCamelCase):

    • 每个单词的首字母都大写。
    • 例如: StudentNameTotalVolumeCurrentSpeed

选择哪种命名法取决于你的团队约定或项目规范。在C++中,通常推荐使用小驼峰命名法来命名变量,而使用大驼峰命名法来命名类和结构体。

5.4.3 常量

在C++中,常量是指一旦初始化后其值就不能再被改变的变量。定义常量可以使代码更安全、易于维护,并且可以提供关于程序如何操作的明确信息。C++中定义常量的方法有几种,主要包括:

  1. 使用const关键字:

    • const关键字可以用来定义任何类型的常量,如整数、浮点数、字符等。
    • const int MAX_USERS = 100; 
      const double PI = 3.14159;

       

  2. 使用#define预处理器:

    • #define指令用于定义宏,它告诉编译器在编译之前替换标识符。
    • #define MAX_USERS 100

在编程中,"魔法数字"(Magic Numbers)是指在代码中直接使用的硬编码数值,这些数值没有明确的含义或解释。使用魔法数字通常会降低代码的可读性和可维护性,因为它们使代码变得难以理解和修改。

在这个例子中,65就是一个魔法数字,它直接出现在代码中,其他人可能不清楚为什么是65,也可能在退休年龄政策改变时忘记更新。

if (employee.age > 65) {
    // retirement logic
}

在改进的例子中,使用常量RETIREMENT_AGE代替了数字65,这样代码的意图就变得清晰明确,同时也便于将来的修改和维护。

const int RETIREMENT_AGE = 65;

if (employee.age > RETIREMENT_AGE) {
    // retirement logic
}

还有,比如像前文中使用的字符串“HelloWorld”,这些都是魔法数字。

5.4.4 静态变量

5.4.5 运算符 + - * / % ++ -- >> <<

5.4.6 内存中的程序与变量

5.5 练习

创建一个字符串变量用来保存你的名字,并把你的名字输出在屏幕中。

多个字符串之间可以使用加号“+”进行拼接。

6 理论与术语总结

posted @   庞兴庆  阅读(185)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示