学C的第十六天【操作符详解:9. 条件操作符;10. 逗号表达式;11. 下标引用,函数调用和结构函数;12.表达式求值:整型提升、算术转换、操作符的属性;练习:使用函数完成整型函数的打印、元素逆置】-2

简介: 12.表达式求值 1. 表达式求值的顺序一部分是由操作符的优先级和结合性决定。 2. 有些表达式的操作数在求值的过程中可能需要转换为其它类型。

12.表达式求值

               

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

           

2. 有些表达式的操作数求值的过程中可能需要转换为其它类型

12.1 : 隐式类型转换(整型提升)

                       

1. C语言的整型算术运算总是至少以缺省(默认)整型类型的精度来进行的。

2. 为了获得这个精度表达式中的字符(char)和短整型(short)操作数使用之前被转换为普通整型char1字节int4字节,这种转换称为整型提升,这里的转换只是计算时临时转换一下,变量本身不会转换类型。

整型提升的意义:

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

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

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

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

               

(一个有符号的数字,在二进制序列里面就把最高位规定为符号位

//整型提升
#include <stdio.h>
int main()
{
  // 存放时截断,使用时整型提升
  char c1 = 5; 
  //00000000000000000000000000000101 -- 整型5的32为bit位(四个字节:32位)
  //00000101 -- char类型5的8为bit位(一个字节:8位)(截断)
  //00000101 -- c1
  char c2 = 127;
  //00000000000000000000000001111111 - 整型
  //01111111 -- c2
  char c3 = c1 + c2;
  //计算时要整型提升:整型提升是按照变量的数据类型的符号位来提升的
  //char类型是有符号的char,此时高位就是它的符号位,为 0,所以 补 0 到 32位,
  // 
  //00000101 --》00000000000000000000000000000101
  //              相加
  //01111111 --》00000000000000000000000001111111
  //      从 char 变成 int
  //得      00000000000000000000000010000100 -- 32位
  // 
  // 要把32位放进8位得char中,再进行截断
  //00000000000000000000000010000100 --》10000100
  //所以 c3 = 10000100
  //    这时打印 c3 ,会打印 -124
  //原因:
  //  %d - 以10进制的形式打印有符号的整数
  // 因为 c3 是 char 类型的,不是 %d 要的整型,所以这里也要进行整型提升
  // c3 = 10000100 ,char类型是有符号的char,此时高位就是它的符号位,为 1 
  // 所以 补 1 到 32位,
  //  (无符号整形提升,直接高位补0)
  // 10000100 --》
  // 11111111111111111111111110000100 -- 补码
  // 11111111111111111111111110000011 -- 反码(补码 - 1)
  // 10000000000000000000000001111100 -- 原码(反码按位取反)
   //       得 打印时得 -124
  printf("%d\n", c3);
  return 0;
}

09639eccaf46481db84cd53b83947902.png

(一定程度上)证明 整形提升 的存在

                (有符号数字和无符号数字

10010100,如果这是有符号数字最高位就是符号位不是有效数字

如果这是无符号数字最高位就是有效数字表示 2的7次方

截断

以char类型为例,截断会直接获取二进制序列最低的8位,即一个字节,其它位全部丢弃)

             

//证明 整形提升:
#include <stdio.h>
//%d  - 以10进制的形式打印有符号的整数
//%u  - 以10进制的形式打印无符号的整数
int main()
{
  char c = 1;
  printf("%u\n", sizeof(c));
  printf("%u\n", sizeof(+c));
  printf("%u\n", sizeof(-c));
  return 0;
}

1373aaa8d4ce445ca4d34d448f054c37.png

12.2 :算术转换


1. 如果某个操作符的各个操作数属于不同的类型(>=4个字节),那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换(向上转换),这里的转换只是计算时临时转换一下,变量本身不会转换类型。2. 如果某个操作数的类型在下面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算

           

3. 警告: 算术转换要合理,要不然会有一些潜在的问题,如精度丢失。            

5b2855bfd4e64039916abcabd1299d9e.png

12.3 :操作符的属性

           

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

1. 操作符优先级

2. 操作符结合性

3. 是否控制求值顺序

           

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

操作符优先级

(图中从高到下,优先级从高到低


操作符

描述

用法示例

结果类型

结合性

是否控制求值顺序

()

聚组

(表达式)

与表达式同

N/A

()

函数调用

rexprexp...,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 sizeof(

)

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


(运用这些操作符时,要尽量写得通俗易懂,可以使用括号来确定计算顺序,形成唯一路径,避免写成垃圾代码,不同编译器会有不同结果)

查看汇编语言观察垃圾代码的运行

//垃圾代码:
#include <stdio.h>
int main()
{
  int i = 1;
  int ret = (++i) + (++i) + (++i);
  printf("%d\n", ret);
  printf("%d\n", i);
  return 0;
}

f586e3ba695741c78293e9fc45f70d1a.png

(可见VS的运行顺序是:先算三次自增再进行两次相加,其它编译器就不一定了

             

 

总结

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


练习:

补充:sizeofstrlen 的对比

           

1. sizeof操作符strlen 库函数

         

2. sizeof 计算的是占用内存的大小,单位是字节不关注内存中存放的具体数据

3. strlen 是求字符串长度的,只能针对字符串,会寻找字符串中的 \0,统计的是字符串中 \0 之前出现的字符的个数

使用函数完成整型函数的打印元素逆置初始化

//创建一个整形数组,完成对数组的操作
//
//实现函数init() 初始化数组为全0
//实现print()  打印数组的每个元素
//实现reverse()  函数完成数组元素的逆置。
//要求:自己设计以上函数的参数,返回值。
#include <stdio.h>
//实现print()  打印数组的每个元素
void print(int* arr, int sz)
{
  //输出:
  int j = 0;
  for (j = 0; j < sz; j++)
  {
    printf("%d ", arr[j]);
  }
  //换行:
  printf("\n");
}
//实现reverse()  函数完成数组元素的逆置。
void reverse(int* arr, int sz)
{
  int i = 0;
  for (i = 0; i < (sz / 2); i++)
    //循环条件???
    //6个元素调3次,5个元素掉两次
    //sz/2 肯定够用
  {
    int* left = arr + i;
    int* right = arr + sz - 1 - i;
    // 使用指针移位,类似下标,易混
    // 让左右指针慢慢往中间靠
    int tmp = *left;
    *left = *right;
    *right = tmp;
    // 获取指针值是用 * 
  }
}
//实现函数init() 初始化数组为全0
void init(int* arr, int sz)
{
  //arr:首元素地址 ; sz为几就循环几次
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    int* tmp = arr + i; //创建指针变量存放指针
    *tmp = 0; //取指针中的值,并赋为0
  }
}
int main()
{
  int arr[5] = { 1,2,3,4,5 };
  int sz = sizeof(arr) / sizeof(arr[0]); //元素个数
  //实现print()  打印数组的每个元素
  print(arr, sz);
  //实现reverse()  函数完成数组元素的逆置。
  reverse(arr, sz);
  print(arr, sz);
  //实现函数init() 初始化数组为全0
  init(arr, sz);
  print(arr, sz);
  return 0;
}

df96e258f14a43bc8bb5d813ed3b649f.png


相关文章
|
6月前
|
C语言 C++
逗号表达式与赋值表达式
逗号表达式与赋值表达式
52 0
|
存储 C语言
【C语言】 条件操作符 -- 逗号表达式 -- []下标访问操作符,()函数调用操作符 -- 常见关键字 -- 指针 -- 结构体
【C语言】 条件操作符 -- 逗号表达式 -- []下标访问操作符,()函数调用操作符 -- 常见关键字 -- 指针 -- 结构体
|
6月前
|
编译器 C++ 索引
C learning_13 操作符前篇(条件操作符、 逗号表达式、 下标引用、函数调用和结构成员、 表达式求值)
C learning_13 操作符前篇(条件操作符、 逗号表达式、 下标引用、函数调用和结构成员、 表达式求值)
|
C语言
赋值运算和赋值表达式
赋值运算和赋值表达式。
212 0
|
索引
操作符之关系操作符,逻辑操作符,条件操作符,逗号表达式,下标引用操作符,函数调用操作符,访问结构体成员操作符
操作符之关系操作符,逻辑操作符,条件操作符,逗号表达式,下标引用操作符,函数调用操作符,访问结构体成员操作符
|
编译器 C语言
操作符的属性,C语言中运算符的优先性和结合性,常见的问题表达式
操作符的属性,C语言中运算符的优先性和结合性,常见的问题表达式
|
C语言
【C语言】 操作符(下): -- 条件操作符 --逗号表达式 -- 下标引用操作符 --表达式求值2
【C语言】 操作符(下): -- 条件操作符 --逗号表达式 -- 下标引用操作符 --表达式求值2
|
C语言 索引
【C语言】 操作符(下): -- 条件操作符 --逗号表达式 -- 下标引用操作符 --表达式求值1
【C语言】 操作符(下): -- 条件操作符 --逗号表达式 -- 下标引用操作符 --表达式求值1
|
存储 编译器 C语言
【C语言】表达式求值相关问题汇总—>隐式类型转换(整型提升)、算数转换与操作符优先级汇总(收藏查阅)
【C语言】表达式求值相关问题汇总—>隐式类型转换(整型提升)、算数转换与操作符优先级汇总(收藏查阅)
106 0