操作符详解(二)

目录

1. 下标访问[]、函数调用()

1.1 [ ] 下标引用操作符

1.2 ( ) 函数调用操作符 

2. 结构成员访问操作符

2.1 结构体

2.1.1 结构的声明

2.1.2 结构体变量的定义和初始化

2.2 结构成员访问操作符

2.2.1 结构成员的直接访问

2.2.2 结构成员的间接访问

3. 操作符的属性:优先级、结合性

3.1 优先级

3.2 结合性

3.3 优先级表

4. 表达式求值

4.1 整型提升

4.2 算术转换

4.3 问题表达式解析

4.3.1 表达式1

4.3.2 表达式2

4.3.3 表达式3(例子)

4.3.4 表达式4(例子)

4.4 总结


1. 下标访问[]、函数调用()

1.1 [ ] 下标引用操作符

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

int arr[10] = {0};
arr[5] = 6;//[ ] - 下标引用操作符
//arr 和 5是两个操作数

1.2 ( ) 函数调用操作符 

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

#include 

void test()
{
	printf("hehe\n");
}

int main()
{
	int c = Add(3, 5);//()函数调用操作符 - 函数名和参数就是操作数
	test();//()函数调用操作符
	printf("hehe\n");
	return 0;
}
int main()
{
	int a = 10;
	//sizeof是操作符,不是函数
	printf("%zd\n", sizeof(a));
	printf("%zd\n", sizeof a);
	//函数必须有(),而sizeof可以不用,所以sizeof不是函数
	return 0;
}

2. 结构成员访问操作符

2.1 结构体

C语言已经提供了内置类型,如:char,short,int,long,float,double等等,但是只有这些是不够的。C语言增加了结构体这样的数据类型,让程序员根据需求创造合适的类型。

结构是一些值的集合,这些值称为成员变量,结构的每个成员可以式不同类型的变量,如:标量、数组、指针,甚至式其他结构体。

2.1.1 结构的声明

struct tag//struct是关键字,tag是结构体的标签名
{
	member - list;//成员列表,至少要有一个成员
}variable - list;//变量列表

举个例子:

#include 

//想要描述一个学生
//名字 + 年龄 + 成绩
struct Stu
{
	char name[20];
	int age;
	float score;
} s3 = { "xiaoming", 33, 66.0f }, s4 = { "xiaohong", 18, 100.0f };

int main()
{
	struct Stu s1 = { "zhangsan", 20, 95.0f };
	struct Stu s2 = { "lisi", 21, 88.0f };
	return 0;
}

2.1.2 结构体变量的定义和初始化

//代码1:变量的定义
struct Point
{
	int x;
	int y;
}p1;            //声明类型的同时定义变量p1
struct Point p2;//定义结构体变量p2

//代码2:初始化
struct Point p3 = { 10, 20 };

struct Stu
{
	char name[20];//名字
	int age;      //年龄
	float score;  //成绩
};


struct Stu s1 = { "zhangsan", 20 };//初始化
struct Stu s2 = { .age = 20, .name = "lisi" };//指定顺序初始化

//代码3
struct Node
{
	int data;
	struct Point p;
	struct Node* next;
}n1 = { 10, {4, 5},NULL };//结构体嵌套初始化

struct Node n2 = { 20, {5, 6}, NULL };

2.2 结构成员访问操作符

2.2.1 结构成员的直接访问

结构成员的直接访问是通过点操作符(.)访问的。点操作符接受两个操作数。

使用方式:结构体变量.成员名

示例如下:

操作符详解(二)_第1张图片

联系前面的代码得到:

操作符详解(二)_第2张图片

2.2.2 结构成员的间接访问

有时我们得到的不是一个结构体变量,而是得到了一个指向结构体的指针。

使用方式:结构体指针->成员名 

示例如下:

操作符详解(二)_第3张图片

3. 操作符的属性:优先级、结合性

优先级和结合性这两个属性决定了表达式求值的计算顺序。 

3.1 优先级

优先级指如果一个表达式包含多个运算符,哪个运算符应该优先执行。(注意一定要是相邻操作符)各种运算符的优先级是不一样的。

比如:

3 + 4 * 5;

 表达式 3 + 4 * 5 里既有加法运算符,又有乘法运算符,由于乘法的优先级比加法高,所以先计算4 * 5,而不是 3 + 4 。

3.2 结合性

如果两个运算符的优先级相同,就需要结合性作用。可根据运算是左结合,还是右结合,决定执行顺序。大部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如复制运算符(=)。 

5 * 6 / 2;

* 和 / 的优先级一样,优先级没法确定先计算哪个,而他么都是左结合的运算符,所以从左到右执行,先计算 5 * 6,再计算 / 2 。 

3.3 优先级表

这些是常用的操作符优先级:

圆括号(())

自增运算符( ++ ),自减运算符( -- )

单目运算符(+ 和 -)

乘法( * ),除法( / )

加法( + ),减法( - )

关系运算符( < 、>等)

赋值运算符( = )

优先级 运算符 描述 结合性
1

++  --

( )

[ ]

.

->

(type){list}

后缀自增和自减

函数调用

数组下标

结构体与联合体成员访问

结构体与联合体成员通过指针访问

复合字面量(C99)

从左到右
2

++  --

+  -

!  ~

(type)

*

&

sizeof

_Alignof

前缀自增和自减

一元加与减

逻辑非与逐位非

转型

间接(解引用)

取址

取大小

对齐要求(C12)

从右到左
3

*

/

%

乘法

除法

余数

从左到右
4

+

-

加法

减法

5

<<

>>

逐位左移

逐位右移

6

<  <=

>  =>

分别为小于和小等于的关系运算符

分别为大于和大等于的关系运算符

7

==

!=

等于

不等于

8 & 逐位与
9 ^ 逐位异或
10 | 逐位或
11 && 逻辑与
12 || 逻辑或
13 ? : 三元条件 从左到右
14

=

+=  -=

*=  /=  %=

<<=  =>>

&=  ^=  |=

简单赋值

以和及差赋值

以积、商及余数赋值

以逐位左移及右移赋值

以逐位与、异或及或赋值

15 , 逗号 从左到右

4. 表达式求值

4.1 整型提升

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

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

操作符详解(二)_第4张图片

 代码中的 c1 和 c2 是字符,在使用之前先转化为 int类型,两个 int 运算后的结果再放到 c3 里。

char 类型和 short 类型的大小都没有达到 int 的大小,所以转换为 int 被称作整型提升。

整型提升的意义:

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

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

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

 举例如下:

操作符详解(二)_第5张图片

4.2 算术转换

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

long double
double
float
unsigneg long int
long int
unsigner int
int

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

4.3 问题表达式解析

4.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

这样编写存在潜在的问题的,所以在编写时可以将它们拆开来写,或者加上括号,明确地给出计算过程。 

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

在这个式子中,-- 的优先级高于 + ,所以可以确定 -- 的运算在 + 之前,但是我们不能确定第一个c的值是 -- 前的原始的数据还是 -- 后的数据,所以表达式的的结果无法预测。

所以这个代码也是拆开写。

4.3.3 表达式3(例子)
//表达式3
int main()
{
	int i = 10;
	i = i-- - --i * (i = -3) + i++ + ++i;
	printf("i = %d\n", i);
	return 0;
}

 以上代码在不同编译器上的测试结果如下:

翻译器
-128 Tandy 6000 Xenix 3.2
-95 Think C 5.02(Macintoch)
-86 IBM PowerPC AIX 3.2.5
-85 Sun Sparc cc(K&C编译器)
-63 gcc, HP_UX 9.0, Power C 2.0.0
4 Sun Sparc acc(K&C编译器)
21 Turbo C/C++ 4.5
22 FreeBSD 2.1 R
30 Dec Alpha OSF1 2.0
42 Microsoft C 5.1

五花八门的结果说明不同的编译器对这样的代码的解读方式是有所差异的。

4.3.4 表达式4(例子)
//表达式4
#include 

int fun()
{
	static int count = 1;
	return ++count;
}

int main()
{
	int answer;
	answer = fun() - fun() * fun();
	printf("%d\n", answer);
	return 0;
}

同样的,上述代码中,我们可以通过优先级得知先算乘法,再算减法,但是先调用哪一个 fun() 是无法确定的。

4.4 总结

即使我们有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯一计算路线,那么写出的这样的表达式它就是存在潜在风险的,不要写出特别复杂的表达式。

你可能感兴趣的:(c语言)