赞
踩
这是一个只有懂的才懂的痛点。
我们在FSMC传输时使用的都是Uint16,但是呢此时直接使用该值就会出问题。
尤其是转接的时候,当一个int16通过Uint16给到你,你又需要传递给别人的时候,你需要做的是Uin16 b =(int16)a
union{
struct{
a:1;
}bit;
Uint16 all;
}state
枚举类型
enum{
a,//a=1,也是可以的
b,
c
}Type;
跟结构体和共用体一样,最后一定要有分号。内部用逗号。纠正一下,只有枚举内部用逗号,结构体和共用体内部一定要用分号!
C语言最初由 Dennis Ritchie 于 1969 年到 1973 年在 AT&T 贝尔实验室里开发出来,主要用于重新实现 Unix 操作系统。此时,C语言又被称为 K&R C。其中,K 表示 Kernighan 的首字母,而 R 则是 Ritchie 的首字母。
由于C语言被各大公司所使用(包括当时处于鼎盛时期的 IBM PC),因此到了 1989 年,C语言由美国国家标准协会(ANSI)进行了标准化,此时C语言又被称为 ANSI C。
而仅过一年,ANSI C 就被国际标准化组织 ISO 给采纳了。此时,C语言在 ISO 中有了一个官方名称——ISO/IEC 9899: 1990。其中:
• 9899 是C语言在 ISO 标准中的代号,像 C++ 在 ISO 标准中的代号是 14882;
• 而冒号后面的 1990 表示当前修订好的版本是在 1990 年发布的。
对 于ISO/IEC 9899: 1990 的俗称或简称,有些地方称为 C89,有些地方称为 C90,或者 C89/90。不管怎么称呼,它们都指代这个最初的C语言国际标准。
在随后的几年里,C语言的标准化委员会又不断地对C语言进行改进,到了 1999 年,正式发布了 ISO/IEC 9899: 1999,简称为 C99 标准。
C99 标准引入了许多特性,包括内联函数(inline functions)、可变长度的数组、灵活的数组成员(用于结构体)、复合字面量、指定成员的初始化器、对IEEE754浮点数的改进、支持不定参数个数的宏定义,在数据类型上还增加了 long long int 以及复数类型。
毫不夸张地说,即便到目前为止,很少有C语言编译器是完整支持 C99 的。
2007 年,C语言标准委员会又重新开始修订C语言,到了 2011 年正式发布了 ISO/IEC 9899: 2011,简称为 C11 标准。
C11标准新引入的特征尽管没 C99 相对 C90 引入的那么多,但是这些也都十分有用,比如:字节对齐说明符、泛型机制(generic selection)、对多线程的支持、静态断言、原子操作以及对 Unicode 的支持。
编译就是通过编译器将人写到代码翻译成机器语言。
编译器将.c翻译成.obj目标文件,.obj是二进制文件,但不是最终的.exe文件。
.obj文件通过链接器将目标文件与目标 文件与库文件合并成最终的.exe文件,也是二进制文件。
我们所讲的编译器是包含了链接器的。
将生成的.o文件直接放到.c的位置,可以提高编译速度。
对CCS来说,.o文件在CCSProject ,Debug,同名目录下。
先将.o复制到本地工程.c目录下,然后将文件夹拖动到CCS工程中,接下来将.c文件exclude from project 然后编译,惊奇得发现.o代替.c了!!!
预处理命令可以改变程序设计环境,提高编程效率
#define 宏名(形参表) 字符串;
#define M(y) y*y+3*y
/*宏定义*/
......
k=M(5);
/*宏调用*/
前加##或后加##,将标记作为一个合法的标识符的一部分,例如:
#define A(x) T_##x
则 int A(1) = 10; //等效于int T_1 = 10;
#define A(x) Tx##__
则 int A(1) = 10; //等效于int T1__ = 10;
编译器根据条件执行或忽略块。
#ifdef MAVIS
#include ”horse.h"
#indlude STABLES 5
#else
#include "cow.h"
#define STABLES 15
#endif
如果定义了MAVIS,则执行下列指令,否则
是否定义,#define MAVIS
与普通选择语句主要区别为预处理器不识别{},这种指令结构可以用于嵌套。
在程序中使用上述结构
int main(void)
{
for (i=1; i<LIMIT;i++)
{
#ifdef JUST %是否定义了JUST
printf("i=%d,running total=%d\n",I,total);
#endif
}
}
定义JUST并合理使用#ifdef,编译器会执行用于调试程序的代码,打印中间值。调试结束后,可移除JUST定义再重新编辑。
#ifndef指令
与上述逻辑相反,判断后面的标识符是否是未定义的,可以防止相同的宏被重复定义
#ifndef SIZE
#define SIZE 100
#endif
如果没有定义SIZE,则
#ifdef __cplusplus
extern “C” {
#endif
// Code
#ifdef __cplusplus
}
#endif
在C++编译器中,已经内置了__cplusplus这个宏定义,所以在使用C++编译器编译其它语言(比如C语言)时,用这样的方式,可以让编译器把extern “C” 代码块中的内容按照C语言的方式进行编译。
#if SYS==1
#include "ibmpc.h"
#elif SYS==2
#include "vax.h"
#elif SYS==3
#include "mac,h"
#endif
泛型选择(C11)
泛型编程指那些没有特定类型,但是一旦指定一种类型,就可以转换成指定类型的代码。
for(i=0;i<100;i++)
{
printf("i count is %d\n",i);
}
分支
1. else if
相当于 if… else
if… else
最少支持嵌套127层。
if(判断条件1){
语句块1
} else if(判断条件2){
语句块2
}else if(判断条件3){
语句块3
}else if(判断条件m){
语句块m
}else{
语句块n
}
while中条件判断语句,看其结果是true还是false
如果是false,循环结束;如果是true,循环继续执行
do
{
printf("count %d",i);
}while(i<20);
switch(value)
{
case 1:printf("one");break;
case 2:printf("two");break;
case 3:printf("three");break;
default:printf("other");break;
}
c中,变量名、函数名、标号等统称为标识符
除库函数的函数名由系统定义外,其余都由用户自定义。
常量是指程序在运行时其值不能改变的量。常量不占内存,在程序运行时它作为操作对象直接出现在运算器的各种寄存器中。
C语言基本数据类型为:整型、字符型、实数型。
初始化静态或全局变量时,不能调用函数,会产生错误信息。
u8 unsigned char
u16 unsigned short int
u32 unsigned int
u64 unsiged _INT64
int8 signed char
int16 signed short int
int32 signed int
int64 siged _INT64
u8 unsigned char
u16 unsigned short
u32 unsigned long
u64 unsigend long long
float32 float
float64 double
int8 signed char
int16 signed short
int32 signed long
int64 sigend long long
一维数组:类型说明符 数组名 [常量表达式];
int a[100]; //定义一个数组名为a,存储100个int类型的数组,其元素分别是a[0]~a[99]
float b[10]; //数组名为b的,存储10个float类型的数组,其元素分别是b[0]~b[9]
char c[256]; //定义一个数组名为c的字符型数组 长度为256,其元素分别是c[0]~c[255]
当给部分元素赋初值的时候,未被赋值的元素将自动赋值为0
int a[100]={1,2,3,4,5}; //定义一个整型数组a,前5个元素即赋值为1,2,3,4,5,后95个元素值值全部为0
float b[10]={1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9.9,0.0}; //定义float数组b并对全部float类型的元素都分别赋值
char c[256]={'C','l','a','n','g','u','a','g','e'}; //定义一个数组名为c的字符型数组 并对前9个元素进行赋值,其余元素全部为'\0'
二维数组的定义:
类型说明符 数组名[行数][列数];
int a[3][4];/*定义一个整形二维数组a,有3行4列共12个元素分别为:
a[0][0] a[0][1] a[0][2] a[0][3]
a[1][0] a[1][1] a[1][2] a[1][3]
a[2][0] a[2][1] a[2][2] a[2][3]
*/
char arry[10][10];//定义一个字符型二维数组arry,有10行10列,依次为arry[0][0]~arry[9][9]供100个元素
int a[3][4]={{1,2,3,4},{10,20,30,40},{100,200,300,400}};//定义一个三行四列的二维数组,按行赋值
int a[3][4]={1,2,3,4,10,20,30,40,100,200,300,400};//定义一个三行四列的二维数组并对其中的12(3*4)个元素进行赋值
char str[25] = “Good morning”;
printf(“%#X, %#X\n”, &a, str);
定义一个数组u16 pBuffer[x].x=sizeof()。然后定义函数的时候就可以定义(u16* pbuffer),传递过去首地址也就是pBuffer数组名就可以了。长度该是多少传多少。
事实上也可以这样
DeBug(MAIN_DE,"主函数执行");
void DeBug(u16 IdNumber,char* message)
以下是对二维字符数组元素的合法引用:
1.printf(“%c”,arr【1】【4】);//输出1行4列元素‘g’字符
2.scanf(“%c”,&arr【2】【3】);//输入字符到arr【2】行3列元素中
3.arr【2】【0】=‘B’,//把字符赋值给第二行0列的元素
4.printf(“%s”,arr【1】);//arr【1】为第二行的数组名(首元素地址),输出orange
5.scanf(“%s”,arr【2】);//输入字符串到arr【2】行,从arr【2】行的首地址开始存放
char c[10];
char c[6]={'c', ' h ', 'i', 'n', 'a' , '\0' };
‘\0’为字符串结束符。如果不对 c[5]赋任何值,‘\0’会由系统自动添加。
字符数组也可采用字符串常量的赋值方式,例如:
char a[]={"china"};
static 一般用于修饰局部变量,全局变量,函数。
void test()
{
int a = 1;
a++;
printf("%d ", a);
}
int main(void)
{
int i = 0;
while (i <= 10)
{
test();
i++;
}
return 0;
结果为2 2 2 2 2 2 2 2 2 2 。将test中int a=1;改为static int a=1; 后: 结果为2 3 4 5 6 7 8 9 10。Static可以讲一个局部变量变为全局变量,作用等同。
全局变量具有外部链接属性。而static修饰全局变量时,这个全局变量的外部链接属性变为内部链接属性。
类似于static修饰全局变量。函数同样具有外部属性。而static修饰函数时,这个函数的外部链接属性变为内部链接属性。
整数型和数组型这些开辟空间都是固定的,有点浪费或者不够用,这时候就要用动态内存开辟。
malloc和free都声明在 stdlib.h 头文件中。
#include <stdio.h>
int main()
{
//代码1
int num = 0;
scanf("%d", &num);
int arr[num] = {0};
//代码2
int* ptr = NULL;
ptr = (int*)malloc(num*sizeof(int));
if(NULL != ptr)//判断ptr指针是否为空
{
int i = 0;
for(i=0; i<num; i++)
{
*(ptr+i) = 0;
}
}
//释放ptr所指向的动态内存
free(ptr);
ptr = NULL;//是否有必要?,释放后,空间还给操作系统,ptr就是野指针,后面如果用了就很危险,所以要赋值null
return 0;
}
m是指内存,alloc是指分配
void* malloc (size_t size);
初始化静态或全局变量时,不能调用函数。
c语言中提供了free,用于动态内存分配的回收和释放,原型如下:
void free (void* ptr);
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
当我们不释放动态申请的内存时,程序结束时由操作系统自动回收,如果程序不结束,动态内存是不会自动回收的,会造成内存泄漏的问题。
用于定义内联函数,像普通函数一样被调用,但是在调用时并不通过函数的调用机制,而是直接在调用点展开,这项可以大大减少由函数调用带来的开销,从而提高程序的运行效率
使用注意
告知编译器编译方法的关键字。不优化编译。这个变量有可能被其他子程序改变,所以不能从寄存器里读取变量的值,要从内存里读取。
编译器优化的时候可能会跳过某一段变量的外部赋值(外部用户按键)过程。
attribute_(at(绝对地址))的作用就是绝对定位。
绝对定位不能在函数中定义,函数中定义的普通变量是局部变量,局部变量是定义在栈区的,栈区由MDK自动分配、释放,不能定义为绝对地址。
一般我们的变量都存在于内部SRAM中,在使用该语句能够将变量定义到对应地址的flash或者ram中。
但是定义的长度不能超过栈或Flash的大小,否则就会造成栈、Flash溢出。
定位到 flash 中,常用于固化信息,例如:设备的出厂信息,FLASH 标记等
const uint8_t usF[] __attribute__((at(0x00030000))) = {0x11,0x22,0x33,0x44,0x55,0x66};//定位在flash中,0x00030000开始的6个字节信息固定
在 0x08010000 的 flash 地址上固定写入数据:
编译后:
我们在烧录数据的时候,一般是从0x08000000开始按照顺序烧录到flash里面的,要让数据能够定义到绝对地址如0800F000,就必须保证文件内数据也是存储在该地址,所以,MDK在生成文件时会填充0x00字段。
常用于数据量较大的缓存,如:串口接收数据。也用于某个位置的特定变量。
uint8_t ucU[USART_RECV_LEN] __attribute__ ((at(0x00025000))); //接收缓冲,最大USART_RECV_LEN个字节,起始地址为 0x00025000
如果不加 const 修饰,则定位到了 RAM 。
attribute((aligned(x)))定义的是最小对齐边界,它可以用来修饰structure也可以用来修饰变量或者structure成员变量.
#pragma pack(push,2)
typedef struct test{
char a;
double b;
char c;
int d;
} __attribute__ ((aligned (32))) test ;
#pragma pack(pop)
#pragma pack(push,2) 将编译器的最大对齐边界设置为 2
以 2 为最大对齐边界后,test 的内存对齐情况(先不考虑 aligned 属性)
typedef struct test{
char a; // 1B
double b; //因为最大对齐是2,所以只需要满足2的倍数,不需要满足8的倍数
//填充 1B ,b 本身占据 8B
char c; //对齐时我们要选择最小的对齐值,对于 c最小的对齐值是1(不是2)
//无填充,自身占据 1B
int d; //对齐值为2,故填充 1B,自身占据 4B
} test ; //整个结构体对齐值为 2(不是8,因为已经设置了最大对齐值是2)
// (1B) + (1B+8B) + (1B) + (1B+4B) + 0B(已经满足2的倍数,无需对结构体进行填充)
当前(不考虑 aligned 属性)时,test 类型占据 16B,最大对齐值 2 。
aligned 属性设置的是最小对齐值,也就是说当前类型或变量的对齐值要大于或等于aligned 属性设置的对齐值,所以编译器重新设置 test类型的对齐值为32,则test struct需要填充16B。
1 __attribute__((section("section_for_test")))
2
3 /* 定义一个全局变量 */
4 char for_test[] = "hello the world!";
“attribute((section(“section_for_test”)))”将变量 for_test 输入节区样式设置为“section_for_test”
scf文件
19 #define for_test_start 0x61000000
20 #define for_test_size 0x00001000
77 Load_section for_test_start for_test_size{
78
79 /***************** 第四部分 ***************/
80 run_section_for_test m_data2_start m_data2_size{ ; 执行域位于 DTCM
81 *(section_for_test)
82 }
map文件
Load Region Load_section (Base: 0x61000000, Size: 0x00000014, Max: \
0x00001000, ABSOLUTE)
Execution Region run_section_for_test (Base: 0x20200000, Size: \
0x00000014, Max: 0x00040000, ABSOLUTE)
7
Base Addr Size Type Attr Idx E Section Name Object
10 0x20200000 0x00000011 Data RW 1260 section_for_test main.o
11
12 /********************** 第二部分 ******************/
13 Image Symbol Table
14
15 Local Symbols
16
17 Symbol Name Value Ov Type Size Object(Section)
18 section_for_test 0x20200000 Section 17 main.o(section_for_test)
19
20 /********************* 第三部分 ******************/
21 Global Symbols
22
23 Symbol Name Value Ov Type Size Object(Section)
24
25 for_test 0x20200000 Data 17 main.o(section_for_test)
定义的位置
#include "core_cm3.h"
#include "core_cmFunc.h"
终于现身了:
__attribute__( ( always_inline ) ) static __INLINE void __enable_irq(void)
{
__ASM volatile ("cpsie i");
}
关于中断屏蔽有两个接口:disable_irq和disable_irq_nosync,该两接口使用场景如下:
1、disable_irq:在非中断处理函数中使用,会阻塞;
2、disable_irq_nosync:在中断处理函数中使用,不会阻塞;用于屏蔽相应中断;
一、为什么要屏蔽中断?
使能中断后,一旦触发中断,系统会进入中断处理函数;如果下一个中断触发的时候,前一个中断处理函数已经完成,这是理想状态,不会发生异常;如果前一个中断处理函数还未完成,那么就会导致中断嵌套。为了不出现中断嵌套,必须在中断处理函数中屏蔽中断,待中断处理完成后,再主动使能中断。
二、disable_irq不能放在中断处理函数中
如果在中断处理函数中使用disable_irq屏蔽相应中断,系统将会出现死锁状态,最后死机,然后重启。(已验证)
三、enable_irq配套使用
当中断处理函数已经完成所有工作,在返回之前需要主动调用接口enable_irq使能中断,否则该中断将一直被屏蔽。
ANSI C 定义了许多宏。在编程中您可以使用这些宏,但是不能直接修改这些预定义的宏。
当前日期,一个以 “MMM DD YYYY” 格式表示的字符串常量。
当前时间,一个以 “HH:MM:SS” 格式表示的字符串常量。
这会包含当前文件名,一个字符串常量。
这会包含当前行号,一个十进制常量。
STDC 当编译器以 ANSI 标准编译时,则定义为 1;判断该文件是不是标准 C 程序。
printf("%d\n",__LINE__);
printf("%s\n",__FILE__);
#pragma 是一个预处理指令,用于向编译器发送特定的指示,以控制编译器的行为和设置。它通常用于向编译器提供一些额外的指令或指示,以便进行编译器特定的优化、警告控制、对齐方式设置等。
一、 编译器优化指示
#pragma 可以用于向编译器发出性能优化的指示。例如,#pragma inline 可以启用函数内联优化,#pragma optimize 可以设置函数级别的最优化级别等。通过使用这些指示,可以告诉编译器如何优化代码以提高程序的性能。
二、编译器警告控制
#pragma 可以用于控制编译器产生的警告信息。例如,#pragma warning 可以用于控制特定警告的显示与隐藏,或者设置警告的级别。通过使用这些指示,可以灵活地控制编译器的警告输出。
三、对齐方式设置
#pragma 可以用于设置结构体、变量或对象的对齐方式。例如,#pragma pack 可以设置结构体的对齐方式,或者 #pragma pack(push, n) 和 #pragma pack(pop) 可以在指定的代码块内设置和恢复对齐方式。通过使用这些指示,可以控制内存对齐以提高数据访问的效率。
四、其他特定功能指示
#pragma 还可用于执行一些特定编译器的功能指示。例如,#pragma once 可以用于指示编译器只包含一个特定的头文件一次,#pragma message 可以在编译时输出一条自定义的消息等。
#pragma pack 禁止对齐
#pragma pack(4)
//按4字节对齐,但实际上由于结构体中单个成员的最大占用字节数为2字节,因此实际还是按2字节对齐
typedef struct
{
char buf[3];
//buf[1]按1字节对齐
//buf[2]按1字节对齐
//由于buf[3]的下一成员word a是按两字节对齐,因此buf[3]按1字节对齐后,后面只需补一空字节
word a;
//#pragma pack(4),取小值为2,按2字节对齐。
}kk;
#pragma pack() //取消自定义字节对齐方式
对齐的原则是min(sizeof(word ),4)=2,因此是2字节对齐,而不是我们认为的4字节对齐。word为结构体中单个成员的最大占用字节数。
1个字节a 1个字节b//对齐
1个字节c 1个空字节
2个字节D
1个字节...
#Pragma message 参数
能够设置程序中函数代码存放的代码段
(比较常用)若用在头文件的最开始处就能够保证头文件被编译一次.
优先级
a. 圆括号,[ ]
b. ! ++ ,从右向左a++
c. ×
d. > < !=
e. && &(位)
f. ||
g. =
转义字符通常用于代表难于表达的或是无法键入的字符。
%s 字符串
%10s 10位的字符串
%m.nf 总共m位,n位小数点以后的。
%d输出的number变量是十进制整数
%d是一个占位符,其作用是指出输出 num 值的位置。
类型 函数名(形参表说明) /*函数首部*/
{
说明语句 /* 函数体*/
执行语句
}
类型:是指函数返回值的类型。
stdio.h文件是所有C语言编译器的标准部分,用来提供输入和输出的支持。它和我们平常见到的 123.txt 并无差别,只是后缀名不同而已
定义在stdio.h中的派生类型,定义文件指针。FILE *p;
字符串输出函数
sprintf(A,B,C,D)把多个元素组合成一个字符串,但不显示在屏幕上。A是目标字符串地址。其余参数与printf相同。
如spintf(des,“%s,%-19s:\n”,last,first);
%d整型输出,%ld长整型输出,
%o以八进制数形式输出整数,
%x以十六进制数形式输出整数,
%#x表示以0x开头的整数输出
%u以十进制数输出unsigned型数据(无符号数)。可以理解为整数输出。
%c用来输出一个字符,
%s用来输出一个字符串,
%f用来输出实数,以小数形式输出,
%5.2f 格式表示输出宽度为5(包括小数点),并包含2位小数。
%e以指数形式输出实数,
%g根据大小自动选f格式或e格式,且不输出无意义的零。
无前缀方式:
printf("%o",num) //无前缀o的8进制数
printf("%x",num) //无前缀0x的小写16进制数
printf("%X",num) //无前缀0X的大写16进制数
有前缀方式:
printf("%#o",num) //有前缀o的8进制数
printf("%#x",num) //有前缀0x的小写16进制数
printf("%#X",num) //有前缀0X的大写16进制数
如果转换说明符为%* d,那么参数列表中应该包括一个*的值和一个d的值,来控制宽度和变量的值。
scanf("%d",&width);
printf("The number is: %*d\n",width,number);
printf("Then please input width and precision:\n");
scanf("%d %d",&width,&precision);
printf("Weight = %*.*f\n",width,precision,weight);
八:o (august)
十:d
十六:x(six)
格式输入函数,从键盘上把数据输入到指定的变量之中。
scanf(“格式控制字符串”,输入项地址列表);
格式说明符中,可以指定数据的宽度,但不能指定数据的精度。
字符输出函数,其功能是在终端(显示器)输出单个字符。
putchar(‘A’); /*输出大写字母A */
putchar(x); /*输出字符变量x的值*/
putchar(‘\n’); /*换行*/
getchar函数的功能是接收用户从键盘上输入的一个字符。
getchar会以返回值的形式返回接收到的字符.
char c; /*定义字符变量c*/
c=getchar(); /*将读取的字符赋值给字符变量c*/
toupper():处理字符串中的每个字符转成大写。
tolower():大写转小写字母。
isalpha(ch):收到字母,返回非零值,非字母,0.
ispunct():收到标点符号,返回非零值,非标点符号,0.
sizeof是一种单目运算符,以字节为单位返回某操作数的大小,用来求某一类型变量的长度。
#include<stdio.h>
int main()
{
int n=0;
int intsize = sizeof(int);
printf("int sizeof is %d bytes\n",intsize);
return 0;
}
isspace():若是空白字符返回1,否则返回0。
字符串长度函数
strlen(A):统计字符串长度。
strlen(string); string是指针变量。
字符串拼接函数
a. strcat(A,B):将第二个字符串的备份附加在第一个字符串的末尾作为第一个字符串。拼接函数。第一个字符串的结尾空字符被覆盖。第二个字符串不变。
b. strncat:无法检查第一个数组能否容纳第二个字符串。如果多出来的字符溢出到相邻的存储单元会出问题。
strncat(A,B,C),第三个参数规定了最大添加字符数。也就是添加到第C个字符或者遇到空字符时停止。
字符串比较函数
字符串查找函数
strchr(A,'\ '):字符查找函数。没有找到返回空指针NULL.
把 src 所指向的字符串复制到 dest
char *strcpy(char *dest, const char *src)
如果目标数组 dest 不够大,而源字符串的长度又太长,可能会造成缓冲溢出的情况。
字符串复制函数概述
a. strcpy():拷贝字符串。
如果拷贝字符数小于n,z则拷贝整个字符串包括空字符。如果拷第n还未拷完则不会拷空字符。因此若要确保拷贝的是一个字符串,则把C设为n-1,然后令A[n-1]=‘\0’.
. atoi():函数,把字母数字转换为整数。返回值为整数。
如果输入字符串atoi(“42stjijso”)将会返回42.
如果输入字符串atoi(“sjji”)将会返回0.
头文件:stdlib.h
. atof():把字符串数字转换成double类型数值。
. atol():把字符串转换成long类型数值。
. strtol()有错误检测功能的atoi函数。把字符串转换成long类型的值。
. strtoul():把字符串转换成unsigned long类型值。
. strtod():把字符串形式的数字转换为double类型的数字。
strtol与strtoul更智能,可以检测首字符是否是数字。
union 共用体名
{
数据类型 成员名 1;
数据类型 成员名 2;
......
数据类型 成员名 n;
}变量名表列;
union hold
{
int digit;
double bigf1;
char letter;
};
共用体变量的地址和它的各成员的地址都是同一地址。
变量:
#include<stdio.h>
union INFO
{
int a;
int b;
int c;
};
int main()
{
union INFO A;
A.a=1;
A.b=2;
A.c=3;
printf("a:%d\n",A.a);
printf("b:%d\n",A.b);
printf("c:%d\n",A.c);
return 0;
}
C 语言的语法规定,结构体内变量的存储空间是连续的。
struct student{
int id;
struct teacher{
int age;
}th;
};
int main(){
struct strudent stu;
stu.th.age=18;就可以访问结构体的结构体。
return 0;
}
结构体嵌套
在一个结构中包含另一个结构.
struct names{
char first[LEN];
char last[LEN];
};
struct guy{
struct names handle;
char favfood[LEN];
char job[LEN];
float income;
};
int main(void)
struct guy fellow={
{ "ewen","villard"},
"griid al",
"coach",
68112.00
}; 初始化
//访问结构体成员
fellow.handle.first
结构体指针成员变量引用方法是通过“->”符号实现
struct U_TYPE *usart3;//定义结构体指针变量 usart1;
比如要访问 usart3 结构体指针指向的结构体的成员变量 BaudRate,方法是:
Usart3->BaudRate;
结构体指针的输出
struct Animal {
char *name; //指针成员
int age;
char info[200]; //字符数组
struct Animal *nextAnimal; //指针成员
};
Animal *nextAnimal 在没有初始化的情况下输出程序编译没有问题,运行报错
// 初始化指针变量
animal.nextAnimal = (struct Animal *)malloc(sizeof(struct Animal));
printf("animal.nextAnimal->name: %s, age: %i, info: %s\n", animal.nextAnimal->name, animal.nextAnimal->age, animal.nextAnimal->info);
再次编译重新运行,还是报错。还需要初始化 animal.nextAnimal->name 这个变量
// 初始化 name 变量
animal.nextAnimal->name = "cat";
也就是说结构体指针变量有些会给默认值,有些又不会给,所以都要初始化指针变量。
#define usart1 (USART_TypeDef *) UART1_BASE 本身后面是个地址,相当于定义了一个指针变量,指向串口的结构体地址。
#define代表的是等价替换,放过去就好了!所以usart1的类型就是USART_TypeDef *。
经常用结构体数组来 表示具有相同数据结构的一个群体,如一个班的学生档案
横行
struct address
{
char name[30];
/*姓名,字符数组作为结构体中的成员 */
char street[40]; /*街道*/
unsigned long tel; /*电话,无符号长整型作为结构体中的成员 */
unsigned long zip; /*邮政编码*/
}
竖列
student[3]={
{"Zhang","Road NO.1",111111,4444},
{"Wang"," Road NO.2",222222,5555},
{"Li"," Road NO.3",333333,6666}
}
函数指针是指向函数的指针变量。
函数指针可以像一般函数一样,用于调用函数、传递参数。
typedef int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型
调用实例
#include <stdio.h>
int max(int x, int y)
{
return x > y ? x : y;
}
int main(void)
{
/* p 是函数指针 */
int (* p)(int, int) = & max; // &可以省略
int a, b, c, d;
printf("请输入三个数字:");
scanf("%d %d %d", & a, & b, & c);
/* 与直接调用函数等价,d = max(max(a, b), c) */
d = p(p(a, b), c);
printf("最大的数字是: %d\n", d);
return 0;
}
函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。
调用过程:
populate_array() 函数定义了三个参数,其中第三个参数是函数的指针,通过该函数来设置数组的值。
我们定义了回调函数 getNextRandomValue(),它返回一个随机值,它作为一个函数指针传递给 populate_array() 函数。
#include <stdlib.h>
#include <stdio.h>
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
for (size_t i=0; i<arraySize; i++)
array[i] = getNextValue();
}
// 获取随机值
int getNextRandomValue(void)
{
return rand();
}
int main(void)
{
int myarray[10];
/* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于传入此参数时传入了 int , 而不是函数指针*/
populate_array(myarray, 10, getNextRandomValue);
for(int i = 0; i < 10; i++) {
printf("%d ", myarray[i]);
}
printf("\n");
return 0;
}
地址就是可以唯一标识某一点的一个编号。
#include<stdio.h>
int main()
{
int i;
int a[10]={1,2,3,4,5,6,7,8,9,0};
char b[10]={'c','l','a','n','g','u','a','g','e'};
for(i=0;i<10;i++)
{
printf("int Address:0x%x,Value:%d\n",&a[i],a[i]);
}
printf("\n");
for(i=0;i<10;i++)
{
printf("char Address:0x%x,Value :%c\n",&b[i],b[i]);
}
return 0;
}
int *p=a;
指针放的就是地址,p即为’那儿’,*p则是那儿的值。
指针一定存储的是变量的小端的首地址。
C语言编译器对指针这个特殊的盒子
先将a那儿的值*a给了p那儿,然后a那儿地址加1,p那儿地址加1。
int a[10]; /*定义 a 为包含 10 个整型数据的数组*/
int *p; /*定义 p 为指向整型变量的指针*/
p=&a[0]; /*把 a[0]元素的地址赋给指针变量 p*/
p=a; /*等价于 p=&a[0]; */
int *p=a; /*等价于 int *p=&a[0]; */
p+i(或a+i)就是数组元素 a[i]的地址,* (p+i)( 或* (a+i) )就是 a[i]的值
采用*(a+i)或*(p+i)形式,用间接访问的方法来访问数组元素,其中 a 是数组名,p 是 指向数组的指针变量,其初值 p=a
char *str = "www.dotcpp.com" ;
这是对字符指针进行初始化。此时,字符指针指向一个字符串常量的首地址。 等价于
char string[ ] = "Welcome to dotcpp.com";
string 是数组名,代表字符数组的首地址。
字符串指针和字符串数组两种方式都可以访问字符串
但它们有着本质的区别:
#include<stdio.h>
int main()
{
char *str = "www.dotcpp.com";
char string[]="Welcome to dotcpp.com";
str[0]='C'; //试图修改str指向的常量区的字符串内容
return 0;
}
常量的定义
const int a=100;a就不能变了
编译器翻译成了只读的变量。但是它还是通过地址可以变的
内存中还是一个变量,在可读可写的区域,所以还是可以变的。
标T的是推荐的写法,
第一种char和const都是修饰指针*的,所以不分前后。p地址可以变,p里面的内容就不能操作了
第二种const是修饰p的,不分前后。地址不可变了,内容可以变。相对硬件资源就很合适这种状况。
第三种,啥都不能变,如ROM
防止优化
别名,typedef 定义新的类型来代替已有的类型。
typedef int INTEGER; /*指定用 INTEGER 代表 int 类型*/
typedef float REAL; /*指定用 REAL 代表 float 类型*/
int i, j; /*与 INTEGER i, j;*/
float pi; /*与 REAL pi;*/
左移:从二进制的角度看现象,就是左移几位就在右边添几个0。
从逻辑上来讲左移n位就是乘以2的n次方了。
右移:从二进制角度看,则是在左边填0,右边去除移动的位数的位 。
(如遇到时1>>1, 便是0.);
(注意:如果操作数是一个正数,那么左边的空缺位使用0补,如果操作数是一个负数,那么左边的空缺位使用1补)
从逻辑上来讲右移就是除以2的n次方;
按位逻辑运算符有四个,皆用于整型数据,包括char。
a. ~按位取反
b. &按位与 与:乘
c. |按位或 或:加
d. ^按位异或 异或:加-除
磁极,相同为零,不用为1.
乘2的n次幂
number<<n
若number为非负,则用number除以2的n次幂
number>>n
#include <stdio.h>
#define u8 short int //2字节 8位
#define u16 long int //4字节 16位
#define u32 long long //8字节 32位
int main()
{//1. 从u8 -->>>u16的转化
u8 a[2] = { 0xcd, 0xe2 };
u16 b;
//分析:
b=a[0]; //得到高位
b=b<<8;//向左移8位。结果:0xcd00
b=b|a[1];//或上a[1]=0xe2; 即0xcde2
//打印结果:a[1]=0xe2 a[0]0xcd
// b=0xcde2
// 2. 从u16 -->>>u8的转化
u8 a[2] = { 0 };
u16 b = 0xc32d;
//分析:
a[1] = b >> 8;//b向右移8位,0x3c;
a[0] = b & 0xff;//b与上oxff,得到0x2d
//打印结果:a[1]=0xc3 a[0]= 0x2d
// b=0xc32d
// 3.调换高低位的位置 需要先将u16--->>u8 ,调换后,u8--->16
u8 a[2] = { 0xab, 0xcd };
u16 b;
b= a[0] | a[1] << 8;//a[1]左移8位0xcd00,再或上a[0]0x00ab(为方便理解)这样看;
//结果:
//a[1]=0xcd a[0]=0xab
//b=0xcdab
// 4. 从u32 -->>>u8的转化
int i = 4;
u32 a=0x1234abcd;
u8 b[4];
b[0] = a >> 24;
b[1] = (a >> 16)&0xff;
b[2] =( a >> 8)&0xff;
b[3] = a &0xff ;
while (i--)
{
printf("0x%02x\n", b[i]);
}
//5. 从u8 -->>>u32的转化
int i = 4; //8-->32
u32 a;
u8 b[4] = {0xcf,0xb3,0x43,0xbb};
//此顺序不能改
a = (long long)b[3] << 32;//高位
a = b[0] << 24|b[3];//最低位
a =a|(( b[1]&0x00ff)<<16);//低位
a = a | ((b[2] & 0x0000ff) <<8);//次高位
printf("0x%02x\n", a);
// 6.从u32 -->>>u16的转化
int i = 2;
u32 a = 0x1234abcd;
u16 b[2];
b[0] =( a >>16);
b[1] = a &0xffff;
while (i--)
{
printf("0x%02x\n", b[i]);
}
//7. 从u16 -->>>u32的转化
int i = 2;
u32 a ;
u16 b[2] = {0xa1cb,0x2bea};
a=b[0]<<16;
a = b[1] |( b[0]<<16);
printf("0x%02x\n", a);
getchar();
}
//8.int --->>bit(二进制)的转化
int int2_bit(int a)
{
int i,bit,size=sizeof(a)*8;
for(i=0;i<size;i++)
{
bit=a&(1<<(size-i-1));
if(bit==1)
pirntf("1");
else
printf("0");
if(i%4==3)
printf(" ");
}
}
//9.bit-->int的转化
int power(int x)//x的指数幂
{
int i=1,t=2;
if(x ==0)
t=1;
for(;i<x;i++)
{
t=2*t;
}
return t;
}
int bit_int(char *x)
{ int i=0,n=7,tmp,sum;
for(;i<8;i++,n--)
{
tmp=x[i]*pwer(n);
sum+=tmp;
}
return sum;
}
//记录一个整数的二进制有多少个1
int ch_sum(int x)
{
int tmp,is=0;
for(int i=0;i<sizeof(x)*8;i++)
{
tmp=1<<i;
if(x&tmp)
{
is++;
}
}
return is;
}
//取补码
int8_t otcp( uint8_t a)
{
uint8_t b=~a,t=1<<7;
if(a<0)
{
b|=t;
}
return b;
}
//将u8转成16进制类行改
u8 a[2] = { 0x6, 0xc };
u16 b;
//分析:
b=a[0]; //得到高位
b=b<<8;//向左移8位。结果:0x600
b=b|(a[1]<<4);//左移4 ,得到到0xc60
b=b>>4;//去除0 得到到0xc6
printf("0x%0x ",b);
//剔除空格数据间的空格符
u8 d_k(u8 *databuf)
{ int of=0;
for(int i=0;i<sizeof(datebuf);i++)
{
char c=databuf[i];
if(c==' ')
{
++of;
}
else
{
rxbuf[i-of]=c;
}
}
return rxbuf;
}
//ascii码转16进制
char c_buf='a';
int b='a'-'0' ;
printf("0x%0x",b);//hex 输出
printf("\n%d",b);//十进制 输出
printf("\n%d",c_buf);//字符 以十进制 输出
位带操作,指的就是单独对一个bit位进行读和写。
STM32 中,有两个地方实现了位带,一个是SRAM区的最低 1MB 空间,令一个是外设区最低 1MB 空间。这两个 1MB 的空间除了可以像正常的 RAM 一样操作外,他们还有自己的位带别名区,位带别名区把这 1MB 的空间的每一个位膨胀成一个 32 位的字,当访问位带别名区的这些字时,就可以达到访问位带区某个比特位的目的。
外设外带区的地址为:0X40000000~0X40100000,大小为 1MB,这 1MB 的大小在 103系列大/中/小容量型号的单片机中包含了片上外设的全部寄存器,这些寄存器的地址为:0X40000000~0X40029FFF 。 外 设 位 带 区 经 过 膨 胀 后 的 位 带 别 名 区 地 址 为 :0X42000000~0X43FFFFFF,这个地址仍然在 CM3 片上外设的地址空间中。在 103 系列大/中小容量型号的单片机里面,0X40030000~0X4FFFFFFF 属于保留地址,膨胀后的 32MB位带别名区刚好就落到这个地址范围内,不会跟片上外设的其他寄存器地址重合。
STM32 的全部寄存器都可以通过访问位带别名区的方式来达到访问原始寄存器比特位的效果
对于片上外设位带区的某个比特,记它所在字节的地址为 A,位序号为 n(0<=n<=7),则该比特在别名区的地址为:
AliasAddr= =0x42000000+ (A-0x40000000)84 +n*4;
0X42000000 是外设位带别名区的起始地址,0x40000000 是外设位带区的起始地址,(A-0x40000000)表示该比特前面有多少个字节,一个字节有 8 位,所以8,一个位膨胀后是 4 个字节,所以4,n 表示该比特在 A 地址的序号,因为一个位经过膨胀后是四个字节,所以也*4。
SRAM 的位带区的地址为:0X2000 0000~X2010 0000,大小为 1MB,经过膨胀后的位带别名区地址为:0X2200 0000~0X23FF FFFF,大小为 32MB。操作 SRAM 的比特位这个用得很少。
SRAM位带别名区地址:
对于 SRAM 位带区的某个比特,记它所在字节的地址为 A,位序号为 n(0<=n<=7),则该比特在别名区的地址为:
AliasAddr= =0x22000000+ (A-0x20000000)84 +n*4;
公式分析同上。
以下运算需要1584字节的代码量
t
e
m
p
=
1.43
−
t
e
m
p
0.0043
+
25
temp = \frac{1.43-temp}{0.0043}+25
temp=0.00431.43−temp+25
以下运算需要40个字节的代码量
t
e
m
p
=
1.43
f
−
t
e
m
p
0.0043
f
+
25
temp = \frac{1.43f-temp}{0.0043f}+25
temp=0.0043f1.43f−temp+25
加f代表指定单精度,不加f会按照双精度处理。
字符型的范围是0-65535,没有负的char
char16位,可以存储一个中文。Java采用Unicode编码。
定义一个字符串
const char add[] = “你好”。
浮点数后面加f或F是float型
浮点数赋值 float a=(float)1.1
浮点数后面加d或D是double型。
œ Double 是分数,或实数。
科学记数法5.3E12也是double型。
默认整数是int,可以赋值给float,自动进行类型转换。
默认小数点的类型是double型。
数据类型转换
○ 布尔类型不能进行数据转换
○ ()
○ 防止数据溢出
○ 并不是四舍五入,所有的小数位都会舍弃
运算中会自动转换为数据范围大的那种
byte、char、short都可以进行加减乘除的数学运算
○ 运算时首先翻译为int类型,再进行计算
○ int ()=byte+byte;右边变int+int,因此左边要用int接收
○ 对于byte、char、char,如果右侧没超过左侧范围,编译器自动补上强制,超过范围报错。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。