w~嵌入式C语言~合集6
我自己的原文哦~ https://blog.51cto.com/whaosoft/13870384
一、开源MCU简易数字示波器项目
这是一款采用STC8A8K MCU制造的简单示波器,只有零星组件,易于成型。这些功能可以涵盖简单的测量:
该作品主要的规格如下:
- 单片机:STC8A8K64S4A12 @27MHz
- 显示屏:0.96“ OLED,分辨率为 128x64
- 控制器:一个 EC11 编码器
- 输入:单通道
- 秒/秒:500 毫秒、200 毫秒、100 毫秒、50 毫秒、20 毫秒、10 毫秒、5 毫秒、2 毫秒、1 毫秒、500us、200us、100us
100us( 仅在自动触发模式下可用) - 电压范围:0-30V
- 采样额定值:250kHz @100us/格
所有操作均由 EC11 编码器完成。输入包括单击,双击,长按,旋转和旋转时按。这似乎有点复杂,不用担心,下面有细节。该编码器的资源几乎已经耗尽。如果有新功能,可能需要额外的输入组件。
主界面 - 参数模式
- 单击编码器:运行/停止采样。
- 双击编码器:进入波形滚动模式。
- 长按编码器:进入设置界面。
- 旋转编码器:调整参数。
- 按下时旋转编码器:在选项之间切换。
- 切换自动和手动量程:连续顺时针旋转编码器以进入自动量程。逆时针旋转编码器以进入手动范围。
主界面 - 波浪滚动模式
- 单击编码器:运行/停止采样。
- 双击编码器:进入参数模式。
- 长按编码器:进入设置界面。
- 旋转编码器:水平滚动波形。(仅在采样停止时可用)
- 按下时旋转编码器:垂直滚动波形(仅在采样停止时可用)
设置界面
- 单击式编码器:不适用
- 双击编码器:不适用
- 长按编码器:返回主界面。
- 旋转编码器:调整参数。
- 按下时旋转编码器:在选项之间切换。
功能
- 触发电平:对于重复信号,触发电平可以使其在显示屏上稳定。对于单发信号,触发电平可以捕获它。
- 触发斜率:触发斜率确定触发点是在信号的上升沿还是下降沿。
- 触发模式:
- 自动模式:连续扫描。单击编码器可停止或运行采样。如果触发,波形将显示在显示屏上,触发位置将放在图表的中心。否则,波形将不规则地滚动,并且显示屏上将显示“Fail”。
- 正常模式:完成预采样后,可以输入信号。如果触发,波形将显示在显示屏上并等待新的触发。如果没有新的触发器,波形将被保留。
- 单模:完成预采样后,可以输入信号。如果触发,将显示波形并停止采样。用户需要单击编码器才能开始下一次采样。
- 对于正常模式和单模式,请确保已正确调整触发电平,否则显示屏上不会显示波形。
- 指标:通常,指标 on 表示采样正在运行。更重要的用途是在单触发和正常触发模式下,在进入触发阶段之前,需要预先采样。在预采样阶段,指示器不会亮起。在指标亮起之前,我们不应该输入信号。选择的时间尺度越长,预采样的等待时间就越长。
- 保存设置:退出设置界面时,设置和主界面中的所有参数都将保存在EEPROM中。
作品展示部分效果如下:
好了,最好放该项目代码以及资料白嫖地址了:
https://github.com/CreativeLau/Mini-DSO
二、在STM32上实现驱动注册initcall机制
1、前言
每个硬件如LED控制,GPIO口需要初始化,初始化函数bsp_led_init();这个函数需要在主函数中调用初始化,类似这样:
void bsp_init(void)
{bsp_rcc_init();bsp_tick_init();bsp_led_init();bsp_usart_init();
}
这样存在的问题是:
当有很对驱动,加入100个硬件驱动,我们只用到了了50个,剩下的源文件不参与编译,此时如果忘记将主函数中的相应初始化删除,就会报错。这样操作很麻烦,不能很好的实现单个驱动文件的隔离。
那么现在就提供解决此问题的方式。这个方式源自于Linux内核--initcall机制。具体讲解网络上很多,在此不在详细说明。
可阅读:
keil 之Image:
linux的initcall机制(针对编译进内核的驱动) :
2、代码
头文件:
#ifndef _COLA_INIT_H_
#define _COLA_INIT_H_#define __used __attribute__((__used__))typedef void (*initcall_t)(void);#define __define_initcall(fn, id) \static const initcall_t __initcall_##fn##id __used \__attribute__((__section__("initcall" #id "init"))) = fn; #define pure_initcall(fn) __define_initcall(fn, 0) //可用作系统时钟初始化
#define fs_initcall(fn) __define_initcall(fn, 1) //tick和调试接口初始化
#define device_initcall(fn) __define_initcall(fn, 2) //驱动初始化
#define late_initcall(fn) __define_initcall(fn, 3) //其他初始化void do_init_call(void);#endif
源文件:
#include "cola_init.h"void do_init_call(void)
{extern initcall_t initcall0init$$Base[];extern initcall_t initcall0init$$Limit[];extern initcall_t initcall1init$$Base[];extern initcall_t initcall1init$$Limit[];extern initcall_t initcall2init$$Base[];extern initcall_t initcall2init$$Limit[];extern initcall_t initcall3init$$Base[];extern initcall_t initcall3init$$Limit[];initcall_t *fn;for (fn = initcall0init$$Base;fn < initcall0init$$Limit;fn++){if(fn)(*fn)();}for (fn = initcall1init$$Base;fn < initcall1init$$Limit;fn++){if(fn)(*fn)();}for (fn = initcall2init$$Base;fn < initcall2init$$Limit;fn++){if(fn)(*fn)();}for (fn = initcall3init$$Base;fn < initcall3init$$Limit;fn++){if(fn)(*fn)();}}
在主进程中调用void do_init_call(void)进行驱动初始化,驱动注册初始化时调用:
pure_initcall(fn) //可用作系统时钟初始化 fs_initcall(fn) //tick和调试接口初始化device_initcall(fn) //驱动初始化late_initcall(fn)
举个例子:
static void led_register(void)
{led_gpio_init();led_dev.dops = &ops;led_dev.name = "led";cola_device_register(&led_dev);
}device_initcall(led_register);
这样头文件中就没有有对外的接口函数了。
3、代码
gitee:
https://gitee.com/schuck/cola_os
girhub:
https://github.com/sckuck-bit/cola_os
三、C语言#pragma once的用法
1 #pragma once有什么作用?
为了避免同一个头文件被包含(include)多次,C/C++中有两种宏实现方式:
一种是#ifndef方式;
另一种是#pragma once方式。
在能够支持这两种方式的编译器上,二者并没有太大的区别。但两者仍然有一些细微的区别。
2 两者的使用方式有何区别
示例代码如下:
//方式一:
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__... ... // 声明、定义语句
#endif//方式二:
#pragma once... ... // 声明、定义语句
3 两者各有何特点
(1)#ifndef
#ifndef的方式受C/C++语言标准支持。它不仅可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件(或者代码片段)不会被不小心同时包含。
当然,缺点就是如果不同头文件中的宏名不小心“撞车”,可能就会导致你看到头文件明明存在,但编译器却硬说找不到声明的状况——这种情况有时非常让人郁闷。
由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式。
(2)#pragma once
#pragma once 一般由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。
你无法对一个头文件中的一段代码作pragma once声明,而只能针对文件。
其好处是,你不必再担心宏名冲突了,当然也就不会出现宏名冲突引发的奇怪问题。大型项目的编译速度也因此提高了一些。
对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名冲突引发的“找不到声明”的问题,这种重复包含很容易被发现并修正。
另外,这种方式不支持跨平台!
4 两者之间有什么联系?
#pragma once 方式产生于#ifndef之后,因此很多人可能甚至没有听说过。目前看来#ifndef更受到推崇。
因为#ifndef受C/C++语言标准的支持,不受编译器的任何限制;
而#pragma once方式却不受一些较老版本的编译器支持,一些支持了的编译器又打算去掉它,所以它的兼容性可能不够好。
一般而言,当程序员听到这样的话,都会选择#ifndef方式,为了努力使得自己的代码“存活”时间更久,通常宁愿降低一些编译性能,这是程序员的个性,当然这是题外话啦。
还看到一种用法是把两者放在一起的:
#pragma once
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__... ... // 声明、定义语句#endif
总结:
看起来似乎是想兼有两者的优点。
不过只要使用了#ifndef就会有宏名冲突的危险,也无法避免不支持#pragma once的编译器报错,所以混用两种方法似乎不能带来更多的好处,倒是会让一些不熟悉的人感到困惑。
选择哪种方式,应该在了解两种方式的情况下,视具体情况而定。
只要有一个合理的约定来避开缺点,我认为哪种方式都是可以接受的。
而这个已经不是标准或者编译器的责任了,应当由程序员自己或者小范围内的开发规范来搞定。
为了避免同一个文件被include多次:
- #ifndef方式
- #pragma once方式
在能够支持这两种方式的编译器上,二者并没有太大的区别,但是两者仍然还是有一些细微的区别。
方式一:#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
... ... // 一些声明语句
#endif方式二:#pragma once
... ... // 一些声明语句
#ifndef的方式依赖于宏名字不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。
当然,缺点就是如果不同头文件的宏名不小心“撞车”,可能就会导致头文件明明存在,编译器却硬说找不到声明的状况。
#pragma once则由编译器提供保证:同一个文件不会被包含多次。
注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。带来的好处是,你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题。
对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。
当然,相比宏名碰撞引发的“找不到声明”的问题,重复包含更容易被发现并修正。
方式一 由语言支持所以移植性好,
方式二 可以避免名字冲突。
四、C语言Makefile:从基础到高级应用
Makefile是一种编译控制文件,广泛用于项目的自动化构建。它定义了一系列的规则来指导构建的过程。
通过Makefile,开发者可以轻松管理大型项目的编译链接、清理等任务。
本文将从Makefile的基础用法讲起,逐步深入到更高级的应用,为你呈现一个全面而详细的Makefile使用手册。
Makefile的基本结构
一个最简单的Makefile包含规则,规则由目标(target)、依赖(dependencies)和命令(commands)三部分组成:
target: dependenciescommands
命令前的Tab键是必须的。下面是一个简单的示例:
hello: hello.cgcc -o hello hello.c
变量的使用
在Makefile中声明变量可以使得我们的代码更加简洁。
CC=gcc
CFLAGS=-std=c99
LDFLAGS=
OBJ=main.o utils.oapp: $(OBJ)$(CC) -o app $(OBJ) $(LDFLAGS)main.o: main.c$(CC) $(CFLAGS) -c main.cutils.o: utils.c utils.h$(CC) $(CFLAGS) -c utils.c
通用规则和模式匹配
模式规则可以减少我们重复相同命令的工作量。
%.o: %.c$(CC) $(CFLAGS) -c $<
$< 是自动变量之一,代表依赖列表中的第一项。
自动化变量
Makefile提供了一系列自动化变量,它们在规则的命令中非常有用:
- $@ 表示规则中的目标文件名;
- $^ 表示所有的依赖文件列表;
- $< 表示第一个依赖文件;
- $? 表示所有比目标新的依赖文件列表。
函数的使用
Makefile中内置了许多函数,用以执行字符串操作、文件操作等。
例如,获取源文件列表:
SRC=$(wildcard *.c)
OBJ=$(patsubst %.c,%.o,$(SRC))
控制Make的行为
- make -B 强制重新编译所有目标;
- make -n 显示将要执行的命令而不实际执行;
- make -f <file> 指定使用其他名称的Makefile文件;
- make -j 允许并行执行(多核编译)。
高级用法 - 条件判断
Makefile也支持条件判断,这在不同环境需要执行不同命令时非常有用。
ifeq ($(OS),Windows_NT)RM=del /Q
elseRM=rm -f
endifclean:$(RM) *.o
使用变量和文件包含来组织Makefile
对于大型项目,组织多个Makefile是一种好方法。
# 在子Makefile中
include config.mk
自定义函数
通过定义可以重用的函数,你可以使你的Makefile变得更加强大和灵活。
define run-cc
$(CC) $(CFLAGS) -o $@ $^
endefapp: $(OBJ)$(call run-cc)
处理多目标
定义一个规则来批量处理多个文件。
FILES := file1 file2 file3all: $(FILES)$(FILES):touch $@
伪目标的使用
伪目标不代表实际的文件,它只是一个动作的名称。
.PHONY: cleanclean:rm -f *.o app
调试Makefile
你可以使用make --debug或添加注释来帮助调试Makefile。
app: main.o utils.o# 这是一个链接的命令$(CC) -o app main.o utils.o
结语
Makefile是构建自动化的强大工具,既可以简化小型项目的构建流程,也能够灵活管理大型应用程序的复杂构建系统。
通过本文的详细论述和丰富示例,您应该能够基本掌握Makefile的各项技能,并在实际项目中加以应用。
希望以上内容对你深入理解和使用Makefile有所帮助。记住,“实践出真知”——编写你自己的Makefile并尝试使用这些特性是最好的学习方式。
五、嵌入式工具代码合集
嵌入式开发中常用的C语言工具代码确实很重要。以下是一些利剑级别的C语言工具代码示例,以及它们的简要讲解。
1 循环队列(Circular Buffer)
typedef struct {int buffer[SIZE];int head;int tail;int count;
} CircularBuffer;void push(CircularBuffer *cb, int data) {if (cb->count < SIZE) {cb->buffer[cb->head] = data;cb->head = (cb->head + 1) % SIZE;cb->count++;}
}int pop(CircularBuffer *cb) {if (cb->count > 0) {int data = cb->buffer[cb->tail];cb->tail = (cb->tail + 1) % SIZE;cb->count--;return data;}return -1; // Buffer is empty
}
循环队列是一种高效的数据结构,适用于缓冲区和数据流应用,例如串口通信接收缓冲。
2 断言(Assertion)
#define assert(expression) ((void)0)
#ifndef NDEBUG
#undef assert
#define assert(expression) ((expression) ? (void)0 : assert_failed(__FILE__, __LINE__))
#endifvoid assert_failed(const char *file, int line) {printf("Assertion failed at %s:%d\n", file, line);// Additional error handling or logging can be added here
}
断言用于在程序中检查特定条件是否满足,如果条件为假,会触发断言失败,并输出相关信息
3 位域反转(Bit Reversal)
unsigned int reverse_bits(unsigned int num) {unsigned int numOfBits = sizeof(num) * 8;unsigned int reverseNum = 0;for (unsigned int i = 0; i < numOfBits; i++) {if (num & (1 << i)) {reverseNum |= (1 << ((numOfBits - 1) - i));}}return reverseNum;
}
该函数将给定的无符号整数的位进行反转,可以用于某些嵌入式系统中的位级操作需求。
4 固定点数运算(Fixed-Poin Arithmetic)
typedef int16_t fixed_t;#define FIXED_SHIFT 8
#define FLOAT_TO_FIXED(f) ((fixed_t)((f) * (1 << FIXED_SHIFT)))
#define FIXED_TO_FLOAT(f) ((float)(f) / (1 << FIXED_SHIFT))fixed_t fixed_multiply(fixed_t a, fixed_t b) {return (fixed_t)(((int32_t)a * (int32_t)b) >> FIXED_SHIFT);
}
在某些嵌入式系统中,浮点运算会较慢或不被支持。因此,使用固定点数运算可以提供一种有效的浮点数近似解决方案。
5 字节序转换(Endianness Conversion)
uint16_t swap_bytes(uint16_t value) { return (value >> 8) | (value << 8); }
用于在大端(Big-Endian)和小端(Little-Endian)字节序之间进行转换的函数。
6 位掩码(Bit Masks)
#define BIT_MASK(bit) (1 << (bit))
用于创建一个只有指定位被置位的位掩码,可用于位操作。
7 计数器计数(Timer Counting)
#include <avr/io.h>void setup_timer() {// Configure timer settings
}uint16_t read_timer() {return TCNT1;
}
在AVR嵌入式系统中,使用计时器(Timer)来实现时间测量和定时任务。
8 二进制查找(Binary Search)
int binary_search(int arr[], int size, int target) {int left = 0, right = size - 1;while (left <= right) {int mid = left + (right - left) / 2;if (arr[mid] == target) {return mid;} else if (arr[mid] < target) {left = mid + 1;} else {right = mid - 1;}}return -1; // Not found
}
用于在已排序的数组中执行二进制查找的函数。
9 位集合(Bitset)
#include <stdint.h>typedef struct {uint32_t bits;
} Bitset;void set_bit(Bitset *bitset, int bit) {bitset->bits |= (1U << bit);
}int get_bit(Bitset *bitset, int bit) {return (bitset->bits >> bit) & 1U;
}
实现简单的位集合数据结构,用于管理一组位的状态。
这些代码示例代表了嵌入式开发中常用的一些利剑级别的C语言工具代码。它们在嵌入式系统开发中具有广泛的应用,有助于优化性能、节省资源并提高代码的可维护性。
六、STM32单片机的堆栈
学习STM32单片机的时候,总是能遇到“堆栈”这个概念。分享本文,希望对你理解堆栈有帮助。
对于了解一点汇编编程的人,就可以知道,堆栈是内存中一段连续的存储区域,用来保存一些临时数据。堆栈操作由PUSH、POP两条指令来完成。而程序内存可以分为几个区:
- 栈区(stack)
- 堆区(Heap)
- 全局区(static)
- 文字常亮区程序代码区
程序编译之后,全局变量,静态变量已经分配好内存空间,在函数运行时,程序需要为局部变量分配栈空间,当中断来时,也需要将函数指针入栈,保护现场,以便于中断处理完之后再回到之前执行的函数。
栈是从高到低分配,堆是从低到高分配。
普通单片机与STM32单片机中堆栈的区别
普通单片机启动时,不需要用bootloader将代码从ROM搬移到RAM。
这里我们可以先看看单片机程序执行的过程,单片机执行分三个步骤:
- 取指令
- 分析指令
- 执行指令
根据PC的值从程序存储器读出指令,送到指令寄存器。然后分析执行执行。这样单片机就从内部程序存储器去代码指令,从RAM存取相关数据。
RAM取数的速度是远高于ROM的,但是普通单片机因为本身运行频率不高,所以从ROM取指令慢并不影响。
而STM32的CPU运行的频率高,远大于从ROM读写的速度。所以需要用bootloader将代码从ROM搬移到RAM。
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。
其实堆栈就是单片机中的一些存储单元,这些存储单元被指定保存一些特殊信息,比如地址(保护断点)和数据(保护现场)。
如果非要给他加几个特点的话那就是:
- 这些存储单元中的内容都是程序执行过程中被中断打断时,事故现场的一些相关参数。如果不保存这些参数,单片机执行完中断函数后就无法回到主程序继续执行了。
- 这些存储单元的地址被记在了一个叫做堆栈指针(SP)的地方。
结合STM32的开发讲述堆栈
从上面的描述可以看得出来,在代码中是如何占用堆和栈的。可能很多人还是无法理解,这里再结合STM32的开发过程中与堆栈相关的内容来进行讲述。
如何设置STM32的堆栈大小?
在基于MDK的启动文件开始,有一段汇编代码是分配堆栈大小的。
这里重点知道堆栈数值大小就行。还有一段AREA(区域),表示分配一段堆栈数据段。数值大小可以自己修改,也可以使用STM32CubeMX数值大小配置,如下图所示。
STM32F1默认设置值0x400,也就是1K大小。
Stack_Size EQU 0x400
函数体内局部变量:
void Fun(void){ char i; int Tmp[256]; //...}
局部变量总共占用了256*4 + 1字节的栈空间。所以,在函数内有较多局部变量时,就需要注意是否超过我们配置的堆栈大小。
函数参数:
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
这里要强调一点:传递指针只占4字节,如果传递的是结构体,就会占用结构大小空间。提示:在函数嵌套,递归时,系统仍会占用栈空间。
堆(Heap)的默认设置0x200(512)字节。
Heap_Size EQU 0x200
大部分人应该很少使用malloc来分配堆空间。虽然堆上的数据只要程序员不释放空间就可以一直访问,但是,如果忘记了释放堆内存,那么将会造成内存泄漏,甚至致命的潜在错误。
MDK中RAM占用大小分析
经常在线调试的人,可能会分析一些底层的内容。这里结合MDK-ARM来分析一下RAM占用大小的问题。在MDK编译之后,会有一段RAM大小信息:
这里4+6=1640,转换成16进制就是0x668,在进行在调试时,会出现:
这个MSP就是主堆栈指针,一般我们复位之后指向的位置,复位指向的其实是栈顶:
而MSP指向地址0x20000668是0x20000000偏移0x668而得来。具体哪些地方占用了RAM,可以参看map文件中【Image Symbol Table】处的内容:
七、嵌入式代码头文件的规则
1、头文件作用
C语言里,每个源文件是一个模块,头文件为使用该模块的用户提供接口。接口指一个功能模块暴露给其他模块用以访问具体功能的方法。使用源文件实现模块的功能,使用头文件暴露单元的接口。用户只需包含相应的头文件就可使用该头文件中暴露的接口。
通过头文件包含的方法将程序中的各功能模块联系起来有利于模块化程序设计:
1)通过头文件调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制库即可。用户只需按照头文件中的接口声明来调用库功能,而不必关心接口如何实现。编译器会从库中提取相应的代码。
2)头文件能加强类型安全检查。若某个接口的实现或使用方式与头文件中的声明不一致,编译器就会指出错误。这一简单的规则能大大减轻程序员调试、改错的负担。
在预处理阶段,编译器将源文件包含的头文件内容复制到包含语句(#include)处。在源文件编译时,连同被包含进来的头文件内容一起编译,生成目标文件(.obj)。如果所包含的头文件非常庞大,则会严重降低编译速度(使用GCC的-E选项可获得并查看最终预处理完的文件)。因此,在源文件中应仅包含必需的头文件,且尽量不要在头文件中包含其它头文件。
2、 头文件组织原则
源文件中实现变量、函数的定义,并指定链接范围。头文件中书写外部需要使用的全局变量、函数声明及数据类型和宏的定义。
建议组织头文件内容时遵循以下原则:
1)头文件划分原则:类型定义、宏定义尽量与函数声明相分离,分别位于不同的头文件中。内部函数声明头文件与外部函数声明头文件相分离,内部类型定义头文件与外部类型定义头文件相分离。
注意,类型和宏定义有时无法分拆为不同文件,比如结构体内数组成员的元素个数用常量宏表示时。因此仅分离类型宏定义与函数声明,且分别置于* .th和*.fh文件(并非强制要求)。
2)头文件的语义层次化原则:头文件需要有语义层次。不同语义层次的类型定义不要放在一个头文件中,不同层次的函数声明不要放在一个头文件中。
3)头文件的语义相关性原则:同一头文件中出现的类型定义、函数声明应该是语义相关的、有内部逻辑关系的,避免将无关的定义和声明放在一个头文件中。
4)头文件名应尽量与实现功能的源文件相同,即module.c和module.h。但源文件不一定要包含其同名的头文件。
5)头文件中不应包含本地数据,以降低模块间耦合度。
即只有源文件自己使用的类型、宏定义和变量、函数声明,不应出现在头文件里。作用域限于单文件的私有变量和函数应声明为static,以防止外部调用。将私有类型置于源文件中,会提高聚合度,并减少不必要的格式外漏。
6)头文件内不允许定义变量和函数,只能有宏、类型(typedef/struct/union/enum等)及变量和函数的声明。特殊情况下可extern基本类型的全局变量,源文件通过包含该头文件访问全局变量。但头文件内不应extern自定义类型(如结构体)的全局变量,否则将迫使本不需要访问该变量的源文件包含自定义类型所在头文件[1]。
7)说明性头文件不需要有对应的源文件。此类头文件内大多包含大量概念性宏定义或枚举类型定义,不包含任何其他类型定义和变量或函数声明。此类头文件也不应包含任何其他头文件。
8)使用#pragma once或header guard(亦称include guard或macro guard)避免头文件重复包含。#pragma once是一种非标准但已被现代编译器广泛支持的技巧,它明确告知预处理器“不要重复包含当前头文件”。而header guard则通过预处理命令模拟类似行为:
#ifndef _PRJ_DIR_FILE_H //必须确保header guard宏名永不重名
#define _PRJ_DIR_FILE_H//<头文件内容>#endif
使用#pragma once相比header guard具有两个优点:
- 更快。编译器不会第二次读取标记#pragma once的文件,但却会读若干遍使用header guard 的文件(寻找#endif);
- 更简单。不再需要为每个文件的header guard取名,避免宏名重名引发的“找不到声明”问题。
缺点则是: - #pragma once保证物理上的同一个文件不会被包含多次,无法对头文件中的一段代码作#pragma once声明。若某个头文件具有多份拷贝(内容相同的多个文件),pragma不能保证它们不被重复包含。当然,这种重复包含很容易被发现并修正。
9) C++中要引用C函数时,函数所在头文件内应包含extern "C"。
//.h文件头部
#ifdef __cplusplus
extern "C" {
#endif//<函数声明>//.h文件尾部
#ifdef __cplusplus
}
#endif
被extern "C"修饰的变量和函数将按照C语言方式编译和连接,否则编译器将无法找到C函数定义,从而导致链接失败。
10)头文件内要有面向用户的充足注释,从应用角度描述接口暴露的内容。
3、 头文件包含原则
在实际编程中,常常因头文件包含不当而引发编译时报告符号未定义的错误或重复定义的警告。要消除符号未定义的编译错误,只需在引用符号(变量、函数、数据类型及宏等)前确保它已被声明或定义[4]。要消除重复定义的警告,则需合理设计头文件包含顺序和层次。
建议包含头文件时遵循以下原则:
1)源文件内的头文件包含顺序应从最特殊到一般,如:
#include "通用头文件" //内部可能定义本模块数据类型别名
#include "源文件同名头文件"
#include "本模块其他头文件"
#include "自定义工具头文件"
#include "第三方头文件"
#include "平台相关头文件"
#include "C++库头文件"
#include "C库头文件"
优点是每个头文件必须include需要的关联头文件,否则会报错。同时,源文件同名头文件置于包含列表前端便于检查该头文件是否自完备,以及类型或函数声明是否与标准库冲突。
2)减少头文件的嵌套和交叉引用,头文件仅包含其真正需要显式包含的头文件。
例如,头文件A中出现的类型定义在头文件B中,则头文件A应包含头文件B,除此以外的其他头文件不允许包含。
头文件的嵌套和交叉引用会使程序组织结构和文件组织变得混乱,同时造成潜在的错误。大型工程中,原有头文件可能会被多个其他(源或头)文件包含,在原有头文件中添加新的头文件往往牵一发而动全身。若头文件中类型定义需要其他头文件时,可将其提出来单独形成一个全局头文件。
3)头文件应包含哪些头文件仅取决于自身,而非包含该头文件的源文件。
例如,编译源文件时需要用到头文件B,且源文件已包含头文件A,而索性将头文件B包含在头文件A中,这是错误的做法。
4)尽量保证用户使用此头文件时,无需手动包含其他前提头文件,即此头文件内已包含前提头文件。
例如,面积相关操作的头文件Area.h内已包含关于点操作的头文件Point.h,则用户包含Area.h后无需再手动包含Point.h。这样用户就不必了解头文件的内在依赖关系。
5)头文件应是自完备的,即在任一源文件中包含任一头文件而不会产生编译错误。
6)源文件中包含的头文件尽量不要有顺序依赖。
7)尽量在源文件中包含头文件,而非在头文件中。且源文件仅包含所需的头文件。
8)头文件中若能前置声明(亦称前向声明[5]),就不要包含另一头文件。仅当前置声明不能满足或过于麻烦时才使用include,如此可减少依赖性方面的问题。示例如下:
struct T_MeInfoMap; //前置声明
struct T_OmciMsg; //前置声明typedef FUNC_STATUS (*OmciChkFunc)(struct T_MeInfoMap *ptMeInfo, struct T_OmciMsg *ptMsg, struct T_OmciMsg *ptAckMsg);//OMCI实体信息
typedef struct{INT16U wMeClass; //实体类别OMCI_ATTR_INFO *pMeAttrInfo; //实体所定义的属性信息指针INT8U ucAttrNum; //实体所定义的属性数目INT16U wTotalAttrLen; //实体所有属性所占的总字节数,初始化为0,动态计算INT8U *pszDbName; //实体存库时的数据表名称,建议不要超过DB_NAME_LEN(32)INT16U wMaxRecNum; //实体存库时支持的最大记录数目OmciChkFunc fnCheck; //Omci校验函数指针BOOL bDbCreated; //实体数据表是否已创建
}OMCI_ME_INFO_MAP;
如上,在OmciChkFunc函数的实现源文件内包含T_MeInfoMap和T_OmciMsg所在头文件即可。
另举一例如下:
typedef TBL_SET_MODE (*OperTypeFunc)(INT8U *pTblEntry);typedef INT8U (*CmpRecFunc)(VOID *pvCmpData, VOID *pvRecData); //为避免头文件交叉引用,与CompareRecFunc异名同构//表属性信息
typedef struct{INT16U wMaxEntryNum; //表属性最大表项数目(实体记录数目wMaxRecNum * wMaxEntryNum <= MAX_RECORD_NUM)OperTypeFunc fnGetOperType; //操作类型函数指针。根据表项数据或外界需求(只读表)解析当前表项操作类型TBL_KEY_INFO tCmpKeyInfo; //检索表属性子表记录时的匹配关键字信息(TBL_KEY_INFO)CmpRecFunc fnCmpAddKey; //增加表项时需要检测的关键字匹配函数指针CmpRecFunc fnCmpDelKey; //删除表项时需要检测的关键字匹配函数指针INT16U wTblEntrySize; //表属性表项字节数,由外部动态赋值
}TBL_ATTR_INFO;
如上,CompareRecFunc函数原型由其他头文件提供,此处为避免头文件交叉引用定义其异名同构原型CmpRecFunc。
在不会引起歧义的前提下,头文件内尽可能使用VOID指针代替非基本类型的值变量或指针,以避免再包含类型定义所在的头文件。但这将影响代码可读性并降低程序执行效率,应权衡利弊。
9)避免包含重量级的平台头文件,如windows.h或d3d9.h等。若仅使用该头文件少量函数,可extern函数到源文件内。如下:
/****************************************************************************************外部函数声明 (当外部接口未提供头文件或头文件过于复杂时) ****************************************************************************************/
//因声明所在头文件引用混乱,此处仅extern函数声明。
extern INT32S DBShmCliInit(VOID); //#include "db_shm_mgr.h"
extern INT32S cmLockInit(VOID); //#include "common_cmapi.h"
若还使用该头文件某些类型和宏定义,可创建适配性源文件。在该源文件内包含平台头文件,封装新的接口并将其声明在同名头文件内,其他源文件将通过适配头文件间接访问平台接口。如下:
/*****************************************************************************************
* 文件名称:Omci_Send_Msg.c
* 内容摘要:OMCI消息转发接口
* 其它说明: 该头文件封装SEND接口,以避免其他源文件包含支撑api和pid公共头文件导致引用混乱。*****************************************************************************************/#include "Omci_Common.h"
#include "Omci_Send_Msg.h"
#include "oss_api.h"/**********************************************************************************************函数实现区
**********************************************************************************************///向自身进程发送异步消息
INT32U OmciAsynSendSelf(INT16U wEvent, VOID *pvMsg, INT16U wMsgLen)
{PID dwSelfPid = 0;SELF(&dwSelfPid);return ASEND(wEvent, pvMsg, wMsgLen, dwSelfPid);
}
10)对于函数库(包括标准库和自定义的公共宏及接口)的头文件,可将其加入到一个通用头文件中。需要控制该头文件的体积(主要是该头文件所包含的所有头文件内容大小),并确保所有源文件首先包含该通用头文件。示例如下:
#ifndef _OMCI_COMMON_H
#define _OMCI_COMMON_H/*******************************************************************************************
* 说明:
* 本文件仅应包含与具体通信协议无关的通用数据类型及宏定义。
* 为简化头文件包含且不失可移植性,本文件内可包含少量C库通用头文件。
* 因本文件内定义基本数据类型别名,故.c文件中应将本头文件置于包含列表顶端,
* 否则编译时可能产生类型未定义错误。
*******************************************************************************************/#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <limits.h>#include "Omci_Byte.h"//<Other Contents...>
注意,示例头文件内包含C库文件虽能简化包含,但却与规则1冲突。也可另外增加包含库文件列表的通用头文件。
11)若不确定类型、宏定义或函数声明所在头文件具体路径,可在源文件中再次定义或声明,编译器会以redefined警告或conflicting错误给出类型、宏定义或函数声明所在头文件路径。
4、代码文件组织原则
建议C语言项目中代码文件组织遵循以下原则:
1)使用层次化和模块化的软件开发模型。每个模块只能使用所在层和下一层模块提供的接口。
2)每个模块的文件(可能多个)保存在一个独立文件夹中。
模块文件较多时可采用子目录的方式,物理上隔离不同层次的文件。子目录下源文件和头文件应分开存放,如分别置入include和source目录。
3)用于模块裁减的条件编译宏保存在一个独立文件中,便于软件裁减。
4)硬件相关代码和操作系统相关代码与工程代码相对独立保存,以便于软件移植。
5)按相同功能或相关性组织源文件和头文件。同一文件内的聚合度要高,不同文件中的耦合度要低。
在对既有工程做单元测试时,耦合度低的文件布局非常便于搭建环境。
6)声明和定义分开,使用头文件暴露模块需要提供给外部的类型、宏、变量和函数。尽量做到模块对外部透明,用户在使用模块功能时无需了解具体的实现。
7)作为对外接口的头文件一经发布,应保持稳定。修改时一定要慎重。
8)文件夹和文件命名要能够反映出模块的功能。
9)正式版本和测试版本使用统一文件,使用宏控制是否产生测试输出。
10)必要的注释不可缺少。
5、 注解
【注1】全局变量的使用原则
1)若全局变量仅在单个源文件中访问,则可将该变量改为该文件内的静态全局变量;
2)若全局变量仅由单个函数访问,则可将该变量改为该函数内的静态局部变量;
3)尽量不要使用extern声明全局变量,最好提供函数访问这些变量。直接暴露全局变量是不安全的,外部用户未必完全理解这些变量的含义。
4)设计和调用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题。
【注2】#pragma once的可移植性
#ifndef由C/C++语言标准支持,不受编译器任何限制;而#pragma once仅由编译器提供保证,存在可移植性等问题。某些gcc编译器版本(如3.2.3)会报告“warning: #pragma once is obsolete”的警告,而其他较老版本的编译器可能会报错。但随着gcc 3.4的发布,#pragma once中的一些问题(主要与符号链接和硬链接有关)得以解决,#pragma once命令也标记为“未废弃”。
还有种写法同时使用#pragma once和header guard编写“可移植性”代码,以利用编译器可能支持的#pragma once优化。如下:
#pragma once
#ifndef _PRJ_DIR_FILE_H
#define _PRJ_DIR_FILE_H//<头文件内容>#endif
该法似乎兼有两者的优点。但既然使用#ifndef就有宏名重名的风险,也无法避免不支持#pragma once的编译器告警或报错,故混用两种方法似乎不能带来更多的好处,反倒让不熟悉的人感到困惑。
注意,如果使用header guard,理论上可在代码任何地方判断当前是否已经包含某个头文件。但应避免通过该判断来改变后续代码的逻辑走向!这种做法将使程序依赖于头文件的包含顺序,极不可取。若需要实现“若当前包含HeaderA.h,才加入StructB结构”,可对StructB结构创建HeaderB.h头文件,在HeaderA.h中包含HeaderB.h。
【注3】extern "C"
C++语言在编译时为实现函数重载,会结合函数名、参数数目及类型信息而生成一个中间函数名。例如,C++中函数void foo(int x, float y)编译后在符号库中生成的名字为_ foo_int_float(不同编译器可能生成不同函数名,但均采用相同机制,生成的新名字称为”mangled name”);而该函数被C编译器编译后在符号库中的名字为_foo。
C语言中不支持extern "C"声明,在.c文件中包含extern "C"时会出现编译语法错误。
当然编译器也可以为其他语言提供链接说明。例如:extern "FORTRAN"、extern "Ada"等。
【注4】声明(declaration)与定义(definition)
全局变量或函数可(在多个编译单元中)有多处声明,但只允许定义一次。全局变量定义时分配空间并赋初始值(如果有);函数定义时提供函数体内容。
#pragma once
#ifndef _PRJ_DIR_FILE_H
#define _PRJ_DIR_FILE_H//<头文件内容>#endif
在多个源文件中共享变量或函数时,需确保定义和声明的一致性。通常在某个相关的源文件中定义,然后在头文件中进行外部声明。需要使用时包含相应的头文件即可。定义变量的源文件也应包含该头文件,以便编译器检查定义和声明的一致性。
该规则可提供高度的可移植性:它与ANSI/ISO C标准一致,同时也兼顾大多数ANSI前的编译器和链接器。(Unix编译器和链接器常使用允许多重定义的“通用模式”,只要保证最多对一处定义进行初始化即可。该方式被ANSI C标准称为一种“通用扩展”)。某些很老的系统可能要求显式初始化以区别定义和外部声明。
通用扩展在《深入理解计算机系统》中解释为:多重定义的符号只允许最多一个强符号。函数和定义时已初始化的全局变量是强符号;未初始化的全局变量是弱符号。Unix链接器使用以下规则来处理多重定义的符号:
规则一:不允许有多个强符号。在被多个源文件包含的头文件内定义的全局变量会被定义多次(预处理阶段会将头文件内容展开在源文件中),若在定义时显式地赋值(初始化),则会违反此规则。
规则二:若存在一个强符号和多个弱符号,则选择强符号。
规则三:若存在多个弱符号,则从这些弱符号中任选一个。
当不同文件内定义同名(即便类型和含义不同)的全局变量时,该变量共享同一块内存(地址相同)。若变量定义时均初始化,则会产生重定义(multiple definition)的链接错误;若某处变量定义时未初始化,则无链接错误,仅在因类型不同而大小不同时可能产生符号大小变化(size of symbol `XXX' changed)的编译警告。在最坏情况下,编译链接正常,但不同文件对同名全局变量读写时相互影响,引发非常诡异的问题。这种风险在使用无法接触源码的第三方库时尤为突出。
因此,应尽量避免使用全局变量。若确有必要,应采用静态全局变量(无强弱之分,且不会和其他全局符号产生冲突),并封装访问函数供外部文件调用。
【注5】前向声明(forward declaration)
结构体类型S在声明之后定义之前是一个不完全类型(incomplete type),即已知S是一个类型,但不知道包含哪些成员。不完全类型只能用于定义指向该类型的指针,或声明使用该类型作为形参指针类型或返回指针类型的函数。指针类型对编译器而言大小固定(如32位机上为四字节),不会出现编译错误。
假设先后定义两个结构A和B,且两个结构需要互相引用。在定义A时B还没有定义,则要引用B就需要前向声明结构B(struct B;)。示例如下:
typedef BOOL (*func)(const DefStruct *ptStrt);typedef struct DefStruct_t
{int i;func f;
}DefStruct;
如上在DefStruct中使用回调函数func声明,这样交叉引用必然编译报错。进行前向声明即可:
typedef struct DefStruct_t DefStruct;
typedef BOOL (*func)(const DefStruct *ptStrt);struct DefStruct_t
{int i;func f;
};
注意,在前向声明和具体定义之间涉及标识符(变量、结构、函数等)实现细节的使用都是非法的。若函数被前向声明但未被调用,则编译和运行正常;若前向声明函数被调用但未被定义,则编译正常但链接报错(undefined reference)。将具体定义放在源文件中可部分避免该问题。
相关文章:
w~嵌入式C语言~合集6
我自己的原文哦~ https://blog.51cto.com/whaosoft/13870384 一、开源MCU简易数字示波器项目 这是一款采用STC8A8K MCU制造的简单示波器,只有零星组件,易于成型。这些功能可以涵盖简单的测量: 该作品主要的规格如下: 单片机…...
坐标转换:从WGS-84到国内坐标系(GCJ-02BD-09)
目录 🍅点击这里查看所有博文 随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记…...
快速上手 MetaGPT
1. MetaGPT 简介 在当下的大模型应用开发领域,Agent 无疑是最炙手可热的方向,这也直接催生出了众多的 Agent 开发框架。在这之中, MetaGPT 是成熟度最高、使用最广泛的开发框架之一。 MetaGPT 是一款备受瞩目的多智能体开发框架,…...
「Docker已死?」:基于Wasm容器的新型交付体系如何颠覆十二因素应用宣言
一、容器技术的量子跃迁 1. 传统容器体系的测不准原理 某金融平台容器集群真实数据: 指标Docker容器Wasm容器差异度冷启动时间1200ms8ms150倍内存占用256MB6MB42倍镜像体积780MB12MB65倍内核调用次数2100次/s23次/s91倍 二、Wasm容器的超流体特性 1. 字节码的量子…...
有源晶振输出匹配电阻选择与作用详解
一、输出匹配电阻的核心作用 阻抗匹配 减少信号反射:当信号传输线阻抗(Z0)与负载阻抗不匹配时,会发生反射,导致波形畸变(如振铃、过冲)。 公式:反射系数Γ (Z_L - Z0) / (Z_L Z0)…...
Shell脚本-while循环应用案例
在Shell脚本编程中,while循环是一种非常有用的控制结构,适用于需要基于条件进行重复操作的场景。与for循环不同,while循环通常用于处理不确定次数的迭代或持续监控某些状态直到满足特定条件为止的任务。本文将通过几个实际的应用案例来展示如…...
【JavaScript】二十七、用户注册、登陆、登出
文章目录 1、案例:用户注册页面1.1 发送验证码1.2 验证用户名密码合法性1.3 已阅读并同意用户协议1.4 表单提交 2、案例:用户登陆页面2.1 tab切换2.2 登陆跳转2.3 登陆成功与登出 1、案例:用户注册页面 1.1 发送验证码 需求:用户…...
Vue中Axios实战指南:高效网络请求的艺术
Axios作为Vue生态中最流行的HTTP客户端,以其简洁的API和强大的功能成为前后端交互的首选方案。本文将带你深入掌握Axios在Vue项目中的核心用法和高级技巧。 一、基础配置 1. 安装与引入 npm install axios 2. 全局挂载(main.js) import …...
SAP-pp 怎么通过底表的手段查找BOM的全部ECN变更历史
表:ABOMITEMS,查询条件是MAST的STLNR (BOM清单) 如果要得到一个物料的详细ECN历史,怎么办? 先在MAST表查找BOM清单,然后根据BOM清单在ABOMITEMS表里面查询组件,根据查询组件的结果…...
数据需求管理办法有哪些?具体应如何应用?
目录 一、数据需求管理的定义 二、数据需求管理面临的问题 1.需求理解偏差 2.需求变更频繁 3.需求优先级难以确定 4.数据质量与需求不匹配 三、数据需求管理办法的具体流程 1.建立有效的沟通机制 2.规范需求变更管理流程 3.制定需求优先级评估标准 4.加强数据质量管…...
单片机 + 图像处理芯片 + TFT彩屏 复选框控件
复选框控件使用说明 一、控件概述 本复选框控件是一个适用于单片机图形界面的UI组件,基于单片机 RA8889/RA6809 TFT显示屏 GT911触摸屏开发。控件提供了丰富的功能和自定义选项,使用简单方便,易于移植。 主要特点: 支持可…...
塔能合作模式:解锁工厂能耗精准节能新路径
在工厂寻求能耗精准节能的道路上,除了先进的技术,合适的合作模式同样至关重要。塔能科技提供的能源合同管理(EMC)和交钥匙方式(EPC),为工厂节能项目的落地实施提供了有力支持,有效解…...
使用PHP对接印度股票市场数据
在本篇文章中,我们将介绍如何通过StockTV提供的API接口使用PHP语言来获取并处理印度股票市场的数据。我们将以查询公司信息、查看涨跌排行榜和实时接收数据为例,展示具体的操作流程。 准备工作 首先,请确保您已经从StockTV获得了API密钥&am…...
make学习三:书写规则
系列文章目录 Make学习一:make初探 Make学习二:makefile组成要素 文章目录 系列文章目录前言默认目标规则语法order-only prerequisites文件名中的通配符伪目标 Phony Targets没有 Prerequisites 和 recipe内建特殊目标名一个目标多条规则或多个目标共…...
Arduino 入门学习笔记(五):KEY实验
Arduino 入门学习笔记(五):KEY实验 开发板:正点原子ESP32S3 例程源码在文章顶部可免费下载(审核中…) 1. GPIO 输入功能使用 1.1 GPIO 输入模式介绍 在上一文章中提及到 pinMode 函数, 要对…...
Grok发布了Grok Studio 和 Workspaces两个强大的功能。该如何使用?如何使用Grok3 API?
最近Grok又更新了几个功能:Grok Studio 和 Workspaces。 其中 Grok Studio 主要功能包括: 代码执行:在预览标签中运行 HTML 片段、Python、JavaScript 等。 Google Drive 集成:附加并处理 Docs、Sheets、Slides等文件。 协作工…...
学习spark总结
一、Spark Core • 核心功能:基于内存计算的分布式计算框架,提供RDD弹性分布式数据集,支持转换(如map、filter)和动作(如collect、save)操作。 • 关键特性:高容错性(L…...
LeetCode 24 两两交换链表中的节点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。 示例 1: 输入:head [1,2,3,4] 输出:[2,1…...
Qt中的全局函数讲解集合(全)
目录 1.qAbs 2.qAsConst 3.qBound 4.qConstOverload 5.qEnvironmentVariable 6.qExchange 7.qFloatDistance 8.qInstallMessageHandler 在头文件<QtGlobal>中包含了Qt的全局函数,现在就这些全局函数一一详解。 1.qAbs 原型: template &…...
《明解C语言入门篇》读书笔记四
目录 第四章:程序的循环控制 第一节:do语句 do语句 复合语句(程序块)中的声明 读取一定范围内的值 逻辑非运算符 德摩根定律 德摩根定律 求多个整数的和及平均值 复合赋值运算符 后置递增运算符和后置递减运算符 练习…...
【每日随笔】文化属性 ② ( 高维度信息处理 | 强者思维形成 | 认知重构 | 资源捕获 | 进化路径 )
文章目录 一、高维度信息处理1、" 道 " - 高维度信息2、上士对待 " 道 " 的态度3、中士对待 " 道 " 的态度4、下士对待 " 道 " 的态度 二、形成强者思维1、认知重构 : 质疑本能 -> 信任惯性2、资源捕获 : 远神崇拜 -> 近身模仿3…...
terraform查看资源建的关联关系
一、使用 terraform graph 命令生成依赖关系图 该命令会生成资源间的依赖关系图(DOT 格式),需配合 Graphviz 工具可视化。 1. 安装 Graphviz # Ubuntu/Debian sudo apt-get install graphviz# MacOS brew install graphviz 2. 生成并查看…...
win11报错 ‘wmic‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件 的解决方案
方法一:检查环境变量 右键点击“此电脑”或“计算机”: 选择“属性”,然后点击“高级系统设置”。 进入环境变量设置: 在“系统属性”窗口中,点击“环境变量”。 检查Path变量: 在“系统变量”部分,找到并…...
监控易一体化运维:巡检管理,守护企业系统稳定的坚固防线
在数字化浪潮奔涌的当下,企业业务高度依赖信息技术系统,数据流量呈爆发式增长。从日常办公到核心业务运作,每一个环节都离不开稳定可靠的系统支持。在这种背景下,确保系统时刻处于最佳状态的重要性。而监控易的巡检管理功能&#…...
技能点总结
技能点总结 1、多线程导致事物失效的原因1.1 线程间竞争条件1.2 可见性问题1.3 原子性破坏1.4 死锁与活锁1.5 事务隔离级别问题1.5.1 脏读、不可重复读、幻读 1、多线程导致事物失效的原因 多线程环境下事物失效是一个常见问题,主要原因包括以下几个方面࿱…...
23种设计模式-行为型模式之命令模式(Java版本)
Java 命令模式(Command Pattern)详解 🧠 什么是命令模式? 命令模式是一种行为型设计模式,它将请求封装成一个对象,从而使你可以使用不同的请求、队列、日志请求以及支持可撤销的操作。 命令模式将请求的…...
聊一聊接口测试的核心优势及价值
目录 一、核心优势 提前发现问题,降低修复成本 高稳定性与维护效率 全面覆盖复杂场景 性能与安全测试的基石 高度自动化与高效执行 支持微服务与分布式架构 二、核心价值 加速交付周期及降低维护成本 提升质量与用户体验 增强安全性及促进团队间的协作 …...
大学之大:索邦大学2025.4.27
索邦大学:千年学术传承与现代创新的交响 一、前身历史:从巴黎大学到现代索邦的千年脉络 1. 中世纪起源:欧洲学术之母的诞生 索邦大学的历史可追溯至9世纪,其前身巴黎大学被誉为“欧洲大学之母”。1257年,神学家罗伯特…...
python文本合并脚本
做数据集本地化时,用到了文本txt合并问题,用了trae -cn ai辅助测试一下效果,还可以吧,但还是不如人灵光,反复的小错,如果与对成手,应该很简单,这里只做了测试吧,南无阿弥…...
Coding Practice,48天强训(25)
Topic 1:笨小猴(质数判断的几种优化方式,容器使用的取舍) 笨小猴__牛客网 #include <bits/stdc.h> using namespace std;bool isPrime(int n) {if(n < 1) return false;if(n < 3) return true; // 2和3是质数if(n % 2 0 …...
pytorch学习使用
1. 基础使用 1.1 基础信息 # 输出 torch 版本 print(torch.__version__)# 判断 cuda 是否可用 print(torch.cuda.is_available()) """ 2.7.0 False """1.2 创建tensor # 创建一个5*3的矩阵,初始值为0. print("-------- empty…...
《AI大模型应知应会100篇》第38篇:大模型与知识图谱结合的应用模式
第38篇:大模型与知识图谱结合的应用模式 摘要 随着大模型(如GPT、BERT等)和知识图谱技术的快速发展,两者的融合为构建更精准、可解释的智能系统提供了新的可能性。本文将深入探讨大模型与知识图谱的能力互补性、融合架构设计以及…...
TypeScript中的type
在 TypeScript 中,type 是一个非常重要的关键字,用于定义类型别名(Type Alias)。它允许你为一个类型创建一个新的名字,从而使代码更加简洁和可读。type 可以用来定义基本类型、联合类型、元组类型、对象类型等。以下是…...
数据库3,
describe dt drop table 删表 df delete from删行 usw update set where更新元素 iiv insert into values()插入行 sf select from选行 select *选出所有行 (ob order by 排序 由低到高 DESC由高到低 order by score&#…...
I-CON: A Unifying Framework for Representation Learning
1,本文关键词 I-Con框架、表征学习、KL散度、无监督分类、对比学习、聚类、降维、信息几何、监督学习、自监督学习、统一框架 2,术语表 术语解释I-Con本文提出的统一表征学习方法,全称Information Contrastive Learning,通过最…...
mybatis首个创建相关步骤
1。先关联数据库,用户,密码,数据库保持一致 2.添加包和类 1.User放和数据库属性一样的 package com.it.springbootmybatis01.pojo;lombok.Data lombok.AllArgsConstructor lombok.NoArgsConstructor public class User {private Integer i…...
vue3子传父——v-model辅助值传递
title: 子组件向父组件传值 date: 2025-04-27 19:11:09 tags: vue3 vue3子传父——v-model辅助值传递 一、子组件发出 1.步骤一创建emit对象 这个对象使用的是defineEmits进行的创建,emit的中文意思又叫发出,你就把他当成一个发出数据的函数方法来用…...
Golang | 向倒排索引上添加删除文档
syntax "proto3";package types;message Keyword {string Field 1; // 属性/类型/名称string Word 2; // 关键词 }message Document {string Id 1; //业务使用的唯一Id,索引上此Id不会重复uint64 IntId 2; //倒排索引上使用的文档id(业务侧不用管这…...
秒杀系统 Kafka 架构进阶优化
文章目录 前言1. Kafka Topic 分区(Partition)设计2. Kafka 消费者高可用部署(Consumer Scaling)3. Kafka Redis 多级限流降级设计4. 秒杀链路全链路追踪(Tracing)5. Kafka 死信队列(DLQ&#…...
探索大语言模型(LLM):自监督学习——从数据内在规律中解锁AI的“自学”密码
文章目录 自监督学习:从数据内在规律中解锁AI的“自学”密码一、自监督学习的技术内核:用数据“自问自答”1. 语言建模:预测下一个单词2. 掩码语言模型(MLM):填补文本空缺3. 句子顺序预测(SOP&a…...
Java自定义注解详解
文章目录 一、注解基础注解的作用Java内置注解二、元注解@Retention@Target@Documented@Inherited@Repeatable(Java 8)三、创建自定义注解基本语法注解属性使用自定义注解四、注解的处理方式1. 编译时处理2. 运行时处理(反射)五、实际应用场景1. 依赖注入框架2. 单元测试框…...
在使用docker创建容器运行报错no main manifest attribute, in app.jar
原因就是在打包的时候pom配置有问题,重新配置再打包 我的dockerfile FROM openjdk:11 MAINTAINER yyf COPY *.jar /app.jar EXPOSE 8082 ENTRYPOINT ["java","-jar","app.jar"] 修改过后,经测试成功了 参考我的pom <?xml ver…...
C#中属性和字段的区别
在C# 中属性和字段的区别 在 C# 中,字段(field)和属性(property)都是用于存储数据的成员,但它们有重要的区别: 主要区别 1. 访问控制 - 字段:直接存储数据的变量 - 属性:通过访问器(get/set)控制对私有字段的…...
分析型数据库入门指南:如何选择适合你的实时分析工具?
一、什么是分析型数据库?为什么需要它? 据Gartner最新报告显示,超过75%的企业现已在关键业务部门部署了专门的分析型数据库,这一比例还在持续增长。 随着数据量呈指数级增长,传统数据库已无法满足复杂分析场景的需求…...
第三方软件检测报告:热门办公软件评估及功能表现如何?
第三方软件检测报告是重要文件。它用于对软件做专业评估。能反映软件各项性能。能反映软件安全性等指标。该报告为软件使用者提供客观参考。该报告为软件开发者提供客观参考。有助于发现问题。还能推动软件改进。 检测概述 本次检测针对一款热门办公软件。采用了多种先进技术…...
GPUStack昇腾Atlas300I duo部署模型DeepSeek-R1【GPUStack实战篇2】
2025年4月25日GPUStack发布了v0.6版本,为昇腾芯片910B(1-4)和310P3内置了MinIE推理,新增了310P芯片的支持,很感兴趣,所以我马上来捣鼓玩玩看哈 官方文档:https://docs.gpustack.ai/latest/insta…...
2025年邵阳市工程技术研究中心申报流程、条件、奖补
一、邵阳市工程技术研究中心申报条件 (一)工程技术研究中心主要依托科技型企业组建,依托单位应具有以下条件: 1. 具有较强技术创新意识的领导班子和技术水平高、工程化实践经验丰富的工程技术研发队伍,其中固定人员…...
【Python】Matplotlib:立体永生花绘制
本文代码部分实现参考自CSDN博客:https://blog.csdn.net/ak_bingbing/article/details/135852038 一、引言 Matplotlib作为Python生态中最著名的可视化库,其三维绘图功能可以创造出令人惊叹的数学艺术。本文将通过一个独特的参数方程,结合极…...
Spark Streaming核心编程总结(四)
一、有状态转化操作:UpdateStateByKey 概念与作用 UpdateStateByKey 用于在流式计算中跨批次维护状态(如累加统计词频)。它允许基于键值对形式的DStream,通过自定义状态更新函数,将历史状态与新数据结合,生…...
虚拟数字人:从虚拟到现实的跨越与未来展望
在人工智能和数字技术飞速发展的今天,虚拟数字人(Digital Humans)正逐渐从科幻走向现实,成为科技领域的新焦点。虚拟数字人不仅在娱乐、教育、客服等领域展现出巨大的应用潜力,还在不断推动着人机交互方式的变革。本文…...