【C语言航路】第五站:操作符(一)

简介: 【C语言航路】第五站:操作符(一)

一、操作符分类

操作符一般分为以下几类:

算术操作符

移位操作符

位操作符

赋值操作符

单目操作符

关系操作符

逻辑操作符

条件操作符

逗号表达式

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

二、算术操作符

以下是五种算术操作符

+        -        *        /        %      

其中前三种相信大家都了解的很清楚。跟数学中的运算一致。需要注意的就是/和%这两个操作符

1./号操作符

我们直接看这样一段代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
  int ret = 10 / 3;
  printf("%d", ret);
  return 0;
}

他的运行结果是

这是因为对于除法操作符而言,两边的操作数都是整数,则执行的是整数除法,而如果想要计算出小数,则需要两边的操作数至少有一个是浮点类型的。

计算出浮点数的案例如下

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
  //int ret = 10 / 3;
  //printf("%d", ret);
  double ret = 10.0 / 3;
  printf("%lf", ret);
  return 0;
}

运行结果为

但是我们这块是六位小数,如果只想要一位小数。则将第八行中的lf改为.1lf,这个代表的是打印小数点后一位。如下所示

2.%操作符

我们看下面一段代码

#include<stdio.h>
int main()
{
  int ret = 10 % 3;
  printf("%d", ret);
}

运行结果为

其实%操作符(取模操作符)可以计算的是两个操作数的余数。并且两个操作数必须为整数,否则报错。

三、移位操作符

>>右移操作符

<<左移操作符

注意:移位操作符只能作用于整数

首先我们得知道,这个移位操作符,移的是二进制位,所以说这就要求我们要熟练掌握进制转换,我们在之前出过一篇进制转换的文章,这里附上链接:进制转换

其次是他们的使用,他们的使用是这样的,并且他们的移位并不会改变自身的大小

#include<stdio.h>
int main()
{
  int a = 10;
  int b = a << 2;
  int c = a >> 1;
  return 0;
}

1.整数的二进制表示

这是我们了解移位操作符的基础,首先我们必须了解的是,整数的二进制表示形式一共有三种。分别是原码,反码,补码。

1.计算机中的正数和负数的二进制序列

比如说我们看这一段很简单的代码

上图中a是10,b是-10,他们的区别就是一个是整数一个是负数

a是10的话,我们不难得出他的二进制序列是1010

而a又是一个整型类型。一个int类型是32个bit位,所以我们得出

1010的前面需要补0。

所以二进制序列其实应该为00000000 00000000 00000000 00001010

但是呢我们如何区分他是+10还是-10呢?

我们是用最高位来进行区分的

最高位是1,他就是负数,最高位是0,它就是正数。

所以a,也就是10,他的二进制序列为00000000 00000000 00000000 00001010

       b,也就是-10,他的二进制序列为10000000 00000000 00000000 00001010

我们将上面这两种,按照他的正负,直接就能写出来的二进制序列称为原码。

2.原码,反码和补码

我们在上面说过,整数的的二进制表示形式一共有三种,分别是原码,反码和补码。而我们在上面也解释了如何求出一个原码。原码其实就是按照一个数的正负,直接就能写出来的二进制序列。

那么反码和补码又是如何求出来的呢?

我们这里给出一条结论,正数的原码,反码,补码相同;负数的反码,补码是需要经过计算的,反码是符号位不变,其他位按位取反,得到的就是反码,补码是反码+1得到的。

我们这里举出一个例子

对于上面的a而言,他是一个正数10

他的原码是00000000 00000000 00000000 00001010

他的反码是00000000 00000000 00000000 00001010

他的补码是00000000 00000000 00000000 00001010

也就是原码,反码,补码均相同

对于上面的b而言,他是一个负数-10

他的原码是10000000 00000000 00000000 00001010

他的反码是11111111 11111111 11111111 11110101

他的补码是11111111 11111111 11111111 11110110

原码是需要将最高位,也就是符号位改为1得到的

反码是需要将原码的符号位不变,其他位均按位取反得到的

补码是反码+1得到的

3.原码反码补码三码的相互转换图解

对于正数而言,他的三码统一,所以无需进行转换

需要注意的是负数的三码转换,下面的负数转化的三码转换图解:

对于一个已知的原码,符号位不变,其他位按位取反可以得到反码,然后反码+1可以得到补码。(如图中黑色线所示)

相反的,如果补码想要得到原码可以直接按照上面相反的步骤即可得到(如图中绿色线所示)

当然还有另外一种方法,也可以从补码转换成原码,那就是和原码转换成补码的方法一样。补码的符号位不变,其他位按位取反,然后+1.这样也可以得到原码 (如图中橙色线条所示)

为了方便我们理解,我们举一个例子

对于上面的b而言,他是一个负数-10

他的原码是10000000 00000000 00000000 00001010

他的反码是11111111 11111111 11111111 11110101

他的补码是11111111 11111111 11111111 11110110

这是我们刚刚走过一个流程,是-10的原码如何得出补码的

那么从补码到原码的方法,按照逆运算,也就是绿色线条的方法,肯定是可以得到原码的

我们来看一下橙色线条的方法

-10的补码是11111111 11111111 11111111 11110110

符号位不变,其余按位取反得:

                  10000000 00000000 00000000 00001001

+1得           10000000 00000000 00000000 00001010

最终的结果恰好就是原码。

2.数据在内存中的存储

为什么我们突然要讲一下原码,反码和补码呢?他们有什么用呢?其实这是因为内存中存储的其实是补码。所以在参与移位操作符运算的时候均需要按照补码来移位。而绝不是对原码进行操作,这一点也是很多人在移位操作符上经常犯得经典的错误,标准的零分。

3.左移操作符详解

(1)正数的左移

我们看这样一段代码

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

这段代码中使用了一个左移操作符。我们先来分析一下,左移操作符是如何操作的?

我们在前文中说过,数据在内存中存储的是补码,所以移位操作符应该作用于补码

如下图所示,假设这是a在内存中的存放

我们让其进行左移一位。就变成下图所示

左边有一位被多出去了,右边少了一位。此时计算机就会将左边多出去的扔掉,不要了,然后和右边自动补0,如下图所示,紫色的0被扔掉,补充绿色的0

此时我们的二进制序列就变为了

00000000000000000000000000010100,当然这是补码,以上所有的操作都是对于补码的操作

我们打印出来的时候,我们用的是原码,但是由于正数三码统一,所以上述补码转换为原码的结果仍然是它

而它转换成10进制就是20

这就是左移操作符的图解了

我们按照上面的代码往下走,这个a<<1以后变为了20赋值给了b,所以b其实就是20了,但是这个过程中a并没有发生改变

最终运行结果就是

事实上,我们会发现,左移操作符,他使得二进制序列左移一位,他会使得我们每一位的权重都变为了原来的二倍。所以说左移操作符会使得原来的数扩大十倍。

所以左移操作符的运算规则是左边丢弃,右边补零

(2)负数的左移

我们继续来看一下负数的案例,我们看这个代码

#include<stdio.h>
int main()
{
  int a = -10;
  int b = a << 1;
  printf("%d\n", a);
  printf("%d\n", b);
  return 0;
}

对于此时的这个a而言

他的原码是10000000 00000000 00000000 00001010

他的反码是11111111 11111111 11111111 11110101

他的补码是11111111 11111111 11111111 11110110

假设它在内存中是这样放的

我们对其进行左移,而左移的规则是左边丢弃,右边补零

所以最终的补码结果为

11111111 11111111 11111111 11101100

转化为反码为(补码-1)

11111111 11111111 11111111 11101011

转化为原码为(符号位不变,按位取反)

10000000 00000000 00000000 00010100

而此时正好就是-20

我们运行一下

而此时我们会发现仍然符合我们上面的规则,左移一位,扩大2倍。

4.右移操作符详解

(1)两种右移

与左移不同的是,右移要比左移复杂一点,因为它有两种右移方式,而左移只有一种

右移有如下两种方式。具体采用哪种方式是取决于编译器的

1.算术右移(绝大多数编译器,平常见到的)

2.逻辑右移

我们看这样一段代码

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

我们先写出-1的原码,进而推导出补码

原码:10000000 00000000 00000000 00000001

反码:11111111 11111111 11111111 11111110(符号位不变,其他按位取反)

补码:11111111 11111111 11111111 11111111(反码+1)

下图是-1在内存中的存储

我们右移一位,得到下图

可是这里出现一个一个问题,是补0还是1呢?

这块就回到了我们一开始所说的右移的两种方式了

算术右移:右边丢弃,左边补原来的符号位

逻辑右移:右边丢弃,左边直接补0

而我们目前的visual studio2022编译器采用的是算术右移

所以我们补1

补了1以后呢,我们发现,这和我们之前的一模一样

所以这个补码转换成原码是

10000000 00000000 00000000 00000001

转换成十进制数就是-1

所以我们编译器应该的结果就是两个-1

注意:对于移位运算,不要移动负数位,也不要超出数据的大小。这是c语言标准未定义的。是很危险的操作。

四、位操作符

位操作符有

&        //按位与

|        //按位或

^        //按位异或

注:他们的操作数必须都是整数,同时也是针对二进制位进行计算的

1.按位与&

我们看这样一段代码

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

这段代码中

由于在内存中数据都是补码,所以&两端的数据都应该先转化成补码

a是3

所以它的原码是00000000 00000000 00000000 00000011

而正数三码统一,所以补码就是上面的二进制序列

b是-5

所以它的原码是10000000 00000000 00000000 00000101

             反码为11111111 11111111 11111111 11111010

             补码为11111111 11111111 11111111 11111011

而按位与(&)的运算是,每一个对应的二进制位依次进行计算,只有对应的二进制位都为1,结果才为1,否则为0

所以上述两个反码的运算过程见下图

所以也就是说计算出来的c的补码为00000000 00000000 00000000 00000011

而这个c的符号位为0,也就是正数,因此三码统一,所以它的原码等于补码

所以得出c的值为3

我们在总结一下&的运算规则:

对应的二进制有0,则为0,都为1,才为1

2.按位或|

我们看这样一段代码

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

对于这段代码而言

我们仍然是将a和b的二进制补码形式计算出来

a的补码为00000000 00000000 00000000 00000011

b的补码为11111111 11111111 11111111 11111011

而按位或( | )运算是,每一个对应二进制位依次进行计算,只要出现1,则为1,两个都为0,才为0

所以计算过程为

也就是c的补码为11111111 11111111 11111111 11111011

有了c的补码就可以计算出它的反码,进而推导出原码

所以c的原码为10000000 00000000 00000000 00000101, 也就是-5

我们在总结一下|的运算规则:

对应的二进制有1,则为1,都为0,才为0

3.按位异或 ^

(1)详解异或操作符

我们来看这样一段代码

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

对于^这个操作,异或的计算是这样的,将每一个二进制数依次进行计算,相同为0,相异为1

所以计算过程为

也就是c的原码为10000000 00000000 00000000 00001000 ,而这个正好是-8

所以运行结果为

我们总结一下^的运算规律

对应的二进制序列,相同为0,相异为1

(2)一道经典的题目

题目描述:

不创建第三个变量,实现两个整数的交换

我们最容易想到的就是创建一个临时变量来进行交换,这样是可以交换,但是违背了题意,那么我们应该怎么做呢?

请看下面的代码

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

运行结果为

我们发现这段代码居然可以实现交换,那这又是为什么呢?

我们仔细分析一下这段代码,如下图所示

但是这种写法存在一种问题,如果a和b的值太大了,导致他们的和溢出了,但是他们本身是不溢出的。这样就会出现问题。所以这样写是存在局限性的。那么我们还有更好的办法吗?我们说当然由。别忘记我们正在讲解异或的知识,所以我们考虑用异或去解决这个问题

请看这段代码

#include<stdio.h>
int main()
{
  int a = 3;
  int b = 5;
  printf("%d %d\n", a, b);
  a = a ^ b;
  b = a ^ b;
  a = a ^ b;
  printf("%d %d\n", a, b);
  return 0;
}

运行结果为

可见可以实现交换,那么为什么可以进行交换呢?我们来详细分析一下

第一步操作中,a^b赋给了a

第二步操作中,用现在的a^b,我们将第一步的a代入得:(a^b)^b,也就是a^b^b,而异或是满足交换律,结合律的,式子中正好由两个b,所以两个b异或的结果正好就是0,而0^a的结果是什么呢?

异或的运算规则是依次比较对应二进制(补码),相异为1,相同为0。而二进制只有0和1这两个,0和0异或结果为0,0和1异或,结果为1。所以我们得出结论:0与任意数异或结果为该任意数,这一点似乎与小学数学中所学的1乘以任意数结果为该任意数有着异曲同工之妙。其实,看到这块,我们也可以试着推测一下1异或任意数的值是多少呢?我们知道1异或0等于1,1异或1等于0。这样我们变可以想到任意一个数异或1以后。它的二进制补码中的1变为0,0变为1。而这些计算出来的是补码,最终我们还要-1,然后符号位不变,其他按位取反。所以我们得出一个结论:1异或任意整数,若该任意数大于等于0且不等于1,则结果为该任意数+1;若该任意数等于1,则结果为0;若该任意数小于0,则结果为该任意数-1。这个结论读者可自行验证

因此有了上面的分析,我们可以得到a^b^b的结果就是a,所以第二步的操作实际上就是把b赋给了a

第三步的操作就与第二步的操作分析过程类似,结果就是将原来的a赋给了b。

当然我们上面讲了两种方式,但是要注意,在实际的应用中,以上两种方式交换都不如直接使用第三个变量合适。因为对于异或而言,它的可读性差,并且效率也不如使用第三个变量高,而且它只能针对整数进行交换。这里仅仅只是作为一种思维题考查。在实际应用中很少见。

五、赋值操作符

1.赋值操作符(=)

赋值操作符,是一个很常见的操作符,它可以修改我们以前不想要的一个值。

int weight=12;

weight=120;//直接进行赋值修改即可

同时赋值操作符也可以连续使用

int x=10;

int y=20;

int a=0;

a=x=y+1;

这个代码的意思就是,先将y+1的值赋给x,然后将x赋给a,这样写的话其实不利于我们进行调试,而且可读性差,所以我们一般都将其拆开

x=y+1;

a=x;

这样写就更加清晰了

2.复合赋值符

复合赋值符顾名思义,就是将赋值操作符和一些其他的操作符结合起来。由如下几种

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

举个例子,如下图所示,这些复合赋值符都是可以进行展开的,这样就可以实现改变我们原来的值,所有的操作符都是同理的

六、单目操作符

1.单目操作符的概念

什么是单目操作符呢?要想了解这个,我们先看下面的讲解

a + b 这个操作中,a和b是操作数,+是操作符。这个操作符其实是一个双目操作符,所谓双目操作符,就是有两个操作数。

那么根据这个我们可以得到,所谓单目操作符,就是只有一个操作数。

2.单目操作符的种类

有如下几种单目操作符

!           逻辑反操作    

-           负值                

+          正值

&          取地址

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

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

--          前置、后置--

++          前置、后置++

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

(类型)       强制类型转换

3.单目操作符详解

(1)!逻辑反操作

我们直接看这个代码

#include<stdio.h>
int main()
{
  int flag = 5;
  if (flag) 
  {
    printf("1\n");
  }
  if (!flag)
  {
    printf("2\n");
  }
  printf("%d\n", flag);
  printf("%d\n", !flag);
  printf("%d\n", !!flag);
  return 0;
}

运行结果为

第一次打印出来1是因为,只有0为假,非零为真

第二次打印出来5,是因为flag就是5

第三次打印出来0,是因为使用了!,导致5变成了假,也就是0

第四次打印出来1,是因为使用了两次!,使用一次变为0,第二次使用变为真,而这个真默认为1

除此之外,我们还有一点需要说明一下

就是布尔类型,在c语言中也有布尔类型,当然这个是在c99中引入的,所以很多编译器不支持,布尔类型其实是判断真假的类型

我们看这个代码

#include<stdio.h>
#include<stdbool.h>
int main()
{
  _Bool flag = true;
  if (flag)
  {
    printf("1");
  }
  return 0;
}

运行结果为

或者说这段代码,可以用来判断闰年

#include<stdio.h>
#include<stdbool.h>
_Bool is_leap_year(int y)
{
  if ((y % 4 == 0) && (y % 100 != 0) || (y % 400==0))
  {
    return true;
  }
  else
  {
    return false;
  }
}
int main()
{
  int y = 0;
  scanf("%d", &y);
  int ret=is_leap_year(y);
  printf("%d", ret);
}

当然,这个这个布尔类型除了可以使用_Bool以外,还可以使用bool来说明类型,两者是等价的

(2)+ -操作符

这两个操作符就很简单了,就是我们初中数学所学的运算。完全一致,这里给出一个例子

#include<stdio.h>
int main()
{
  int a = 10;
  printf("%d\n", a);
  printf("%d\n", +a);
  printf("%d\n", -a);
  return 0;
}

运行结果为

其实在这里,有一部分人会跟unsigned的用法混淆!这两个本应该是没有关系的两个东西,不过既然提到,在这里也提及一下

unsigned 是无符号整型

我们一般的类型前面其实应该加一个signed 意思是有符号类型,但是省略了

我们知道,在我们计算一个数的原码时候,第一位是符号位,这就是有符号类型的意思

而无符号类型,第一位是不作为符号位的。

也就是说,对于一个(unsigned int)10而言

它的原码是00000000 00000000 00000000 00001010,所以补码也是这个,而补码的第一位的零是不作为符号位,直接按补码转换成二进制来进行打印。

对于(unsigned int )-10而言

我们还是先写出它的原码

10000000 00000000 00000000 00001010

反码为

11111111 11111111 11111111 11110101

补码为

11111111 11111111 11111111 11110110

-10在内存中存储的是补码,由于第一位不是符号位,所以,直接按补码的大小直接转化为十进制就是它真正的值

代码为

#include<stdio.h>
int main()
{
  unsigned int a = 10;
  unsigned int b = -10;
  printf("%u\n%u", a, b);
  return 0;
}

注意:unsigned类型需要以%u来打印

结果为

(3)&取地址 *解引用

对于取地址操作符而言,一般都是对一个变量取地址的,这里给出代码,详细说明取地址的各种情形

#include<stdio.h>
int main()
{
  //对于变量
  int a = 10;
  int* p = &a;//取出a的地址,放入p这个指针中
  char ch = 'a';
  char* pc = &ch;//取出ch的地址
  char arr[10] = { 0 };
  char* p2 = arr;//取出数组首元素的地址
  char* p3 = &arr[0];//取出数组首元素的地址
  //对于常量而言
  char* p4 = "abcdef";//取出来的是a的地址
  printf("%c", *p4);
}

当然还有解引用操作符,如下代码所示,可以通过*去访问a,进而修改它的值

#include<stdio.h>
int main()
{
  int a = 10;
  int* p = &a;
  *p = 20;
  printf("%d", a);
  return 0;
}

(4)sizeof

首先sizeof是关键词也是操作符,这两个是不冲突的,不是必须选择一个的。是用来计算一个类型,一个变量的大小的

我们看一段代码

#include<stdio.h>
int main()
{
  int a = 0;
  printf("%d\n", sizeof(a));
  printf("%d\n", sizeof a );
  printf("%d\n", sizeof(int));
  return 0;
}

运行结果为

需要注意的是,sizeof计算一个变量的大小时,括号可以省略,但是计算类型的大小时,括号不可以省略。sizeof int 这种行为就是报错的,但是sizeof(int)不会

而且根据这一点我们还可以证明sizeof不是一个函数,因为我们sizeof一个变量时候,括号是可以省略的,而函数调用的时候,是不能省略括号的。

类似的,sizeof也可以对数组进行操作

#include<stdio.h>
int main()
{
  int arr[10] = {0};
  printf("%d\n", sizeof(arr));
  printf("%d\n", sizeof arr );
  printf("%d\n", sizeof(int [10]));
  return 0;
}

运行结果为

需要注意的是,int [10]是arr数组的类型,要知道,数组其实也是有类型的。这一点在后面还会提到

然后还需要注意的就是这一段代码

#include<stdio.h>
int main()
{
  int a = 5;
  short s = 3;
  printf("%d\n", sizeof(s = a + 3));
  printf("%d\n", s);
}

这段代码的运行结果为

第一个打印出2是因为假设里面的发生了,则a+3放到了s里面,s的大小是2

第二个打印出来的是3的原因是sizeof内部的表达式是不进行计算的!!!

而它不进行计算的原因是我们的程序由.c文件变成.exe可执行程序文件时候需要经过几个阶段

而sizeof是在编译阶段就已经计算出来了,所以内部的表达式不会进行计算

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

与计算反码不同的是,这个波浪号是使全部位都按位取反。而计算反码时符号位不会发生改变,当然这个也只能作用于整数。我们举一个例子,如下代码所示

#include<stdio.h>
int main()
{
  int a = 0;
  printf("%d", ~a);
  return 0;
}

运行计算过程为

运行结果为

当然在我们说到这块的时候,我们可以试着应用一下我们的位操作符做一个经典题目

如下图所示,这段代码中,想要使得a的二进制序列中,从右往左数第五个二进制数改为1,该如何实现呢?

这道题有很多人连思考都没有,就想要使用指针,这是一个经典的错误,标准呢的零分,因为指针操作的内存最小单位都是字节,而这个是一个比特位,要知道一个比特位是四个字节。所以使用指针是肯定不可以的。那么该如何解决呢?其实这是一个针对改变比特位的问题,我们自然而然就想到使用位操作符。而位操作符我们截至到目前总共学习了>> 右移<< 左移,&按位与,|按位或,^按位异或,以及~,二进制取反。这几种,而此时我们想要解决我们可以这样想,任何一个数或上1都会变成1,或上0还是原来的数,所以我们可以给这个二进制数或上一个只有第五个比特位是1的一个数,那个数又该如何获得呢?其实这里我们就可以使用左移操作符了,我们只要使用左移操作符,问题就迎刃而解了

#include<stdio.h>
int main()
{
  int a = 9;
  //00000000 00000000 00000000 00001001
  //00000000 00000000 00000000 00010000(可由1<<4得到)
  //00000000 00000000 00000000 00011001(按位或得到了目标)
  a |= (1 << 4);
  printf("%d", a);
  return 0;
}

运行结果为

那么问题来了,如果我们又想要吧第五位改回去该如何做呢?

其实我们思考一下,这个问题应该与前一个问题是对称的。所以我们推测,应该是需要使用&,而我们知道,任何一个数&0,都为0,按位与1,仍为原来的数,所以我们得到,这个数应该按位与一个只有第五位是0,其余全为1的二进制序列。那么这个该如何得到呢?其实我们不难想象,在上面中,我们是按位或一个第五位为1,其余全为0的数,而这个数不正好是我们现在需要的那个数的二进制刚好反了过来吗,由此我们得到,使用一个~操作符即可

#include<stdio.h>
int main()
{
  int a = 9;
  //00000000 00000000 00000000 00001001
  //00000000 00000000 00000000 00010000(可由1<<4得到)
  //00000000 00000000 00000000 00011001(按位或得到了目标)
  a |= (1 << 4);
  printf("%d\n", a);
  //00000000 00000000 00000000 00011001
  //11111111 11111111 11111111 11101111(由~(1<<4)得到)
  //00000000 00000000 00000000 00001001(按位与可以得到目标)
  a &= (~(1 << 4));
  printf("%d\n", a);
  return 0;
}

运行结果为

(6)++ --操作符

我们看这样一段代码

#include<stdio.h>
int main()
{
  int a = 0;
  int b = a++;//后置++,先使用后++
  printf("%d", a);
  printf("%d", b);
  return 0;
}

这个是一个后置++操作符,先使用后++,所以结果应该为11,10

而前置++,也是同理的,只不过他是先++,后使用

#include<stdio.h>
int main()
{
  int a = 0;
  int b = ++a;//后置++,先引用后使用
  //等价于 a = a + 1,b=a;
  printf("%d", a);
  printf("%d", b);
  return 0;
}

运行结果为两个11

还有一种场景是这样的

#include<stdio.h>
int main()
{
  int a = 10;
  printf("%d\n", a++);
  printf("%d\n", a);
  return 0;
}

此时的第一个打印是先使用后++。所以结果应该为10 11

如果换成前置++

#include<stdio.h>
int main()
{
  int a = 10;
  printf("%d\n", ++a);
  printf("%d\n", a);
  return 0;
}

则结果为 11  11

而如果在函数中使用的话,也是会有前置和后置的区别的

#include<stdio.h>
void test(int b)
{
  printf("%d\n", b);
}
int main()
{
  int a = 10;
  test(a++);
  test(++a);
  return 0;
}

结果为

以上就是++操作符的使用场景的总结,而--操作符和++操作符是完全类似的。我们可以看出++,--这些操作符是带有副作用的,使用之后会改变它自身的值

(7)强制类型转换

这个也是属于一种操作符,我们看一下使用场景

#include<stdio.h>
int main()
{
  int a = (int)3.14;
  return 0;
}

在这段代码中,我们知道a是一个整型,3.14是一个double类型,我们非要将3.14放到a中,那么就会发生强制类型转换

还有我们之前在三子棋小游戏,扫雷,猜数字小游戏中都使用到了一个srand和time函数

#include<stdio.h>
int main()
{
  srand((unsigned int)time(NULL));
  return 0;
}

这是因为,time 返回的是一个time_t类型的数据,而srand需要的是unsigned int类型的数据,所以需要强制类型转换

4.sizeof与数组的一个经典题目

#include <stdio.h>
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;
}

我们在第四站:数组时候就讲过,数组名一般情况都代表首元素地址,只有两种情况不是,一种是&数组名,这是整个数组的地址,另外一种就是sizeof(数组名),这计算出来的是整个数组的大小。

所以说我们(1)处打印的是4*10=40   (2)处打印的是10*1=10  

由于函数传一个数组名传的其实是地址,虽然它形参写的看起来是一个数组,但实际上是一个指针,所以(3)(4)计算的结果都是4/8,32位平台位4,64位平台为8

运行结果为


总结:

好了本节内容就先暂时讲解到这块,本篇篇幅已经较长了,已经讲解了1.3w字了。所以剩余内容放在后面的文章中继续讲解。本节主要讲解了算术操作符,计算机中二进制的表示,移位操作符,位操作符,赋值操作符,以及单目操作符,希望对大家有所帮助!

本站未完,欲知后事,请看下节

相关文章
|
3月前
|
存储 C语言 索引
【C语言篇】操作符详解(下篇)
如果某个操作数的类型在上⾯这个列表中排名靠后,那么⾸先要转换为另外⼀个操作数的类型后执⾏运算。
74 0
|
3月前
|
程序员 编译器 C语言
【C语言篇】操作符详解(上篇)
这是合法表达式,不会报错,但是通常达不到想要的结果, 即不是保证变量 j 的值在 i 和 k 之间。因为关系运算符是从左到右计算,所以实际执⾏的是下⾯的表达式。
257 0
|
29天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
35 10
|
1月前
|
存储 编译器 C语言
【C语言】简单介绍进制和操作符
【C语言】简单介绍进制和操作符
161 1
|
1月前
|
存储 编译器 C语言
初识C语言5——操作符详解
初识C语言5——操作符详解
174 0
|
3月前
|
C语言
C语言操作符(补充+面试)
C语言操作符(补充+面试)
45 6
|
3月前
|
存储 编译器 C语言
十一:《初学C语言》— 操作符详解(上)
【8月更文挑战第12天】本篇文章讲解了二进制与非二进制的转换;原码反码和补码;移位操作符及位操作符,并附上多个教学代码及代码练习示例
59 0
十一:《初学C语言》—  操作符详解(上)
|
4月前
|
C语言
五:《初学C语言》— 操作符
本篇文章主要讲解了关系操作符和逻辑操作符并附上了多个代码示例
44 1
五:《初学C语言》—  操作符
|
5月前
|
C语言
C语言逻辑操作符的短路问题
C语言逻辑操作符的短路问题
|
5月前
|
算法 C语言
【C语言】:巧用移位操作符,位操作符解决问题
【C语言】:巧用移位操作符,位操作符解决问题
35 0