下面就各操作的重点和坑点讲解
注意:整数在内存中存的都是补码,下面的位操作都是发生在补码身上,再用原码显示出来。
注意:浮点数是不能用移位操作的,下面的操作数都是整数。
注意:原码------>补码(先反码再减一;先减一再反码)。
int num = 10;
int a = num << 1;
//0000 0000 0000 0000 0000 0000 0000 1010
//0000 0000 0000 0000 0000 0000 0001 0100
//a的值为20
//注意num本身没有发生改变,除非使用<=
那么究竟什么时候用逻辑右移,什么时候用算术右移呢?就测试情况来看的话,绝大多数编译器用的都是算术右移。
另外移动的位数必须是正数,负数是未定义的!
#include
int main()
{
int a = 10;
int b = 20;
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a = %d b = %d\n", a, b);
return 0;
}
//a ^ a = 0
//0 ^ a = a
//a ^ 0 = a
//假设^存在逆运算v
//则a ^ a v a= 0 v a,a= 0 v a
//则0 v a = a = 0 ^ a,故“v”<-->“^”。
//本方法缺陷在于:只能是整数,并且可读性差,效率也不一定提高
//方法1:
#include
int main()
{
int num = 10;
int count= 0;//计数
while(num)
{
if(num%2 == 1)
count++;
num = num/2;
}
printf("二进制中1的个数 = %d\n", count);
return 0;
}//思考这样的实现方式有没有问题?
//方法2:
#include
int main()
{
int num = -1;
int i = 0;
int count = 0;//计数
for(i=0; i<32; i++)
{
if( num & (1 << i) )
count++;
}
printf("二进制中1的个数 = %d\n",count);
return 0;
}//思考还能不能更加优化,这里必须循环32次的。
//方法3:
#include
int main()
{
int num = -1;
int i = 0;
int count = 0;//计数
while(num)
{
count++;
num = num&(num-1);
}
printf("二进制中1的个数 = %d\n",count);
return 0;
}//这种方式是不是很好?达到了优化的效果,但是难以想到。
//连续赋值
int a = 10;
int x = 0;
int y = 20;
a = x = y + 1;
//上面连续赋值的等价写法
int a = 10;
int x = 0;
int y = 20;
x = y + 1;
a = x;
int a = 3;//与a = 3不一样
下面就重点讲解
#include
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//(3)
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//(4)
}
int main()
{
int arr[10] = {0};
char ch[10] = {0};
printf("%d\n", sizeof(arr));//(1)
printf("%d\n", sizeof(ch));//(2)
test1(arr);
test2(ch);
return 0;
}
其中(1)(2)(3)(4)的值为40、10、8、8
因为(3)(4)是指针变量,本程序是在x64环境下运行的,所以得出的是指针类型的大小,如果是x86环境则值为4。
//前置++和--
#include
int main()
{
int a = 10;
int x = ++a;//先对a进行自增,然后对使用a,也就是表达式的值是a自增之后的值。x为11。
int y = --a;//先对a进行自减,然后对使用a,也就是表达式的值是a自减之后的值。y为10;
printf("x=%d, y=%d", x, y);
return 0;
}
//后置++和--
#include
int main()
{
int a = 10;
int x = a++;//先对a先使用,再增加,这样x的值是10;之后a变成11;
int y = a--;//先对a先使用,再自减,这样y的值是11;之后a变成10;
printf("x=%d, y=%d", x, y);
return 0;
}
还有一道易错题目
#include int main()
{
int a, b, c;
a = 5;
c = ++a;
b = ++c, c++, ++a, a++;
b += a++ + c;//注意这里先用a再++
printf("a = %d b = %d c = %d\n:", a, b, c);
return 0;
}
最后的结果是: a = 9,b= 23,c = 8
#include
int main()
{
int a = 0;
//0000 0000 0000 0000 0000 0000 0000 0000经过~后变成
//1111 1111 1111 1111 1111 1111 1111 1111生成一个补码
//1111 1111 1111 1111 1111 1111 1111 1110返回反码
//1000 0000 0000 0000 0000 0000 0000 0001返回原码
printf("%d\n", ~a);//故输出-1
return 0;
}
注意有一种写法的理解
while(~scanf("%d", &number))//这里就是将scanf读取失败返回EOF(-1)的二进制按位取反为-1
{
//某些代码
}
int main()
{
int a = 1;
printf("%f %zd\n", (float)a, sizeof(a));
printf("%d %zd\n", a, sizeof(a));
return 0;
}//可以看到两次输出sizeof都是4(x64)平台
注意:等于“==”不是“=”,这一点经常会出错
注意:逻辑操作符的短路特性,可以减小运算。当a&&b中,a表达式为假,b表达式就不会被执行;当a||b中,a表达式为真,b表达式就不会被执行
#include
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
//i = a++ || ++b || d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
//程序输出的结果是什么?因为a为0(先使用未++),后面的表达式就不重要了
//a = 1
//b = 2
//c = 3
//d = 4
//如果换成i = a++ || ++b || d++;呢?
//a = 1
//b = 3
//c = 3
//d = 4
//分支语句写法
if(a > 5)
{
b = 3;
}
else
{
b = -3;
}
//使用三目操作符改写
a > 5 ? b = 3 : b = -3;
* exp1, exp2, exp3, ……
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
//代码1
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);//整个表达式的值是最后一个表达式的值
//演示了结构体的成员访问方法
#include
struct Stu//一个结构体
{
char name[10];
int age;
char sex[5];
double score;
};
void set_age1(struct Stu stu)
{
stu.age = 18;
}
void set_age2(struct Stu* pStu)
{
pStu->age = 18;//结构成员访问
}
int main()
{
struct Stu stu;
struct Stu* pStu = &stu;//结构成员访问
stu.age = 20;//结构成员访问
set_age1(stu);
pStu->age = 20;//结构成员访问
set_age2(pStu);
return 0;
}
表达式求值的顺序一部分是由操作符的优先级和结合性决定。同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
①C语言的整形算术运算总是至少以缺省(默认)整形类型的精度来进行的.
②为了获得这个精度,表达式中的“字符操作数”和“短整型操作数”在使用之前被转换为“普通整型”,这种转换称为整型提升。
char a, b = 3, c = 127;//b和c都被提升为整型类型,然后执行加法运算
//0000 0000 0000 0000 0000 0000 0000 0011 = 3
//0000 0000 0000 0000 0000 0000 0111 1111 = 127
//先截断存储在char中0000 0011和0111 1111,由于不溢出,故值不变
a = b + c;
//由于需要运算,需要整型提升来提高计算精度,因此0000 0011和0111 1111又变成了
//0000 0000 0000 0000 0000 0000 0000 0011
//0000 0000 0000 0000 0000 0000 0111 1111
//相加后就是//0000 0000 0000 0000 0000 0000 1000 0010 = 130
//加法运算完成后,结果被截断为1000 0010,然后被存储在a中。
//由于整个过程没有负数和溢出问题,所以平时使用运算的时候,整型提升会观察不出来
为什么说“没有负数”呢?
//①负数的整形提升
char c1 = -1;
//变量c1的二进制位(补码)中只有8个比特位:1111111
//因为 char 默认为有符号的 char,所以整形提升的时候,高位补充符号位,即为1
//提升之后的结果是:11111111111111111111111111111111
//②正数的整形提升
char c2 = 1;
//变量c2的二进制位(补码)中只有8个比特位:00000001
//因为 char 默认为有符号的 char,所以整形提升的时候,高位补充符号位,即为0
//提升之后的结果是:00000000000000000000000000000001
//③无符号整形提升,高位补0
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if(a==0xb6)
{
printf("a");
}
if(b==0xb600)
{
printf("b");
}
if(c==0xb6000000)
{
printf("c");
}
return 0;
}
//c只要参与表达式运算,就会发生整形提。
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类
型,否则操作就无法进行。下面的层次体系称为寻常算术转换。(从高到下排列)
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运
算。C相信程序员,不会在这方面要求程序员转换,但是程序员必须承担相应的责任。例如下面的例子,就展示了算术转换一些潜在的风险。
float f = 3.14;
int num = f;//隐式转换,会有精度丢失
a*b + c*d + e*f
//可能顺序1;
a*b
c*d
a*b + c*d
e*f
a*b + c*d + e*f
//可能顺序2;
a*b
c*d
e*f
a*b + c*d
a*b + c*d + e*f
c + ++c;
//无法明确前一个c用的是加加之前的c值还是之后的c值
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf( "%d\n", answer);//输出多少?
return 0;
}
//上述代码中的 answer = fun() - fun() * fun();
//只能通过操作符的优先级得知:先算乘法,再算减法,函数的调用先后顺序无法通过操作符的优先级确定。
注意:上述的表达式在不同的环境可能会有不同的代码。