S5——C操作符详解,你都知道吗? 下

简介: 讲解主要内容: 1. 各种操作符的介绍 2. 表达式求值 以下让我们开始正式重新认识和理解操作符吧!

7.  关系操作符

关系操作符

>

>=

<

<=        

!=        用于测试“不相等”

==        用于测试“相等”

注意:在编程的过程中==和=不小心写错,导致的错误

==        两个等于在C语言中才是数学中的相等(判断常量和变量相等,好的习惯是把常量写在右边)。

=        一个相等在C语言中是赋值(注:赋值是左边为变量)

代码实例:

//关系操作符
//==  用于测试“相等”
#include<stdio.h>
int main()
{
  int a = 0;
  if (0 == a)//判断常量与变量是否相等,好的习惯把变量放在右边
  {
    printf("%d\n", a);
  }
  if (a = 0)//如写在左边,少些一个等号不报错,一个等号是赋值
  {
    printf("%d\n", a);
  }
  return 0;
}

8.  逻辑操作符

逻辑操作符

&&     逻辑与(并且:参与运算的两个逻辑值都为真时,结果为真)

||        逻辑或(或者:参与运算的两个逻辑值都为假时,结果为假)

注意区分逻辑与(或)和按位与(或):

逻辑与(或)----->只关注真假

按位与(或)----->通过二进制计算得到

代码实例:

//逻辑操作符
//判断闰年的条件
//1、能被4整除,并且不能被100整除
//2、能被400整除
#include<stdio.h>
int main()
{
  int y = 2048;
  if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
  {
    printf("%d是闰年\n", y);
  }
  else
  {
    printf("%d不是闰年\n", y);
  }
  return 0;
}

补充:逻辑操作符的短路特性

&&操作符,左边为假,右边无需计算

||操作符,左边为真,右边就无需计算

代码实例:

//逻辑操作符的短路特性
#include<stdio.h>
int main()
{
  int a = 0;
  int i = 0;
  int b = 2;
  int c = 3;
  int d = 4;
  //代码1
  //&&操作符,左边为假,右边无需计算
  //i = a++ && ++b && d++;
  //代码2
  // ||操作符,左边为真,右边就无需计算
  i= a++ || ++b || d++;
  printf(" a=%d\n b=%d\n c=%d\n d=%d\n", a, b, c, d);
  return 0;
}

运行结果:

代码1:

代码2:

9.  条件操作符(三目操作符)

代码实例:

//条件操作符
#include<stdio.h>
int main()
{
  int a = 10;
  int b = 20;
  int max = 0;
  //代码1:使用选择语句,找两个数的较大值
  //if (a > b)
  //{
  //  max = a;
  //}
  //else
  //{
  //  max = b;
  //}
  //printf("max=%d\n", max);
  //代码2
  //转换为条件表达式,是怎么样的?
  //改用条件表达式实现找两个数的较大值
  max = (a > b ? a : b);
  printf("max=%d\n", max);
  return 0;
}

运行结果:

10.  逗号表达式

exp1,exp2,exp3,...expN

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

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

代码实例:

//逗号表达式
#include<stdio.h>
int main()
{
  int a = 1;
  int b = 2;
  int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式
  printf("%d\n", c);//13
  return 0;
}

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

1.  []下标引用操作符

操作数:一个数组名+一个索引值(下标)

代码实例:

//1、[]—下标引用操作符
//操作数:一个数组名+一个索引值(下标)
#include<stdio.h>
int main()
{
  int arr[10] = { 1,2,3,4,5 };
  printf("%d\n", arr[4]);// []—下标引用操作符,操作数是arr,4
  return 0;
}

2.  ()函数调用操作符

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

代码实例:

//2、()函数调用操作符
//接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
#include<stdio.h>
#include<string.h>//预处理,对strlen的声明
int main()
{
  int len = strlen("abcdef");//()就是函数调用操作符,操作数:strlen,“abcdef”
  printf("%d\n", len);//strlen函数是计算字符串长度的,len=6
  return 0;
}

3.  访问一个结构的成员

.        结构体变量.成员名

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

代码实例:

//结构体
//类型:内置类型和自定义类型
//内置类型:我们常用的int char short long ,long long float double
//自定义类型(聚合类型):结构体,枚举,联合体
//为什么要有自定义类型?
//生活中有些(复杂)对象要被描述的话,不能简单使用单个内置类型
//如书:书名,作者,定价……(用{}聚合在一起)
#include<stdio.h>
//结构体类型
struct Book
{
  char name[20];//书名
  char author[20];//作者
  double price;//定价
};
void print1(struct Book* p)
{
  printf(" %s %s %.2lf\n", (*p).name, (*p).author, (*p).price);
  printf(" %s %s %.2lf\n", p->name, p->author, p->price);
  //结构体指针->成员名
}
int main()
{
  struct Book b1 = { "数学","张三",66 };
  struct Book b2 = { "英语","李四",88 };
  //那结构体成员是怎么访问的呢?
  //结构体变量.成员名
  printf(" %s %s %.2lf\n", b1.name, b1.author, b1.price);
  printf(" %s %s %.2lf\n", b2.name, b2.author, b2.price);
  print1(&b1);//传址调用
  return 0;
}

运行结果:


12. 表达式求值

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

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


12.1  隐式类型转换

C的整型算术运算总是至少以缺省(缺省就是默认的意思)整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前转换为普通整型,这种转换称为整形提升。

整形提升的意义:

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

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

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


代码实例:

代码1:

//隐式类型转换
//C的整形算术运算总是至少以缺省整形类型的精度进行的
//为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整形提升
//整形提升是针对于类型小于整形的
//char short int long……
// 1  2   4
//代码1
#include<stdio.h>
int main()
{
  char a = 3;
  char b = 127;
  char c = a + b;
  //b和c的值被提升为普通整型,然后再执行加法运算。
  //加法运算完成之后,结果将被截断,然后再存储于a中
  printf("c=%d\n", c);
  return 0;
}

整形提升是针对于类型小于整形的,如char short类型操作数在使用之前转换为int型

代码2:

如何进行整形提升呢?

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

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

代码3:

//代码3
#include<stdio.h>
int main()
{
  //当前VS编译器char---->signed char
  char a = 3;
  //整形的二进制:00000000000000000000000000000011
  // 截断
  //char存储:00000011
  char b = 127;
  //整形的二进制:00000000000000000000000001111111
  //截断
  //char存储:01111111
  char c = a + b;
  //a-00000011
  //b-01111111
  //整形提升
  //a-00000000000000000000000000000011
  //b-00000000000000000000000001111111
  //a+b:00000000000000000000000010000010
  //截断
  //c-10000010
  printf("c=%d\n", c);
  //%d是打印十进制的整数
  //c-10000010
  //整形提升
  //补码:11111111111111111111111110000010
  //过程:11111111111111111111111110000001
  //原码:10000000000000000000000001111110
  return 0;
}

代码运行C是多少呢?

C=-126

为什么呢?那我们了解一下char的取值范围吧。


代码4:

//整形提升的例子
//整形提升是针对于类型小于整形的,如char short类型操作数在使用之前转换为int型
//代码4
#include<stdio.h>
int main()
{
  char a = 0xb6;
  short b = 0xb600;
  int c = 0xb6000000;
  if (a == 0xb6)//a-10110110要整形提升,变成负数,假
  {
    printf("a");
  }
  if (b == 0xb600)//b-1011011000000000要整形提升,变成负数,假
  {
    printf("b");
  }
  if (c == 0xb6000000)//c不整形提升,真
  {
    printf("c");
  }
  return 0;
}

代码5:

//代码5
#include<stdio.h>
int main()
{
  char c = 1;
  printf("%u\n", sizeof(c));
  printf("%u\n", sizeof(+c));//发生整形提升
  printf("%u\n", sizeof(-c));//发生整形提升
  return 0;
}

运行结果:

C只要参与表达式运算,就会发生整形提升。

12.2  算术转换

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

long double

double

float

unsigned long

intlong int

unsigned int

int

由低到高转换(从下向上)

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

注意:算术转换讨论的类型都是大于或等于int型的

警告:

但是算术转换要合理,要不然会有一些潜在的问题。(就高不就低,否则可能精度丢失)

例:

float f = 3.14;

int num = f;//隐式转换,会有精度丢失

总结:

低于int型的char,short为整形提升,等于或大于int型的为算术转换。(从低到高)

12.3 操作符的属性

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

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

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

操作符优先级

操作符 描述 用法示例 结合类型 结合性 是否控制求值顺序

() 聚组 (表达式) 与表达式同 N/A  否

() 函数调用 rexp(rexp,...,rexp) rexp L-R 否

 [] 下标引用 rexp[rexp] lexp L-R 否

 . 访问结构成员 lexp.member_name lexp L-R 否

 -> 访问结构指针成员 rexp->member_name lexp L-R 否

++ 后缀++ lexp++ rexp L-R 否

--            后缀--     lexp-- rexp L-R 否

! 逻辑反 !rexp rexp R-L 否

~ 按位取反 ~rexp rexp R-L 否

+ 单目,表示正值 +rexp rexp R-L 否

- 单目,表示负值 -rexp rexp R-L 否

++        前缀自增        ++lexp rexp R-L 否

--         前缀自减         --lexp         rexp R-L 否

*         间接访问         *rexp lexp R-L 否

& 取地址 &lexp rexp R-L 否

sizeof 取其长度,以字节表示        sizeof rexp sizeo(类型) rexp R-L 否

(类型 ) 类型转换 (类型)rexp rexp R-L 否

* 乘法 rexp*rexp rexp L-R 否

/ 除法 rexp/rexp  rexp L-R 否

% 整数取余 rexp%rexp rexp L-R 否

+ 加法 rexp+rexp rexp L-R 否

- 减法 rexp-rexp rexp L-R 否

<< 左移位 rexp<<rexp rexp L-R 否

>> 右移位 rexp>>rexp rexp L-R 否

> 大于 rexp>rexp rexp L-R 否

>= 大于等于 rexp>=rexp rexp L-R 否

< 小于 rexp<rexp rexp L-R 否

<= 小于等于 rexp<=rexp rexp L-R 否

== 等于 rexp==rexp rexp L-R 否

!= 不等于 rexp!=rexp rexp L-R 否

& 位于 rexp&rexp rexp L-R 否

^ 位异或 rexp^rexp rexp L-R 否

| 位或 rexp|rexp rexp L-R 否

&& 逻辑与 rexp&&rexp rexp L-R 是

|| 逻辑或 rexp||rexp rexp L-R 是

?: 条件操作符 rexp?rexp:rexp rexp N/A 是

= 赋值 lexp=rexp rexp R-L 否

+= 以...加 lexp+=rexp rexp R-L 否

-= 以...减 lexp-=rexp rexp R-L 否

*= 以...乘 lexp*=rexp rexp R-L 否

/= 以...除 lexp/=rexp rexp R-L 否

%= 以...取模 lexp%=rexp rexp R-L 否

<<= 以...左移 lexp<<=rexp rexp R-L 否

>>= 以...右移 lexp>>=rexp rexp R-L 否

&= 以...与 lexp&=rexp rexp R-L 否

^= 以...异或 lexp^=rexp rexp R-L 否

|= 以...或 lexp|=rexp rexp R-L 否

, 逗号 rexp,rexp rexp L-R 真


说明:N/A是没有的意思,R-L是从右向左

从上到下优先级变低。

小结:

操作符的属性

1.首先确定优先级,相邻操作符按照优先级高低计算(相邻操作符才讨论优先级)
2.优先级相同的情况下,结合性才起作用。

3.注意是否控制求值顺序(只有四个:&&  ||  (?:)  ,)


那知道了操作符的属性是否就能确定计算的唯一路径呢?

一些问题表达式:

1.


2.

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

3.

这个代码是有问题的。

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

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

函数的调用先后顺序无法通过操作符的优先级确定。

4.



看看同样的代码产生了不同的结果,这是为什么?

简单看一下汇编代码.就可以分析清楚.

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


总结:


我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。



相关文章
|
8月前
|
编译器 C语言 索引
操作符详解下(非常详细)
操作符详解下(非常详细)
30 0
|
4天前
|
编译器 Linux C语言
操作符详解(2)
操作符详解(2)
14 0
|
28天前
|
存储 编译器
操作符详解1(一)
本文介绍了编程中的操作符分类和位操作,包括算术操作符(如 +, -, *, /, %)、移位操作符(<<, >>)、位操作符(&, |, ^)、赋值操作符、单目操作符、关系操作符、逻辑操作符和条件操作符。还讨论了原码、反码和补码的概念,以及左移(<<)和右移(>>)操作符的工作原理,包括逻辑右移和算术右移的区别。最后提到了位操作符按位与(&)、按位或(|)、按位异或(^)和按位取反(~)的运算规则。
22 1
|
11天前
|
索引
操作符,详细讲解
操作符,详细讲解
|
28天前
|
编译器 C语言 C++
操作符详解2(二)
本文介绍了编程中的操作符转换和表达式解析规则。当操作数类型不同时,会进行寻常算术转换,按照long double、double、float等类型顺序进行向上转换。表达式求值时,虽然操作符有优先级,但不能决定操作数的求值顺序,例如`a*b + c*d + e+f`中乘法先于加法,但具体计算顺序不确定,可能导致不同结果。同样,如`c++ + c--`这样的表达式也是有歧义的,因为++和--的左右结合性不能确定操作数的获取顺序。文章强调,复杂的表达式可能因编译器差异产生不同结果,应避免使用可能导致非唯一计算路径的表达式以减少潜在风险。
22 0
|
28天前
|
存储 C语言
操作符详解2(一)
C语言操作符具有优先级和结合性,决定表达式求值顺序。优先级高的运算符先执行,相同优先级时看结合性,左结合从左到右,右结合从右到左。例如,3+4*5中乘法优先级高于加法,5*6/2中乘法和除法优先级相同,但都是左结合,所以先计算5*6。圆括号具有最高优先级,可改变其他运算符优先级。表达式求值时,整型运算以缺省整型精度进行,小整型值会被提升为int或unsigned int。正数提升时高位补0,负数提升时高位补1,无符号整型提升始终补0。
29 0
|
1月前
|
编译器 索引
操作符详解3
操作符详解3
21 0
|
6月前
|
存储 编译器
操作符详解(1)
操作符详解(1)
27 0
|
10月前
操作符详解(一)
操作符详解(一)
62 0
|
11月前
|
C语言 索引
详解操作符(中)
详解操作符(中)