输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

本文介绍了如何使用位操作解决两个问题:一是通过循环计算一个整数二进制中有多少个1;二是使用简洁的语句判断一个整数是否为2的整数次幂。同时,提供了反汇编后的循环代码对比,展示了第二种方法的高效性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

牛客网上的一道面试题。


两种解法:

其一:

<script src="https://code.youkuaiyun.com/snippets/667542.js"></script>


其二:

如果一个整数不为0,那么这个整数至少有一位是1。

如果我们把这个整数减去1,那么原来处在整数最右边的1就会变成0,原来在1后面的所有 的0都会变成1。其余的所有位将不受到影响。

举个例子:一个二进制数1100,从右边数起的第三位是处于最右边的一个1。减去1后,第三位变成0,它后面 的两位0变成1,而前面的1保持不变,因此得到结果是1011。

我们发现减1的结果是把从最右边一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位 开始所有位都会变成0。如1100&1011=1000。

也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0。那么 一个整数的二进制有多少个1,就可以进行多少次这样的操作。

<script src="https://code.youkuaiyun.com/snippets/667542.js"></script>


扩展:如何用一个语句判断一个整数是不是二的整数次幂?
PS:n&(n-1)==0;//二进制数只有一位位1,则该数是2的整数次幂.


反汇编后对比两段循环代码:


        while(n!=0)
00E3137C  cmp         dword ptr [n],0  
00E31380  je          wmain+49h (0E31399h)  
             {
             count++;
00E31382  mov         eax,dword ptr [count]  
00E31385  add         eax,1  
00E31388  mov         dword ptr [count],eax  
             n = n & (n-1);
00E3138B  mov         eax,dword ptr [n]  
00E3138E  sub         eax,1  
00E31391  and         eax,dword ptr [n]  
00E31394  mov         dword ptr [n],eax  






while(n)
00CC137C  cmp         dword ptr [n],0  
00CC1380  je          wmain+4Dh (0CC139Dh)  
{
if(n&1)
00CC1382  mov         eax,dword ptr [n]  
00CC1385  and         eax,1  
00CC1388  je          wmain+43h (0CC1393h)  
count++;
00CC138A  mov         eax,dword ptr [count]  
00CC138D  add         eax,1  
00CC1390  mov         dword ptr [count],eax  
n= (unsigned int) n>>1;
00CC1393  mov         eax,dword ptr [n]  
00CC1396  shr         eax,1  
00CC1398  mov         dword ptr [n],eax  


第二钟解法确实会效率高。


PS:要注意n可能是个负数,移位时要先强制类型转换。


<think>嗯,用户让我用C语言编写一个程序,求一个整数存储在内存中的二进制1个数,而且不可以使用函,只要一个主函。我需要仔细想想怎么实现这个功能。 首先,我得回忆一下,在C语言中,如何统计二进制1个数。常见的方法可能有几种,比如逐位检查,或者用n & (n-1)的方法来消除最右边的1,直到变成0为止。这种方法可能效率更高,因为循环次等于1个数。 不过用户要求不使用函,只用一个主函。所以整个逻辑都要放在main里面。可能需要让用户输入一个整数,然后处理这个数,计算其中1个数,再输出结果。 那具体怎么做呢?假设用户输入整数是int类型,那要考虑负数的情况吗?因为负数二进制补码形式会有很多1。例如,-1补码是全1。题目中没有说明是否要处理负数,但一般来说,统计补码中的1个数可能更符合要求。不过,用户提供的例子可能只考虑正。这里可能需要先明确,但用户没有特别说明,所以应该处理所有整数的情况。 那如何处理呢?如果用移位操作的话,对于负数右移的话,在C语言中是算术右移,左边补符号位,所以如果用普通的右移操作的话,可能会导致死循环,因为符号位一直存在。比如,对于-1来说,右移之后还是-1,这样循环不会结束。所以需要将变量转换为无符号整数来处理,或者使用一个循环固定次,比如32次(对于32位整数)或64次,每次检查一位。 或者,可以将输入整数转换为无符号类型,这样右移的时候左边补0,这样就不会无限循环了。这样的话,不管原来的是正还是负,都可以正确统计其中的1个数。 所以步骤大概是这样的: 1. 读取用户输入整数n。 2. 将n转换为无符号整数,或者直接以无符号的方式处理。 3. 初始化一个器count为0。 4. 循环,直到n为0: a. 如果n的最低位是1,count加1。 b. 将n右移一位。 5. 输出count的值。 或者,另一种方法是使用n & (n-1)的方式,每次消除最右边的1,直到n变为0。这样的循环次等于1个数,可能更高效。但同样,对于负数的情况需要注意。 比如,假设n是负数,那么当用这种方法时,n-1的运算可能会有不同的效果。例如,假设n是-1,即全1补码,那么n-1会变成-2,二进制是全1除了最后一位是0。但此时n & (n-1)会是-2,这样循环的次可能不是预期的32次或者64次?这可能有问题。或者,如果转换为无符号的话,可能更合适。 可能正确的做法是将输入整数转换为无符号类型,然后进行处理。这样无论原是否有符号,都按无符号二进制形式来处理。 例如: unsigned int m = (unsigned int)n; 然后处理m的值,这样移位的时候就不会有符号位的问题了。 所以,不管用户输入的是正还是负数,都可以正确统计二进制中的1个数。 现在,把这些思路写成代码的话,主函的结构应该是: #include <stdio.h> int main() { int num; scanf("%d", &num); unsigned int m = num; // 转换为无符号类型 int count = 0; while (m) { count += m & 1; m >>= 1; } printf("%d\n", count); return 0; } 但是这样可能有一个问题,比如,当输入的是负数的时候,转换为无符号整数的话,二进制位的表示是不同的。例如,在C语言中,int和unsigned int的转换是按照位模式保持不变的。所以,对于负数来说,转换为unsigned int后,其二进制表示和原来的补码形式是一样的。所以,这个转换是正确的,可以正确统计原内存中的二进制中的1个数。 那这样的话,上面的代码应该可以正确工作。 例如,输入-1,在32位系统中,unsigned int的二进制是全1,所以count会是32。 测试一下,假设输入的是3,二进制11,count是2。 输入0的话,count是0。 那这样的话,代码应该可以正确统计内存中的二进制1个数。 不过,还有一种情况需要考虑:如果用户使用的是不同的架构,比如int是16位或者64位的话,代码可能需要调整循环次。例如,在代码中,如果直接使用m >>=1直到m变为0的话,无论位多少,都能正确统计。比如,如果是16位的整数,当m是负数转为无符号后,循环次16次,直到所有位都被移出。这时候,不管原的位是多少,都能正确统计。 或者,上面的代码是否正确? 例如,如果输入的是1,那么二进制是000...0001,count是1。对于32位系统,转换后的无符号同样正确。 那这样的话,代码应该是正确的。 或者,是否应该使用一个固定次的循环,比如32次,每次检查每一位? 比如: int count =0; for(int i=0; i<32; i++){ if ( (num >> i) & 1 ) count++; } 但是这样,当num是负数的时候,右移i位后,符号位会被扩展,导致高位始终是1。比如,对于int是32位的情况下,num是-1的话,每次右移i位,最高位还是1,所以(num >>i) &1 会是1,所以循环32次的话,count会是32。这同样正确。但这里num的类型是int,右移带符号的整数是算术右移,所以对于每一位来说,只有当原补码中的该位是1的时候,才会被统计到。例如,对于负数来说,这种方法是否可行? 或者,这种方法是否正确?比如,对于int类型的num,当i超过0到31的范围时,右移i位的行为是否未定义?在C语言中,移位超过位是未定义的行为。例如,如果int是32位,那么移位超过31位是未定义的。因此,这样的循环可能有问题。 所以,为了安全起见,应该避免这种情况。所以之前的用无符号的方法可能更安全,因为无论移多少位,都是逻辑右移,补0,而不会导致无限循环或者未定义行为。 所以,原来的代码是正确的。 那现在,用户的要求是不使用函,只用一个主函。那么,代码应该没问题。例如: #include <stdio.h> int main() { int num; scanf("%d", &num); unsigned int m = num; int count = 0; while (m) { count += m & 1; m >>= 1; } printf("%d\n", count); return 0; } 这段代码是否符合用户的要求? 是的,因为它没有使用任何自定义函,只有主函。 但有没有例外情况?比如,当输入是0的时候,不会进入循环,直接输出0,正确。 那这个代码是否在所有情况下都正确? 例如,对于num为负数时,比如-1,转换后的无符号是全1,所以循环会执行直到所有位都被处理,count会是32(假设是32位int)。 是的。例如,在32位系统中,unsigned int m = -1的话,m的值是4294967295,二进制1。循环中每次检查最后一位,然后右移,直到m变成0,所以循环执行32次,每次count加1,所以最终count是32。 所以这段代码是正确的。 因此,这个代码应该能满足用户的需求。</think>以下是使用C语言编写的程序,用于计算一个整数存储在内存中的二进制1个数,仅使用主函且不调用其他函: ```c #include <stdio.h> int main() { int num; scanf("%d", &num); // 输入整数 unsigned int m = num; // 转为无符号处理二进制位 int count = 0; // 计器 while (m) { // 当m不为0时循环 count += m & 1; // 检查最后一位是否为1 m >>= 1; // 右移一位 } printf("%d\n", count); // 输出结果 return 0; } ``` ### 代码说明: 1. **输入处理**:通过 `scanf` 读取用户输入整数 `num`。 2. **转换为无符号**:将 `num` 转换为 `unsigned int` 类型,确保右移时高位补0,避免符号位干扰。 3. **循环统计1个数**: - `m & 1`:检查当前最低位是否为1。 - `count += m & 1`:若最低位为1,计器加1。 - `m >>= 1`:右移一位,处理下一位。 4. **输出结果**:循环结束后打印计器的值,即二进制1个数。 ### 示例: - 输入 `3`(二进制 `11`),输出 `2`。 - 输入 `-1`(补码1,假设32位系统),输出 `32`。 - 输入 `0`,输出 `0`。 此方法适用于正负整数,能正确统计内存中二进制位的实际1个数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值