绝妙四道题(C语言)

x33g5p2x  于2021-11-22 转载在 其他  
字(4.7k)|赞(0)|评价(0)|浏览(471)

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

  1. #include<stdio.h>
  2. #define exchange(n) \
  3. //这里的 \ 就是换行,就是 你要定义的宏,太长了,使用这个斜杠,可以让你在下一行继续写
  4. n = ((n & 0x55555555) << 1) | ((n & 0xaaaaaaaa) >> 1)
  5. // 5的奇数位左移一位 5的偶数位右移一位
  6. // 奇数位代替偶数位,偶数位代替奇数位,不就完成了交换了嘛!,再将两者进行 按位或,奇数位和偶数位上的值互不影响。
  7. // 其结果就是我们想要的想要的结果
  8. int main()
  9. {
  10. int a = 5;
  11. exchange(a);
  12. printf("a = %d\n", a);
  13. return 0;
  14. }

附图:

模拟实现 atoi函数

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

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

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

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

程序一:

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. int main()
  4. {
  5. int ret = atoi("1234");
  6. printf("%d\n", ret);
  7. return 0;
  8. }
程序一效果图:

程序二:

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

程序三

由程序的输出结果发现,atoi 函数 不会转换 空格,直接跳过。.

程序四

由程序的输出结果发现,atoi 函数 不会转换 符号+ , 直接跳过。.

程序五

由程序的输出结果发现,atoi 函数 遇到 负号时,会保留下来。.

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

由程序的输出结果发现,atoi 函数 遇到 空指针时,无法转换数据,而且程序会崩溃,意味着 atoi 无法转换 空指针。

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

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

.#### 附图:

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

可以看出,atoi 函数遇到很大的字符数字串,它无能为力,它只能转换 int 取值范围(-2^31 ~ 2的31次方-1)内的字符数字串

正式模拟实现 atoi 函数

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

代码如下

  1. #include<stdio.h>
  2. #include<ctype.h>
  3. #include<assert.h>
  4. #include<limits.h>
  5. enum STATE
  6. {
  7. legal,// 合法
  8. illegal//不合法
  9. };
  10. enum STATE state = illegal;//判断是否在转换的过程中碰到异常情况
  11. int my_atoi(const char* str)
  12. {
  13. assert(str);// 这里解决 空指针NULL的情况
  14. int flag = 1;//标记正负数
  15. long long ret = 0;//用来判断 是否有溢出的情况(转换的数字字符太大了)
  16. //如果用int 来定义ret,是不行,因为int是放不下,比它大的数,会将转换成自己放的下数,这样的话我们就无法判断,是否有溢出现象
  17. if (!*str)// 这里利用了 枚举 来区分 空字符串 和 0字符串,都返回0的情况
  18. {
  19. //我们认为 "",atoi遇到它,返回 0,是非法的
  20. //因为我一开始就把就 state,设置为 非法。
  21. // 虽然这里只返回 0,但是 state(状态)还是处于非法的,
  22. // 反之如果 0 字符串的话,我们就把 stata 改成合法就行了
  23. // 只要在输出结果后,看一下程序的状态就知道,是谁返回的 0.
  24. return 0;// 所以这时候返回的0,肯定是非法的
  25. }
  26. while (isspace(*str))//这里是跳过空白字符,isspace函数如果发现*str是一个空格,它就返回一个非零值(为真),否,则返回0(为假)
  27. {
  28. str++;//地址自增加一,跳过空白
  29. }
  30. if (*str == '+')// 识别正负数(flag == 1)
  31. {
  32. str++;// 如果它 是 +,flag就不动它,然后跳过它
  33. }
  34. else if (*str == '-')
  35. {
  36. flag = -1;//是负的,我们就把他改成 -1,然后跳过它
  37. str++;
  38. }
  39. while (*str)// 数字字符的识别,如果它数字字符,它肯定是为真的('0' ~ '9' 等价于ASCII码值 48 ~ 57),直到遇到 '\0'(ASCII码值为0),则为假,跳出循环
  40. {
  41. if (isdigit(*str))// isdigit 判断 *str 是否是 数字字符
  42. {
  43. ret = ret * 10 + flag*(*str - '0');// 见附图1
  44. str++;
  45. /* flag,表示符号,如果是正数flag ==1,那么该表达式就是正数相加,如果是负数 flag == -1,那么该表达式就是负数相加*/
  46. // 判断是否溢出
  47. if (ret < INT_MIN || ret > INT_MAX)// INT_MIN,int最小取值,INT_MAX int 最大取值
  48. // ret 定义为 long long 类型的理由就在于此
  49. // 如果产生一个 超出 int 范围的值,而 ret 是一个int 类型,所以会将其截断放进ret
  50. // 那么我们这个 if 语句将毫无意义(因为ret永远不可能超出int 的取值范围),所以我们将 ret 定义成 long long 型(ret放进一个取值范围更大的类型),那么产生一个超出 int 范围的值
  51. // 我们的 ret 能存的下,也就意味着我们能够,进行if语句的判断
  52. // 见 附图 2
  53. {
  54. state = illegal;//非法,这个是可以不用写的,因为 stata一开始就是非法,这是值是提醒你们一下
  55. return 0;// 这个零就是异常返回
  56. }
  57. }
  58. // 异常字符(非数字字符)
  59. else
  60. {
  61. state = legal;//合法的
  62. return ret;
  63. }
  64. }
  65. // 前面如果有任何一项条件满足,程序都不会走到这一步
  66. // 走到这里,说明 模拟实现的 atoi 是 将一个字符串,从头到尾都转换成功了(遇到字符串最后一个停止标识符'\0'结束的)
  67. state = legal;// 那这个肯定是合法的
  68. return ret;
  69. }
  70. int main()
  71. {
  72. char c[1024] = { 0 };
  73. scanf("%s", c);
  74. int ret = my_atoi(c);
  75. printf("%d\n", state);// 输出为 0 是合法, 1 是非法
  76. printf("%d\n", ret);//如果输出为 0,说明是异常返回,是非法的
  77. return 0;
  78. }

附图1:

附图2

效果图:

offsetof宏的实现

代码如下

  1. #include<stdio.h>
  2. struct S
  3. {
  4. char a;
  5. char b;
  6. int c;
  7. };
  8. offsetof的返回值是int
  9. #define OFFSETOFF(struct_type,struct_member) (int)&(((struct_type*)0)->struct_member)
  10. ((struct_type*)0) 假设结构体的地址为0 真实地址,你们直接调试内存,&结构体变量名就可以知道了
  11. 这样做的目的:是结构体后面成员的地址,都成为偏移量,这样就省的我们再去进一步的转它
  12. int main()
  13. {
  14. struct S s = { 0 };
  15. printf("%d\n",OFFSETOFF(struct S, a));
  16. printf("%d\n", OFFSETOFF(struct S,b));
  17. printf("%d\n", OFFSETOFF(struct S, c));
  18. return 0;
  19. }

附图

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

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

  1. #include<stdio.h>
  2. #include<string.h>
  3. int main()
  4. {
  5. int arr[] = { 1, 2, 3, 4, 5, 1, 2, 3, 4, 6 };
  6. // 找出 5 和 6
  7. // 这里我们用异或的办法(相异出1 ,相同出 0)
  8. // 如果无法将数组元素统一异或,那么我就需要分组
  9. 假设
  10. //分组(将两个不同的数字,放进2个组里,其他 成对的数字,都是成对的 按照某种规律,放进对应分组)
  11. //1 1 2 2 5 将这组数异或在一起, 1 和1 异或等于0,0 再和 2异或 等于 2,2 和 2 在异或等于0,最后 0和 5 异或 等于 5,那么我不就得到了该数字
  12. //3 3 4 4 6 与上同理
  13. // 并不一定要这样分,只是假设
  14. //也可以这样分
  15. // 1 1 2 2 3 3 5
  16. // 4 4 6
  17. // 只要你能把那两个不同数,放进不同的分组里,其他的,两个相同的数放进同一个分组,就可以了
  18. // 那怎么分组呢?
  19. // 目标是什么,只出现一次的2个数字,必须在不同的分组里
  20. // 101 5的二进制码
  21. // 110 6的二进制码
  22. //只要是不相同的2个数,它的二进制码里,必定有 某一个二进制位不同(一个是0,一个是1)
  23. // 那么我以这不相同的二进制位,进行分组呢?
  24. // 1 的二进制 001, 2的二进制 010, 3的二进制 011, 4的二进制 100
  25. // 那么想象一下,如果我这里 最低位二进制进行分组
  26. // 分组是不是如下情况所示?
  27. // 1 1 3 3 5
  28. // 2 2 4 4 6
  29. // 那么 第二个进制位呢?
  30. // 分组情况
  31. // 1 1 4 4 5 第二位 是 0
  32. // 2 2 3 3 6 第二位 是 1
  33. // 那么我们开始了
  34. // 其实将数组int arr[] = { 1, 2, 3, 4, 5, 1, 2, 3, 4, 6 };
  35. // 异或在一起,其结果就是 5 和 6 异或的结果,其过程和假设是一样的(相同为 0,相异为1,那么 1 2 3 4 都被抵消了,那剩下就是5 和 6的异或)
  36. int ret = 0;
  37. int i = 0;
  38. int sz = sizeof(arr) / sizeof(arr[0]);
  39. for (i = 0; i < sz; i++)
  40. {
  41. ret = ret^arr[i];
  42. }// 其最后的结果就是 5 和 6 异或的结果
  43. // 5^6 == 101 ^ 110 == 011
  44. // 异或结果为 1,表示 该 1 的二进制位的位置,在 两个数字中 所对应的 二进制码的位置 是不同的,表示我可以 以此 进行分组
  45. // 然后就是 计算 该 1 的二进制位的位置,
  46. int pos = 0;
  47. for (i = 0; i < 32; i++)
  48. { // 遍历 二进制位
  49. if ((ret>>i) & 1 == 1) // 只要 某个二进制位 与的结果为1, n那么我就要记住 该 1 在 二进制码中位置,以该位置为准,对数组里的数进行分组
  50. // 请注意这里ret并没有 改变,只是移动看了下位置,并没重新赋值
  51. {
  52. pos = i;// 为了 记住 这个 1 的位置(二进制不相同的位 的 位置),创建一个 pos 整形变量
  53. break;
  54. }
  55. }
  56. // 最后按照 pos (不同的位。异或结果为 1 的 位)的 位置,对 数组元素 进行分组。
  57. int m = 0;
  58. int n = 0;
  59. for (i = 0; i < sz; i++)
  60. {
  61. if (((arr[i] >> pos) & 1) == 1)// 将数组元素 二进制码 第 pos 位的二进制 为 1 的,分为一组,且异或在一起
  62. 异或过程,可参考程序开头的 假设 部分
  63. {
  64. m ^= arr[i];
  65. }
  66. else// 将数组元素 二进制码 第 pos 位的二进制 为 0 的,分为一组,且异或在一起
  67. {
  68. n ^= arr[i];
  69. }
  70. }
  71. printf("%d\n", m);
  72. printf("%d\n", n);
  73. return 0;
  74. }

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

相关文章