绝妙四道题(C语言)

文章目录

  • 宏实现 二进制 奇数位与偶数位互换
    • 附图:
  • 模拟实现 atoi函数
    • 首先 atoi 是一个库函数,功能是将字符串转换成为整型
    • atoi的应用结构 :int atoi( const char *string );
      • atoi库函数,在 头文件 stdlib.h 底下
    • 我们先来使用 atoi函数,来验证一波它的功能
      • 程序一:
        • 程序一效果图:
      • 程序二:
      • 程序三
      • 程序四
      • 程序五
      • 程序六(特殊情况1,要转换的是 NULL 空指针)
      • 程序七(特殊情况2:字符串里只有一个字符串的结束标识符 '\0'):
      • 程序八(特殊情况3:转换的字节太大了)
    • 正式模拟实现 atoi 函数
      • 代码如下
      • 附图1:
      • 附图2
      • 效果图:
  • offsetof宏的实现
    • 代码如下
      • 附图
  • 编写一个函数找出数组中只出现一次的数字
    • 附带要求:该数组中只有两个数字是出现一次,其他所有数字都出现了两次
  • 本文结束(好好琢磨最后一题)。

宏实现 二进制 奇数位与偶数位互换

#include

#define exchange(n)   \  
//这里的 \ 就是换行,就是 你要定义的宏,太长了,使用这个斜杠,可以让你在下一行继续写

n =	((n & 0x55555555) << 1) | ((n & 0xaaaaaaaa) >> 1)
//     5的奇数位左移一位        5的偶数位右移一位


// 奇数位代替偶数位,偶数位代替奇数位,不就完成了交换了嘛!,再将两者进行 按位或,奇数位和偶数位上的值互不影响。
// 其结果就是我们想要的想要的结果
int main()
{
     
	int a = 5;
	exchange(a);
	printf("a = %d\n", a);
	return 0;
}

附图:

绝妙四道题(C语言)_第1张图片


模拟实现 atoi函数

首先 atoi 是一个库函数,功能是将字符串转换成为整型

绝妙四道题(C语言)_第2张图片


atoi的应用结构 :int atoi( const char *string );

atoi库函数,在 头文件 stdlib.h 底下

绝妙四道题(C语言)_第3张图片


我们先来使用 atoi函数,来验证一波它的功能

程序一:

#include
#include
int main()
{
     
	int ret = atoi("1234");
	printf("%d\n", ret);
	return 0;
}

程序一效果图:

绝妙四道题(C语言)_第4张图片


程序二:

绝妙四道题(C语言)_第5张图片

由程序的输出结果,我们发现,atoi 当遇到 非数字的字符时,它就不再转换字符串了。


程序三

绝妙四道题(C语言)_第6张图片
由程序的输出结果发现,atoi 函数 不会转换 空格,直接跳过。.


程序四

绝妙四道题(C语言)_第7张图片
由程序的输出结果发现,atoi 函数 不会转换 符号+ , 直接跳过。.


程序五

绝妙四道题(C语言)_第8张图片
由程序的输出结果发现,atoi 函数 遇到 负号时,会保留下来。.


程序六(特殊情况1,要转换的是 NULL 空指针)

绝妙四道题(C语言)_第9张图片
由程序的输出结果发现,atoi 函数 遇到 空指针时,无法转换数据,而且程序会崩溃,意味着 atoi 无法转换 空指针。


程序七(特殊情况2:字符串里只有一个字符串的结束标识符 ‘\0’):

绝妙四道题(C语言)_第10张图片
由程序的输出结果发现,atoi 函数 面对一个只有 字符串结束标识符 '\0’时,会将其转换成它的ASCII码值 0。那么问题来了,如果我想转换一个字符串里只有 字符 '0’时,它也返回0(见下方附图),那我们怎么区分两者呢?

.#### 附图:
绝妙四道题(C语言)_第11张图片


程序八(特殊情况3:转换的字节太大了)

绝妙四道题(C语言)_第12张图片
可以看出,atoi 函数遇到很大的字符数字串,它无能为力,它只能转换 int 取值范围(-2^31 ~ 2的31次方-1)内的字符数字串


正式模拟实现 atoi 函数

 现在我们已将知道 atoi 各种特性,和它的应用结构 :int atoi( const char *string );
 现在我们来模拟实现它吧

代码如下

#include
#include
#include
#include

enum STATE
{
     
	legal,// 合法
	illegal//不合法
};

enum STATE state = illegal;//判断是否在转换的过程中碰到异常情况

int my_atoi(const char* str)
{
     
	assert(str);// 这里解决 空指针NULL的情况 

	int flag = 1;//标记正负数
	long long ret = 0;//用来判断 是否有溢出的情况(转换的数字字符太大了)
	//如果用int 来定义ret,是不行,因为int是放不下,比它大的数,会将转换成自己放的下数,这样的话我们就无法判断,是否有溢出现象
	
	if (!*str)// 这里利用了 枚举 来区分  空字符串 和 0字符串,都返回0的情况
	{
     
		//我们认为 "",atoi遇到它,返回 0,是非法的
		//因为我一开始就把就 state,设置为 非法。
		// 虽然这里只返回 0,但是 state(状态)还是处于非法的,
		// 反之如果 0 字符串的话,我们就把 stata 改成合法就行了
		// 只要在输出结果后,看一下程序的状态就知道,是谁返回的 0.
		return 0;// 所以这时候返回的0,肯定是非法的
	}
	while (isspace(*str))//这里是跳过空白字符,isspace函数如果发现*str是一个空格,它就返回一个非零值(为真),否,则返回0(为假)
	{
     
		str++;//地址自增加一,跳过空白
	}
	if (*str == '+')// 识别正负数(flag == 1)
	{
     
		str++;// 如果它 是 +,flag就不动它,然后跳过它
	}
	else if (*str == '-')
	{
     
		flag = -1;//是负的,我们就把他改成 -1,然后跳过它
		str++;
	}
	while (*str)// 数字字符的识别,如果它数字字符,它肯定是为真的('0' ~ '9' 等价于ASCII码值  48 ~ 57),直到遇到 '\0'(ASCII码值为0),则为假,跳出循环 
	{
     
		if (isdigit(*str))// isdigit 判断 *str 是否是 数字字符
		{
     
			ret = ret * 10 + flag*(*str - '0');// 见附图1
			str++;
			/* flag,表示符号,如果是正数flag ==1,那么该表达式就是正数相加,如果是负数 flag == -1,那么该表达式就是负数相加*/
			
			// 判断是否溢出
			if (ret < INT_MIN || ret > INT_MAX)// INT_MIN,int最小取值,INT_MAX int 最大取值
				// ret 定义为 long long 类型的理由就在于此
				// 如果产生一个 超出 int 范围的值,而 ret 是一个int 类型,所以会将其截断放进ret
				// 那么我们这个 if 语句将毫无意义(因为ret永远不可能超出int 的取值范围),所以我们将 ret 定义成 long long 型(ret放进一个取值范围更大的类型),那么产生一个超出 int 范围的值
				// 我们的 ret 能存的下,也就意味着我们能够,进行if语句的判断
                // 见 附图 2
			{
     
				state = illegal;//非法,这个是可以不用写的,因为 stata一开始就是非法,这是值是提醒你们一下
				return 0;// 这个零就是异常返回
			}
		}
		// 异常字符(非数字字符)
		else
		{
     
			state = legal;//合法的
			return ret;
		}
		
	}
	// 前面如果有任何一项条件满足,程序都不会走到这一步
	// 走到这里,说明 模拟实现的 atoi 是 将一个字符串,从头到尾都转换成功了(遇到字符串最后一个停止标识符'\0'结束的)
	state = legal;// 那这个肯定是合法的
	return ret;

}

int main()
{
     
	char c[1024] = {
      0 };
	scanf("%s", c);
	int ret = my_atoi(c);
	printf("%d\n", state);// 输出为 0 是合法, 1 是非法
	printf("%d\n", ret);//如果输出为 0,说明是异常返回,是非法的
	return 0;
}


附图1:

绝妙四道题(C语言)_第13张图片


附图2

绝妙四道题(C语言)_第14张图片


效果图:

绝妙四道题(C语言)_第15张图片


绝妙四道题(C语言)_第16张图片


offsetof宏的实现

代码如下

#include

struct S
{
     
	char a;
	char b;
	int c;
};
                                      offsetof的返回值是int                                 
#define OFFSETOFF(struct_type,struct_member)  (int)&(((struct_type*)0)->struct_member)
                     
((struct_type*)0) 假设结构体的地址为0, 真实地址,你们直接调试内存,&结构体变量名就可以知道了
这样做的目的:是结构体后面成员的地址,都成为偏移量,这样就省的我们再去进一步的转它

int main()
{
     
	struct S s = {
      0 };
	printf("%d\n",OFFSETOFF(struct S, a));
	printf("%d\n", OFFSETOFF(struct S,b));
	printf("%d\n", OFFSETOFF(struct S, c));
	return 0;
}

附图

绝妙四道题(C语言)_第17张图片


编写一个函数找出数组中只出现一次的数字

附带要求:该数组中只有两个数字是出现一次,其他所有数字都出现了两次

#include
#include
int main()
{
     
	int arr[] = {
      1, 2, 3, 4, 5, 1, 2, 3, 4, 6 };
	// 找出 5 和 6
	// 这里我们用异或的办法(相异出1 ,相同出 0)
	// 如果无法将数组元素统一异或,那么我就需要分组
	假设 
	//分组(将两个不同的数字,放进2个组里,其他 成对的数字,都是成对的 按照某种规律,放进对应分组)
	//1 1 2 2 5 将这组数异或在一起, 1 和1 异或等于0,0 再和 2异或 等于 2,2 和 2 在异或等于0,最后 0和 5 异或 等于 5,那么我不就得到了该数字
	//3 3 4 4 6 与上同理
	
	// 并不一定要这样分,只是假设
	//也可以这样分
	// 1 1 2 2 3 3 5
	// 4 4 6
	// 只要你能把那两个不同数,放进不同的分组里,其他的,两个相同的数放进同一个分组,就可以了


	// 那怎么分组呢? 
	// 目标是什么,只出现一次的2个数字,必须在不同的分组里
	// 101  5的二进制码
	// 110  6的二进制码
	//只要是不相同的2个数,它的二进制码里,必定有 某一个二进制位不同(一个是0,一个是1)
	// 那么我以这不相同的二进制位,进行分组呢?
	//  1 的二进制 001, 2的二进制 010, 3的二进制 011, 4的二进制 100

	// 那么想象一下,如果我这里 最低位二进制进行分组
	// 分组是不是如下情况所示?
	//  1 1 3 3 5
	//  2 2 4 4 6


	// 那么 第二个进制位呢?
	// 分组情况
	// 1 1 4 4 5  第二位 是 0
	// 2 2 3 3 6  第二位 是 1



	// 那么我们开始了
	//  其实将数组int arr[] = { 1, 2, 3, 4, 5, 1, 2, 3, 4, 6 };
	// 异或在一起,其结果就是 5 和 6 异或的结果,其过程和假设是一样的(相同为 0,相异为1,那么 1 2 3 4 都被抵消了,那剩下就是5 和 6的异或)
	int ret = 0;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
     
		ret = ret^arr[i];
	}// 其最后的结果就是 5 和 6 异或的结果
	// 5^6 == 101 ^ 110 ==  011


	// 异或结果为 1,表示 该 1 的二进制位的位置,在 两个数字中 所对应的 二进制码的位置 是不同的,表示我可以 以此 进行分组
	// 然后就是 计算 该 1 的二进制位的位置,
	int pos = 0;
	for (i = 0; i < 32; i++)
	{
      // 遍历 二进制位
		if ((ret>>i) & 1 == 1) // 只要 某个二进制位 与的结果为1, n那么我就要记住 该 1 在 二进制码中位置,以该位置为准,对数组里的数进行分组
		// 请注意这里ret并没有 改变,只是移动看了下位置,并没重新赋值
		{
     
			pos = i;// 为了 记住 这个 1 的位置(二进制不相同的位 的 位置),创建一个 pos 整形变量
			break;
		}
	}

	// 最后按照 pos (不同的位。异或结果为 1 的 位)的 位置,对 数组元素 进行分组。
	int m = 0;
	int n = 0;
	for (i = 0; i < sz; i++)
	{
     
		if (((arr[i] >> pos) & 1) == 1)// 将数组元素 二进制码 第 pos 位的二进制 为 1 的,分为一组,且异或在一起
		异或过程,可参考程序开头的 假设 部分
		{
     
			m ^=  arr[i];
		}
		else// 将数组元素 二进制码 第 pos 位的二进制 为 0 的,分为一组,且异或在一起
		{
     
			n ^= arr[i];
		}
	}


	printf("%d\n", m);
	printf("%d\n", n);
	return 0;
}

本文结束(好好琢磨最后一题)。

你可能感兴趣的:(c语言)