C语言操作符和表达式详细讲解

Posted 敲键盘的钢琴家

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言操作符和表达式详细讲解相关的知识,希望对你有一定的参考价值。

1、操作符分类:

1、算术操作符 + - * / %
2、移位操作符 << >>
3、位操作符 & | ^
4、赋值操作符 = += -= …
5、单目操作符 sizeof ! ++ –
6、关系操作符 > >= < <= != ==
7、逻辑操作符 && ||
8、条件操作符 ?:
9、逗号表达式 ,
10、下标引用、函数调用和结构成员 [] -> () .

2、算术操作符

+-*/ 取余%

+、-、*都非常简单,但要注意/和%

  • 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
  • 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
int ret =9/2;    整数除法
double tmp=9/2.0 浮点数除法
  • % 操作符的两个操作数必须为整数。返回的是整除之后的余数。

3、移位操作符

3.1 操作符基础知识

  • 移位操作符,移动的是二进制位(补码)
  • 对于整数的二进制有3种表示形式:原码、反码、补码
  • 正整数-原码、反码、补码相同
  • 负整数
    1、原码-直接按照数字的正负写出的二进制序列
    2、反码-原码的符号位不变,其他位按位取反
    3、补码-反码+1
  • 警告⚠ : 对于移位运算符,不要移动负数位,这个是标准未定义的 。并且操作数要针对整数。

3.2 左移操作符<<:左边丢弃,右边补0

int a=5;
int b=a<<1;
//原00000000000000000000000000000101
//反00000000000000000000000000000101
//补00000000000000000000000000000101
//将a的二进制位向左移动一位,首位的0被丢弃,后面
//的空缺位置补0,如下图所示
//注意:在这个过程中a的值没变,只不过是对a进行了一种运算

3.3 右移操作符>>
1、逻辑移位:左边用0填充,右边丢弃

int a=-1;
int b=a>>1;
//11111111111111111111111111111111
//移位得到
//01111111111111111111111111111111

2、算术以为:左边用原该值的符号位填充,右边丢弃,大多数情况下编译器以此为主

int a=-1;
int b=a>>1;
//11111111111111111111111111111111
//移位得到
//11111111111111111111111111111111

4、位操作符

  • 位操作符有
& //按位与
| //按位或
^ //按位异或
注:他们的操作数必须是整数
  • &按位与,二进制位都为1为1,有一个0及以上时为0;
int main()
{
    int a=3;
    int b=-2;
    int c=a&b;
    //00000000000000000000000000000011  3的补码
    //11111111111111111111111111111110  -2的补码
    //00000000000000000000000000000010   按位与的结果
    //得到的结果是补码,要翻译成原码才是屏幕显示的
}
  • |按位或 二进制位只要有1就为1
int main()
{
    int a=3;
    int b=-2;
    int c=a&b;
    //00000000000000000000000000000011  3的补码
    //11111111111111111111111111111110  -2的补码
    //11111111111111111111111111111111 按位或的结果
  • ^异或 二进制位相同为0,相异为1
int main()
{
    int a=3;
    int b=-2;
    int c=a&b;
    //00000000000000000000000000000011  3的补码
    //11111111111111111111111111111110  -2的补码
    //11111111111111111111111111111101 按位异或的结果
  • 位操作符的应用,交换两个变量
//方法0 交换变量常用的临时变量法
int main()
{
    int a=10;
    int b=90;
    int tmp=0;
     tmp=a;
     a=b;
     b=tmp;

}
//方法一 整型变量有最大上限,如果数值较大,会出现溢出
int main()
{
    int a=10;
    int b=90;
    a=a+b;
    b=a-b;
    a=a-b;
    return 0;
}
//方法二 
int main()
{
    int a=10;
    int b=90;
    a=a^b;
    b=a^b;
    a=a^b;
}
尽管方法二较为简便,但是在过程中使用较少,因为可读性低,且效率低于方法0。
对方法二的理解:a和b异或得到密码,b和密码异或能翻译出a,a和密码继续异或
能翻译出原来的b。
a^a=0 0^a=a
a^a^b=b a^b^a=b 所以异或是支持交换律的

5、赋值操作符

赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。

int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值。


赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y+1;//连续赋值
先将y+1的值赋给x,再将x的值赋给a。可读性低

拆分后的同义句:这样的写法更加清晰而且易于调试
x=y+1;
a=x;

6、符合赋值符

简单介绍一些赋值运算符

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

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

int a=10;
a=a>>1;
a>>=1;//复合赋值

int x = 10;
x = x+10;
x += 10;//复合赋值
//其他运算符一样的道理。这样写更加简洁

7、单目操作符:只有一个操作数的操作符

  • 一些单目操作符
!           逻辑反操作
 -           负值
 -           正值
&           取地址
sizeof      操作数的类型长度(以字节为单位)
~           对一个数的二进制按位取反
--          前置、后置--
++          前置、后置++
 -           间接访问操作符(解引用操作符)
(类型)       强制类型转换
  • !逻辑反操作
!逻辑反操作,把真变成假,把假变成真
int main()
{
    int a=0;
    int b=!a;  //!a=1
    if(a) //a为真打印Hello
    {
        printf("Hello");
    }
     if(!a) //a为假打印Hello
    {
        printf("Hello");
    }
}
  • *解引用操作符
int main()
{
    int a=10;
    itn *p=&a;
    int b=*p;
    *p=20
}
//对于任何变量都由所其空间和空间里的值构成
//左值用的是空间,右值用的是内容
//*p放在右边用的是指向空间里面的值,*p放在左边用的是指向的空间
  • sizeof
//1、sizeof是操作符,不是函数
//2、sizeof是计算变量或者类型创建变量的内存大小,单位字节
//和内存中存放什么数据没有关系
int main()
{
   int a;
    printf("%d",sizeof(a));//4
    printf("%d",sizeof a);//4
//当是变量时可以省略,求的还是a的内存大小,说明sizeof不是函数
    printf("%d",sizeof(int));//4
    printf("%d",sizeof int)//4 有些编译器支持这种写法
       
    
    int b=5;
    short s=10;
    printf("%d",sizeof(s=a+2));//2
    printf("%d",s);//10
在正常情况下int类型的值放入short类型中会发生截断,在sizeof中
因为是将值放到short类型的s中,所以计算的值为2sizeof内部的表达式是不参与运算的,所以s的值仍为10
}

详解sizeof内部的表达式不产与运算:在编译期间就把sizeof的功能实现了,编译器一看s的类型取决于short就得到了2这个结果,即在链接的过程中没有机会执行a+2这个动作。所以sizeof内部的表达式不参与运算。

  • 详解sizeof和数组
#include <stdio.h>
void test1(int arr[])
{
 printf("%d\\n", sizeof(arr));//(2) 4
}
void test2(char ch[])
{
 printf("%d\\n", sizeof(ch));//(4) 4
}
int main()
{
 int arr[10] = {0};
 char ch[10] = {0};
 数组名单独放在sizeof内部,
 数组名表示整个数组,
 计算的是整个数组的大小,单位是字节
 printf("%d\\n", sizeof(arr));//(1) 40    
 printf("%d\\n", sizeof(ch));//(3) 10
 数组传参的时候,数组名会降级传过去首元素的地址,本质上是个指针。
 sizeof计算的指针的大小,在32位为464位为8
 test1(arr);
 test2(ch);
 return 0;
}
  • ~将二进制位按位取反
//可以与其他操作符配合使用,进行二进制位改变。
int a=0;
//00000000000000000000000000000000
~a;
//11111111111111111111111111111111
  • ++和–
int main()
{
//后置++,先使用,后++
    int a=10;
    int b=a++;
    printf("%d",a);//11
    printf("%d",b);//10
}
int main()
{
//前置++,先++,后使用
    int a=10;
    int b=a++;
    printf("%d",a);//11
    printf("%d",b);//11
}
//-- 也分为前置--和后置--,用法和++一样。
//注意:++和--不要写出过于复杂的表达式,比如下面代码:
int main()
{
    int a=1;
    int b=(++a)+(++a)+(++a);
    return 0;
}
  • ()强制类型转换
int main()
{
    int a=3.14//默认写出的浮点数是double
    //直接这样写可能会出现丢失数据
    //正确写法
    int a=(int)3.14;
}

8、关系操作符

用在同类型的变量比较,时间和地址的比较没有意义
>
>=
<
<=
!=   用于测试“不相等”
==      用于测试“相等”

这些关系运算符比较简单,没什么可讲的,但是我们要注意一些运算符使用时候的陷阱。
警告: 在编程的过程中===不小心写错,导致的错误。

9、逻辑操作符

&&     逻辑与(表并且)
int main()
{
    int a=3;
    int b=5;
//逻辑与只关注真假,a为真并且b为真,那整体就为真
//a和b有一个为假就为假
    int c=a&&b;
}
||     逻辑或(表或者)
int main()
{
    int a=0;
    int b=5;
//a和b中有一个为真,表达式即为真
    int c=a||b;
}

真题
#include <stdio.h>
int main()
{
    int i = 0,a=0,b=2,c =3,d=4;
    i = a++ && ++b && d++;//1 2 3 4 
先使用,a以0的值使用,因为前面为假,所以表达式整体为假,还没有到
++b部分,现在以0&&d++,为整体,前面为假,整体为假,d++也没有执行。
    i = a++||++b||d++//1 3 3 4
a++,先使用原先a的值,a为假,继续到++b,看真或假
    printf("a = %d\\n b = %d\\n c = %d\\nd = %d\\n", a, b, c, d);
    return 0;
}

#include <stdio.h>
int main()
{
    int i =0,a=1,b=2,c =3,d=4;
    i = a++||++b||d++;
先运行到a++,先使用a的值1,为真,所以整个a++||++b为真,
得到结果1++b被跳过,接着1||d++1
的结果为真,整个表达式的为真,得到结果1,d++也被跳过
    printf("a = %d\\n b = %d\\n c = %d\\nd = %d\\n", a, b, c, d);
//2 2 3 4
    return 0;
}

10、条件操作符

  • exp1? exp2 : exp3,也是唯一的三目操作符,如果表达式的结果为真,则表达式2的结果为整体表达式的结果,否则表达式3的结果为整体表达式的结果。可以看成if else选择语句。
转换成条件表达式
if (a > 5)
        b = 3;
else
        b = -3;

a>5?b=3:b=-3;

求两个数的最大值
int main()
{
    int a=10;
    int b=20;
    int m=((a>b)?(a):(b));//利用括号括起来,可读性高且语法安全
}

11、逗号表达式

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

/代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c是多少?
13

//代码2
if (a =b + 1, c=a / 2, d > 0)
真正起到判断作用的是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)
{
//解决了原有的代码冗余问题
         //业务处理
}

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

1、[] 下标引用操作符
操作数:一个数组名+一个索引值

int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
arr[9]->*(arr+9)->*(9+arr)->9[arr]
//语法支持9[arr]形式,但一般不写成9[arr]的形式。
[]的两个操作数是arr和9
printf("%p---%p",&arr[8],arr+8);//这俩地址一样
//arr+i就是数组arr中,下标为i的元素的地址

2、()函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。

#include <stdio.h>
 void test1()
 {
 printf("hehe\\n");
 }
 void test2(const char *str)
 {
 printf("%s\\n", str);
 }
 int main()
 {
 test1();            //实用()作为函数调用操作符。
 test2("hello bit.");//实用()作为函数调用操作符。
 return 0;
 }

3、结构成员的访问

. 用法:结构体.成员名

->用法:结构体指针->成员名

//自定义数据类型
struct Book
{
    char name[10];
    float price;
    char id[10];
};
void Print1(struct Book b)
{
    printf("书名:%s\\n",b.name);
    printf("价格:%f\\n",b.price);
    printf("书号:%s\\n",b.id);
}
void Print2()
{
    printf("书名:%s\\n",b->name);
    printf("价格:%f\\n",b->price);
    printf("书号:%s\\n",b->id);
}
int main()
{
    struct Book b={"C book","55.5f","c21415215"};
    b.name="数据结构";//错误写法,name是首元素的地址
    strcpy(b.name,"数据结构");//利用strcpy来实现改变
    Print1(b);
    Print2(&b);

以上是关于C语言操作符和表达式详细讲解的主要内容,如果未能解决你的问题,请参考以下文章

C语言操作符和表达式详细讲解

C语言基础学习笔记五操作符详解(详细讲解+练习巩固+记忆总结)

C Primer Plus第6版_源代码+练习答案

超详细讲解C语言文件操作!!

详细讲解 —— 操作符(C语言初阶)(万字长文)

C语言☀️函数超详讲解☀️(详细讲解+代码演示+图解)建议收藏