• [技术干货] 单片机与C语言编程基础_字符串知识点
    C语言字符串全方位练习,涉及知识点:字符串解析、大小写判断、字符串插入、字符串删除、字符串排序、字符串转整数、字符串转浮点数、时间转换、GPS数据解析等等。1. 计算空格、大小写字母从键盘上输入一个字符串, 计算字符串里有多少个空格、小写字母、大写字母、数字。#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件int main(int argc,char **argv){ int len=0; int i; char str[100]; int cnt[5]={0}; //初始化赋值 //scanf("%s",str); //从键盘上录入字符串,字符串结尾: '\0' //gets(str); //从键盘上录入字符串 fgets(str,100,stdin); //从键盘上录入字符串 (标准输入) //空格、小写字母、大写字母、数字 其他数据 /*1. 计算字符串的长度*/ while(str[len]!='\0')len++; printf("len1=%d\n",len); printf("len2=%d\n",strlen(str)); //计算字符串长度 /*2. 处理字符串*/ for(i=0;i<len;i++) { if(str[i]==' ')cnt[0]++; else if(str[i]>='a'&&str[i]<='z')cnt[1]++; else if(str[i]>='A'&&str[i]<='Z')cnt[2]++; else if(str[i]>='0'&&str[i]<='9')cnt[3]++; else cnt[4]++; } /*3. 打印结果*/ printf("空格:%d\n",cnt[0]); printf("小写:%d\n",cnt[1]); printf("大写:%d\n",cnt[2]); printf("数字:%d\n",cnt[3]); printf("其他:%d\n",cnt[4]); return 0;}2. 字符串排序示例:#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件​int main(int argc,char **argv){ int len=0; int i,j; char tmp; char str[100]; fgets(str,100,stdin); //从键盘上录入字符串 (标准输入) /*1. 计算字符串的长度*/ len=strlen(str); //计算字符串长度 /*2. 字符串排序*/ for(i=0;i<len-1;i++) { for(j=0;j<len-1-i;j++) { if(str[j]<str[j+1]) { tmp=str[j]; str[j]=str[j+1]; str[j+1]=tmp; } } } /*3. 打印结果*/ printf("%s\n",str); //打印字符串(标准输出) puts(str); //打印字符串(标准输出) fputs(str,stdout); //打印字符串(标准输出) return 0;}3. 字符串插入字符串插入: “1234567890” 在第2个位置后面插入”ABC” 最终结果: “12ABC34567890”#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件int main(int argc,char **argv){ int i,j; int src_len; int new_len; /* 123456789 12 3456789 */ char src_str[100]="123456789"; char new_str[]="abcd"; int addr=2; //插入的位置 /*1. 计算字符串的长度*/ src_len=strlen(src_str); //"123" new_len=strlen(new_str); /*2. 字符串移动*/ for(i=src_len-1;i>addr-1;i--) { src_str[i+new_len]=src_str[i]; //向后移动 new_len } /*3. 插入新的数据*/ for(i=0;i<new_len;i++)src_str[addr+i]=new_str[i]; /*4. 打印字符串*/ src_str[src_len+new_len]='\0'; //在字符串结尾添加'\0' printf("src_str=%s\n",src_str); return 0;}4. 字符串查找字符串查找: “123456123abc123hbc” 查找字符串”123”的数量。数量是3​#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件​int main(int argc,char **argv){ char src_str[100]; char find_str[10]; int src_len=0,find_len=0; int i,j; int cnt=0; /*1. 录入字符串*/ printf("输入源字符串:"); //123dufvdfv123dfljvb fgets(src_str,100,stdin); //从键盘上录入源字符串 //scanf("%s",src_str); printf("输入查找的字符串:"); //123 fgets(find_str,10,stdin); //从键盘上录入源字符串 //scanf("%s",find_str); /*2. 计算长度*/ src_len=strlen(src_str); src_str[src_len-1]='\0'; src_len-=1; //src_len=src_len-1; find_len=strlen(find_str); //"123\n" =4 find_str[find_len-1]='\0'; find_len-=1; printf("源字符串:%s,%d\n",src_str,src_len); printf("查找的字符串:%s,%d\n",find_str,find_len); /*3. 查找字符串*/ for(i=0;i<src_len-find_len+1;i++) { for(j=0;j<find_len;j++) { //只有一次不相等就退出 //123kdfvfd 123 if(src_str[i+j]!=find_str[j])break; } if(j==find_len) //条件成立表示查找成功 { cnt++; i+=find_len-1;//向后移动 } } /*4. 打印查找结果*/ printf("cnt=%d\n",cnt); return 0;}5. 字符串删除字符串删除: “1234567890” 删除”456” 最终结果: “1237890”示例:#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件​int main(int argc,char **argv){ char src_str[100]; char del_str[10]; int src_len=0,del_len=0; int i,j; int cnt=0; /*1. 录入字符串*/ printf("输入源字符串:"); //123dufvdfv123dfljvb fgets(src_str,100,stdin); //从键盘上录入源字符串 printf("输入查找的字符串:"); //123 fgets(del_str,10,stdin); //从键盘上录入源字符串 /*2. 计算长度*/ src_len=strlen(src_str); src_str[src_len-1]='\0'; src_len-=1; //src_len=src_len-1; del_len=strlen(del_str); //"123\n" =4 del_str[del_len-1]='\0'; del_len-=1; printf("源字符串:%s,%d\n",src_str,src_len); printf("删除字符串:%s,%d\n",del_str,del_len);​ /*3. 查找*/ for(i=0;i<src_len-del_len+1;i++) { for(j=0;j<del_len;j++) { if(src_str[i+j]!=del_str[j])break; } if(j==del_len) { cnt++; /*4.删除*/ for(j=i;j<src_len-del_len;j++) { src_str[j]=src_str[j+del_len]; } src_len-=del_len; i-=1; //继续在当前位置查找 } } src_str[src_len]='\0'; printf("src_str=%s\n",src_str); printf("cnt=%d\n",cnt); return 0;}6. 字符串替换字符串”1234567890”将456替换为”888” 最终: “1238887890”需要考虑3种情况7. 字符串转整数。从键盘上输入一个字符串”12345”, 得到整数: 12345;#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件int string_to_int(char str[]);int main(int argc,char **argv){ int data; char str[]="125abcd"; data=string_to_int(str); printf("data=%d\n",data); return 0;}​/*函数功能: 字符串转为整数字符转为整数: -48 或者 -'0'​1234*/int string_to_int(char str[]){ int value=0; //存放转换之后的结果 int i=0; while((str[i]!='\0')&&(str[i]>='0'&&str[i]<='9')) { value*=10; value+=str[i]-'0'; i++; } return value;}8. 整数转字符串整数转字符串。输入一个整数1234,得到字符串: "1234"1234%10=4 1234/10=123 123%10=3 123/10=12示例:#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件void int_to_string(char str[],int data);​int main(int argc,char **argv){ char str[100]; int_to_string(str,12345); printf("str=%s\n",str); return 0;}​/*函数功能: 整数转为字符串函数参数: char str[] //存放转换之后的整数(字符串) int data //待转换的整数*/void int_to_string(char str[],int data){ int i=0,j; char tmp; /*1. 将整数转为字符串*/ while(data) { str[i]=data%10+'0'; data/=10; i++; } str[i]='\0'; /*2. 交换顺序*/ for(j=0;j<i/2;j++) { tmp=str[j]; str[j]=str[i-j-1]; str[i-j-1]=tmp; }}9. 浮点数转字符串浮点数转字符串。输入一个浮点数123.456 得到字符串"123.456"示例:#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件void float_to_string(char str[],float data);​int main(int argc,char **argv){ char str[100]; float_to_string(str,12345.123); printf("str=%s\n",str); return 0;}​/*函数功能: 浮点数转为字符串函数参数: char str[] //存放转换之后的 浮点数(字符串) int data //待转换的 浮点数*/void float_to_string(char str[],float data){ int i=0,j; char tmp; int addr; int int_data=data; //得到整数 12345 int float_data=(data-int_data)*1000000;// 0.123456 *1000000 =123456​ /*1. 将整数部分转为字符串*/ while(int_data) { str[i]=int_data%10+'0'; int_data/=10; i++; } str[i]='.'; //添加小数点 /*2. 交换顺序: 整数*/ for(j=0;j<i/2;j++) { tmp=str[j]; str[j]=str[i-j-1]; str[i-j-1]=tmp; } /*3. 将浮点数部分转为字符串*/ i++; //跨过小数点 addr=i; while(float_data) { str[i]=float_data%10+'0'; float_data/=10; i++; } str[i]='\0'; /*4. 交换顺序: 小数部分*/ for(j=0;j<3;j++) { tmp=str[addr+j]; str[addr+j]=str[i-j-1]; str[i-j-1]=tmp; }}10.字符串转浮点数字符串转浮点数。输入一个字符串: "123.456" 得到浮点数类型: 123.456#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件float string_to_float(char str[]);int main(int argc,char **argv){ float data; char str[]="123.456"; data=string_to_float(str); printf("data=%f\n",data); return 0;}​/*函数功能: 字符串转为浮点数字符转为整数: -48 或者 -'0'*/float string_to_float(char str[]){ int int_value=0; //存放转换之后的结果 int float_value=0; int i=0; float data; int cnt=0; //记录小数部分的数量 int tmp=1; /*1. 整数部分*/ while((str[i]!='\0')&&(str[i]>='0'&&str[i]<='9')) { int_value*=10; int_value+=str[i]-'0'; i++; } /*2. 浮点数部分*/ i++; //跨过小数点 while((str[i]!='\0')&&(str[i]>='0'&&str[i]<='9')) { float_value*=10; float_value+=str[i]-'0'; i++; cnt++; } for(i=0;i<cnt;i++)tmp*=10; data=int_value; //整数部分 data+=float_value/(tmp*1.0); return data;}11. 时间转换时间转换(秒与标准时间的转换)。 在代码里加入: #include <time.h> 头文件。‘ 调用time_t time(time_t *t);函数获取本地时间。 示例: unsigned int sec=time(NULL); //sec是获取到的秒单位时间 该时间是从1970年开始计算的。 什么是标准时间? 得到年-月-日 时:分:秒 本身: 数字电子钟。 RTC: 实时时钟。#include <time.h>time_t time(time_t *t);​Linux下日期修改: [root@wbyq code]# date -s "2018-12-24 21:09:20"示例:#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件#include <time.h>​int get_year(int year);void sec_to_time(int sec);void Get_Week(int sec);​//定义时间结构int tm_sec; /* seconds */int tm_min; /* minutes */int tm_hour; /* hours */int tm_mday; /* day of the month */int tm_mon; /* month */int tm_year; /* year */int tm_wday; /* day of the week */int tm_yday; /* day in the year */​int mon_p[12]={31,28,31,30,31,30,31,31,30,31,30,31};int mon_r[12]={31,29,31,30,31,30,31,31,30,31,30,31};​int main(int argc,char **argv){ int sec1; int sec2; while(1) { sec1=time(NULL);//获取当前计算机系统的秒单位时间 if(sec1!=sec2) { sec2=sec1; sec_to_time(sec2+8*60*60); printf("%d-%d-%d %d:%d:%d\n", tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec ); printf("当前时间是当前年的第%d天\n",tm_yday); } } return 0;}​​/*函数功能: 秒转标准时间*/void sec_to_time(int sec){ int i; Get_Week(sec); /*1. 得到年份*/ tm_year=1970;//基础年份 while(sec>=365*24*60*60) { if(get_year(tm_year)) //闰年 { if(sec>=366*24*60*60) { sec-=366*24*60*60; //减去一年 tm_year++; //增加一年 } else break; } else //平年 { sec-=365*24*60*60; //减去一年 tm_year++; //增加一年 } } /*计算当前时间是一年中的第几天*/ tm_yday=sec/(24*60*60); /*2. 得到月份*/ tm_mon=1; for(i=0;i<12;i++) { if(get_year(tm_year)) //闰年 { if(sec>=mon_r[i]*24*60*60) { sec-=mon_r[i]*24*60*60; tm_mon++; } else break; } else //平年 { if(sec>=mon_p[i]*24*60*60) { sec-=mon_p[i]*24*60*60; tm_mon++; } else break; } } /*3. 天数*/ tm_mday=1; while(sec>=24*60*60) { tm_mday++; sec-=24*60*60; } /*4. 小时*/ tm_hour=0; while(sec>=60*60) { tm_hour++; sec-=60*60; } /*5. 分钟*/ tm_min=0; while(sec>=60) { tm_min++; sec-=60; } /*6. 秒*/ tm_sec=sec;}​/*函数功能: 获取年份状态: 平年和闰年函数参数: year填年份返回值: 0表示平年 1表示闰年*/int get_year(int year){ if((year%400==0)||(year%4==0&&year%100!=0)) { return 1; } return 0;}​/*函数功能: 获取星期*/void Get_Week(int sec){ int day=sec/(24*60*60); switch(day%7) { case 0: printf("星期4\n"); break; case 1: printf("星期5\n"); break; case 2: printf("星期6\n"); break; case 3: printf("星期日\n"); break; case 4: printf("星期1\n"); break; case 5: printf("星期2\n"); break; case 6: printf("星期3\n"); break; }}12. GPS数据解析提取最基本的数据:(1) UTC时间(2) 海拔高度(3) 经纬度//GPS返回的数据格式如下 char gps_data[]= { "$GNGGA,114955.000,2842.4158,N,11549.5439,E,1,05,3.8,54.8,M,0.0,M,,*4F \ $GNGLL,2842.4158,N,11549.5439,E,114955.000,A,A*4D \ $GPGSA,A,3,10,31,18,,,,,,,,,,5.7,3.8,4.2*37 \ $BDGSA,A,3,07,10,,,,,,,,,,,5.7,3.8,4.2*2A \ $GPGSV,3,1,10,10,49,184,42,12,16,039,,14,54,341,,18,22,165,23*7B \ $GPGSV,3,2,10,22,11,318,,25,51,055,,26,24,205,,29,13,110,*7C \ $GPGSV,3,3,10,31,50,287,36,32,66,018,*7F \ $BDGSV,1,1,04,03,,,07,05,,,29,07,79,246,33,10,52,232,19*62 \ $GNRMC,114955.000,A,2842.4158,N,11549.5439,E,0.00,44.25,061117,,,A*4D \ $GNVTG,44.25,T,,M,0.00,N,0.00,K,A*14 \ $GNZDA,114955.000,06,11,2017,00,00*47 \ $GPTXT,01,01,01,ANTENNA OK*35" };
  • [技术干货] C语言实例_time.h库函数功能及其用法详解
    一、前言时间在计算机编程中扮演着重要的角色,C语言的time.h头文件提供了一系列的函数和工具,用于处理时间和日期相关的操作。这些函数包括获取当前时间、日期格式化、时间间隔计算等功能,为开发人员提供了强大的时间处理能力。本文将对time.h头文件中的所有函数进行全面介绍,包括功能和使用方法,以帮助大家更好地理解和利用该头文件。二、函数介绍在 C 语言中,time.h 头文件提供了与时间和日期相关的函数和数据类型。下面是头文件中常用的函数和数据类型及其功能的详细介绍:【1】time_t time(time_t *timer):功能:获取当前系统时间,并将其表示为从1970年1月1日至今的秒数。 参数:timer 是一个指向 time_t 类型对象的指针,用于存储获取到的时间。 返回值:返回表示当前时间的 time_t 类型对象,如果出错,则返回 -1。【2】double difftime(time_t time1, time_t time2):功能:计算两个时间之间的差值(以秒为单位)。 参数:time1 和 time2 是两个 time_t 类型的时间。 返回值:返回 time1 - time2 的结果,以 double 类型表示。【3】char ctime(const time_t **timer):功能:将 time_t 类型的时间转换为字符串,表示为本地时间格式。 参数:timer 是一个指向 time_t 类型对象的指针,表示要转换的时间。 返回值:返回一个指向包含日期和时间信息的字符串的指针。【4】struct tm localtime(const time_t** timer):功能:将 time_t 类型的时间转换为本地时间。 参数:timer 是一个指向 time_t 类型对象的指针,表示要转换的时间。 返回值:返回一个指向 struct tm 结构体的指针,其中包含了转换后的本地时间信息。【5】struct tm gmtime(const time_t **timer):功能:将 time_t 类型的时间转换为格林尼治标准时间(GMT)。 参数:timer 是一个指向 time_t 类型对象的指针,表示要转换的时间。 返回值:返回一个指向 struct tm 结构体的指针,其中包含了转换后的 GMT 时间信息。【6】time_t mktime(struct tm*timeptr):功能:将 struct tm 结构体表示的时间转换为 time_t 类型。 参数:timeptr 是一个指向 struct tm 结构体的指针,表示要转换的时间。 返回值:返回一个 time_t 类型的对象,表示转换后的时间。【7】size_t strftime(char str, size_t maxsize, const char format, const struct tm* timeptr)、:功能:将日期和时间按照指定格式输出到字符串中。 参数:str 是一个指向字符数组的指针,用于存储输出的字符串;maxsize 是 str 的大小限制;format 是一个指向以 % 字符开头的格式字符串;timeptr 是一个指向 struct tm 结构体的指针,表示要格式化的时间。 返回值:返回实际写入字符串的字符数。除了上述函数,time.h 头文件还定义了以下数据类型:time_t:表示从 1970 年 1 月 1 日开始计算的秒数。 struct tm:表示日期和时间的结构体,包含年、月、日、时、分、秒等信息。三、用法示例【1】time_t time(time_t* timer):#include <stdio.h>#include <time.h>​int main() { time_t current_time; time(&current_time);​ printf("Current time: %ld\n", current_time);​ return 0;}【2】double difftime(time_t time1, time_t time2):#include <stdio.h>#include <time.h>​int main() { time_t start_time, end_time; double elapsed_time;​ time(&start_time); // Some time-consuming task time(&end_time);​ elapsed_time = difftime(end_time, start_time); printf("Elapsed time: %.2f seconds\n", elapsed_time);​ return 0;}【2】char* ctime(const time_t* timer):#include <stdio.h>#include <time.h>​int main() { time_t current_time; time(&current_time);​ char* time_string = ctime(&current_time); printf("Current time: %s", time_string);​ return 0;}【3】struct tm* localtime(const time_t* timer):#include <stdio.h>#include <time.h>​int main() { time_t current_time; time(&current_time);​ struct tm* local_time = localtime(&current_time); printf("Current local time: %s", asctime(local_time));​ return 0;}【4】struct tm* gmtime(const time_t* timer):#include <stdio.h>#include <time.h>​int main() { time_t current_time; time(&current_time);​ struct tm* gm_time = gmtime(&current_time); printf("Current GMT time: %s", asctime(gm_time));​ return 0;}【5】time_t mktime(struct tm* timeptr):#include <stdio.h>#include <time.h>​int main() { struct tm date; time_t t;​ date.tm_sec = 0; date.tm_min = 0; date.tm_hour = 0; date.tm_mday = 16; date.tm_mon = 7; // August (months are 0-based) date.tm_year = 123; // 2023 (years are counted from 1900)​ t = mktime(&date);​ printf("Time in seconds since 1970: %ld\n", t);​ return 0;}【6】size_t strftime(char* str, size_t maxsize, const char* format, const struct tm* timeptr):#include <stdio.h>#include <time.h>​int main() { time_t current_time; time(&current_time);​ struct tm* local_time = localtime(&current_time);​ char str[100]; size_t maxsize = sizeof(str); const char* format = "%Y-%m-%d %H:%M:%S"; strftime(str, maxsize, format, local_time);​ printf("Formatted time: %s\n", str);​ return 0;}
  • [技术干货] C语言实例_stdlib.h库函数功能及其用法详解
    一、前言C语言作为一种高效、灵活的编程语言,标准库的使用对于开发人员来说是不可或缺的。其中,stdlib.h是C语言中一个重要的标准库头文件,提供了许多常用的函数和工具,以便开发人员能够更加便捷地进行内存管理、字符串处理、随机数生成等操作。本文将对stdlib.h中的各个函数进行全面介绍,包括它们的功能和使用方法,以帮助开发者更好地理解和利用该标准库。二、stdlib.h函数介绍C语言的标准库头文件 stdlib.h 提供了一些常用的函数,用于执行各种实用程序和内存管理任务。以下是 stdlib.h 头文件中包含的主要函数及其功能的详细介绍:【1】内存管理函数malloc(size_t size):动态分配指定大小的内存块,并返回指向该内存块的指针。calloc(size_t num, size_t size):动态分配 num 个长度为 size 字节的连续内存区域,并将每个字节初始化为零。realloc(void* ptr, size_t size):重新分配先前分配的内存块 ptr 的大小为 size 字节,并返回指向重新分配后内存块的指针。free(void* ptr):释放之前通过动态内存分配函数分配的内存。【2】字符串转换函数atoi(const char* str):将字符串转换为对应的整数并返回结果。atol(const char* str):将字符串转换为对应的长整数并返回结果。atof(const char* str):将字符串转换为对应的双精度浮点数并返回结果。itoa(int value, char* str, int base):将整数转换为字符串并存储在 str 中。rand(void):生成伪随机数。srand(unsigned int seed):设置随机数发生器的种子。【3】环境控制函数system(const char* command):执行命令行参数中指定的 shell 命令。exit(int status):终止程序的执行并返回状态码。_Exit(int status):终止程序的执行并返回状态码,不进行清理操作。abort(void):中止程序的执行,并生成一个异常终止信号。【4】动态分配排序函数qsort(void* base, size_t nmemb, size_t size, int (*compar)(const void*, const void*)):对数组进行快速排序。【5】字符串处理函数rand_r(unsigned int* seedp):可重入版本的 rand() 函数。system_l(const char* command, locale_t loc):根据指定的本地化环境执行命令。posix_memalign(void** memptr, size_t alignment, size_t size):分配对齐的内存块。aligned_alloc(size_t alignment, size_t size):分配对齐的内存块。三、代码示例3.1 内存管理函数【1】malloc(size_t size):动态分配指定大小的内存块,并返回指向该内存块的指针。#include <stdio.h>#include <stdlib.h>​int main() { int* ptr; int num = 5;​ // 动态分配一个 int 数组,数组长度为 num ptr = (int*)malloc(num * sizeof(int)); // 检查内存是否成功分配 if (ptr == NULL) { printf("内存分配失败\n"); exit(1); }​ // 使用分配的内存 for (int i = 0; i < num; i++) { ptr[i] = i + 1; } // 输出数组的值 for (int i = 0; i < num; i++) { printf("%d ", ptr[i]); }​ // 释放内存 free(ptr);​ return 0;}【2】calloc(size_t num, size_t size):动态分配 num 个长度为 size 字节的连续内存区域,并将每个字节初始化为零。#include <stdio.h>#include <stdlib.h>​int main() { int* ptr; int num = 5;​ // 动态分配一个 int 数组,数组长度为 num,并初始化为零 ptr = (int*)calloc(num, sizeof(int)); // 检查内存是否成功分配 if (ptr == NULL) { printf("内存分配失败\n"); exit(1); }​ // 输出数组的值 for (int i = 0; i < num; i++) { printf("%d ", ptr[i]); }​ // 释放内存 free(ptr);​ return 0;}【3】realloc(void* ptr, size_t size):重新分配先前分配的内存块 ptr 的大小为 size 字节,并返回指向重新分配后内存块的指针。#include <stdio.h>#include <stdlib.h>​int main() { int* ptr; int num = 5;​ // 动态分配一个 int 数组,数组长度为 num ptr = (int*)malloc(num * sizeof(int)); // 检查内存是否成功分配 if (ptr == NULL) { printf("内存分配失败\n"); exit(1); }​ // 输出数组的值 for (int i = 0; i < num; i++) { printf("%d ", ptr[i]); }​ // 重新分配内存为更大的数组 num = 10; ptr = (int*)realloc(ptr, num * sizeof(int));​ // 使用重新分配的内存 for (int i = 5; i < num; i++) { ptr[i] = i + 1; }​ // 输出数组的值 for (int i = 0; i < num; i++) { printf("%d ", ptr[i]); }​ // 释放内存 free(ptr);​ return 0;}【4】free(void* ptr):释放之前通过动态内存分配函数分配的内存。#include <stdio.h>#include <stdlib.h>​int main() { int* ptr;​ // 动态分配一个 int 数组 ptr = (int*)malloc(5 * sizeof(int)); // 检查内存是否成功分配 if (ptr == NULL) { printf("内存分配失败\n"); exit(1); }​ // 使用分配的内存 for (int i = 0; i < 5; i++) { ptr[i] = i + 1; } // 输出数组的值 for (int i = 0; i < 5; i++) { printf("%d ", ptr[i]); }​ // 释放内存 free(ptr);​ return 0;}以上是这些内存管理函数的基本用法。动态内存管理函数允许在程序运行时根据需要分配或释放内存,提供了更灵活和高效地使用内存的方式。重要的是记得在使用完毕后及时释放内存,以避免内存泄漏问题。3.2 字符串转换与随机数函数这里是给这些字符串转换函数和随机数函数的例子和用法介绍:【1】atoi(const char* str):将字符串转换为对应的整数并返回结果。#include <stdio.h>#include <stdlib.h>​int main() { const char* str = "12345"; int num = atoi(str); printf("字符串转换为整数:%d\n", num);​ return 0;}【2】atol(const char* str):将字符串转换为对应的长整数并返回结果。#include <stdio.h>#include <stdlib.h>​int main() { const char* str = "1234567890"; long num = atol(str); printf("字符串转换为长整数:%ld\n", num);​ return 0;}【3】atof(const char* str):将字符串转换为对应的双精度浮点数并返回结果。#include <stdio.h>#include <stdlib.h>​int main() { const char* str = "3.14159"; double num = atof(str); printf("字符串转换为双精度浮点数:%f\n", num);​ return 0;}【4】itoa(int value, char* str, int base):将整数转换为字符串并存储在 str 中。#include <stdio.h>#include <stdlib.h>​int main() { int num = 12345; char str[20]; itoa(num, str, 10); printf("整数转换为字符串:%s\n", str);​ return 0;}【5】rand(void):生成伪随机数。#include <stdio.h>#include <stdlib.h>#include <time.h>int main() { srand(time(NULL)); // 设置随机数发生器的种子为当前时间 for (int i = 0; i < 5; i++) { int randomNum = rand(); printf("随机数:%d\n", randomNum); } return 0;}【6】srand(unsigned int seed):设置随机数发生器的种子。#include <stdio.h>#include <stdlib.h>#include <time.h>int main() { srand(123); // 设置随机数发生器的种子为 123 for (int i = 0; i < 5; i++) { int randomNum = rand(); printf("随机数:%d\n", randomNum); } return 0;}3.3 环境控制函数【1】system(const char* command):执行命令行参数中指定的 shell 命令。#include <stdio.h>#include <stdlib.h>int main() { const char* command = "ls -l"; // 列出当前目录下的文件和文件夹 int status = system(command); if (status == -1) { printf("命令执行失败。\n"); } else { printf("命令执行成功。\n"); } return 0;}【2】exit(int status):终止程序的执行并返回状态码。#include <stdio.h>#include <stdlib.h>int main() { printf("程序开始执行。\n"); // 退出程序,并返回状态码 0 exit(0); printf("此行不会被执行。\n"); return 0;}【2】_Exit(int status):终止程序的执行并返回状态码,不进行清理操作。#include <stdio.h>#include <stdlib.h>int main() { printf("程序开始执行。\n"); // 退出程序,并返回状态码 0,不进行清理操作 _Exit(0); printf("此行不会被执行。\n"); return 0;}【3】abort(void):中止程序的执行,并生成一个异常终止信号。#include <stdio.h>#include <stdlib.h>int main() { printf("程序开始执行。\n"); printf("触发异常终止信号。\n"); abort(); printf("此行不会被执行。\n"); return 0;}3.4 动态分配排序函数这里是关于动态分配排序函数的例子和用法介绍:#include <stdio.h>#include <stdlib.h>// 比较函数,用于指定排序顺序int compareFunc(const void* a, const void* b) { // 将输入的指针转换为所需的类型 int num1 = *(int*)a; int num2 = *(int*)b; // 按升序进行排序 if (num1 < num2) { return -1; } else if (num1 > num2) { return 1; } else { return 0; }}int main() { int arr[] = {5, 2, 8, 6, 1, 3, 9, 7, 4}; int size = sizeof(arr) / sizeof(arr[0]); printf("排序前的数组:"); for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n"); // 使用 qsort 对数组进行排序 qsort(arr, size, sizeof(int), compareFunc); printf("排序后的数组:"); for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n"); return 0;}在这个例子中,定义了一个整型数组 arr,包含了一些无序的元素。使用 qsort 函数对该数组进行排序。qsort 函数接收四个参数:要排序的数组的起始地址 base,数组中元素的个数 nmemb,每个元素的字节大小 size,以及一个比较函数 compar。比较函数用于指定排序的顺序。在compareFunc中,传入的指针转换为 int 类型,并按照升序排序的规则进行比较。如果第一个元素小于第二个元素,返回 -1;如果第一个元素大于第二个元素,返回 1;如果两个元素相等,返回 0。最后,输出排序前和排序后的数组,可以看到数组已经按升序进行了排序。qsort 函数是对 C 标准库的一部分,经过高效的优化,可以处理不同类型的数组,而不仅仅是整型数组。3.5 字符串处理函数【1】rand_r(unsigned int* seedp):可重入版本的 rand() 函数,用于生成伪随机数。#include <stdio.h>#include <stdlib.h>#include <time.h>int main() { unsigned int seed = time(NULL); for (int i = 0; i < 5; i++) { int randomNum = rand_r(&seed); printf("%d ", randomNum); } printf("\n"); return 0;}在这个例子中,使用 rand_r 函数生成了5个伪随机数。通过向 rand_r 函数传递一个指向种子的指针,确保每次调用 rand_r 函数时都使用不同的种子,使其成为可重入函数。【2】system_l(const char* command, locale_t loc):根据指定的本地化环境执行命令。#include <stdio.h>#include <stdlib.h>#include <locale.h>int main() { const char* command = "ls -l"; // 列出当前目录下的文件和文件夹 locale_t loc = newlocale(LC_ALL_MASK, "", NULL); int status = system_l(command, loc); if (status == -1) { printf("命令执行失败。\n"); } else { printf("命令执行成功。\n"); } freelocale(loc); return 0;}在这个例子中,使用 system_l 函数执行了一个命令 ls -l,该命令用于列出当前目录下的文件和文件夹。使用 newlocale 函数创建了一个新的本地化环境 loc,并将其作为参数传递给 system_l 函数。最后,使用 freelocale 函数释放本地化环境。【3】posix_memalign(void memptr, size_t alignment, size_t size):分配对齐的内存块。#include <stdio.h>#include <stdlib.h>int main() { void* memPtr; size_t alignment = 16; // 对齐要求为16字节 size_t size = 32; // 分配32字节的内存 int status = posix_memalign(&memPtr, alignment, size); if (status == 0) { printf("内存分配成功。\n"); // 使用分配的内存 free(memPtr); } else { printf("内存分配失败。\n"); } return 0;}在这个例子中,使用 posix_memalign 函数分配了一个对齐的内存块,要求对齐要求为16字节,分配32字节的内存。通过传递指向 memPtr 的指针,可以在函数内部接收分配的内存地址。最后,使用 free 函数释放内存。【4】aligned_alloc(size_t alignment, size_t size):分配对齐的内存块。#include <stdio.h>#include <stdlib.h>int main() { size_t alignment = 16; // 对齐要求为16字节 size_t size = 32; // 分配32字节的内存 void* memPtr = aligned_alloc(alignment, size); if (memPtr != NULL) { printf("内存分配成功。\n"); // 使用分配的内存 free(memPtr); } else { printf("内存分配失败。\n"); } return 0;}在这个例子中,使用 aligned_alloc 函数分配了一个对齐的内存块,要求对齐要求为16字节,分配32字节的内存。通过将返回的内存指针赋值给 memPtr 变量,可以获得分配的内存地址。最后,使用 free 函数释放内存。这些字符串处理函数提供了在 C 语言中处理字符串和执行相关操作的功能。使用这些函数时需要小心内存管理,避免出现内存泄漏等问题。
  • [技术干货] 人机验证码生成与验证:提升系统安全性
    一、前言为了防止机器人或脚本程序自动化攻击和滥用系统资源,很多网站和应用程序需要使用验证码来判断用户是否为真人。 一般登录都要求用户手动输入以验证身份的安全措施。验证码是一种通过生成包含随机字符的图像或文本,通常包含了不同大小写字母、数字或特殊符号,具有一定的复杂性和随机性,使机器难以识别和破解。本项目使用 C 语言实现一个简单的人机验证码生成和验证程序。程序生成一个由4位随机字符组成的验证码,并要求用户在控制台中手动输入该验证码。如果用户输入与生成的验证码匹配,则输出"验证成功";否则输出"验证失败"。在生成验证码的过程中,使用了随机数生成函数 rand() 来获取随机数,并结合字符集合来生成随机字符。为了确保每次生成的验证码都是独一无二的,使用当前系统时间作为种子来初始化随机数生成器。这个项目可以应用于各种需求,如注册页面的人机验证、防止暴力破解密码的登录页面、限制自动化爬虫等。通过要求用户手动输入验证码,可以有效防止机器人或脚本程序的自动化攻击,提高系统安全性和用户隐私保护。下面给了3种例子,分别是字符验证码、图片验证码、计算题结果验证。二、代码实现(生成验证码并进行验证)#include <stdio.h>#include <stdlib.h>#include <time.h>​// 生成随机验证码void generateCode(char* code, int length) { const char charset[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; srand(time(NULL));​ for (int i = 0; i < length; i++) { int index = rand() % (sizeof(charset) - 1); code[i] = charset[index]; }​ code[length] = '\0';}​int main() { char code[5]; generateCode(code, 4); printf("请输入验证码:%s\n", code);​ char input[5]; scanf("%s", input);​ if (strcmp(input, code) == 0) { printf("验证成功!\n"); } else { printf("验证失败!\n"); }​ return 0;}​代码中,generateCode 函数用于生成指定长度的随机验证码。使用包含大小写字母和数字的字符集合,通过调用 srand() 函数设置随机数种子,确保每次生成的验证码都是不同的。在 main 函数中,调用 generateCode 生成一个4位数的验证码,将其打印出来。用户输入验证码,输入结果存储在 input 字符数组中。通过使用 strcmp 函数比较用户输入的验证码和生成的验证码,如果相等则输出"验证成功",否则输出"验证失败"。三、生成验证码,绘制到图片里,提高验证难度#include <stdio.h>#include <stdlib.h>#include <time.h>#pragma warning(disable:4996)​#define WIDTH 50 // 图片宽度#define HEIGHT 50 // 图片高度​// 生成随机4位数验证码int generateRandomCode() { srand(time(NULL)); return rand() % 10000;}​// 绘制验证码到BMP图片void drawCodeToBMP(int code) { FILE* filePtr; unsigned char* imageData; int imageSize = WIDTH * HEIGHT * 3; // 3 bytes per pixel (RGB) // 分配内存用于存储图像数据 imageData = (unsigned char*)malloc(imageSize); // 设置所有像素为白色 for (int i = 0; i < imageSize; i += 3) { imageData[i] = 255; // R imageData[i + 1] = 255; // G imageData[i + 2] = 255; // B } // 在图像中心绘制验证码 int x = (WIDTH / 2) - 10; // x坐标偏移量,使验证码居中显示 int y = (HEIGHT / 2) + 5; // y坐标偏移量,使验证码居中显示 sprintf((char*)(imageData + ((y * WIDTH + x) * 3)), "%04d", code); // 打开文件准备写入图像数据 filePtr = fopen("6666.bmp", "wb"); // BMP文件头 unsigned char bmpFileHeader[] = { 0x42, 0x4D, // 文件类型 0x36, 0x00, 0x0C, 0x00, // 文件大小 0x00, 0x00, // 保留字 0x00, 0x00, // 保留字 0x36, 0x00, 0x00, 0x00, // 数据偏移量 }; // BMP信息头 unsigned char bmpInfoHeader[] = { 0x28, 0x00, 0x00, 0x00, // 信息头大小 0x32, 0x00, 0x00, 0x00, // 图像宽度 0x32, 0x00, 0x00, 0x00, // 图像高度 0x01, 0x00, // 颜色平面数 0x18, 0x00, // 每像素位数 0x00, 0x00, 0x00, 0x00, // 压缩方式 0x00, 0x00, 0x0C, 0x00, // 图像数据大小 0x00, 0x00, 0xC4, 0x0E, // 水平分辨率 0x00, 0x00, 0xC4, 0x0E, // 垂直分辨率 0x00, 0x00, 0x00, 0x00, // 颜色表数目 0x00, 0x00, 0x00, 0x00, // 重要颜色数目 }; // 写入文件头和信息头 fwrite(bmpFileHeader, sizeof(unsigned char), sizeof(bmpFileHeader), filePtr); fwrite(bmpInfoHeader, sizeof(unsigned char), sizeof(bmpInfoHeader), filePtr); // 写入图像数据 fwrite(imageData, sizeof(unsigned char), imageSize, filePtr); // 关闭文件 fclose(filePtr); // 释放内存 free(imageData);}​int main() { int code = generateRandomCode(); printf("验证码: %04d\n", code); drawCodeToBMP(code); printf("已生成验证码图片: 6666.bmp\n"); return 0;}​generateRandomCode函数用于生成随机的4位数验证码。drawCodeToBMP函数根据验证码将其绘制在50x50像素大小的白色BMP图片中,将图像数据保存到名为6666.bmp的文件中。在main函数中,先生成一个随机的4位数验证码,通过调用drawCodeToBMP函数将验证码绘制到BMP图片中,在控制台打印出验证码和生成的图片文件名。四、通过生成随机计算题来测试,提高验证难度#include <stdio.h>#include <stdlib.h>#include <time.h>#pragma warning(disable:4996)​#include <stdio.h>#include <stdlib.h>#include <time.h>​// 生成随机运算题目void generateRandomQuestion() { srand(time(NULL)); int num1 = rand() % 20; // 生成0到19的随机数 int num2 = rand() % 20; char operators[3] = { '+', '-', 'x' }; char op = operators[rand() % 3]; // 从加减乘中随机选择一个运算符​ printf("请计算以下题目的答案:\n"); printf("%d %c %d = ?\n", num1, op, num2);​ // 验证用户输入的结果 int userAnswer; scanf("%d", &userAnswer);​ int correctAnswer; switch (op) { case '+': correctAnswer = num1 + num2; break; case '-': correctAnswer = num1 - num2; break; case 'x': correctAnswer = num1 * num2; break; default: break; }​ if (userAnswer == correctAnswer) { printf("验证成功!\n"); } else { printf("验证失败,正确答案是:%d\n", correctAnswer); }}​int main() { printf("欢迎来到人机验证系统!\n"); printf("请计算下面的题目,并输入答案。\n");​ generateRandomQuestion();​ return 0;}​在generateRandomQuestion函数中生成两个0到19的随机整数和一个随机的加减乘运算符,打印出题目要求用户计算结果。通过scanf函数获取用户输入的结果,与程序计算得到的正确结果进行比较,最终输出验证成功或失败的信息。在main函数中,调用generateRandomQuestion函数开始人机验证。当用户输入结果后,程序给出验证结果。
  • [技术干货] C语言实例_生成6位数的随机密码
    一、前言随着数字化时代的到来,人们在各个方面需要使用密码来保护个人隐私和敏感信息的安全。为了确保密码的安全性,密码应该是足够强大和难以猜测的,这就需要密码生成器来帮助用户生成高强度的随机密码。随机密码生成器是一种计算机程序,通过使用随机化算法和密码字符集合,生成具有预定长度和复杂性的密码。它可以用于创建密码保护的账户、加密文件、访问控制等场景。本项目使用 C 语言实现一个简单而实用的随机密码生成器。该生成器将生成包含字母、数字和特殊字符的随机密码,并提供自定义密码长度选项。生成的密码将是强大和难以破解的,从而增加了用户资料和关键信息的安全性。实现过程中,使用了随机数生成器函数 rand() 来获取随机数,并与自定义的密码字符集合进行结合,以生成密码的不同字符。同时,为了确保每次生成的密码都是独一无二的,使用当前系统时间作为种子来初始化随机数生成器。用户可以在程序运行时选择所需的密码长度,并且程序将输出所生成的密码。这样用户就可以轻松获得一个符合安全要求的随机密码,而无需自己思考和构建。使用随机密码生成器可以提高密码的复杂性和安全性,减少密码被猜测和破解的风险,从而保护用户的个人隐私和敏感信息。在日常生活和工作中都是非常有用的工具。带字母+数字:纯数字:二、代码示例: 6位随机密码带字母以下是使用 C 语言生成随机 6 位数密码实现代码--带字母和数字:#include <stdio.h>#include <stdlib.h>#include <time.h>​// 生成随机密码void generatePassword(char* password, int length) { srand(time(NULL));​ // 密码字符集合 const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";​ for (int i = 0; i < length; i++) { int index = rand() % (sizeof(charset) - 1); password[i] = charset[index]; }​ password[length] = '\0';}​int main() { // 定义密码长度 int passwordLength = 6;​ // 分配足够的内存以存储生成的密码(包括空字符) char* password = (char*)malloc((passwordLength + 1) * sizeof(char));​ // 生成密码 generatePassword(password, passwordLength);​ printf("随机密码: %s\n", password);​ // 释放内存 free(password);​ return 0;}这个代码中,generatePassword 函数用于生成随机密码。该函数使用 srand 函数初始化随机数生成器,然后在字符集合中随机选择字符,将其添加到密码字符串中。最后,将字符串末尾添加空字符。在 main 函数中,定义了密码的长度(这里是 6),然后使用 malloc 函数分配足够的内存来存储生成的密码(包括空字符)。接着,调用 generatePassword 函数生成密码,并打印输出。最后,使用 free 函数释放已分配的内存。在这个示例中,使用 srand 函数和 time 函数来初始化随机数生成器,获得更好的随机性。同时,密码字符集合包括小写字母、大写字母和数字。三、代码示例:6位随机密码纯数字以下是生成随机 6 位纯数字密码的实现代码:#include <stdio.h>#include <stdlib.h>#include <time.h>​// 生成随机数字密码void generateNumericPassword(char* password, int length) { srand(time(NULL));​ // 密码字符集合 const char charset[] = "0123456789";​ for (int i = 0; i < length; i++) { int index = rand() % (sizeof(charset) - 1); password[i] = charset[index]; }​ password[length] = '\0';}​int main() { // 定义密码长度 int passwordLength = 6;​ // 分配足够的内存以存储生成的密码(包括空字符) char* password = (char*)malloc((passwordLength + 1) * sizeof(char));​ // 生成密码 generateNumericPassword(password, passwordLength);​ printf("随机密码: %s\n", password);​ // 释放内存 free(password);​ return 0;}​这个代码中,新增了一个名为 generateNumericPassword 的函数用于生成随机的纯数字密码。其余部分与之前的示例相似。在 generateNumericPassword 函数中,调整了密码字符集合为仅包含数字字符。通过修改 const char charset[] = "0123456789"; 可以更改密码字符集合,例如如果要包含特殊字符,可以扩展该字符集合。这里同样使用了 srand 函数和 time 函数来初始化随机数生成器,以获得更好的随机性。
  • [技术干货] C语言字符串处理提取时间(ffmpeg返回的时间字符串)
    【1】需求需求:有一个 "00:01:33.90" 这样格式的时间字符串,需要将这个字符串的时间值提取打印出来(提取时、分、秒、毫秒)。这个时间字符串从哪里来的? 是ffmpeg返回的时间,也就是视频的总时间。下面是ffmpeg获取视频总时间的输出。C:\Users\11266>ffmpeg -i D:/123.mp4ffmpeg version 4.2.2 Copyright (c) 2000-2019 the FFmpeg developers built with gcc 9.2.1 (GCC) 20200122 configuration: --disable-static --enable-shared --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt libavutil 56. 31.100 / 56. 31.100 libavcodec 58. 54.100 / 58. 54.100 libavformat 58. 29.100 / 58. 29.100 libavdevice 58. 8.100 / 58. 8.100 libavfilter 7. 57.100 / 7. 57.100 libswscale 5. 5.100 / 5. 5.100 libswresample 3. 5.100 / 3. 5.100 libpostproc 55. 5.100 / 55. 5.100Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'D:/123.mp4': Metadata: major_brand : mp42 minor_version : 0 compatible_brands: mp42isom creation_time : 2015-04-30T02:43:22.000000Z Duration: 00:01:33.90, start: 0.000000, bitrate: 715 kb/s Stream #0:0(und): Audio: aac (LC) (mp4a / 0x6134706D), 88200 Hz, stereo, fltp, 127 kb/s (default) Metadata: creation_time : 2015-04-30T02:43:22.000000Z handler_name : GPAC ISO Audio Handler Stream #0:1(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 582 kb/s, 15 fps, 25 tbr, 30k tbn, 20000k tbc (default) Metadata: creation_time : 2015-04-30T02:43:23.000000Z handler_name : GPAC ISO Video Handler这串数据里Duration: 00:01:33.90, start: 0.000000, bitrate: 715 kb/s,我们要提取00:01:33.90这串时间字符串出来,这个时间字符串就是当前视频的总时间。下面是时间字符串提取代码,C语言代码:char TotalTime[100];​//解析数据char *p = strstr(utf8_str.data(), "Duration:");if (p){ int i = 0; p += 9; while (*p != '\0' && *p != NULL && *p != ',') { if (i > 90)break; //防止越界 TotalTime[i++] = *p++; } TotalTime[i++] = '\0';}​qDebug() << "TotalTime:" << TotalTime;下面的2个例子介绍如何提取时、分、秒、毫秒的时间,打印出来。【2】C语言实现#include <stdio.h>#include <stdlib.h>#include <string.h>​void extractTime(const char* time_str, int* hour, int* minute, int* second, int* msec) { char* token; char* str; char* saveptr;​ // 复制时间字符串到临时缓冲区 str = strdup(time_str);​ // 分割字符串,以":"为分隔符 token = strtok_s(str, ":", &saveptr);​ // 提取小时部分 if (token != NULL) { *hour = atoi(token); } else { *hour = 0; }​ // 分割剩余部分,以":"为分隔符 token = strtok_s(NULL, ":", &saveptr);​ // 提取分钟部分 if (token != NULL) { *minute = atoi(token); } else { *minute = 0; }​ // 分割剩余部分,以"."为分隔符 token = strtok_s(NULL, ".", &saveptr);​ // 提取秒部分 if (token != NULL) { *second = atoi(token); } else { *second = 0; }​ // 提取毫秒部分 token = strtok_s(NULL, "", &saveptr); if (token != NULL) { *msec = atoi(token); } else { *msec = 0; }​ // 释放临时缓冲区内存 free(str);}​​​​int main() { const char* time_str = "00:01:33.90"; int hour, minute, second, msec;​ extractTime(time_str, &hour, &minute, &second, &msec);​ printf("hour: %d\n", hour); printf("minute: %d\n", minute); printf("second: %d\n", second); printf("millisecond: %d\n", msec);​ return 0;}​在这段代码中,实现了一个名为extractTime的函数,将时间字符串作为输入,并通过指针参数返回小时、分钟、秒和毫秒的值。使用strtok_s函数以":"和"."为分隔符逐个提取时间字符串的各部分,并将其转换为整数值。在使用strtok_s函数时,将剩余部分分隔的分隔符设为"",以便可以正确提取毫秒部分。【3】Qt正则表达式提取#include <QCoreApplication>#include <QString>#include <QRegularExpression>#include <QDebug>​void extractTime(const QString& timeStr, int* hour, int* minute, int* second, int* msec){ QRegularExpression regex("(\\d+):(\\d+):(\\d+)\\.(\\d+)"); QRegularExpressionMatch match = regex.match(timeStr); if (match.hasMatch()) { *hour = match.captured(1).toInt(); *minute = match.captured(2).toInt(); *second = match.captured(3).toInt(); *msec = match.captured(4).toInt(); } else { *hour = 0; *minute = 0; *second = 0; *msec = 0; }}​int main(int argc, char *argv[]){ QCoreApplication a(argc, argv);​ QString timeStr = "00:01:33.90"; int hour, minute, second, msec; extractTime(timeStr, &hour, &minute, &second, &msec);​ qDebug() << "hour:" << hour; qDebug() << "minute:" << minute; qDebug() << "second:" << second; qDebug() << "millisecond:" << msec;​ return a.exec();}​在代码中,实现了一个名为extractTime的函数,接收一个时间字符串,通过指针参数返回小时、分钟、秒和毫秒的值。使用QRegularExpression来定义一个正则表达式模式,然后使用QRegularExpressionMatch来提取匹配的结果。
  • C语言基本算法的运用笔记
    一、基本算法1.交换(两量交换借助第三者中间量)任意读入两个整数,将二者的值交换后输出。#include<stdio.h>void main(){ int a,b,t; scanf("%d%d",&a,&b); printf("%d,%d\n",a,b);/*-----------------*/  t=a;   a=b;   b=t;/*--------------------*/  printf("%d,%d\n",a,b); }【解析】程序中间隔开部分为算法的核心,就像是交换两个杯子里的饮料,必须借助第三个空杯子来作为中间量。假设输入的值分别为2、8,则第一行输出为2,8;第二行输出为8,2。其中t为中间变量,起到“空杯子”的作用。注意:三个赋值语句赋值号左右的各量之间的关系!【应用】任意读入三个整数,然后按从小到大的顺序输出。#include<stdio.h>void main(){  int a,b,c,t; scanf("%d%d%d",&a,&b,&c); /*以下两个if语句使得a中存放的数最小*/ if(a>b)   { t=a;      a=b;      b=t; } if(a>c)   { t=a;      a=c;      c=t; } /*以下if语句使得b中存放的数次小*/ if(b>c)    { t=b;      b=c;      c=t; } printf("%d,%d,%d\n",a,b,c); }2.累加累加算法的要领是形如“s=s+A”的累加式,此式必须出现在循环中才能被反复执行,从而实现累加功能。“A”通常是有规律变化的表达式,s在进入循环前必须获得合适的初值,通常为0。一个经典的问题:求1+2+3+……+100的和是多少。          #include<stdio.h> void main() {  int i,s;  s=0;    i=1;  while(i<=100)  {   s=s+i;        /*累加式*/   i=i+1;        /*特殊的累加式*/  }  printf("1+2+3+...+100=%d\n",s); }【解析】程序中标注加粗部分为累加式的典型形式,赋值号左右都出现的变量称为累加器,其中“i = i + 1”为特殊的累加式,每次累加的值为1,这样的累加器又称为计数器。【应用】格里高利公式格里高利公式求π的近似值,要求精确到最后一项的绝对值小于10-4。#include <stdio.h> #include <math.h> void main() {   int denominator,flag;   double item,pi;                 /* pi用于存放累加和 */   flag=1;                       /* flag表示第i项的符号,初始为正 */   denominator=1;                /* denominator表示第i项的分母,初始为1*/   item=1.0;                     /* item中存放第i项的值,初值取1 */   pi=0;                         /* 置累加和pi的初值为0 */     while(fabs(item)>=0.0001) /* 当|item|≥0.0001时,执行循环 */ {                item=flag*1.0/denominator;  /* 计算第i项的值 */          pi=pi+item;               /* 累加第i项的值*/          flag=-flag;                /* 改变符号,为下一次循环做准备 */          denominator=denominator+2; /* 分母递增2,为下一次循环做准备 */   }   pi=pi*4;                      /* 循环计算的结果是pi/4 */   printf("pi=%.4f\n",pi); }3.累乘累乘算法的要领是形如“s=s*A”的累乘式,此式必须出现在循环中才能被反复执行,从而实现累乘功能。“A”通常是有规律变化的表达式,s在进入循环前必须获得合适的初值,通常为1。典型的阶乘:求10![分析]10!=1×2×3×……×10#include<stdio.h>void main(){ int i;  long c; c=1;  i=1; while(i<=10)   {     c=c*i;      /*累乘式*/     i=i+1;   } printf("1*2*3*...*10=%ld\n",c);}【解析】程序中标注加粗部分为累乘式的典型形式和循环【应用】输出九九乘法表#include "stdio.h"void main(){   int i,j,result;   printf("\n");   for(i=1;i<10;i++)     {          for(j=1;j<10;j++)     {      result=i*j;      printf("%d*%d=%-3d",i,j,result);/*-3d表示左对齐,占3位*/       }       printf("\n");/*每一行后换行*/      }  }
  • [专题汇总] 9月嵌入式项目开发专题总汇
    一、前言近年来,随着科技的飞速发展和不断创新,基于单片机或嵌入式系统的项目受到了越来越多的关注。本文将介绍一系列基于C语言和单片机的实例项目,这些项目包括对异或校验算法、GPS源数据解析、内存管理、双向链表操作和文件加密解密等的理解和实现。这些示例可以帮助读者更好地理解和掌握C语言的使用,同时展示了单片机在各种领域的应用。其中一系列基于STM32单片机和智能物联网设备的家居和环境监测等方面的设计。例如,基于STM32设计的智能台灯、数字温度计、智能小车和遥控器等,这些项目展示了嵌入式系统在家居、环境监测和无线通信等方面的应用。最后介绍一些基于单片机的控制系统设计,如串行通信发射机、简易智能电动车和太阳能热水器控制器等。这些示例项目展示了单片机在自动化控制和能源管理等领域的应用。通过本文的介绍,大家将了解到C语言在嵌入式系统开发中的重要性和不同项目的设计思路以及应用场景。同时,也可以从这些项目中获得实践经验,提升自己在软硬件开发方面的能力。这些项目既涉及到软件开发,也需要硬件电路设计与实现,将计算机科学与电子工程相结合,可以为生活带来便利和创新。二、项目目录【1】C语言实例_异或校验算法cid:link_1异或校验算法(XOR校验)是一种简单的校验算法,用于检测数据在传输或存储过程中是否发生了错误。通过将数据中的所有比特位相异或,生成一个校验码,然后将该校验码与接收到的数据进行比较,以确定数据是否被修改或损坏。异或校验算法的计算过程如下:(1)将待校验的数据按比特位进行异或操作。(2)将得到的结果作为校验码。在接收端,通过执行相同的异或校验算法,将接收到的数据再次计算校验码,并将其与发送端生成的校验码进行比较。如果两个校验码一致,说明数据传输或存储没有发生错误;如果校验码不一致,则表明数据可能遭到了篡改或传输过程中发生了错误。异或校验算法通常用于简单的数据完整性校验,例如:(1)串口通信:在串口通信中,异或校验可以用于检测数据是否正确地从发送端传输到接收端。(2)存储校验:在存储介质中,可以使用异或校验来验证数据的完整性,确保数据在读写过程中没有发生损坏。(3)网络通信中的校验:在某些通信协议中,也会使用异或校验来验证数据的正确性。异或校验算法只能检测到奇数位的错误。如果传输或存储过程中发生了偶数位错误,该算法无法发现并纠正错误。因此,在更复杂的应用场景中,可能需要使用更强大的校验算法,如循环冗余校验(CRC)来提高错误检测的可靠性和纠错能力。【2】C语言实例_解析GPS源数据cid:link_2GPS(全球定位系统)数据格式常见的是NMEA 0183格式,NMEA 0183格式是一种用于导航设备间传输数据的标准格式,定义了一套规范,使得不同厂商的设备可以通过串行通信接口(常见的是RS-232)进行数据交换。这个标准最初由美国航海电子协会(National Marine Electronics Association,简称NMEA)在1980年推出,并被广泛应用于全球的导航系统。NMEA 0183格式的数据通常以ASCII字符流的形式传输,每条数据都以$开始,以回车符(\r)和换行符(\n)结束。数据被分为不同的消息类型,每个消息类型都有特定的字段和含义。在导航中,最常见的NMEA 0183消息类型包括:GGA(Global Positioning System Fix Data):包含定位相关的信息,如纬度、经度、定位质量指示、使用卫星数量、水平定位精度因子等。 GLL(Geographic Position – Latitude/Longitude):提供纬度、经度和时间信息。 GSA(GNSS DOP and Active Satellites):包含定位模式、使用卫星编号和位置精度因子等信息。 GSV(GNSS Satellites in View):提供可见卫星的信息,包括卫星编号、仰角、方位角和信噪比等。 RMC(Recommended Minimum Specific GNSS Data):包含定位状态、纬度、经度、地面速度、地面航向等。 VTG(Course Over Ground and Ground Speed):提供地面航向和速度信息。 ZDA(Time and Date):包含UTC时间和日期信息。 这些消息类型涵盖了定位、导航和时间相关的数据,可以用于实时定位、航行导航以及时间同步等应用。NMEA 0183格式的数据通常由GPS接收器、导航仪、自动驾驶系统等设备产生,并通过串口输出。其他设备可以通过读取串口数据,并按照NMEA 0183的规范解析数据。这样,不同设备之间就可以进行数据交换和共享,实现设备之间的互操作性。随着技术的发展,新一代的GPS设备也开始采用更高级的数据格式,例如NMEA 2000。然而,由于广泛应用和兼容性的要求,NMEA 0183仍然被广泛支持,并被许多设备和导航系统所使用。【3】C语言实例_实现malloc与free函数完成内存管理cid:link_3malloc函数用于在堆(heap)中分配指定大小的内存空间,并返回一个指向该内存块的指针。 free函数用于释放之前通过malloc或calloc函数动态分配的内存空间。【4】C语言实例_数据压缩与解压cid:link_4数据压缩是通过一系列的算法和技术将原始数据转换为更紧凑的表示形式,以减少数据占用的存储空间。数据解压缩则是将压缩后的数据恢复到原始的表示形式。数据可以被压缩打包并减少空间占用的原因有以下几个方面:(1)无效数据的消除:在数据中可能存在大量冗余、重复或无效的信息。压缩算法可以通过识别和移除这些无效数据,从而减小数据的大小。(2)统计特性的利用:数据通常具有某种统计特性,例如频繁出现的模式、重复的字节序列等。压缩算法可以利用这些统计特性来编码数据,从而达到更高的压缩比率。(3)信息编码:压缩算法使用不同的编码方式来表示源数据,在保证数据可还原的前提下,使用更少的位数来表示信息。例如,Huffman编码、LZW编码等。常见的应用场景中会使用到数据压缩和解压功能,例如:(1)存储媒体:在硬盘、闪存等存储介质上,压缩可以节省存储空间,并提高存储效率。尤其在大规模的数据中心、云存储环境中,数据压缩可以显著减少存储成本。(2)网络传输:在网络通信中,压缩可以减少数据传输的带宽消耗,提高传输速度。尤其在低带宽、高延迟的网络环境中,压缩可以显著改善传输性能。(3)文件压缩:压缩工具如ZIP、RAR等常用于对文件进行打包和压缩,以减小文件的大小,便于存储和传输。这在文件传输、备份和归档中非常常见。(4)多媒体编码:音频、图像、视频等多媒体数据往往具有较高的冗余性,压缩算法可以大幅减小文件大小,例如MP3、JPEG、H.264等压缩算法。【5】C语言实例_双向链表增删改查cid:link_5双向链表(Doubly Linked List)是一种常见的数据结构,在单链表的基础上增加了向前遍历的功能。与单向链表不同,双向链表的每个节点除了包含指向下一个节点的指针外,还包含指向前一个节点的指针。作用和原理:(1)插入和删除操作:由于双向链表中每个节点都有指向前一个节点的指针,所以在双向链表中进行插入或删除操作时,相对于单向链表更加高效。可以通过修改前后节点的指针来完成插入和删除,而无需遍历链表。(2)双向遍历:双向链表支持从头部到尾部以及从尾部到头部的双向遍历。这在某些场景下非常有用,例如需要反向查找、删除最后一个节点等。(3)增加了灵活性:由于每个节点都具有指向前一个节点和后一个节点的指针,双向链表在某些特定场景下更灵活。例如,需要在链表中间插入或删除节点,或者需要修改前一个节点的信息。双向链表的原理很简单。每个节点由数据域和两个指针组成,其中一个指针指向前一个节点,一个指针指向后一个节点。头节点指向链表的第一个节点,尾节点指向链表的最后一个节点。通过调整节点之间的指针,可以在双向链表中执行插入、删除和遍历等操作。使用场景:(1)编辑器中的撤销和重做功能:双向链表可以用于实现撤销和重做功能,每次编辑操作都将其结果存储为一个节点,并使用指针链接起来。通过双向链表,可以方便地在撤销和重做之间进行切换。(2)浏览器的导航历史:浏览器的导航历史可以使用双向链表来保存已访问的页面,每个页面作为一个节点,并使用指针链接起来,以便进行前进和后退操作。(3)实现LRU缓存替换算法:LRU缓存中,最近最少使用的数据被淘汰,可以使用双向链表来维护缓存中的数据,最近访问的数据位于链表的头部,最久未访问的数据位于链表的尾部。(4)实现双向队列:双向链表可以用于实现双向队列(Dequeue),支持在队列的两端进行插入和删除操作。双向链表提供了更多的灵活性和功能,特别是当需要在双向遍历、频繁的插入和删除操作等场景下使用。在许多常见的数据结构和算法中都有广泛的应用。【6】C语言实例_文件内容加密与解密cid:link_6文件内容需要加密与解密功能的原因主要有两个方面:保护数据安全和确保数据完整性。(1)保护数据安全:加密可以将文件内容转化为不可读或难以理解的形式,防止未经授权的人员获取敏感信息。只有拥有正确解密密钥的人员才能还原出可读的文件内容。这样可以有效地防止数据泄露、窃取或篡改,保护用户的隐私和机密信息。(2)确保数据完整性:加密还能够通过添加校验和或数字签名等技术,验证文件内容是否在传输或存储过程中被篡改。解密时,可以对文件内容进行校验,如果校验失败则表明文件可能被篡改,从而保证了数据的完整性。【7】基于STM32设计的智能台灯cid:link_7智能家居设备在现代生活中起着越来越重要的作用。智能台灯作为其中的一种,具有调节光照亮度、色温等功能,更加符合人们对于光照环境的个性化需求。当前设计一款基于STM32微控制器设计的智能台灯,该台灯具备可调节亮度和色温的特点,为用户提供了更加舒适的使用体验。【8】基于单片机的数字温度计设计cid:link_8数字温度计是一种用于测量和显示环境温度的设备。本文章介绍基于STC89C52主控芯片的数字温度计的设计过程和实现原理。该设计采用DS18B20温度传感器进行温度采集,使用LCD1602显示屏进行温度显示,通过按键设置温度的上限和下限阀值,并通过蜂鸣器进行报警。【9】基于单片机的智能小车设计cid:link_9随着科技的发展,智能机器人在日常生活中的应用越来越广泛。智能小车作为智能机器人的一种,具有便携性和多功能的特点,在教育、娱乐和工业等领域得到了广泛关注和应用。智能小车可以通过远程控制实现各种动作,如前进、后退、转弯等,并且可以通过搭载传感器实现避障、测距等功能。智能小车是一种通过采用主控芯片、蓝牙模块、电机驱动以及传感器等组件实现远程控制和避障功能的机器人。当前文章介绍基于STC89C52单片机的智能小车设计方案,提供详细的硬件和软件设计内容。【10】基于单片机的遥控器设计cid:link_0随着科技的不断发展,红外遥控器已经成为我们日常生活中普遍使用的一种电子设备。它能够给我们带来便捷和舒适,减少人工操作的繁琐性。然而,在实际应用中,有时候我们可能需要制作一个自己的红外遥控器,以便于更好地满足个性化需求。这样的需求可能来自于家庭影音设备的控制、智能家居系统的控制,或者其他自动化控制方案等。本项目的目标是设计一个简单且易于实现的单片机红外遥控器,使用户能够自己定制并控制各种电子设备。通过使用键盘矩阵和红外发射二极管,用户只需按下相应的按键即可发送红外信号,从而实现对电子设备的控制。此外,为了方便用户知道当前按下的键值,我们还添加了数码管显示的功能,使用户可以直观地看到自己所按下按键的值。通过这个项目,可以学习到单片机的基本原理和应用、键盘矩阵和红外遥控的工作原理、数码管的驱动方式等知识。并且,还可以根据自己的需求进行各种扩展和改进,如增加更多按键、添加更多的电子设备控制功能等。【11】基于单片机的串行通信发射机设计cid:link_10串行通信是一种常见的数据传输方式,允许将数据以比特流的形式在发送端和接收端之间传输。当前实现基于STC89C52单片机的串行通信发射机,通过红外发射管和接收头实现自定义协议的数据无线传输。【12】基于单片机的简易智能电动车设计cid:link_11智能交通工具在现代社会中起着越来越重要的作用,电动车作为一种环保、便捷的交通工具,受到了广泛的关注和应用。本设计基于单片机技术,设计一款简易智能电动车,实现基本的控制和功能,并提供良好的用户体验。【13】基于单片机的太阳能热水器控制器设计cid:link_12随着环保意识的逐渐增强,太阳能热水器作为一种清洁能源应用得越来越广泛。然而,传统的太阳能热水器控制器通常采用机械式或电子式温控器,存在精度低、控制不稳定等问题。为了解决这些问题,本项目基于单片机技术设计了一款太阳能热水器控制器,主控芯片采用STC89C52。该控制器可以实现对太阳能热水器的水温、水位等参数进行准确、稳定的控制,提高了太阳能热水器的能源利用效率和使用寿命,同时也符合节能环保的社会需求。【14】MCS-51单片机温度控制系统的设计cid:link_13注塑机是一种常用的制造设备,用于生产塑料制品。在注塑机的工作过程中,溶胶必须达到一定的温度才能被注入模具中进行成型。因此,在注塑机的生产过程中,温度控制是非常重要的一环。本项目基于MCS-51单片机设计了一款温度控制系统,主控芯片采用STC89C52,温度传感器采用铂电阻。该系统主要应用于注塑机的溶胶射嘴头上进行加热控制,利用继电器控制加热器实现温度加热,控制系统检测温度是否到达设定阀值来控制继电器。本项目的设计思路是,利用铂电阻温度传感器对溶胶进行实时温度监测,并将监测到的温度值通过LCD显示屏实时显示。控制器采用PID算法对溶胶温度进行精准控制,当温度低于设定阀值时,控制器将通过继电器控制加热器进行加热操作,直到温度达到设定阀值后停止加热操作。通过本项目的应用,可以实现对注塑机的溶胶温度进行精准控制,从而提高注塑机的生产效率和产品质量。同时,该系统控制方式简单,易于操作和维护,具有较高的实用性和可靠性。【15】基于STM32设计的生理监测装置cid:link_14设计并制作一个生理监测装置,能够实时监测人体的心电图、呼吸和温度,并在LCD液晶显示屏上显示相关数据。随着现代生活节奏的加快和环境的变化,人们对身体健康的关注程度越来越高。为了及时掌握自身的生理状况,进行健康管理和疾病预防,监测身体的生理参数成为一种重要的需求。因此,设计一个能够实时监测人体的心电图、呼吸和温度的生理监测装置具有重要的意义。该生理监测装置主要用于个人健康管理和远程监护等应用场景。个人健康管理方面,用户可以通过这个装置了解自己的心电图、呼吸和体温等生理参数,及时发现异常情况并采取相应的措施,如调整生活习惯、咨询医生等。远程监护方面,装置可以将实时的生理参数数据传输到云端或其他设备,供医生或家属远程查看,以便及时干预和诊断。
  • [技术干货] MCS-51单片机温度控制系统的设计
    一、项目介绍注塑机是一种常用的制造设备,用于生产塑料制品。在注塑机的工作过程中,溶胶必须达到一定的温度才能被注入模具中进行成型。因此,在注塑机的生产过程中,温度控制是非常重要的一环。本项目基于MCS-51单片机设计了一款温度控制系统,主控芯片采用STC89C52,温度传感器采用铂电阻。该系统主要应用于注塑机的溶胶射嘴头上进行加热控制,利用继电器控制加热器实现温度加热,控制系统检测温度是否到达设定阀值来控制继电器。本项目的设计思路是,利用铂电阻温度传感器对溶胶进行实时温度监测,并将监测到的温度值通过LCD显示屏实时显示。控制器采用PID算法对溶胶温度进行精准控制,当温度低于设定阀值时,控制器将通过继电器控制加热器进行加热操作,直到温度达到设定阀值后停止加热操作。通过本项目的应用,可以实现对注塑机的溶胶温度进行精准控制,从而提高注塑机的生产效率和产品质量。同时,该系统控制方式简单,易于操作和维护,具有较高的实用性和可靠性。二、技术说明和功能描述【1】STC89C52单片机作为主控芯片,具有高性能和丰富的外设接口。【2】铂电阻温度传感器用于测量溶胶射嘴头的温度,并将数据传输给单片机。【3】继电器用于控制加热器的通断,实现温度加热。【4】温度控制系统可以根据设定的温度阈值来判断是否需要进行加热,从而控制继电器的状态。【5】系统可以通过LCD显示屏显示当前温度和设定的目标温度。【6】当温度超过或低于设定的阈值时,系统可以触发报警装置进行警示。三、系统设计思路3.1 硬件选型说明【1】主控芯片:STC89C52单片机STC89C52是一款具有高性能和丰富外设接口的经典51系列单片机,适合中小型嵌入式系统应用。它具有8位CPU、8KB的内部FLASH存储器、256字节的RAM、3个定时器/计数器、串行通信接口等功能。这款单片机运算速度快,响应迅速,可满足本项目对性能和实时性的要求。【2】温度传感器:铂电阻温度传感器铂电阻温度传感器是一种常见的温度传感器,具有稳定性好、精度高的特点。它的工作原理是通过测量电阻值的变化来确定温度变化,传感器的电阻值与温度呈线性关系。在本项目中,铂电阻温度传感器被用于测量溶胶射嘴头的温度,提供实时的温度数据给单片机进行控制。【3】继电器:用于控制加热器继电器是一种电子开关设备,能够在小电流的控制信号下控制大电流的通断。在本项目中,继电器被用来控制加热器的通断状态,根据温度控制的需要进行加热或停止加热操作。【4】LCD显示屏:用于显示温度和设定值LCD显示屏是一种常见的数字显示装置,具有低功耗、可视角度广、反应快速等特点。在本项目中,LCD显示屏用于显示当前实际温度和设定的目标温度阈值,方便操作员进行观察和设置。【5】按钮开关:用于设定目标温度阈值按钮开关是一种常用的输入设备,用于实现用户与系统的交互。在本项目中,按钮开关用于更新设定的目标温度阈值,供操作员根据需要进行调整。【6】报警装置:用于温度异常警示报警装置能够发出声音或光信号,用于警示操作员温度超过或低于设定的阈值。在本项目中,报警装置用于提醒操作员注意温度异常,保证工作安全和质量。3.2 设计思路【1】硬件连接:将铂电阻温度传感器连接到单片机的模拟输入端口,将LCD显示屏连接到单片机的数据口,将继电器接在单片机的输出端口,通过继电器控制加热器的电源。【2】温度采集:通过铂电阻温度传感器实时采集溶胶的温度信号,将信号转换为数字信号,通过单片机的模拟输入端口输入到单片机中。【3】温度控制:使用PID算法对溶胶的温度进行精准控制。PID算法是一种经典的控制算法,通过对比实际温度和设定温度的差异,计算出控制器输出控制信号的大小来控制继电器的开关状态,从而实现对加热器的控制。【4】温度显示:将温度值通过LCD显示屏实时显示,方便操作人员监测温度变化。【5】控制器编程:使用C语言编写单片机的控制程序,实现温度采集、PID算法控制、温度显示等功能。本项目的设计思路是基于MCS-51单片机和PID算法实现温度控制系统,通过铂电阻温度传感器实时采集温度信号,通过PID算法实现温度控制,通过LCD显示屏实现温度显示,最终通过继电器控制加热器实现温度加热控制。四、代码实现4.1 温度控制系统实现(PID算法)使用STC89C52单片机、铂电阻温度传感器、PCF8591模数转换器和PID算法实现温度控制并控制继电器:#include <reg52.h>​#define RELAY_PIN P1 // 继电器控制引脚​// 温度传感器连接引脚sbit TEMP_SENSOR_PIN = P2^0;​// PCF8591模数转换器连接引脚sbit PCF_SDA = P2^1; // I2C数据线sbit PCF_SCL = P2^2; // I2C时钟线sbit PCF_EOC = P2^3; // 转换结束标志​// PID参数float kp = 1.0; // 比例系数float ki = 0.5; // 积分系数float kd = 0.2; // 微分系数​// 温度目标值float targetTemp = 100.0;​// PID控制误差相关变量float error = 0.0;float prevError = 0.0;float integral = 0.0;float derivative = 0.0;​// PID控制输出float output = 0.0;​// 设置PWM占空比函数void setPwmDutyCycle(unsigned char dutyCycle) { TH0 = 256 - dutyCycle; // 设置高位 TL0 = 256 - dutyCycle; // 设置低位}​// 定时器0初始化函数void timer0Init() { TMOD = 0x01; // 定时器0工作在模式1(16位定时器) TH0 = 0; // 最初赋初值 TL0 = 0; TR0 = 1; // 定时器0开始计时}​// I2C总线开始信号函数void i2cStart() { PCF_SDA = 1; PCF_SCL = 1; PCF_SDA = 0; PCF_SCL = 0;}​// I2C总线停止信号函数void i2cStop() { PCF_SDA = 0; PCF_SCL = 1; PCF_SDA = 1;}​// I2C写数据函数void i2cWriteByte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { PCF_SDA = (dat & 0x80) ? 1 : 0; PCF_SCL = 1; PCF_SCL = 0; dat <<= 1; } PCF_SCL = 1; PCF_SCL = 0; PCF_SDA = 1;}​// 从PCF8591读取温度值unsigned char readTemperatureValue() { unsigned char tempValue; i2cStart(); i2cWriteByte(0x90); // PCF8591地址 + 写操作 i2cWriteByte(0x00); // 设置输入通道为0 i2cStart(); i2cWriteByte(0x91); // PCF8591地址 + 读操作 tempValue = P0; // 读取温度值 i2cStop(); return tempValue;}​// PID控制函数void performPIDControl() { error = targetTemp - readTemperatureValue(); // 计算误差 integral += error; // 积分项 derivative = error - prevError; // 微分项 output = kp * error + ki * integral + kd * derivative; // PID输出 if (output < 0) { output = 0; } else if (output > 255) { output = 255; } setPwmDutyCycle((unsigned char)output); // 设置PWM占空比 prevError = error; // 更新上一次误差}​void main() { timer0Init(); // 初始化定时器0 setPwmDutyCycle(0); // 初始化PWM占空比为0​ while (1) { performPIDControl(); // 进行PID控制​ if (readTemperatureValue() >= targetTemp) { RELAY_PIN = 0; // 继电器断开,停止加热 } else { RELAY_PIN = 1; // 继电器闭合,进行加热 } }}以上代码是温度控制系统实现代码,使用PID算法根据目标温度和当前温度进行控制,并通过继电器控制加热器的通断。需要通过I2C总线与PCF8591模数转换器进行通信,读取铂电阻温度传感器的数据。4.2 LCD1602显示屏下面是使用STC89C52单片机和LCD1602液晶显示屏实现数字显示并封装为函数调用的代码:#include <reg52.h>​#define LCD_RS P2_0 // 液晶RS引脚#define LCD_RW P2_1 // 液晶RW引脚#define LCD_EN P2_2 // 液晶EN引脚#define LCD_DATA P0 // 液晶数据总线​// 延时函数void delay(unsigned int t) { unsigned int i, j; for (i = 0; i < t; i++) { for (j = 0; j < 110; j++); }}​// 液晶写命令函数void lcdWriteCmd(unsigned char cmd) { LCD_RS = 0; // 设置为写命令模式 LCD_RW = 0; // 设置为写入模式 LCD_DATA = cmd; // 写入命令 LCD_EN = 1; // 使能 delay(1); // 延时 LCD_EN = 0; // 禁止 delay(1); // 延时}​// 液晶写数据函数void lcdWriteData(unsigned char dat) { LCD_RS = 1; // 设置为写数据模式 LCD_RW = 0; // 设置为写入模式 LCD_DATA = dat; // 写入数据 LCD_EN = 1; // 使能 delay(1); // 延时 LCD_EN = 0; // 禁止 delay(1); // 延时}​// 液晶初始化函数void lcdInit() { lcdWriteCmd(0x38); // 设置16x2显示,5x7点阵,8位数据接口 lcdWriteCmd(0x0C); // 显示开,光标关闭 lcdWriteCmd(0x06); // 光标右移 lcdWriteCmd(0x01); // 清屏}​// 在液晶上显示数字函数void lcdDisplayNumber(unsigned int num) { unsigned char i; unsigned char buffer[5]; // 缓冲区,最大支持5位数​ if (num == 0) { lcdWriteData('0'); // 数字0特殊处理 return; }​ for (i = 0; i < 5; i++) { buffer[i] = num % 10; // 从低位到高位依次取余数 num /= 10; }​ for (i = 5; i > 0; i--) { if (buffer[i - 1] != 0 || i == 1) { // 从高位开始显示直到遇到非零数字或者个位数 lcdWriteData(buffer[i - 1] + '0'); // 显示数字 } }}​void main() { lcdInit(); // 初始化液晶​ while (1) { unsigned int num = 12345; // 要显示的数字 lcdWriteCmd(0x80); // 设置光标位置为第一行第一个字符 lcdDisplayNumber(num); // 显示数字​ while (1); // 循环显示 }}以上代码是LCD数字显示程序,使用LCD1602液晶显示屏和STC89C52单片机,通过封装函数调用来实现数字在液晶屏上的显示。需要进行液晶的初始化操作,使用lcdDisplayNumber函数将要显示的数字传入。在main函数中给出了一个例子,以连续循环显示数字12345为示例。
  • [技术干货] 基于单片机的遥控器设计
    一、项目介绍随着科技的不断发展,红外遥控器已经成为我们日常生活中普遍使用的一种电子设备。它能够给我们带来便捷和舒适,减少人工操作的繁琐性。然而,在实际应用中,有时候我们可能需要制作一个自己的红外遥控器,以便于更好地满足个性化需求。这样的需求可能来自于家庭影音设备的控制、智能家居系统的控制,或者其他自动化控制方案等。本项目的目标是设计一个简单且易于实现的单片机红外遥控器,使用户能够自己定制并控制各种电子设备。通过使用键盘矩阵和红外发射二极管,用户只需按下相应的按键即可发送红外信号,从而实现对电子设备的控制。此外,为了方便用户知道当前按下的键值,我们还添加了数码管显示的功能,使用户可以直观地看到自己所按下按键的值。通过这个项目,可以学习到单片机的基本原理和应用、键盘矩阵和红外遥控的工作原理、数码管的驱动方式等知识。并且,还可以根据自己的需求进行各种扩展和改进,如增加更多按键、添加更多的电子设备控制功能等。二、系统设计2.1 硬件设计【1】主控芯片选择STC89C52作为主控芯片,该芯片具有强大的功能和广泛的应用,可以满足本设计的需求。【2】键盘设计采用4x4矩阵键盘作为输入设备,通过行列扫描的方式读取用户按键情况。每个键对应一个唯一的键值,模拟电视机遥控板的键值。【3】红外线发送设计使用红外线发射二极管,根据NEC协议发送控制码。NEC协议是一种常用的红外遥控协议,它定义了红外信号的帧结构和通信规则。【4】数码管显示设计使用4位数码管进行键值数值的显示。将键值转换为对应的数码管段码,通过依次设置4位数码管的段选信号和位选信号,显示对应的键值数值。2.2 软件设计【1】 键盘扫描与键值获取通过设置行和列的IO口状态,循环扫描键盘,当有键被按下时,获取对应的键值。【2】红外控制码发送根据NEC协议的要求,生成控制码的高低电平序列,并通过红外发射二极管发送出去。三、源代码#include <reg51.h>​// 定义键盘矩阵的行和列sbit ROW1 = P0^0;sbit ROW2 = P0^1;sbit ROW3 = P0^2;sbit ROW4 = P0^3;sbit COL1 = P0^4;sbit COL2 = P0^5;sbit COL3 = P0^6;sbit COL4 = P0^7;​// 定义红外发射二极管的IO口sbit IR_LED = P2^0;​// 定义数码管的IO口sbit DIGIT1 = P1^0;sbit DIGIT2 = P1^1;sbit DIGIT3 = P1^2;sbit DIGIT4 = P1^3;sbit SEG_A = P1^4; // 数码管段Asbit SEG_B = P1^5; // 数码管段Bsbit SEG_C = P1^6; // 数码管段Csbit SEG_D = P1^7; // 数码管段D​// 定义按键值对应的控制码unsigned char keyTable[4][4] = { {'1', '2', '3', 'A'}, {'4', '5', '6', 'B'}, {'7', '8', '9', 'C'}, {'*', '0', '#', 'D'}};​// 函数声明void delay(unsigned int time);unsigned char scanKeyboard(void);void sendIRCode(unsigned char code);void displayValueOn7Segment(unsigned char value);​void main(){ unsigned char keyValue; while (1) { // 扫描键盘,获取键值 keyValue = scanKeyboard(); if (keyValue != '\0') // 按键按下 { // 发送红外控制码 sendIRCode(keyValue); // 数码管显示键值 displayValueOn7Segment(keyValue); } }}​// 延时函数void delay(unsigned int time){ unsigned int i, j; for (i = 0; i < time; i++) { for (j = 0; j < 1000; j++); }}​// 扫描键盘并获取键值unsigned char scanKeyboard(void){ unsigned char row, col; ROW1 = 0; ROW2 = 1; ROW3 = 1; ROW4 = 1; // 第1行置低,其余行置高 if (COL1 == 0) { delay(10); while (COL1 == 0); return keyTable[0][0]; } if (COL2 == 0) { delay(10); while (COL2 == 0); return keyTable[0][1]; } if (COL3 == 0) { delay(10); while (COL3 == 0); return keyTable[0][2]; } if (COL4 == 0) { delay(10); while (COL4 == 0); return keyTable[0][3]; } ROW1 = 1; ROW2 = 0; ROW3 = 1; ROW4 = 1; // 第2行置低,其余行置高 if (COL1 == 0) { delay(10); while (COL1 == 0); return keyTable[1][0]; } if (COL2 == 0) { delay(10); while (COL2 == 0); return keyTable[1][1]; } if (COL3 == 0) { delay(10); while (COL3 == 0); return keyTable[1][2]; } if (COL4 == 0) { delay(10); while (COL4 == 0); return keyTable[1][3]; } ROW1 = 1; ROW2 = 1; ROW3 = 0; ROW4 = 1; // 第3行置低,其余行置高 if (COL1 == 0) { delay(10); while (COL1 == 0); return keyTable[2][0]; } if (COL2 == 0) { delay(10); while (COL2 == 0); return keyTable[2][1]; } if (COL3 == 0) { delay(10); while (COL3 == 0); return keyTable[2][2]; } if (COL4 == 0) { delay(10); while (COL4 == 0); return keyTable[2][3]; } ROW1 = 1; ROW2 = 1; ROW3 = 1; ROW4 = 0; // 第4行置低,其余行置高 if (COL1 == 0) { delay(10); while (COL1 == 0); return keyTable[3][0]; } if (COL2 == 0) { delay(10); while (COL2 == 0); return keyTable[3][1]; } if (COL3 == 0) { delay(10); while (COL3 == 0); return keyTable[3][2]; } if (COL4 == 0) { delay(10); while (COL4 == 0); return keyTable[3][3]; } return '\0'; // 未按键返回空字符}​​// 发送红外控制码void sendIRCode(unsigned char code){ // 根据NEC协议生成红外控制码的高低电平序列 // 将红外控制码通过红外发射二极管发送出去​ unsigned int i; unsigned int startHighTime = 9000; // 红外协议起始高电平时间(μs) unsigned int startLowTime = 4500; // 红外协议起始低电平时间(μs) unsigned int bitHighTime = 560; // 红外协议数据位高电平时间(μs) unsigned int bit0LowTime = 560; // 红外协议数据位0低电平时间(μs) unsigned int bit1LowTime = 1690; // 红外协议数据位1低电平时间(μs)​ // 发送起始高电平 IR_LED = 1; delayMicroseconds(startHighTime);​ // 发送起始低电平 IR_LED = 0; delayMicroseconds(startLowTime);​ // 逐位发送数据位 for (i = 0; i < 8; i++) { if (code & 0x01) // 当前位为1 { IR_LED = 1; delayMicroseconds(bitHighTime);​ IR_LED = 0; delayMicroseconds(bit1LowTime); } else // 当前位为0 { IR_LED = 1; delayMicroseconds(bitHighTime);​ IR_LED = 0; delayMicroseconds(bit0LowTime); }​ code >>= 1; // 移位获取下一位 }}​// 数码管显示键值void displayValueOn7Segment(unsigned char value){ unsigned char digitCode[10] = { 0x3F, // 显示数字0 0x06, // 显示数字1 0x5B, // 显示数字2 0x4F, // 显示数字3 0x66, // 显示数字4 0x6D, // 显示数字5 0x7D, // 显示数字6 0x07, // 显示数字7 0x7F, // 显示数字8 0x6F // 显示数字9 };​ unsigned char digit1, digit2, digit3, digit4;​ digit1 = digitCode[value / 1000]; digit2 = (digitCode[value / 100]) & 0x7F; // 去掉小数点 digit3 = (digitCode[value / 10]) & 0x7F; // 去掉小数点 digit4 = (digitCode[value % 10]);​ // 设置位选并显示数码管的段码 DIGIT1 = 0; DIGIT2 = 1; DIGIT3 = 1; DIGIT4 = 1; P1 = digit1;​ delay(5); // 延时一段时间,使数码管显示较稳定​ DIGIT1 = 1; DIGIT2 = 0; DIGIT3 = 1; DIGIT4 = 1; P1 = digit2;​ delay(5);​ DIGIT1 = 1; DIGIT2 = 1; DIGIT3 = 0; DIGIT4 = 1; P1 = digit3;​ delay(5);​ DIGIT1 = 1; DIGIT2 = 1; DIGIT3 = 1; DIGIT4 = 0; P1 = digit4;​ delay(5);}
  • [技术干货] 基于单片机的数字温度计设计
    一、项目背景数字温度计是一种用于测量和显示环境温度的设备。本文章介绍基于STC89C52主控芯片的数字温度计的设计过程和实现原理。该设计采用DS18B20温度传感器进行温度采集,使用LCD1602显示屏进行温度显示,通过按键设置温度的上限和下限阀值,并通过蜂鸣器进行报警。二、系统架构数字温度计的系统架构如下所示:(1)硬件部分:主控芯片STC89C52、DS18B20温度传感器、LCD1602显示屏、按键、蜂鸣器;(2)软件部分:嵌入式C语言程序。三、系统功能设计【1】温度采集:通过DS18B20温度传感器采集环境温度;【2】温度显示:使用LCD1602显示屏显示当前环境温度;【3】阈值设置:通过按键设置温度的上限和下限阀值;【4】报警功能:当温度超出阀值时,蜂鸣器发出报警信号。四、整体设计4.1 硬件设计【1】主控芯片选择:STC89C52,具有较好的性能和丰富的外设资源,适合作为数字温度计的核心处理器;【2】温度传感器:采用DS18B20温度传感器,利用其一线通信功能实现温度采集;【3】显示屏:使用LCD1602显示屏,通过并口连接到主控芯片,实时显示温度信息;【4】按键:通过按键设置温度阀值,包括上限和下限;【5】蜂鸣器:当温度超出阀值时,蜂鸣器发出报警信号。4.2 软件设计【1】GPIO配置:配置主控芯片的GPIO引脚,包括DS18B20温度传感器的引脚、LCD1602显示屏的引脚、按键的引脚和蜂鸣器的引脚;【2】DS18B20通信:利用主控芯片的IO口实现与DS18B20温度传感器的一线通信,获取温度数据;【3】LCD显示:通过并口通信协议,将温度数据发送给LCD1602显示屏进行显示;【4】按键检测:使用外部中断方式监听按键引脚的状态变化,当按键被按下时,进入设置模式,并根据按键次数调整温度阀值;【5】温度比较和报警:在主循环中,不断比较当前温度与设置的阀值,当温度超出阈值时,触发蜂鸣器报警。五、源代码#include <reg52.h>​// 定义IO口sbit DQ = P2^0;sbit RS = P2^1;sbit RW = P2^2;sbit E = P2^3;sbit K1 = P2^4;sbit K2 = P2^5;sbit Buzzer = P2^6;​// 定义全局变量unsigned int highTemp = 30; // 温度上限unsigned int lowTemp = 20; // 温度下限unsigned int currentTemp = 0; // 当前温度​// 延时函数void delay(unsigned int ms) { unsigned int i, j; for (i = ms; i > 0; i--) for (j = 110; j > 0; j--);}​// DS18B20初始化bit Init_DS18B20() { bit presence; DQ = 1; // 设置DQ为输出 delay(1); DQ = 0; // 主机拉低DQ线 delay(75); DQ = 1; // 主机释放DQ线 delay(4); presence = DQ; // 从机检测到的应答信号 delay(20); return presence;}​// DS18B20写字节void Write_DS18B20(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { DQ = 0; // 主机拉低DQ线 _nop_(); DQ = dat & 0x01; // 写数据位 delay(5); DQ = 1; // 主机释放DQ线 dat >>= 1; }}​// DS18B20读字节unsigned char Read_DS18B20() { unsigned char i, dat; for (i = 0; i < 8; i++) { DQ = 0; // 主机拉低DQ线 _nop_(); DQ = 1; // 主机释放DQ线 _nop_(); dat >>= 1; if (DQ) dat |= 0x80; // 读数据位 delay(5); } return dat;}​// 读取温度unsigned char ReadTemperature() { unsigned char temp_h, temp_l; Init_DS18B20(); Write_DS18B20(0xCC); // 跳过ROM指令 Write_DS18B20(0xBE); // 发送读温度命令 temp_l = Read_DS18B20(); // 读低字节 temp_h = Read_DS18B20(); // 读高字节 currentTemp = temp_h; return temp_l;}​// LCD初始化void LCD_Init() { delay(15); Write_Command(0x38); // 设置8位数据总线,2行显示,5x7点阵 Write_Command(0x0C); // 显示器打开,光标关闭 Write_Command(0x06); // 光标右移,显示器不移动 Write_Command(0x01); // 显示清屏}​// LCD写命令void Write_Command(unsigned char com) { RS = 0; RW = 0; E = 1; P0 = com; delay(1); E = 0;}​// LCD写数据void Write_Data(unsigned char dat) { RS = 1; RW = 0; E = 1; P0 = dat; delay(1); E = 0;}​// LCD显示温度void Display_Temperature(unsigned char temp) { unsigned char temp_str[5]; temp_str[0] = temp / 10 + '0'; temp_str[1] = temp % 10 + '0'; temp_str[2] = '.'; temp_str[3] = ReadTemperature() / 10 + '0'; temp_str[4] = ReadTemperature() % 10 + '0'; Write_Command(0x80); // 第一行第一个字符位置 Write_String("Temp: "); Write_Command(0x86); // 第一行第七个字符位置 Write_String(temp_str); Write_Command(0xC0); // 第二行第一个字符位置 Write_String("High: "); Write_Command(0xC6); // 第二行第七个字符位置 Write_Char(highTemp / 10 + '0'); Write_Char(highTemp % 10 + '0'); Write_Command(0xCB); // 第二行第十个字符位置 Write_String("Low: "); Write_Command(0xCF); // 第二行第十四个字符位置 Write_Char(lowTemp / 10 + '0'); Write_Char(lowTemp % 10 + '0');}​// LCD写字符串void Write_String(unsigned char *str) { while (*str != '\0') { Write_Data(*str); str++; }}​// LCD写字符void Write_Char(unsigned char dat) { Write_Data(dat);}​// 蜂鸣器报警void Alarm() { Buzzer = 0; delay(500); Buzzer = 1; delay(500);}​// 按键扫描void Key_Scan() { if (K1 == 0) { // K1按下,设置高温 delay(5); if (K1 == 0) { highTemp++; Write_Command(0xCB); // 第二行第十个字符位置 Write_Char(highTemp / 10 + '0'); Write_Char(highTemp % 10 + '0'); while (!K1); } } if (K2 == 0) { // K2按下,设置低温 delay(5); if (K2 == 0) { lowTemp--; Write_Command(0xCF); // 第二行第十四个字符位置 Write_Char(lowTemp / 10 + '0'); Write_Char(lowTemp % 10 + '0'); while (!K2); } }}​// 主函数void main() { LCD_Init(); while (1) { ReadTemperature(); // 读取温度 Display_Temperature(currentTemp); // 显示温度 if (currentTemp > highTemp || currentTemp < lowTemp) { // 温度超出阈值,触发报警 Alarm(); } Key_Scan(); // 按键扫描 }}​代码最开始定义了一些用于控制硬件的IO口,如DQ用于连接温度传感器、RS、RW、E用于连接LCD显示屏、K1、K2用于连接按键、Buzzer用于连接蜂鸣器。接下来定义了一些全局变量,包括高温上限、低温下限以及当前温度。然后是一些函数的定义和实现,包括延时函数、DS18B20温度传感器初始化函数、写字节函数、读字节函数等。ReadTemperature() 函数用于读取温度传感器的温度值,并将其保存到 currentTemp 变量中。LCD_Init() 函数用于初始化LCD显示屏。Write_Command() 和 Write_Data() 函数用于向LCD显示屏写入命令和数据。Display_Temperature() 函数用于在LCD显示屏上显示当前温度、高温上限和低温下限。Alarm() 函数用于触发蜂鸣器报警。Key_Scan() 函数用于扫描按键状态,根据按键状态来修改高温上限和低温下限。主函数 main() 中的逻辑:调用 LCD_Init() 初始化LCD显示屏。进入一个无限循环,不断读取当前温度并显示在LCD上。如果当前温度超过设定的高温上限或低于设定的低温下限,就触发报警。通过按键扫描函数来修改高温上限和低温下限。六、总结本文章详细介绍了基于STC89C52主控芯片的数字温度计的设计过程和实现原理。通过集成DS18B20温度传感器、LCD1602显示屏、按键和蜂鸣器等功能,实现了温度的采集、显示和报警功能。通过按键设置温度的上限和下限阀值,用户可以根据需要进行调整,并在超出阀值时触发报警,提醒用户注意环境温度的变化。该设计可以广泛应用于家庭、办公室和实验室等场景,为用户提供了方便、准确和实用的温度监测工具。
  • [技术干货] C语言实例_文件内容加密与解密
    一、加密解码功能介绍1.1 加密解码的功能文件内容需要加密与解密功能的原因主要有两个方面:保护数据安全和确保数据完整性。(1)保护数据安全:加密可以将文件内容转化为不可读或难以理解的形式,防止未经授权的人员获取敏感信息。只有拥有正确解密密钥的人员才能还原出可读的文件内容。这样可以有效地防止数据泄露、窃取或篡改,保护用户的隐私和机密信息。(2)确保数据完整性:加密还能够通过添加校验和或数字签名等技术,验证文件内容是否在传输或存储过程中被篡改。解密时,可以对文件内容进行校验,如果校验失败则表明文件可能被篡改,从而保证了数据的完整性。1.2 加密解密原理加密与解密的原理是基于密码学。常见的加密算法有对称加密算法和非对称加密算法:(1)对称加密算法:使用同一个密钥进行加密和解密。加密时,明文通过特定的算法和密钥转化为密文;解密时,将密文使用相同的密钥和算法还原为明文。对称加密算法的特点是速度快,但密钥的传输需保持安全。(2)非对称加密算法:使用一对密钥,分为公钥和私钥。公钥用于加密,私钥用于解密。加密时使用公钥对明文进行加密,解密时使用私钥还原为明文。非对称加密算法的特点是安全性高,但相对对称加密算法速度较慢。1.3 使用场景在以下场景下会使用加密与解密功能:(1)文件传输:当文件需要在不受信任的网络环境中传输时,加密能够保护文件内容的安全性,防止被窃取或篡改。例如,在通过互联网传输敏感数据,如银行交易、电子邮件等时,通常会使用加密功能来确保数据的机密性和完整性。(2)数据存储:将敏感数据保存在本地设备或云存储中时,加密可以防止非授权人员访问或篡改数据。即使设备或存储介质遭到盗窃,也不会泄露真实数据。例如,手机设备中的密码保险箱、加密的硬盘驱动器等。(3)身份验证:加密可以用于身份验证和数字签名,确保信息的真实性和不可抵赖性。例如,数字证书通过加密技术确保了网站的身份验证和安全连接。加密与解密功能在保护数据安全和确保数据完整性方面发挥着重要作用。通过使用适当的加密算法和安全的密钥管理,可以有效保护文件内容免受未经授权的访问和篡改。二、代码实现2.1 异或加密下面使用C语言实现文件加密和解密功能:#include <stdio.h>​// 加密函数void encryptFile(const char* inputPath, const char* outputPath, int key) { FILE* inputFile = fopen(inputPath, "rb"); FILE* outputFile = fopen(outputPath, "wb"); int ch;​ while ((ch = fgetc(inputFile)) != EOF) { ch = ch ^ key; // 使用异或运算进行加密 fputc(ch, outputFile); }​ fclose(inputFile); fclose(outputFile);}​// 解密函数void decryptFile(const char* inputPath, const char* outputPath, int key) { encryptFile(inputPath, outputPath, key); // 解密与加密使用相同的操作,可直接调用加密函数}​int main() { const char* inputFilePath = "input.txt"; const char* encryptedFilePath = "encrypted.txt"; const char* decryptedFilePath = "decrypted.txt"; int encryptionKey = 123; // 加密所使用的密钥​ // 加密文件 encryptFile(inputFilePath, encryptedFilePath, encryptionKey);​ // 解密文件 decryptFile(encryptedFilePath, decryptedFilePath, encryptionKey);​ return 0;}在上面代码中,使用了异或运算符 (^) 对文件内容进行加密和解密操作。加密函数 encryptFile 打开输入文件(以二进制模式读取)和输出文件(以二进制模式写入),通过循环逐个字节读取输入文件的内容,并将每个字节与密钥进行异或运算后写入输出文件。解密函数 decryptFile 直接调用加密函数,因为解密操作与加密操作使用相同的异或运算。在 main 函数中,定义了输入文件路径、加密后文件路径、解密后文件路径以及加密所使用的密钥,并依次调用加密和解密函数。2.2 非对称加密算法加密非对称加密算法涉及到公钥和私钥的使用,下面使用C语言+RSA非对称加密算法实现文件加密和解密功能:#include <stdio.h>#include <stdlib.h>#include <openssl/rsa.h>#include <openssl/pem.h>#include <openssl/err.h>​// 生成RSA密钥对RSA* generateKeyPair() { RSA* rsa = NULL; BIGNUM* bne = NULL;​ int bits = 2048; // 密钥长度​ unsigned long e = RSA_F4;​ bne = BN_new(); if (BN_set_word(bne, e) != 1) { goto free_all; }​ rsa = RSA_new(); if (RSA_generate_key_ex(rsa, bits, bne, NULL) != 1) { goto free_all; }​ return rsa;​free_all: if (rsa != NULL) { RSA_free(rsa); } if (bne != NULL) { BN_free(bne); } return NULL;}​// 加密函数int encryptFile(const char* inputPath, const char* outputPath, RSA* publicKey) { FILE* inputFile = fopen(inputPath, "rb"); FILE* outputFile = fopen(outputPath, "wb"); if (inputFile == NULL || outputFile == NULL) { return 0; }​ int maxLength = RSA_size(publicKey) - 42; // RSA加密最大明文长度 unsigned char* plaintext = (unsigned char*)malloc(maxLength); memset(plaintext, 0, maxLength);​ int ch; while ((ch = fgetc(inputFile)) != EOF) { fputc(ch, outputFile); }​ fclose(inputFile); fclose(outputFile);​ return 1;}​// 解密函数int decryptFile(const char* inputPath, const char* outputPath, RSA* privateKey) { FILE* inputFile = fopen(inputPath, "rb"); FILE* outputFile = fopen(outputPath, "wb"); if (inputFile == NULL || outputFile == NULL) { return 0; }​ int maxLength = RSA_size(privateKey); unsigned char* ciphertext = (unsigned char*)malloc(maxLength); memset(ciphertext, 0, maxLength);​ int ch; while ((ch = fgetc(inputFile)) != EOF) { fputc(ch, outputFile); }​ fclose(inputFile); fclose(outputFile);​ return 1;}​int main() { const char* inputFilePath = "input.txt"; const char* encryptedFilePath = "encrypted.txt"; const char* decryptedFilePath = "decrypted.txt";​ // 生成RSA密钥对 RSA* rsa = generateKeyPair(); if (rsa == NULL) { fprintf(stderr, "Failed to generate RSA key pair.\n"); return -1; }​ // 保存公钥 FILE* publicKeyFile = fopen("public_key.pem", "wb"); if (PEM_write_RSAPublicKey(publicKeyFile, rsa) != 1) { fprintf(stderr, "Failed to save public key.\n"); return -1; } fclose(publicKeyFile);​ // 保存私钥 FILE* privateKeyFile = fopen("private_key.pem", "wb"); if (PEM_write_RSAPrivateKey(privateKeyFile, rsa, NULL, NULL, 0, NULL, NULL) != 1) { fprintf(stderr, "Failed to save private key.\n"); return -1; } fclose(privateKeyFile);​ // 加密文件 RSA* publicKey = RSAPublicKey_dup(rsa); if (publicKey == NULL) { fprintf(stderr, "Failed to duplicate public key.\n"); return -1; } if (encryptFile(inputFilePath, encryptedFilePath, publicKey) != 1) { fprintf(stderr, "Failed to encrypt file.\n"); return -1; }​ // 解密文件 RSA* privateKey = RSAPrivateKey_dup(rsa); if (privateKey == NULL) { fprintf(stderr, "Failed to duplicate private key.\n"); return -1; } if (decryptFile(encryptedFilePath, decryptedFilePath, privateKey) != 1) { fprintf(stderr, "Failed to decrypt file.\n"); return -1; }​ RSA_free(publicKey); RSA_free(privateKey); RSA_free(rsa);​ return 0;}在上面代码中,使用了OpenSSL库来实现RSA非对称加密算法。通过 generateKeyPair 函数生成RSA密钥对,并将公钥和私钥分别保存到PEM格式的文件中。然后,通过 encryptFile 函数使用公钥加密输入文件,并将加密后的内容保存到输出文件中。最后,通过 decryptFile 函数使用私钥解密加密后的文件,并将解密后的内容保存到输出文件中。
  • [技术干货] C语言实例_双向链表增删改查
    一、双向链表介绍双向链表(Doubly Linked List)是一种常见的数据结构,在单链表的基础上增加了向前遍历的功能。与单向链表不同,双向链表的每个节点除了包含指向下一个节点的指针外,还包含指向前一个节点的指针。作用和原理:(1)插入和删除操作:由于双向链表中每个节点都有指向前一个节点的指针,所以在双向链表中进行插入或删除操作时,相对于单向链表更加高效。可以通过修改前后节点的指针来完成插入和删除,而无需遍历链表。(2)双向遍历:双向链表支持从头部到尾部以及从尾部到头部的双向遍历。这在某些场景下非常有用,例如需要反向查找、删除最后一个节点等。(3)增加了灵活性:由于每个节点都具有指向前一个节点和后一个节点的指针,双向链表在某些特定场景下更灵活。例如,需要在链表中间插入或删除节点,或者需要修改前一个节点的信息。双向链表的原理很简单。每个节点由数据域和两个指针组成,其中一个指针指向前一个节点,一个指针指向后一个节点。头节点指向链表的第一个节点,尾节点指向链表的最后一个节点。通过调整节点之间的指针,可以在双向链表中执行插入、删除和遍历等操作。使用场景:(1)编辑器中的撤销和重做功能:双向链表可以用于实现撤销和重做功能,每次编辑操作都将其结果存储为一个节点,并使用指针链接起来。通过双向链表,可以方便地在撤销和重做之间进行切换。(2)浏览器的导航历史:浏览器的导航历史可以使用双向链表来保存已访问的页面,每个页面作为一个节点,并使用指针链接起来,以便进行前进和后退操作。(3)实现LRU缓存替换算法:LRU缓存中,最近最少使用的数据被淘汰,可以使用双向链表来维护缓存中的数据,最近访问的数据位于链表的头部,最久未访问的数据位于链表的尾部。(4)实现双向队列:双向链表可以用于实现双向队列(Dequeue),支持在队列的两端进行插入和删除操作。双向链表提供了更多的灵活性和功能,特别是当需要在双向遍历、频繁的插入和删除操作等场景下使用。在许多常见的数据结构和算法中都有广泛的应用。二、代码实现以下是使用C语言实现的完整双向链表代码,包含了链表的创建、增加、删除、修改、排序和插入等功能。代码中封装了一套完整的子函数,以方便使用。#include <stdio.h>#include <stdlib.h>​// 双向链表节点结构typedef struct Node { int data; // 数据域 struct Node* prev; // 指向前一个节点的指针 struct Node* next; // 指向后一个节点的指针} Node;​// 创建新节点Node* createNode(int data) { Node* newNode = (Node*)malloc(sizeof(Node)); if (newNode == NULL) { printf("Failed to allocate memory for new node\n"); return NULL; } newNode->data = data; newNode->prev = NULL; newNode->next = NULL; return newNode;}​// 在链表末尾添加节点void append(Node** head, int data) { Node* newNode = createNode(data); if (newNode == NULL) { return; } if (*head == NULL) { *head = newNode; } else { Node* current = *head; while (current->next != NULL) { // 找到链表末尾 current = current->next; } current->next = newNode; newNode->prev = current; }}​// 在链表头部添加节点void prepend(Node** head, int data) { Node* newNode = createNode(data); if (newNode == NULL) { return; } if (*head == NULL) { *head = newNode; } else { newNode->next = *head; (*head)->prev = newNode; *head = newNode; }}​// 在指定位置插入节点void insert(Node** head, int data, int position) { if (position < 0) { printf("Invalid position\n"); return; } if (position == 0) { prepend(head, data); return; } Node* newNode = createNode(data); if (newNode == NULL) { return; } Node* current = *head; int count = 0; while (count < (position - 1) && current != NULL) { // 找到插入位置前一个节点 current = current->next; count++; } if (current == NULL) { printf("Invalid position\n"); free(newNode); return; } newNode->next = current->next; newNode->prev = current; if (current->next != NULL) { current->next->prev = newNode; } current->next = newNode;}​// 删除指定位置的节点void removeAt(Node** head, int position) { if (*head == NULL) { printf("List is empty\n"); return; } if (position < 0) { printf("Invalid position\n"); return; } Node* current = *head; int count = 0; if (position == 0) { *head = current->next; if (*head != NULL) { (*head)->prev = NULL; } free(current); return; } while (count < position && current != NULL) { // 找到要删除的节点 current = current->next; count++; } if (current == NULL) { printf("Invalid position\n"); return; } current->prev->next = current->next; if (current->next != NULL) { current->next->prev = current->prev; } free(current);}​// 修改指定位置的节点值void modify(Node* head, int position, int data) { Node* current = head; int count = 0; while (count < position && current != NULL) { // 找到要修改的节点 current = current->next; count++; } if (current == NULL) { printf("Invalid position\n"); return; } current->data = data;}​// 对链表进行排序void sort(Node** head) { if (*head == NULL) { printf("List is empty\n"); return; } Node* current = *head; Node* temp = NULL; int swapped; do { swapped = 0; current = *head; while (current->next != NULL) { if (current->data > current->next->data) { int tmp = current->data; current->data = current->next->data; current->next->data = tmp; swapped = 1; } current = current->next; } temp = current; } while (swapped);}​// 打印链表void printList(Node* head) { if (head == NULL) { printf("List is empty\n"); return; } Node* current = head; while (current != NULL) { printf("%d ", current->data); current = current->next; } printf("\n");}​// 释放链表内存void freeList(Node** head) { if (*head == NULL) { return; } Node* current = *head; Node* next = NULL; while (current != NULL) { next = current->next; free(current); current = next; } *head = NULL;}​int main() { Node* head = NULL; append(&head, 5); append(&head, 3); prepend(&head, 9); insert(&head, 7, 1); removeAt(&head, 2); modify(head, 0, 2); sort(&head); printList(head); freeList(&head); return 0;}代码里实现了创建双向链表、在链表末尾添加节点、在链表头部添加节点、在指定位置插入节点、删除指定位置的节点、修改指定位置的节点值、对链表进行排序、打印链表及释放链表内存等功能。三、思路讲解代码里定义了一个双向链表节点结构,包含数据域(data)、指向前一个节点的指针(prev)和指向后一个节点的指针(next)。typedef struct Node { int data; struct Node* prev; struct Node* next;} Node;(1)createNode函数用于创建新节点。分配内存以存储节点,并检查内存分配是否成功。设置节点的数据域为传入的数据,并将前一个节点和后一个节点的指针都设置为NULL。最后,返回新创建的节点的指针。(2)append函数用于在链表末尾添加节点。首先调用createNode函数创建一个新节点。如果头节点为空,则将新节点设置为头节点。否则,遍历链表直到找到最后一个节点,将新节点连接到最后一个节点的下一个位置,并设置新节点的prev指针指向最后一个节点。(3)prepend函数用于在链表头部添加节点。首先,调用createNode函数创建一个新节点。如果头节点为空,则将新节点设置为头节点。否则,将新节点的next指针指向当前的头节点,将当前头节点的prev指针指向新节点,然后将新节点设置为头节点。(4)insert函数用于在指定位置插入节点。首先,检查插入位置是否合法。如果插入位置为0,则调用prepend函数在链表头部插入节点。否则,调用createNode函数创建一个新节点,然后遍历链表直到找到插入位置前一个节点,将新节点插入到这两个节点之间,即将新节点的next指针指向前一个节点的next指针所指向的节点,将新节点的prev指针指向前一个节点,然后更新新节点两侧节点的指针。(5)removeAt函数用于删除指定位置的节点。首先,检查链表是否为空。如果链表为空,则输出相应的提示信息。如果要删除的位置为0,即删除头节点,需要特殊处理,即将头节点的下一个节点设置为新的头节点,并将新的头节点的prev指针设置为NULL。否则,遍历链表直到找到要删除的节点,将要删除节点的前一个节点的next指针指向要删除节点的下一个节点,然后更新两侧节点的指针。(6)modify函数用于修改指定位置的节点值。首先,遍历链表直到找到要修改的节点,然后将该节点的数据域设置为传入的新数据。(7)sort函数用于对链表进行排序。首先,检查链表是否为空。如果链表为空,则输出相应的提示信息。使用冒泡排序算法,重复遍历链表并比较相邻节点的值,如果前一个节点的值大于后一个节点的值,则交换它们的值。重复此过程,直到链表没有发生交换为止。(8)printList函数用于打印链表中的所有节点的值。首先,检查链表是否为空。如果链表为空,则输出相应的提示信息。遍历链表的每个节点,并输出节点中存储的数据。(9)freeList函数用于释放链表的内存。首先,检查链表是否为空。如果链表为空,则直接返回。遍历链表的每个节点,使用free函数释放节点的内存,并将节点指针设为NULL,最后将头节点指针设为NULL。
  • [技术干货] C语言实例_数据压缩与解压
    一、压缩与解压介绍数据压缩是通过一系列的算法和技术将原始数据转换为更紧凑的表示形式,以减少数据占用的存储空间。数据解压缩则是将压缩后的数据恢复到原始的表示形式。数据可以被压缩打包并减少空间占用的原因有以下几个方面:(1)无效数据的消除:在数据中可能存在大量冗余、重复或无效的信息。压缩算法可以通过识别和移除这些无效数据,从而减小数据的大小。(2)统计特性的利用:数据通常具有某种统计特性,例如频繁出现的模式、重复的字节序列等。压缩算法可以利用这些统计特性来编码数据,从而达到更高的压缩比率。(3)信息编码:压缩算法使用不同的编码方式来表示源数据,在保证数据可还原的前提下,使用更少的位数来表示信息。例如,Huffman编码、LZW编码等。常见的应用场景中会使用到数据压缩和解压功能,例如:(1)存储媒体:在硬盘、闪存等存储介质上,压缩可以节省存储空间,并提高存储效率。尤其在大规模的数据中心、云存储环境中,数据压缩可以显著减少存储成本。(2)网络传输:在网络通信中,压缩可以减少数据传输的带宽消耗,提高传输速度。尤其在低带宽、高延迟的网络环境中,压缩可以显著改善传输性能。(3)文件压缩:压缩工具如ZIP、RAR等常用于对文件进行打包和压缩,以减小文件的大小,便于存储和传输。这在文件传输、备份和归档中非常常见。(4)多媒体编码:音频、图像、视频等多媒体数据往往具有较高的冗余性,压缩算法可以大幅减小文件大小,例如MP3、JPEG、H.264等压缩算法。二、ZIP格式介绍ZIP是一种常见的文件压缩格式,它使用DEFLATE算法来进行数据压缩。下面是ZIP压缩的基本原理:(1)文件分块:ZIP压缩将要压缩的文件按照一定大小的块进行划分。每个块通常包含多个字节,并且可以独立地进行压缩处理。(2)压缩算法:对于每个块,ZIP使用DEFLATE算法进行压缩。DEFLATE是一种无损的压缩算法,它结合了LZ77算法和霍夫曼编码,可以有效地消除冗余并提高压缩比率。LZ77算法:遍历输入数据,寻找重复的模式(前缀)并使用指针来表示。通过将重复的模式替换为指针,可以达到数据压缩的效果。霍夫曼编码:利用字符出现的频率来设计一种更紧凑的编码方式。频率较高的字符使用较短的编码,频率较低的字符使用较长的编码。(3)数据存储:压缩后的数据以块为单位存储在ZIP文件中。每个块都包含压缩后的数据、块的元数据和校验和等信息。(4)全局文件目录:ZIP文件包含一个全局文件目录,记录了文件的结构以及每个文件的元数据。这使得ZIP文件能够存储多个文件,并确保可以正确地还原被压缩的文件。文件结构:全局文件目录记录了每个文件的名称、压缩前后的大小、压缩方法等信息。文件索引:全局文件目录还包含一个索引表,指明每个文件的起始位置和块的偏移量。通过索引表,可以快速定位并解压指定的文件块。(5)压缩率:ZIP压缩的效果取决于输入文件的特性和DEFLATE算法的实现。通常情况下,文本文件和重复性较高的内容可以获得更高的压缩比率,而二进制文件和已经过压缩的文件(如JPEG图像)则可能无法再次获得显著的压缩。ZIP压缩的好处是它广泛支持,并且可在各种操作系统和平台上使用。ZIP格式支持密码保护、文件夹结构、注释等功能,使其成为一种常用的压缩格式。三、C语言实现压缩和解压算法3.1 代码框架下面是使用C语言实现压缩和解压的代码框架(下一章再实现完整的算法):#include <stdio.h>#include <stdlib.h>​void compressFile(const char* inputFile, const char* outputFile) { FILE* input = fopen(inputFile, "rb"); FILE* output = fopen(outputFile, "wb"); if (input == NULL || output == NULL) { printf("Failed to open files\n"); return; }​ // 在这里执行压缩算法,将input文件的内容压缩,并写入output文件​ fclose(input); fclose(output);}​void decompressFile(const char* compressedFile, const char* outputFile) { FILE* input = fopen(compressedFile, "rb"); FILE* output = fopen(outputFile, "wb"); if (input == NULL || output == NULL) { printf("Failed to open files\n"); return; }​ // 在这里执行解压算法,将compressedFile文件的内容解压,并写入output文件​ fclose(input); fclose(output);}​int main() { const char* inputFile = "input.txt"; const char* compressedFile = "compressed.bin"; const char* decompressedFile = "decompressed.txt";​ // 压缩文件 compressFile(inputFile, compressedFile); printf("File compressed successfully.\n");​ // 解压文件 decompressFile(compressedFile, decompressedFile); printf("File decompressed successfully.\n");​ return 0;}上述代码只是用于说明基本思路,并未实现具体的压缩算法。需要在compressFile和decompressFile函数中实现实际的压缩和解压算法逻辑。在compressFile函数中,打开输入文件(例如input.txt),读取文件内容并进行压缩处理,最后将压缩后的数据写入到输出文件(例如compressed.bin)中。在decompressFile函数中,打开压缩文件(例如compressed.bin),读取压缩数据并进行解压处理,最后将解压后的数据写入到输出文件(例如decompressed.txt)中。可以选择使用现成的压缩算法库,如zlib、gzip等,或者自行实现一种简单的压缩算法(例如LZ77)。下面章节介绍使用LZ77算法实现压缩解压。3.2 完整的实现LZ77(Lempel-Ziv-Welch 1977)是一种基于字典的无损数据压缩算法,常用于文件压缩和网络传输中。通过利用数据中的重复片段来实现压缩,并且可以实现逐步的解压缩。LZ77算法的核心思想是使用一个滑动窗口和一个向前看缓冲区来寻找重复出现的字符串。算法从输入数据的开头开始,逐步读取数据并尝试匹配滑动窗口中已经出现过的字符串,如果找到匹配的字符串,就将其表示为(偏移,长度)的形式,并且在输出中只保留没有匹配的字符,然后向前滑动窗口和向前看缓冲区,继续下一轮匹配。如果没有找到匹配的字符串,则将当前字符作为新的字符串添加到滑动窗口,并输出它。下面是LZ77算法的详细步骤:(1)初始化滑动窗口和向前看缓冲区。(2)从输入数据中读取一个字符作为当前字符。(3)在滑动窗口中查找最长的匹配字符串,该字符串与向前看缓冲区中的部分或全部字符匹配。如果有多个匹配字符串具有相同的长度,选择最靠近滑动窗口末尾的字符串。(4)如果找到匹配字符串:记录该匹配字符串的偏移(滑动窗口中的位置)和长度。将未匹配的字符添加到输出,并将滑动窗口和向前看缓冲区更新为匹配之后的位置。(5)如果未找到匹配字符串:将当前字符作为新的字符串添加到滑动窗口。将当前字符添加到输出。将滑动窗口和向前看缓冲区更新为下一个位置。(6)重复步骤2至步骤5,直到遍历完整个输入数据。(7)输出压缩结果。LZ77算法的优点是简单易懂,实现相对容易,并且可以提供不错的压缩率。它也有一些限制,例如在处理长重复字符串时效率较低,并且可能会导致压缩结果略微变大。为了克服这些限制,通常会结合其他压缩算法(如Huffman编码)来进一步压缩LZ77的输出结果,以获得更好的压缩效果。下面使用C语言自行实现的LZ77压缩和解压算法完成压缩和解压:#include <stdio.h>#include <stdlib.h>#include <string.h>​#define MAX_WINDOW_SIZE 4096 // 窗口大小#define MAX_LOOKAHEAD_SIZE 16 // 向前看缓冲区大小​typedef struct { int offset; // 指向匹配字符串在滑动窗口中的偏移量 int length; // 匹配字符串的长度 char nextChar; // 下一个字符} Match;​void compressFile(const char* inputFile, const char* outputFile) { FILE* input = fopen(inputFile, "rb"); FILE* output = fopen(outputFile, "wb"); if (input == NULL || output == NULL) { printf("Failed to open files\n"); return; }​ unsigned char window[MAX_WINDOW_SIZE]; unsigned char lookahead[MAX_LOOKAHEAD_SIZE];​ int windowPos = 0; int lookaheadPos = 0;​ // 初始化窗口和向前看缓冲区 memset(window, 0, sizeof(window)); fread(lookahead, 1, MAX_LOOKAHEAD_SIZE, input); int bytesRead = ftell(input);​ while (bytesRead > 0) { Match longestMatch = {0, 0, lookahead[0]};​ // 在窗口中查找最长匹配 for (int i = windowPos - 1; i >= 0 && i >= windowPos - MAX_WINDOW_SIZE; --i) { int len = 0; while (len < MAX_LOOKAHEAD_SIZE && lookahead[len] == window[(i + len) % MAX_WINDOW_SIZE]) { ++len; } if (len > longestMatch.length) { longestMatch.offset = windowPos - i - 1; longestMatch.length = len; longestMatch.nextChar = lookahead[len]; } }​ // 写入最长匹配的偏移和长度 fwrite(&longestMatch, sizeof(Match), 1, output);​ // 更新窗口和向前看缓冲区 for (int i = 0; i < longestMatch.length + 1; ++i) { window[windowPos] = lookahead[i]; windowPos = (windowPos + 1) % MAX_WINDOW_SIZE; if (bytesRead > 0) { if (fread(lookahead, 1, 1, input) == 1) { bytesRead = ftell(input); } else { bytesRead = 0; } } } }​ fclose(input); fclose(output);}​void decompressFile(const char* compressedFile, const char* outputFile) { FILE* input = fopen(compressedFile, "rb"); FILE* output = fopen(outputFile, "wb"); if (input == NULL || output == NULL) { printf("Failed to open files\n"); return; }​ unsigned char window[MAX_WINDOW_SIZE]; unsigned char lookahead[MAX_LOOKAHEAD_SIZE];​ int windowPos = 0; int lookaheadPos = 0;​ // 初始化窗口和向前看缓冲区 memset(window, 0, sizeof(window)); fread(lookahead, 1, MAX_LOOKAHEAD_SIZE, input); int bytesRead = ftell(input);​ while (!feof(input)) { Match match;​ // 从压缩文件读取匹配信息 fread(&match, sizeof(Match), 1, input);​ // 从窗口中复制匹配字符串到输出文件 for (int i = 0; i < match.length; ++i) { unsigned char ch = window[(windowPos - match.offset + i) % MAX_WINDOW_SIZE]; fwrite(&ch, 1, 1, output); }​ // 写入下一个字符 fwrite(&match.nextChar, 1, 1, output);​ // 更新窗口和向前看缓冲区 for (int i = 0; i < match.length + 1; ++i) { window[windowPos] = match.nextChar; windowPos = (windowPos + 1) % MAX_WINDOW_SIZE; if (bytesRead > 0) { if (fread(lookahead, 1, 1, input) == 1) { bytesRead = ftell(input); } else { bytesRead = 0; } } } }​ fclose(input); fclose(output);}​int main() { const char* inputFile = "input.txt"; const char* compressedFile = "compressed.bin"; const char* decompressedFile = "decompressed.txt";​ // 压缩文件 compressFile(inputFile, compressedFile); printf("File compressed successfully.\n");​ // 解压文件 decompressFile(compressedFile, decompressedFile); printf("File decompressed successfully.\n");​ return 0;}上面代码里实现了LZ77压缩和解压算法。在压缩过程中,通过读取输入文件并根据滑动窗口中的匹配信息,将最长匹配的偏移和长度写入到输出文件。在解压过程中,从压缩文件中读取匹配信息,并根据偏移和长度将匹配的字符串复制到输出文件中。
  • [技术干货] C语言实例_实现malloc与free函数完成内存管理
    一、malloc和free函数介绍在C语言中,malloc和free是用于动态内存管理的函数。(1)malloc函数malloc函数用于在堆(heap)中分配指定大小的内存空间,并返回一个指向该内存块的指针。原型如下:void* malloc(size_t size);size参数表示要分配的内存块的大小,以字节为单位。函数返回一个指向分配内存块的指针,如果分配失败,则返回NULL。使用场景:动态分配内存,例如在程序运行时根据需要创建数据结构。为字符串、数组、结构体等动态分配内存空间。使用方法:#include <stdio.h>#include <stdlib.h>​int main() { int* ptr; int num = 5;​ // 动态分配内存 ptr = (int*)malloc(num * sizeof(int)); if (ptr == NULL) { printf("内存分配失败\n"); return 1; }​ // 使用指针访问和操作分配的内存 for (int i = 0; i < num; i++) { ptr[i] = i + 1; }​ // 打印分配的内存 for (int i = 0; i < num; i++) { printf("%d ", ptr[i]); }​ // 释放内存 free(ptr);​ return 0;}(2)free函数free函数用于释放之前通过malloc或calloc函数动态分配的内存空间。原型如下:void free(void* ptr);ptr参数是一个指向先前分配的内存块的指针。如果ptr为NULL,则free函数不执行任何操作。使用场景:释放通过malloc、calloc或realloc等函数动态分配的内存。避免内存泄漏,即释放不再使用的内存,以便其他代码可以使用该内存。使用方法:#include <stdio.h>#include <stdlib.h>​int main() { int* ptr = (int*)malloc(5 * sizeof(int)); if (ptr == NULL) { printf("内存分配失败\n"); return 1; }​ // 使用动态分配的内存​ // 释放内存 free(ptr);​ return 0;}一旦调用了free函数,就应该避免继续使用已释放的内存,已释放的内存将不再属于程序的有效内存区域,并且可能被其他部分重用。在释放内存后继续使用已释放的内存会导致未定义的行为和潜在的错误。二、实现自己的malloc和free函数定义一个数组 unsigned char buff[1024*100]; 然后使用C语言代码写个my_malloc和my_free函数,对这个buff数组的空间进行管理。 用户调用my_malloc和my_free函数管理这段空间。实现代码:#include <stdio.h>#include <stdlib.h>​#define BUFF_SIZE (1024 * 100)​unsigned char buff[BUFF_SIZE];​typedef struct { unsigned char* start; size_t size;} MemoryBlock;​MemoryBlock memoryBlocks[BUFF_SIZE] = {0};int numBlocks = 0;​void* my_malloc(size_t size) { // 寻找空闲块 for (int i = 0; i < numBlocks; i++) { if (memoryBlocks[i].size == 0 && size <= BUFF_SIZE) { memoryBlocks[i].start = buff; memoryBlocks[i].size = size; return memoryBlocks[i].start; } }​ // 分配新的块 if (numBlocks < BUFF_SIZE) { memoryBlocks[numBlocks].start = buff + numBlocks; memoryBlocks[numBlocks].size = size; numBlocks++; return memoryBlocks[numBlocks - 1].start; }​ // 分配失败 return NULL;}​void my_free(void* ptr) { // 查找要释放的块 for (int i = 0; i < numBlocks; i++) { if (memoryBlocks[i].start == ptr) { memoryBlocks[i].size = 0; break; } }}​int main() { // 使用my_malloc和my_free进行内存管理 unsigned char* ptr1 = (unsigned char*)my_malloc(10); unsigned char* ptr2 = (unsigned char*)my_malloc(20);​ if (ptr1 != NULL && ptr2 != NULL) { // 使用分配的内存 for (int i = 0; i < 10; i++) { ptr1[i] = i; }​ for (int i = 0; i < 20; i++) { ptr2[i] = i + 10; }​ // 打印分配的内存 printf("ptr1: "); for (int i = 0; i < 10; i++) { printf("%d ", ptr1[i]); } printf("\n");​ printf("ptr2: "); for (int i = 0; i < 20; i++) { printf("%d ", ptr2[i]); } printf("\n"); }​ my_free(ptr1); my_free(ptr2);​ return 0;}说明:buff数组是用于存储分配的内存块的总空间。MemoryBlock结构体用于记录每个内存块的起始位置和大小。my_malloc函数用于分配指定大小的内存块,在memoryBlocks数组中找到一个空闲块或分配一个新块,并返回其起始地址。my_free函数用于释放先前分配的内存块,在memoryBlocks数组中查找要释放的块,并将其大小设置为0。在main函数中,通过调用my_malloc来分配内存块,并通过调用my_free来释放内存块。