2.2 数据类型与长度
练习2-1
原文链接:https://blog.csdn.net/taolusi/article/details/52291798
编写一个程序以确定分别由signed及unsigned 限定的char、short、int与long类型变量的取值范围。采用打印标准头文件中的相应值以及直接计算两种方式实现
方法1 打印标准头文件中的响应值
#include <stdio.h>
#include <limits.h>
//determing ranges of types
int main()
{
// signed types
printf("Signed char min = %d\n", SCHAR_MIN);
printf("Signed char max = %d\n", SCHAR_MAX);
printf("Signed short min = %d\n", SHRT_MIN);
printf("Signed short max = %d\n", SHRT_MAX);
printf("Signed int min = %d\n", INT_MIN);
printf("Signed int max = %d\n", INT_MAX);
printf("Signed long min = %ld\n", LONG_MIN);
printf("Signed long max = %ld\n", LONG_MAX);
// unsigned types
printf("unsigned char max = %u\n", UCHAR_MAX);
printf("unsigned short max = %u\n", USHRT_MAX);
printf("unsigned int max = %u\n", UINT_MAX);
printf("unsigned long max = %lu\n", ULONG_MAX);
return 0;
}
方法2 利用按位运算符进行计算
#include<stdio.h>
//determing ranges of types
int main()
{
// signed types
printf("Signed char min = %d\n", -(char)((unsigned char) ~0 >> 1));
printf("Signed char max = %d\n", (char)((unsigned char) ~0 >> 1));
printf("Signed short min = %d\n", -(short)((unsigned short) ~0 >> 1));
printf("Signed short max = %d\n", (short)((unsigned short) ~0 >> 1));
printf("Signed int min = %d\n", -(int)((unsigned int) ~0 >> 1));
printf("Signed int max = %d\n", (int)((unsigned int) ~0 >> 1));
printf("Signed long min = %ld\n", -(long)((unsigned long) ~0 >> 1));
printf("Signed long max = %ld\n", (long)((unsigned long) ~0 >> 1));
// unsigned types
printf("unsigned char max = %u\n", (unsigned char) ~0);
printf("unsigned short max = %u\n", (unsigned short) ~0);
printf("unsigned int max = %u\n", (unsigned int) ~0);
printf("unsigned long max = %lu\n", (unsigned long) ~0);
return 0;
}
(char)((unsigned char) ~0 >> 1)这个表达式的作用:
~0将0的各个二进制位取反全部转换为1,(unsigned char) ~0将结果值转换为unsigned char类型,(unsigned short) ~0 >> 1将unsigned char类型值右移一位以清除符号位,(char)((unsigned char) ~0 >> 1))将结果值进一步转换为char类型,最终得到了signed char类型的最大值。
2.6 关系运算符与逻辑运算符
练习2-2
在不使用运算符&&或|| 的条件下编写一个与上面的for循环语句等价的循环语句
#include <stdio.h>
#include <stdlib.h>
#define MAXLENGTH 100
int getline(char line[], int limit);
int main()
{
int length;//当前行的长度
char line[MAXLENGTH] ;//保存当前行的字符串
int i = 0;
for (i = 0; i < MAXLENGTH; i++)
line[i] = '\0';
while ((length = getline(line, MAXLENGTH)) > 0)
{
printf("%d\n", length);
printf("%s\n", line);
}
return 0;
}
/*getline函数:将输入的字符保存到line数组中,并返回数组长度。 */
int getline(char line[], int limit)
{
int c;
int i;
c = i = 0;
enum loop { NO, YES };
enum loop okloop = YES;
while (okloop == YES) //进行循环的条件
{
if (i >= limit - 1)
okloop = NO;
else if ((c = getchar()) == '\n')
okloop = NO;
else if ((c = getchar()) == EOF)
okloop = NO;
else
line[i] = c;
++i;
}
if (c == '\n')
{
line[i] = c;
++i;
}
line[i] = '\0';
return i;
}
不够准确。
2.7 类型转换
练习2-3
编写函数 htoi(s),把由十六进制数字组成的字符串(包含可选的前缀0x或0X)转换为与之等价的整型值。字符串中允许包含的数字包括:0~9、a~f以及A~F。
C语言程序设计(第二版) 练习2-3 个人设计
#include <stdio.h>
#include <stdlib.h>
int htoi(char s[]);
int main()
{
char s[] = "0xA1F";
printf("%d\n", htoi(s));
system("pause");
return 0;
}
int htoi(char s[]) /*十六进制转换函数*/
{
int i, n, num; /*定义循环变量,中间变脸,最终转换值*/
n = 0;
num = 0; /*以下两行为变量设置初始值*/
i = 0;
if (s[i] == '0') { /*以下四行为判断是否为十六进制数值,以0x或0X开头作为条件*/
++i;
if (s[i] == 'x' || s[i] == 'X') {
++i;
for (; s[i] != '\0'; ++i) { /*判断字符串是否结束并循环*/
if (s[i] >= '0' && s[i] <= '9') /*如果字符为数字,转换为相应值*/
n = s[i] - '0';
if (s[i] >= 'A' && s[i] <= 'F') /*如果字符为大写字母,转换为相应十进制的值,如F在十进制中的值为15.*/
n = s[i] - 'A' + 10;
if (s[i] >= 'a' && s[i] <= 'f') /*如果字符为小写字母,转换为相应十进制的值。*/
n = s[i] - 'a' + 10;
num = num * 16 + n; /*循环求值,同下面的十进制2564从左边开始计算求其十进制值2564一样,这是十六进制A1F从左边开始计算求其十进制值2591的一个规律。*/
}
}
}
return num;
}
后面一个值,是用前面相同的值计算出来的,则考虑用循环(for或者while)
有自增量时,用for;没有时,用while.
文件的结束符 EOF ;字符串的结束符 ‘\0’
C语言三个结束符:EOF ‘\0’ ‘\n’
OK,我们现在讲一下解题步骤中第四步那个公式的来源:
如果你去百度或谷歌十六进制转换十进制,告诉你的方法都是从十六进制的最右端开始,乘以16的几次方一类的,这个方法适用于人,但是如果用C来实现,显得有点麻烦了,所以我才去了从高位开始转换,就是从最左端
我举个十进制的例子,大家就会豁然开朗了
比如有一个字符串‘2564’,他是十进制
它等于什么?
我们有那个公式来展开2564的话就是这样:
0 * 10 + 2 = 2
2 * 10 + 5 = 25
25 * 10 + 6 = 256
256 * 10 + 4 = 2564
因为是十进制所以我们乘以10,十六进制我们乘以16.
#include<stdio.h>
#define MAXLEN 1024
#define YES 1//是十六进制
#define NO 0//不是十六进制
int htoi(char array[]);//进制转换函数
int getlines(char array[], int maxlen);//字符串读取函数
int main()
{
int len;//字符串长度
int ten_number;//十进制数
char array[MAXLEN];
while ((len = getlines(array, MAXLEN)) > 0)
{
ten_number = htoi(array);
printf("%d", ten_number);
putchar('\n');
}
return 0;
}
int getlines(char array[], int maxlen)
{
int c, i;
for (i = 0; i < maxlen - 1 && (c = getchar()) != EOF && c != '\n'; i++)
{
array[i] = c;
}
if (c == '\n')
{
array[i] = c;
i++;
}
array[i] = '\0';
return i;
}
int htoi(char array[])
{
int i, n;
int result = 0;
int state = YES;//判断是否是十六进制
i = 0;
if (array[i] == '0')
{
i++;
if (array[i] == 'x' || array[i] == 'X')
{
i++;
n = 0;
for (; array[i] != '\0' && state == YES; i++)
{
if (array[i] >= '0' && array[i] <= '9')
{
result = array[i] - '0';
}
else if (array[i] >= 'A' && array[i] <= 'F')
{
result = array[i] - 'A' + 10;
}
else if (array[i] >= 'a' && array[i] <= 'f')
{
result = array[i] - 'a' + 10;
}
else
{
state = NO; \\不止是开头两个字符,所有的字符如有任意一个不在十六进制的表达范围,则state被赋值为NO,所以这个命令语句处在这个位置
}
if (state == YES) \\接着上面的判断,所有的字符都在十六进制的表达范围之中,则state仍为YES
{
n = n * 16 + result;
}
}
}
return n;
}
else
printf("error");
}
不同的main()函数的输出情况:
原因:满足while语句的条件而且一直满足,没有被终止。
(1)在循环开始处加上break:输入并判断一行后,退出程序。
不要在循环的最后加,没有意义。
加入break表明要退出while循环,但while循环后面还有其他语句的话,还是会执行。
(2)在循环最后加入continue:输入一行,判断之后,无限循环输出。
加入continue表明要退出当次while循环,接着从while循环的开始部分重新执行下来。
(3)在循环最后加入return:输入一行后,退出函数。
加入return表明退出while循环,且后面还有其他语句的话,直接跳过不会执行。
2.8 自增运算符和自减运算符
#include <stdio.h>
int main()
{
int i, j;
char s[12] = { 'I',' ','a','m',' ','h','a','p','p','y','q'};
int c = 'q';
for (i = j = 0; s[i] != '\0'; ++i)
if (s[i] != c)
{
s[j++] = s[i];
printf("%d %d\n", i, j);
}
s[j] = '\0';
printf("%s\n",s);
return 0;
}
P56(PDF)/ P37(书):
这里的 i 即:不需要使用任何具体值,仅需要递增变量。前缀后缀方式均可以。每次循环之后,i 的值总是加1的,不必关心 i++ 的值是多少。
需要使用 j 代表的下标,所以 j 必须先给值再自增。
因为 for 的执行顺序1-2-4-3-2-4-3-2-4-3-2-……,不管是i++ 还是++i,j 的值总比 i 大 1 .
如果用++j,那么读入的时候发生把s[0]写到s[1]的位置,后面依次类推,最终s[0] 的值不变,其他前一个写到后一个的位置,即全部都成为s[0]的值,且读不到字符数组结束符。
练习 2-4
第一种就是将s1字符串的字符,一个一个的去和s2的全部字符对比,一旦发现有重复的,比如s1中的第5个字符与s2中的某个字符重复,我们就把s1中,从第六个字符(包括第六个)之后的字符全部往前移动一位,这样第五个字符就被覆盖了,如同被删除。
#include<stdio.h>
#define MAXLEN 1024
void squeeze(char s1[], char s2[]);
void getlines(char array[], int maxlen);
int main()
{
char s1[MAXLEN], s2[MAXLEN];
getlines(s1, MAXLEN);
getlines(s2, MAXLEN);
squeeze(s1, s2);
printf("%s", s1);
return 0;
}
void squeeze(char s1[], char s2[])
{
int i = 0, j = 0;
for (i = 0; s1[i] != '\0'; i++) //s1不动时,遍历s2,且依次处理s1(s2不动时,遍历s1,处理s1时也是每次都把s1中不相同的值前移。此方法中 i,j 两个for循环的顺序没有影响。)
{
for (j = 0; s2[j] != '\0'; j++)
{
if (s1[i] == s2[j])
{
int k = i;
while (s1[k] != '\0')
{
s1[k] = s1[k + 1]; //之后的字符全部往前移动一位
k++;
}
j = -1; //可以删除,没有什么实际作用
s1[k] = '\0';
}
}
}
}
void getlines(char array[], int maxlen)
{
int c, i;
for (i = 0; i < maxlen - 1 && (c = getchar()) != EOF && c != '\n'; i++)
{
array[i] = c;
}
if (c == '\n')
{
array[i++] = c;
}
array[i] = '\0';
}
第二种也是要对比两个字符串,举个例子来解释这种方法,
例如s1中的第一个字符没有在s2中出现过,就将这个字符存到s1的第一个位置
再对比s1中第二个字符,如果这个字符在s2中也有,那么就不做任何操作
再对比s1中第三个字符,如果s2中没有这个字符,那么就将这个字符存进s1的第二个字符中
以此类推,最后给s1的最后一个位置存一个’\0’
其原理就是将不与s2重复的字符重新储存进s1中
#include<stdio.h>
#define MAXLEN 100
void getlines(char array[], char maxlen)
{
int i, c;
for (i = 0; i < maxlen - 1 && (c = getchar()) != EOF && c != '\n'; i++)
{
array[i] = c;
}
if (c == '\n')
{
array[i] = c;
i++;
}
array[i] = '\0';
}
void squeeze(char s1[], char s2[])
{
int i, j, k;
for (i = k = 0; s1[i] != '\0'; i++)
{
for (j = 0; s2[j] != '\0' && s2[j] != s1[i]; j++) //对不相等的值其实进行了后面的拷贝操作,但是是在遍历完成s2[]之后才进行的,并不是立刻拷贝。
{ //此方法中就必须保持s1不动,遍历s2,处理s1
;
}
if (s2[j] == '\0')
{
s1[k++] = s1[i]; //拷贝操作,在遍历完s2之后,把不相等的那一个s1[i]的值重新写入s1中
}
/* 如果在遍历s2时,中间出现一个与s1相同的,for跳出,if不满足跳出,就什么都不执行,且j也不会发生自增,这个重复的值不执行拷贝,也就相当于被删除了。
接着判断s2中后面的字符与该s1中的字符是否相同,若后面的与该字符s1中的字符都相同,因为不满足for循环的条件,所以j不会自增,即到不了s2结束符处,也是什么都不执行。 */
//如果s1的某个字符,与s2整个字符串都不相同,就会把s2遍历到最后,使s2[j] == '\0',就会执行上面这个if语句,不相同的值从s1[0]开始保存。
}
s1[k] = '\0';
}
int main()
{
char s1[MAXLEN];
char s2[MAXLEN];
getlines(s1, MAXLEN);
getlines(s2, MAXLEN);
squeeze(s1, s2);
printf("%s", s1);
return 0;
}
练习 2-5
编写函数 any(s1, s2),将字符串s2中的任一字符在字符串s1中第一次出现的位置作为结果返回。如果s1中不包含s2中的字符,则返回-1。
#include<stdio.h>
#define MAXLEN 1024
int getlines(char array[], int limit);
void any(char s1[], char s2[]);
int main()
{
char s1[MAXLEN];
char s2[MAXLEN];
int lens1, lens2;
lens1 = 0;
lens2 = 0;
lens1 = getlines(s1, MAXLEN);
lens2 = getlines(s2, MAXLEN);
any(s1, s2);
return 0;
}
int getlines(char array[], int limit)
{
int i, c;
for (i = 0; i < limit - 1 && (c = getchar()) != EOF && c != '\n'; i++)
{
array[i] = c;
}
if (c == '\n')
{
array[i] = c;
i++;
}
array[i] = '\0';
i++;
return i;
}
void any(char s1[], char s2[])
{
int i, j, sign, cnt;
i = j = sign = cnt = 0;
for (i = 0; s2[i] != '\0' && s2[i] != '\n'; i++)
{
sign = 0;
for (j = 0; sign == 0 ; j++) //sign == 0 保证只判断及返回某个字符第一次出现的位置。当第二次出现时,因为不满足判断条件,不进入这个for循环。
{
if (s1[j] == s2[i] && (s1[j] != '\0'))
{
sign = 1;
cnt++;
printf("%c在%d位置首次出现。\n", s2[i], j + 1);
}
}
if (s1[j] == '\0') //一次也没有执行上面的循环,说明s1中不包含s2中的这个字符。不能用cnt == 0 及 sign 作为判断标准。
{
printf("s1中不包含s2中的字符%c,%d\n", s2[i], -1);
}
}
if (s2[i] == '\0') //一次也没有执行上面的循环,说明s1中不包含s2中的这个字符。不能用cnt == 0 及 sign 作为判断标准。
{
printf("s1中不包含s2中的任意一个字符.");
}
}
有多余行输出:
当全部不包含时,乱输出或者不输出:
#include <stdio.h>
int any(int p[], char s[], char t[]); /*定义any函数*/
int length(char t[]); /*定义字符串s2的长度函数*/
/*主程序为判断字符串s2中的任一字符在字符串s1中第一次出现的位置*/
int main()
{
int i;
int c;
i = c = 0;
int p[100];
char a[] = "abc"; //函数中的字符串s
char b[] = "abcdef"; //函数中的字符串t,将b中的每一个字符依次与a中的每一个字符比较,相等的返回该字符在a中的位置。
any(p, a, b); //并没有使用函数any的返回值,但是数组p中元素已经被赋给相应的值,可以取用其中的任意一个。
c = length(b); //确保把b中的元素全部判断完
for (i = 0; i < c; i++)
printf("%d ", p[i]);
printf("\n");
return 0;
}
/*any函数程序*/
int any(int p[], char s[], char t[])
{
int i, j, m, k;
for (j = m = 0; t[j] != '\0'; j++) { /*字符串t中的字符从第一个开始与字符串s中的字符配对*/
k = 0;
for (i = 0; (s[i] != '\0') && (s[i] != t[j]); i++) /* 字符串t中的每一个字符与s中的第一个字符不相等,则k等于1,后面的还要接着判断,若有一个相等的(且还没有到达s的结束符)(此时k不会返回去等于0,仍为1)则要返回此时s的位置。即下面if中的前一个条件;第一个就相等则k=0,可知要返回该字符在s中的位置,即1,后面的不用再判断。 */
k = 1;
if (((k == 1) && (i != length(s))) || ((k == 0) && (i == 0)))
p[m++] = i + 1; //将该字符在s中的位置,赋给数组p的元素,且数组p中的下标增加1
else
p[m++] = -1; //否则就将-1赋给数组p中的元素,且数组p中的下标增加1
}
return p[m];
}
int length(char t[])
{
int i, j;
for (i = 1; t[i] != '\0'; ++i)
;
j = i;
return j;
}
2.9 按位运算符
#include <stdio.h>
int main()
{
int n = 4049;
n = n & 127;
printf("%d", n);
return 0;
}
//结果为81
练习 2-6
编写一个函数setbits(x, p ,n, y),该函数返回对x执行下列操作后的结果值: 将x中从第p位开始的n个(二进制)位设置为y中最右边n位的值,x的其余各位保持不变。
前提假定:最右边的一位是第0位
#include<stdio.h>
unsigned setbits(unsigned x, int p, int n, unsigned y)
{
return ((x & ~(~(~0 << n) << p + 1 - n)) | ((~(~0 << n) & y) << p + 1 - n));
}
int main()
{
printf("%d\n", setbits(93, 4, 3, 211));
return 0;
}
执行顺序:
(1)执行按位或 | 前:
~0
(~0 << n)
(0 << n) 建立了最右边n位全为1的屏蔽码,前面的高位全部为0
((0 << n) << p + 1 - n) 将这n位全为1的屏蔽码左移p+1-n位(第n-1位移至第p位(n-1+p+1-n,原位数加移动的步数),第0位移至第p+1-n位),低位用0补全.除这n位屏蔽码为1之外,其余前后位均为0.
((~0 << n) << p + 1 - n) 之后再取反,此时除这n位屏蔽码为0之外,其余全部为1.
(x & ((~0 << n) << p + 1 - n) 将x中的屏蔽码对应的n位二进制位全部都变为0,剩余的保持原值
(2)执行按位或 | 后:
~0
(~0 << n)
(0 << n) 建立了最右边n位全为1的屏蔽码,前面的高位全部为0
((0 << n) & y) 保持y中最右边低n位的值不变,其余左边高位的二进制全部变为0
(((0 << n) & y) << p + 1 - n) 将y中最右边的低n位左移p+1-n位(第n-1位移至第p位(n-1+p+1-n,原位数加移动的步数),第0位移至第p+1-n位),低位用0补全,前面的二进制位也为0
(3)执行按位或 |
(x & ((~0 << n) << p + 1 - n)) | (((0 << n) & y) << p + 1 - n)
只分别取x中的原值(最高位至第p+1位,第p-n位至最低位第0位);
y中的原值(第p位至第p+1-n位,共n位)
用倒推法做:
x中间n位全为0:利用屏蔽码的左移(左移的步数p + 1 - n)及取反,保留x前后部分的原值;
y中 保留y中最右边低n位的原值,并将该部分左移(左移的步数p + 1 - n);及除与x中目标n位对应的部分之外 y中高位及低位 要全部为0.
(左移后,y中的低位会自动补0;
因此任务即是要把y中的低位部分屏蔽掉再左移,保留其原值再左移。
将y的高位变为0,保留低位原值,利用不移动的屏蔽码即可实现。)
整数在计算机中补码的各二进制位,不是数组,不是字符串,不能用元素赋值。
一种改变十进制整数值的思路:利用最右边低位全为1的屏蔽码(0 << n) 。
屏蔽码(0 << n) 的意思即:在用屏蔽码与整数进行按位与预算&时,保留整数的二进制码中 与该屏蔽码中为1的n位部分 对应的部分的原值,使该部分不受影响,如同被屏蔽掉。
屏蔽码(0 << n) 是从整数的低位开始屏蔽(保留整数原值)的。其中为1的片段长度n不变时, 该片段自身可通过进行左移、取反的运算,实现在与整数进行按位与运算&时,改变对整数中屏蔽的位置(保留整数原值的位置)。 如(2)中屏蔽整数前后两端,保留整数前后两端原值。
屏蔽码(0 << n) 中为1的片段长度n变长,则屏蔽的范围变大。(1)如果不左移该屏蔽码(0 << n) 全为1的部分,直接与整数进行按位与&运算,则保留该整数中最右边低n位 与屏蔽码中为1的n位部分 对应的部分 原值,其他高位二进制全部变为0。将该整数中最右边低n位的部分屏蔽掉(保留原值)。
(2) 如果先将屏蔽码中为1的部分左移,再将移动后的屏蔽码取反,然后与该十进制计算机中的二进制补码进行按位与运算&,即可保留该整数中与 左移后屏蔽码为1的部分所在的片段 对应的部分的原值。即将整数中左移后屏蔽码为1的n位部分所在的片段 的前后部分屏蔽掉(保留原值)。
当屏蔽码为1的部分所在的片段长度n不变,从屏蔽码最右端左移到最左端时,则整数中前面部分前n位会被屏蔽掉(保留原值)。
练习 2-7
编写一个函数invert(x,p,n),该函数返回对x执行下列操作后的结果:将x从第p位开始的n个(二进制)位求反(即1变成0,0变成1),x的其余各位保持不变。
前提假定:最右边的一位是第0位
#include <stdio.h>
unsigned invert(unsigned x, int p, int n);
int main()
{
int i;
unsigned a;
for(i = 0; i < 3; i++)
{
printf("输入一个整数:");
scanf_s("%X",&a,sizeof(unsigned));
printf("对第二三四位求反:%#X\n",invert(a,4,3));
}
return 0;
}
unsigned invert(unsigned x, int p, int n)
{
return (~(~0<<n) << (p-n+1)) ^ x;
}
按位异或运算^ :两个操作数的对应位相同的时候设置为0,不同的时候设置为1.
((0<<n) << (p-n+1)) 将屏蔽码中的1移到中间 与x需要处理位的相同位置处
将该屏蔽码与x进行异或运算^,正好实现题目中的求反要求。
X^1的结果 与 ~X的结果相同。
输入16进制数,返回16进制数
#include <stdio.h>
unsigned invert(unsigned x, int p, int n);
int main()
{
int i;
unsigned a;
a = 26;
printf("对第二三四位求反:%d\n", invert(a, 4, 3));
return 0;
}
unsigned invert(unsigned x, int p, int n)
{
return (~(~0 << n) << (p - n + 1)) ^ x;
}
设定a为十进制某数,输出十进制数。
练习 2-8
第一种:就是默认二进制位数,前面没有多余的0来补位,比如111的二进制是1101 111,我就认为题目的函数对1101 111进行循环右移,就是把最右端的数字放到最左端,即 1101 111
#include<stdio.h>
int rightrot(unsigned int x, int n)
{
int k, i;
int ribits;
k = x;
for (i = 0; k != 0; i++)//算出x二进制形式有多少位
{
k = k >> 1; //先将K右移一位,再将移动后的K值赋成新的K值
}
/* K经过多次右移,会变成0 .K =0时,i的值为K右移的次数,也即K二进制形式的位数。 */
ribits = (~(~0 << n) & x) << (i - n); //(~(~0 << n) & x)结果X中低n位二进制全部都保留原值,高位二进制全部归为0. ribits是将X中保留原值的n位部分移i-n位到最前面,其余低(i-n)位全部归为0(先从右端移出的也先进入左端,所以直接把从右端移出的部分按原顺序平移到最前即可).
x = (x >> n) | ribits; //x >> n时,X中高n位补0.
return x;
}
int main()
{
printf("%d", rightrot(111, 3));
return 0;
}
第二种:这一种理解方式是比较符合实际的,就是计算机中二进制数位是有规定的,比如有的计算机是32位。
那么111的二进制就不是1101 111了,而是0000 0000 0000 0000 0000 0000 0110 1111(在前面补了25位0,对数值大小无影响)。
那么我们把最右端的一个数字放到最左端就是1000 0000 0000 0000 0000 0000 0110 111 结果显然与第一种不同。
#include<stdio.h>
unsigned rightrot(unsigned x, unsigned n)
{
while (n > 0) {
if ((x & 1) == 1) //x&1用来判断x的奇偶性,x&1结果为1则n为奇数,为0则n为偶数。
x = (x >> 1) | ~(~0U >> 1);//U表示无符号,相当于unsigned. 0U表示无符号整型0
else
x = (x >> 1);
n--;
}
return x;
}
int main()
{
printf("%d",rightrot(111,3));
return 0;
}
X为奇数,如111时,共移动右三位。
每次只移动一位,每次都判断被移动数的奇偶性:
(1)当被移动数为奇数时,即将其最后一位的1移到最前面,其余保留原值且右移一位:
x: 0000 0000 0000 0000 0000 0000 0110 1111
x >> 1: 0 0000 0000 0000 0000 0000 0000 0110 111
~0U:1111 1111 1111 1111 1111 1111 1111 1111
~0U >> 1: 0 1111 1111 1111 1111 1111 1111 1111 111
(0U >> 1):1 0000 0000 0000 0000 0000 0000 0000 000(不是屏蔽码(0 << n))
(x >> 1) | (0U >> 1) : 按位或运算 1 0000 0000 0000 0000 0000 0000 0110 111
(2)当被移动数为偶数时,即将其最后一位的0移到最前面,其余保留原值且右移一位:
使用 x >> 1即可满足要求,高位补0,低位舍弃,其余右移。
2.10 赋值运算符与表达式
函数bitcount统计X中值为1的二进制位数。
#include<stdio.h>
int bitcount(unsigned x)
{
int b;
for (b = 0; x != 0; x >>= 1)
{
if (x & 01)
b++;
}
return b;
}
int main()
{
printf("%d", bitcount(111));
return 0;
}
//结果为6,对应111的二进制1101111.
练习 2-9
原bitcount函数:统计x中值为1的二进制位数。
#include<stdio.h>
int bitcount(unsigned x)
{
int b;
for (b = 0; x != 0; x >>= 1)
{
if (x & 01)
b++;
}
return b;
}
int main()
{
printf("%d", bitcount(111));
return 0;
}
//结果为6,对应111的二进制1101111.
表达式 x &= (x - 1)可以删除x中最右边值为1的一个二进制位。
利用该表达式改进bitcount函数。
#include<stdio.h>
int bitcount(unsigned x)
{
int b = 0;
while (x != 0)
{
x &= (x - 1); //等价于 x = x & (x - 1)
b++;
}
return b;
}
int main()
{
printf("%d", bitcount(111));
return 0;
}
//结果为6,对应111的二进制1101111.
#include<stdio.h>
int bitcount(unsigned x)
{
int b = 0;
for (b = 0; x != 0; x &= (x - 1))
{
b++;
}
return b;
}
int main()
{
printf("%d", bitcount(111));
return 0;
}
//结果为6,对应111的二进制1101111.
2.11 条件表达式
练习 2-10
重新编写将大写字母转换为小写字母的函数lower,并用条件表达式替代其中的if-else结构。
原lower函数:
#include<stdio.h>
int lower(int c)
{
if (c >= 'A' && c <= 'Z')
return c + 'a' - 'A';
else
return c;
}
int main()
{
int c = 0;
int n = 0;
while ((c = getchar()) != EOF && c != '\n')
{
n = lower(c);
printf("%c", n); //printf("%d", n); 也可
}
return 0;
}
用条件表达式?: 改写后的lower函数。
#include<stdio.h>
int lower(int c)
{
(c >= 'A' && c <= 'Z') ? (c + 'a' - 'A') || c;
}
int main()
{
int c = 0;
int n = 0;
while ((c = getchar()) != EOF && c != '\n')
{
n = lower(c);
printf("%c", n); //printf("%d", n); 也可
}
return 0;
}