【c语言】操作符详解

1.操作符分类

目录

1.操作符分类

2.算术操作符

3.移位操作符

4.位操作符

5.赋值操作符

6.单目操作符

6.1 单目操作符

7.关系操作符

8.逻辑操作符

9.条件操作符

10. 逗号表达式

11. 下标引用、函数调用和结构成员

11.1 下标引用操作符

11.2 函数调用操作符

11.3 成员访问操作符

12. 表达式求值

12.1 隐式类型转换(整型提升)

12.2 算术转换

12.3 操作符的属性 


  • 算术操作符
  • 移位操作符
  • 位操作符
  • 赋值操作符
  • 单目操作符
  • 关系操作符
  • 逻辑操作符
  • 条件操作符
  • 逗号表达式
  • 下标引用、函数调用和结构成员

2.算术操作符

算术操作符:+    -   *   /  %

注意:

  1. 除号两端都是整数时是整数除法,除号两端有小数有才能进行小数除法
  2. 取模操作符不能作用与浮点数

3.移位操作符

注意:
  1. 移位操作符的操作数只能是整数;
  2. 移位移动的是整数的二进制位

整数的二进制表示形式有三种:原码,反码,补码

原码:按照数值的正负,直接写出的二进制序列就是原码

(一个整数有4个字节=32bit位,对于有符号整数,最高位的一位是符号位,符号位为1表示负数,符号位为0表示负数。对于无符号整数(unsigned int),所有位都是有效位,没有符号位)

注意:对于正的整数,原码反码补码相同;对于负的整数,原码反码补码需要计算

反码:源码符号位不变,其他位按位取反

补码:反码的二进制+1就是补码

(原码取反加一得到补码;补码减一取反,或取反加一得到原码)(计算方式相同——只需要一套硬件电路即可)

(0一般作为无符号数)

注意:整数在内存中存的都是补码的二进制序列,整数在计算中使用的也是补码

<< 左移操作符:左边丢弃,右边补0(有乘2的效果,无论正负)
#include 
int main() {
	int m = 7;
	int n = m << 1;//m不会改变
	printf("%d\n", n);
	return 0;
}
>> 右移操作符 :
  1. 逻辑移位:左边用0填充,右边丢弃
  2. 算术移位:左边用原该值的符号位填充,右边丢弃

注意:大多数编译器采用的都是算术右移

(-1的补码为32个全1)

【c语言】操作符详解_第1张图片

 注意:对于移位运算符,不要移动负数位,这个是标准未定义的。

4.位操作符

& :按位与,按二进制位与,有0则0,全1才1;
(a&1:得到a的最低位,移位后可以得到a的每一位)
| :按位或,按二进制位或,有1则1,全0才0;
^ :按位异或,相同为0,相异为1;
(异或支持交换律)
(注意:a^a=0;a^0=a)
注:他们的操作数必须是整数。
面试题:不能创建临时变量(第三个变量),实现两个数的交换
//法一:
int main() {
	int a = 3;
	int b = 5;
	a = a + b;
	b = a - b;
	a = a - b;
	//可以实现两个数的交换,但当a,b的值太大时,a+b会超过整数能表达的最大值,所以a+b不会是a与b的和
	return 0;
}
// 法二:代入法
int main() {
	int a = 3;
	int b = 5;
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	printf("%d %d", a, b);
	return 0;
}
//异或不会产生进位,则不会溢出
// 只适用于整型,可读性差,一般还是创建临时变量实现两个变量的值交换,效率较高

编写代码实现:求一个整数存储在内存中的二进制中1的个数。

#include 

//思路:按位与1+右移(作业)
//方法一:
//00000000000000000000000000001010
int main() {
	int m = 0;
	printf("请输入一个数:\n");
	scanf("%d", &m);
	int count = 0;
	for (int i = 0; i < 32; i++) {
		if (m & 1)
			count++;
		m >>= 1;
	}
    //此处也可以:
	//for (int i = 0; i < 32; i++) {
	//	if ((m >> i) & 1)//注意:error:((m >>= i) & 1)
	//		count++;
	//}
	printf("%d\n", count);
	return 0;
}
//方法二:十进制数%10/10可以得到每一位,那么二进制序列可以%2/2得到每一位
int count_num_of_1(unsigned int n) {//注意:如果用该方法需要使用无符号形参
	int count = 0;
	while (n) {
		if (n % 2 == 1) {
			count++;
		}
		n /= 2;
	}
	return count;
}
int main() {
	int num = 0;
	printf("请输入一个数:\n");
	scanf("%d", &num);
	int ret = count_num_of_1(num);
	printf("%d\n", ret);
	return 0;
}
//方法三:(重要)
int count_num_of_1(int n) {//注意:如果用该方法需要使用无符号形参
	int count = 0;
	while (n) {
		n = n & (n - 1);
		//该表达式每一次执行,n最右边的一个1会变为0,执行的次数就是n的个数
		count++;
	}
	return count;
}
int main() {
	int num = 0;
	printf("请输入一个数:\n");
	scanf("%d", &num);
	int ret = count_num_of_1(num);
	printf("%d\n", ret);
	return 0;
}

5.赋值操作符

注意:赋值是重新给一个值

int a = 1;//是初始化,不是赋值

a = 2;//是赋值

赋值操作符可以连续使用,但是不易调试,一步就跳过

复合赋值符

+= ,-= ,*= ,/= ,%= ,>>= ,<<= ,&= ,|= ,^=

6.单目操作符

6.1 单目操作符

  • !           逻辑反操作
  • -           负值
  • +           正值(注意:正号不改变符号)
  • &           取地址(注意:数组地址的表示:int(*pa)[10] = &arr;)
  • sizeof      操作数的类型长度(以字节为单位)
    //sizeof是计算类型创建变量的大小或者变量的大小,单位是字节
    //sizeof计算的结果是size_t类型的
    //size_t是无符号整型的
    //对size_t类型的数据进行打印,可以使用%zd
    //对于不支持%zd的编译器可以使用%d或者%u
    int main(){
        int a = 10;
        //a的类型是整型,可以用sizeof计算其类型创建变量的大小也可以计算a的大小
    
        printf("%zd\n",sizeof(a));
        printf("%zd\n",sizeof a);
        //sizeof计算变量的类型大小时,变量可以不加()
        //说明sizeof是操作符而不是函数,不需要函数调用操作符
    
        printf("%zd\n",sizeof(int));
    
        //sizeof计算数组大小
        int arr[10] = {0};
        printf("%zd\n",sizeof(arr));
        printf("%zd\n",sizeof(arr[0]));
        printf("%zd\n",sizeof(arr) / sizeof(arr[0]));//元素个数
        return 0;
    }
  • ~           对一个数的二进制按位取反(0按位取反的结果是-1)
    #include 
    //~的用途
    int main() {
    	int a = 10;
    	//a在计算机中的存储:00000000000000000000000000001010
    	
    	//将a的倒数第五位变为1得到:00000000000000000000000000011010
    	// 按位或00000000000000000000000000010000(1)
    	a |= (1 << 4);
    	printf("%d\n", a);
    
    	//将a还原:按位与1111111111111111111111111111111111101111;由(1)按位取反得到
    	a &= ~(1 << 4);
    	printf("%d\n", a);
    	
    	return 0;
    }
    
    //常用于单片机开发,嵌入式开发
  • --          前置、后置--
  • ++          前置、后置++(注意:无论前置还是后置,操作数都会进行自减/自增操作,但是操作数赋给其他变量时,前置后置的结果不同——前置:先变化再赋值;后置:先赋值再变化)
  • *           间接访问操作符(解引用操作符)
    //解引用操作符和取地址操作符是一对,通常搭配使用
    int main(){
        int m = 1;
        int* p = &m;
        *p;//对p进行解引用操作,*p是通过p中存放的地址,找到p指向的对象,*p就是a
        return 0;
    }
  • (类型)       强制类型转换

<<   >>   &  |   ^   ~ 都是针对二进制位的操作 

注意:

#include 
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)、(2)两个地方分别输出多少?
// 40,8
//(3)、(4)两个地方分别输出多少?
// 10,8
// 注意:数组传参的本质传的是首元素的地址

7.关系操作符

>
>=
<
<=
!=   用于测试 不相等
==       用于测试 相等
注意:判断操作符“==”和赋值操作符“=”

8.逻辑操作符

&&        逻辑与
||           逻辑或
区分 逻辑与 按位与
区分 逻辑或 按位或
1 & 2 -----> 0
1 && 2 ----> 1
1 | 2 -----> 3
1 || 2 ----> 1

注意:

&& 属于短路操作,即如果第一个操作数是 false ,那么就不会再对第二个操作数求值

||也是短路操作符,如果第一个操作数的求值结果为 true ,就不会对第二个操作数求值了。

#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);
	printf("i = %d\n", i); 
	return 0;
}
//程序输出的结果是什么?
//1 2 3 4
//0

9.条件操作符

exp1 ? exp2 : exp3
真       计算     不计算
假      不计算    计算
唯一的三目操作符
#include 
//计算a,b的较大值
int main() {
	int a = 10;
	int b = 30;
	int m = 0;
	
	/*if (a > b) {
		m = a;
	}
	else
		m = b;*/

	m = (a > b ? a : b);
	printf("%d\n", m);

	return 0;
}

10. 逗号表达式

逗号表达式,就是用逗号隔开的多个表达式。

逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。

前面的表达式可能会影响到最后一个表达式

int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
//c:13
a = get_val();
count_val(a);
while (a > 0) {
         //业务处理
        a = get_val();
        count_val(a);
}
//如果使用逗号表达式,改写:
while (a = get_val(), count_val(a), a>0) {
         //业务处理
}
//代码不在冗余,整个表达式从左向右以此计算,但起到作用的还是最后一个表达式的结果
#include 
int main()
{
    int arr[] = {1,2,(3,4),5};
    printf("%d\n", sizeof(arr));
    return 0;
}
//(3,4)逗号表达式的结果是最后一个表达式的结果:4
//16
#include 
int main()
{
	int a, b, c;
	a = 5;
	c = ++a;
	b = ++c, c++, ++a, a++;
    //b = ++c是逗号表达式中的一个表达式,逗号操作符优先级低于赋值操作符
	b += a++ + c;
	printf("a = %d b = %d c = %d\n:", a, b, c);
	return 0;
}

11. 下标引用、函数调用和结构成员

11.1 下标引用操作符

操作数:一个数组名 + 一个索引值(下标)

11.2 函数调用操作符

接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。

至少为一个参数,函数可以无参,但必须有函数名

11.3 成员访问操作符

.    结构体.成员名(有变量)
-> 结构体指针->成员名(有地址)
#include 
//成员访问操作符
struct Book {
	char name[20];
	int price;
};
void Print(struct Book* pb) {
	printf("%s %d\n", (*pb).name, (*pb).price);
	printf("%s %d\n", pb->name, pb->price);
}
int main() {
	struct Book b = { "c语言指南",55 };
	printf("%s %d\n", b.name, b.price);
	Print(&b);
	return 0;
}

12. 表达式求值

12.1 隐式类型转换(整型提升)

C 的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为 整型 提升
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int(默认为有符号)或unsigned,然后才能送入CPU去执行运算。
注意:char到底是signed char还是unsigned int是不确定的,c语言标准没有明确规定,是取决于编译器的
如何进行整体提升呢?
整形提升是按照变量的数据类型的符号位(当前二进制序列的最高位)来提升的
无符号数整型提升,高位补0
实例一:
#include 
int main() {
	char a = 5;
	//5的补码:00000000000000000000000000000101
	//存入char类型中:00000101
	char b = 126;
	//126的补码:00000000000000000000000001111110
	//存入char类型中:01111110
	char c = a + b;
	//a整型提升:00000000000000000000000000000101
	//b整型提升:00000000000000000000000001111110
	//加法运算c:00000000000000000000000010000011
	//存入c中:10000011
	printf("%d\n", c);
	//按整型打印
	//c整型提升:11111111111111111111111110000011——补码
	//c的原码:  10000000000000000000000001111101——取反加一
	//-125(01111111——127)
	return 0;
}

实例二:

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

实例三:

int main()
{
    char c = 1;
    printf("%u\n", sizeof(c));
    printf("%u\n", sizeof(+c));
    printf("%u\n", sizeof(-c));
    return 0; 
}
//整型提升的前提:字符型或短整型进行整型运算(有操作符:+-*/%...)或打印

12.2 算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换
long double
double
float
unsigned long int
long int
unsigned int
int

注意:向上转换

12.3 操作符的属性 

复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。

C语言运算符优先级:

优先级

运算符

名称或含义

使用形式

结合方向

说明

是否控制求值顺序

1

[]

数组下标

数组名[常量表达式]

左到右

--

()

圆括号

(表达式)/函数名(形参表)

--

.

成员选择(对象)

对象.成员名

--

->

成员选择(指针)

对象指针->成员名

--

2

++

自增运算符

变量名++(后缀)

左到右

单目运算符

--

自减运算符

变量名--

-

负号运算符

-表达式

右到左

~

按位取反运算符

~表达式

++

自增运算符

++变量名(前缀)

--

自减运算符

--变量名

*

取值运算符

*指针变量

&

取地址运算符

&变量名

!

逻辑非运算符

!表达式

(类型)

强制类型转换

(数据类型)表达式

--

sizeof

长度运算符

sizeof(表达式)

--

3

/

表达式/表达式

左到右

双目运算符

*

表达式*表达式

%

余数(取模)

整型表达式%整型表达式

4

+

表达式+表达式

左到右

双目运算符

-

表达式-表达式

5

<< 

左移

变量<<表达式

左到右

双目运算符

>> 

右移

变量>>表达式

6

大于

表达式>表达式

左到右

双目运算符

>=

大于等于

表达式>=表达式

小于

表达式<表达式

<=

小于等于

表达式<=表达式

7

==

等于

表达式==表达式

左到右

双目运算符

!=

不等于

表达式!= 表达式

8

&

按位与

表达式&表达式

左到右

双目运算符

9

^

按位异或

表达式^表达式

左到右

双目运算符

10

|

按位或

表达式|表达式

左到右

双目运算符

11

&&

逻辑与

表达式&&表达式

左到右

双目运算符

12

||

逻辑或

表达式||表达式

左到右

双目运算符

13

?:

条件运算符

表达式1?

表达式2: 表达式3

右到左

三目运算符

14

=

赋值运算符

变量=表达式

右到左

--

/=

除后赋值

变量/=表达式

--

*=

乘后赋值

变量*=表达式

--

%=

取模后赋值

变量%=表达式

--

+=

加后赋值

变量+=表达式

--

-=

减后赋值

变量-=表达式

--

<<=

左移后赋值

变量<<=表达式

--

>>=

右移后赋值

变量>>=表达式

--

&=

按位与后赋值

变量&=表达式

--

^=

按位异或后赋值

变量^=表达式

--

|=

按位或后赋值

变量|=表达式

--

15

逗号运算符

表达式,表达式,…

左到右

--

只有四个操作符可以控制关系式的求值顺序

根据操作符的优先级无法确定表达式的唯一计算路径

问题代码:

eg:a*b+c*d+m*n(尽量拆开,如果每个变量各自为表达式其中又用到相同的变量,那么计算路径将会对结果产生影响)

eg:c + --c(有问题,加号的左操作数什么时候准备会对结果有影响)

eg:i = i-- - --i * ( i = -3 ) * i++ + ++i(不同编译器有不同结果)

eg:三个函数的调用顺序不同,结果不同

int fun()//2,3,4...
{
     static int count = 1;
     return ++count; }
int main()
{
     int answer;
     answer = fun() - fun() * fun();
     printf( "%d\n", answer);//输出多少?
     return 0; }
#include 
int main()
{
	int i = 1;
	int ret = (++i) + (++i) + (++i);
	printf("%d\n", ret);
	printf("%d\n", i);
	return 0;
}
//尝试在linux 环境gcc编译器,VS2013环境下都执行,看结果。
// vs:12;linux:10

【c语言】操作符详解_第2张图片

注意:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。

你可能感兴趣的:(c语言,开发语言)