一、操作符

算术操作符:+ - * / %

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;
}

操作符详解_结构体_02

同理,使用逻辑或操作符时,只要左边算出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.同理,操作符的优先级只能决定--运算在+之前,但是无法得知,+的左操作数的获取在右操作数之前还是之后,所以结果是不可预测的,是有歧义的