REGRET
blog

C语言初阶之操作符与表达式

前言

本文主要包括各种操作符的介绍与表达式求值,欢迎各位小伙伴与我一起学习。

一、操作符

分类

  • 算术操作符
  • 移位操作符
  • 位操作符
  • 赋值操作符
  • 单目运算符
  • 关系操作符
  • 逻辑操作符
  • 条件运算符
  • 逗号运算符
  • 下标访问,函数调用和结构体员

1.算术操作符

   +  加    -  减    *  乘    /  除    %  取余

例1:

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

int main()
{
    int a = 5/2;
    printf("%d",a);
    return 0;
}

例2:

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

int main()
{
    float a = 5.0/2;
    printf("%lf",a);
    return 0;
}
  1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
  2. 对于 / 操作符,如果两个操作数都为整数,执行整数除法;而只要有浮点数执行的就是浮点数除法。
  3. % 操作符的两个操作数必须为整数,返回的是整除之后的余数。

2.移位操作符

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

左移操作符规则

例:

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

int main()
{
    int a = 5;
    int b = a<<1;
    printf("%d",b);
    return 0;
}

图片

规则:左边丢弃,右边补0
警告:对于位运算符,不要移动负数位,这个是标准未定义的。

例:
int num = 10;
num >> -1 ;

右移操作符规则

例:

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

int main()
{
    int a = 16;
    // >> 右移操作符
    //移动的是二进制位
    //00000000000000000000000000010000
    //32个bit位
    int b = a>>1;
    printf("%d",b);
    return 0;
}

右移操作符:

  1. 算术右移:
    右边丢弃,左边补原符号位(我们见到的基本上都是算术右移)

  2. 逻辑右移:
    右边丢弃,左边补0

例:

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

int main()
{
    int a = -1;
    //存储到内存的是补码
    //1000000000000000000000000000001
    //第一位是符号位,1表示负数,0表示正数
    int b = a>>1;
    printf("%d",b);
    return 0;
}

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

整数的二进制表示形式有三种(正数的反码和补码与原码一样)

  • 原码:直接根据数值写出的二进制序列
  • 反码:原码的符号位不变,其他位按位取反就是反码
  • 补码:反码+1

例:-1的原反补:

原码:1000000 00000000 00000000 00000001
反码:1111111 11111111 11111111 11111110
补码:1111111 11111111 11111111 11111111

3.位操作符

按位与操作符:         &
按位或操作符:         |
按位异或操作符:        ^

注:都是用补码进行计算,如果是负数,要先找出它的补码进行计算:a-b = a的补码+(-b)的补码

按位与:有一个为0,结果就为0,两个都为1,结果才为1.

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

int main()
{
    int a = 3;
    int b = 5;
    int c = a&b;
    printf("%d",c);
    return 0;
}

按位或 :有一个为1,结果就为1,两个都为0,结果才为0.

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

int main()
{
    int a = 3;
    int b = 5;
    int c = a|b;
    printf("%d",c);
    return 0;
}

按位异或 :相同为0,相异为1.

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

int main()
{
    int a = 3;
    int b = 5;
    int c = a^b;
    printf("%d",c);
    return 0;
}

不创建临时变量(第三个变量),交换两个数:

1.加减法
缺点:可能会溢出,不可取

 a = a + b;
 b = a - b;
 a = a - b;

2.乘除法
缺点:可能会溢出,不可取

 a = a * b;
 b = a / b;
 a = a / b;

3.异或法
不会溢出

 a = a ^ b;
 b = a ^ b;
 a = a ^ b;

例:

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

int main()
{
    int a = 2;
    int b = 5;
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    printf("a: %d \n", a);
    printf("b: %d \n", b);
    return 0;
}
//第一次:010^101=111
//第二次:111^101=010
//第三次:111^010=101

4.赋值操作符

赋值操作符就是你的变量已经有一个值了,但是你不满意,你就可以使用它把一个新的值赋值给它,其实就是把右边的值赋值到左边,放进变量里进行存储。

赋值操作符是一个等号=,而判断操作符是两个等号==

复合操作符:

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

5.单目运算符

   !	                     逻辑反操作
   -	                     负值
   +	                     正值
   &	                     取地址
   sizeof	                 操作数的类型长度
   ~	                     对一个数的二进制按位取反
   --	                     前置,后置--
   ++	                     前置,后置++
   *	                     间接访问操作符
  (类型)                      强制转换类型

取地址:

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

int main()
{
    int a = 5;
    int *p = &a;
    *p = 10;
    printf("%p\n", &a);
    printf("*pa: %d\n", *pa);
    printf("a: %d\n", a);
    return 0;
}

sizeof 计算的是变量所占内存空间的大小:

例1:

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

int main()
{
    int a = 10;
    char c = 'r';
    char *p = &c;
    int arr[10] = { 0 };
    printf("%d\n",sizeof(a));
    printf("%d\n",sizeof(c));
    printf("%d\n",sizeof(p));
    printf("%d\n",sizeof(arr));
    return 0;
}

例1:

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

int main()
{
    short s = 0;
    int a = 10;
    printf("%d\n",sizeof(s = a + 5));
    //算的是s的大小,s是short型,占两个字节
    //sizeof里的表达式不进行运算
    printf("%d\n",s);
    return 0;
}

++ 的使用:

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

int main()
{
    int a = 10;
    int b = 10;
    printf("%d\n",++a);
    printf("%d\n",b++);
    printf("%d\n",b);
    return 0;
}

强制类型转换:

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

int main()
{
    int a = (float)3.14;
    //将float类型强制转换成int类型
    printf("%d\n",a);
    return 0;
}

sizeof与数组:

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

void test1(int arr[])
{
    printf("%d\n",sizeof(arr));
}
void test2(char ch[])
{
    printf("%d\n",sizeof(ch));
}
int main()
{
    int arr[10] = { 0 };
    char ch[10] = { 0 };
    printf("%d\n",sizeof(arr));
    printf("%d\n",sizeof(ch));
    test1(arr);
    test1(ch);
    return 0;
}

6.关系操作符

 > , >=
 < , <=
 !=                   不等于
 ==                   判断两个数是否相等
 =                    赋值

7.逻辑操作符

  逻辑与       &&
  逻辑或       ||

&&:如果逻辑与左边的条件为假的话,后面就不用算了

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

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

||:如果逻辑或左边的条件为真的话,后面就不用算了

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

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

8.条件运算符

exp1 ? exp2 : exp3
三目操作符: x > n ? 1 : 0
这里的意思是:x大于n吗?true返回1,否则为0

9.逗号运算符

exp1, exp2, exp3, …expN

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

例:

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

int main()
{
    int a = 1;
    int b = 2;
    int c = (a > b,a = b + 1,a,b = a + 1);
    printf("%d\n",c);
}

10.下标访问,函数调用和结构成员

1. [ ]下标引用操作符,操作数:一个数组名 + 一个索引值  
2. 函数调用操作符( ),一个函数callFunc( )
3. 结构成员访问操作符分两种 . 和 ->

1.下标访问:

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

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    printf("%d\n",arr[9]);
    return 0;
}

2.函数调用:

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

int get_max(int x,int y)
{
    if(x > y)
        return x;
    else
        return y;
}
int main()
{
    int a,b;
    int max = 0;
    scanf_s("%d%d",&a,&b);
    max = get_max(a,b);
    //调用函数的时候的()就是函数调用操作符
    //操作数有三个,函数名get_max,参数a和b
    printf("%d\n",max);
    return 0;
}

3.访问一个结构体的成员:

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

  struct str
{
    char name[20];
    char id[20];
    int age;
};
int main()
{
    struct str s = {"uzi", "006", 18};
    printf("Name: %s\n", s.name);
    printf("Id : %s\n", s.id);
    printf("Age: %d\n", s.age);
    return 0;
}

它本身就向内存申请了一部分空间,你也可以用指针来写

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

struct str
{
    char name[20];
    char id[20];
    int age;
};

int main()
{
    struct str s = {"uzi", "006", 18};
    struct str *ps = &s;
    printf("%s\n", (*ps).name);
    //也可以这样写:printf("%s\n", ps->name);
    return 0;
}

二、表达式求值

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

1.隐式类型转换

C的整型算术运算符总是至少以缺省整型类型的精度来进行的。

为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

整型提升的意义:

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。

因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。

通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。

如何进行整体提升:

整型提升是按照数据类型的符号位来提升的。

整形提升规则:

有符号数,高位补符号位 ; 无符号数,高位补0.

例:

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

int main()
{
    char a = 3;
    //000000000000000000000011
    //它取值时取的是它的高位:00000011  -a
    char b = 127;
    //000000000000000001111111
    //高位:01111111  -b
    char c = a + b;
    //000000000000000000000011  -a
    //000000000000000001111111  -b
    //000000000000000010000010  -c
    //a和b在这之前已经发生整型提升
    printf("%d\n",c)
    //高位:10000010  -c
    //111111111111111110000010 -补码
    //111111111111111110000001 -反码
    //100000000000000001111110 -原码
    //-126
    return 0;
}

2.算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。

下面的层次体系成为:寻常算术转化。

   long double
   double
   float
   unsugned long int
   long int
   unsigned int
   int

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

注意:算数转换要合理,要不然会有一些潜在的问题

3.操作符的属性

复杂表达式的求值有三个影响的因素:

  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序

如果两个操作符相邻,先执行哪个取决于它们的优先级,如果优先级相同就取决于它们的结合性。

一些问题表达式:

表达式1:

a*b + c*d + e*f

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

表达式的计算机顺序就可能是
a*b
c*d
a*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

表达式2:

c + -- c;

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

代码3:非法表达式

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

int main()
{
    int i = 10;
    i = i-- - --i * ( i = -3 ) * i++ + ++i;
    printf("i = %d\n", i);
    return 0;
}

代码4:

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

int fun()
{
    static int count = 1;
    return ++count;
    //2   3   4
}

int main()
{
    int anwer;
    anwer = fun() - fun()*fun();
    printf("%d\n", anwer);
    //这个代码是有问题的,你不知道它先调用的是谁
    //有可能是2-3*4  又或者是4-2*3
    return 0;
}

虽然在大多数的编译器上求得结果都是相同的,但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。函数的调用先后顺序无法通过操作符的优先级确定。

代码5:

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

int main()
{
    int i = 1;
    int ret = (++i) + (++i) + (++i);
    printf("%d\n", ret);
    printf("%d\n", i);
    return 0;
}

在不同的环境下执行代码,产生了不同的结果。原因:这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级和结合性无法决定第一个 + 和第三个前置 ++ 的先后顺序。

总结:一定要保证只有唯一的一个运算顺序,不然代码就是错误的。

结尾

为梦想颠簸的人有很多,不差你一个,但如果你能坚持到最后,那你就是唯一。半山腰太挤了,总得去山顶看看。

posted on 2022-07-31 18:07  REGRET。  阅读(172)  评论(0编辑  收藏  举报