目录
一. 前言
二. 各种类型操作符完成的操作
2.1 算数操作符
2.2 移位操作符
2.2.1 左移操作符
2.2.2 右移操作符
2.3 位操作符
2.3.1 按位与操作符 &
2.3.2 按位或操作 |
2.3.3 按位异或操作符 ^
2.4 赋值操作符
2.5 单目操作符(只有一个操作数)
2.5.1 sizeof:操作数的类型长度(字节为单位)
2.5.2 ~:按位取反操作符
2.5.3 ++、-- 操作符
2.5.4 取地址操作符 &
2.6 关系操作符
2.7 逻辑操作符
2.7.2 ||:逻辑或
2.8 三目操作符
2.9 逗号表达式(exp 1, exp 2 , ... , exp n)
2.10 下标引用操作符[ ]
2.11 结构体成员访问操作符 . ->
三. 表达式求值
3.1 整型提升
3.2 算数转换
在C语言中,每一条指令都有一个或多个操作符,来告诉指令应该进行什么操作。C语言为用户提供了各种类型的操作符来帮助用户完成相应的操作,是C语言程序设计中十分重要的一部分。本文详细汇总了C语言各种操作符的知识点。按照操作符的分类,介绍了不同操作符实现的功能以及表达式求值。从图1.1所示的思维导图可以清晰的看出C语言中操作符的分类以及每种分类下面具体有哪些操作符。
算数操作符较为基础,但应注意整数除法与小数除法的区分、只能对整数取模(对负整数也可以取模)。注意,若要进行小数除法,应将除数和被除数定义为float或double类型,同时将除法操作符左右两边的操作数之一写为小数形式。
//整数除法
int a = 3 / 5; //运算结果为0,而非0.6
int a = 6 / 5; //运算结果为1,而非1.2
//小数除法
float a = 6 / 5; //运算结果为1,
//若希望运算结果为1.2,则应将除法运算操作符的两个操作数其中至少一个写为小数形式
float a = 6.0 / 5; //运算结果为1.200000
//取模运算
int a = 7 % 3; //a = 1
int b = -5 % 2; //b = -1
左移操作符<< 右移操作符>>
左移操作符运算规则:高位丢弃、低位补0。 下面举例说明:
如图2.1所示的程序的打印结果为20,该程序进行的操作是将10的二进制序列左移一位,图2.2展示了左移操作的执行原理
对于右移操作符>>,其存在两种可能的情况:
1. 算数右移:低位舍弃,高位补原来的符号位
2. 逻辑右移:低位舍弃,高位补0
为了验证在集成编译环境VS2019下右移操作执行的是算数右移还是逻辑右移,编写如图2.3所示的程序,该程序进行将a=-1右移一位的操作,由于-1的最高位为1(代表负数),则通过分析该程序,可以得出执行的是算数右移。
分析过程如图2.4所示,我们已知整数的二进制序列在内存中存储的是其补码,故对a进行右移一位操作也是对其补码进行,假设高位补0,右移之后得到的二进制序列补码是1111....1111,对应的十进制数为-1,故验证可得此处是在进行算数右移。
这里拓展一些知识:
整型的二进制表示形式有三种:
1. 原码:直接根据数值写出的二进制序列为原码
2. 反码:原码符号不变,其他位按位取反就是反码
3. 补码:反码 + 1,就是补码
如图2.5所示的实例所示,对二进制序列进行按位与判断,若某一位上两个二进制序列均为1,则输出1,若其中一个为0或两个均为0,输出0。
如图2.6所示的实例所示,对二进制序列进行按位或判断,若某一位上两个二进制序列其中一个为1或均为1,则输出1,两个均为0,输出0。
如图2.7所示的实例,按位异或的操作规则是对应的二进制位相同为0相异为1。
复合赋值:+=、-=、*=、%=、^=、<<=、>>=
注意:C语言是支持连续赋值的,但一般不推荐使用连续赋值。如图2.8所示的程序,相当于降y+1赋值给x,再将x赋值给a。打印出的结果为x=11,a=11。
sizeof 可以对变量名和变量类型求大小,对变量名求大小是可以省略括号,但对变量类型求大小时不可以省略括号。
int a=10;
printf("%d\n",sizeof(a));
printf("%d\n",sizeof a); //sizeof对变量名求大小可不加括号
printf("%d\n",sizeof(int));//sizeof对变量类型求大小不可不加括号
sizeof可以对数组求大小:
int arr[10]={0};
printf("%d\n",sizeof(arr)); //计算结果为40
//int [10] 即为数组arr[10]的变量类型
printf("%d\n",sizeof(int [10])); //计算结果为40
在sizeof内部的表达式不参与计算,且有时要进行多余位的截断:
int main()
{
short s = 5;
int a = 10;
printf("%d\n", sizeof(s = a + 2)); //打印结果为2,a+2要放入s,s为短整型,占2个字节,s的空间不会因为a为int类型而发生改变,要进行高位截断
printf("%d\n", s); //在sizeof内部的表达式不参与运算。这里打印结果为5
return 0;
}
整型变量在内存中存的是其补码,在按位取反操作中也是对整型的补码按位取反,对补码取反后得到的二进制序列就是所求结果的原码。 按位取反操作符对符号位也取反。下面的代码输出结果为4,其执行逻辑如图2.9所示。
int main()
{
int a = -5;
int b = ~a;
printf("%d\n", b);//输出结果为4
return 0;
}
注意区分前置++(--)和后置++(--)的区别:
前置++:先++后使用 后置++:先使用后++ (-- 操作符同理)
int main()
{
int a1 = 10, a2 = 10;
int b = a1++;
int c = ++a2;
printf("%d\n", b); //打印10
printf("%d\n", c); //打印11
return 0;
}
通过&操作符,可以将变量存储在内存中的位置提取出来,%p可以用来打印地址(16进制输出),解引用操作符*可以通过访问地址来改变变量的值。
int main()
{
int a = 10;
printf("%p", &a); //打印地址用%p,16进制输出
int* pa = &a; //pa是用来存放地址的 -- pa为指针变量
*pa = 20; //*为解引用操作符(间接访问操作符),这里为通过访问地址更改a的值。
}
(5)强制类型转换操作符 (类型)
int a = (int)3.1415 //将3.1415强制转换为整型
主要的关系操作符有:>,<,>=,<=,!=,==
注意:量字符串比较不能用==,应该用strcmp(arr1,arr2)
如果:arr1 < arr2 ,返回值 <0
如果:arr1 > arr2,返回值 >0
如果:arr1 = arr2,返回值 =0
2.7.1 &&:逻辑与
对于逻辑与操作 exp1 && exp2,当exp1与exp2均为真时,整个表达式为真,否则为假。
对于逻辑或操作exp1 || exp2,但exp1与exp2其中之一为真或都为真时,整个表达式为真,exp1和exp2均为假时整个表达式为假。
注意:对于逻辑与操作符,若前面的表达式被判断为假,则不执行后面的表达式。如下面的程序所示,执行该程序打印的结果为1 2 3,因为第一个逻辑与操作符左边的等式(a++)已经被判别为假,所以后面的++b和c++均不执行。
int main()
{
int a = 0, b = 2, c = 3;
int i = a++ && ++b && c++;
printf("%d %d %d\n", a, b, c);//打印结果为1 2 3
return 0;
}
同理,对于逻辑或操作符||,若前面的表达式被判别为真,则不执行后面的表达式。
三目操作符仅包含条件操作符,之所以将其成为三目操作符,是因为其有三个操作数。
条件操作符(exp1 ? exp2 : exp3)运行规则:若exp1为真,则执行exp2运行,exp3不运行,整个表达式的值是exp2的值;若exp1为假,则exp3运行,exp2不运行,整个表达式的值是exp2的值。
例:利用三目操作符编写函数求a b中较大的变量:
int max(int a, int b)
{
return a > b ? a : b;
}
执行规则:从左到右依次运行,逗号表达式最终的结果是最后一个表达式的运行值。
运用逗号表达式可以避免代码过度冗余:
代码1:
a = get_val();
count_val(a);
while(a > 0)
{
a = get_val( );
count_val(a);
}
代码2:(改用逗号表达式)
while(a = get_val( ), count_val(a), a > 0)
{
;
}
可以明显看出,代码2与代码1等价,但代码2比代码1更加简洁明了。
用于查找一个数组中的特定元素(数组是通过下标进行访问的)
int arr[5]={1,2,3,4,5};
printf("%d\n",arr[2]);//[ ]为下标引用操作符。打印3
结构体成员访问有三种方式:
1. 通过操作符 . (点)直接访问,即:(结构体变量名).(结构体成员名)
2. 通过解引用结构体变量的地址来访问,即:(*结构体指针变量名).(结构体成员名)
3. 通过操作符->,直接应用于结构体指针访问,即:(结构体指针变量名)->(结构体成员名)
下面的代码以定义学生信息结构体为例,通过三种方式访问并打印结构体成员变量:
struct stu //定义结构体变量
{
char name[20];
int age;
double score;
};
int main()
{
struct stu s = { "张三",20,89.5 }; //初始化结构体变量
//直接访问结构体成员变量
printf("name = %s\n",s.name);
printf("age = %d\n", s.age);
printf("score = %lf\n", s.score);
printf("\n");
//通过解引用结构体指针访问结构体成员变量
struct stu* ps = &s;
printf("name = %s\n", (*ps).name);
printf("age = %d\n", (*ps).age);
printf("score = %lf\n", (*ps).score);
printf("\n");
//通过操作符->,直接应用于结构体指针访问结构体成员变量
printf("name = %s\n", ps->name);
printf("age = %d\n", ps->age);
printf("score = %lf\n", ps->score);
return 0;
}
表达式的求值顺序一部分是由操作符的优先级和结合性决定的。同时,操作数要进行相应的类型转换,主要的类型转换有整型提升(隐式类型转换)和算数转换。
以实例来说明整型提升的概念:
char a,b,c;
....
a = b + c;
b、c被提升为普通整型,然后再执行加法运算,加法运算完成后,结果先被截断,在存储于a中。
整型提升的意义:
表达式的运算要在CPU相应的运算器内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int字节长度,同时也是CPU内通用寄存器长度。因此,表达式中各种小于int长度的值,都必须先转换为int类型长度进行运算,然后再截断。
整型提升的过程示例:
int main()
{
char a = 3;
char b = 127;
char c = a + b; //发生整型提升
printf("%d\n", c); //打印 -126
return 0;
}
如果某个操作符的各个操作数属于不同类型,那么要将其中一个操作数转化为另一个操作数的类型,否则操作无法进行。
操作符相关知识点总结完毕,感谢大家的阅读,敬请批评指正!