《ESP32-S3使用指南—IDF版 V1.6》第二章 常用的C语言知识点

news/2025/1/15 9:37:56/文章来源:https://www.cnblogs.com/zdyz/p/18662210

第二章 常用的C语言知识点

1)实验平台:正点原子DNESP32S3开发板

2)章节摘自【正点原子】ESP32-S3使用指南—IDF版 V1.6

3)购买链接:https://detail.tmall.com/item.htm?&id=768499342659

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32S3.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子DNESP32S3开发板技术交流群:132780729

本章将为大家介绍常用的C语言知识点。对于已经熟练掌握C语言的开发者,可以选择跳过本节内容;而对于基础相对薄弱的开发者,我们强烈建议深入学习本章内容,以便为后续的ESP32开发打下坚实基础。由于C语言是一门博大精深的编程语言,不可能在章节内全面讲解。因此,本节将专注于复习与ESP32开发密切相关的几个核心C语言知识点,旨在帮助大家更好地学习并编写ESP32代码。通过本章的学习,您将能够回顾和巩固在ESP32开发中常用的C语言特性,从而提升您的编程技能,为后续的项目开发做好准备。
本章将分为如下几个小节:
5.1 位操作
5.2 define宏定义
5.3 ifdef条件编译
5.4 extern外部申明
5.5 typedef类型别名
5.6 struct结构体
5.7 指针

5.1 位操作
位操作是直接在整数的二进制位上进行操作的一种技术。C语言提供了多种位操作符,这些操作符允许程序员对整数的二进制位进行读取、设置、清除或翻转。位操作在处理底层硬件、优化代码、处理二进制数据等方面非常有用。C语言支持如下6种位操作:


表5.1.1六种位操作

以下是C语言中的一些位操作符及其描述:
1, 按位与(&):如果两个相应的二进制位都为1,则结果为1,否则为0。

int a = 60;     /* 60 = 0011 1100 */
int b = 13;     /* 13 = 0000 1101 */
int c = a & b;  /* c =  0000 1100 */

2,按位或(|):如果两个相应的二进制位中至少有一个为1,则结果为1,否则为0。

int a = 60;     /* 60 = 0011 1100 */
int b = 13;     /* 13 = 0000 1101 */
int c = a | b;  /* c =  0011 1101 */

3,按位异或(^):如果两个相应的二进制位不同,则结果为1,否则为0。

int a = 60;     /* 60 = 0011 1100 */
int b = 13;     /* 13 = 0000 1101 */
int c = a ^ b;  /* c =  0011 0001 */

4,按位取反(~):将整数的二进制位翻转。

int a = 60;     /* 60 = 0011 1100 */
int b = ~a;     /* b =  1100 0011 */

5,左移(<<):将整数的二进制位向左移动指定的位数,右侧用0填充。

int a = 60;     /* 60 = 0011 1100 */
int b = a << 2; /* b =  0111 1000 =240 */

6,右移(>>):将整数的二进制位向右移动指定的位数,左侧根据整数的符号位填充0或1。

int a = 60;     /* 60 = 0011 1100 */
int b = a >> 2; /* b =  0000 1111 =15 */

使用位操作可以执行许多有用的任务,例如检查一个数的特定位是否为1,设置或清除一个数的特定位,快速地进行整数乘法和除法,等等。然而,使用位操作时需要特别小心,因为错误的操作可能会导致不可预测的结果。

5.2 define宏定义
在C语言中,#define是预处理指令的一部分,用于定义宏。宏可以是一个简单的常量、一个带有参数的表达式或是一个代码块。使用宏可以在编译前替换代码中的特定部分,从而实现代码的重用、简化和提高可读性。

  1. 无参数宏定义
    无参数宏定义是最简单的形式,它只是一个标识符和一个值的组合。
#define PI 3.14159

在代码中,每次出现PI,预处理器都会将其替换为3.14159。
2. 带参数宏定义
带参数宏定义允许宏接受一个或多个参数,并返回一个表达式。

#define SQUARE(x) ((x) * (x))

这里,SQUARE是一个宏,它接受一个参数x,并返回x的平方。注意,宏中的参数应该用括号括起来,以避免因为运算符优先级导致的问题。
3. 宏定义的代码块
虽然不常见,但宏定义还可以包含多个语句。这通常用于实现复杂的操作或内联函数。

#define SWAP(a, b) do { \  typeof(a) temp = a; \  a = b; \  b = temp; \  
} while (0)

SWAP宏交换两个变量的值。由于宏是文本替换,因此需要用do ... while(0)来确保宏内部的多条语句被当作一个单独的语句块处理。
4. #undef
可以使用#undef指令来取消一个宏的定义。

#define PI 3.14159  
/* ... 使用PI... */  
#undef PI
/* 之后PI不再是一个宏 */
  1. 宏定义的注意事项
    l 宏定义只是简单的文本替换,没有类型检查或作用域限制。
    l 宏可能会因为参数的运算符优先级导致预期之外的行为,所以使用时要特别小心。
    l 宏定义可能会导致代码膨胀,因为每个宏的使用都会导致相同代码的重复插入。
    l 避免在宏中使用复杂的表达式或逻辑,因为这会增加代码阅读和维护的难度。
    l 使用宏时要谨慎,以避免出现意外的副作用或难以调试的错误。

5.3 ifdef条件编译
在C语言中,#ifdef是预处理指令的一部分,用于条件编译。条件编译允许你在编译时根据特定的条件来决定是否包含某些代码段。这对于编写跨平台或可配置的代码非常有用。
#ifdef检查是否定义了一个宏(使用#define指令)。如果宏已经定义,那么#ifdef和紧随其后的#endif之间的代码将被包含在编译中。如果宏没有定义,那么这部分代码将被忽略。
下面是一个简单的例子:

#define FEATURE_A  int main() {  #ifdef FEATURE_A  /* 这段代码将被编译,因为FEATURE_A已经定义 */  printf("FeatureA is enabled.\n");  #else  /* 这段代码不会被编译 */  printf("FeatureA is disabled.\n");  #endif  #ifndef FEATURE_B  /* 这段代码将被编译,因为FEATURE_B没有被定义  */printf("FeatureB is not defined.\n");  #else  /* 这段代码不会被编译 */  printf("FeatureB is defined.\n");  #endif  return 0;  
}

在这个例子中,FEATURE_A被定义了,所以#ifdef FEATURE_A和#endif之间的代码会被编译。而FEATURE_B没有被定义,所以#ifndef FEATURE_B和#endif之间的代码会被编译。
除了#ifdef,还有其他的条件编译指令:
l #ifndef:如果宏没有定义,则包含代码。
l #if:用于检查宏是否定义以及它的值是否为真(非零)。
l #elif:与#if和#else结合使用,用于检查多个条件。
l #else:如果前面的#if或#ifdef条件不满足,则包含代码。
l #endif:标记条件编译块的结束。
这些指令通常在源代码的顶部使用,以根据特定的配置或平台条件包含或排除代码段。例如,你可能想要在不同的操作系统上使用不同的系统调用,或者在调试和发布版本中包含或排除调试代码。

5.4 extern外部申明
在C语言中,extern关键字用于声明一个变量或函数,而不是定义它。extern告诉编译器,变量的定义或函数的实现在其他地方,可能是在另一个源文件中。这允许程序的不同部分共享同一个变量或函数,而无需在每个文件中都重复定义它。
1,变量外部声明
当你想在一个源文件中使用另一个源文件中定义的变量时,你需要使用extern来声明这个变量。例如,假设你有一个名为variables.c的文件,它定义了一个名为globalVar的全局变量:

/* variables.c */  
#include <stdio.h>  int globalVar = 100; /* 定义全局变量 */

现在,如果你想在另一个源文件main.c中使用这个变量,你可以这样声明它:

/* main.c */
#include <stdio.h>  extern int globalVar; /* 外部声明全局变量 */  int main()
{  printf("Thevalue of globalVar is: %d\n", globalVar);  return 0;  
}

在这个例子中,main.c并没有定义globalVar,而是使用了extern关键字来声明它。这告诉编译器,globalVar的定义存在于其他地方,并且在链接阶段,链接器会找到这个定义。
2,函数外部声明
同样,当你想在一个源文件中调用另一个源文件中定义的函数时,你需要使用extern来声明这个函数。例如,假设你有一个名为functions.c的文件,它定义了一个名为myFunction的函数:

/* functions.c */
#include <stdio.h>  void myFunction() {  printf("Thisis my function!\n");  
}

然后,在main.c中,你可以这样声明并使用这个函数:

/* main.c */
#include <stdio.h>  extern void myFunction(); /* 外部声明函数*/  int main()
{  myFunction(); /* 调用函数*/  return 0;  
}

在这个例子中,main.c中并没有定义myFunction,而是通过extern关键字进行了声明。在编译和链接阶段,链接器会找到myFunction的定义,并将其与main.c中的调用关联起来。
3,注意事项
l extern只能用于声明变量或函数,不能用于定义。定义会分配内存空间,而声明不会。
l 当你在一个源文件中定义了一个变量或函数,并想在另一个源文件中使用它时,你需要在第二个源文件中使用extern进行声明。
l 外部声明必须在使用变量或函数之前进行。
l 在链接阶段,链接器会查找所有extern声明的定义,如果找不到,就会出现链接错误。
l 如果多个源文件定义了同一个extern变量,那么链接器会将其视为错误,因为同一个变量只能有一个定义。但是,多个源文件可以包含同一个extern函数的声明,因为这个函数可能在不同的地方有不同的实现。

5.5 typedef类型别名
typedef用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。例如在编写程序时经常使用到的uint8_t、uint16_t和uint32_t等都是由typedef定义的类型别名,其定义如下:

typedef unsigned char      uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int       uint32_t;

这么一来就可以在编写程序代码的时候使用uint8_t等代替unsigned char等,极大地提高的代码的可读性可编写代码的效率。

5.6 struct结构体
struct用于定义结构体,结构体就是一堆变量的集合,结构体中的成员变量的作用一般都是相互关联的,定义结构体的形式如下:

struct 结构体名
{成员变量1的定义;成员变量2的定义;......
};

例如:

struct lcd_device_struct
{uint16_t width;uint16_t height;
};

如上举例的结构体定义,一堆描述LCD屏幕的变量的集合,其中包含了LCD屏幕的宽度和高度。
结构体变量的定义如下:

struct lcd_device_struct lcd_device;

如上,就定义了一个名为lcd_device的结构体变量,那么怎么访问这个结构体变量中的成员变量呢?如下:

lcd_device.width = 240;
printf("LCD Height: %d\n", lcd_device.height);

如上就展示了结构体变量中成员变量的访问操作。
任何时候,我们只需要修改结构体成员变量,往结构体中间加入新的成员变量,而不需要修改函数定义就可以达到修改入口参数同样的目的了。这样的好处是不用修改任何函数定义就可以达到增加变量的目的。
理解了结构体在这个例子中间的作用吗?在以后的开发过程中,如果你的变量定义过多,如果某几个变量是用来描述某一个对象,你可以考虑将这些变量定义在结构体中,这样也许可以提高你的代码的可读性。
使用结构体组合参数,可以提高代码的可读性,不会觉得变量定义混乱。当然结构体的作用就远远不止这个了,同时,VSCode中用结构体来定义外设也不仅仅只是这个作用,这里我们只是举一个例子,通过最常用的场景,让大家理解结构体的一个作用而已。后面一节我们还会讲解结构体的一些其他知识。
5.7 指针
指针是一个值指向地址的变量(或常量),其本质是指向一个地址,从而可以访问一片内存区域。在编写ESP32代码的时候,或多或少都要用到指针,它可以使不同代码共享同一片内存数据,也可以用作复杂的链接性的数据结构的构建,比如链表,链式二叉树等,而且,有些地方必须使用指针才能实现,比如内存管理等。
申明指针我们一般以p开头,如:

char * p_str = “This is a test!”;

如上,就定义一个名为p_str的指针变量,并将p_str指针指向了字符串“This is a string!”保存在内存中首地址,对于ESP32来说,此时p_str的值就是一个32位的数,这个数就是一个内存地址,这个内存地址就是上述字符串保存在内存中的首地址。
通过p_str指针就可以访问到字符串“This is a string!”,那具体是如何访问的呢?前面说p_str保存的是一个内存地址,那么就可以通过这个内存地址去内存中读取数据,通过p_str就可以访问地址为p_str的内存数据,(p_str + 1)可以访问下一个内存地址中的数据。
知道了如何访问内存中的数据,但是读取到的数据要如何解析呢?这就有p_str指针的类型决定了。在这个例子中p_str是一个char类型的指针,那么访问p_str就是访问地址为p_str,大小为sizeof(char)(一般为一个字节)的一段内存数据,在这个例子中就可以读取到字符“T”, 读取(p_str+ 1)就是“h”,以此类推。
为了更加直观的演示,我们试着编写如下代码并观察输出结果的变化:

/**
*@brief       程序入口
*@param       无
*@retval      无
*/
void app_main(void)
{esp_err_t ret;uint8_t temp = 0x88;                        /* 定义变量temp */uint8_t *p_num = &temp;                     /* 定义指针p_num,指向temp的地址 */ret=nvs_flash_init();                     /* 初始化NVS */
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||ret == ESP_ERR_NVS_NEW_VERSION_FOUND){ESP_ERROR_CHECK(nvs_flash_erase());ret =nvs_flash_init();}printf("temp:0X%X\r\n", temp);                         /* 打印temp的值 */printf("*p_num:0X%X\r\n", *p_num);                    /* 打印*p_num的值 */printf("p_num:0X%x\r\n", (unsigned int)(long)p_num);   /* 打印p_num的值 */printf("&p_num:0X%x\r\n", (unsigned int)(long)&p_num); /* 打印&p_num的值 */
}

此代码的输出结果为:

图5.7.1 终端输出结果
①: p_num:是uint8_t类型指针,指向temp变量的地址,其值等于temp变量的地址。
②:*p_num:取p_num指向的地址所存储的值,即temp的值。
③:p_num:指针的地址。
④:&p_num:取p_num指针的地址,即指针自身的地址。
以上,就是指针的简单使用和基本概念说明,指针的详细知识和使用范例大家可以百度学习,网上有非常多的资料可供参考。指针是C语言的精髓,在后续的代码中我们将会大量用到各种指针,大家务必好好学习和了解指针的使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/868951.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Avalonia 简单实现输入法光标跟随效果

本文将告诉大家如何在 Avalonia 里面实现输入法光标跟随效果本文是在 11.1.0 的 Avalonia 版本里面实现效果 本文内容里面只给出关键代码片段,如需要全部的项目文件,可到本文末尾找到本文所有代码的下载方法 核心是编写一个类型,让这个类型继承 TextInputMethodClient 类。然…

【一文详解】解决跨网文件传输三大瓶颈 选择专业文件摆渡系统

在数字化办公日益普及的今天,跨网文件传输成为许多企业面临的技术瓶颈。传统的文件传输方式往往无法满足跨地域、跨网络环境中的需求,尤其是随着数据量的增大和传输频率的提高,企业在进行文件交换时频繁遇到速度慢、安全性差、稳定性差等问题。因此,选择一款专业的文件摆渡…

SecureCRT v9.5.2 for Mac SSH终端操作工具

SecureCRT v9.5.2 for Mac SSH终端操作工具 安装 介绍 SecureCRT Mac是一款SSH终端工具,为计算专业人士提供高级会话管理工具。也是一个功能强大且值得信赖的基于GUI的SHH和Telnet客户端,以及旨在提高工作效率并简化重复任务的终端仿真器。借助SecureCRT mac版的帮助,您可以…

Gitlab流水线配置

由于格式和图片解析问题,为了更好阅读体验可前往 阅读原文流水线的流程是,提交代码后,gitlab会检测项目根目录里的.github-ci.yml文件,根据文件中的流水线自动构建,配置文件格式正确性可以在gitlab进行文件校验,格式使用yaml文件格式,一个yaml文件就是一个流水线,里面会…

验证表单输入:必填域

问题 希望确保必须为一个表单元素提供一个值。例如,希望保证一个文本框不为空。 解决方案 使用filter_has_var()查看这个元素在相应的输入数组中是否存在。 严格表单验证 // 检查POST请求中是否存在first_name字段,并且这个字段的长度大于0 if(!(filter_has_var(INPUT_POST, …

[2025.1.13 JavaSE学习]集合-7(Hashtable Properties)

Hashtable 特点和HashMap处于同一级,实现了Map接口,继承了Dictionary类 Hashtable的key和value都不能为null,否则会抛出NullPointException 是线程安全的,带有synchronized修饰扩容机制底层有数组Hashtable$Entry[ ],初始化大小为11 临界值threshold为8 = 11/* 0.75 而后按…

Ansible工具集使用指南

作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任。 目录一.ansible相关的工具概述1.查看ansible相关的工具2.本质上是一个Python脚本文件二.ansible-doc工具使用指南1 ansible-doc查看ansible文档2.列出支持的模块列表3.过滤出指定模块4.列出指定类型模块5.显示…

网络云服务

一、网络服务介绍 网络是指多个计算机或其他设备连接在一起,以便它们可以互相通信和共享资源的系统。 网络可以是局域网(Local Area Network,LAN)、广域网(Wide Area Network,WAN)或互联网(Internet)等不同类型。 华为云拥有丰富的网络服务,提供安全、可扩展的云上网…

Get-Command *http* 后,你会看到与 HTTP 相关的命令和工具的列表。以下是这些命令和它们的简要描述:

inetcpl.cpl 是 Internet Explorer 设置面板的控制面板项文件,运行此命令将打开 Internet Explorer 的设置界面,其中包括浏览器的常规设置、连接设置、安全性设置等。Get-Command *http* 后,你会看到与 HTTP 相关的命令和工具的列表。以下是这些命令和它们的简要描述: 1. N…

快速上手 INFINI Console 的 TopN 指标功能

背景 在分布式搜索引擎系统(如 Easysearch、Elasticsearch 和 OpenSearch)中,性能监控至关重要。为了确保系统的高效运行和资源的合理分配,我们通常需要关注一段时间内关键资源的使用情况,特别是索引、节点和分片的内存与 CPU 占用情况。 通过对这些关键指标进行 TopN 查询…

特斯拉CEO埃隆马.斯克的五步工作法,怎么提高工程效率加速产品开发?

简介 在《埃隆马斯克传》这本书中,有两个章节写到了特斯拉 CEO 埃隆马斯克为了在一段时间内,提升特斯拉汽车 model 3 的产能到每个月 5000 辆这个数量级,在书中叫 “量产地狱”,这是他的目标。 马斯克扎根工厂,睡在工厂的地板上近一年,亲自参与生产线的调试和优化,通过反…

iStoreOS_x86-U盘安装写入内置硬盘启动

https://doc.linkease.com/zh/guide/istoreos/install_x86.html#_1-%E8%A7%86%E9%A2%91%E4%BB%8B%E7%BB%8D 86 物理机x86物理机,范围很广,可以是各种"电脑",或者J4125/N5105等小主机。这里介绍x86实机安装iStoreOS固件。#1.视频介绍#2.准备工作一个 U盘 一个显示器…