C语言操作符总结

c语言操作符

分类

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

下面对每种操作符进行详细讲解

算法操作符

+   -   *   %

1. 除了%操作符之外,其他的几个操作符都用于整数或浮点数
1. 对于/操作符如果两个操作数都为整数,执行整数除法。而只要有两边任意一个操作数为浮点数执行的就是浮点数除法(即就是结果为小数)。返回的是整除之后的商。
2. %操作符的的左右两个操作数都必须为整数。返回的是整除之后的余数。

移位操作符

<< 左移操作符
>> 右移操作符

移位操作符的本质是对数的二进制补码进行移位

左移操作符

移位规则:
向左移动,左边抛弃,右边补0

int n = 10;
int m = n << 1;
printf("%d\n", m);
printf("%d\n", n);
输出结果是  
m=20
n=10  

所以左移操作符有对数*2的效果。
内部原因是
n在计算机中的二级制存储是00000000000000000000000000001010
经过左移操作符后变为
00000000000000000000000000010100
n<<1不进行赋值的情况下,自身的值不会发生变化

注意:左移操作符不建议写左移负数,这个是标准未定义的。
int num = 10;
num>>-1;//error
右移操作符

移位规则分为两种:
1. 逻辑移位
右边丢弃,左边补0
2. 算术移位
右边丢弃,左边用原该值的符号为填充

例如:
int mun=-1;
这样内存中存储-1的补码为32个全1
11111111111111111111111111111111
算术右移:左边用原该值的符号位填充
11111111111111111111111111111111

由于是负数,所以符号位为1,即左边补1

逻辑右移:左边补0
01111111111111111111111111111111

具体是算数右移还是逻辑右移由编译器决定,每个编译器的规定都不一样。

位操作符

&  //按位与(两个同时为1才为1)
|  //按位或(只要有一个为1就为1)
^  //按位异或(相同为0不同为1)
注:他们的操作数必须是整数
#include<stdio.h>
int main()
{
    int num1 = 1;
    int num2 = 2;
    int z1 = num1 & num2;
    printf("%d\n",z1);
    int z2 = num1 | num2;
    printf("%d\n", z2);
    int z3 = num1 ^ num2;
    printf("%d\n", z3);
    system("pause");
    return 0;
}

运行结果如下:
这里写图片描述

也可以用于两个数的交换不创建临时变量(第三变量)

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int a = 10;
    int b = 20;
    a = a^b;
    b = a^b;
    a = a^b;
    printf("a=%d   b=%d\n", a, b);
    system("pause");
    return 0;
}

运行结果如下:
这里写图片描述

赋值操作符

赋值操作符也就是你可以给自己重新赋值
int a = 10;//不满意
a = 5;//不满意就赋值更改
赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y + 1;//连续赋值
但是同样的语义
x = y + 1;
a = x;
这样写就更加清晰且容易调试;
符合赋值符
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
=

这些运算符都可以写成复合的效果
比如:

int x = 10;
x = x + 10;
x += 10;//符合复制
//其他运算符都是一样的道理

单目操作符

单目操作符包括以下几种
!           逻辑反操作
-            负值
+            正值
&            取地址
sizeof       操作数的类型长度(以字节为单位)
~            对一个数的二进制按位取反
--           前置、后置--
++           前置、后置++
*            间接访问操作符(解引用操作符)
(类型)      强制类型转换     

强制类型转换:
int num = (int)3.14;
//输出num=3;
代码举例:

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int a = -10;
    int *p = NULL;
    printf("%d\n", !2);
    printf("%d\n", !0);
    a = -a;
    p = &a;
    printf("%d\n", sizeof(a));
    printf("%d\n", sizeof(int));
    printf("%d\n", sizeof a);
    printf("%d\n", sizeof int);//不能这样写
    system("pause");
    return 0;
}

运行结果:
这里写图片描述
关于sizeof可以求变量/(类型)所占空间的大小。
sizeof和数组

#include<stdio.h>
#include<stdlib.h>
void test1(int arr[])
{
    printf("%d\n", sizeof(arr));//2
}
void test2(char ch[])
{
    printf("%d\n", sizeof(ch));//2
}
int main()
{
    int arr[10] = { 0 };
    char ch[10] = { 0 };
    printf("%d\n", sizeof(arr));//3
    printf("%d\n", sizeof(ch));//4
    test1(arr);
    test2(ch);
    system("pause");
    return 0;
}

1处是通过sizeof计算整个数字的大小,为10*4=40;
2处同1处一样,为10*1=10;
3除是通过函数调用计算,在调用函数中数组作为实参传递给实参的只是数组首元素的地址,所以在函数内部用sizeof计算数组大小时,实际上计算的是数组首元素地址的大小 ,为4
4同3一样
运算结果如下:
这里写图片描述

//对于前置++,--
//后置++,--
#include<stdio.h>
#include<stdlib.h>
int main()
{
    int a = 10;
    int x = ++a;//先加后用
    int x1 = --a;//先减后用
    int y = a++;//先用后加
    //int x1 = --a;//先减后用
    int y1 = a--;//先用后减
    printf("%d,%d,%d,%d", x,x1,y,y1);
    system("pause");
    return 0;
}
运行结果:
11,10,10,11  

有一些比较奇怪的程序:

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int i = 1;
    int ret = (++i) + (++i) + (++i);
    printf("%d\n", ret);
    printf("%d\n", i);
    system("pause");
    return 0;
}
linux环境的结果:
10
4
VS环境的结果:
12
4

同样的代码产生不同的结果,这是为什么?
1. 说明这个代码本身不是一个好代码
2. 跟表达式的求值有关系
3. 表达式的求值依赖于运算符的优先级和结合性
4. 这样的表达式产生的结果就是严重的依赖于表达式的求值顺序

关系操作符

关系操作符
>
>=
<
<=
!=    用于测试"不想等"
==    用于测试"相等"

一些陷阱
x=get_value();
if(x=5)
这里看似x如果等于5就执行事物处理,实际上代码是不是这个意思呢?
我们将判断的==搞成赋值的=,这样的事物无论x是多少都会被执行。

逻辑操作符

逻辑操作符有哪些:
&&     逻辑与
||     逻辑或

区分逻辑与和按位与
区分逻辑或和按位或
1&2————>0
1&&2———->1
1|2————->3
1||2————>1
逻辑与或是把这个数字当真假相与或
按位与或是把这个数的二进制补码按位相与或

逻辑与和或的特点:
#include<stdio.h>
#include<stdlib.h>

int main()
{
    int i = 0, a = 0, b = 2, c = 3, d = 4;
    i = a++&&++b&&d++;
    printf("a=%d\n b=%d\nc=%d\nd=%d\n", a, b, c, d);
    system("pause");
    return 0;
}

运行结果:
a=1
b=2
c=3
d=4
在&&计算过程中只要碰见0后面的就不计算

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int i = 0, a = 0, b =2 , c = 3, d = 4;
    i = a++||++b||d++;
    printf("a=%d\n b=%d\nc=%d\nd=%d\n", a, b, c, d);
    system("pause");
    return 0;
}

运行结果是:
1 3 3 4
对于或来说碰见为真的就不用再算了

条件操作符

exp1 ? exp2 : exp3

意思为表达式exp1为真整个表达式的结果就为exp2,如果表达式exp1为假整个表达式的结果就为exp3

if (a > 5)
        b = 3;
    else
        b = -3;//与下列式子等价
a > 5 ? 3 : -3;

使用条件表达式实现找两个数中较大值
max = (a > b) ? a : b;

逗号表达式

exp1,exp2xexp3,....expN

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

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int a = 1;
    int b = 2;
    int c = (a > b, a = b + 10, a, b = a + 1);
    printf("%d", c);
    system("pause");
    return 0;
}

c的结果是13
原因是:按照逗号表达式规定结果取最后一个b=a+1;但是前三个表达式也要计算,所以最后一个表达式就为b=12+1=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)
{

}   

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

  1. [ ]
操作数:一个数组名+一个索引值
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符
[ ]的两个操作数是arr和9
int arr[10] = {1,2,3,4,5,6,7,8,9,0}
int i = 0;
printf("%p\n",&arr[4]);//0036FEF8
printf("%p\n",arr+4);//0036FEF8
printf("%d\n",*(4+arr));//5
printf("%d\n",*(arr+4));//5
printf("%d\n",4[arr]);//5
printf("%d\n",*(4+arr));//5
printf("%d\n",arr[4]);//5

2.( )

函数调用操作符
接收一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
void test2(const char *str)
test2("hello world");

3. 访问一个结构的成员
.结构体.成员名
->结构体指针->成员名

struct Stu
{
    char name[10];
    int age;
    char sex[5];
    double score;
};
void set_age1(struct Stu stu)
{
    stu.age = 18;
}
void set_age(struct Stu* pStu)
{
    pStu->age = 18;//结构成员访问
}
int main()
{
    struct Stu stu;
    struct Stu* pStu = &stu;//结构成员访问
    stu.age = 20;//结构成员访问
    set_age1(stu);
    pStu->age = 20;//结构体成员访问
    set_age2(pStu);
    return 0;
}

表达式求值

表达式求值的顺序一部分是由操作符的优先级和结合型决定。同样,有些表达式的操作符在求值的过程中可能需要转换为其他类型。

隐式类型转换

C的整型算术运算总是至少以缺省整型类型的精度来计算。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换成为【整型提升】

这里写图片描述
上图就是
char a,b,c;

c=b+a;
b和a的值被提升为普通整型,然后在执行加法运算。加法运算完成后,结果将进行普通提升,然后被截断,再存储于a中。

对于上式可能整型提升结果和使用8位算术运算的结构是一样的,但是如果用a = (~a^b << 1) >> 1;就不一样了。
由于存在求补和左移操作,所以8为的精度是不够的。标准要求进行完整的整型求值,所以对于这类表达式的结果,不会存在歧义。
算术转换
如果某个操作符的各个操作数属于不同类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int 
long int 
unsigned int 
int 

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。

警告:
算数转换要合理,要不然会有一些潜在问题。
eg:
float f = 3.14;
int num = f;//隐式转换,存在精度丢失

操作符的属性

复杂表达式的求值有三个影响的因素。
  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序

两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
可参考《C和指针》操作符优先级图表

一些问题表达式
//表达式的求值部分由操作符的优先级决定
a*b + c*d + e*f

注释:代码在计算的时候,由于比+的优先级高,只能保证的计算是比+早,但是优先级并不能决定第三个*比第一个+早执行。

所以表达式的计算顺序就可能是:
a*b
c*b + c*d
e*f
a*b +c*d + e*f
或者:
a*b
c*d
e*f
a*b + c*d
a*b + c*d + e*f

c + --c;

注释:同上,操作符的优先级只能决定自减–的运算在+的运算的前面,但是我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。

//非表达式
int main()
{
    int i = 10;
    i = i -- - --i * ( i = -3 ) * i++ + ++i;
    printf("i = %d\n", i);
    reeturn 0;
}
上条代码在不同的编译器中测试结果:

这里写图片描述
例如:
int ret = (++i)+(++i)+(++i);//error
这个代码有问题是因为在编译过程中有多种计算路径,在不同的编译有不同的结果。

int fun()
{
    static int count = 1;
    return ++count;
}
int main()
{
    int answer;
    answer = fun() = fun() * fun();
    printf("%d\n", answer);
    return 0;
}

static是定义静态变量只要定义就不会销毁。
但是上面的函数也是错误的,在计算过程中不能确定唯一的计算路径,在不同的编译器中运行会有不同的运行结果。

总结:我们不能写出依赖求值顺序的表达式。这样的表达式是不可移植的。尽量避免。尽量写出只有唯一计算路径的表达式。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值