第2章.STM32开发C语言常用知识点

目录

0. 《STM32单片机自学教程》专栏总纲 

2.1. STM32嵌入式开发C语言编程的不同

2.2. C语言常用知识点

2.2.1 位操作

2.2.2 define 宏定义

2.2.3 条件编译

2.2.3.1 #ifdef

2.2.3.2 #ifndef

2.2.3.3 #if !defined

2.2.4 extern 变量声明

2.2.5  typedef 类型别名

2.2.6 结构体

2.2.6.1 结构体的声明和定义

2.2.6.2 引用结构体成员变量 

2.2.6.3 结构体的作用

2.2.6.4 结构体成员的内存分布与对齐

2.2.7 关键字

2.2.7.1 volatile 

2.2.7.2 const

2.2.7.3 static

2.2.8 指针        

参考资料:

0. 《STM32单片机自学教程》专栏总纲 

        本文作为专栏《STM32单片机自学教程》专栏其中的一部分,返回专栏总纲,阅读所有文章,点击Link:  

STM32单片机自学教程-[目录总纲]_stm32 学习-CSDN博客

2.1. STM32嵌入式开发C语言编程的不同

         STM32开发中的C语言编程与通用计算机编程之间存在一些显著的区别,这些区别主要源于两者不同的应用场景和硬件环境。如下图2.1-1,区别主要体现在以下五个方面: 

图2.1-1 STM32嵌入式开发C语言编程和通用编程的区别点

  1. 硬件相关性
    • STM32开发中的C语言编程直接关联到特定的硬件,如微控制器、IO端口、中断、DMA等。开发者需要直接操作这些硬件资源,因此必须了解相关的硬件手册和寄存器配置。
    • 通用计算机编程则更多关注于软件设计和算法实现,与硬件的关联度较低。开发者通常不需要直接操作硬件寄存器,而是通过操作系统提供的API进行编程。
  2. 资源限制
    • STM32等嵌入式系统通常具有有限的内存、存储空间和计算能力。因此,在STM32开发中,C语言编程需要特别注意内存管理、代码优化和性能调优。
    • 通用计算机则具有较大的内存和存储空间,以及强大的计算能力。开发者在编写通用计算机程序时,通常不需要过分关注这些资源限制。
  3. 实时性要求
    • STM32等嵌入式系统通常需要满足严格的实时性要求,即系统需要在规定的时间内响应外部事件。因此,在STM32开发中,C语言编程需要特别注意时间管理和代码执行效率。
    • 通用计算机编程则通常不需要满足如此严格的实时性要求。
  4. 开发工具和环境
    • STM32开发通常使用专门的嵌入式开发环境和工具链,如Keil MDK、IAR Embedded Workbench、STM32CubeIDE等。这些工具提供了针对STM32硬件的特定支持和优化。
    • 通用计算机编程则可以使用各种通用的集成开发环境(IDE),如Visual Studio、Eclipse、Dev-C++等。
  5. 调试和测试
    • STM32开发中的调试和测试通常需要借助专门的调试器、仿真器和测试工具,以模拟硬件环境和验证程序功能。
    • 通用计算机编程则可以使用各种调试器和测试框架,以方便地进行程序调试和测试。 

        总之,STM32开发中的C语言编程与通用计算机编程在硬件相关性、资源限制、实时性要求、开发工具和环境以及编程语言特性等方面存在显著的区别。这些区别要求开发者在编写STM32程序时,需要更加注重底层编程和硬件操作,并充分考虑到嵌入式系统的特殊性和限制。 

2.2. C语言常用知识点

        我们这里就列举部分 STM32 学习中会遇见的 C  语言基础知识点。

2.2.1 位操作

        C 语言位操作就是对基本类型变量可以在位级别进行操作。C 语言支持如下表6种位操作:

表2.1-1-C语言支持的位操作 

运算符含义
&按位与
|按位或
^按位异或
~按位取反
<<左移
>>右移

        这些按位与或,取反,异或,右移,左移这些我们就不多做详细讲解,毕竟这里不是给大家普及C语言的基本知识,不清楚的大家可以再复习一下。下面我们着重讲解位操作在嵌入式开发中的一些实用技巧。 

1.在不改变其他位的值的状况下对某几个位进行设值

        这个场景在单片机开发中经常使用,方法就是先对需要设置的位用"&"操作符进行清零操作,然后用"|"操作符设值。比如我要改变 GPIOA 的状态,可以先对寄存器的值进行&清零操作:

GPIOA->CRL &= 0XFFFFFF0F;    /* 将第 4~7位清 0 */ 
/*然后再与需要设置的值进行|或运算:*/ 
GPIOA->CRL |= 0X00000040;    /* 设置相应位的值(4),不改变其他位的值 */ 

2.移位操作提高代码的可读性

         移位操作在单片机开发中非常重要,下面是 delay_init 函数的一行代码:

SysTick->CTRL |= 1 << 1;

        这个操作就是将 CTRL 寄存器的第 1 位(从 0 开始算起)设置为 1,为什么要通过左移而不是直接设置一个固定的值呢?其实这是为了提高代码的可读性以及可重用性。这行代码可以很直观明了的知道,是将第 1 位设置为 1。如果写成:

SysTick->CTRL |= 0X0002;

         这个虽然也能实现同样的效果,但是可读性稍差,而且修改也比较麻烦。

3.~按位取反操作使用技巧

        按位取反在设置寄存器的时候经常被使用,常用于清除某一个/某几个位。下面是 delay_us函数的一行代码:

SysTick->CTRL &= ~(1 << 0) ;    /* 关闭 SYSTICK */ 

        该代码可以解读为  仅设置 CTRL 寄存器的第 0 位(最低位)为 0,其他位的值保持不变。同样我们也不使用按位取反,将代码写成:  

SysTick->CTRL &= 0XFFFFFFFE;        /* 关闭 SYSTICK */ 

        可见前者的可读性,及可维护性都要比后者好很多。

4.^按位异或操作使用技巧

        该功能非常适合用于控制某个位翻转,常见的应用场景就是控制 LED 闪烁,如:

GPIOB->ODR ^= 1 << 5;

        执行一次该代码,就会使 PB5 的输出状态翻转一次,如果我们的 LED 接在 PB5 上,就可以看到 LED 闪烁了。 

2.2.2 define 宏定义

        define 是 C 语言中的预处理命令,它用于宏定义(定义的是常量),可以提高源代码的可读性,为编程提供方便。常见的格式:  

 #define         标识符       字符串

         "标识符"为所定义的宏名;"字符串"可以是常数、表达式、格式串等。例如:

#define PIE 3.14159f

         PIE在后续出现的地方都代表3.14159。后续如果想修改π的值,可以直接在宏定义的地方修改,不用再在程序出现的每一个地方再去修改,而且非常直观,代码可读性强。

2.2.3 条件编译

2.2.3.1 #ifdef

        嵌入式程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。条件编译命令最常见的形式为:

   #ifdef 标识符  程序段 1  #else  程序段 2  #endif  

        它的作用是:当标识符已经被定义过(一般是用#define 命令定义),则对程序段 1 进行编译,否则编译程序段 2。  其中#else 部分也可以没有,即:

    #ifdef  程序段 1  #endif 
2.2.3.2 #ifndef
#ifndef SOME_MACRO  
// 如果 SOME_MACRO 没有被定义,则编译以下代码  
#endif
2.2.3.3 #if !defined
#if !defined(SOME_MACRO)  
// 如果 SOME_MACRO 没有被定义,则编译以下代码  
#endif

          这也是检查是否没有定义某个宏的方法,但它使用了!defined操作符.

        在这个例子中!defined(SOME_MACRO) 是一个条件表达式,当 SOME_MACRO 没有被定义时,该表达式的值为真(非零),从而编译 #if 和对应 #endif 之间的代码。下面是STM32里的一段代码:

    #if !defined  (HSE_VALUE)  #define HSE_VALUE            24000000U  #endif  

        如果没有定义HSE_VALUE这个宏,则定义HSE_VALUE宏,并且HSE_VALUE的值为24000000U。24000000U中的U表示无符号整型,常见的,UL表示无符号长整型,F表示浮点型。这里加了U以后,系统编译时就不进行类型检查,直接以U的形式把值赋给某个对应的内存,如果超出定义变量的范围,则截取。

2.2.4 extern 变量声明

        C 语言中 extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。这里面要注意,对于extern声明变量可以多次,但定义只有一次。在我们的代码中你会看到看到这样的语句: 

extern uint16_t speed_x; 

        这个语句是申明 speed_x变量在其他文件中已经定义了,在这里要使用到。所以,你肯定可以找到在某个地方有变量定义的语句:

uint16_t speed_x;

2.2.5  typedef 类型别名

        typedef 用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。

        例如C99标准中引入的头文件<stdint.h>,定义了一组具有固定宽度的整数类型,包括有符号和无符号的8位、16位、32位和64位整数。这些类型分别命名为int8_t、int16_t、int32_t、int64_t(以及对应的无符号类型uint8_t、uint16_t、uint32_t、uint64_t)。在STM32F10x的标准库函数stm32f10x.h中又对这些数据类型进行了重新定义,代码如下:

typedef int32_t  s32;
typedef int16_t s16;
typedef int8_t  s8;
typedef uint32_t  u32;
typedef uint16_t u16;
typedef uint8_t  u8;

        typedef在 MDK 用得最多的就是定义结构体的类型别名和枚举类型了。

struct _GPIO 
{ 
__IO uint32_t CRL; 
__IO uint32_t CRH; 
… 
}; 

         定义了一个结构体 GPIO,这样我们定义结构体变量的方式为:

struct  _GPIO  gpiox;       /* 定义结构体变量 gpiox */ 

         但是这样很繁琐,MDK中有很多这样的结构体变量需要定义。这里我们可以为结体定义一
个别名GPIO_TypeDef,这样我们就可以在其他地方通过别名GPIO_TypeDef来定义结构体变量了,方法如下: 

typedef struct 
{ 
__IO uint32_t CRL; 
__IO uint32_t CRH; 
… 
} GPIO_TypeDef; 

        Typedef为结构体定义一个别名GPIO_TypeDef,这样我们可以通过GPIO_TypeDef来定义结构体变量:  

GPIO_TypeDef gpiox;

         这里的 GPIO_TypeDef 就跟 struct  _GPIO 是等同的作用了,但是 GPIO_TypeDef 使用起来方便很多。

2.2.6 结构体

        在C语言中,结构体(struct)是一种用户自定义的数据类型,它允许你将不同类型的数据项组合成一个单独的数据结构。结构体可以用来表示一个具有复杂属性的实体,比如一个人(具有姓名、年龄、性别等属性)或者一本书(具有书名、作者、出版日期等属性)。

2.2.6.1 结构体的声明和定义
/*声明结构体类型: */struct 结构体名 { 成员列表; }变量名列表; 

         你可以在声明结构体的时候直接创建结构体变量,也可以先定义结构体类型,然后再创建变量,如下面几种方式都是可以的:

// 直接定义并创建结构体变量  
struct {  int age;  char name[50];  
} person1;  // 直接定义并创建结构体变量  
struct Person{  int age;  char name[50];  
} person2; // 先定义结构体类型,再创建变量  
struct Person {  int age;  char name[50];  
};  
struct Person person3;
2.2.6.2 引用结构体成员变量 

        要访问结构体变量的成员,你需要使用.运算符(对于结构体变量)或->运算符(对于指向结构体的指针)。

/*接前面章节2.6.1的示例代码*/
// 访问结构体变量的成员  
person1.age = 25;    // 如果有一个指向结构体的指针  
struct Person *ptr = &person2;  
ptr->age = 30; 
2.2.6.3 结构体的作用

        下面我们将简单的通过一个实例描述一下结构体的作用。 

        在我们单片机程序开发过程中,经常会遇到要初始化一个外设比如串口,它的初始化状态是由几个属性来决定的,比如串口号,波特率,极性,以及模式。对于这种情况,在我们没有学习结构体的时候,我们一般的方法是:

 void usart_init(uint8_t usartx, uiut32_t BaudRate, uint32_t Parity,  
uint32_t Mode); 

        这种方式是有效的同时在一定场合是可取的。但是试想,如果有一天,我们希望往这个函数里面再传入一个/几个参数,那么势必我们需要修改这个函数的定义,重新加入新的入口参数,随着开发不断的增多,那么是不是我们就要不断的修改函数的定义呢?这是不是给我们开发带来很多的麻烦?那又怎样解决这种情况呢?
        我们使用结构体参数,就可以在不改变入口参数的情况下,只需要改变结构体的成员变量就可以达到改变入口参数的目的。
        结构体就是将多个变量组合为一个有机的整体,上面的函数usartx,BaudRate,Parity,Mode等这些参数,他们对于串口而言,是一个有机整体,都是来设置串口参数的,所以我们可以将他们通过定义一个结构体来组合在一个。MDK中是这样定义的:

typedef struct 
{  
uint32_t BaudRate; 
uint32_t WordLength; 
uint32_t StopBits; 
uint32_t Parity; 
uint32_t Mode; 
uint32_t HwFlowCtl; 
uint32_t OverSampling;    
} UART_InitTypeDef; 

        这样,我们在初始化串口的时候入口参数就可以是 USART_InitTypeDef 类型的变量或者指针变量了,于是我们可以改为:

void usart_init(UART_InitTypeDef *huart); 

        这样,任何时候,我们只需要修改结构体成员变量,往结构体中间加入新的成员变量,而不需要修改函数定义就可以达到修改入口参数同样的目的了。这样的好处是不用修改任何函数定义就可以达到增加变量的目的。
        在以后的开发过程中,如果你的变量定义过多,如果某几个变量是用来描述某一个对象,你可以考虑将这些变量定义在结构体中,这样也许可以提高你的代码的可读性。使用结构体组合参数,可以提高代码的可读性,不会觉得变量定义混乱。

2.2.6.4 结构体成员的内存分布与对齐

        首先一些基本知识点:
(1)声明一个结构体类型的时候是没有为它分配任何存储空间的,只有在定义结构体变量的时候,才会为变量分配存储空间。
(2)结构体中可以有不同的数据类型成员,成员在定义时依次存储在内存连续的空间中,结构体变量的首地址就是第一个成员的地址,内存偏移量就是各个成员相对于第一个成员地址的差(即,把低位内存分配给最先定义的变量)。
(3)理论上,结构体所占用的存储空间是各个成员变量所占的存储空间之和,但是为了提高CPU的访问效率,采用了内存对齐方式:
        ①结构体的每一个成员起始地址必须是自身类型大小的整数倍,若不足,则不足部分用数据填充至所占内存的整数倍。
        ②结构体大小必须是结构体占用最大字节数成员的整数倍,这样在处理数组时可以保证每一项都边界对齐根据上面的说明,我们举例子分析如下:

    struct test { char a; int b; float c; double d; }mytest; 

        这个结构体所占用的内存怎么算呢?理论结果为17,实际上并不是17,而是24。为什么会这样呢?这个就是前面我们说的内存对齐。
        char型变量占1个字节,所以它的起始地址是0。int类型占用4个字节,它的起始地址要求是4的整数倍数,那么内存地址1、2、3就需要被填充(被填充的内存不适于变量),b从4开始。float类型也是占用4个字节,起始地址要求是4的倍数,所以c的起始地址就是8。double类型变量占用8个字节,起始地址为16,12~15被填充。这里,第一个成员a的地址首地止,第二个成员b的偏移量为4,第三个成员c的偏移量是8,以此类推,是如下图2.2-1所示:

图2.2-1 结构地地址内存分配  

2.2.7 关键字

        在STM32的一些库函数头文件中,经常会看到如下代码, 表示将 volatile 或者 volatile  const 来代替某一个符号。

#define   __I     volatile 
#define   __O     volatile    
#define   __IO    volatile            
#define   __IM     volatile const    
#define   __OM     volatile            
#define   __IOM    volatile 
2.2.7.1 volatile 

         volatile 表示强制编译器减少优化,告诉编译器必须每次去内存中取变量值。     

        程序运行时数据是存储在主内存(物理内存)中的,每个线程先从主内存拷贝变量到对应的寄存器中。对没有加volatile的变量进行读写时,为了提高读取速度,编译器进行优化时,会先把主内存中的变量读取到一个寄存器中,以后,再读取此变量的值时,就直接从该寄存器中读取,而不是直接从内存中读取了,这样的读写速度比较快。如果其它程序改变了内存中变量的值,上面已经保存到寄存器中的值不会跟着改变,从而造成应用程序读取的值和实际的变量值不一致。加了修饰关键字volatile以后的变量,表示不想被编译器优化掉,每次都要从内存中读取该变量的数据,不会用寄存器里的值,这样确保了数据的准确性,但影响了效率。 

2.2.7.2 const

        const称为常量限定符,用来限定特定变量为只读属性,如果修改此变量,则编译器会报错。const修饰的变量存储在只读数据段,在程序结束时释放,而const局部变量存储在栈中,代码块结束时释放。用const定义变量时就要初始化该变量:       

 const int a = 1; 
2.2.7.3 static

        static关键字修饰的变量称为静态变量,如果该变量在声明时未赋初始值,则编译器自动初始化为0,静态变量存储在全局区(静态区)。
        在函数内被static声明的变量,仅能在本函数中使用,也叫静态局部变量。
        在文件内(函数体外)被static声明的变量,仅能被本文件内的函数访问,不能被其他文件中的函数访问,也叫静态全局变量。
        静态全局变量和普通的全局变量不同,静态全局变量仅限于本文件中使用,在其它文件中可以定义一个与静态全局变量名字相同的变量。普通的全局变量可以通过extern外部声明后被其他文件使用,也就是整个工程可见,而且其他文件不能再定义一个与普通全局变量名字相同的变量了。

        用static修饰的函数和用static修饰的变量类似。 下面是用法举例说明:

1.局部静态变量
        当在函数内部声明一个变量为static时,该变量的存储期将变为整个程序的执行期,而不是只在函数调用被时存在。这意味着局部变量只会被初始化一次,并且会保留其值,直到程序结束。这在需要跨函数调用保留某些信息时非常有用。

void func() {  static int count = 0; // 只在程序开始时初始化一次  count++;  printf("%d\n", count);  
}

        每次调用func()时,count的值都会递增。

2.全局静态变量
        在文件级别(即不在任何函数内部)声明的static变量只能在该文件内部可见。这意味着它们只能被定义它们的文件内的函数访问,而不能被其他文件访问。这提供了一种封装机制,允许你在一个文件中定义和使用变量,而不用担心与其他文件冲突。

// file1.c  
static int file_scope_var = 42; // 只能在file1.c中访问  // file2.c  
extern int file_scope_var; // 错误:无法在其他文件中访问file_scope_var

3.静态函数:
        当在文件级别使用static关键字声明一个函数时,该函数将具有内部链接,即它只能在其定义的文件内被调用。这提供了另一种封装机制,允许你隐藏函数的实现细节,只暴露需要被其他文件使用的函数。

// file1.c  
static void internal_function() {  // ...  
}  // file2.c  
extern void internal_function(); // 错误:无法在其他文件中调用internal_function

4.静态初始化

        尽管这不是static的直接用途,但它在静态初始化中扮演了重要角色。当全局变量或静态变量被声明并赋予初值时,编译器会确保在程序开始执行之前进行初始化。这通常是在main()函数之前发生的。 

2.2.8 指针        

        在STM32开发中,指针的作用十分重要。首先,指针是C语言的一个重要组成部分,它允许我们通过内存地址直接访问和操作数据。在STM32这样的嵌入式系统开发中,指针的使用与底层硬件的联系尤为密切。

        具体来说,STM32库开发中,我们对寄存器进行了封装,将寄存器放入到结构体(如GPIOX)当中。通过指针,我们可以指向这些结构体的地址,从而访问和操作寄存器,完成对寄存器的配置。这种方式可以减少开发时的代码量,提高开发效率。

        同时,指针移位操作在STM32开发中也是常见的。通过指针移位,我们可以方便地访问连续的内存区域,比如数组或结构体中的连续元素。在C语言中,我们可以通过指针算术运算(如加法、减法)来实现指针的移位。需要注意的是,在进行指针移位操作时,应确保指针类型和指向的数据类型一致,并遵循C语言指针算术运算的规则。

        此外,指针还可以用于访问和操作内存映射的硬件寄存器。在STM32中,许多硬件资源都是通过内存映射的方式暴露给软件的。通过指针,我们可以直接访问这些硬件寄存器的地址,从而实现对硬件的控制和配置。

        总的来说,指针在STM32开发中具有重要的作用,它允许我们通过内存地址直接访问和操作数据,实现对硬件的底层控制和优化。然而,由于指针直接操作内存地址,因此在使用时也需要格外小心,以避免出现内存泄漏、野指针等问题。

        指针的具体使用方法,这里就不再赘述。

参考资料:


        【1】哔站江协科技STM32入门教程

        【2】《STM32单片机原理与项目实战》刘龙、高照玲、田华著

        【3】《ARM Cortex-M3嵌入式原理及应用》黄可亚著

        【4】《STM32嵌入式微控制器快速上手》陈志旺著

        【5】《STM32单片机应用与全案例实践》沈红卫等著

        【6】《野火STM32开发指南》

        【7】《正点原子STM32开发指南》

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Sealos急速部署生产用k8s集群

最近一段时间部署k8s全部使用sealos了&#xff0c;整体使用感觉良好&#xff0c;基本没有什么坑。推荐给大家。 使用 Sealos&#xff0c;可以安装一个不包含任何组件的裸 Kubernetes 集群。 最大的好处是提供 99 年证书&#xff0c;用到我跑路是足够了。不用像之前kubeadm安装…

在uniapp中如何安装axios并解决跨域问题

目录 1、安装axios 2、导入 3、使用&#xff08;发请求&#xff09; 2.解决跨域问题 1.为什么要解决跨域问题&#xff1f; 2.前端如何解决跨域问题&#xff1f; 1、安装axios npm install axios 2、导入 在main.js中导入使用 import axios from axios; // 创建一个名…

通过 Java 操作 redis -- hash 哈希表基本命令

目录 使用命令 hset&#xff0c;hget 使用命令 hexists 使用命令 hdel 使用命令 hkeys&#xff0c;hvals 使用命令 hmget&#xff0c;hmset 关于 redis hash 哈希表类型的相关命令推荐看Redis - hash 哈希表 要想通过 Java 操作 redis&#xff0c;首先要连接上 redis 服务…

思通数科大模型在智能数据查询系统中的深度应用:销售数据分析的革新

在企业决策支持系统中&#xff0c;销售数据分析占据着举足轻重的地位。思通数科的大模型技术&#xff0c;结合自然语言处理&#xff08;NLP&#xff09;和机器学习&#xff0c;为智能数据查询系统提供了强大的分析能力。本文将详细描述思通数科大模型在销售数据分析中的应用&am…

固定资产管理系统参考论文(论文 + 源码)

【免费】固定资产管理系统.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89282536 固定资产管理系统 摘 要 随着计算机信息技术的发展以及对资产、设备的管理科学化、合理化的高要求&#xff0c;利用计算机实现设备及资产的信息化管理已经显得非常重要。 固…

渗透之sql注入实战2(二次注入)

目录 平台地址&#xff1a; 开始&#xff1a; 方法1&#xff1a; 方法二 找提示 这里存在一个文件包含&#xff08;file&#xff09;。 爆源码 index.php源码 confirm.php源码&#xff1a; search.php源码&#xff1a; change.php源码&#xff1a; delete.php源码&…

【如何在本地安装Llama 3,坐好30秒!稳的很!!】

如何在本地安装Llama 3&#xff0c;30秒闪速教程 点击 Ollama官网 点击对应系统内容进行下载 解压后打开文件: 双击后打开 复制 打开CMD 窗口终端 复制黏贴 ollama run llama3 等下载完成 OK&#xff0c;安装完成&#xff01; 输入问题&#xff0c;愉快玩耍&#xff01; …

嵌入式学习69-C++(Opencv)

知识零碎&#xff1a; QT的两种编译模式 1.debug 调试模式 …

C++:类与对象—继承

类与对象—继承 一、继承是什么&#xff1f;二、继承定义三、基类和派生类对象赋值转换四、继承中的作用域五、派生类的默认成员函数六、继承与友元七、继承与静态成员八、复杂的菱形继承及菱形虚拟继承九、继承的总结和反思十、考察重点 一、继承是什么&#xff1f; 继承(inh…

​​​【收录 Hello 算法】第 4 章 数组与链表

第 4 章 数组与链表 数据结构的世界如同一堵厚实的砖墙。 数组的砖块整齐排列&#xff0c;逐个紧贴。链表的砖块分散各处&#xff0c;连接的藤蔓自由地穿梭于砖缝之间。 本章内容 4.1 数组4.2 链表4.3 列表4.4 内存与缓存 *4.5 小结

Clion STM32CubeMX 项目

系列文章目录 前言 最后修改 2024 年 4 月 16 日 操作系统&#xff1a;Windows / Linux / macOS 所需工具 STM32CubeMX、GNU ARM 工具链 项目格式&#xff1a; CMake 兼容配置&#xff1a; OpenOCD 运行与调试/嵌入式 GDB 服务器 对于以 STM32 板卡为目标的嵌入式项目&#xf…

小白入门:创建一个SpringBoot项目

前言 我们在创建SpringBoot项目时候&#xff0c;会出现不确定和报错的情况很多&#xff0c;大家可以按照我的做法来简单创建一个SpringBoot项目 1.环境配置 下载安装并配置jdk1.8下载apache mavenidea软件 2.开始创建项目 Server URL&#xff1a;初始是start.spring.io,我…

重写muduo之Thread、EventLoopThread、EventLoopThreadPool

目录 1、概述 2、Thread 2.1 Thread.h 3、EventLoopThread 3.1 EventLoopThread.h 3.2 EventLoopThread.cc 4、 EventLoopThreadPool 4.1 EventLoopThreadPool.h 4.2 EventLoopThreadPool.cc 1、概述 管理事件循环线程的调度的 打包了一个EventLoop和线程&#xff0c;…

JUC下的ScheduledThreadPoolExecutor详解

ScheduledThreadPoolExecutor是Java并发编程框架中一个强大且灵活的线程池实现&#xff0c;专为定时与周期性任务而设计。作为ThreadPoolExecutor的子类&#xff0c;它不仅继承了线程池管理的高效与灵活性&#xff0c;还内置了基于优先级队列的延迟任务调度机制&#xff0c;支持…

0508_IO3

练习1&#xff1a; 1&#xff1a;使用 dup2 实现错误日志功能 使用 write 和 read 实现文件的拷贝功能&#xff0c;注意&#xff0c;代码中所有函数后面&#xff0c;紧跟perror输出错误信息&#xff0c;要求这些错误信息重定向到错误日志 err.txt 中去 1 #include <stdio.h…

掌控网络流量,优化网络性能 - AnaTraf网络流量分析仪登场

在当今日新月异的网络环境中,网络流量监控和性能诊断已成为企业IT部门不可或缺的重要工作。只有充分了解网络流量状况,才能有效优化网络性能,提高业务运营效率。针对这一需求,全新推出的AnaTraf网络流量分析仪应运而生,为企业提供全面的网络监控和性能诊断解决方案。 快速定位…

NodeMCU ESP8266 操作 SSD1306 OLED显示屏详解(图文并茂)

文章目录 1 模块介绍2 接线介绍3 安装SSD1306驱动库4 源码分析4.1 硬件兼容性4.2 可能存在的问题总结1 模块介绍 我们将在本教程中使用的OLED显示屏是SSD1306型号:单色0.96英寸显示屏,像素为12864,如下图所示。 OLED显示屏不需要背光,这在黑暗环境中会产生非常好的对比度。…

【抽样调查】分层抽样上

碎碎念&#xff1a;在大一大二时听课有的时候会发现听不太懂&#xff0c;那时候只觉得是我自己的基础不好的原因&#xff0c;但现在我发现“听不懂”是能够针对性解决的。比如抽样调查这门课&#xff0c;分析过后我发现我听不懂的原因之一是“没有框架”&#xff0c;一大堆知识…

ssrf初步

一&#xff0c;简介 全称&#xff1a;Server-Side Request Forgery&#xff08;中文&#xff1a;服务器端请求伪造&#xff09; 攻击者从服务端发起请求&#xff0c;让服务器连接任意外部系统&#xff0c;从而泄露敏感数据。主要利用各种协议的请求伪造&#xff0c;例如php协…

Linux无root配置Node,安装nvm

1. 安装NVM&#xff1a; curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash # 或者&#xff0c;如果你使用wget wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash 对于bash用户&#xff0c;可以运行&…