exp1 ? exp2 : exp3
前面我们学过像a+b这样有两个操作数的表达式,+有两个操作数,称为双目操作符;像!a这样有
一个操作数的,!只有一个操作数,称为单目单目操作符。
今天我们要学的条件操作符有三个操作数,我们把它称作三目操作符。
那么它的作用是什么呢?
上述条件操作符中有三个表达式,exp1、exp2、exp3.
条件操作符的作用是:
当表达式exp1为真时,计算表达式exp2,不计算表达式exp3,并把exp2的结果作为整个式子的结果。
当表达式exp1为假时,计算表达式exp3,不计算表达式exp2,并把exp3的结果作为整个式子的结果。
其实我们可以看出来条件操作符的作用就相当于一个 if....else语句
举例:
if (a > 5)
{
b = -3;
}
else
{
b = 3;
}
上述代码可以用条件操作符实现如下:
(a > 5) ? b = -3 : b = 3
我们也可以用条件操作符来实现前面学过的求两数的较大值
#define _CRT_SECURE_NO_WARNINGS 1
#include
int main()
{
int a = 0;
int b = 0;
int m = 0;
scanf("%d %d", &a, &b);
m = (a > b) ? a : b;
printf("%d\n", m);
return 0;
}
exp1,exp2,exp3,exp4.......expN
逗号表达式就是用逗号隔开的多个表达式。
逗号表达式从左向右依次执行,整个表达式的结果是最后一个表达式的结果。
看下面一段代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
return 0;
}
问题来了,c的值是多少呢?
从左向右依次进行,1>2(假),a=b+10=12,1,b=a+1=13。
最终b=a+1=13的结果是整个表达式的结果,所以c的值是13.、
再看下面一段代码:
if(a = b + 1,c = a / 2,d > 0)
其中真正起作用的是d > 0,但是前面的也要计算。
逗号表达式有时候也可以简化代码:
a = get_val();
count_val(a);
while (a > 0)
{
//业务处理
a = get_val();
count_val(a);
}
这段代码中明显有冗余,如果用逗号表达式可以这样写:
while (a = get_val(),count_val(a), a > 0)
{
//业务处理
}
[ ] 下标引用操作符
() 函数调用操作符
. -> 结构体成员操作符
[ ] 下标引用操作符是应用于数组中的,如果我们要访问数组元素,就要用到下标引用操作符。
#define _CRT_SECURE_NO_WARNINGS 1
#include
int main()
{
int arr[10] = { 1,2,3,4,5 };
// 数组下标: 0 1 2 3 4 5 6 7 8 9
//数组下标从0开始
printf("%d\n", arr[2]);//[]下标引用操作符,arr 和 2是 [] 的两个操作数
return 0;
}
上述代码在访问下标为2的数组元素时用到arr[2],其中[ ]是下标引用操作符,arr 和 2是 [ ]的两个操作数。
() 函数调用操作符:
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
int main()
{
int len = strlen("abcdef");//()就是函数调用操作符,strlen和"abcdef"是它的两个操作数
printf("%d\n", len);
return 0;
}
上述代码在求字符串长度是用到函数strlen()函数,()就是函数调用操作符,strlen和"abcdef"是它的两个操作数。
思考一个问题,对于函数调用操作符来说,最少有几个操作数?
最少一个,因为使用函数时()里面可以没有参数,那么它的操作数就只有一个,函数名。
. -> 结构体成员操作符:
前面讲结构体时说过,结构体是一个复杂类型,或者叫自定义类型,int,short,long,long long,float,double,char是内置类型,但是生活中的事物要完全描述出来,仅仅靠内置类型远远不够。这时候就要用到结构体了。
. 操作符的使用:结构体变量.成员名
#define _CRT_SECURE_NO_WARNINGS 1
#include
struct Book
{
char name[30];
char auther[20];
float price;
};
int main()
{
struct Book b1 = { "我与地坛","史铁生",75.5 };
struct Book b2 = { "人间失格","太宰治",55.5 };
printf("%s %s %.1f\n", b1.name, b1.auther, b1.price);
printf("%s %s %.1f\n", b2.name, b2.auther, b2.price);
//结构体变量.成员名
return 0;
}
-> 操作符的使用:结构体指针->成员名
#define _CRT_SECURE_NO_WARNINGS 1
#include
struct Book
{
char name[30];
char auther[20];
float price;
};
Print(struct Book* p1, struct Book* p2)
{
printf("%s %s %.1f\n", p1->name, p1->auther, p1->price);
printf("%s %s %.1f\n", p2->name, p2->auther, p2->price);
}
int main()
{
struct Book b1 = { "我与地坛","史铁生",75.5 };
struct Book b2 = { "人间失格","太宰治",55.5 };
Print(&b1,&b2);
return 0;
}
上述代码封装了一个打印函数Print(),将结构体变量b1和b2的地址传参,形参用指针类型struct Book*接受,在访问结构体成员时用操作符 ->访问。
到这里C语言里的操作符我们就全部介绍完了。
我们在计算2+3*5时有相应的计算步骤,先计算3*5,后计算2+15。
那么在C语言中也有相应的求值顺序。
表达式求值的顺序一部分是由操作符的优先级和结合性决定的。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
下面我们要学两点;
1.表达式在计算的过程中有哪些类型转换?
2.表达式的求值顺序是怎样的?
首先解决第一个问题,表达式在计算的过程中有哪些类型转换?
要知道,类型转化有两种:整型提升、算数转换
C的整型算数运算总是至少以缺省整型类型的精度进行的。
为了获得这个精度,表达式中的字符型和短整型操作数在使用之前被转换为普通整型(即int型),这种转换称为整型提升。
由上图可知,两个char类型相加时,我们先将其转化为int型,再进行运算,而char长8bit,int长32bit,这样即使运算过程中产生进位也不会发生数据的丢失。
那么如何进行整型提升呢?
整型提升是按照变量的数据类型的符号位来提升的
看下面一段代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include
int main()
{
char a = 5;
char b = 127;
char c = a + b;
printf("%d\n", c);
return 0;
}
运行结果:
打印结果为什么是-124呢?
下面我们来分析一下:
a是整型,本来在内存中应该是32位:00000000000000000000000000000101
但是我们定义的char型只有8位,发生截断:00000101
同样,b是整型,本来在内存中应该是32位:00000000000000000000000001111111
但是我们定义的char型只有8位,发生截断:01111111
后面执行c = a + b表达式,有人说直接将00000101和01111111相加,结果等于132,这样算对吗?
显然不对,前面说过运算时表达式中的字符型和短整型操作数在使用之前要被转换为普通整型(即int型),即整型提升。下面我们对a和b整型提升:
整型提升是按照变量数据类型的符号位来提升的,char型的最高位是符号位,a的符号位是0,那a整型提升后就是00000000000000000000000000000101。
b的符号位是0,那b整型提升后就是00000000000000000000000001111111。
然后再相加:00000000000000000000000010000100。
c在存放时发生截断:10000100
前面我们说过%d的作用是:以10进制的形式打印有符号的整数。那既然是整数,就应该是32位,所以还要对c整型提升。
char型的最高位是符号位,c的符号位是1,,整型提升后为补码:11111111111111111111111110000100
化为原码:10000000000000000000000001111100,十进制输出为-124
注意:无符号数整型提升,高位补0
下面我们可以用一段代码来验证整型提升的存在:
#define _CRT_SECURE_NO_WARNINGS 1
#include
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;
}
运行结果:
上述代码只打印出了 c ,由此可见a和b都发生了整型提升,因为a是字符型,b是短整型。
再强调一遍:整型提升只针对短整型和字符型。
再看一段代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include
int main()
{
char c = 0;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}
运行结果:
这段代码也能证明c发生整型提升,只有c参与运算(即代码中给c加上正负号),就会发生整型提升。补充一点:%u的作用是以10进制的形式打印无符号的整数。
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的类型转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算数转换。
long double
double
float
unsigned long int
long int
unsigned int
int
上述类型中没有char和short类型,因为它们在运算时发生整体提升,而上述类型进行算数转换时是由低到高进行的。
例如:float f = 3.14f;
int n = 10;
f + n;
在f + n时先将n转换为float型,然后运算。注意转换时在运算时临时转换的,n本身的类型并没有改变。
到此我们第一个问题就讲完了。
下面来看第二个问题,表达式的求值顺序是怎样的?
复杂表达式的求值有三个影响因素:
1.操作符的优先级
2.操作符的结合性
3.是否控制求值顺序
两个相邻的操作符先执行哪个?取决于它们的优先级,如果优先级相同,取决于他们的结合性。
下面一张表完整的总结了操作符的属性:
上表中的优先级是从高到低排列的,优先级最低的是逗号。
表达式计算时,相邻操作符先看优先级,优先级高的先算,优先级低的后算,例如:2+3*5,乘号的优先级比加号高,所以先乘后加。而当优先级相同时,就要看结合性,例如:2+3+5,都是加号,优先级相同,我们可以在表中看一下加法的结合性是(L-R)这意味着加法是从左向右进行的,即先2+3得到5,然后5+5。(注意:这里讨论的是相邻操作符)
那相反的结合性是(R-L)的就是从右向左进行运算。
说了优先级和结合性,我们在来看一下是否影响求值顺序。
表中是否影响求值顺序这一栏绝大多数都是否,只有&& 逻辑与,||逻辑或, ?:条件操作符和逗号操作符影响求值顺序。
这个之前的操作符详解(1)中也讲过,逻辑与只要左边为假,右边的表达式就不再计算。逻辑或只要左边为真,右边的表达式就不再计算。像这种就叫影响了求值顺序。
下面我们来看一些问题表达式:
表达式1:
a*b + c*d +e*f
上述表达式先算谁后算谁呢?
可以知道的是乘法优先级比加法优先级高,所以肯定先算乘法,后算加法。但是这仅限于相邻的操作符,只有相邻的乘法和加法知道计算顺序,像第一个加号和第三个乘号先算谁?第一个乘号和第三个加号先算谁?都确定不了。那就可能有好几种计算顺序了,下图列出两种:
表达式2:
c + --c
通过查表可以知道自减操作符的优先级高于加法操作符,所以应该先自减然后加,但是问题来了,c的值到底用运算之前的还是运算之后的,假设初始化c的值是2,那第一个c的值是第二个c自减后的1还是初始化的2?没法确定。这样的表达式容易造成错误。
像上面这种无法确定唯一计算路径的代码就是问题代码,在写代码时一定要注意!
今天就学到这里,未完待续。。。