运算符的种类
C语言的运算符有赋值运算符、逻辑运算符、条件运算符、算数运算符、以及位运算符。关于位运算符的内容有些复杂,我开了个博文单独讨论它
赋值运算符
赋值运算符 的符号是 “=”,在C语言中,它不同于我们数学中的“等于”,“等于”在C语言中使用“==”表示,接下来会提到。赋值运算符的含义是: 将 "=" 右边的数值或者变量甚至地址赋值给 "=" 左边的变量。为了便于下面的理解,在这里,我们介绍两个概念:左值 和 右值。
左值,一般认为就是能够表示一块内存的名称或者表达式(如指针表达式);一般的左值的值都是可以修改的,但是c语言中有
const
这个关键字,它声明的左值是常量,其值不可被修改。这就存在了可修改的左值和不可修改的左值。左值可以放在赋值符号左侧,也可以放在赋值符号右侧,但是:只有可修改的左值才能放在赋值符号的左侧!
右值,C Primer Plus上给的解释是:可以赋值给可修改左值的量,但本身不是左值。其实它就是那些本身不表示任何内存的常量或变量或者可以表示求值的表达式甚至函数,它只能放在赋值符号右侧。
我们来举个例子:
int num = 5;
double temp;
double count;
const int price = 200;
temp = 7.5;
count = (num - temp)*3;
上面这段代码中num,temp,count都是可修改的左值,price是不可修改的左值(注意,
const int price = 200
中的=
是初始化,不是赋值符号!),常量 5 , 200 , 7.5 ,以及表达式(num - temp)*5
都是右值,它们不表示特定的内存。
算数运算符
算术运算符 顾名思义,就是进行算数运算的运算符,根据算术运算符运算需要的值(或变量)的个数,有以下几类:
一元运算符 | ++ 、 -- |
---|---|
二元运算符 | +、-、*、/、% |
一元运算符只能单个值进行运算,而且进行运算的必须是可修改的左值(这就意味这多重自增如
++(a++);
等不成立)。
二元运算符至少需要两个值。这没什么可说的,我们这里重点讨论递增运算符 “++”。
递增(减)运算符可以放在值的前面和后面,如num++
和++num
;num++
和++num
的运算结果都是num + 1;
,但它们内部的运算过程是完全不同的。我们试着运行下面这两段代码:
第一段代码
void main()
{
int num = 5;
if(num ++ >= 6)
{
printf("num++判断成功");
}
printf("num = %d",num);
}
运行结果为:num = 6
第二段代码
void main()
{
int num = 5;
if(++num >= 6)
{
printf("++num判断成功\n");
}
printf("num = %d",num);
}
运行结果为:
++num判断成功
num = 6
我们可以看到,只有++num通过了条件判断,而num++则被阻拦在了条件语句之外。但它们最终都实现了num的递增运算。这可以看出++num是先执行自增,再进行判断,而num++是先进行了判断,再执行自增。我们用下面这段代码来探索它内部的运作机制
void main ()
{ int num1 = 5;
int num2 = 5;
num1 = num1 ++;
num2 = ++num2;
printf("num1 = %d\n",num1);
printf("num2 = %d\n",num2);
}
运行结果为
num1 = 5
num2 = 6
为什么会出现这种情况?细心的朋友在编译的时候会发现,编译器在警告我们后置++的num1这个变量未声明(实际上前置++的num2也会报错,似乎是编译器的问题)。事实上,赋值符号左侧的num1已经不是我们之前声明的那个变量了,相当于一个同名的变量占用了原来变量的内存,你完全可以重新定义一个变量来接受自增的结果,会得到同样的值。我们可以这样理解:
num1 ++ 是 先将num1 的值赋值给一个临时变量temp,然后将num1的值加1,如果有后续运算的话,就都使用temp来运算。本例中就是将temp复制给新的num1变量。
实际上,变量的值在运算前是存储在栈中的,前置++是将num变量自增后的num+1直接压入num栈中,而后置++是先将变量num压入num的栈中,然后再将变量自增后的num+1再押入num的栈中,num+1会堆在num的上方,会优先被取出。
更详细的原理可参考某位不知名大佬的博文,原文没找到,只有这篇转载文前置++和后置++ 运算的详解及实例代码
关系运算符
关系运算符有 > 、< 、 <= 、>= 、== 、!=
它可以对两个变量、数值、字符、甚至表达式进行比较,一般在条件语句中,用来作为条件判断。
它的运算结果是一个数字,0表示假,非0表示真。(类似于_Bool类型,不过_Bool类型只能表示1个位(0和1),它会将任何非0的数值自动转成1)。我们尝试思考一下下面这段代码的运行结果:
void main()
{
int num = 0;
if(num = 0)
{
printf("武林至尊,宝刀屠龙");
}
else if(num =3)
{
printf(“号令天下,莫敢不从”);
}
else
{
printf("倚天不出,谁与争锋");
}
}
运行结果为:
if结构是根据条件语句中的运算值来决定是否执行语句块的,如果为0,就表示假,跳过后面的语句块。非0就表示条件语句为真,执行后面的语句块。
但这似乎只在c语言中行得通,java中的boolean类型的值是ture和false,编译器不会通过这种if条件中是数值的语句。
条件运算符的补充 - 三目运算符
运算符的目,是根据参与运算的数的个数来区分的,条件运算符是惟一有3个操作数的运算符。它相当于一个if语句,对条件进行判断,然后给出两种不同情况的值。比如:num = 5 > 6 ? 20 : 30
这个运算的含义是:先判断5>6是否为真,如果为真,那么结果为20,否则,结果为30;然后将右边的值赋值给左边的num。
但三目运算符更常用于下面这类判断:printf("%s\n",sex = 0?"女":"男");
值为0,打印“女”,否则打印“男”;可以在统计中使用。
逻辑运算符
运算符 | 表达式 | 说明 |
---|---|---|
&&而且 | 条件1 && 条件2 | 条件1和条件2同时为真,结果才为真;否则,结果为假 |
|| 或者 | 条件1 || 条件2 | 条件1或条件2为真,结果就为真;否则,结果为假 |
! 非 | ! 条件 | 条件为真时,结果为假;反之,结果为真 |
逻辑运算符 || 和 && 来进行多个条件判断,当一个条件难以满足时,就可以使用逻辑运算符,可以避免使用多个if语句来嵌套。某些情况下,问题从正面难以处理时,就可以使用 ! 运算符从问题的反面来考虑。值得注意的时,|| 运算符在前一个条件为真时,就不会执行下一个条件语句。同理,&&运算符在第一个条件为假时也不会继续执行,这就是所谓的短路运算。我们看下面这个例子:
int main()
{
int num = 5;
if(num > 4 || ++ num > 3)
{
}
printf("num = %d",num);
}
运行结果为:
num = 5
条件1满足后条件2就会被短路,虽然条件2也满足,但并未执行,所以++num操作没有成功。
运算符的优先级
我们上面讲了常用的四种运算符,那在具体运算中,究竟该先进行哪种运算呢?
单目运算符优先级最高: ! 、~(按位非,位运算符) 、 ++ 、-- 、 sizeof()、当然还有( ),使用括号可以调整运算优先级。
第二序列是 算术运算符 > 条件运算符 > 逻辑运算符。
优先级最低的是 赋值运算符。