C语言 位运算 宏定义

一、位运算

首先,必须了解各种类型各占多少字节

char //1个字节
short //2个字节
int //4个字节
long int //4个字节
long long //8个字节

1.1、什么是位运算

从现代计算机中所有的数据二进制的形式储存在设备中。即0、1两种状态,计算机对二进制数进行的运算(+、-、*、/)都是位运算,即将符号位共同参与运算的运算。

1.2、 位运算概览

”&“ —与运算—当两个位同时为1的时候,才为1
”|“ —或运算—当两个位同时为0的时候,才为0
“^” —异或—两个位相同为0,相异为1
”~“ —取反—0取反为1,1取反为0
“<<”—左移—高位遗弃,地位补0
“>>”—右移—对无符号数,高位补0;有符号数,有的情况下补符号位(算术右移),有的情况下补0(逻辑右移)

1.3、实例

1.3.1、例1
int main()
{
	char a=20,char b=10;
	char c=0;
	c=a&b;
	c=a|b;
	c=a^b;
	c=`a;
	return 0;
}

分析得:
在这里插入图片描述
这个实列只是简单的对4中位运算进行演示

1.3.2、例2:判断二进制数中有多少个1

方法一:

int GetBit(unsigned int x)
{
	unsigned tmp=1;
	int sum=0;
	while(x!=0)
	{
		int pos=tmp&x;
		if(pos==1)
		{
			num++;
		}
		x=x>>1;
	}
	return sum;
}

这个方法是运用与与运算的性质(当两个位都是1的时候才是1),在主程序的的开头设置一个无符号的tmp,令其等于1,进入循环,让每一个二进制数都和tmp进行与运算,当与运算的结果为1的时候,证明这个二进制位上的数是1,sum+=1,并右移一位继续进行循环,在循环结束之后,对sum进行输出,这样就可以求出二进制数中1的个数了。
注:定义无符号的整型x的原因是,如果x是一个负数,那么当程序在执行右移的过程中,高位不断补1,那么程序将会崩溃掉。
方法二:

int GetBit(int x)
{
	int sum = 0;
	while (x != 0)
	{
		x = x & (x - 1);
		sum += 1;
	}
	return sum;
}

分析:
在这里插入图片描述
当二进制数不为0的时候进入循环,让x与(x-1)进行与运算,并把与运算的结果复制给x,每发生一次与运算,sum的值+1,直到x=0结束循环,最终输出sum的值,即为二进制数中1的个数。
这里x&(x-1)实质上是对x进行-1之后与原来x进行与运算,每进行一次二进制就消去一个1。
这种方法对x是否有无符号就没有要求了。

方法三:

int GetBit(unsigned int x)
{
	int sum = 0;
	for (int i = 0; i < 8; ++i)
	{
		sum += "\0\1\1\2\1\2\2\3\1\2\2\3\2\3\3\4"[x & 0x0f];
		x = x >> 4;
	}
	return sum;
}

在这里插入图片描述
这里实际上是查表法,将0-15的二进制数中的1都统计出来,当输入一个数的时候,将其二进制数右移4位,在表中查数即可。

sum += “\0\1\1\2\1\2\2\3\1\2\2\3\2\3\3\4”[x & 0x0f];

1.3.3例3

运用异或运算,判断数组中的重复数

int main()
{
	int ar[] = { 2,3,4,5,6,7,5,4,3,2,6 };
	int tmp = ar[0];
	int n = sizeof(ar) / sizeof(ar[0]);
	for (int i = 0; i < n; ++i)
	{
		tmp = tmp ^ ar[i];
	}
	printf("%d \n", tmp);
	return 0;
}

#define 指令将 标识符 定义为宏,即指示编译器会将之后出现的所有 标识符 都替换为 替换列表,而它也可以被进一步处理
其中,#表示这是一条预处理指令。

不带参数的宏定义

用一个指定的标识符(即名字)来代表一个字符串。

#define 标识符 字符串 //标识符会被字符串替代

#define MAX 10
  • 作用:
    • 程序在进行预处理的时候,会将程序中的所有的MAX变量用10进行替换。
  • 说明:
    • 宏定义只是用宏名代替一个字符串,也就是做一个简单的替换,不做正确性的检查。预处理时不作任何语法检查。只有对已被宏展开后的源程序进行编译的时候才会发现语法错误并报错。
    • 宏定义不是c语句,不必在末行加分号。如果加了分号则会连分号一起进行置换
    • 宏没有类型可言。对于文件来说,宏只是文本文件,不开辟空间(没有类型,没有空间)

带参数的宏定义

//#define 宏名(参数表) 字符串
#define MAX(a,b) (a>b?a:b)
int max_int(int a,int b)
{
	return a>b?a:b;
}
int main()
{
	int x=10,y=10;
	int max=0;
	max=max_int(++x,y); 	// 1
	max=MAX(++x,y);		// 2
	return 0;
}
  1. max=max_int(++x,y);
    在这里插入图片描述

在这里只是简单的函数调用,只对x++一次。

  1. max=MAX(++x,y);
    不是函数的调用,而是宏的展开
    展开:max=MAX(++x>y?++x:y)
    展开后,可以发现,对x++了两次

宏还具有一定的副作用

#define MAX(++a,b) (++a>b?a:b)
int max_int(int a,int b)
{
	return ++a>b?a:b;
}
int main()
{
	int x=10,y=10;
	int max=0;
	max=max_int(x,y); 	// 1
	max=MAX(x,y);		// 2
	return 0;
}

在这里插入图片描述

宏的副作用是:宏定义中的形参会对函数中实参的值进行改变,而函数调用不会发生这种情况。
说到底,始终记住一点,宏定义是替换原则。

宏定义中的运算符(# ##)

#define MYCHAR(name) char ch_##name[]=#name

int main()
{
	MYCHAR(zqz);	1
	MYCHAR(student);	2
	return 0;
}

1=> MYCHAR(zqz)<==> char ch_zqz[]=“zqz”;
##是链接符,将宏参和zqz链接起来,形成新的字符串,形成一个字符串数组
#是将宏参转义成字符串,给宏参加上双引号

在宏当中,##号可以将一个字符串和宏参链接成一个新的字符串,#号可以在进行编译的时候,可以给宏参加上一个双引号

补充:
一、
当在定义宏的时候,代表的语句过多

#define Print(x) x=10;printf("%d\n",x);

发现语句过长
等价于

#define Print(x)	x=10;\
			printf("%d\n",x);

‘\’(宏的语句的链接)后面什么都不加,直接回车下一行,是链接下一行

二、

#defint int int*
int main()
{
	int p=nullptr;
#undef int 
	int a=10;
	return 0;
}

由于在预编译阶段,宏只进行替换,不做正确性的检查。
因此,在程序执行的过程中,将int p = nullptr,中的int 替换成int *,所以,p就变成了一个指针类型,它指向一个空值。

#undef 撤销宏的定义

撤销宏的定义之后,下面int a = 10,这个a就是一个整型变量的值。

三、#ifndef —— #else——#endif

#define VS60
//已经定义VS60
int main()
{
	char stra[20] = { "yhping" };
	char strb[20];
//若未定义则执行下面的语句
//否则执行else后面的
#ifndef VS60
	strcpy_s(strb, 20, stra);		1
#else
	strcpy(strb, stra);				2
#endif
	return 0;
}

如果未定义VS60(称为:真),则执行#ifndef下面的语句,如果定义(称为:假),则执行else右面的语句。
#ifdef则是将已定义变成真,未定义变成假
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值