C语言操作符详解

简介: C语言操作符详解

1.操作符分类

操作符分为:算术操作符,移位操作符,位操作符,赋值操作符,单目操作符,关系操作符,逻辑操作符,条件操作符,逗号操作符,下标引用、函数调用和结构成员。

下面让我来为大家一一详解

2.算术操作符

+     -     *     /     %

这五个操作符想必大家都已经用的得心应手了,不过我还是要来提一下 / 和 % 需要注意的一些小细节,我们直接来看代码

50fe792aafa74246992166601a3508c9.png对于 / 操作符如果两个基数都为整数,执行整数除法,而只要有浮点数,执行的就是浮点数除法


74ef20d3e4224a69822ec993c31dfb0b.png

以上这种写法为什么会报错呢?

这是因为 % 操作符的两个操作数必须为整数,返回的是整除后的余数。除了 % 操作符之外,其他的几个操作符都可以用于整数和浮点数。

3.移位操作符

<<  左移操作符

>>  右移操作符

注:移位操作符的操作数只能是整数

整数在内存中存储的是二进制位的补码,移位操作符移动的是存储在内存中的补码,要学习它我们首先要了解什么是原码,反码,补码。

3.1原码,反码,补码

整型的二进制可以用原码,反码和补码来表示,而计算机中是不能识别负号的,所以计算机中用补码的形式来存储正数和负数,规定最高符号位是0,表示正数,最高符号位是1,表示负数,正数的原码,反码,补码相同,而负数需要进行计算,其中反码 = 原码的最高符号位不变,其他位按位取反;补码 =反码 + 1

下面我们拿 12 和 -12来为大家举个栗子:

12 ---整数,在C语言可以存放到 int 类型的变量中,int 类型是4个字节,32个比特位

00000000000000000000000000001100    ---原码

00000000000000000000000000001100    ---反码

00000000000000000000000000001100    ---补码

正数的原码,反码,补码相同

-12

10000000000000000000000000001100    ----原码

111111111111111111111111111111110011    ----反码(原码的符号位不变,其他位按位取反)

111111111111111111111111111111110100    ----补码(反码 + 1)

负数的反码 = 原码的最高符号位不变,其他位按位取反     补码 =反码 + 1

3.2左移操作符

移位规则:左边抛弃,右边补零

我们来看个栗子

#include<stdio.h>
int main()
{
  int m = 12;
  int n = m << 1;
  printf("n=%d", n);
  return 0;
}

我们一起来用上面的方法计算一下会输出多少

12,正数补码与原码相同

00000000000000000000000000001100  ---补码

向左移动一个一个比特位,左边抛弃,右边补零

00000000000000000000000000011000 = 24(补码原码相同)

下面看个图先


57cd7b2b7d364f138746e5c2c062a271.png

通过多组计算我们得到了一个有趣的现象:用左移操作符,每左移一位都可以对原数乘以2

3.3右移操作符

移位规则:首先右移运算分为两种:

1.逻辑移位:左边用0填充,右边丢弃

2.算数移位:左边用原该值的符号位填充,右边丢弃

其中到底用哪一种,主要是取决编译器的,大多数用的都是算数移位。今天我们要讲解的也是这个

我们还是来举个栗子

#include<stdio.h>
int main()
{
  int m = 12;
  int n = m >> 1;
  printf("n=%d", n);
  return 0;
}

我们运用上面的规则来计算一下会输出多少

12,正数补码与原码相同

00000000000000000000000000001100  ---补码

向右移动一个比特位,左边用原该值的符号位填充,右边丢弃

00000000000000000000000000000110 = 6(原码补码相同)

右移操作符也有一个有趣的现象,我们来看图

895a6f557ec340b79ddfd712000210c3.png

通过计算我们发现:用右移操作符,每右移一位都可以对原数除以2

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

例如:

3a7b011fcb6747cda388b7429ef7885b.png

4.位操作符

&   按位与

|    按位或

^   按位异或

注:他们的操作数必须是整数

4.1按位与 &

使用规则:补码二进制位同为1时才为1,不同为 0

下面来看实例:

#include<stdio.h>
int main()
{
  int m = 12;
  //000000000000000000000000000001100(原码补码相同)
  int n = -6;
  //100000000000000000000000000000110(原码)
  //111111111111111111111111111111001(反码)
  //111111111111111111111111111111010(补码)
  int i = m & n;
  //000000000000000000000000000001100(m补码)
  //111111111111111111111111111111010(n补码)
  //m&n
  //000000000000000000000000000001000 = 8(i的补码,正数等于原码)
  printf("i=%d", i);
  return 0;
}

4.2按位或 |

使用规则:二进制位同为0时才为0,不同时为1

下面来看实例:

#include<stdio.h>
int main()
{
  int m = 12;
  //000000000000000000000000000001100(原码补码相同)
  int n = -6;
  //100000000000000000000000000000110(原码)
  //111111111111111111111111111111001(反码)
  //111111111111111111111111111111010(补码)
  int i = m | n;
  //000000000000000000000000000001100(m补码)
  //111111111111111111111111111111010(n补码)
  //m|n
  //111111111111111111111111111111110(i补码,负数)
  //111111111111111111111111111111101(i反码)
  //100000000000000000000000000000010 = -2(i原码)
  printf("i=%d", i);
  return 0;
}

4.3按位异或 ^

使用规则:二进制位相同为0,不同为1

下面来看实例:

#include<stdio.h>
int main()
{
  int m = 12;
  //000000000000000000000000000001100(原码补码相同)
  int n = -6;
  //100000000000000000000000000000110(原码)
  //111111111111111111111111111111001(反码)
  //111111111111111111111111111111010(补码)
  int i = m ^ n;
  //000000000000000000000000000001100(m补码)
  //111111111111111111111111111111010(n补码)
  //m^n
  //111111111111111111111111111110110(i补码,负数)
  //111111111111111111111111111110101(i反码)
  //100000000000000000000000000001010 = -10(i原码)
  printf("i=%d", i);
  return 0;
}

想必看到这儿大家对这三个位操作符的使用就很清楚了吧,那么下面我们来看一道奇怪的题目

4.3 奇怪的题目:不能创建临时变量(第三个变量),实现两个数的交换

我猜你跟我当时一样,一时半会有点儿想不出来,下面我用两个方法来求解:

方法一:加减法

#include<stdio.h>
int main()
{
  int a = 3;
  int b = 6;
  a = a + b;
  b = a - b;
  a = a - b;
  printf("a=%d b=%d", a, b);//a=6,b=3
  return 0;
}

方法二:按位异或

#include<stdio.h>
int main()
{
  int a = 3;
  //00000000000000000000000000000011(补码)
  int b = 6;
  //00000000000000000000000000000110(补码)
  a = a^b;
  //00000000000000000000000000000101(补码)
  b = a^b;
  //00000000000000000000000000000011 = 3(补码=原码)
  a = a^b;
  //00000000000000000000000000000110 = 6(补码=原码)
  printf("a=%d b=%d", a, b);//a=6,b=3
  return 0;
}

5.赋值操作符

赋值操作符可以让你给自己重新赋值,得到一个自己满意的值

比如:

#include<stdio.h>
int main()
{
  int height = 165;//身高
  height = 180;//赋值
  int weight = 150;//体重
  weight = 120;//赋值
  return 0;
}

在这里我们要注意赋值和初始化的区别:

初始化是创建变量的同时给变量定义一个值,会导致系统分配一个新的存储空间

int a = 0; //初始化语句:依次给出变量类型,名称和初值
int b = 8;//同上
int c; //定义语句:依次给出变量类型,名称,但没有给出初值,严谨的说这被称为非初始化的定义

赋值是定义后给变量一个值,不会导致系统分配新的存储空间,而只是将一个已分配的存储空间内部所存储的值复制到另一个已分配的存储空间中

c = 6//赋值语句:不需要(也不能)给出变量的类型,因为所涉及的变量必定已经被定义过了
a = b//同上

顺便提一下,定义分为两种:

1.非初始化的定义(如c),这不是个好习惯,不推荐。

2.初始化(如a,b),这是个好习惯,值得推荐。

5.1连续赋值问题

赋值操作符可以连续使用,比如:

#include<stdio.h>
int main()
{
  int a = 6;
  int b = 8;
  int c = 5;
  a = b = c + 3;//连续赋值
}

连续赋值的代码阅读体验感极差 ,是不推荐的,同样的语意我们不妨分开来写

#include<stdio.h>
int main()
{
  int a = 6;
  int b = 8;
  int c = 5;
  b = c + 3;
    a = b;
}

这样的写法是不是更加一目了然,且易于尝试,这是推荐的

5.2复合赋值符

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

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

#include<stdio.h>
int main()
{
  int a = 12;
  a += 12;
  //相当于a=a+12
  return 0;
}

其他几个的使用都是一个道理,这样写可以是代码更简洁

6.单目操作符

6.1单目操作符介绍

!        逻辑反操作

-        负值

+       正值

&       取地址

sizeof     操作数的类型长度(以字节为单位)

~       对一个数的二进制按位取反

--       前置、后置--

++     前置、后置++

*       间接访问操作符(解引用操作符)

(类型)    强制类型转换

我们选几个来讲解一下

1.逻辑反操作符 !

#include<stdio.h>
int main()
{
  int a = 3;
  int b = 0;
  if (a)
    printf("666\n");
  if (!b)//条件为假成立
    printf("888\n");
  return 0;
}//输出666
     //888

2.对一个二进制位按位取反 ~

#include <stdio.h>
int main()
{
  int a = 12;
  //00000000000000000000000000001100(补码)
  int b = ~a;
  //11111111111111111111111111110011(补码)
  //11111111111111111111111111110010(反码)
  //10000000000000000000000000001101 = -13(原码)
  printf("b=%d", b);
  return 0;
}

3. ++ 和 -- 操作符

#include<stdio.h>
int main()
{
  int a = 3;
  int b = ++a;//前置++,先++,后使用
  //a = a + 1, b = a;
  printf("a=%d b=%d\n", a, b);//4 4
  int a = 3;
  int b = a++;//后置++,先使用,后++
  //b = a, a = a + 1;
  printf("a=%d b=%d\n", a, b);//4 3
  int a = 3;
  int b = --a;//前置--,先--,后使用
  //a = a - 1, b = a;
  printf("a=%d b=%d\n", a, b);//2 2
  int a = 3;
  int b = a--;//后置--,先使用,后--
  //b = a, a = a - 1;
  printf("a=%d b=%d\n", a, b);//2 3
  return 0;
}

4.强制类型转换操作符 类型)

#include <stdio.h>
int main()
{
  float a = 3.14f;
  int b = (int)a;//将a从float类型强制转化为int类型
    printf("b=%d", b);//b=3
  return 0;
}

6.2sizeof和数组

sizeof()可以求变量(类型)所占空间的大小(以字节为单位)

举个栗子(32位平台下)

b4608432ffe1469f90c75b7ebb684b67.png

我们通过代码来看用sizeof求数组大小的一些细节

0de7e9964077428084c5c9d0ff8049be.png

注意 sizeof(数组名)是求整个数组的大小

再来举起第二个栗子:

160b27ab89694f15b4d2a24e543cdf1f.png

这里函数传参传过去的是数组名,也就是数组首元素的地址,所以在32位平台下的大小是4

7.关系操作符

>     >=     <     <=     !=     ==

!= 用于测试”不相等“,== 用于测试”相等“ 这几个运算符都比较简单想必大家都能理解

注意:在编译过程中 ==(相等) 和 = (赋值)不小心写错导致的错误!

8.逻辑操作符

&&    逻辑与

||       逻辑或

逻辑操作符用来判断真假很常见,&&是两边都为真为真,|| 是只要有一边为真就为真

我们来两道例题:

#include <stdio.h>
int main()
{
    int i = 0,a=0,b=2,c =3,d=4;
    i = a++ && ++b && d++;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0;
}//输出1 2 3 4

这里为什么会输出1 2 3 4呢?

这是因为逻辑与和逻辑或会发生”短路现象“:逻辑与和逻辑或操作符都是从左往右依次执行,对于&&如果前操作数为假(0),那么后面的式子就不会执行,为真(非0)则继续执行;II 也是一样的道理如果前式为真,那么后面的式子就不会执行,为假则会继续执行。

这里a++是后置++,先使用再++,a=0后,后面的式子就不会判断了,所以a使用后++为1,b,c,d的值不变所以就会输出1 2 3 4

再来看另一个同理的题目:

#include <stdio.h>
int main()
{
    int i = 0,a=0,b=2,c =3,d=4;
    i = a++||++b||d++;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0;
}//输出1 3 3 4

同样的道理,我相信有友们都能理清楚。

9.条件操作符

exp1 ? exp2 : exp3

我们分别用if语句和条件表达式来求两个数中的较大值:

//if语句
if(a>1)
{
    b=6;
}
else
{
    b=-6;
}
//条件表达式
b = (a>1)?6:-6

这样看来有时候使用条件操作符会比if语句更加简洁

10.逗号表达式

exp1,exp2,exp3,exp...

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

我们还是举个栗子,来看看怎样使用:

#include <stdio.h>
int main()
{
  int a = 1;
  int b = 2;
  int c = (a > b, a = b + 10, a, b = a + 1);
  //         0  ,    a=12   ,a=12,  b=13
  printf("%d\n", c);
  return 0;
}//最终输出最后一个表达式的结果13.

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

11.1下标引用操作符[ ]

 操作数:一个数组名 + 一个索引值  

int arr[10];//创建数组
arr[9] = 10;//引用数组中第十个元素赋值为10
//[ ]的两个操作数是arr和9

11.2函数调用操作符

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

 #include <stdio.h>
 void test1()
 {
 printf("happy\n");
 }
 int main()
 {
 test1(); //()作为函数调用操作符。
 return 0;
 }

11.3访问结构成员

访问一个结构的成员

.     结构体。成员名

->  结构体指针->成员名

#include<stdio.h>
struct S
{
  int data[1000];
  char buf[100];
};
void print1(struct S ss)
{
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    printf("%d ", ss.data[i]);
  }
  printf("%s\n", ss.buf);
}
void print2(struct S* ps)
{
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    printf("%d ",ps->data[i]);
  }
  printf("%s\n", ps->buf);
}
int main()
{
  struct S s = { {1,2,3},"haha" };
  print1(s);
  print2(&s);
  return 0;
}

12.表达式求值

表达式求值的顺序一部分是由操作符的优先级和结合性决定。

同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。

12.1隐式类型转换

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

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

整型提升的意义

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

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

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

例如:

char a,b,c;
a = b + c;

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

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

//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//无符号整形提升,高位补0

下面来看一个栗子

#include <stdio.h>
int main()
{
  char a = 0xb6;
  //10110110=a
  //整型提升后
  //11111111111111111111111110110110(a最高符号位是1,所以补1)
  short b = 0xb600;
  //1011011000000000=b
  //整型提升后
  //11111111111111111011011000000000
  int c = 0xb6000000;
  if (a == 0xb6)//会发生整型提升不会再等于0xb6
    printf("a");
  if (b == 0xb600)//会发生整型提升
    printf("b");
  if (c == 0xb6000000)//int类型不会发生整型提升
    printf("c");
  //最终输出c
  return 0;
}

这里a,b要进行整形提升,但是c不需要整形提升 a,b整形提升之后,变成了负数,所以表达式 a==0xb6 , b==0xb600 的结果是假,但是c不发生整形提升,则表达式 c==0xb6000000 的结果是真.

栗子X2:

int main()
{
 char c = 1;
 printf("%u\n", sizeof(c));
 printf("%u\n", sizeof(+c));
 printf("%u\n", sizeof(-c));
 return 0;
}
//只要short和char类型的运算,就会发生整型提升。

这里c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节.表达式 - c 也会发生整形提升 , 所以 sizeof( - c) 是4个字节, 但是 sizeof(c) , 就是 1 个字节.

12.2算数转换

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

1.long double
2.double
3.float
4.unsigned long int
5.long int
6.unsigned int
7.int

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

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

float f = 3.14;//double类型转换为float类型
int num = f;//float类型转换为int类型,这是隐式转换,会有精度丢失

12.3操作符的属性

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

1. 操作符的优先级

2. 操作符的结合性

3. 是否控制求值顺序。

两个相邻的操作符先执行哪个,取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。  至于操作优先级有友们可以在CSDN里搜索一下某个大佬的表格,每次要用的时候就看看,时间长了那些常用的就记住了。

好了以上就是今天的全部内容了,对友友们有帮助的话不妨三连加关注走一波,后期会持续更新C语言干货。

目录
相关文章
|
29天前
|
存储 C语言 索引
【C语言篇】操作符详解(下篇)
如果某个操作数的类型在上⾯这个列表中排名靠后,那么⾸先要转换为另外⼀个操作数的类型后执⾏运算。
|
29天前
|
程序员 编译器 C语言
【C语言篇】操作符详解(上篇)
这是合法表达式,不会报错,但是通常达不到想要的结果, 即不是保证变量 j 的值在 i 和 k 之间。因为关系运算符是从左到右计算,所以实际执⾏的是下⾯的表达式。
|
1月前
|
C语言
C语言操作符(补充+面试)
C语言操作符(补充+面试)
33 6
|
1月前
|
存储 编译器 C语言
十一:《初学C语言》— 操作符详解(上)
【8月更文挑战第12天】本篇文章讲解了二进制与非二进制的转换;原码反码和补码;移位操作符及位操作符,并附上多个教学代码及代码练习示例
43 0
十一:《初学C语言》—  操作符详解(上)
|
2月前
|
C语言
五:《初学C语言》— 操作符
本篇文章主要讲解了关系操作符和逻辑操作符并附上了多个代码示例
33 1
五:《初学C语言》—  操作符
|
3月前
|
C语言
C语言逻辑操作符的短路问题
C语言逻辑操作符的短路问题
|
3月前
|
编译器 C语言
【C语言】:中移位操作符,位操作符详运算规则详解
【C语言】:中移位操作符,位操作符详运算规则详解
27 1
|
3月前
|
存储 编译器 C语言
|
3月前
|
存储 C语言 索引
【C语言基础】:操作符详解(二)
【C语言基础】:操作符详解(二)
|
3月前
|
编译器 C语言
C语言学习记录——操作符详解知识点选记(算术操作符、单目操作符、移位操作符、关系操作符、逻辑操作符、条件操作符......)二
C语言学习记录——操作符详解知识点选记(算术操作符、单目操作符、移位操作符、关系操作符、逻辑操作符、条件操作符......)二
38 3