C语言 操作符详解
Posted 跳动的bit
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言 操作符详解相关的知识,希望对你有一定的参考价值。
文章目录
前言
在之前学习的过程中,有大致了解过一些操作符,但还有一些操作符没见过,所以这篇文章将会把操作符都过一遍。本文章所有代码依赖的平台:(Visual Studio2017 && Windows)
一、算术操作符
+ - * / %
算术操作符这里加减乘除就不过多的了解了
这里需要注意的有2点:
a. / 和 %的区别
#include<stdio.h>
int main()
{
int a1 = 3 / 5;//取商
int a2 = 3 % 5;//取余
printf("a1 = %d\\na2 = %d\\n", a1, a2);//a1 = 0; a2 = 3;
return 0;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
int main02()
{
int a = 5 / 3.0;//ok
//int a = 5 % 3.0;//err
return 0;
}
b. 小数除法
#include<stdio.h>
int main()
{
//观察a1、a2、a3的结果
float a1 = 3.0 / 5;
float a2 = 3 / 5.0;
float a3 = 3.0 / 5.0;
printf("a1 = %.1f\\na2 = %.1f\\na3 = %.1f\\n", a1, a2, a3);//a1,a2,a3 = 0.6
return 0;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//当执行上面上面这段程序时 - warning:从double到float截断
//因为直接写6.0(没有定义的情况)这种小数时,编译器会默认为double类型
//更适合的写法:
//float a1 = 3.0f / 5f;
//double a2 = 3.0 / 5;
小结:
- / 和 % 都是执行除法运算,但是 / 算的是商,%算的是余数
- 除了 % 操作符之外 ,其它的操作符可以用于整数和浮点数;% 操作符的2个操作数必须为整数
- / 操作符如果2个操作数都为整数,执行整数除法;而只要有任何1个操作数为float类型或doble类型,执行的是小数除法
二、移位操作符
<< >>
<<:左移操作符
#include<stdio.h>
int main()
{
int a = 2;
//把a的二进制位向左移动1位
int b = a << 1;
printf("b = %d\\n", b);//4
//注意在对a进行移位后,并不会改变a本身
printf("a = %d\\n", a);//2
return 0;
}
`>>:右移操作符:
1.算术右移:右边丢弃,左边补原符号位
2.逻辑右移:右边丢弃,左边补0
/***********************************************************************
目的:验证当前机器是算术右移还是逻辑右移
分析:在右移操作符下,正数的算术右移和逻辑右移是一样的。所以这里采用负数来验证。(对于移位操作时,移动的是二进制补码:而正数的原反补相同,移动原码 = 移动补码;而负数需要转换为补码再移动)
平台:Visual studio 2017 && windows
*************************************************************************/
整数的二进制表示形式:
原码:直接根据数值写出的二进制序列就是原码(相当于屏幕上能看到的数)
反码:原码的符号位不变,其它位按位取反
补码:反码+1 (内存中的存储形式)
#include<stdio.h>
int main()
{
int a = -1;
//把a的二进制位向右移动1位
//由些可见,当前编译器采用的是算术右移
int b = a >> 1;
printf("b = %d\\n", b);//-1
return 0;
}
注意:
int a = 10;
int b = a << -5; // err,这是标准未定义的语法。属于垃圾代码
小结:
- 移位操作符移动的是二进制位
- 左移操作符遵循左边丢弃,右边补0的原则
- 右移操作符则有2个情况:1.算术右移(右边丢弃,左边补原符号位) 2.逻辑右移(右边丢弃,左边补0)
经验证当前机器使用算术右移
三、位操作符
& | ^
#include<stdio.h>
//&:按(二进制位)位与 -> 对应的二进制位有1个或2个为0,则为0;否则为1
int main()
{
int a = 3;
int b = 5;
//...011 -> 3
//...101 -> 5
//...001 -> 1
int c = a & b;
printf("c = %d\\n", c);//1
return 0;
}
//|:按(二进制位)位或-> 对应的二进制位有1个或2个为1,则为1;否则为0
int main02()
{
int a = 3;
int b = 5;
//...011 -> 3
//...101 -> 5
//...111 -> 7
int c = a | b;
printf("c = %d\\n", c);//7
return 0;
}
//^:按(二进制)位异或 -> 对应的二进制位,相同为0,相异为1
int main03()
{
int a = 3;
int b = 5;
//...011 -> 3
//...101 -> 5
//...110 -> 6
int c = a ^ b;
printf("c = %d\\n", c);//6
return 0;
}
小结:
- &:按(二进制位)位与:对应的二进制位,同真为真,其余为假
- | :按(二进制位)位或:对应的二进制位,同假为假,其余为真
- ^:按(二进制)位异或 :对应的二进制位,相同为0,相异为1
- 它们的操作数必须为整数
四、赋值操作符
= += -= *= /= %= >>= <<= &= |= ^=
1.连续赋值
int main()
{
int a = 10;
int x = 0;
int y = 20;
//a = x = y + 1;//连续赋值语法支持,但不建议这样写
//这样写可读性更高
x = y + 1;
a = x;
return 0;
}
2.复合赋值,这里就介绍1个,其它的复合赋值都是相同的用法
int main()
{
int a = 10;
a = 100;
//a = a + 100;
a += 100;//复合赋值 -> 同a = a + 100
return 0;
}
3.注意:=是赋值操作符
==等于
五、单目操作符
! - + & sizeof ~ – ++ * (类型)
注:单目操作符只有一个操作数
#include<stdio.h>
//!:非,即真为假,假为真
int main()
{
int flag = 5;
if(flag)
printf("hehe\\n");
if(!flag)
printf("haha\\n");
return 0;
}
//-----------------------------------------------------------------------------
//-:负
int main02()
{
int a = 10;
a = -a;
printf("%d\\n", a);//-10
return 0;
}
//-----------------------------------------------------------------------
//sizeof:计算变量所占内存的大小(单位:字节)
int main03()
{
int a = 10;
//sizeof的三种形式:
printf("%d\\n", sizeof(a));
printf("%d\\n", sizeof(int));
printf("%d\\n", sizeof a);//有的人会认为sizeof是一个函数:函数名()。这种写法说明了sizeof是一个操作符
//printf("%d\\n", sizeof int);//err
return 0;
}
//sizeof计算数组大小
int main04()
{
int arr[10] = {0};
printf("%d\\n", sizeof(arr));
printf("%d\\n", sizeof arr);
printf("%d\\n", sizeof(int [10]));//sizeof(数据类型);对于数组的类型把数组名去掉,剩下的就是数据类型
return 0;
}
//观察下面程序,分析结果?
int main05()
{
short s = 5;
int a = 10;
printf("%d\\n", sizeof(s = a + 2));//2
printf("%d\\n", s);//5
return 0;
//sizeof括号中放的表达式是不参与运算的
}
//-----------------------------------------------------------------------
//~:按(二进制位)位取反
int main06()
{
int a = -1;
//10000000 00000000 00000000 00000001 - 原
//11111111 11111111 11111111 11111110 - 反
//11111111 11111111 11111111 11111111 - 补
//按位取反(包括符号位):
//00000000 00000000 00000000 00000000
int b = ~a;
printf("%d\\n", b);//0
printf("%d\\n", a);//-1 - 并不会改变a
return 0;
}
//++:自增
int main07()
{
int a1 = 10;
int b1 = a1++;//后置++,先使用,再++
printf("a1 = %d\\n", a1);//11
printf("b1 = %d\\n", b1);//10
//++++++++++++++++++++++++++++++++++++++++++++
int a2 = 10;
int b2 = ++a2;//前置++,先++,后使用
printf("a2 = %d\\n", a2);//11
printf("b2 = %d\\n", b2);//11
return 0;
//++++++++++++++++++++++++++++++++++++++++++++++
//--也是如此
int a3 = 10;
printf("%d\\n", a3--);//10
printf("%d\\n", a3);//9
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++
//对于自增或自减,可能会见到很多一些没有意义的代码,甚至是错误的:
int main08()
{
int a = 1;
int b = (++a) + (++a) + (++a);
printf("%d\\n", b);
return 0;
//结果(已验)
//在VS2017下结果是12
//在Linux_Ubuntu下结果是10
}
//--------------------------------------------------------
//&:取地址
//*:指针
int main09()
{
int a = 10;
printf("%p\\n", &a);//取a的地址以十六进制打印
int* pa = &a;//将a的地址存放于pa,pa就是一个指针变量,pa的类型就是int*
*pa = 20;//这里的* - 解引用操作符或间接访问操作符;这里就是通过pa存的地址找到a,并把a赋值20
printf("%d\\n", 20);//20;
return 0;
//这里注意区别&:按位与;&:取地址。当&有2个操作数时,它就是按位与;当&仅有1个操作数时,它就代表取地址
}
//--------------------------------------------------------
//(类型):强制类型转换
int main10()
{
int a1 = 3.14;//warning:从double类型转换到int可能会丢失数据
int a2 = (int)3.14;//将double类型的数据强转为int
return 0;
}
六、关系操作符
> >= < <= != ==
对于关系操作符这里没啥可讲的,相对简单。值的注意的是:
1.==是判断相等;而=是赋值
2.==不能比较2个字符串相等
七、逻辑操作符
&& ||
#include<stdio.h>
int main()
{
int a = 3;
int b = 0;
if(a && b)//同真为真,其余为假。这里只要有1个为假,则为假
{
printf("hehe\\n");
}
if(a || b)//同假为假,其余为真。这里只要有1个为真,则为真
{
printf("haha\\n");
}
return 0;
}
八、条件操作符
exp1 ? exp2 :exp3
注:也被称为三目操作符
#include<stdio.h>
int main()
{
int a = 3;
int b = 0;
if(a > 5)
b = 1;
else
b = -1;
//上面的if...else语句使用三目操作符只要一句代码:
//三目操作符:表达式1的结果为真,则执行表达式2,否则执行表达式3
b = (a > 5 ? 1 : -1);
return 0;
}
九、逗号表达式
exp1, exp2, exp3, … expN
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
int c = 0;
//逗号表达式:从左向右依次计算,但是整个表达式的结果是最后一个表达式的结果,而最后一个表达式的结果可能会受到前面表达式的影响
int d = (c = 5, a = c + 3, b = a - 4, c += 5);
printf("%d\\n", d);//10
return 0;
}
十、下标引用、函数调用和结构成员
[ ] () . ->
#include<stdio.h>
//[]:下标引用操作符,有两个参数:第一个是数组名,第二个是元素大小或索引值
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};//这里的[]里面是元素个数
printf("%d\\n", arr[4]);//数组名[索引值] -> 可以访问数组元素
return 0;
}
//--------------------------------------------------------
//():函数调用操作符,接受一个或多个操作数:第一个操作数是函数名,剩余的操作数是传递给函数的参数
//定义函数
int Add(int x, int y)
{
return x + y;
}
int main01()
{
int a = 10;
int b = 20;
//调用函数
int ret = Add(a, b);//这里的()就是函数调用操作符
printf("%d\\n", ret);
return 0;
}
//--------------------------------------------------------
//.:
//->
//这里先介绍结构体:struct:
//定义结构体类型
struct Book
{
char name[20];
char id[20];
int price;
};
int main02()
{
//对于生活中的一些事物,我们要怎样描述它的信息:(这些信息又是不同的类型的,有字符串,整型等等。这时C语言提供了struct结构体关键字,它可以囊括不同的数据类型)
//书:书名、书号、作者、定价、书号
//人:名字、年龄、性别
//初始化结构体:通过之前的了解我们知道初始化变量:int a = 10 -> 类型 + 变量 = 值 -> 所以这里结构体的类型就是struct Book
struct Book b = {"C语言", "cc202005011", "55"};
//打印结构体成员
//1.使用.来访问结构体成员
printf("书名:%s\\n", b.name);//通过(变量+.+成员名)可以访问结构体成员
printf("书号:%s\\n", b.id);
printf("定价:%d\\n", b.price);
printf("-------------分割线-------------\\n");
//2.使用*和.来访问结构体成员
struct Book* pb = &b;//存储结构体的地址
printf("书名:%s\\n", (*pb).name);
printf("书号:%s\\n", (*pb).id);
printf("定价:%d\\n", (*pb).price);
printf("-------------分割线-------------\\n");
//3.使用->来访问结构体成员
printf("书名:%s\\n", pb -> name);//通过(指针变量 -> 成员名)可以访问结构体成员
printf("书号:%s\\n", pb -> id);
printf("定价:%d\\n", pb -> price);
return 0;
}
十一、操作符的优先级
在程序设计中也是有操作符的优先级的,同加减乘除,优先级高的先算。
影响表达式求值的有三个因素:
1.操作符的优先级
2.操作符的结合性(优先级相同的情况下,结合性决定了运算顺序)
3.是否控制求值顺序(比如&&,如果左边为假,则整体为假)
以下整理了C语言中操作符的优先级:
注:N\\A是无结合性
L-R是从左向右
R-L是从右向左
1、一些有问题的表达式
此类表达式没有办法确定唯一的计算路径,未来应该避免这类的表达式
a * b + c * d + e * f;
这个表达式执行的顺序可能就有2种:
c + --c;
比如:c = 3时:
同上,操作符的优先级只能决定–的运算在+的运算的前面,但是我们不知道,+操作符的左操作数的获取是在右操作数的之前还是之后的值,所以是有歧义的。
此代码来自《C和指针》
int main()
{
int i = 10;
i = i-- - --i * (i = -3) * i++ + ++i;
printf(“i = %d\\n”, i);
return 0;
}
经作者的验证:相同的代码在不同的编译器下跑出的结果大不相同
int fun ()
{
static int count = 1;//注意count通过static修饰后,下一次进来时并不会销毁
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf("%d\\n", answer);
return 0;
}
在VS2017下的结果为2 - 3 * 4 = -10
3、… …
对于fun()函数的调用有可能会有不同的先后调用顺序
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\\n", ret);
}
在VS2017中是12 -> 4 + 4 + 4:
在Linux下是10 -> 3 + 3 + 4:
总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那个就是存在问题的。
十二、趁热打铁
1、交换变量,不能使用第3个变量(出自品茗股份c++笔试题)
/***********************************************************************
目的:不能创建临时变量(第3个变量),实现2个数的交换
分析:1.借助2数之和与变量的运算:
2.使用异或:
平台:Visual studio 2017 && windows
*************************************************************************/
实现代码1:
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
printf(a = %d b = %d\\n", a, b);//3 5
a = a + b20160206.CCPP体系详解(0016天)