下标引用函数调用和表达式求值

时间:2023-01-30 19:59:03

前言:

下标的用法非常非常的重要关乎到数组函数指针结构体等等一切,函数的调用也有很多细节表达式求值涉及优先级问题要注意

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

注:一切下标又是从0开始计数的,也就是说下标对应的真实个数比下标大1这一点很容易被忽略

1.1 [ ] 下标引用操作符

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

int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9。

1.2 ( ) 函数调用操作符

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

这里的test1()时无参函数不需要传参,test2()函数需要传一个字符串,函数使用的时指针来接受字符串

#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char *str)
{
printf("%s\n", str);
}
int main()
{
test1(); //实用()作为函数调用操作符。
test2("hello bit.");//实用()作为函数调用操作符。
return 0;
}

下标引用函数调用和表达式求值

1.3 访问一个结构的成员  

1.3.1例1

. 结构体.成员名

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

//结构体
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
};
void set_age1(struct Stu stu)
{
stu.age = 40;

}
void set_age2(struct Stu* pStu)
{
pStu->age = 50;//结构成员访问
}
int main()
{
struct Stu stu;
struct Stu* pStu = &stu;//结构成员访问

stu.age = 20;//结构成员访问
set_age1(stu);
printf("%d\n",stu.age);//20

pStu->age = 30;//结构成员访问
set_age2(pStu);
printf("%d\n", stu.age);//50
return 0;
}

下标引用函数调用和表达式求值

1.3.2 例2

//创建一个结构体类型-struct stu
struct stu
{
char name[20];
int age;
char id[20];
};
int main()
{
//使用struct stu这个结构体类型创建了一个学生对象s1,并初始化
struct stu s1 = { "张三",20,"20220814" };
/*printf("%s\n",s1.name);//普通写法
printf("%d\n",s1.age);
printf("%s\n",s1.id);*/
struct stu* sp = &s1;//指针的繁琐写法
/*printf("%s\n", (*sp).name);
printf("%d\n",(*sp).age);
printf("%s\n", (*sp).id);*/
printf("%s\n",sp->name);//指针简单写法
printf("%d\n", sp->age);
printf("%s\n", sp->id);
return 0;
}

下标引用函数调用和表达式求值

2. 表达式求值

表达式求值的顺序一部分是由操作符的优先级和结合性决定。 同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。

2.1 隐式类型转换

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

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

2.2 整型提升的意义:

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长 度。 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,然后才能送入CPU去执行运算。

2.3 示例

//整形提升
int main()
{
char a = 3;
//00000000000000000000000000000011 补码
//00000011 a 因为a是char类型只有8位,所以截断 b也一样

char b = 127;
//00000000000000000000000001111111 补码
//01111111 b

// 整形提升后的a b
//00000000000000000000000000000011 a补码
//00000000000000000000000001111111 b补码
//a和b如何相加 两个char相加要先提升为整形在计算
//00000000000000000000000010000010 补码

char c = a + b;
//10000010 c 因为c是char类所以对相加结果截断
//11111111111111111111111110000010 c整形提升后 补码 //因为输出是 %d 所以又要对c整形提升
//11111111111111111111111110000001 反码
//10000000000000000000000001111110 -126原码

printf("%d\n",c); //-126
return 0;
}

2.4 如何进行整体提升呢?

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

简单来讲就是有符号数按照最高符号位补全32位,无符号数补0不全32位

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

2.4.1 示例1

实例1中的a,b要进行整形提升,但是c不需要整形提升 a,b整形提升之后,变成了负数,所以表达式 a==0xb6 , b==0xb600 的结果是假,但是c不发生整形提升,则表 达式 c==0xb6000000 的结果是真. 所程序输出的结果是: c

//整形提升的例子:
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;
}

下标引用函数调用和表达式求值

2.4.2 示例2

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

//实例2
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}

下标引用函数调用和表达式求值

2.5 算术转换

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

long double
double
float
unsigned long int
long int
unsigned int
int

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

警告: 但是算术转换要合理,要不然会有一些潜在的问题。

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

2.6 操作符的属性

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

1. 操作符的优先级

2. 操作符的结合性

3. 是否控制求值顺序。

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

操作符优先级

下标引用函数调用和表达式求值

下标引用函数调用和表达式求值

下标引用函数调用和表达式求值

下标引用函数调用和表达式求值

3.一些问题表达式

3.1 例1

//表达式的求值部分由操作符的优先级决定。
//表达式1
a*b + c*d + e*f

注释:代码1在计算的时候,由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不 能决定第三个*比第一个+早执行。

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

3.1 例2

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

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

3.3例3

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

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

下标引用函数调用和表达式求值

3.4 例4

这个代码有没有实际的问题? 有问题!

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

但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法, 再算减法。 函数的调用先后顺序无法通过操作符的优先级确定。

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

3.5 例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编译器,VS2013环境下都执行,看结果。

下标引用函数调用和表达式求值

下标引用函数调用和表达式求值

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

简单看一下汇编代码.就可以分析清楚. 这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级 和结合性是无法决定第一个 + 和第 三个前置 ++ 的先后顺序。

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

留言:

欢迎评论区交流