|
3dh4jxqwyqg64023061236.gif
+ j1 j/ b! m9 c! n$ A: {1 L点击上方蓝色字体,关注我们2 a- S5 u8 a. I' {
$ X+ ^$ j( }0 ~7 p9 y1 ^5 @1* l6 f4 w- { z/ W7 r% E7 L$ s
volatile关键字
9 R! E1 }" `6 S" @2 wvolatile是一个特征修饰符,提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。, V% I* a, i" f5 T( |( l! q3 u
" F! O6 G" [# w+ ~$ |* a g
常用场景:中断服务与主程序共享变量。示例代码如下:, p: c- f, N$ C/ s% k* D2 V
: U/ X' m& Q: V d7 E" v- s9 c% N//volatile uint8_t flag=1;uint8_t flag=1; r r) \# p# A& d
void test(void){ while(flag) { //do something }}
- r) j0 o/ m0 i( z3 A7 L' k//interrupt service routinevoid isr_test(void){ flag=0;} K7 F) c. \, a8 N7 c
如果没使用volatile定义flag,可能在优化后test陷入死循环,因为test里使用的flag并没修改它,开启优化后,编译器可能会固定从某个内存取值。0 u: K2 w5 W2 Q$ f- t; ?! @- W
' {/ L! A0 u3 K3 d w4 \24 K$ Z# p( F$ i/ l- b
const关键字
0 `6 p$ y% ^ m8 n) l- Q! rconst 是 constant 的缩写,意思是“恒定不变的”,它是定义常变量的关键字。0 h% L3 g5 }7 Q' E u9 c$ V
通常有4种用法。
) M* j* |$ B4 H: v1 z: h4 X# d: v3 Y; ]
7 m" q B5 J/ g1 x+ D3 f
1、修饰变量
0 P- `& m) B8 V8 }; S
* c% ?( J: L$ l; I9 K& u' o采用const修饰变量,即变量声明为只读,保护变量值以防被修改。
& c" [) x/ K$ ~
8 y& ?$ E9 F3 r# P' b" c5 `const int i = 1;或者int const i=1;- i& z' C8 S" ^# Y4 ?
变量i具有只读特性,不能够被更改;若想对i重新赋值,如i = 10,属于错误操作。8 q$ t, W; c( c, q% x
0 w' _7 e! R0 U% |$ V" N0 Z. e$ u: N& \1 ?3 O( y; k
2、修饰数组
9 k4 _1 M+ e% `* _% m" f& h& r+ f
数组元素与变量类似,具有只读属性,不能被更改,一旦更改,编译时就会报错。
% c7 n2 L6 |0 T# [7 g) s1 n- a% d- q0 A6 V+ Q
const int array[5] = {1,2,3,4,5};array[0] = array[0]+1; //错误,array是只读的,禁止修改
9 `! K+ Z8 h' \) T' K1 K5 n. A( Z( s使用大数组存储固定的信息,例如查表(表驱动法的键值表),可以使用const节省ram。编译器并不给普通const只读变量分配空间,而是将它们保存到符号表中,无需读写内存操作,程序执行效率也会提高。7 Y5 g% }: R4 P9 ^* l6 u
( B* t5 \3 z8 @+ ]) w/ o
3 e& ^. O& c @: ^3、修饰指针; H* D y. @/ J+ q2 t
$ ^" v* m5 x2 p0 ~) K% B" pC语言中const修饰指针要特别注意,共有两种形式,一种是用来限定指向空间的值不能修改;另一种是限定指针不可更改。
7 b0 S& M6 y7 j- R1 r
* \. M6 F+ a( o9 r8 Lint i = 1;int j = 2;' L3 w9 A9 V* ]4 J! w5 B6 V
const int *p1 = &i;int* const p2 = &j;
6 @% Y2 m/ t2 K6 ]3 g& F上面定义了两个指针p1和p2,区别是const后面是指针本身还是指向的内容。
2 p+ `2 m5 `6 q' e8 u5 f$ ?$ K
9 f; n3 z' m/ G' h在定义1中const限定的是*p1,即其指向空间的值不可改变,若改变其指向空间的值如*p1=10,则程序会报错;但p1的值是可以改变的,对p1重新赋值如p1=&k是没有任何问题的。
6 p+ d( c+ ^* c3 q4 g4 K9 \. ^: Q G. g
在定义2中const限定的是指针p2,若改变p2的值如p2=&k,程序将会报错;但*p2,即其所指向空间的值可以改变,如*p2=20是没有问题的,程序正常执行。
; Z+ M# s5 w" U2 B! G$ w) {$ A" s9 [
5 `3 O$ X, @* o
4、 修饰函数参数' k- N& r8 L! V" g0 a5 C
const关键字修饰函数参数,对参数起限定作用,防止其在函数内部被修改,所限定的函数参数可以是普通变量,也可以是指针变量。2 o2 G( x$ S$ H |/ B. E0 c) Y; u: X) f
9 Q D/ X( ]3 {% E/ b9 t" a" V# rvoid fun(const int i){ …… i++; //对i的值进行了修改,程序报错}
) M- M1 D/ ^' d S9 e5 W4 q5 `7 U# B常用的函数如strlen。$ ~! v+ G) l# ~
/ h" g) F' x/ V; q3 bsize_t strlen(const char *string);
) Q* l( R9 y9 A) B0 k0 cconst在库函数中使用非常普遍,是一种自我保护的安全编码思维。
& w; T: U6 w8 H3
" z8 ?. \" v9 O' V( Rstatic关键字 E) f2 P' ?1 }9 e+ B
1、static修饰全局变量,该变量只在本文件内被访问,不能在其他文件被直接访问。7 K3 c4 Y0 u8 V
+ _+ H- J* e5 h' A
2、static修饰函数,该函数只能在本文件内被访问,不能被其他文件访问。但是可以通过嵌套的方式调用,变相的封装的表现。
" |/ ^ }4 @9 m) K3 ?0 Q
' D4 u* `4 D' }+ j1 O/ k3、static修饰局部变量,更改该局部变量的生命周期。0 \% a5 S- x: o0 k9 P& Z
生命周期:将临时变量的生命周期变成全局变量的生命周期。作用域不变:作用域仍然是在本代码块内。
- R0 U2 h" \* Q6 e
! _( [9 O" }; Z% d4 h Y z8 w3 a9 \$ X
struct与union
; z; |9 l) v; g @- }可以使用struct结构体来存放一组不同类型的数据。. G* C" f( A% g1 W) y
7 P9 b2 s' I- f% O7 N9 c$ Lstruct 结构体名{ 结构体所包含的变量或数组};
3 ?( x% K. g' |7 w结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员,通常我们使用结构体定义和解析协议,如下所示:1 n; o- e, ?! @% B2 t7 T
" r! x) K, P9 M$ A4 Q8 \$ d" ~// WiFi接收数据帧,控制切换模式#pragma pack(1)typedef struct receive_data_mode_t{ uint8_t device_head; // 数据帧头:0XA0+功能码(FUNCTION_ID3),A款产品智能插座 uint16_t device_len; // 数据包总长度 uint16_t device_id; // 节点ID 0X0001~0XFFFE char software_version[15]; // 软件版本 SMART_SW_A1_1.0 A款产品软件1.0版本 char hardware_version[15]; // 硬件版本 SMART_HW_A1_1.0 A款产品硬件1.0版本 uint8_t switch_mode; // 切换模式 0:运行模式,1:配置模式,2:节点升级,3:节点重启 uint16_t crc; // 校验位}ReceiveData_Mode_t;#pragma pack()9 Z$ J' y1 Q0 B( H0 U
union共用体关键字,定义union下面的成员变量共享一块内存,每一个成员在任一时刻有且只有一个成员使用此块内存。/ T! }6 u P1 h g4 `& }
- Q3 w! [& h6 v# Z! [
union 共用体名{ 成员列表};
* r6 Z6 H1 S4 d9 P结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。- ~1 S. n; r; Q! b! I6 M
通常使用共用体做一些标志位操作,例如以下示例,可以非常灵活的访问Val中的bit位。7 p$ B4 i- E, O% e- n
, _. e' @/ m, Rtypedef union { BYTE Val; struct __packed { BYTE b0:1; BYTE b1:1; BYTE b2:1; BYTE b3:1; BYTE b4:1; BYTE b5:1; BYTE b6:1; BYTE b7:1; } bits;}BYTE_VAL, BYTE_BITS;, Q2 o, y# O" f2 n7 u; U
或者使用共用体实现单字节与多字节的转化和拼接,如下所示:
y1 x, n, C2 P
0 b$ x6 e( r. }& C d. T#include "stdio.h"
& Z8 _0 w: u5 z/ o5 T* ktypedef struct{ union { struct { unsigned char low; unsigned char high; }; unsigned short result; };}test_t;. f0 g6 M& e* @8 r
int main(int argc, char *argv[]){ test_t hello;
2 |$ t7 U! e [3 x/ j hello.high=0x12; hello.low=0x34;' I n: k/ _2 n& q8 O9 O6 S
printf("result=%04X\r
# J7 W) j# ]" d( r5 @",hello.result);//输出 result=1234 P- V$ n. B0 Y+ t$ z
return 0;}
8 O) N$ h& b/ v% }0 Y) G7 g4 t$ w# {; ]3 _, o0 o' v% I
5
# _( \( K$ \. o/ J( j. Y* O1 {预定义标识符
& m" I6 l4 ^9 F3 J一般编译器都支持预定义标识符,这些标识符结合printf等打印信息帮助程序员调试程序是非常有用的,一般编译器会自动根据用户指定完成替换和处理。
2 _, J1 M) H' a4 t
1 V$ o( G- m! _0 u' ?9 |! |常用的预定义标识符如下所示:
) a3 a3 o5 M* p2 R- p3 Y$ C( u5 `4 `- ]! \8 L
__FILE__ //表示编译的源文件名__LINE__ //表示当前文件的行号__FUNCTION__ //表示函数名__DATE__ //表示编译日期__TIME__ //表示编译时间0 K$ Q. @0 ~5 T4 Z0 P( ]# j9 N0 P, m4 J
在Debug打印日志时候经常会用到,如下所示:
8 `) M2 ^- }3 E3 b3 V& ?+ q% g% G" M& ]6 I( u
printf("file:%s,line:%d,date:%s,time:%s",__FILE__,__LINE__,__DATE__,__TIME__);3 p+ K" \! Z# Y- ~3 H
! R5 K1 P2 m1 t! f+ n- g; ~6* L3 c7 d. o% L, u: e0 R
#与##
4 d. y. B2 a% w+ B7 G#:是一种运算符,用于带参宏的文本替换,将跟在后面的参数转成一个字符串常量。. g5 P6 ]& [" \* \! o: w. l
# @6 i( G5 U, V7 v6 N# z
##:是一种运算符,是将两个运算对象连接在一起,也只能出现在带参宏定义的文本替换中。
* v6 o, U2 i" _$ I; ?) @( K$ s5 l& M+ O
#include "stdio.h"
: L$ ^: I7 W2 l+ i! t, i. y! \#define TO_STR(s) #s#define COMB(str1,str2) str1##str2# f" E% c+ P2 [# @/ l5 b! z8 h
int main(int argc, char *argv[]){ int UART0= 115200;
/ l* l3 g5 R* r+ d) G printf("UART0=%d
b2 U4 X1 i) K O9 U- d% N", COMB(UART, 0));//字符串合并为变量UART0 printf("%s( T- k* N4 v/ C/ c# m
", TO_STR(3.14));//将数字变成字符串4 _, S4 K% n0 O @+ L3 R
return 0;}
$ O- x0 M* O4 M- q" E% W% b
; M4 R/ k7 r! c7 B. a3 K7' B; I7 V1 d' t# ^
void 与 void*关键字
2 A6 R! y% Q2 t4 Qvoid表示的是无类型,不能声明变量或常量,但是可以把指针定义为void类型,如void* ptr。void* 指针可以指向任意类型的数据,在C语言指针操作中,任意类型的数据地址都可转为void* 指针。因为指针本质上都是unsigned int。5 Q& H5 s9 O6 I! n
& x+ O8 o3 t; q常用的内存块操作库函数:1 ~! a; T+ x! C2 @5 ]' T, h
, m6 v- W9 u b7 v. @0 l. I7 {void * memcpy( void *dest, const void *src, size_t len );void * memset( void *buffer, int c, size_t num);
4 o. M$ H' ]8 e" `: Z数据指针为void* 类型,对传入任意类型数据的指针都可以操作。另外其中memcpy第二个参数,const现在也如前文所述,拷贝时对传入的原数据内容禁止修改。( |8 c" i: T7 q; ?4 D
& o1 y4 Z" X; ?$ t特殊说明,指针是不能使用sizeof求内容大小的,在ARM系统固定为int 4字节。对于函数无输入参数的,也尽量加上void,如下所示:
0 R/ G' l( }; ^6 D A' j3 U6 i: P1 r% P, w& t
void fun(void);* p1 _! P$ l- p! p' o
2 D% w5 }- N) f& o: w5 V
8! n6 a) H; N& z* s4 Y ~- B
weak关键字1 f H$ R( y6 r$ `- X/ x+ y
一般简化定义如下所示:% f P* U1 [6 m* Z+ ~# K$ ~6 F7 B
" M8 N. ^2 Z5 _$ `; z) x
#define _WEAK __attribute__((weak))
8 b1 {$ c z0 C) W' i& o函数名称前面加上__WEAK属性修饰符称为“弱函数”,类似C++的虚函数。链接时优先链接为非weak定义的函数,如果找不到则再链接带weak函数。
3 O& A4 ~9 O; T$ T* A* h! G
# C1 m- {. c3 w8 G5 o3 z_WEAK void fun(void) { //do this}
H$ N, q1 Q K. `8 e. @//不在同一个.c,两同名函数不能在同一个文件void fun(void) { //do that}
7 T+ \' t/ u/ H' `+ e/ ]这种自动选择的机制,在代码移植和多模块配合工作的场景下应用较多。例如前期移植代码,需要调用某个接口fun,但当前该接口不存在或者未移植完整使用,可以使用weak关键字定义为空函数先保证编译正常。! [1 M4 Q7 w" i/ W8 f5 {0 Q
& ?- A7 ?: H Z9 ]
后续移植完成实现了fun,即软件中有2个fun函数没有任何错误,编译器自动会识别使用后者。当然也粗暴的#if 0屏蔽对fun的调用,但要确保后续记得放开。
& s$ }) M5 z7 B$ T9 ?# F) H. G n6 D2 V! g. A8 @
w32z1grc1ce64023061336.png
6 l/ N: B( V9 j& K& b& X; G# r
往期推荐什么是内存碎片?. f# N; k9 m2 V5 e3 q; T3 n3 }
详解UDS CAN诊断:什么是UDS(ISO 14229)诊断?
# g+ n' w( O; E& n% R" Z磁耦合共振无线供电装置( B5 A5 U$ i; A8 G$ U7 i4 q
C语言:十六进制(HEX)和浮点类型(float、double)转换. ?9 ]: |' i5 g( {
HarmonyOS 分布式多端应用一站式开发平台(DevEco Studio 安装)' q% \! `( I2 Q2 @ G7 R
+ Y/ W4 T: R$ q6 q) ^& {) ?* B0 P/ H. V8 K" h9 i
uhuraiqmgcz64023061436.jpg
U5 f2 h% {, b/ {2 [* L: b" m! j
ndwrrmbi1fp64023061536.gif
) ]4 [9 d. u0 O' s8 W$ ^点击阅读原文,更精彩~ |
|