C语言的操作符(operator):
一、算术操作符
常用的算术操作符是这5个:+ - * / %
(1) 其中,除了%操作符,其余的都适用于浮点类型和整型。
(2) /操作符的两个操作数,只要有一个是浮点数,则执行浮点数除法。
(3) %是取模运算符,左数除以右数,结果返回余数。(即求余)
二、移位操作符
左移操作符:<<
左移1位相当于乘以2。
在左移中,最左边的几位被丢弃,右边补0。如下图,左移4位:
右移操作符:>>
右移1位相当于除以2。
右移云算符存在的问题:
(1)逻辑右移:左边空余的位置补0
(2)算术右移:左边移入的位由原先该值的符号位决定,符号位为1则移入的位均是1,符号位为0则移入的位都是0,以此来保证原来数字的正负。如,10101011右移两位,逻辑右移的结果为00101010,算术右移的结果为11101010。
例题:求一个整数存储在内存中的二进制中1的个数
#include<stdio.h>
int main()
{
int num;
int i = 0;
int count = 0;
scanf("%d", &num);
for (i = 0; i < 32; ++i)
{
if ((num >> i) & 1== 1)
{
count++;
}
printf("该数二进制中1的个数为:%d\n", count);
}
return 0;
}
PS:无论是左移还是右移,都不能移动负数位。如:a<<-1
三、位操作符
位操作符对操作数执行&(与)、|(或)、^(异或)等逻辑操作。进行&操作时,如果两个位都是1,结果为1,否则结果为0;进行 | 操作符时,如果两个位都是0,则结果为0,否则结果为1;进行^操作时,如果两个位不同,结果为1,如果两个位相同,结果为0。
异或(^)操作符的常见用法:任何数和它本身异或的结果为0;任何数和0异或的结果为为它本身。例题:不能创建临时变量(第三个变量),实现两个数的交换。代码如下:
//不创建临时变量,实现两个数的交换
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
a ^= b;
b ^= a;
a ^= b;
printf("a = %d , b= %d\n", a, b);
return 0;
}
利用位运算,将一个数value的第n位设置为1: value = value | 1<<n;
利用位运算,将一个数value的第n位设置为0: value = value & ~ (1 << n);
四、赋值操作符
赋值操作符,用一个 = 表示。它可以让你得到一个之前不满意的值。即可以给变量重新赋一个新值。
赋值操作符的结合性是从右到左,赋值表达式的值就是左操作数的新值。如:表达式a =x = y+3; 相当于a = (x = y+3); 也相当于x = y+3;a = x; 注意,在这个例子中,如果x是一个字符型变量,那么y+3的值就会被截去一段,以便容纳于字符变量中,那么a所被赋予的值就是被截取之后的值,即精度丢失,故而,认为a和x是被赋予相同的值是不正确的。
复合赋值符
复合赋值符有以下形式:
+= -= *= /= %= <<= >>= &= |= ^=
赋值操作符可以让程序员把代码写得更清楚一些。另外,编译器可以产生更为紧凑的代码。例如,a += expression;相当于a = a + (expression);两者差别并不大,但是,考虑以下下面的问题,如果函数f(x)没有副作用,他们是等同的:
a[ 2 * ( y – 6 * f(x) )] = a[ 2 * ( y – 6* f(x) )] + 1;
a[ 2 * ( y – 6 * f(x) )] += 1;
在第1种形式中,用于选择增值位置的表达式必须写两次,一次在赋值号的左边一次在赋值号的右边。由于编译器无从知道函数f(x)有无副作用,所以它必须两次计算下标表达式的值。第2种形式效率更高,因为下标只计算1次。
五、单目操作符
单目操作符有以下几种:
! ++ -- & sizeof ~ + - * (类型)
(1)!操作符对操作数执行逻辑反操作。如果操作数为真,其结果为假。如果操作数为假,其结果为真。
(2)++和--操作符都有两种形式,即前缀形式和后缀形式。两个操作符都必须使用变量作为操作数而不能是表达式。以++为例:前缀形式的++操作符,先用后加;后缀形式的++操作符,先加后用。--操作符用法与之雷同。 一个关于++操作符和—操作符的怪诞程序:
#include<stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("ret = %d\n",ret);
printf("i = %d\n",i);
return 0;
}
此程序在Linux环境运行下的结果为:
而在VS2013环境运行下的结果为:
在不同的编译环境产生不同的运算结果,可知此代码本身不是一个好的代码,我们在编程使用++运算符的时候一定要留心。而之所以产生此原因是表达式的求值依赖于运算符的优先级和结核性。
(3)&操作符取它的操作数在内存中的地址。注意改操作符的操作数只能是变量,不能是常量。
(4)sizeof操作符用于判断它的操作数的类型长度,注意sizeof(x)并不是一个函数。关于sizeof运算符有以下几种形式是合法的:sizeof(int);合法。sizeof(x);合法。sizeof x;合法。sizeof int;不合法。
关于sizeof和数组有下面一个例子:
#include <stdio.h>
void test1(int a[])
{
printf("%d\n", sizeof(a));//3
}
void test2(char c[])
{
printf("%d\n", sizeof(c));//4
}
int main()
{
int a[10] = { 0 };
char c[10] = { 0 };
printf("%d\n", sizeof(a));//1
printf("%d\n", sizeof(c));//2
test1(a);
test2(c);
return 0;
}
此程序,在1、2两处分别输出40、10;在3、4两处分别输出4、4。注意:数组在作为参数传递时会有降维问题,一维数组会降为一维指针。在32位机里,所有指针类型均占4个字节。
(5)~操作符对整型类型进行求补,操作数中原先所有为1的位变为0,所有原先为0的位变为1。
(6)+ - 操作符分别产生操作数的正值和负值。
(7)* 操作符是间接访问操作符(解引用操作符),它与指针一起使用,用于访问指针所指向的值。
(8)(类型) 操作符被称为强制类型转换,它用于显式地把表达式的值转换为另外的类型。例如,为了获得int型变量a的浮点数值,可以这样写:(float)a;强制类型转换具有很高的优先级,所以把强制类型转换放在一个表达式的前面只会改变表达式第一个项目的类型。如果要对整个表达式的结果进行强制类型转换,必须把整个表达式用()括起来。
六、关系操作符
关系操作符有以下几种:
> >= < <= != ==
这些操作符所产生的结果都是一个整型值,而不是布尔值。如果两端的操作数符合操作符指定的关系,表达式的结果是1,如果不符合,表达式的结果是0。关系操作符的结果是整型值,所以可以赋值给整型变量,常用于if和while语句中作为测试表达式
注意一个常见的错误:在大多数语言中,使用 = 操作符来比较相等性。在C中,你必须使用双等号==来执行这个比较,单个=用于赋值操作。如果在测试相等性的地方出现赋值符,在编译的时候是合法的,它并非是一个语法错误,而这往往会带给我们一些困扰。
七、逻辑操作符
逻辑操作符有&&和|| 。注意区分逻辑操作符和位操作符( &和 | ):
1&2------->0
1&&2----->1
1 | 2------->3
1 || 2------->1
注意:&&操作符的左操作数总是先求值,如果它的值为真,然后就紧接着对右操作数进行求值,如果左操作数的值为假,那么右操作数便不再进行求值; || 操作符首先对左操作数求值,如果它的值为真,右操作数便不再求值;因为整个表达式的值此时已经确定。这个行为常被称为“短路求值”。举个例子,一个360笔试题:
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++&&++b&&d++;
printf("a = %d\nb = %d\nc = %d\nd = %d\n",a, b, c, d);
return 0;
}
八、条件操作符
expression1 ? expression2 : expression3
条件操作符的优先级非常低。在整个式子中,首先计算的是expression1,如果它的真值为真(非零值),那么整个表达式的值就是expression2的值,expression3不会进行求值。但是,如果expression1的值是假(零值),那么整个表达式的值就是expression3的值,expression2不会进行求值。
九、逗号操作符
expression1,expression2,expression3,...,expressionN
逗号操作符将两个或多个表达式分隔开来。这些表达式自左向右逐个进行求值,整个逗号表达式的值就是最后那个表达式的值。例如代码:
int a = 1;
int b = 2;
int c = (a>b,a=b+10,a,b=a+1);
最后,c的结果为13。
再例如:
a = get_val();
count_val();
while(a>0)
{
a=get_val();
count_val(a);
}
//上面代码可以该写为:
while(a=get_val(),count_val(a),a>0)
{
}
十、下标引用、函数调用和结构成员
(1)[ ]
操作数:一个数组名+一个索引值
int arr[10];//创建数组
arr[9]=10;//使用下标引用操作符
[]的两个操作数是arr和9
(2)()
函数调用操作符
接收一个或多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。例如:
#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char *str)
{
printf("%s\n", str);
}
int main()
{
test1();
test2("hello world!");
return 0;
}
(3)访问一个结构体成员
. 结构体名.结构体成员名
-> 结构体指针名->结构体成员名
#include <stdio.h>
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
};
void set_age1(struct Stu stu)
{
stu.age = 10;
}
void set_age2(struct Stu* pStu)
{
pStu->age = 18;//结构成员访问
}
int main()
{
struct Stu stu;
struct Stu* pStu = &stu;//结构成员访问
stu.age = 20;//结构成员访问
set_age1(stu);
pStu->age = 20;//结构成员访问
set_age2(pStu);
return 0;
}
参考书目:《C和指针》、《C语言程序设计》等、、、