目录
1 . 操作符
1.1 算数操作符
常用算数操作符:
+ - * / %
其中除了%操作符外,其余几个操作符既适用于浮点类型又适用于整数类型。
当 / 操作符的两个操作数都是整数时,执行整除运算,其他情况执行浮点数除法。
%(取模操作符),它接受两个整型操作数,把左操作数除以右操作数,返回的值是余数而不是商。
1.2 移位操作符
移位操作符移动的是二进制位。(二进制位简称“位”,是二进制计数系统中表示小于2的整数的符号,一般用0或1表示)。左移位操作符为<<,右移位操作符为>>。左操作数的值(操作符左边的数)将移动由右操作数指定的位数。两个操作数都必须是整型类型。
下图以整数2具体表示:
移位操作只是简单地把一个值地位向左或向右移动。在左移位中,值最左边的几位被丢弃,右边多出来的几个空位则由0补齐。
如下图:
而右移位操作符在像移动后,对左边补充进的数字有两种方案。一种是逻辑移位,左边移入的位用0填充;另一种是算数移位,左边移入的位由原先该值的符号位决定(PS:正数最左边为0,负数最左边为1),符号位为1则移入的位均为1,符号位为0则移入的位均为0,这样能够保持原数的正负形式不变。
例如,值10010110右移两位,逻辑移位的结果是00100101,但算数移位的结果是11100101。
注意:标准说明无符号值执行的所有移位操作都是逻辑移位,但对于有符号值,采取哪种移位方案则取决于编译器。如果移位的位数比操作数的位数还要多,会发生不可预测的效果(同样由编译器决定)。
1.3 位操作符
位操作符有:& (与) | (或) ^(异或)
当两个位进行&操作时,如果两个结果都为1,结果为1,否则都为0。当两个位进行 | 操作时,如果两个位都是0,结果为0,否则结果为1。当两个为进行 ^ 操作时,如果两个位不同,结果为1,如果两个位相同,结果为0。
位操作符要求操作数为整数类型,它们对操作数对应的位进行指定操作,每次对左右操作数的各一位进行操作。
举例如下图:
1.4 .1 赋值操作符
赋值操作符用一个等号表示。赋值是表达式的一种,而不是某种类型的语句。所以,只要是允许出现表达式的地方都允许进行赋值。
如:
x = y + 3;
这一语句包含两个操作符,+ 和 =。首先进行加法运算,所以 = 的操作数是变量 x 和 表达式 y+3 的值。 赋值操作符就是把右操作数的值存储于左操作数指定的位置。但赋值也是个表达式,表达式就具有一个值。赋值表达式的值就是左操作数的新值,它可以作为其他操作符的操作数。
如下面的语句:
a = x = y + 3;
由于赋值操作符的结合性(求值的顺序)是从右到左,所以这个表达式也相当于:
a = ( x = y + 3 );
它的意思和下面的语句组合完全相同:
x = y + 3;
a = x;
又如下面这个稍微复杂一些的例子:
r = s + ( t = u - v ) / 3;
这条语句把表达式 u - v 的值赋给 t,然后把 t 除以3,再把除法的结果和s相加,其结果再赋值给r。等同于下面的语句:
t = u - v;
r = s + t / 3;
两相比较之下,不难发现后者的写法更好一些,因为它们更便于日常的阅读和调试。人们在编写内嵌赋值操作的表达式时很可能想在一行就表达完自己的意思,但这样做也许会造成表达式难于阅读。因此,在使用这个“特性”之前,要确信这一方法能带来更大的收益。
1.4.2 复合赋值符
目前所介绍的操作符还有一种复合赋值的操作形式:
+= -= *= /= %=
<<= >>= &= ^= | =
以其中的+=操作符为例(其余与它非常相似,只是使用的操作符不同),+=操作符的用法如下:
a += 2;
相当于
a = a + (2);
通常比起后者我更建议使用前者的方式,在熟练之后,将会发现+=的形式能使代码写得更加清楚。另外,编译器可以产生更加紧凑的代码。
如下的例子:
a [ 2 * ( y - 6 * f(x) ) ] = a [ 2 * ( y - 6 * f(x) ) ] + 1;
a [ 2 * ( y - 6 * f(x) ) ] += 1;
在第一种形式中,由于编译器不知道函数 f 是否有副作用(编译器根据对代码进行优化的需要自行规定对实参的求值顺序。有的编译器规定自左至右,有的编译器规定自右至左),所以它必须两次计算下标表达式的值。而第二种只计算一次,效率更高。
1.5 单目操作符
单目操作符是只接受一个操作数的操作符。是
! ++ - & sizeof
~ -- + * ( 类型)
接下来逐一介绍:
! 操作符对它的操作数执行逻辑反操作:如果操作数为真,其结果则为假。如果操作数为假,其结果为真。这个操作符实际上产生一个整型结果,0(假)或 1(真)。
~ 操作符对整型类型的操作数进行求补操作,操作数中原先为1的位变为0,所有原先为0的位变为1.
- 操作符产生操作数的负值。
+ 操作符产生操作数的值(啥也不干,单纯对称)。
& 操作符产生它的操作数的地址。(有机会在指针里面会讲,算了先简略的讲讲)
//定义一个变量a,和一个指针b
int a = 3, *b; //变量b 类型是int * 所以写成int *b
...
b = &a;
上面的语句声明了一个整型变量和一个指向整型变量的指针。接着,&操作符取变量a的地址,并把它赋值给指针变量(提供门牌号)。
* 操作符是间接访问操作符,它与指针一起使用,用于访问指针所指向的的值。在前面例子中的赋值操作完成之后,表达式b的值就是变量a的地址,而进行解引用后(*b这样解引用),*b的值则是变量a的值(拿到门牌号,敲门,拿a里存放的东西)。
sizeof 操作符判断它的操作数的类型长度,以字节位单位表示(一个字节等于8个比特位)操作数既可以是个表达式(常常是单个变量),也可以是两边加上括号的类型名,如下两个例子:
sizeof( int ) sizeof (x)
第1个表达式返回整型变量的字节数,其结果取决于你所使用的环境(32位环境,和64位环境)。第2个表达式返回变量x所占据的字节数。
注意!!!判断表达式的长度并不需要对表达式进行求值,所以sizeof(a=b+1)并没有对a赋任何值。
(类型) 这一操作符被称为强制类型转换。它用于显示地把表达式的值转换为另外的类型。例如
int a是个整形变量,在通过(float)a进行强制类型转换后就变成可浮点数值。
++ 操作符具有两种使用形式,前缀形式和后缀形式。两种都执行+1操作。前缀形式的++操作出现在操作数的前面。操作数被增加,表达式的值变为操作数增加后的值。后缀形式的++操作出现在操作数的后面。操作数的值仍被增加,但表达式的值是操作数增加前的值。通过下面的例子可以更好的理解:
int a, b, c, d;
a = b = 10; //a 和 b被赋值为10
c = ++a; //前缀形式 a先增加到11,然后c才能的到值11.
d = b++; //后缀形式 d先得到b的值10,然后b才会增加到11.
-- 操作符与++操作符类似,同样有两种形式,前缀和后缀。不过执行的是-1操作。
1.6 关系操作符
这类操作符用于测试操作数之间的各种关系。这类操作符是:
> >= < <= != ==
前四个用于比较大于,大于等于,小于和小于等于。
而!=的意思是不等于,用于判断两者是否不同。如:
int a = 3;
int b = 4;
if ( a != b )
return false;
==操作符则用于比较两者是否相同。
注意!!!切勿搞混 = (赋值) 和 == (比较),两者意思完全不同!!!
1.7 逻辑操作符
逻辑操作符有&&(且) 和 ||(或)。虽然看上去像位操作符,但具体作用却完全不同。
expression1 && expression2,如果expression1和expression2的值都为真,那么整个表达式的值也是真的,如果两个表达式任何一个表达式的值位假,那么整个表达式的值则为假。
首先通过下面这段代码来看&&操作符:
int a = 3;
int b = 6;
if ( a < 5 && b > 5)
{
printf("nice");
}
由于&&操作符的优先级比>和<操作符的优先级都要低,所以表达式相当于(a>5)&& (b>5)。满足条件则打印nice。
尽管&&操作符的优先级较低,但它任然会对两个关系表达式施加控制。&&操作符的左边的操作数总是首先进行求值,如果它的值为真,才会对右边的操作数进行求值。如果左操作数为假,那么右操作数便不再进行求值。如:
int a = 3;
int b = 5;
int c = 0;
int d = 0;
if ( ( (c = a - 5) > 0) && ( (d = b + 1) > 0))
{
printf("nice");
}
在这一代码中,c的值由于进行a - 5的赋值变为了-2,而-2 > 0为假,那么&&的有边便不在进行判断,也就是说 d = b + 1 这一操作将不在进行,不管它的值是真是假,都不进行,d的值依然是0.
而|| 操作符(或)也具有相同的特点,它首先对左操作数进行求值,如果它的值是真,那么右操作数变不再求值。而如果左操作数进行求值后为假,才会对右操作数进行求值,若两者都为假,则返回假,两者有一个为真,则为真。如:
int a = 3;
int b = 6;
if ( a > 5 || b > 5)
{
printf("ncie");
}
左为假,右为真,则依然为真。上述的这种行为往往被称为“短路求值”(很形象吧)。
注意!!!不要把&&和&,||和|搞混,一个是逻辑操作符,一个是位操作符,不能乱来。
1.8 条件操作符
条件操作符接受三个操作数(也被称为三目运算)。下面使它的用法:
expression1 ? expression2 : expression3
条件操作符的优先级非常低,所以它的各个操作数(?和:)即使不加括号,一般也不会有问题。但是为了更加清楚,还是加上比较稳妥。
首先计算的是expression1,如果它的值为真(非零值),那么整个表达式的值就是expression2的值,如果expression1的值为假,那么整个条件语句的值就是expression3的值。举例如下:
int b = 0;
int a = 5;
(a > 4) ? (b=1) : (b=2);
由于a>4为真,那么b便被赋值为1.如果a>4为假,那么b的值将被赋为2.
1.9 逗号操作符
逗号操作符将两个或多个表达式分搁开来,这些表达式自左向右逐个进行求值,整个逗号表达式的值就是最后那个表达式的值。例如:
int a = 1;
int b = 4;
int c = 5;
if (a+2, b / 2, c > 0)
{
printf("nice");
}
首先a+2变为3,其次b / 2变为2,然后再将最后一个式子作为判断条件,c>0为真。打印nice.
1.10 下标引用,函数调用和结构成员
这些操作符如果有机会将在以后联系其他内容进行详细介绍,这边先提一嘴。
下标引用操作符是一对方括号[ ].它接受两个操作数:一个数组名和一个索引值。事实上,下标引用并不仅限于数组名(还是有机会^ _ ^)。下标引用的下标值总是从零开始的。并且不会对下标值进行有效性检查。除了优先级不同之外,下标引用操作和间接访问表达式是等价的。这里是它们两者的关系:
array [ 下标 ]
*( array + ( 下标 ) )
通常编译器在编译过程中会自动的将第一种转换为第二种。
函数调用操作符接受一个或多个操作数。它的第一个操作数是你希望调用的函数名,剩余的操作数就是传递给函数的参数。把函数调用以操作符的方式实现意味着“表达式”可以替代“常量”作为函数名(以后会详细讲解)。
. 和 ->操作符用于访问一个结构的成员。如果s是个结构变量,那么访问s.a就访问名叫a的成员。当你拥有一个指向结构的指针而不是结构本身,且与访问它的成员时,就需要->操作符而不是.操作符(我将会在结构体一文中进行详细讲解,如果你没找到我这篇文章,就说明我太懒了,记得评论区踹我一脚- _ -)。
2. 布尔值
C并不具备显示的布尔类型,所以使用整数来代替。其规则是:
零是假,任何非零值都为真。(标准并没有说1这个值比其他任何非零值“更真”,只是我们为了日常方便才这么说的。)
3. 左值和右值
!!!为了理解有些操作符存在的限制,必须理解左值和右值之间的区别!!!
左值就是哪些能够出现在赋值符号左边的东西。右值就是那些可以出现在赋值符号右边的东西,你也许认为我在说废话文学,但这很重要。左值意味着一个位置(房子),右值意味着一个值。 例如:
a = b + 25;
a是个左值,因为它标识了一个可以存储结果值的地方(房子),b + 25是个右值,因为它指定了一个值。但是请注意!!!
它们并不可以互换。
b + 25 = a;
原先作左值的a此时可以作为右值,这没问题,因为每个位置包含一个值。然而,b + 25 不能作为左值,因为它并为标识一个特定的位置(就像我在上面提到的一排房子一样,b房子门牌号+了25你就不知道是谁家了,不能随便访问!!!)。因此这条赋值语句是非法的。同样你也不能将一个字面值常量作为左值,你不能将5 = 6(相当于强行撬开存放5这一数据的房子,再把5拉出来,然后把6塞进去,这犯法啊!!!)。所以在使用右值的地方也可以使用左值,但是在需要左值的地方不能使用右值。
4. 表达式求值
4.1 隐式类型转换
C的整型算术运算总是至少以却省整型类型的精度精选的,为了获得这个精度,表达式中的字符型和短整型操作数在使用之前被转换为普通整型。这种转换称为整型提升。例如:
char a, b, c;
...
a = b + c;
b和c的值将被提升为普通整型,然后再执行加法运算。加法运算的结果将被截断,然后在存储到a中(比如int型占4个字节,char类型占1个字节,你要把4个字节硬塞到1个字节里,这肯定不行啊,这尺寸不对啊,于是便会发生截断,至于截断,和整型提升我将会单独写一篇文章来进行说明。如果没有,就踢我一脚/dog)。
4.2 算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另外一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算数转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低(小的变大),那么它首先将转换为另外一个操作数的类型再执行操作。
4.3 操作符的属性
复杂表达式的求值顺序是由三个因素决定的:操作符的优先级,操作符的结合性以及操作符是否控制执行的顺序。两个相邻的操作符哪个先执行取决于它们的优先级,如果两者优先级相同,那么它们的执行顺序由它们的结合性决定。简单地说,结合性就是一串操作符是从左向右依次执行还是从右向左逐个执行。(关于操作符优先级将在下一篇文章中详细介绍。)
_____________________________________________________________________________仓促成文,如有内容偏颇之处,望指正。