概述:成长笔记#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 环境搭建
二、C语言关键字
2.1 数据类型char、short、int
2.1.1 数据类型
1、基础类型
2、u8,u16,u32和int区别
int | 基本整数类型 | 4字节 | 0-0xffff ffff |
---|---|---|---|
u8 | unsigned char | 1字节 | 0-0xff |
u16 | unsigned short | 2字节 | 0-0xffff |
u32 | unsigned long | 4字节 | 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 时间戳是从1970年1月1日(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.c | static 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(“初始化中”); break;
case SYSTEM_CONNECTING :
printf(“连接中”); break;
case 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
三、指针、回调函数、数据拷贝
【挖坑自填】
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);
}
四、栈、队列、链表、数组【】
【挖坑自填】