嵌入式软件工程师—成长笔记#05

概述:成长笔记#05 C语言加强

first of all: 实际工作中、和刷题不一样。不会用到很多指针和复杂语法

一、C语言开发基础

1.1 源文件→可执行文件过程

在这里插入图片描述

1.2 软件下载

在这里插入图片描述

MingW | GCC【编译链工具】

http://www.mingw.org

MingW 是将C语言编译器 GCC 移植到了 Windows 平台下

MinGW安装与环境配置教程 注意要配置为用户环境变量 而非 系统环境变量

vscode | Source Insight【阅读编写代码 IDE】

https://code.visualstudio.com

虚拟机 【搭建Linux系统ubuntu】

https://www.vmware.com

1.3 环境搭建

GCC环境搭建

二、C语言关键字

2.1 数据类型char、short、int

2.1.1 数据类型

1、基础类型
在这里插入图片描述
2、u8,u16,u32和int区别

int基本整数类型4字节0-0xffff ffff
u8unsigned char1字节0-0xff
u16unsigned short2字节0-0xffff
u32unsigned long4字节0-0xffff ffff
//在stdint.h中:

typedef unsigned char uint8_t;

typedef unsigned short uint16_t;

typedef unsigned long uint32_t;

//在stm32f10x.h 中:

typedef uint32_t u32;

typedef uint16_t u16;

typedef uint8_t u8;
2.1.2 数据转换

1、float ->u32 ->u8 【高精度 转 低精度】

(1) float 转 u32

union联合体,联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)

由于float为4字节32位,long也为4字节32位。巧用union联合体

typedef union
{
    float a;
    uint32_t b;
}Data;

(2) u32 转 u8

左移运算符(<<)
-定义:将一个运算对象的各二进制位全部左移若干位【左边的二进制位丢弃,右边补0】
-例如:a=1010 1110,a = a<< 2 将a的二进制位左移2位、右补0,即得a=1011 1000。
-若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。

右移运算符(>>)
-定义:将一个数的各二进制位全部右移若干位 【正数左补0,负数左补1,右边丢弃】
-例如:a=a>>2 将a的二进制位右移2位,左补0 或者 左补1得看被移数是正还是负。
-操作数每右移一位,相当于该数除以2。

int main()
{
    uint8_t tmp_buf[4];    
    Data A;
    A= 1122801711; //十六进制为:42 EC 98 2F 【32位】
    
    // WiFi传输   
    tmp_buf[0]=(A>>16)>>8;   // 取高16位 中的高8位----42 【8位】
    tmp_buf[1]=(A>>16)&0xff;  //  取高16位 中的低8位----EC
    tmp_buf[2]=(A&0xffff)>>8;   // 取低16位 中的高8位----98
    tmp_buf[3]=(A&0xffff)&0xff;   // 取低16 位中的低8位----2F
    
    printf("%x\n",A);
    for(int i=0;i<4;++i){
        printf("%x",tmp_buf[i]);
        cout << endl;
    }
    return 0;
}

2、u8 ->u32 ->float【低精度 转 高精度】

高8低8 合并为 16; 高16低16 合并为 32

方法1:(tmp_buf[0]<<24)+(tmp_buf[1]<<16)+(tmp_buf[2]<<8)+tmp_buf[3] 【推荐】
方法2:(tmp_buf[0]*pow(2,24)+tmp_buf[1]*pow(2,16)+tmp_buf[2]*pow(2,8)+tmp_buf[3]) 【不推荐】

int main()
{
    Data res;
    //B= (tmp_buf[0]*pow(2,24)+tmp_buf[1]*pow(2,16)+tmp_buf[2]*pow(2,8)+tmp_buf[3]);
    B= (tmp_buf[0]<<24)+(tmp_buf[1]<<16)+(tmp_buf[2]<<8)+tmp_buf[3];
    printf("%x\n %f",B,B);   // %f 浮点数  %x 16进制
    cout << endl;
    return 0;
}
2.1.3 数据类型转化实例

(1)网关mac地址怎么上报?| 小数点怎么上报?【巧用数组再组合】

C语言中字符串的本质:实际上是以“\0”结尾的字符数组
"xxxx"是一个常量数组【const char[]或者说const char *const】不可被修改。
char *a = “xxxx” 是将a置为"xxxx"的首地址,不改变其不可修改的性质。
char a[] = “xxxx” 是把"xxxx"拷贝给a[5],相当于char a[5];strcpy(a, “xxxx”);

float temp=37.5; 	//原始数据
char *mac="192.168.31.84";  //原始数据  //char mac[]="192.168.31.84"; 

char mac_array[4]={192,168,31,84};  //发送数组出去,远端再重组
char temp_array[2]={37,5}; //发送数组出去,远端再重组

(2)Unix时间戳上报?【转化 u8 TO u32】

Unix 时间戳是从197011日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。从而推算出当前时间。

服务器会把时间戳的16进制下发。收到4个16进制数。

unsigned int time=1655730681;
unsigned char time_buffer[]={0x62,0xB0,0x71,0xF9};

unsigned int u8Tou32(unsigned char *buffer)
{
	return (buffer[0]<<24)+(buffer[1]<<16)+(buffer[2]<<8)+buffer[3] 
}

void main()
{
	time1=u8Tou32(time_buffer);
	printf("%ld \n",time1);
}

2.2 流程控制 if、switch

2.2.1 形式

switch的每个case后一定要加break

switch (day)
{
case 1:
case 2:
	xxxxxx;
	break;
default:
	xxxxxx;
	break;
}

if(表达式)
{
	语句1;
}
else if(表达式)
{
	语句2;
}
else
{
	语句3}
2.2.2 实例

(1)智能音箱:网络状态判断

//用switch
typedef enum{
	NET_INIT=0,
	NET_CONNECTING,
	NET_CONNECT_SUCCESS,
	NET_CONNECT_FALL,	
	NET_CONNECT_ERROR,
}NET_STATUS;

void main()
{
	NET_STATUS net_status;
	switch(net_status) 
	{
	case NET_INIT:
			break;
	case NET_CONNECTING:
			break;
	case NET_CONNECT_SUCCESS:		
			break;
	case NET_CONNECT_FALL:		
			break;
	case NET_CONNECT_ERROR:		
			break;
	default:
			break;
	}
}

(2)传感器开关灯

// 用if判断即可
if(senser==1)
{
	flag_led=1;
}

2.3 循环控制 for、while

建议优先考虑for循环,然后是while循环

2.3.1 形式
   while(判断条件语句) 
   {
		循环体语句;
	}
	//建议优先考虑for循环,然后是while循环 ,最后是do…while循环
	for(初始化表达式语句;判断条件语句;控制条件语句) 
	{
		循环体语句;
	}
2.3.2 实例

(1)自动售货机:判断是否存在待处理订单

//循环判断一个量用while
void main()
{
	int status;
	while(status==true)
	{
		printf("您有待处理订单");
	}
	
	for(;status==true;)
	{
		printf("您有待处理订单");  //奇怪
	}
}

(2)智能音箱:打印搜索到的WiFi热点信息

//操作数组用for
void main()
{
	int wifi_array[20]={1,2,3,4,5};
	int index=0;
	
	while(index<sizeof(wifi_array))     //奇怪
	{
		printf("%d \n",wifi_array[index]);
		index++;
	}

	for(index=0;index<sizeof(wifi_array);index++)
	{
		printf("%d \n",wifi_array[index]);
	}
	
}

(3)自动售货机:判断当前所以订单金额

//操作数组用for
void main()
{
	int sum=0;
	int order_array[20];
	for(index=0;index<sizeof(order_array);index++)
	{
		sum=sum+order_array[index];
	}
	printf("%d \n",sum);
}

2.4 数据类型 static、const、define

2.4.1 static

(1)作用
①限制函数作用域:【修饰函数】
相当于面向对象的private

文件静态函数函数
A.cstatic void func()void testc()
B.c不可调用可以调用
用testc函数内部调用func,间接调用从而保证安全
//test.h
#ifndef _TEST_H_   //防止重定义
#define _TEST_H_
	void test(void);
#endif
//test.c
#include <stdio.h>
#include "test.h"

static void func() 
{
	printf("func");
}
void testc()
{
	func();
}
//mian.c
void main()
{
	testc();  
}

②管理变量生命周期:【修饰变量】
局部变量(函数内声明)每次调用会被重置。
静态局部变量,只在首次调用时声明,则不会被重置。

//test.c
void func()
{
	int num=0;
	static int static_num=0;
	num++;
	static_num++;
	printf("%d \n",num);
	printf("%d \n",static_num);
}
//mian.c
void main()
{
	func();   //num=1  static_num=1
	func();   //num=1  static_num=2
	func();   //num=1  static_num=3
	func();   //num=1  static_num=4
}

(2)实例

①智能网关项目:zigbee 模块的同事都需要一个“日志打印函数”,设计一个日志打印函数给他们调用
限制函数作用域: 开放一个test() 在test() 内部调用“日志打印函数”

②点餐屏:统计打印机油墨损耗(打印次数)
管理变量生命周期:令print_num为static,每次调用打印函数,print_num++

2.4.1 const

作用
①定义常量变量,保证不再被修改

const int val=20;
val=30//报错

②保护函数传参时,传入参数不在函数内被修改

函数参数传递三种方式【传值,传址(引用传递),传址(指针传递)】
传值:将实参的拷贝给函数或方法,在函数内对形参进行操作,操作的对象是实参的拷贝,对实参本身没有影响
传址:将实参的地址传递给函数,在函数内对形参进行操作等同于对实参进行相同的操作

传值指针传递引用传递
函数原型void switch(int x,int y)void switch(int *x,int *y){ temp=*x; *x=*y; *y=temp }void switch(int &x,int &y)
调用形式void switch(a,b)void switch(&a,&b)void switch(a,b)
意义a,b不交换传入地址,a,b交换x,y相当于a,b别名

使用【引用传递参数】或【按地址传递参数】给一个函数, 在这个函数里这个参数的值若被修改,则函数外部传进来的变量的值也发生改变,若想保护传进来的变量不被修改,可以使用const保护。

void fun1(const int &val)  //加const保护
 { 
	val = 10; //出错 
 }
 
void fun2(int &val)   //没加const保护
{ 
	val = 10; //没有出错 
}

③修饰常对象: c++ 继承的时候可以防止修改

2.4.1 define

(1)作用
①流程控制define
.h文件:为了防止文件多的时候,多个头文件 重复定义。

#ifndef _EM_CONFIG_H_ 
#define _EM_CONFIG_H_ 
	void test();
#endif

.c文件: 控制版本流程

#ifdef OPEN_LOG_1 
	AAAAA 
#elif OPEN_LOG_2 
	BBBBB
#endif

②常用define—注意所有数值、变量都带上( )

#define OPEN_LOG 
#define EM_LOG_LEVEL
#define APPID "123456"
#define PI 3.14
#define MAX_LEN (100+1)
#define MAX(a,b) ((a)>(b)?(a):(b))
#define SWAP(a,b) do{\
	int t=0;\
	t=a;\
	a=b;\
	b= t;\
while(0)

//将一个字母转换为大写
#define UPCASE(c) (((c))='a'&&(c)(='z')?((c)-0x20):(c))
//判断字符是不是10进值的数字
#define DECCHK(c) ((c)>='0'&&(c)<='9')
//防止溢出的一个方法
#define INC_SAT(val) (val = ((val)+1 > (val))?(val)+1:(val))
//返回数组元素的个数
#define ARR_SIZE(a) (sizeof((a))/sizeof((a[0])))

#define CONNECT(a,b) a##b 
#define TOCHAR(a) @#a 
#define TOSTRING(a) #a

③与typedef区别

typedef 为:替换与别名

typedef unsigned char uint8; 
typedef unsigned short uint16; 
typedef unsigned int uint32;

易混:

typedef char * PCHAR1; //PCHAR1 为 char * 别名
#define PCHAR2 char *  //char * 用 PCHAR2 代写

PCHAR1 c1, c2; //c1、c2 都为char *
PCHAR2 c3, c4;//c3是char *,而c4是char

(2)实例
①智能手环:如何在一份代码中定义测试与发布版本

#define VERSION_PRE 1
// #define VERSION_GOLD 1

void main()
{
#ifdef VERSION_PRE 
	printf("这是测试版本");
#elif VERSION_GOLD 
	printf("这是正式版本");
#endif
}

②智能音箱:定义一个日志开关功能

#define OPEN_LOG 

void main()
{
#ifdef OPEN_LOG 
	printf("打印日志:------");
#ifdef VERSION_PRE 
	printf("这是测试版本");
#elif VERSION_GOLD 
	printf("这是正式版本");
#endif
#endif
}

2.5 数据结构 enum、struct

2.5.1 enum枚举类型

(1)作用

typedef enum
{ 
	SYSTEM_INIT = 0, 
	SYSTEM_CONNECTING, //自动增加为1
	SYSTEM_xxxxx  //自动增加为2
}SYSTEM_STATUS;

SYSTEM_STATUS status; //status只能取以上几个值

(2)实例
智能手环:网络状态表示(初始化、蓝牙连接中、连接成功、连接失败)

typedef enum
{ 
	SYSTEM_INIT = 0, 
	SYSTEM_CONNECTING, //自动增加为1
	SYSTEM_OK,  //自动增加为2
	SYSTEM_ERROR  //自动增加为3
}SYSTEM_STATUS;

void main()
{
	SYSTEM_STATUS wifi_status=SYSTEM_INIT;
	switch(wifi_status) 
	{
	case SYSTEM_INIT :
		printf(“初始化中”); breakcase SYSTEM_CONNECTING :	
		printf(“连接中”); breakcase SYSTEM_OK  :	
		printf(“连接成功”); break}
}
2.5.2 struct

(1)作用
在这里插入图片描述

①结构体嵌套结构体

typedef struct
{
int num; 
int time; 
int money; 
}T_Pay; 

typedef struct
{ 
int system_status; 
int net_status; 
int motor_status[4]; 
int pay_status; 
T_Pay m_Pay; 
//void *(statusCallbackfunc)(); 
}T_Device; 

T_Device g_Device;

void main()
{
	g_Device.m_Pay.num=6;
	printf("%d \n",g_Device.m_Pay.num);
}

②结构体指针
结构体指针的访问变量方法
way1: p->结构体成员
way2: (*p).结构体成员

//结构体的定义
struct student
{
	int num;
	char name[20];
	char sex;
};
//指针访问
struct student *p;//定义结构体指针
p=&st1;
printf("%d %s %c\n",p->num,p->name,p->sex);
//指针访问
struct student *p;//定义结构体指针
p=&st1;
printf("%d %s %c\n", (*p).num, (*p).name, (*p).sex);

(2)实例
售货机:设备所有资源管理(设备状态、网络状态、电机状态、传感器状态、 订单状态等等)

typedef struct
{ 
int system_status; 
int net_status; 
int motor_status[4]; 
int pay_status; 
T_Pay m_Pay; 
//void *(statusCallbackfunc)(); 
}T_Device; 

T_Device g_Device1;
T_Device g_Device2;
T_Device g_Device3;

2.6 inline内联、exterend全局变量【】

2.6.1 inline

如何避免这些问题?
1.在头文件中声明函数,在.c文件中定义函数

2.使用其他文件中的函数时,包含对应头文件

3.为了避免"multiple definition",多个.c文件使用的inline函数,定义为static inline

三、指针、回调函数、数据拷贝

【挖坑自填】

数据拷贝1
数据拷贝2

3.1 指针

(1)作用

变量为了表示数据而生, 指针为了传递数据为生。
指针就是一个变量,不过放的是别的变量地址

在这里插入图片描述

(2)实例

智能音箱:音频数据处理

传值指针传递引用传递
函数原型void switch(int x,int y)void switch(int *x,int *y){ temp=*x; *x=*y; *y=temp }void switch(int &x,int &y)
调用形式void switch(a,b)void switch(&a,&b)void switch(a,b)
意义a,b不交换传入地址,a,b交换x,y相当于a,b别名
char pcm_data_char; 
char pcm_data_arrary[4096] 

void pcmSpeex(char *data)
{ 
  //具体的算法:*data=*data*x*y
}
pcmSpeex(&pcm_data_char); 
pcmSpeex(pcm_data_arrary); //传入数组的首地址

3.2 回调函数

【补充】函数指针

指针就是用来传递参数的,否则瞎指来指去没意思

①意义:函数的地址为:函数存放位置的首地址。如果把 函数地址 做为参数传递给函数,则可以在函数中灵活调用其他函数。
②使用函数指针步骤

声明函数指针→令函数指针 指向 函数地址→通过函数指针调用函数
要提供函数类型(返回值+参数列表)函数名和形参不是函数名 直接赋值给 函数指针c/c++不同
int (*pf1)(int,char);pf1=func;pf1(1,5);c++
bool (*pf2)(int);(*pf1)(1,5);c
void func(int x,int y)
{
	printf("%d,%d \n",x,y);
}
void main()
{
	func(5,6); //直接调用

	void (*pf1)(int,int);
	pf1=func;
	(*pf1)(5,6); //指针调用
}

(1)使用
①理解
假设有一个表白神器函数(给出了表白准备工作,表白收尾工作),只需要每个人给出自己的个性化表白函数。在表白神器函数调用个性化表白函数

个性化表白函数即为回调函数。表白神器函数为调用者函数。
这样就可把 回调函数 的代码嵌入到 调用者函数 中。

调用者函数回调函数
给出实现的流程框架给出具体的实施办法
只确定 回调函数 种类,不管具体功能
void A个性化表白函数(void)
{
	printf("A唱跳"); 
}
void B个性化表白函数(void)
{ 
	printf("B rap"); 
}
void C个性化表白函数(void)
{ 
	printf("C打篮球"); 
}

void 表白神器函数(void (*Pf)())
{  
	printf("表白准备");
	(*pf)();
	printf("表白结束");
}

void mian()
{
	表白神器函数(A个性化表白函数);
	表白神器函数(B个性化表白函数);
	表白神器函数(C个性化表白函数);
}

②给回调函数传递参数(即回调函数有参数时)

方法一:内部传参,调用者函数 内部赋值
void callback1(int a)
{
	printf("%d \n",a);
}

void show(void (*pf)(int))
{
	int b=3;
	(*pf)(b);
}

show(callback1);

方法二:外部传参,调用者函数 多定义一个变量
void callback1(int a)
{
	printf("%d \n",a);
}

void show(void (*pf)(int),int b)
{
	(*pf)(b);
}

show(callback1,3)

(2)实例
1、送餐机器人:底盘移动到目标位置后,如何通知应用程序?
2、智能音箱:网络状态连接后,如何通知应用程序?

–传统方式:
1、开放变量,让别人直接获取 2、通过getStatus()类似的函数定时获取

–建议方式:
1、回调方式实现

void addCallbackFunc( void (*statusChange)(int status))
{ 
 //todo
}

3.3 数据拷贝【】

(1)作用

-malloc 裸机不建议使用,带操作系统的可以
-freertos 实现类内存管理池
-linux 有内存管理的模块(实际访问映射表、不直接访问硬件地址)

1、四种拷贝函数
这篇写的非常详细~
①memcpy()

原型void *memcpy ( void * dest, const void * src, size_t num );
功能memcpy()会复制 src 所指的内存内容的前 num 个字节到 dest所指的内存地址上;【不关心被复制的数据类型,只是逐字节地进行复制,这给函数的使用带来了很大的灵活性,可以面向任何数据类型进行复制】
注意①dest 指针要分配足够的空间,也即大于等于 num字节的空间 ② dest 和 src所指的内存空间不能重叠【如果发生了重叠,使用 memmove() 会更加安全】③memcpy() 会完整的复制 num个字节,不会因为遇到“\0”而结束【与 strcpy() 不同】
返回值返回指向 dest 的指针。注意返回的指针类型是void【使用时一般要进行强制类型转换】
#include  <string,h>
#include  <stdio.h>
#include  <stdlib.h>
 
#define N (15)
 
int main()
{
    char *p1 = "qing zhu yi!";
    char *p2 = (char *)malloc(sizeof(char) * N);
    char *p3 = (char *)memcpy(p2, p1, N);
    printf("p2 = %s\np3 = %s\n", p2, p3);
 
    free(p2);
    p2 = NULL;
    p3 = NULL;
    system("pause");
    return 0;
}
运行结果: p2 = qing zhu yi!    p3 = qing zhu yi!
 1) 代码首先定义p1,p2,p3三个指针,但略有不同,p1指向一个字符串字面值,给p2分配了10个字节的内存空间;

 2) 指针p3通过函数memcpy直接指向了指针p2所指向的内存,也就是说指针p2、p3指向了同一块内存。然后打印           p2,p3指向的内存值,结果是相同的;

 3) 最后按照好的习惯释放p2,并把p3也置为NULL是为了防止再次访问p3指向的内存,导致野指针的发生;

②memmove()

原型void *memmove(void *dest, const void *src, size_t num);
功能
注意
返回值

③strcpy()

原型char*strcpy(char *dest, const char *src);
功能
注意
返回值

④strcpy()

原型char *strncpy(char *dest, const char *src, size_t n);
功能
注意
返回值

2、内存分配malloc与free

malloc/free和new/delete的区别:
malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
maloc/free无法对于非内部数据类型的对象操作(即 必须用new/delete 一个类的对象)

在堆区申请空间并返回空间地址
int *p =(int*)malloc(4);  // 申请四个字节的空间,且该空间为一个int型变量空间,而不是别的。

int *p;
p=(int*)malloc(4);
释放某个地址空间的,被释放的地址的值不是0,而是某个初始值。
int *p = (int*)malloc(4);
*p=12;

free(p);  //不能释放栈区空间,会出现崩溃

3、工业级拷贝实现

在这里插入图片描述
(2)实例
1、智能音箱:配网时,获取到网络列表后,进行过滤,如何写过滤函数?

void handleWifiList(const char *plistdata,int len)
{ 
	char *mdata = (char *)malloc(len);
 	memcpy(mdata,plistdata,len); 
 	free(mdata); 
 }

四、栈、队列、链表、数组【】

【挖坑自填】
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五、项目实战【】

5.1.Google测试框架

5.2.Zlog日志框架

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值