目录
1.算数操作符
+ - * / %
① 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。% 操作符的两个操作数必须为整数,返回的是整除之后的余数
②对于 / 操作符如果两个操作数都为整数,执行整数除法,而只要有浮点数执行的就是浮点数除法。也就是说,如果想要得到小数,除数和被除数之间至少有一个数是浮点数
int main()
{
int a = 6/5;
printf("%d ", a);
float b = 6 / 5;
printf("%f ", b);
float c = 6 / 5.0;
printf("%f ", c);
return 0;
}
输出:1 1.000000 1.200000
2.移位操作符
<< 左移操作符
>> 右移操作符
①左移操作符
移位规则:左边抛弃、右边补0
②右移操作符
移位规则:
① 算术移位
左边用原该值的符号位填充,右边丢弃(负数符号位为1,正数符号位为0)
② 逻辑移位
左边用0填充,右边丢弃
一个整数的二进制表示有三种:原码、反码、补码
-1的二进制表示(以下方法针对负整数,正整数的原码、反码、补码相同):
原码:10000000000000000000000000000001(第一位为符号位)
反码:11111111111111111111111111111110(符号位不变,原码其他位按位取反)
补码:11111111111111111111111111111111(符号位不变,反码+1)
负整数内存中存储的是补码
int main()
{
int a = -1;
int b = a >> 1;
printf("a=%d ", a);
printf("b=%d", b);
return 0;
}
输出:a=-1 b=-1
VS2019右移操作符使用的是算数右移,且a右移一位并不会改变a的值
🐖:对于移位运算符,不要移动负数位,这个是标准未定义的且没有意义
3.位操作符
& 按位与
| 按位
^ 按位异或
位操作符的操作数必须是整数
按位指按二进制位
int main()
{
int a = 3; //00000000000000000000000000000011
int b = 5 ;//00000000000000000000000000000101
int c = a & b;//00000000000000000000000000000001
int d = a | b;//00000000000000000000000000000111
int e = a ^ b;//00000000000000000000000000000110
printf("a&b=%d ", c);
printf("a|b=%d ", d);
printf("a^b=%d ", e);
return 0;
}
输出:1 7 6
练习1:不能创建临时变量(第三个变量),实现两个数的交换
法一:加减(存在缺点:数字过大时可能溢出)
int main()
{
int a = 3;
int b = 5;
a = a + b;
b = a - b;
a = a - b;
printf("a=%d\n", a);
printf("b=%d", b);
return 0;
}
法二:异或
int main()
{
int a = 3;//a:011
int b = 5;//b:101
a = a ^ b;//a:110
b = a ^ b;//b:011--原来的a
a = a ^ b;//a:101--原来的b
printf("a=%d\n", a);
printf("b=%d", b);
return 0;
}
练习2:求一个整数存储在内存中的二进制中1的个数
法1:缺点是不能计算负数
int main()
{
int num = 10;
int count = 0;
while (num)
{
if (num % 2 == 1)
count++;
num = num / 2;
}
printf("%d二进制中1的个数%为%d\n", num, count);
return 0;
}
法2:缺点是需要循环32次
int main()
{
int num = 10;//00000000000000000000000000010010
int i = 0;
int count = 0;//计数
for (i = 0; i < 32; i++)
{
if ((num >>i)&1==1)
count++;
}
printf("%d二进制中1的个数%为%d\n", num,count);
return 0;
}
法3:
int main()
{
int num = 10;
int count = 0;
while (num)
{
count++;
num = num & num - 1;
}
printf("%d二进制中1的个数为:%d\n", num, count);
return 0;
}
例:
num=10010 num-1=10001
num=10000 num-1=01111
num=00000
4.赋值操作符
直接赋值:=
复合赋值:+= -= *= /= %= >>= <<= &= |= ^=
一个 = 是赋值,两个 = 是判断相等
5.单目操作符
! 逻辑反操作- 负值+ 正值& 取地址sizeof 操作数的类型长度(以字节为单位)~ 对一个数的二进制按位取反-- 前置、后置 --++ 前置、后置 ++* 间接访问操作符 ( 解引用操作符 )( 类型 ) 强制类型转换
①用sizeof计算数组大小时,可以不加括号,但是计算变量类型的大小时,必须加括号
int main()
{
char arr[10] = { 0 };
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof arr);
printf("%d\n", sizeof int);
return 0;
}
输出:10 4 10
因此,sizeof是一个操作符,并不是函数
②以下两种方式均可以计算数组的大小
int main()
{
int arr[10] = { 0 };
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(int[10]));
return 0;
}
③
int main()
{
short s = 5;
int a = 10;
printf("%d\n", sizeof(s = a + 2));
printf("%d\n", s);
return 0;
}
输出:2 5
这是因为电脑是在编译器阶段处理sizeof的,sizeof括号里的表达式是不参与运算的,s=a+2在编译器阶段已经处理过,在运行期间并不会计算s=a+2
④前置/后置++与--
后置++:先使用,再++
int main()
{
int a = 10;
int b = a++;
printf("%d ", a);
printf("%d ", b);
return 0;
}
输出:11 10
前置++:先++,再使用
int main()
{
int a = 10;
int b = ++a;
printf("%d ", a);
printf("%d ", b);
return 0;
}
输出:11 11
int main()
{
int a = 10;
printf("%d ", a--);
printf("%d ", a);
return 0;
}
输出:10 9
⑤强制类型转换示例
int a = (int)3.14;
⑦问:
(1)、(2)两个地方分别输出多少?
(3)、(4)两个地方分别输出多少?
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//(2)
}
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));//(3)
test1(arr);
test2(ch);
return 0;
}
(1):40
(2):4/8
(3):10
(4):4/8
指针变量大小不论类型,都是4或8
6.关系操作符
>>=<<=!= 用于测试 “ 不相等 ”== 用于测试 “ 相等 ”
7.逻辑操作符
&& 逻辑与|| 逻辑或
逻辑与和逻辑或只关注真假
②逻辑与和或的特点:
//360笔试题
#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);
return 0; }
//程序输出的结果是什么?
输出:a = 1 b = 2 c = 3 d = 4
这是因为在执行 i = a++ && ++b && d++; 语句时,a=0,表达式的结果必定为假,++b与d++不会执行
当使用逻辑操作符时,我们会遇到一种“短路”现象。意思也就是说一旦明确整个表达式的值,那么就不再计算剩余的表达式部分了。因此,整个表达式的靠后部分有可能就不会被运算。
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);
return 0;
}
输出:a = 1 b = 3 c = 3 d = 4
同理,当计算得出 a++||++b 的结果为1时,此表达式的结果必定为1,因此d++不会被执行
8.条件操作符
exp1 ? exp2 : exp3
例:使用条件表达式实现找两个数中较大值
int main()
{
int a = 1;
int b = 2;
int max= a > b ? a : b;
printf("%d", max);
return 0;
}
9.逗号表达式
exp1 , exp2 , exp3 , …expN逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
例1:
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式
printf("%d", c);
return 0;
}
输出:13
例2:if后的语句是否能被执行,取决于最后一个条件,也就是d>0是否成立
if (a =b + 1, c=a / 2, d > 0)
例3:
a = get_val();
count_val(a);
while (a > 0)
{
//业务处理
a = get_val();
count_val(a);
}
while (a = get_val(), count_val(a), a>0)
{
//业务处理
}
10.下标引用、函数调用和结构成员
10.1. [ ] 下标引用操作符
操作数:一个数组名 + 一个索引值
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
void test1()
{
printf("hehe\n");
}
void test2(const char* str)
{
printf("%s\n", str);
}
int main()
{
test1();
test2("hello");
return 0;
}
10.3 访问一个结构的成员
① . 结构体 . 成员名② -> 结构体指针 -> 成员名
struct Book
{
char name[20];
char id[20];
int price;
};
int main()
{
int num = 10;
struct Book b1 = { "C语言","C20220824",50 };
struct Book b2 = { "C++","C++20220824",60 };
printf("书名:%s\n书号:%s\n定价:%d\n", b1.name, b1.id, b1.price);
printf("书名:%s\n书号:%s\n定价:%d\n", b2.name, b2.id, b2.price);
return 0;
}
struct Book
{
char name[20];
char id[20];
int price;
};
int main()
{
int num = 10;
struct Book b1 = { "C语言","C20220824",50 };
struct Book* pb1 = &b1;
struct Book b2 = { "C++","C++20220824",60 };
struct Book* pb2 = &b2;
//printf("书名:%s\n书号:%s\n定价:%d\n", (*pb1).name, (*pb1).id, (*pb1).price);
//printf("书名:%s\n书号:%s\n定价:%d\n", (*pb2).name, (*pb2).id, (*pb2).price);
printf("书名:%s\n书号:%s\n定价:%d\n", pb1->name, pb1->id, pb1->price);
printf("书名:%s\n书号:%s\n定价:%d\n", pb2->name, pb2->id, pb2->price);
return 0;
}
11.表达式求值
11.1 隐式类型转换
整形提升是按照变量的数据类型的符号位来提升的①负数的整形提升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 = 3;
char b = 127;
char c = a + b;
printf("%d", c);
return 0;
}
输出:-126
这是因为,a和b都是char类型,都没有达到一个int的大小,这里就会发生整形提升
整形提升后的两个二进制序列相加得00000000 00000000 00000000 10000010,因此c=10000010,c的补码为11111111 11111111 11111111 10000010,c的反码为11111111 11111111 11111111 10000001,c的原码为10000000 00000000 00000000 01111110,从而得到输出结果为-126
例2:
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
这是因为a,b都需要进行整形提升,而c本身就是整形,不需要再进行整形提升了
例3:
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
printf("%u\n", sizeof(!c));
return 0;
}
输出:1 4 4 4
11.2 算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型(向精度更高的去转换),否则操作就无法进行。这样的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
🐖:但是算术转换要合理,要不然会有一些潜在的问题
float f = 3.14;
int num = f;//隐式转换,会有精度丢失
11.3 操作符的属性
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
int a = 4;
int b = 5;
int c = a + b * 7;//优先级决定了计算顺序
int d = a + b + 7;//优先级不起作用,结合性决定
操作符优先级(优先级由高到低)
N/A:不考虑结合性
L-R:从左向右结合
R-L:从右向左结合
问题表达式:
1.a*b + c*d + e*f
相邻操作符 * 比 + 的优先级高,因此,①>② ③>② ③>④ ⑤>④,但是优先级并不能决定⑤比①早执行,因此无法确定唯一的计算路径,结果是不可预测的,是有歧义的
2.c + --c
操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的
3.非法表达式
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
在不同编译器中程序的结果不同
4.
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf( "%d\n", answer);//输出多少?
return 0;
}
输出:-10(2-3*4)
虽然在大多数的编译器上求得结果都是相同的,但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。函数的调用先后顺序无法通过操作符的优先级确定。
5.
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);
printf("%d\n", i);
return 0;
}
linux环境结果:10 4
vs环境结果:12 4
这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序
总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的