|
1 位操作 位操作与位带操作并不相同,位操作就是对一个变量的每一位做运算,而逻辑位操作是对这个变量整体进行运算。 下面是六种常用的操作运算符: 按位取反 - void test01(), O% U9 `8 C# d- w
- {
J) }) w! u! A. m - int num = 7;+ T- y4 v& k/ [ y
- printf("~num = %d\n", ~num);//-89 N1 f. m' ^% [9 S2 }
! P% j2 q }; T9 E3 m- // 0111 按位取反 1000 机器中存放的都是补码
4 l+ R0 _; ]7 r3 i( \! t - //补码转换原码需要分有符号数和无符号数两种
7 z8 J/ ]' M3 N. E( \ - }
复制代码 " v6 Q: W2 S1 D; H8 H9 y
* ]( ?& s% s) f2 U/ R% U
按位与 - void test02()
8 W' m8 f$ i! c - {
. r4 J' C9 e3 w* [ - int num = 128;
4 Z, R. \. G, h& B q - //换算为八位,1换算就是00000001, 这样只要所给数字的二进制最后一位是1.那么就是奇数,否则就是偶数
# u4 D c! f) @) v) @2 w - if ( (num & 1) == 0) " E6 F, y* G! x$ w$ g3 `9 H3 r! D
- {7 u, i" Z8 A, h* ]/ e4 U9 c0 m" F
- printf("num为偶数\n");
! Q7 Y3 x+ Z! _, z - }; H- s" I/ Z2 s& q
- else
! z5 Z/ _4 W' i - {
V3 ?! c& U( E( y7 u9 y, D) R - printf("num为奇数\n");
/ y3 G! G4 ?6 X8 q1 B - }
, c- c$ Z0 L. D; I2 s - }
复制代码
# ~# n3 N; T H按位异或 - void test03()3 V- ?. ~% A( i% b, }
- {. O' V6 l1 M5 Z$ A
- //按位异或的意思是,两个数字相同为0,不同为1。我们可以利用按位异或实现两个数的交换 x! o- T3 A! D$ _9 {8 w
- num01 = 1; // 0001
- O+ A8 a& J. u2 v, } - num02 = 4; // 0100
- i1 k& Y# q& _ P( \, N - printf("num01 ^ num02 = %d", num01 ^ num02); // 5 两个二进制按位异或之后是: 01010 _, R7 \ H5 _) X+ P1 h
- ; d" ^- z7 L9 P6 B+ v9 G
- printf("交换前\n");) b& w7 k$ ?# }! c* i
- printf("num01 = %d\n", num1);
4 i7 k1 |8 w. h- u - printf("num02 = %d\n", num2);
, @+ r3 u4 `, M4 R s: d: w3 p* k4 B - ( G, `- {9 w Q) ~+ B
- num01 = num01 ^ num02;
* I. F* C. V" V - num02 = num01 ^ num02;
+ {$ O+ V. Z3 i2 d5 C" P) f - num01 = num01 ^ num02;
8 [$ n5 p R1 ?3 b+ K - //不用临时数字实现两个变量交换
+ L+ l( t6 B# S - printf("交换后\n");& W5 S, {4 B9 ^8 n% n
- printf("num01 = %d\n", num1);2 [3 [$ D+ R/ j. y, ]" Z
- printf("num02 = %d\n", num2);
: A8 T* g* T5 U( C7 N! G s - }
复制代码
, m, O5 \! G+ N' k ^& T5 |" B按位或7 p" _& ]. A# S. _$ l
计算方法:
6 S, O a8 m3 I0 m) n3 { 参加运算的两个数,换算为二进制(0、1)后,进行与运算。只有当 相应位上全部为1时取1, 存在0时为0。 printf是格式化输出函数,它可以直接打印十进制,八进制,十六进制,输出控制符分别为%d, %o, %x, 但是它不存在二进制,如果输出二进制,可以手写,但是也可以调用stdlib.h里面的itoa函数,他不是标准库里面的函数,但是大多数编译器里面都有这个函数。 - #include <stdio.h>
' Z$ j. C# e- F6 C( p - #include <stdlib.h>: q0 b9 H- f! x" i; T0 R
1 \6 w) Y5 p3 y" h- V- int main()
' K; y( E. r5 K, k+ q5 @ - {3 D; J, W' }' M4 b% }% c; T
- test04();
# a% b& _7 x# y- x$ [ - }
, w+ @" [1 b6 q, V: | - 8 `$ z( D2 N9 L4 z
- int test04()
$ G0 Q3 z- A7 m6 k$ P* c - {
$ m' @- N/ q+ p9 i, j - int a = 6; //二进制01104 [6 Z' Z: l6 e: ~( Z+ }# F
- int b = 3; //二进制0011% B! Q4 D: R O( ^% x: p% v
- int c = a | b; //a、b按位或,结果8,二进制111,赋值给c# T- e' S& y9 a
- char s[10];
) w1 f0 b2 s! [3 P1 X& R- _* J - itoa(c, s, 2);# h) \: a1 M) P! j3 K2 J
- printf("二进制 --> %s\n", s);//输出:二进制 -->111
$ G& R/ n. X, e* P" h( }1 B6 ^ - }
复制代码
/ o7 h$ W E& l' m7 q6 G2 P' V9 K$ t9 \
左移运算符 - void test05()9 Q8 F' ^+ W: k2 q( G
- {
V' k& |& F7 O0 z) s - int num = 6;/ I7 _ _" m: ]3 g
- printf("%d\n", num << 3);//左移三位,就是0000. X7 c* P9 u( K5 @( m
- }
复制代码
4 |6 D# b* W% g! D# ^# E) E/ r
0 j4 N7 w! K9 _7 A9 n8 d
右移运算符 - void test06()( t6 b, e0 L9 N5 _$ Q" B5 C
- {
- w# }7 _6 f0 g$ N9 u - int num = 6; //0110
$ V# F5 Q$ o$ f9 v% h - printf("%d\n", num >> 1); //右移一位,就是0011,输出3+ N. u4 g' p/ h X+ ~) h2 l- ~4 d8 B
- }
复制代码
+ p# H2 Q( {3 v# ?! e
1 o+ E+ C- j( A" w7 P+ g
上面是用普通c代码举得栗子,下面我们看一下STM32中操作通常用的代码: (1)比如我要改变 GPIOA-> BSRRL 的状态,可以先对寄存器的值进行& 清零操作 - GPIOA-> BSRRL &= 0xFF0F; //将第4位到第7位清零(注意编号是从0开始的)
复制代码 0 g/ p" I0 l/ o, ^
然后再与需要设置的值进行|或运算: - GPIOA-> BSRRL |= 0x0040; //将第4位到第7位设置为我们需要的数字
复制代码
5 M, D, b/ K: N3 W9 J, P (2)通过位移操作提高代码的可读性: - GPIOx->ODR = (((uint32_t)0x01) << pinpos);
复制代码
, e% U r( J; h6 q 上面这行代码的意思就是,先将"0x01"这个八位十六进制转换为三十二位二进制,然后左移"pinpos"位,这个"pinpos"就是一个变量,其值就是要移动的位数。也就是将ODR寄存器的第pinpos位设置为1。 (3)取反操作使用: SR寄存器的每一位代表一个状态,如果某个时刻我们想设置一个位的值为0,与此同时,其它位置都为1,简单的作法是直接给寄存器设置一个值:
9 n2 r3 F9 ~7 J$ L 这样的作法设置第 3 位为 0,但是这样的作法可读性较差。看看库函数代码中怎样使用的: - TIMx->SR = (uint16_t)~TIM_FLAG;
复制代码 6 w9 ^, ]3 H) j3 @; v* _
而 TIM_FLAG 是通过宏定义定义的值: - #define TIM_FLAG_Update ((uint16_t)0x0001) ! D! Z1 }' r* n* S
- #define TIM_FLAG_CC1 ((uint16_t)0x0002)
复制代码 0 d2 g$ ~/ ^) P# ~) S$ u0 C% A
- D J! O# N6 A p
2 define宏定义 define 是 C 语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供 方便。 常见的格式: % W; P, [8 u/ x& y. y/ U
标识符意思是所定义的宏名,字符串可以是常数、表达式或者格式串等,例如: - #define PLL_Q 7 //注意,这个定义语句的最后不需要加分号
复制代码
0 A- _" ?: L+ r1 L: r1 z( k ?3 ifdef条件编译 在程序开发的过程中,经常会用到这种条件编译: - #ifdef PLL_Q
1 ?. C4 j/ h- u4 z- K4 e - 程序段1
, K6 }+ D( X! B, ^/ S! \ - #else; A/ b( r; k- @7 B% D8 {+ H
- 程序段2
& O6 [1 r6 Q; S6 x9 D- ~8 d - #endif
复制代码 上面这段代码作用就是当这个标识符已经被定义过,那么就进行程序程序段1,如果没有则进行程序段2。当然,和我们设计普通的c代码是一样的,"#else"也可以没有,就是上面的代码减去"#else"和程序段2。 - #ifndef PLL_Q //意思就是如果没有定义这个标识符
复制代码
Z7 \9 f1 d! }( L1 K4 extern变量申明 C 语言中 extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义(一个变量只能定义一次,而extern可以申明很多次)使用例子如下: extern u16 USART_RX_STA; 上面例子意思就是申明 “USART_RX_STA” 这个变量在其他文件中已经定义了,"u16"的意思是16位的。
5 E7 f' G0 f2 {! w6 n
5 结构体 定义一个结构体的一般形式为: - struct 结构名
; e& P- @* R1 C" T0 P5 K/ r - {
+ ~3 y6 T7 M3 M1 j - 成员列表- f8 u7 Y: S" h; X- ~& G
- };
复制代码
, C3 x% Q1 A- n4 B. S$ L; U% A/ a 成员列表由若干个成员组成,每个成员都是该结构体的一个组成部分。对每个成员也必须作类型说明,其形式:
% |* K; Z' x$ c7 M 结合上面的说明,我们可以构建一个简单的结构体例子: - struct sutdent. |' [$ I( W, A4 o) i- Q% I1 K
- {
% d7 \" a6 n0 z. n) u+ ^% J5 O - int num;
9 ]8 O0 n% j; ` - char name[20]; //20个字节长的字符
: |4 D5 U) O2 s3 G# A5 M+ m2 Z - char sex;/ S( K, Z! ^, W3 C- |* ~
- int age;
- P2 E Z' g4 }/ q, v6 { - float score;
/ n8 ]$ ?8 C+ L: J* p% I - char addr[30]; //30个字节长的字符
, z+ p: F4 X2 l1 x5 K- O$ x2 K - }
复制代码 : Q+ S! ^! P# r5 e! K4 j B. F* V
6 f9 R1 F9 G# D7 y) }; Y; g
而如果我们想定义结构体变量,那么我们在定义这个结构体的时候直接定义,或者定义完结构体再另外定义结构体变量,比如: - struct sutdent
( K& d8 l3 q3 s - {: q( W* d6 ^( B2 Q: j+ ?
- int num;8 e; e \, f' I
- char name[20]; //20个字节长的字符
# ~+ R: k0 w. z9 B, [" o- U) y! U - char sex;
) C/ n8 L: W& p- z6 q$ L9 k" M - int age;- _1 H3 i9 r3 l k- V; w
- float score;0 Z, A" a$ ?% I' j0 q8 }) }
- char addr[30]; //30个字节长的字符/ S9 t) ]$ _3 J( j
- }student01,student02; //变量名表列(如果由结构体变量名,那么我们可以不写结构体名称)
复制代码
( S: q9 l/ h L6 a5 n# C/ N, `( D% ~* E4 A
有时候我们可能需要用到结构体的嵌套,比如: - struct date
" D, |' r- I! X3 N- Q! W - {
& H# p. l% l5 y8 r+ f: w2 e - int year, month,day;
5 ~ o# ~: ^" y; U7 O( S H/ E - };& C8 j: k2 A# Y* V1 v
- struct sutdent
. s* J/ G& n: t* T j4 l% C7 G - {
; e5 u F) l$ u1 g2 ?+ w; u - int num;
0 _/ }4 ?3 j9 {9 @: h4 `3 L - char name[20]; //20个字节长的字符" |$ F' t5 ~, h6 T
- char sex;
( @% Y7 z; b0 ~) F+ W - struct date birthday; //这里就用到了结构体的嵌套/ s$ O, s6 r2 B( `! o% {# V+ N! H
- int age;
0 `# Q! M& f2 ?: ]0 S* k- g - float score;; e; \! c0 a1 I
- char addr[30]; //30个字节长的字符, ^! A) W% `, l0 d3 P7 R
- }student01,student02; //变量名表列(如果由结构体变量名,那么我们可以不写结构体名称)
复制代码
6 g1 E7 j$ E+ R; E* U
如果需要引用结构体里面的成员内容,可以使用下面的方式: - student01.name = 小李;
& {! }* l0 \+ c0 G" H0 a% A - // 结构体变量名.成员名(注意这里用的是点),这里是对这个成员的赋值
复制代码; ?! y6 j5 y: d( e% @& @
结构指针变量说明的一般形式为:
9 z& v$ t( `8 l& O4 x# m 假如说我们想定义一个指向结构体"student"的指针变量pstu,那么我们可以使用如下代码: ; }8 j- P7 l6 y7 R
如果我们要给一个结构体指针变量赋初值,那么我们可以使用如下的方式: - struct student
0 B! g% }3 b4 B/ \* j1 ` - {7 Z6 {# b+ y3 @: F. E% D
- char name[66];
5 ?" g! E1 n) x9 `$ } - int num;
5 ~- q% ?& c# `8 W% ?+ t - char sex;
$ N! w' p# s+ c4 X3 c - }stu;- C9 d+ W1 z2 q/ u6 X
" t; S7 v! S, d- , ~) c+ p+ K# r! j$ B, ^
- pstu = &stu;
复制代码7 m1 f+ x3 D' V9 l' s
注意上边的赋值方式,我们如果要进行赋值,那必须使用结构体变量,而不能使用结构体名,像下边这样就是错误的。 - struct student+ [0 b4 ~& {) z9 R: o
- {7 R3 C' x. K' z( R
- char name[66];
* t8 s' @) J% t+ h4 x4 l8 B4 [$ l% C - int num;
0 Y$ T$ p; X/ b - char sex;8 [+ t$ J1 C$ c& y1 n
- }stu;
7 `9 I8 i% N7 |( o
" O+ G$ ?9 ? I, D
5 r0 I0 ^$ d: @# B! S6 |& F9 K3 n) w; ]- pstu = &student;
复制代码
$ f4 |8 Z( b n% j7 ~' `( J 这是因为结构名和结构体变量是两个不同的概念,结构名只能表示一个结构形式,编译系统并不会给它分配内存空间(就是说不会给它分配地址),而结构体变量作为一个变量,编译系统会给它分配一个内存空间来存储。 访问结构体成员的一般形式: - (*pstu).name; //(1)(*结构指针变量).成员名;; p+ Z6 e7 e" K+ {' l3 _& E: F1 S
- pstu->name; //(2)结构指针变量->成员名
复制代码 结构体的知识就简单说上边这些。 6 typedef类型别名 typedef用来为现有类型创建一个新的名字,或者称为类型别名,用来简化变量的定义(上边extern变量申明的例子中,"u16"就是对"uint16_t"类型名称的简化)。typedef在MDK中用得最多的就是定义结构体的类型别名和枚举类型。 我们定义一个结构体GPIO: 定义这样一个结构体以后,如果我们想定义一个结构体变量比如"GPIOA",那么我们需要使用这样的代码:
4 u. |5 T- _/ g" ] 虽然也可以达到我们的目的,但是这样会比较麻烦,而且在MDK中会有很多地方用到,所以,我们可以使用"typedef"为其定义一个别名,这样直接通过这个别名就可以定义结构体变量,来达到我们的目的: - typedef struct
% P0 e' s; T: w/ ` @7 b - {) t, x% f6 D) h
- _IO uint32_t MODER;
% w }: [5 V4 q0 h) y - _IO uint32_t OTYPER;
/ \# S% `+ |+ K" D4 t2 \( [ - }GPIO_typedef;
复制代码
: h- k" k$ D& A4 t
这样定义完成之后,如果我们需要定义结构体变量,那么我们只需要这样: - GPIO_typedef _GPIOA,_GPIOB;
复制代码 ) o0 q. L5 J/ U" z5 k5 T
|