一、操作符
算术操作符:+ - * / %
1,除了%操作符之外,其他几个操作符都可以作用于整数和浮点数;
2,对于/操作符,如果两个操作数都为整数,执行整数除法,而只要有浮点数执行的就是浮点数除法;
3,%操作符的两个操作数必须为整数,返回的是整除之后的余数。
#include <stdio.h>
int main()
{
int a = 5 / 2;//商2余1
int b = 5 % 2;//余1
double c = 5.0 / 2;//商2.500000
//double d = 5.0 % 2;//报错
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %lf\n", c);
return 0;
}
移位操作符:>> <<
对于移位操作符,不要移动负数位,这个是标准未定义的,例如a>>-1.
对于位操作符,只能作用于整数,浮点数和整数的存储类型是不一样的。
右移操作符>>
1,算术右移:右边丢弃,左边补原符号位(一般情况下编译器采用的都是算数右移);
2,逻辑右移:右边丢弃,左边补零。
int main()
{
//>> - 右移操作符
//移动的是二进制位
int a = 16;
int b =a >> 1;
//a - 00000000000000000000000000010000
//b - 00000000000000000000000000001000
printf("%d\n", b);//8
return 0;
}
int main()
{
int a = -1;
//整数的二进制表示有:原码,反码,补码
//实际上只对于负数有意义,正数的原、反、补码是相同的
//整数存储在内存中的是补码
//a的原码 - 10000000000000000000000000000001
//a的反码 - 11111111111111111111111111111110
//a的补码 - 11111111111111111111111111111111
int b = a >> 1;
printf("%d\n", b);//-1
return 0;
}
十六进制中f=15,二进制中15=1111,所以f=1111,下图中ffffffff就是32个1,表示内存中a的补码。
左移操作符<<
左边丢弃,右边补0。
int main()
{
int a = 5;//00000000000000000000000000000101
int b = a << 1;//00000000000000000000000000001010
printf("%d\n", b);//10
}
位操作符:& | ^
按位与&
int main()
{
//& - 按二进制位与
int a = 3;//00000000000000000000000000000011
int b = 5;//00000000000000000000000000000101
int c = a & b;//00000000000000000000000000000001
printf("%d", c);//1
return 0;
}
按位或|
int main()
{
& - 按二进制位或
int a = 3;//00000000000000000000000000000011
int b = 5;//00000000000000000000000000000101
int c = a | b;//00000000000000000000000000000111
printf("%d", c);//7
return 0;
}
按位异或^
int main()
{
//& - 按二进制位异或,相同为0,相异为1
int a = 3;//00000000000000000000000000000011
int b = 5;//00000000000000000000000000000101
int c = a ^ b;//00000000000000000000000000000110
printf("%d", c);//6
return 0;
}
e.g.1: 变量a=3, b=5, 交换两个变量的值,不使用第三变量。
int main()
{
int a = 3;
int b = 5;
a = a + b;
b = a - b;//3
a = a - b;//5
printf("a=%d\nb=%d", a, b);
return 0;
}
解1:加减法,理解简单,但存在内存溢出的风险(a+b值过大导致部分二进制位丢失)。
int main()
{
int a = 3;
int b = 5;
a = a ^ b;
b = a ^ b;//3
a = a ^ b;//5
printf("a=%d\nb=%d", a, b);
return 0;
}
解2:异或法,即两数异或产生密码,该密码与两数之一异或得到另一数,不会存在溢出的风险。
e.g.2: 求一个整数存储在内存中的二进制中的1的个数。
int main()
{
int num = 0;
int count = 0;
scanf("%d", &num);
int i = 0;
for (i = 0; i < 32; i++)
{
if ((num >> i) & 1 == 1)
count++;
}
printf("%d", count);
return 0;
}
赋值操作符:=
复合赋值符
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
单目操作符:只有一个操作符
逻辑反操作!
int main()
{
int a = 0;
if (!a)//如果a为假,则!a为真,执行if语句
{
printf("呵呵\n");
}
return 0;
}
负值-
int main()
{
int a = -5;
a = -a;//5
printf("%d", a);
return 0;
}
取地址操作符&
间接访问操作符(解引用操作符)*
int main()
{
int a = 10;
int* p = &a;//取地址操作符
*p = 20;//解引用操作符
return 0;
}
sizeof 计算变量所占内存空间的大小,单位是字节
如果sizeof后是变量,括号可以省略,是变量类型则不可以
int main()
{
int a = 10;
char c = 'r';
char* p = &c;
int arr[10] = { 0 };
printf("%d\n", sizeof(a));//4
printf("%d\n", sizeof(int));//4
printf("%d\n", sizeof(c));//1
printf("%d\n", sizeof(char));//1
printf("%d\n", sizeof(p));//8
printf("%d\n", sizeof(char*));//8
printf("%d\n", sizeof(arr));//40
printf("%d\n", sizeof(int [10]));//40
return 0;
}
sizeof括号中并不会进行真实的赋值及运算,所以s依然为0。
int main()
{
short s = 0;
int a = 10;
printf("%d\n",sizeof(s = a + 5));//2
printf("%d\n", s);//0
return 0;
}
按位取反~
int main()
{
int a = 0;
//~按二进制位取反
//00000000000000000000000000000000
//11111111111111111111111111111111-此时存在内存中的是一个负数的补码
//11111111111111111111111111111110-补码-1得到反码
//10000000000000000000000000000001-反码取反得到原码
printf("%d\n", ~a);//-1
return 0;
}
int main()
{
int a = 11;//00000000000000000000000000000000000001011
a = a | (1 << 2);//1011|0100
printf("%d\n", a);//1111=15
a = a & (~(1 << 2));//0...1111|1...1011
printf("%d\n", a);//0...1011=11
return 0;
}
前置、后置++, --
int main()
{
int a = 10;
printf("%d\n", ++a);//前置++,先自增1,后使用
printf("%d\n", a++);//后置++,先使用,后自增1
printf("%d\n", a);//12
return 0;
}
(类型)强制类型转换
int main()
{
int a = (int)3.14;
return 0;
}
关系操作符:> < >= <= != ==
逻辑操作符:&& ||
int main()
{
int a = 3;
int b = 5;
int c = 0;
int d = a && b;//逻辑与&&,同时为真即为真
int e = a && c;//只要有假便是假
printf("%d\n", d);//1
printf("%d\n", e);//0
return 0;
}
int main()
{
int a = 3;
int b = 0;
int c = 0;
int d = a || b;//逻辑或||,只要有真便是真
int e = b || c;//同时为假才为假
printf("%d\n", d);//1
printf("%d\n", e);//0
return 0;
}
p.s.使用逻辑与操作符时,只要左边算出0,则结果必定为0,后续便不再计算(下题中b和d的自增没有执行计算)。
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);
return 0;
}
同理,使用逻辑或操作符时,只要左边算出1,则结果必定为1,后续便不再计算(下题中d的自增没有执行计算)。
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);//1,3,3,4
return 0;
}
条件操作符:exp1?exp2:exp3
int main()
{
int a = 0;
int b = 0;
if (a > 5)
b = 3;
else
b = -3;
b = (a > 5 ? 3 : -3);//使用三目操作符
return 0;
}
int main()
{
int a = 10;
int b = 20;
int max = 0;
if (a > b)
max = a;
else
max = b;
max = (a > b ? a : b);//使用三目操作符
return 0;
}
逗号表达式:exp1,exp2,exp3,...,expn
逗号表达式,就是用逗号隔开的多个表达式。逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
下标引用操作符:[]
操作数:一个数组名+一个索引值
函数调用操作符:()
操作数:函数名+函数操作符内的对象
int get_max(int x, int y)//这里的()不是函数调用操作符
{
return x > y ? x : y;
}
int main()
{
int a = 10;
int b = 20;
//调用函数的时候的()就是函数调用操作符
int max = get_max(a, b);
return 0;
}
访问一个结构成员:. ->
结构体变量.成员名
结构体指针->成员名
struct stu//创建了一个结构体类型-struct stu
{
//成员变量
char name[20];
int age;
char id[20];
};
int main()
{
int a = 10;
struct stu s1 = { "张三",20,"2019010305" };
struct stu* ps1 = &s1;
printf("%s\n", (*ps1).name);
printf("%s\n", ps1->name);//结构体指针->成员名
//使用struct stu这个类型创建了一个学生对象s1,并初始化
printf("%s\n", s1.name);
printf("%d\n", s1.age);
printf("%s\n", s1.id);
//结构体变量.成员名
return 0;
}
二、表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
隐式类型转换
整型提升
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
按照变量的数据类型的符号位进行提升,例如char(00000011),按照最高位(即符号位)向前24位全部补0;char(10000001),按照最高位(即符号位)向前24位全部补1;如果是无符号数则直接补0。
int main()
{
char a = 3;
//00000000000000000000000000000011
//00000011-a
char b = 127;
//00000000000000000000000001111111
//01111111-b
char c = a + b;
//a和b如何相加?先整型提升,再相加,结果将被截断存储与c中
//00000000000000000000000000000011
//00000000000000000000000001111111
//00000000000000000000000010000010
//10000010-c
printf("%d\n", c);//-126
//打印整型类型,再次进行整型提升
//11111111111111111111111110000010-补码
//11111111111111111111111110000001-反码
//10000000000000000000000001111110-原码
}
算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。(将较小的类型转换为较大的类型,即下图中由下转换为上)
long double
double
float
unsigned long int
long int
unsigned int
int
操作符的属性
1.操作符的优先级
2.操作符的结合性
3.是否控制求值顺序
a*b+c*d+e*f;
p.s.由于比+优先级高,只能保证相邻的两个操作符的顺序,即第一个和第二个比第一个+计算更早,但操作符的优先级并不能决定第三个比第一个+更早执行
c + --c;
p.s.同理,操作符的优先级只能决定--运算在+之前,但是无法得知,+的左操作数的获取在右操作数之前还是之后,所以结果是不可预测的,是有歧义的