C语言高级编程技巧
C语言高级编程技巧简介掌握了C语言的基础知识后如何写出高效、安全、可维护的代码本文从代码优化、函数指针、内存管理、回调函数、Windows编程等多个高级主题出发结合实战经验分享C语言进阶编程的核心技巧。一、代码优化技巧1.1 CPU与位运算计算机对位操作的执行速度最快因为CPU本身就是专门执行位运算的器件。在性能敏感场景中应尽量将乘法和除法转换为位运算和加法。// 性能对比// 除法指令约 50 个机器周期// 位运算/加法约 1-2 个机器周期xn;// 等价于 x * 2^nxn;// 等价于 x / 2^n正数12*5;// 等价于 (12 2) 12// 清零操作a^a;// 结果为 01.2 数学公式替代循环// 优化前循环求和intsum0;for(inti1;in;i)sumi;// 优化后高斯定理intsumn*(n1)/2;1.3 表达式优化xx1;// 访问两次内存x1;// 访问一次内存更高效1.4 if-else 分支顺序优化在多重 if-else 判断中将概率最高的条件放在最前面可以减少平均判断次数。// 统计英文文档中字母数、空格数、数字数// 优化字母出现概率最高先判断字母if(isalpha(ch)){letterCount;}elseif(isdigit(ch)){digitCount;}elseif(isspace(ch)){spaceCount;}1.5 循环内不变量外提// 优化前每次循环都计算 strlenfor(i0;istrlen(buf);i){/* ... */}// 优化后constintlenstrlen(buf);for(i0;ilen;i){/* ... */}1.6 switch 的空间换时间switch语句会维护一张跳转表是典型的空间换时间策略。当分支较多且为离散值时switch比if-else链效率更高。注意switch不能判断范围此时只能使用if-else。二、函数指针2.1 函数指针基础函数名的本质是一个标号其值是存储该函数代码的内存空间首地址——函数名是一个函数指针常量类似于数组名。intadd(inta,intb){returnab;}intmain(void){int(*fPtr)(int,int);// 定义函数指针fPtradd;// 赋值函数名就是地址printf(%d\n,fPtr(2,5));// 调用方式1printf(%d\n,(*fPtr)(2,5));// 调用方式2return0;}2.2 函数指针的用途回调函数将函数指针作为参数传递策略模式运行时切换不同的处理逻辑函数表用数组存储函数指针通过索引调用// 函数表实现命令分发typedefvoid(*CommandFunc)(void);CommandFunc commands[]{cmd_open,cmd_close,cmd_save,cmd_exit};voidexecute(intcmd_id){if(cmd_id0cmd_id4){commands[cmd_id]();}}三、内存拷贝与重叠问题3.1 自实现 memcpy实现memcpy时必须考虑源地址和目标地址重叠的情况void*my_memcpy(void*dest,constvoid*src,size_tcount){char*pdest(char*)dest;constchar*psrc(constchar*)src;if(pdestpsrcpdestpsrccount){// 地址重叠从后向前拷贝for(size_ticount-1;i!(size_t)-1;--i)pdest[i]psrc[i];}else{// 正常情况从前向后拷贝for(size_ti0;icount;i)pdest[i]psrc[i];}returndest;}3.2 memmove vs memcpy// memmove能正确处理重叠区域// memcpy不保证重叠区域的正确性参数加 restrict 限定// 原则不确定是否重叠时用 memmove3.3 memmove_s安全版本// memmove_s 增加了目标缓冲区大小参数防止越界errno_tmemmove_s(void*dest,size_tdestCount,constvoid*src,size_tcount);四、可变参数函数4.1 可变参数函数的编写使用stdarg.h中提供的宏来实现#includestdarg.h#includestdio.hintmon_log(char*format,...){charstr_tmp[128];va_list vArgList;va_start(vArgList,format);intivsnprintf(str_tmp,sizeof(str_tmp),format,vArgList);va_end(vArgList);printf(%s\n,str_tmp);returni;}// 调用intmain(void){intimon_log(%s,%d,%d,%d,test,2,3,4);printf(写入字符数%d\n,i);return0;}4.2 四个关键宏宏功能va_list定义可变参数列表的指针va_start初始化可变参数列表va_arg逐个获取可变参数va_end清理可变参数列表4.3 格式化函数族sprintf(char*buf,constchar*fmt,...);// 输出到字符串fprintf(FILE*fp,constchar*fmt,...);// 输出到文件vsnprintf(char*buf,size_tn,constchar*fmt,va_list ap);// 可变参数版本五、NULL指针与野指针5.1 NULL 指针// C 中的定义#defineNULL((void*)0)// C 中的定义#defineNULL0// 操作 NULL 指针会导致段错误// 但 free(NULL) 是安全的不会出错int*pNULL;free(p);// 安全5.2 野指针的产生野指针的产生有两种主要途径1. 指针未初始化int*p;// 未初始化指向随机地址*p10;// 危险未定义行为// 正确做法int*pNULL;// 初始化为 NULL2. free/delete 后未置 NULLint*p(int*)malloc(sizeof(int)*10);free(p);// p 此时成为野指针指向的内存已被释放// 但 p 的值并不为 NULL// 正确做法free(p);pNULL;5.3 malloc 与 calloc 的区别// calloc malloc memset将内存初始化为零int*p(int*)calloc(10,sizeof(int));// 分配并清零int*q(int*)malloc(10*sizeof(int));// 仅分配内容不确定// 注意calloc 不保证内存中字符串长度为 0// char *p calloc(1, 0); strlen(p) 的结果不确定六、宏定义与 do-while(0) 模式6.1 宏的注意事项宏只是简单的文本替换不做类型检查宏参数要加括号防止优先级问题多行宏使用do { ... } while(0)包裹6.2 do-while(0) 的妙用在C语言函数中中途return容易忘记释放资源。而嵌套if-else又显得累赘。do-while(0)提供了优雅的解决方案// 问题代码嵌套过深retfunc1();if(ret0){retfunc2();if(ret0){retfunc3();// ...}}// 优化使用 do-while(0)do{retfunc1();if(ret!0)break;retfunc2();if(ret!0)break;retfunc3();if(ret!0)break;// 成功逻辑}while(0);// 统一的资源清理代码在这里执行6.3 调试宏#defineDEBUG1#ifdefDEBUG#defineLOG(fmt,...)printf([DEBUG] fmt\n,##__VA_ARGS__)#else#defineLOG(fmt,...)#endif七、回调函数7.1 回调函数的原理回调函数是通过函数指针调用的函数将函数作为参数传递给另一个函数在适当的时候被调用。7.2 异步场景中的回调在网络编程中异步发送消息后需要得到对方的响应。由于发送函数内部无法直接获取结果可以传入回调函数// 定义回调函数类型typedefvoid(*CallbackFunc)(void*context,intresult);// 注册回调voidasync_send(constchar*msg,CallbackFunc callback,void*context){// 发送消息...// 异步接收响应后调用回调callback(context,0);// 0 表示成功}// 使用示例voidon_response(void*ctx,intresult){printf(收到响应结果%d\n,result);}async_send(Hello,on_response,my_context);八、用C语言实现面向对象8.1 隐藏数据接口利用前向声明实现封装// conceal_data_type.h头文件typedefstructconceal_data_typeconceal_data_type_t;externintconceal_data_type_get_a(concel_data_type_t*obj);externvoidconceal_data_type_set_a(concel_data_type_t*obj,intval);// conceal_data_type.c源文件structconceal_data_type{inta;// 其他私有成员...};intconceal_data_type_get_a(concel_data_type_t*obj){returnobj-a;}外部文件只能通过接口函数访问结构体成员无法直接操作成员变量。8.2 用C语言实现继承与多态// 父类structparent{intdat1;intdat2;};// 子类继承父类将父类作为第一个成员structchild{structparentpar;// 必须是第一个成员intdat3;intdat4;};// 多态父类指针可以指向子类对象structchild*chcalloc(1,sizeof(structchild));structparent*par(structparent*)ch;// par-dat1 等价于 ch-par.dat18.3 零长数组柔性数组structmutable_array{intlen;chardata[0];// GNU 扩展零长数组};// sizeof(struct mutable_array) 432位系统structmutable_array*pmalloc(sizeof(structmutable_array)1024);p-len1024;memcpy(p-data,Hello,6);九、C语言中的奇特但有用的语法9.1 字符串字面值拼接constchar*strMyNameis Zhouwy;// 编译器自动拼接str MyNameis Zhouwy9.2 结构体指定成员初始化structstructTest{inta;intb;intc;};structstructTestvar{.a10,.b24,.c56};9.3 数组指定下标初始化intarr[10]{[4]67,[5]34};// arr[4] 67, arr[5] 34, 其余为 0十、Windows C编程要点10.1 临界区CRITICAL_SECTIONCRITICAL_SECTION cs;InitializeCriticalSection(cs);// 初始化EnterCriticalSection(cs);// 进入临界区// ... 访问临界资源 ...LeaveCriticalSection(cs);// 离开临界区DeleteCriticalSection(cs);// 删除临界区10.2 事件对象EventHANDLE hEventCreateEvent(NULL,TRUE,FALSE,NULL);// 创建事件SetEvent(hEvent);// 设置为有信号WaitForSingleObject(hEvent,INFINITE);// 等待信号ResetEvent(hEvent);// 重置为无信号10.3 互斥对象MutexHANDLE hMutexCreateMutex(NULL,FALSE,NULL);// 创建互斥锁WaitForSingleObject(hMutex,INFINITE);// 等待锁ReleaseMutex(hMutex);// 释放锁10.4 Windows 进程间通信方式特点邮槽Mailslot半双工类似匿名管道命名管道面向连接可靠传输支持跨机器通信匿名管道仅限父子进程间通信10.5 Windows 异常处理__try{// 可能出错的代码int*pNULL;*p10;}__except(EXCEPTION_EXECUTE_HANDLER){// 异常处理printf(捕获到异常\n);}__except参数的含义EXCEPTION_CONTINUE_EXECUTION (-1)忽略异常继续执行EXCEPTION_CONTINUE_SEARCH (0)不处理继续寻找上层处理EXCEPTION_EXECUTE_HANDLER (1)识别异常执行处理代码10.6 端口复用intopt1;// 在 bind 之前调用setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,(constvoid*)opt,sizeof(opt));端口复用的主要用途防止服务器重启时之前绑定的端口还未释放或程序异常退出时系统未释放端口。注意多个套接字绑定同一端口时只有最后一个套接字能正常接收数据。十一、内存安全编程原则11.1 核心原则malloc 分配的内存需要手动释放函数结束也不会自动释放free 后的内存内容不变但操作它会导致未定义行为堆内存越界可能在free时才报错难以定位访问非法内存不会立即报错可能在后续某条指令才崩溃11.2 内存调试建议当程序出现莫名其妙的崩溃时检查所有涉及内存操作的代码排查指针是否越界、是否使用已释放的内存使用调试工具如 Valgrind检测内存问题总结C语言的高级编程技巧涵盖了性能优化、内存安全、代码架构等多个层面。掌握这些技巧不仅能让你的代码更加高效和安全还能帮助你更好地理解计算机系统的底层运作机制。在实际开发中请牢记以下原则性能优化要有理有据优先优化热点代码内存管理要严格malloc 与 free 配对使用free 后置 NULL善用回调函数和函数指针实现灵活的代码架构利用do-while(0)等模式简化错误处理流程重视跨平台兼容性编写可移植的代码原始笔记来源frasight/王者归来笔记.c, jdah/StudyC.c