猿创征文|C语言操作符(运算符)一万多字的详细解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 猿创征文|C语言操作符(运算符)一万多字的详细解析

前言

本文章详细地和各位小伙伴展示了我对C语言操作符的理解,欢迎大家一起讨论!

一、操作符分类

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

二、算术操作符

+         -         *        /        %

1.除了%操作符之外,其他的几个操作符可以作用于整数和浮点数。

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

#include<stdio.h>
void main() {
  //下面两个表达式都是以整数为操作数
  int a = 10 / 3;
  double b = 10 / 3;
  //一个操作数为小数形式
  double c = 10.0 / 3;
  //两个操作数都为小数形式
  double d = 10.0 / 3.0;
  printf("a=%d,b=%f,c=%f,d=%f", a, b, c, d);
}

注意:不管最终接收的数据是int型,还是float型,计算结果的值都是由操作数本身的数据类型来控制的。

3.%操作符的两个操作数必须为整数。返回的是整数之后的余数

int main()
{
  int a = 7 % 3;//余1
  printf("%d\n", a);
  return 0;
}

三、移位操作符

<<  左移操作符

>>  右移操作符

注意:移位操作符的操作数只能是整数(因为是转化成二进制进行移位操作)

1.左移操作符

#include<stdio.h>
void main() {
  int a = 2;
  int b = a << 1;
  printf("a=%d,b=%d", a, b);
}

想要了解此操作符的意义,那我们就必须将十进制2转化为对应的int的二进制数

①十进制数2对应的二进制数:00000000000000000000000000000010(32位)

②a<<1表示将此二进制数的所有位都向移动一位,左边丢弃,右边补0。也就得到

00000000000000000000000000000100(32位)也就是提升了此数的一个权位,来到了2^2部分,所以左移操作符相当于该数乘以2。

注意:虽然说a是进行了左移,但不意味着a的值有变化,只是将a进行左移后的值赋值给b,他本身没有发生变化。

③还有如下代码证明

2.右移操作符

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

①逻辑移位:左边用0填充,右边丢弃。

②算术移位:左边用原该值的符号位填充,右边丢弃。

例子1:以正整数为例

首先由于这个4是正整数,所以无论是进行逻辑移位,还是算术移位,都是在往左边补0

①十进制正整数4的二进制数为:00000000000000000000000000000100(32位)

②右移之后就变成了:00000000000000000000000000000010(32位)

③从上面的例子不难总结右移操作符就是在原来的数除以2。

例子2:以负整数为例

首先我们得知道负数是以补码的形式进行存储的,那么怎么将一个数从原码转化成补码呢?转换顺序为:原码->反码->补码,如下所示:

(1)原码:直接根据数值写出的二进制序列就是原码。

(2)反码:原码的符号位不变,其他位按位取反就是反码。

(3)补码:反码+1,就是补码。

注意:正整数的补码就是他的原码

我们知道了原码怎么转变为补码后,就来推推这个列子

①十进制数的-1的二进制原码是:10000000000000000000000000000001(第一位的数值表示正负号,0表示正,1表示负)。

②对应的反码:11111111111111111111111111111110。

③对应的补码:11111111111111111111111111111111。

④对11111111111111111111111111111111进行右移一位(算术右移)得到的的二进制数为:11111111111111111111111111111111,那么该值也是为-1的补码。

注意:内存里是以补码形式存储,但是结果是以原码形式展现的。

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

四、位操作符

位操作符有:

&  按位与

|   按位或

^  按位异或

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

1.&按位与

按位与规则:只有两个对应位置的数都为1,结果才为1;如果有是0对1或者0对0,结果都为0,包括符号位也要变化。(有点像逻辑操作符的&&)

例子1:以正整数和正整数为例

①我们得知道 3,5所对应的补码,如上所示,

②然后按照对应的规则进行按位与&

例子2:以负整数和正整数为例

#include<stdio.h>
void main() {
  int a = -2;
  int b = 3;
  int c = a & b;
//-2原码:10000000000000000000000000000010
//-2反码:11111111111111111111111111111101
//-2补码     11111111111111111111111111111110
//3的补码  00000000000000000000000000000011
//-2 & 3 为:00000000000000000000000000000010(结果为2)
  printf("c=%d", c);
}

①还是一样得知道-2的补码,3的补码。

②然后按照对应的规则进行按位与&。

例子3:以负整数和负整数为例

#include<stdio.h>
void main() {
  int a = -1;
  int b = -2;
  int c = a & b;
  //-1的补码:11111111111111111111111111111111
  //-2的补码:11111111111111111111111111111110
  //-1&-2:11111111111111111111111111111110(记为x)
  //x的反码:11111111111111111111111111111101
  //x的原码:10000000000000000000000000000010(为-2)
  printf("c=%d", c);
}

①首先得知道-2,-1的补码

② 按照对应的规则进行按位与&,结果为-2

2. | 按位或

按位或规则:只要对应的位置有一个1,此位置生成的数也是1,如0对1或者1对1,结果都为1;如果对应的是0对0,那么结果就是0,包括符号位也要变化。(有点像逻辑操作符的||)

例子1:以正整数和正整数为例

①知道5,3的补码

②按照按位或的规则进行运算,得出结果为7

例子2:以负整数和正整数为例

#include<stdio.h>
void main() {
  int a = -2;
  int b = 3;
  int c = a | b;
//-2原码:10000000000000000000000000000010
//-2反码:11111111111111111111111111111101
//-2补码     11111111111111111111111111111110
//3的补码  00000000000000000000000000000011
//-2 | 3 为:11111111111111111111111111111111(记为x)
//x的原码为:
//11111111111111111111111111111110(反码)
//10000000000000000000000000000001(原码)结果为-1
  printf("c=%d", c);
}

①首先得知道-2的补码和3的补码

②然后按照按位或的规则进行运算,得出结果为-1

例子3:以负整数和负整数例子

#include<stdio.h>
void main() {
  int a = -1;
  int b = -2;
  int c = a | b;
  //-1的补码:11111111111111111111111111111111
  //-2的补码:11111111111111111111111111111110
  //-1|-2:11111111111111111111111111111111(记为x)
  //x的反码:11111111111111111111111111111110
  //x的原码:10000000000000000000000000000001(为-1)
  printf("c=%d", c);
}

①的指导-1和-2的补码

②然后按照按位或的规则进行运算,得出结果为-1

3.^按位异或

按位异或规则:按(2进制)位异或,对应的二进制位进行异或,相同为0,相异为1,包括符号位也要变化。

例子1:以正整数和正整数为例

例子2:以正整数和负整数为例

例子3:以负整数和负整数为例

4.位操作符的练习题

例1:不能创建临时变量(第三个变量),实现两个数的交换

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

其实我们还可以从另一个角度去理解:首先我们的指导0^任何数的值都为该数的值;一个数去^自己本身的值为0,例子如下:

那么a ^ b ^ b可以理解为:b ^ b == 0,a ^ 0 = a,那么b = a(此时b的值就为a的值了,完成了交换)

解决两个数值交换的方式还有很多,如下:

int main()
{
    int a = 3;
    int b = 5;
  //交换
    printf("a = %d b = %d\n", a, b);
  //数值太大会溢出
  a = a + b;
  b = a - b;
  a = a - b;
  printf("a = %d b = %d\n", a, b);
  int c = 0;//空瓶
  printf("a = %d b = %d\n", a, b);
  c = a;
  a = b;
  b = c;
  printf("a = %d b = %d\n", a, b);
  return 0;
}

例2:编写代码实现求一个整数存储在内存中的二进制中1的个数

方法一:运用了位操作符和移位操作符

思路如下图:

代码如下:

void main() {
  int a = 5;
  int count = 0;
  int i;
  for (i = 0;i < 32;i++) {
    if (a & (1 << i)) {
      count++;
    }
  }
  printf("%d", count);//结果为2
}

方法二:利用十进制转二进制的思路,用余数来记录1的个数

void main() {
  int a = 5;
  int count = 0;
  int i;
  while (a) {
    if (a % 2 == 1) {
      count++;
    }
    a /= 2;
  }
  printf("%d", count);//结果为2
}

方法三:采用相邻的两个数据进行按位与运算

#include<stdio.h>
void main() {
  int a = 5;
  int count = 0;
  while (a) {
    a = a & (a - 1);
    count++;
  }
  printf("%d", count);//结果为2
}

五、赋值操作符

赋值操作符是一个很好用的操作符,他可以让你重新对一个变量进行赋值。(赋值操作符为“=”)

void main() {
  int weight = 120;//体重
  weight = 100;//使用赋值操作符重新赋值
  double salary = 10000.0;//money
  salary = 200000.0;//使用赋值操作符重新赋值
}
int a = 10;
int x = 0;
int y = 20;
//连续赋值,一般不会这么用,影响了可读性
a = x = y + 1;

1.复合赋值符

  • +=:a += b,a与b的和赋值给a
  • -=:a -= b,a与b的差赋值给a
  • *=:a *= b,a与b的积赋值给a
  • /=:a /= b,a与b的商赋值给a
  • %=:a %= b,a与b的余数赋值给a
  • >>=:a >>= b,a右移b个位后的值赋值给a
  • <<=:a <<= b,a左移b个位后的值赋值给a
  • &=:a &= b,a与b按位与后的值赋值给a
  • |=:a |= b,a与b按位或后的值赋值给a
  • ^=:a ^= b,a与b按位异或后的值赋值给a

这些操作符都可以达到复合的效果!

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

六、单目操作符

1.单目操作符介绍

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

2.!逻辑反操作符

int main()
{
  int flag = 0;
  printf("%d\n", !flag);
  //flag为真,打印hehe
  if (flag)
  {
    printf("hehe\n");
  }
  //flag为假,打印haha
  if (!flag)
  {
    printf("haha\n");
  }
  return 0;
}

3.sizeof操作符

3.1 sizeof操作符的注意事项

sizeof可以用来求变量(类型)所占的空间大小

#include<stdio.h>
void main() {
  short s = 5;
  int a = 10;
  printf("%d\n", sizeof(s = a + 2));
  printf("%d\n", s);
  /*
    sizeof的执行是发生在编译时期,而a + 2的运算是发生在运行时期。
    所以这里的sizeof是发生在a + 2之前的,所以不会有类型提升的说法,
    同时不管你类型是否提升,最终都是放到short型里,所以都是short型。
        sizeof在编译期就结束了,sizeof只想知道你的类型是什么,它不管你
        是否有进行了运算,就直接结束了。
  */
  int arr[10] = { 0 };
  printf("%d\n", sizeof(arr));//单位是字节
  printf("%d\n", sizeof(int[10]));//40 - int [10]是arr数组的类型
  printf("%d\n", sizeof(a));//计算a所占空间的大小,单位是字节
  printf("%d\n", sizeof(int));
  printf("%d\n", sizeof a);//说明了sizeof是一个操作符而不是一个函数
}

编译结果如下:

3.2 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;
}

问:(1)(2)(3)(4)分别是输出什么?答案如下:

位置(1):sizeof arr就是输出int arr[10]的数组长度,也就是40个字节。

位置(2):sizeof(ch)就是输出char ch[10]的数组长度,也就是10个字节。

位置(3):首先我们得知道,传到函数test1()里面的实参虽然是arr,但是它的本质其实是一个指针,也就是int* arr,所以test1()函数里面的sizeof(arr)就是输出int的大小,也就是4个字节。

位置(4):同理于位置(3),也就是输出指针char* ch的大小,也就是4个字节。

注意:无论是什么类型的,他的地址长度是看操作位数决定的32位就是4个字节,64位就是8个字节。

4.~按位取反

int main()
{
  int a = -1;
  //10000000000000000000000000000001 - 原码
  //11111111111111111111111111111110 - 反码
  //11111111111111111111111111111111 - 补码
  //~ 按位取反
  //11111111111111111111111111111111
  //00000000000000000000000000000000
  //
  int b = ~a;
  printf("%d\n", a);
  printf("%d\n", b);
  return 0;
}

按位取反操作符是连同符号位也会进行取反。

5.自加自减举例

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

注意:后置++或者后置--,都是等到整条语句结束的时候才进行自加或自减。

6.&取地址操作符和*解引用操作符

int main()
{
  int a = 10;
  printf("%p\n", &a);//& - 取地址操作符
  int * pa = &a;//pa是用来存放地址的 - pa就是一个指针变量
  *pa = 20;//* - 解引用操作符 - 间接访问操作符
  printf("%d\n", a);//20
  return 0;
}

七、关系操作符

  • >
  • >=
  • <
  • <=
  • !=        用于测试“不相等”
  • ==       用于测试“相等”

这些关系运算符比较简单,没什么可细述,注意别在编程中把==和=混淆就行。

八、逻辑操作符

逻辑操作符有哪些:

  • &&        逻辑与
  • ||           逻辑或

注意:区分逻辑与和按位与;区分逻辑或和按位或。

1&2----->0

1&&2---->1

1|2----->3

1||2---->1

例题:360笔试题

#include <stdio.h>
int main()
{
    int i = 0,a=0,b=2,c =3,d=4;
    i = a++ && ++b && d++;
    //i = a++||++b||d++;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0; }
//程序输出的结果是什么?

逻辑与和逻辑或都有“短路”的效果。

当逻辑与的左边第一个为假,那么右边就不会执行了,直接结束

当逻辑或的左边第一个为真,那么右边就不会执行了,直接结束

按照上面的思路:结果为

a = 1

b = 2

c = 3

d = 4

九、条件操作符

条件操作符也称为三目运算符

结构:exp1 ? exp2 : exp3

int main()
{
  int a = 3;
  int b = 0;
  if (a > 5)
    b = 1;
  else
    b = -1;
  //三目操作符
  b = (a > 5 ? 1 : -1);
  return 0;
}

十、逗号表达式

结构:exp1, exp2, exp3, …expN

逗号表达式,就是用逗号隔开的多个表达式。

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

int main()
{
  int a = 3;
  int b = 5;
  int c = 0;
  //逗号表达式 - 要从做向右依次计算,但是整个表达式的结果是最后一个表达式的结果
  int d = (c = 1, a = c + 3, b = a - 4, c += b);
          //c=10  a=8         b=4    
  printf("%d\n", d);//结果为10,也就是c的值
  return 0;
}

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

1.[ ]下标引用操作符

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

int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  //              0 1     4
  printf("%d\n", arr[4]);//[] - 就是下标引用操作符
  //[] 的操作数是2个:arr , 4
  return 0;
}

2.( )函数调用操作符

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

//函数的定义
int Add(int x, int y)
{
  return x + y;
}
void test()
{}
int main()
{
  int a = 10;
  int b = 20;
  //函数调用
  int ret = Add(a, b);//() - 函数调用操作符
  test();
  return 0;
}

3.访问一个结构体成员

.         结构体.成员名

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

struct Book
{
  //结构体的成员(变量)
  char name[20];
  char id[20];
  int price;
};
int main()
{
  //int num = 10;
  //结构体变量名.成员名
  struct Book b = {"C语言", "C20210509", 55};
  struct Book * pb = &b;
  //结构体指针->成员名
  printf("书名:%s\n", pb->name);
  printf("书号:%s\n", pb->id);
  printf("定价:%d\n", pb->price);
    //利用*解引用知道结构体变量b,在用点操作符去访问结构体成员
  printf("书名:%s\n", (*pb).name);
  printf("书号:%s\n", (*pb).id);
  printf("定价:%d\n", (*pb).price);
    //直接用结构体变量b加点操作符去访问结构体成员
  printf("书名:%s\n", b.name);
  printf("书号:%s\n", b.id);
  printf("定价:%d\n", b.price);
  return 0;
}

十二、表达式求值

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

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

1.隐式类型转换

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

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

整型提升的意义:

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

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

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

例子1:

int main()
{
  char a = 3;
  //00000000000000000000000000000011
  //00000011 - a
  char b = 127;
  //00000000000000000000000001111111
  //01111111 - b
  char c = a + b;
  //00000000000000000000000000000011
  //00000000000000000000000001111111
  //00000000000000000000000010000010
  //10000010 - c,此数就是将原来的值截断
    //%d是按int类型输出,所以这里又是类型提升
  printf("%d\n", c); //-126
    //11111111111111111111111110000010 - 补码
  //11111111111111111111111110000001 - 反码
  //10000000000000000000000001111110 - 原码
  //-126
  //发现a和b都是char类型的,都没有达到一个int的大小
  //这里就会发生整形提升
  return 0;
}

这里因为%d要按照int类型输出,所以就发生了隐式类型提升,符号位是什么就往左补符号位相对应的数。也就是整型提升是按照变量的数据类型的符号位来提升。

例子2:

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

例子3:

//实例1
int main()
{
 char a = 0xb6;
 short b = 0xb600;
 int c = 0xb6000000;
 if(a==0xb6)
 printf("a");
 if(b==0xb600)
 printf("b");
 if(c==0xb6000000)
 printf("c");
 return 0; }

例子3中的a,b要进行整型提升,但是c不需要整型提升

a,b整型提升之后,变成了负数,所以表达式a==0xb6,b==0x600的结果为假,但是c不发生整型提升,则表达式c==0x6000000的结果为正

结果输出c

例子4:

(一)

(二)

首先我们得知道一个运算表达式他是具有两个属性,分别是值属性和类型属性。

值属性:是通过运算后得到的值,可以理解为是在运行时期发生。

类型属性:是通过推断后得到的,可以理解为在编译时期就发生了。

(一)c本身没进行运算,所以结果为1;第2、第3和第4条输出语句中都有运算表达式,所以发生了类型属性推断,也就是隐式类型转换。(在gcc环境中,第四条语句的结果为4,vs的编译环境不太对!所以为1)

(二)首先我们知道了sizeof的类型判断和运算表达式的类型推断都是发生在编译期,所以我们就可以分为两部分来看

①先看s = a + 3,不管它在运算时怎么类型转换,最终都是short型,那么此运算表达式的类型推断为short型。

②sizeof得知了s = a + 3的类型推断为short型,那么它的值也就是2。

2.算术转换

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

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

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。但是算术转换要合理,要不然会有一些潜在的问题。

3.操作符的属性

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

(1)操作符的优先级

(2)操作符的结合性

(3)是否控制求值顺序

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

操作符的优先级如下:

操作符的优先级

操作符 描述 结合性 是否控制求值顺序
() 聚组 N/A
() 函数调用 L-R
[ ] 下标引用 L-R
. 访问结构体成员 L-R
-> 访问结构指针成员 L-R
++ 后缀自增 L-R
-- 后缀自减 L-R
逻辑反 L-R
~ 按位取反 R-L
+ 单目,表示正值 R-L
- 单目,表示负值 R-L
++ 前缀自增 R-L
-- 前缀自减 R-L
* 间接访问(解引用) R-L
& 取地址 R-L
sizeof 取其长度,以字节表示 R-L
(类型) 类型转换 R-L
* 乘法 L-R
/ 除法 L-R
% 整数取余 L-R
+ 加法 L-R
- 减法 L-R
<< 左移位

L-R

>> 右移位 L-R
> 大于 L-R
>= 大于等于 L-R
< 小于 L-R
<= 小于等于 L-R
== 等于 L-R
!= 不等于

L-R

& 按位与 L-R
^ 按位异或 L-R
| 按位或 L-R
&& 逻辑与 L-R
|| 逻辑或 L-R
?: 条件操作符

N/A

= 赋值 R-L
+= 加后赋值 R-L
-= 减后赋值 R-L
*= 乘后赋值 R-L
/= 除后赋值 R-L
%= 取余后赋值 R-L
<<= 左移后赋值 R-L
>>= 右移后赋值 R-L
&= 按位与后赋值 R-L
^= 按位异或后赋值 R-L
|= 按位或后赋值 R-L
逗号 L-R

总结:

(1)单(目)>算(术)>关(系)>逻(辑)>赋(值)

(2)单目运算符和赋值运算符:自右至左结合

(3)其它的:自左到右

4.一些问题表达式

例子1:

//表达式的求值部分由操作符的优先级决定。
//表达式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:

//表达式2 
c + --c;

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

例子3:

//代码3-非法表达式
int main()
{
    int i = 10;
    i = i-- - --i * ( i = -3 ) * i++ + ++i;
    printf("i = %d\n", i);
    return 0; 
}

例子3在不同的编译器中测试结果:非法表达式程序的结果

编译器
-128 Tandy 6000 Xenix3.2
-95 Think C 5.02(Macintosh)
-86 IBM PowerPC AIX 3.2.5
-85 Sun Sparc cc(K&C编译器)
-63 gcc
4 Sun Sparc acc(K&C编译器)
21 Turbo C/C++ 4.5
42 Microsoft C 5.1

例子4:

//代码4
int fun()
{
     static int count = 1;
     return ++count; 
}
int main()
{
     int answer;
     answer = fun() - fun() * fun();
     printf( "%d\n", answer);//输出多少?
     return 0; 
}

有问题!

虽然在大多数的编译器上求得结果都是相同的。

但是上述代码answer = fun() - fun() * fun();中我们只能通过操作符的优先级得知:先算乘法,再算减法。

其实也可以换个思路来理解:函数调用操作符()的优先级高于算术操作符(也叫算术运算符),并且他的操作顺序是从左往右的,等函数调用操作符运算完成之后,才开始算术运算符的操作。显然这并不是我们想要的执行顺序,我们的思路和计算机的运算规律产生了歧义。

例子5:

//代码5
#include <stdio.h>
int main()
{
    int i = 1;
    int ret = (++i) + (++i) + (++i);
    printf("%d\n", ret);
    printf("%d\n", i);
    return 0; 
}
//尝试在linux 环境gcc编译器,VS2019环境下都执行,看结果。

VS2013的环境下:

有的小伙伴就感觉奇怪了,为什么不是2+3+4=9呢?而是4+4+4=12呢?

(1)这就需要我们去了解一下汇编语言,首先得知道计算机有许多寄存器(读取效率:寄存器>内存>磁盘)(寄存器有:eax、ebx、ecx、edx、ebp、esp等等)。

(2)用vs2019进行代码调试,在ret变量那里打开反汇编,就可以看到此例子第2张图片的界面,我们可以发现i的值是寄存到ecx寄存器中的,由寄存器去帮我们进行运算,最终得到一个值,也就是说他是执行了3次++i后才进行的算术运算,所以结果就为12。

(3)可以理解为变量i是从寄存器ecx中取值的步骤,而ecx只有一个且是统一执行到结束,里面存放的结果就是4,所以所有的i都为4。

在调试的状态下,对变量ret右键,就可以看到有转到反汇编的指令,点击就行了。

呼!12340个字,终于写完了,重新对操作符进行学习,加深了我对C语言操作符的认识,感觉还挺有趣的!

点个赞呗!

相关文章
|
16天前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
86 14
|
20天前
|
存储 编译器 C语言
【C语言】数据类型全解析:编程效率提升的秘诀
在C语言中,合理选择和使用数据类型是编程的关键。通过深入理解基本数据类型和派生数据类型,掌握类型限定符和扩展技巧,可以编写出高效、稳定、可维护的代码。无论是在普通应用还是嵌入式系统中,数据类型的合理使用都能显著提升程序的性能和可靠性。
40 8
|
20天前
|
存储 算法 C语言
【C语言】深入浅出:C语言链表的全面解析
链表是一种重要的基础数据结构,适用于频繁的插入和删除操作。通过本篇详细讲解了单链表、双向链表和循环链表的概念和实现,以及各类常用操作的示例代码。掌握链表的使用对于理解更复杂的数据结构和算法具有重要意义。
153 6
|
20天前
|
存储 网络协议 算法
【C语言】进制转换无难事:二进制、十进制、八进制与十六进制的全解析与实例
进制转换是计算机编程中常见的操作。在C语言中,了解如何在不同进制之间转换数据对于处理和显示数据非常重要。本文将详细介绍如何在二进制、十进制、八进制和十六进制之间进行转换。
29 5
|
20天前
|
C语言 开发者
【C语言】断言函数 -《深入解析C语言调试利器 !》
断言(assert)是一种调试工具,用于在程序运行时检查某些条件是否成立。如果条件不成立,断言会触发错误,并通常会终止程序的执行。断言有助于在开发和测试阶段捕捉逻辑错误。
28 5
|
存储 C语言
如何深入掌握C语言操作符及表达式求值(详解)(三)
本文章主要讲解点: ​​​​​​​各种操作符的介绍 表达式求值
如何深入掌握C语言操作符及表达式求值(详解)(三)
|
C语言
如何深入掌握C语言操作符及表达式求值(详解)(一)
本文章主要讲解点: ​​​​​​​各种操作符的介绍 表达式求值
如何深入掌握C语言操作符及表达式求值(详解)(一)
|
C语言 索引
如何深入掌握C语言操作符及表达式求值(详解)(二)
本文章主要讲解点: ​​​​​​​各种操作符的介绍 表达式求值
|
20天前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
40 10
|
20天前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
42 9

推荐镜像

更多