🐶博主主页:@ᰔᩚ. 一怀明月ꦿ
❤️🔥专栏系列:线性代数,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"); #endif return 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"); #endif return 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");//模拟实现atoi printf("%d\n",ret); printf("%d\n",rat); return 0; } 结果: -1234 -1234
🐰offsetof
offsetof宏用于与求偏移量(结构体,联合体...)
offsetof的原型:
offsetof (type,member)
type:类型(结构体,联合体...)
member:成员(结构体成员...)文章来源:https://www.toymoban.com/news/detail-475014.html
#include<stdio.h> #include<stddef.h> typedef struct S { int a;//0-3 int b;//4-7 char c;//8 double 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-3 int b;//4-7 char c;//8 double 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: 00000000000000000000000000001010 0x55555555 01010101010101010101010101010101 只要让10按位与上0x55555555就能保留奇数位(相同为相同的,不同则为0) 奇数位结果:00000000000000000000000000000000 保存偶数位 保存的方法:例如:10(00000000000000000000000000001010) 10: 00000000000000000000000000001010 0xaaaaaaaa 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
🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸 文章来源地址https://www.toymoban.com/news/detail-475014.html
到了这里,关于C中的预处理,宏的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!