C中的预处理,宏

news/2024/11/29 3:50:22/

🐶博主主页:@ᰔᩚ. 一怀明月ꦿ 

❤️‍🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C++

🔥座右铭:“不要等到什么都没有了,才下定决心去做”

🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀

目录

🐰宏的缺点

🐰用宏实现动态开辟的技巧

🐰命名约定

🐰#undef

🐰条件编译

🌸#ifdef

🌸#ifndef

🌸#if defined(symbol)

🌸#if !defined(symbol)

🐰常见的条件编译指令

🌸#if

          🌸多分支的条件编译

🐰文件包含

🐰atoi

🐰offsetof

🐰模拟实现offsetof

🐰有关宏的习题

🌸写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。


🐰宏的缺点

1.每次使用宏的时候,会将宏定义的代码插入到程序当中,增加了程序的长度

2.宏没有办法调式

3.宏由于类型无关,也不够严谨

4.宏可能会带来运算符优先级的问题,导致程序容易出错。(可以适当添加圆括号来解决)

#include<stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
int Max(int x,int y)
{return x>y?x:y;
}
int main()
{int a=2,b=3;//函数printf("%d\n",Max(a, b));//宏printf("%d\n",MAX(a, b));return 0;
}

🐰用宏实现动态开辟的技巧

#include<stdio.h>
#include<stdlib.h>
#define MALLOC(x,type) (type*)malloc(x*sizeof(type))
int main()
{int *p=(int*)malloc(10*sizeof(int));//宏//这样使得动态开辟空间更加方便int* p2=MALLOC(10, int);//只需传递开辟空间的大小,和类型return 0;
}

🐰命名约定

标准并没有规定宏名必须全部大写,函数名不能全部大写,只是一种俗称的习惯,这样更容易区分宏和函数

把宏名全部大写

函数名不全部大写

🐰#undef

#undef用于移除一个宏定义

例如:

#undef用于移除一个宏定义
#include<stdio.h>
#define M 100
int main()
{printf("%d\n",M);
#undef M//M就被移除了printf("%d\n",M);return 0;
}

🐰条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令

调试性的代码,删除可惜,保留碍事,我们选择性编译

🌸#ifdef

#ifdef symbol//如果symbol定义,则执行#ifdef symbol和endif的语句...endif

例如:

#include<stdio.h>
#define __DEBUG__ 10
int main()
{int arr[10];for(int i=0;i<10;i++){arr[i]=i;
#ifdef __DEBUG__//如果没有定义__DEBUG__,则后面#endif之前的语句不编译printf("%d ",arr[i]);
#endif}return 0;
}
结果:
0 1 2 3 4 5 6 7 8 9
因为这里的__DEBUG__ 被定义了,所以会执行#ifdef和#endif之间的语句

🌸#ifndef

#ifndef symbol //如果symbol没定义,则执行#ifdef symbol和endif的语句...#endif
#endif
#include<stdio.h>
#define __DEBUG__ 10
int main()
{int arr[10];for(int i=0;i<10;i++){arr[i]=i;
#ifndef __DEBUG__printf("%d ",arr[i]);
#endif}return 0;
}
没有输出结果,因为__DEBUG__ 定义了,所以不会执行#ifndef和#endif之间的语句。这个和#ifdef和#endif执行的情况相反

🌸#if defined(symbol)

#if defined(symbol)//如果symbol定义,则执行#ifdef symbol和endif的语句...endif
#include<stdio.h>
#define __DEBUG__ 10
int main()
{int arr[10];for(int i=0;i<10;i++){arr[i]=i;
#if defined(__DEBUG__)printf("%d ",arr[i]);
#endif}return 0;
}
结果:0 1 2 3 4 5 6 7 8 9
因为定义了__DEBUG__,所以会执行#if defined(__DEBUG__)和#endif之间的代码,这个和#ifdef和endif使用情况一样

🌸#if !defined(symbol)

#if !defined(symbol)//如果symbol没定义,则执行#ifdef symbol和endif的语句...endif
#include<stdio.h>
#define __DEBUG__ 10
int main()
{int arr[10];for(int i=0;i<10;i++){arr[i]=i;
#if !defined(__DEBUG__)printf("%d ",arr[i]);
#endif}return 0;
}

🐰常见的条件编译指令

🌸#if

#if 常量表达式...#endif

如果常量表达式的值为真,则执行#if和#endif之间的代码 

#include<stdio.h>
int main()
{
#if 1==1//如果常量表达式的值为真,则执行#if和#endif之间的代码printf("hehe\n");
#endifreturn 0;
}
结果:
hehe
因为1==1表达式的值为真

🌸多分支的条件编译

#if 常量表达式...#elif 常量表达式...#elif 常量表达式...endif
#include<stdio.h>
int main()
{
#if 1==1//这里可以与if 和else if的联系,只会执行一个(一组)语句printf("hehe1\n");
#elif 1==1//printf("hehe2\n");
#elif 1==1//printf("hehe3\n");
#endifreturn 0;
}
结果:hehe1
因为第一个常量表达式1==1的值为真,所以执行printf("hehe2\n");,不会执行以下的分支语句了,这里与if和else if执行情况差不多

🐰文件包含

头文件的包含的方式

1.本地文件的包含

#include"test.h"

查找策略:先找在源文件所在的目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件

如果找不到就提示编译错误

2.库文件的包含

#include<stdio.h>

查找策略:直接去标准位置查找头文件,如果找不到就提示编译错

如果找不到就提示编译错误

注意:虽然库函数的头文件可以用双引号查找,但是进行了两次查找,降低了代码的速率

头文件多重包含(C++和类的多重继承类造成成员数据的重复)

解决方法:

添加#pragma once,可以防止头文件多刺被包含

 

🐰atoi

用于将字符串转换成整形

atoi的原型:

int atoi (const char * str);

const char * str:字符串的首地址

#include<stdio.h>
#include<stdlib.h>
int my_atoi(char* ptr)
{int sum=0;int flag=1;if(*ptr=='+'){flag=1;ptr++;}if(*ptr=='-'){flag=-1;ptr++;}while(*ptr){sum=sum*10+(*ptr-'0');ptr++;}sum*=flag;return sum;
}
int main()
{int ret=atoi("-1234");int rat=my_atoi("-1234");//模拟实现atoiprintf("%d\n",ret);printf("%d\n",rat);return 0;
}
结果:
-1234
-1234

🐰offsetof

offsetof宏用于与求偏移量(结构体,联合体...)

offsetof的原型:

offsetof (type,member)

type:类型(结构体,联合体...)

member:成员(结构体成员...)

#include<stdio.h>
#include<stddef.h>
typedef struct S
{int a;//0-3int b;//4-7char c;//8double d;//16-23
}S;
int main()
{printf("%d\n",offsetof(S, a));printf("%d\n",offsetof(S, b));printf("%d\n",offsetof(S, c));printf("%d\n",offsetof(S, d));return 0;
}
结果:
0
4
8
16
分别对应着a,b,c,d的偏移量

🐰模拟实现offsetof

#include<stdio.h>
#include<stddef.h>
typedef struct S
{int a;//0-3int b;//4-7char c;//8double d;//16-23
}S;
#define OFFSETOF(t,m) (int)(&((S*)0)->m)//把0地址处强制转化为结构体指针,然后取出成员的地址,在以整形的形式输出,这样就可以得到偏移量了,(换句话说,就是结构体的成员假设从0地址处进行存储)
//注意:强制类型转化,不会真的原来的类型转化了,而是把原来的类型进行临时拷贝,然后再转化为自己想要的类型,所以没有真正把系统0地址转化为结构体指针,而是把0地址的拷贝转化为了结构体指针类型。
int main()
{printf("%d\n",OFFSETOF(S,a));printf("%d\n",OFFSETOF(S,b));printf("%d\n",OFFSETOF(S,c));printf("%d\n",OFFSETOF(S,d));return 0;
}
结果:
0
4
8
16

🐰有关宏的习题

🌸写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。

解题思路:首先保存奇数位保存的方法:例如:10(00000000000000000000000000001010)10:            000000000000000000000000000010100x55555555     01010101010101010101010101010101只要让10按位与上0x55555555就能保留奇数位(相同为相同的,不同则为0)奇数位结果:00000000000000000000000000000000保存偶数位保存的方法:例如:10(00000000000000000000000000001010)10:            000000000000000000000000000010100xaaaaaaaa     10101010101010101010101010101010只要让10按位与上0xaaaaaaaa就能保留奇数位(相同为相同的,不同则为0)偶数位结果:00000000000000000000000000001010让奇数位左移一位00000000000000000000000000000000<<1得到00000000000000000000000000000000让偶数位右一位00000000000000000000000000001010>>1得到00000000000000000000000000000101然后移位后的奇数位和偶数位相加00000000000000000000000000000000+00000000000000000000000000000101最后的到00000000000000000000000000000101为5
#include<stdio.h>
#define SWAP(n) n=(((n&0x55555555)<<1)+((n&0xaaaaaaaa)>>1))
int main()
{int a=10;SWAP(a);printf("%d\n",a);return 0;
}
结果:
5
10的二进制补码:00000000000000000000000000001010
转化后的补码:00000000000000000000000000000101
所以为5

🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸   


http://www.ppmy.cn/news/41728.html

相关文章

【docx模块】python中可以处理word文档的模块

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 今天给大家带来docx模块得介绍以及使用~ 一.docx模块 Python可以利用python-docx模块处理word文档&#xff0c;处理方式是面向对象的。 也就是说python-docx模块会把word文档&#xff0c;文档中的段落、文本、字体等都看…

Elasticsearch新手向高手:GPT智能助手助你跃升技能巅峰

本文将从三个层次引导您如何利用GPT智能助手学习Elasticsearch&#xff0c;并提供详细的案例和经验分享。 一、初级程序员 学习基础知识&#xff1a;利用GPT了解Elasticsearch的核心概念&#xff0c;如倒排索引、分片、复制等&#xff0c;以及它如何实现高效搜索和存储。 案…

迅为龙芯2K0500全国产开发板

目录 龙芯2K0500处理器 动态电源管理 低功耗技术 产品开发更快捷 全国产设计方案 2K0500核心板 邮票孔连接 丰富接口 高扩展性 系统全开源 品质保障 行业应用 龙芯2K0500处理器 迅为iTOP-LS2K0500开发采用龙芯LS2K0500处理器&#xff0c;基于龙芯自主指令系统&#x…

数据类型。

数据类型分为简单数据类型&#xff08;值类型&#xff09;和复杂数据类型&#xff08;引用类型&#xff09;值类型&#xff1a;在存储时变量中存储的是值本身&#xff08;string、number、boolean、undefined、null&#xff08;null特殊&#xff0c;返回一个空的对象 object&am…

我们为什么需要分布式系统?

分布式系统解决了什么问题&#xff1f; 简单来说&#xff0c;分布式系统的出现&#xff0c;主要是为了解决单体系统的不足。 分布式系统解决了单机性能瓶颈导致的成本问题。由于摩尔定律失效&#xff0c;廉价PC机的性能瓶颈无法继续突破&#xff0c;虽然小型机和大型机能够实…

干货满满!MES生产制造管理全流程分析

阅读本文您将了解&#xff1a;1.什么是MES生产管理流程&#xff1b;2.MES生产管理流程具体步骤&#xff1b;3.实施MES生产管理流程优势&#xff1b;4.MES生产管理流程中可能会遇见的问题。 一、什么是MES生产管理流程 MES生产管理系统&#xff08;又称制造执行系统&#xff0…

时序分析 49 -- 贝叶斯时序预测(一)

贝叶斯时序预测&#xff08;一&#xff09; 时序预测在统计分析和机器学习领域一直都是一个比较重要的话题。在本系列前面的文章中我们介绍了诸如ARIMA系列方法&#xff0c;Holt-Winter指数平滑模型等多种常用方法&#xff0c;实际上这些看似不同的模型和方法之间都具有千丝万缕…

软件测试之测试分类

文章目录按测试对象分界面测试可靠性测试容错性测试文档测试兼容性测试易用性测试安装卸载测试安全测试性能测试内存泄露测试弱网测试按代码的查看情况黑盒测试白盒测试灰盒测试按开发阶段单元测试集成测试系统测试回归测试冒烟测试验收测试测试的大致分类&#xff1a; 下面就…