首页 > 其他分享 >《ESP32-S3使用指南—IDF版 V1.6》第二章 常用的C语言知识点

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

时间:2025-01-14 09:32:51浏览次数:1  
标签:知识点 定义 S3 ESP32 int num 指针 代码 变量

第二章 常用的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语言的精髓,在后续的代码中我们将会大量用到各种指针,大家务必好好学习和了解指针的使用。

标签:知识点,定义,S3,ESP32,int,num,指针,代码,变量
From: https://www.cnblogs.com/zdyz/p/18662210

相关文章

  • 21. 大学生HTML5期末大作业 ―【简单的西藏介绍旅游主题网页】 Web前端网页制作 html5
    目录一、网页概述二、网页文件三、网页效果四、代码展示1.html2.CSS五、总结1.简洁实用2.使用方便3.整体性好4.形象突出5.交互式强六、更多推荐一、网页概述本实例应用html5+css3,div+css布局,代码简单,带实验报告书。本网页支持如Dreamweaver、HBuilder、Text......
  • 18. 大学生HTML5期末大作业 ―【简单的旅游网页】 Web前端网页制作 html5+css3
    目录一、网页概述二、网页文件三、网页效果四、代码展示1.html2.CSS五、总结1.简洁实用2.使用方便3.整体性好4.形象突出5.交互式强六、更多推荐一、网页概述本实例应用html5+css3,div+css布局,代码简单,带实验报告书。本网页支持如Dreamweaver、HBuilder、Text......
  • 信息系统项目管理师2025年考试关键知识点梳理-第8章 项目整合管理-制定项目管理计划、
    1、制定项目管理计划制订项目管理计划是定义、准备和协调项目计划的所有组成部分,并把它们整合为一份综合项目管理计划的过程。本过程的主要作用:生成一份综合文件,用于确定所有项目工作的基础及其执行方式。项目管理计划确定项目的执行、监控和收尾方式,其内容会根据项目所在......
  • 【学习路线】Python Web开发 详细知识点学习路径(附学习资源)
    学习本路线内容之前,请先学习Python的基础知识其他路线:Python基础>>Python进阶>>Python爬虫>>Python数据分析(数据科学) >>Python 算法(人工智能) >>Python Web开发 >>Python自动化运维 >>符号表解释:可根据知识点前的符号按需选学,并获取知识点描述和学习资源......
  • ESP32模拟IIC,0.96英寸OLED(四针),改编自江科大/江协。
    #喜欢的宝子可以copy。#本文采用ArduinoIDE开发,用到了多文件形式。点击这里可以创建文件。下面直接分享代码和效果图。效果图0.96OLED.ino #include"OLED.h"voidsetup(){OLED_Init();OLED_ShowString(1,1,"sugkug");}intcnt=0;voidloop()......
  • 如何使用CSS3或JavaScript实现页面动画效果?
    要使用CSS3或JavaScript实现页面动画效果,可以根据具体需求选择合适的方法。以下是基于我搜索到的资料,详细说明如何使用CSS3和JavaScript实现页面动画效果:使用CSS3实现页面动画效果1.CSS3过渡和动画CSS3提供了强大的过渡和动画功能,可以轻松创建元素状态改变时的平滑效果。......
  • 基于ESP32-S3单片机的OTA升级
    文章目录前言1.ESP32的OTA升级是什么?2.OTA升级的主要步骤3.优点4.ESP32进行OTA升级的机制5.OTA升级整体过程总结系列文章基于ESP32-S3单片机的OTA升级之移植官方代码(一)基于ESP32-S3单片机的OTA升级之巴法云平台(二)基于ESP32-S3单片机的OTA升级之整理官方工......
  • 【大厂面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据...本篇介绍训练网络的时
    【大厂面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据…本篇介绍训练网络的时候如何判断过拟合和欠拟合?【大厂面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据…本篇介绍训练网络的时候如何判断过拟合和欠拟合?文章目录【大厂面试AI算法题中的知识点】方......
  • CSS3新增伪类有哪些并简要描述
    CSS3新增的伪类包括多种,它们为前端开发者提供了更多的选择和灵活性,以便更好地控制页面元素的样式。以下是一些CSS3中新增的重要伪类及其简要描述::nth-child(n):此伪类允许开发者根据元素在父元素中的位置来选择元素。例如,:nth-child(2)会选择父元素中的第二个子元素,不论其类型。......
  • CSS3有哪些新增的特性?
    CSS3相较于之前的版本,新增了许多强大的特性,这些特性为前端开发者提供了更多的可能性和灵活性,使得网页的视觉效果更加丰富多彩,交互性更强。以下是一些主要的CSS3新增特性:选择器:CSS3引入了更多的选择器,包括属性选择器、伪类选择器(如:hover、:active、:visited)、伪元素选择器(如::b......