目录
3.4练习:不能创建临时变量(第三个变量),实现两个数的交换。
4.1.例题练习:编写代码实现:求一个整数存储在内存中的二进制中1的个数。
总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。
1.1:算术操作符
+ - * / %
int main()
{
float a = 3 / 2;
printf("%f", a);//答案是1,错误写法
return 0;
}
int main()
{
float a = 3 / 2.0;//在整数后面加小数点即可,答案1.5
printf("%f", a);
return 0;
}
int main()
{
float a = 3 % 2;
printf("%d", a);//3/2...1,结果是1,得到的是余数
return 0;
}
1:我们想得到结果1.5,结果却是1.000000,因为除号两边是整数,只能执行整数除法: 3/2余1
2:在任意整数上加上一个浮点数,按照小数除法执行
3:对于取模操作数来说,表达式左右值必须为整形
2.1:移位操作符(操作数只能是整数)
<<左移操作符 (移位规则:二进制中左边丢弃,右边补0)
>>右移操作符(移位规则: 1.算术右移:二进制中右边丢弃,左边补符号位)
2.逻辑右移:右边丢弃,左边补零
整数的二进制表示有3种形式:原码,反码,补码
正整数的原码、反码、补码是相同
负整数的原码、反码、补码是要计算的
整数在内存中存储的是补码的二进制
在二进制中,如果该数字是有符号数,最高位为符号位(0表示正数,1表示负数)
2.2例题:
int main()
{
int a = -1;
-1的二进制位 原码10000000000000000000000000000001;(第一位1表示负数)
反码11111111111111111111111111111110 (原码符号位不变,其他位按位取反)
补码11111111111111111111111111111111 (反码加1得到补码)
负数的原反是补是算出来的
return 0;
}
2.3例题:
int main()
{
int a = 2;
printf("%d",a << 1);
return 0;
}
a写成二进制是00000000000000000000000000000010
<<移动1位,遵循左边丢弃,右边补0,变成00000000000000000000000000000100,答案是4
2.4例题:
int main()
{
int a = -2;
printf("%d",a << 1);(实际上打印的是原码)
return 0;
}
原码:10000000000000000000000000000010
反码:11111111111111111111111111111101
补码:11111111111111111111111111111110 (a在内存中存的二进制序列)
<<1一位,11111111111111111111111111111110变成11111111111111111111111111111100
11111111111111111111111111111100(补码)
11111111111111111111111111111011(-1得到反码)
10000000000000000000000000000100 (答案是4)
注意:a的值不会发生改变
2.5:>>右移操作符
int main()
{
int a = 5; //00000000000000000000000000000101
int b = a >> 1;//00000000000000000000000000000010
printf("%d %d",a,b);
return 0;
}
int main()
{
int a = -5;
原码:10000000000000000000000000000101
反码:11111111111111111111111111111010
补码:11111111111111111111111111111011
int b = a >> 1;
补码(采用算术右移,补原符号位):11111111111111111111111111111101
反码(-1):11111111111111111111111111111100
原码:10000000000000000000000000000011
答案:b=-3
printf("%d %d",a,b);
return 0;
}
当前编译器,右移执行的是算术右移,算数右移还是逻辑右移取决于编译器
移位运算符不要移动负数位,这是标准未定义
3.1:位操作符(操作数必须是整数)
& 按位与(二进制)对应的二进制位:两个都为1,结果为1,否则为0
| 按位或(二进制)对应的二进制位:有1为1,同时为0才为0
^ 按位异或(二进制)//对应的二进制位:相同位为0,相异为1
3.2:练习
int main()
{
int a = 3;
int b = -5;
//00000000000000000000000000000011 -> 3的补码
//11111111111111111111111111111011 -> -5的补码
int c = a & b;
//00000000000000000000000000000011
答案是3
int c = a | b;
//11111111111111111111111111111011
答案是-5
int c = a ^ b;
//11111111111111111111111111111000
//11111111111111111111111111110111
//10000000000000000000000000001000
答案是-8
printf("%d\n", c);
return 0;
}
3.3.练习:有一个数组只有一个数字出现过一次,剩余数字都是成对出现,找出只出现过一次的数组(自己动手写下)
提示:1.一个数和它自己异或,对应二进制位相同,结果为0
2. 0和任何数异或都是它自己
3.4练习:不能创建临时变量(第三个变量),实现两个数的交换。
int main()
{
int a = 3;
int b = 5;
printf("a=%d b=%d\n", a, b);
a = a + b;
b = a - b;
a = a - b; //写法1,数字太大会出错
a = a ^ b;
b = a ^ b;
a = a ^ b; //写法二
a=011(二进制位) b=101
a=a^b (a=110)
b=a^b (b=011) 此时,a的内容是110,b的内容变成011
a=a^b (a=101) 你会发现,异或后a和b二进制颠倒了
也可以这么理解,把a=a^b代入b=a^b中, 变成b=b^b^a,由于b^b=0, 0^a=a,相当于把a放到b中
printf("a=%d b=%d\n", a, b);
return 0;
}
3.5:求两个数二进制中不同位的个数
int NumberOf1(int n)
{
int count = 0;
while (n)
{
n = n & (n - 1);
count++;
}
return count;
}
int main()
{
int m = 0;
int n = 0;
scanf("%d%d", &m, &n);
int count = 0;
int ret = m ^ n;
count = NumberOf1(ret);
printf("%d", count);
return 0;
}
4.1.例题练习:编写代码实现:求一个整数存储在内存中的二进制中1的个数。
int main()
{
int a = 5;
scanf("%d",&a);
int count = 0;
for (int i = 0; i < 32; i++)
{
if (((a >> i) & 1) == 1) //a&1,得到最低位是否为1
count++;
}
printf("%d", count);
return 0;
}
5.1:赋值操作符
int main()
{
int weight =120;
weight =100;//赋值修改
int a = 10;
int x = 0;
int y = 20;
a=x=y+1;//连续赋值
//先执行x=y+1,最后x=21,a=21 不推荐这种写法
return 0;
}
5.2:复合赋值符(+= -= *= /= %= >>= <<= &= |= ^=)
int main()
{
int a = 10;
a = a + 2;
a += 2;//等价
a = a >> 1;
a >>= 1;//等价
a = a & 4;
a &= 4;//等价
return 0;
}
6.1:单目操作符
! 逻辑反操作符
+ - 正负值
& 取地址
sizeof 操作数的类型长度(单位字节)
~ 对一个数二进制位按位取反
++ 有前置,后置
-- 有前置,后置
* 解引用操作符
(类型) 强制类型转换
6.2:例题
struct S
{
char name[20];
int age;
};
int main()
{
//& 取地址操作符
//* 解引用操作符(间接访问操作符)
int a = 10;
int* pa = &a;//把a的地址放在int*指针中
*pa = 20;//* - 解引用操作符
int arr[10] = {0};
&arr;//取出数组的地址,数组的地址应该放到【数组指针】中去
struct S s = {0};
struct S* ps = &s;
return 0;
}
6.3:sizeof例题
sizeof是一个操作符,不是函数
计算类型创建的变量所占内存的大小,单位是字节
int main()
{
int a = 10;
printf("%d\n", sizeof a);//可以省略阔号
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));//当它是类型不可以省略
int arr[10] = { 0 };
printf("%d\n", sizeof(arr)); //40个字节
int a = 10;
short s = 0;
printf("%d\n", sizeof(s = a + 2));//打印出来是2,a还是10,sizeof()中的表达式不参与计算
printf("%d\n", s);//s的值是0,s=a+2,a是整形,把整形放到短整型中还是短整型
return 0;
}
sizeof在求表达式结果,在编译期间就求出来了
void test1(int arr[])
{
printf("%d\n", sizeof(arr));// 4/8传过来的是int*指针,指针占内存空间4/8字节
}
void test2(char arr[])
{
printf("%d\n", sizeof(arr));// 4/8传过来的是char*指针,指针占内存空间4/8字节
}
int main()
{
int arr1[10] = { 0 };
printf("%d\n", sizeof(arr));//40
char arr2[10] = { 0 };
printf("%d\n", sizeof(arr));//10
test1(arr1);
test2(arr2);
return 0;
}
6.3:单目操作符练习
int main()
{
int a = 3;
int b = ++a;//前置++,先++,后使用//a=a+1,b=a
int b = a++;//后置++,先使用,后++。//b=a,a=a+1
int b = --a;//前置--,先--,后使用 //a=a-1,b=a
int b = a--;//后置--,先使用,再-- //b=a,a=a-1
return 0;
}
int main()
{
int a = (int)3.14;//类型不匹配,把3.14浮点型数字强制类型转换成int类型数据
printf("%d\n", a);
return 0;
}
7.1:关系操作符
>
>=
<
<=
!= (测试相等)
== (测试相等) 字符串之间不能用==比较是否相等,应该用strcmp
注意:应该随时注意是否为=(赋值)还是==(判断相等)
8.1:逻辑操作符
&& 逻辑与
|| 逻辑或
int main()
{
int a = 1;
int b = 0;
if (a && b)
{
printf("hehe\n");//跟数学中的且,或一样,两个同时满足为真
}
if (a || b)
{
printf("hehe\n");一个满足为真
}
return 0;
}
8.2:&& 和 ||练习
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
//答案是1,2,3,4
左边&& a++后置,先计算后++,为假,后面不计算,只计算了a++
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
//答案是2,2,3,4,a为真后面不计算
return 0;
}
9.1:条件操作符(三目操作符)
exp1?exp2:exp3
int main()
{
if (a > 5)
b = 3;
else
b = -3;
写成:
b = (a > 5 ? 3 : -3);//a>5 ? 是为判断1,否为判断2
找出两个数中最大的数字
int a = 3;
int b = 0;
int m = (a > b ? a : b);
return 0;
}
10.1:逗号表达式
逗号表达式,从左至右依次执行,整个表达式执行的结果是最后一个表达式的结果
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
printf("a=%d b=%d\n", a, b);
printf("%d\n", c);
return 0;
}
答案是a=12,b=13,c=13
a>b不产生影响,表达式为假 ->a=12 -> b=13 把13赋值给c
11.1:下标引用,函数调用和结构成员
[ ]下标引用操作符
int main()
{
int arr[10] = { 0 };
printf("%d\n", arr[1]);
printf("%d\n", 4[arr]);
printf("%d\n", *(arr + 1));//等价写法
return 0;
}
() 函数调用操作符
int main()
{
int ret = Add(2, 3);//()函数调用操作符,操作数就是:Add,2,3
printf("%d\n", ret);
return 0;
}
struct Stu
{
char name[20];
int age;
float score;
};
//结构体变量.成员名
void print1(struct Stu ss)
{
printf("%s %d %f\n", ss.name, ss.age, ss.score);
}
//结构体指针->成员名(指针接收)
void print2(struct Stu* ps)
{
//printf("%s %d %f\n", (*ps).name, (*ps).age, (*ps).score);
printf("%s %d %f\n", ps->name, ps->age, ps->score);
int main()
{
struct Stu s = {"张三", 20, 90.5f};
print1(s);
print2(&s);
return 0;
}
12.1:隐式类型转换
表达式求值顺序一部分是由操作符的优先级和结合性决定
同样,有些表达式操作数在求值时可能需要转换成其他类型
C语言计算至少是以整数进行计算
通用CPU (general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
int main()
{
char c1 = 3;
//00000011 - c1
//00000000000000000000000000000011
char c2 = 127;
//01111111 - c2
//00000000000000000000000001111111
char c3 = c1 + c2;
c1+c2是char类型,需要整型提升(按照符号位提升)
//00000000000000000000000000000011
//00000000000000000000000001111111
//00000000000000000000000010000010
//10000010 - c3 char类型截断,只能存八个比特位,最高位为负数
//11111111111111111111111110000010
//11111111111111111111111110000001
//10000000000000000000000001111110
//-126
printf("%d\n", c3);
return 0;
}
12.2:整型提升例题
int main()
{
char a = 0xb6;//10110110
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
printf("a");
if (b == 0xb600)
printf("b");
if (c == 0xb6000000)
printf("c");
return 0;
}
打印c
a发生整型提升,高位负数,b同理
int main()
{
char c = 1;
printf("%u\n", sizeof(c));//1
printf("%u\n", sizeof(+c));//4
printf("%u\n", sizeof(-c));//4
return 0;
}
c参与了表达式运算,发生了整型提升
12.3:算术转换
如果操作符的各个操作数属于不同类型,除非其中一个操作数的转换为另一个操作数的类型,否则操作无法进行,算术转换是按照类型排名来转换
如果该类型小于int,char和short会整形提升,跟大于等于int类型进行算术转换
long double
double
float
unsigned long int
long int
unsigned int
int
unsigned int 和int 类型一起,int转换为unsigned int类型
float f = 3.14;
int num = f;//隐式类型转换,float精度丢失
12.4:操作符的属性
复杂表达式的求值有三个影响的因素。
1.操作符的优先级
⒉操作符的结合性
3.是否控制求值顺序。(&& || 三目操作符)
int main()
{
int a = 10;
int b = 20;
int c = a + b * 5;//*优先级高
int c = a + b + 5;//优先级一样看结合性,+号结合性从左到右
return 0;
}
知道操作符优先级,结合性,求值顺序,也不一定能求出表达式的唯一计算路径
a*b+c*d+e*f
无法知道是怎么样的求值路径,表达式就可能存在问题
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf("%d\n", answer);//输出多少?
return 0;
}
//不确定,我们只知道* 和- ,*先算,但是不知道fun()函数调用是谁先,结果就有问题,错误代码