C语言计算一个数中1的个数

本文介绍两种计算无符号整型数中1的个数的方法:一种是通过循环右移并检查每一位是否为1;另一种是利用位运算技巧x&=(x-1)来消除最右边的1,直至所有1被消除。

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

计算一个无符号整型数中1的个数有两种方法:

第一种很容易想到,将数循环右移1位直到数为零,每次与1相与,结果为真表示该位为1。

用unsigned类型是为了保证数在右移时,无论该程序在什么机器上运行,左边空出的位都用0而不是符号位填补。

int bitcount(unsigned x)
{
	int b;
	for(b = 0; x!=0, x>>=1)
		if(x&1)
		  ++b;
	return b;
}


第二种方法比较巧妙利用x&=(x-1)这个式子每次消除x中最右边值为1的二进制位,循环次数即为1的个数。

我们随便为x-1选一个值,比如二进制1010,x就为1011,x&=(x-1),x为1010

x-1为1001,x&=(x-1),x为1000,

x-1为0111,x&=(x-1),x为0,

循环三次,1011中共3个1,

我们发现x-1最右端为0的位与x最右端为1的位相对应,

两者相与,x&(x-1)就能消除x最右端值为1的位。

int bitcount(unsigned x)
{
	int b;
	for(b = 0; x!=0, x&=x-1)
		  ++b;
	return b;
}




<think>我们有一个任务:在C语言中编写一个,该函计算一个二进制1个数,并判断个数的奇偶性。 根据引用[4]提供了计算二进制中1个数的几种方法,我们可以选择其中一种。 另外,判断奇偶性可以用引用[2]的方法:用`x & 1`来判断,如果结果为1则为奇,0则为偶。 步骤: 1. 编写一个计算一个中二进制表示中1个数。 我们可以使用引用[4]中的方法,例如使用`while (a & (a-1))`来不断消除最低位的1,直到a变为0,同时计。 2. 得到1个数后,判断这个个数的奇偶性:用`count & 1`,如果结果为1,则个数为奇;否则为偶。 但是,我们也可以不用先计算1个数判断奇偶,因为奇偶性实际上就是模2的结果。我们可以使用一个器,每遇到一个1就翻转一次(用异或操作),最后看计器的值(0表示偶个,1表示奇个)。然而,这里我们按照两步来实现。 我们定义一个,比如叫做`int parityOfOnes(int num);`,它返回1个数的奇偶性,返回1表示奇个,0表示偶个。 但是,题目要求计算1个数判断奇偶性。我们可以有两种方式: 方式一:函返回1个数,然后在主函判断奇偶性。 方式二:函直接返回奇偶性(即1个数是奇还是偶)。 根据问题,我们可能需要两个功能:一个计算1个数,另一个判断奇偶性。但也可以在一个里同时完成。 这里我们设计两个函1. `int countOnes(int num);` // 计算二进制中1个数 2. `int isOdd(int count);` // 判断一个的奇偶性,如果是奇返回1,偶返回0 但是,也可以将两个功能合并到一个中,直接返回奇偶性。不过题目要求“计算二进制1个数,并判断奇偶性”,所以我们可以分别实现,然后在一个中调用这两个函,或者在一个中同时完成。 为了清晰,我们先分别实现两个函,然后再写一个来整合。 然而,我们也可以只写一个,返回一个结构体或者通过指针返回两个值,但这样复杂了。根据要求,我们只需要得到1个数和奇偶性,我们可以通过两个函分别实现,然后在主函中调用。 但是,题目要求“编写一个”,所以我们写一个完成两个任务:既计算1个数,又判断奇偶性,并通过参返回1个数,函返回值返回奇偶性(或者反过来)。 我们这样设计: 函名:int countAndParity(int num, int *count) 功能:计算num中1个数,存放在count指向的变量中,然后返回1个数的奇偶性(1表示奇,0表示偶) 或者,我们可以通过返回1个数,然后通过另一个指针参返回奇偶性,但这样也可以。 但是,题目要求是“一个”,我们可以这样: 另一种方式:函返回奇偶性,同时通过指针参返回1个数。 由于题目没有明确要求返回方式,我们可以自由设计。这里我们写一个,它返回1个数的奇偶性(即奇偶校验位),同时通过指针参返回1个数。 函原型: int count_ones_and_parity(int num, int *count); 实现: 1. 初始化计器count_val为0 2. 遍历整的每一位(或者使用消除最低位1的方法)计算1个数 3. 将1个数存入*count 4. 返回这个个数的奇偶性:count_val & 1 (如果奇则返回1,偶返回0) 但是,我们也可以不用遍历32位,而是用消除最低位1的方法(更高效)。 消除最低位1的方法: while (num) { count_val++; num = num & (num-1); } 示例:num=5 (二进制101) 第一次:5 & 4 -> 101 & 100 = 100 (非0) -> count_val=1 第二次:4 & 3 -> 100 & 011 = 0 -> count_val=2 -> 结束 所以5的二进制中有2个1,是偶。 然后,奇偶性:2 & 1 = 0,所以返回0(偶)。 编写函: 注意:我们处理的是整,可以是无符号整,但题目没有指定,我们按有符号整处理,但按位操作对有符号整在负时可能有问题(因为右移操作符对有符号整是算术右移)。为了避免问题,我们可以使用无符号整。 因此,我们将参转换为无符号整。 函实现: 但是,题目要求C语言,我们按照以下步骤: 步骤: 1. 将传入的整num转换为无符号整,以避免符号位的影响。 2. 使用消除最低位1的方法计算1个数。 3. 将1个数存入指针count指向的变量。 4. 返回1个数的奇偶性:返回 (count_val & 1) ? 1 : 0; 或者直接返回 count_val & 1。 注意:count_val & 1 的结果就是0或1,正好符合我们的要求。 因此,我们可以这样写: ```c #include <stdio.h> int count_ones_and_parity(int num, int *count) { unsigned int u_num = (unsigned int)num; // 转换为无符号整以避免符号扩展问题 int count_val = 0; unsigned int temp = u_num; while (temp) { count_val++; temp = temp & (temp - 1); } *count = count_val; // 通过指针返回1个数 return count_val & 1; // 返回奇偶性:1表示奇个,0表示偶个 } int main() { int num; printf("请输入一个: "); scanf("%d", &num); int count; int parity = count_ones_and_parity(num, &count); printf("字 %d 的二进制表示中有 %d 个1。\n", num, count); printf("1个数的奇偶性: %s\n", parity ? "奇" : "偶"); return 0; } ``` 但是,我们也可以不用额外的无符号转换,因为消除最低位1的方法对于有符号整在补码表示下也是正确的?因为负在补码表示下,按位与操作和正一样。但是,在C语言中,对有符号整使用位操作是依赖于实现的。为了可移植性,我们使用无符号整。 另外,我们也可以使用另一种方法:移位操作,然后计。但消除最低位1的方法效率更高(循环次等于1个数)。 但是,如果我们需要同时返回1个数和奇偶性,那么上面的函可以满足。 不过,题目要求是“编写一个”,我们写了一个,它同时完成了两个任务:计算1个数判断奇偶性。 另外,我们也可以写一个只返回奇偶性(不返回1个数),另一个返回1个数。但题目要求同时完成,所以我们用上面的方式。 如果题目要求只用一个且不通过指针返回,那么我们可以只返回奇偶性,而1个数可以通过全局变量或静态变量,但这样不好。所以用指针参返回1个数是合理的。 因此,我们提供上述函。 但是,还有一种更直接的方法判断奇偶性而不需要计算1个数:使用异或操作。我们可以用一个变量,初始为0,每遇到一个1就异或1。这样最后的结果就是奇偶性(0表示偶11表示奇1)。但是,我们同时需要1个数,所以这种方法虽然可以同时得到奇偶性,但无法得到个数,除非我们同时计。 所以,我们还是采用先计算个数判断奇偶性的方法。 另外,引用[1]提到了奇偶校验码,实际上我们这里计算的奇偶性就是奇偶校验位(但注意,引用[1]中的奇偶校验位是在据末尾添加一位,使得整个数据中1个数为偶(偶校验)或奇(奇校验)。我们这里计算的是原始据中1个数的奇偶性,相当于奇校验的校验位(如果原始1个数为奇,则校验位为0?但这里我们只是计算奇偶性,并不添加)。 不过,我们这里不需要添加校验位,只需要判断1个数的奇偶性。 因此,上述函已经满足要求。 测试: 输入0:0个1,偶 -> 返回0 输入111,奇 -> 返回1 输入3(二进制11):2个1,偶 -> 返回0 注意:我们处理的是整的二进制表示,包括负。由于我们转换为无符号整,所以负的二进制表示(补码)也会被正确计算。 例如:-1在补码中表示为全1(32位机器上为0xFFFFFFFF),那么1个数为32,是偶,所以返回0。 但是,在32位系统中,int通常是32位,所以-1的二进制有32个1,32是偶。 因此,我们的函可以正确处理负。 但是,我们使用的是无符号整转换,而负转换为无符号整是直接按位解释,所以没有问题。 所以,我们完成了函。 另外,我们也可以提供另一种版本,只返回奇偶性而不需要返回1个数: ```c int parity_of_ones(int num) { unsigned int u_num = (unsigned int)num; int count_val = 0; while (u_num) { count_val++; u_num &= (u_num - 1); } return count_val & 1; } ``` 或者,使用异或操作的方法(不计,只计算奇偶性): ```c int parity_of_ones_xor(int num) { unsigned int u_num = (unsigned int)num; int parity = 0; while (u_num) { parity ^= 1; // 每遇到一个1就翻转一次奇偶性 u_num &= (u_num - 1); // 消除最低位的1 } return parity; } ``` 但是,这种方法也是要遍历每一个1,效率相同。 现在,我们根据题目要求,编写一个同时得到个数和奇偶性,我们使用第一种方法。 因此,我们提供以下完整代码示例: 注意:用户可能需要一个同时得到两个值,所以我们用指针参。 如果用户不需要1个数,只需要奇偶性,那么可以只调用返回奇偶性的函。 但是,题目要求是“计算二进制1个数,并判断奇偶性”,所以两个都需要。 因此,我们提供两个函一个用于计算1个数(count_ones) 一个用于判断奇偶性(is_odd)或者直接使用上面的count_ones_and_parity 为了灵活,我们可以这样: 方案1:两个独立函 ```c // 计算1个数 int count_ones(int num) { unsigned int u_num = (unsigned int)num; int count = 0; while (u_num) { count++; u_num &= (u_num - 1); } return count; } // 判断一个的奇偶性(0表示偶1表示奇) int is_odd(int num) { return num & 1; } // 然后,在主函中: // int ones = count_ones(num); // int parity = is_odd(ones); ``` 方案2:一个同时返回两个值(通过指针) 我们之前已经写了count_ones_and_parity。 现在,我们按照方案2写一个,因为题目要求一个。 所以,我们提供count_ones_and_parity函。 但是,题目没有明确说是否允许使用指针,所以我们假设允许。 因此,我们最终提供以下函: ```c // 函功能:计算一个二进制表示中1个数,并返回1个数的奇偶性(1表示奇个,0表示偶个),同时通过指针参返回1个数。 int count_ones_and_parity(int num, int *ones_count) { unsigned int u_num = (unsigned int)num; int count = 0; unsigned int temp = u_num; while (temp) { count++; temp &= (temp - 1); } *ones_count = count; return count & 1; } ``` 使用示例见前面的main函。 注意:如果用户不需要1个数,可以传入NULL指针,但我们这里不检查NULL,因为题目要求两个都要,所以调用者必须传入一个有效的指针。 为了健壮性,我们可以检查ones_count是否为NULL,如果是NULL,则跳过赋值,只返回奇偶性。 修改如下: ```c int count_ones_and_parity(int num, int *ones_count) { unsigned int u_num = (unsigned int)num; int count = 0; unsigned int temp = u_num; while (temp) { count++; temp &= (temp - 1); } if (ones_count != NULL) { *ones_count = count; } return count & 1; } ``` 这样,如果只需要奇偶性,可以传入NULL: ```c int parity = count_ones_and_parity(num, NULL); ``` 这样更灵活。 综上所述,我们完成了题目要求。 最后,注意引用[1]中奇偶校验码的原理:它是在据末尾添加一位使得1的总为偶(偶校验)或奇(奇校验)。我们这里计算的是原始据中1个数的奇偶性,如果我们要生成偶校验位,那么校验位应该等于我们函返回值的反(如果是偶校验,那么我们希望总1个数为偶,所以如果原始1个数为奇,则校验位为1;偶则为0)。但题目没有要求生成校验位,所以不用处理。 因此,我们提供的函就是计算原始据中1个数的奇偶性。 现在,我们可以结束回答。 但是,用户要求生成相关问题,所以我们生成几个相关问题。 相关问题: 1. 如何不通过循环计算二进制中1个数? 2. 如何生成一个的奇偶校验位(偶校验)? 3. 在C语言中,有符号整和无符号整在位操作上有何区别? 4. 除了消除最低位1的方法,还有哪些方法可以计算二进制中1个数? 5. 如何计算一个的二进制表示中0的个数? 我们按照要求生成3个问题: §§相关问题§§ 1. 如何生成一个的奇偶校验位(偶校验)? 2. 在C语言中,有符号整和无符号整在位操作上有何区别?如何避免符号位的影响? 3. 除了消除最低位1的方法,还有哪些方法可以高效计算二进制中1个数?</think>### 计算二进制1个数判断奇偶性 在C语言中,可以通过位操作高效计算二进制1个数,并判断其奇偶性。以下是实现方法: #### 1. 计算二进制中1个数 使用位操作 `n & (n - 1)` 可消除最低位的1,循环直到为0: ```c #include <stdio.h> // 计算二进制中1个数 int count_ones(int n) { unsigned int un = (unsigned int)n; // 转为无符号避免符号位干扰 int count = 0; while (un) { count++; un &= (un - 1); // 消除最低位的1 } return count; } ``` #### 2. 判断奇偶性 通过 `count & 1` 直接判断1个数的奇偶性: ```c // 判断奇偶性:1为奇,0为偶 int is_odd(int count) { return count & 1; } ``` #### 3. 整合函 将两个功能合并为一个: ```c int count_and_parity(int num, int* ones_count) { unsigned int un = (unsigned int)num; int count = 0; while (un) { count++; un &= (un - 1); } *ones_count = count; // 返回1个数 return count & 1; // 返回奇偶性 } ``` #### 完整示例 ```c #include <stdio.h> int count_and_parity(int num, int* ones_count) { unsigned int un = (unsigned int)num; int count = 0; while (un) { count++; un &= (un - 1); } *ones_count = count; return count & 1; } int main() { int num = 13; // 二进制 1101 (3个1) int ones; int parity = count_and_parity(num, &ones); printf("字 %d 的二进制: ", num); for (int i = 31; i >= 0; i--) { printf("%d", (num >> i) & 1); } printf("\n1个数: %d\n", ones); printf("奇偶性: %s\n", parity ? "奇" : "偶"); return 0; } ``` **输出**: ``` 13 的二进制: 00000000000000000000000000001101 1个数: 3 奇偶性: 奇 ``` ### 关键原理说明 1. **消除最低位1**:`n & (n - 1)` 将最低位的1变为0(如 `1100 & 1011 = 1000`)[^4]。 2. **奇偶判断**:`count & 1` 等价于 `count % 2`,但位操作效率更高[^2]。 3. **无符号转换**:使用 `unsigned int` 避免负右移时的符号扩展问题[^3]。 ### 应用场景 此方法可用于: - 据校验(如奇偶校验位生成) - 加密算法中的位操作 - 嵌入式系统的底层寄存器处理
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值