C语言中奇妙又有趣的符号——C语言运算(操作)符

428 阅读16分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情


⭐️前面的话⭐️

C语言运算符是说明特定操作的符号 ,所以它也被称作为操作符,它是构造C语言表达式的工具 。C语言的运算异常丰富,除了控制语句和输入输出以外的几乎所有的基本操作都为运算符处理。除了常见的三大类,算术运算符、关系运算符与逻辑运算符之外,还有一些用于完成特殊任务的运算符,比如位运算符。

📒博客主页:未见花闻的博客主页
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
📌本文由未见花闻原创!
📆掘金首发时间:🌴2022年6月19日🌴
✉️坚持和努力一定能换来诗与远方!
💭参考书籍:📚《C语言》相关
💬参考在线编程网站:🌐牛客网🌐力扣
博主的码云gitee,平常博主写的程序代码都在里面。
博主的github,平常博主写的程序代码都在里面。
🍭作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!


1.C语言运算符

1.1运算符概述

C语言运算符是说明特定操作的符号 ,所以它也被称作为操作符,它是构造C语言表达式的工具 。C语言的运算异常丰富,除了控制语句和输入输出以外的几乎所有的基本操作都为运算符处理。除了常见的三大类,算术运算符、关系运算符与逻辑运算符之外,还有一些用于完成特殊任务的运算符,比如位运算符。

1.2运算符分类

在这里插入图片描述

1.3运算符优先级

优先级一共可以划分为15级,1级优先级最高,15级优先级最低。 在这里插入图片描述

同一优先级的运算符,运算次序由结合方向所决定。

2.C语言运算符超全总结

2.1算数运算符

+:加法
-:减法
*:乘法
/:除法
%:取模(求余数)
++:自增运算,i++++i都相当于i=i+1,但是i++先使用再++,++i是先++再使用。
--:自减运算,用法和++相同。

  1. 除了 % 运算符之外,其他的几个运算符可以作用于整数和浮点数。
  2. 对于 / 运算符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
  3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。

OJ例题1: 💡BC17 计算表达式的值

描述 请计算表达式“(-8+22)×a-10+c÷2”,其中,a = 40,c = 212。 输入描述: 无。 输出描述: (-8+22)×a-10+c÷2计算之后的结果,为一个整数。

#include <stdio.h>
int main(){
    int a = 40,c = 212,b = 0;
    b=(-8+22)*a-10+c/2;
    printf("%d",b);
    return 0;
}

OJ例题2: 💡BC18 计算带余除法

描述 给定两个整数a和b (-10,000 < a,b < 10,000),计算a除以b的整数商和余数。 输入描述: 一行,包括两个整数a和b,依次为被除数和除数(不为零),中间用空格隔开。 输出描述: 一行,包含两个整数,依次为整数商和余数,中间用一个空格隔开。

输入:
15 2
输出:
7 1
#include <stdio.h>
int main(){
    int a=0,b=0,c=0,d=0;
    scanf("%d %d",&a,&b);
    c=a/b;
    d=a%b;
    printf("%d %d",c,d);
    return 0;
}

OJ例题3: 💡BC27 计算球体的体积

描述 给定一个球体的半径,计算其体积。其中球体体积公式为 V = 4/3*πr3,其中 π= 3.1415926。 输入描述: 一行,用浮点数表示的球体的半径。 输出描述: 一行,球体的体积,小数点后保留3位。

输入:
3.0
输出:
113.097
#include <stdio.h>
int main(){
    double V=0,r=0,pi=3.1415926;
    scanf("%lf",&r);
    V=4.0/3*pi*pow(r,3);
    printf("%.3lf",V);
    return 0;
}

OJ例题4: 💡BC129 小乐乐计算函数 在这里插入图片描述

输入:
1 2 3
输出:
0.30
#include <stdio.h>

int max_3(int a, int b, int c)
{
    int max = 0;
    if (a > max)
        max = a;
    if (b > max)
        max = b;
    if (c > max)
        max = c;
    return max;
}
int main()
{
    int a = 0;
    int b = 0;
    int c = 0;
    double m = 0;
    scanf("%d%d%d",&a,&b,&c);
    int m1 = max_3(a + b, b, c);
    int m2 = max_3(a, b + c, c);
    int m3 = max_3(a, b, b + c);
    m = 1.0*m1 / (m2 + m3);
    printf("%.2lf\n",m);
    return 0;
}

2.2关系运算符

>:大于
<:小于
== :等于
>= :大于等于
<= :小于等于
!=:不等于
这些运算符用来构成关系表达式,用来做if分支语句或者循环语句的表达式。
OJ例题: 💡BC49 判断两个数的大小关系

描述 KiKi想知道从键盘输入的两个数的大小关系,请编程实现。 输入描述: 题目有多组输入数据,每一行输入两个整数(范围(1 ~231-1),用空格分隔。 输出描述: 针对每行输入,输出两个整数及其大小关系,数字和关系运算符之间没有空格,详见输入输出样例。

在这里插入图片描述

#include <stdio.h>
int main()
{
    int a = 0;
    int b = 0;
    while(scanf("%d %d",&a,&b) != EOF)
    {
        if (a == b)
        {
            printf("%d=%d\n",a,b);
        }
        else if (a > b)
        {
            printf("%d>%d\n",a,b);
        }
        else
        {
            printf("%d<%d\n",a,b);
        }
    }
    return 0;
}

2.3逻辑运算符

! :逻辑非。
&&:逻辑与,两个条件都为真表达式才为真。
||:逻辑或,两个条件都为假表达式才为假。
用于构成逻辑表达式,在分支语句和循环语句用的多。
需要注意的逻辑或||与逻辑与&&会出现短路效应。 比如

int a = 2;
int b = 3;
int c = 6;
a == 2 || b == 4;//逻辑或:对a进行判断如果为真,就不会判断后面的b是否等于4
b == 4 && c == 6;//逻辑与:对b进行判断如果为假,就不会判断后面的c是否等于6

OJ例题: 💡BC51 三角形判断

描述 KiKi想知道已经给出的三条边a,b,c能否构成三角形,如果能构成三角形,判断三角形的类型(等边三角形、等腰三角形或普通三角形)。 输入描述: 题目有多组输入数据,每一行输入三个a,b,c(0<a,b,c<1000),作为三角形的三个边,用空格分隔。 输出描述: 针对每组输入数据,输出占一行,如果能构成三角形,等边三角形则输出“Equilateral triangle!”,等腰三角形则输出“Isosceles triangle!”,其余的三角形则输出“Ordinary triangle!”,反之输出“Not a triangle!”。

提示:所有的三角形都要满足两边之和大于第三边。

输入:
2 3 2
3 3 3
输出:
Isosceles triangle!
Equilateral triangle!
#include <stdio.h>
int main()
{
    int a = 0;
    int b = 0;
    int c = 0;
    while(scanf("%d %d %d",&a,&b,&c) != EOF)
    {
        if ((a + b >c) && (b + c > a) && (a + c > b))
        {
            if ((a == b) && (b == c))
                printf("Equilateral triangle!\n");
            else if ((a == b ) || (a == c) || (b == c))
                printf("Isosceles triangle!\n");
            else
                printf("Ordinary triangle!\n");
        }
        else
        {
            printf("Not a triangle!\n");
        }
    }
    return 0;
}

2.4位运算符

<<:左移
整数在计算机中储存的是他的二进制补码,<<能够将它的二进制序列左移,末位使用0补齐。

操作数 << 移动位数

比如数字2,补码为
0000 0000 0000 0000 0000 0000 0000 0010 2<<1运算后,得
0000 0000 0000 0000 0000 0000 0000 0100
为数字4
>>:右移
整数在计算机中储存的是他的二进制补码,>>能够将它的二进制序列右移,如果为算术右移则负数首位补1,正数首位补0。如果为逻辑右移,首位通通补0。至于是算术右移还是逻辑右移,不同编译器不同,VS2019为算术右移。

操作数 >> 移动位数

比如-1,原码为
1000 0000 0000 0000 0000 0000 0000 0001
取反,得
1111 1111 1111 1111 1111 1111 1111 1110
1,得补码
1111 1111 1111 1111 1111 1111 1111 1111
-1>>1运算后得
算术右移
1111 1111 1111 1111 1111 1111 1111 1111
逻辑右移
0111 1111 1111 1111 1111 1111 1111 1111
按VS2019算术右移转化为原码得
1000 0000 0000 0000 0000 0000 0000 0001
为数字-1
~:按位取反,将一个数二进制序列每位都取反。

~操作数

比如数字3,补码为
0000 0000 0000 0000 0000 0000 0000 0011
~3运算后得
1111 1111 1111 1111 1111 1111 1111 1100
为一个负数,先将补码还原成原码
1,得
1111 1111 1111 1111 1111 1111 1111 1011
除最高位取反
1000 0000 0000 0000 0000 0000 0000 0100
为数字-4
|:按位或,对两个数的二进制序列每位比较都为0则为0,否则为1。\

操作数 | 操作数

比如2 | 3
0000 0000 0000 0000 0000 0000 0000 0010
0000 0000 0000 0000 0000 0000 0000 0011
结果为
0000 0000 0000 0000 0000 0000 0000 0011
为数字3
^:按位异或,对两个数的二进制序列每位比较不相同为1,否则为0

操作数 ^ 操作数

比如2 ^ 3
0000 0000 0000 0000 0000 0000 0000 0010
0000 0000 0000 0000 0000 0000 0000 0011
结果为
0000 0000 0000 0000 0000 0000 0000 0001
为数字1
&:按位与,对两个数的二进制序列每位比较都为1则为1,否则为0

操作数 & 操作数

比如2 & 3
0000 0000 0000 0000 0000 0000 0000 0010
0000 0000 0000 0000 0000 0000 0000 0011
结果为
0000 0000 0000 0000 0000 0000 0000 0010
为数字2
上面几种运算符的操作对象必须为整数。

#include <stdio.h>

int main()
{
	printf("%d\n", 2 << 1); //4,2左移1位
	printf("%d\n", -1 >> 1);//-1,-1算术右移一位
	printf("%d\n", ~ 3);//-4,对3按位取反
	printf("%d\n", 2 | 3);//3,2|3
	printf("%d\n", 2 ^ 3);//1,2^3
	printf("%d\n", 2 & 3);//2,2&3

	return 0;
}

在这里插入图片描述 OJ例题: 💡BC29 2的n次方计算

描述 不使用累计乘法的基础上,通过移位运算(<<)实现2的n次方的计算。 输入描述: 多组输入,每一行输入整数n(0 <= n < 31)。 输出描述: 针对每组输入输出对应的2的n次方的结果。

输入:
2
10
输出:
4
1024
#include <stdio.h>
int main(){
    int a=0;
    while(scanf("%d",&a) != EOF){
        printf("%d\n",1<<a);
    }
    return 0;
}

2.5赋值运算符

=:赋值,比如a = 2a = c
复合赋值运算符:+= -= /= *= %= <<= >>= |= &= ^=
a += 2相当于 a = a + 2,其他的以此类推。

OJ例题: 💡BC20 kiki算数

描述 问题:KiKi今年5岁了,已经能够认识100以内的非负整数,并且并且能够进行 100 以内的非负整数的加法计算。不过,BoBo老师发现KiKi在进行大于等于100的正整数的计算时,规则如下:

  1. 只保留该数的最后两位,例如:对KiKi来说1234等价于34;
  2. 如果计算结果大于等于 100, 那么KIKI也仅保留计算结果的最后两位,如果此两位中十位为0,则只保留个位。 例如:45+80 = 25 要求给定非负整数 a和 b,模拟KiKi的运算规则计算出 a+b 的值。

输入描述: 一行,输入两个非负整数a和b,用一个空格分隔。(0 <= a,b<= 231-1)。 输出描述: 针对每组输入,输出按照KiKi的运算规则计算出 a+b 的值。

输入:
45 80
输出:
25
#include <stdio.h>
int main(){
    int a=0,b=0,sum=0;
    scanf("%d %d",&a,&b);
    a%=100;
    b%=100;
    sum=a+b;
    if(sum>=100)
        sum%=100;
    printf("%d",sum);
    return 0;
}

2.6条件运算符

? : :条件运算符,也被称为三目运算符,C语言中唯一一个三目运算符。

if (a > b)
	return a;
else
	return b;
//相当于
a > b ? a : b;

2.7逗号运算符

,:逗号运算符,就是用逗号隔开的多个表达式。从左向右依次执行,整个表达式的结果是最后一个表达式的结果。

int main()
{
	int a = 3;
	int b = 2;
	int c = 6;
	int d = (a + b, b + c, a + c);//从左至右依次进行,d的值为最后一个表达式结果,也就是9
	return 0;
}

2.8指针运算符

* :解引用运算符,用于访问一个指针所指向空间内的数据。

&:取地址运算符,用于得到一个数据或数据类型的地址。

#include <stdio.h>

int main()
{
	int a = 68;
	int* pa = &a;
	printf("%p\n", pa);
	printf("%d\n", *pa);
	printf("%d\n", a);
	return 0;
}

在这里插入图片描述

2.9求字节运算符

sizeof:用来计算一个数据或数据类型所占字节大小。

#include <stdio.h>

int main()
{
	int a = 9;
	double pi = 3.14;
	char c = "w";
	float b = 2.3f;
	int* pa = &a;
	char* pc = &c;
	double* ppi = &pi;
	printf("%d\n", sizeof(int));
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(double));
	printf("%d\n", sizeof(pi));
	printf("%d\n", sizeof(char));
	printf("%d\n", sizeof(c));
	printf("%d\n", sizeof(float));
	printf("%d\n", sizeof(b));
	printf("%d\n", sizeof(int*));
	printf("%d\n", sizeof(double*));
	printf("%d\n", sizeof(char*));
	printf("%d\n", sizeof(ppi));
	printf("%d\n", sizeof(pc));
	return 0;
}

在这里插入图片描述

2.10强制类型转换运算符

(数据类型):将一个数据强制转换成想要的数据类型。 比如

int a = 6;
double b = 3.14;
int c = (int) b;
double d = (double) a;
a = (double) a;//6.000000
b = (int) b;//3

2.11成员运算符

.:访问结构体成员,结构体.成员名
->:访问结构体成员,结构体指针 -> 成员名

#include <stdio.h>

struct Book
{
	char book_name[40];
	int book_price;
	char book_id[20];
};
int main()
{
	struct Book C = { "C语言运算符详解" ,49, "9635419049866812" };
	printf("%s\n", C.book_name);
	printf("%s\n", (&C)->book_name);
	printf("%d\n", C.book_price);
	printf("%d\n", (&C)->book_price);
	printf("%s\n", C.book_id);
	printf("%s\n", (&C)->book_id);

	return 0;
}

在这里插入图片描述

2.12下标运算符

[ ]:作为数组下标,可以用来访问数组元素。

#include <stdio.h>

int main()
{
	int a[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);//[]作为下标,访问数组元素

	}
	return 0;
}

在这里插入图片描述

2.13其他运算符

如函数调用符( )。

printf("%...", ...);
scanf("%...", &...);
//等等

3.C语言中的表达式

3.1关系,逻辑,条件表达式

具体内容见博主另一篇文章:C语言的选择与轮回——选择结构与循环结构1.3表达式部分。

3.2隐式类型转换和算术转换

3.2.1隐式类型转换

C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,然后才能送入CPU去执行运算。

整形提升是按照变量的数据类型的符号位来提升的
//负数的整形提升 
char c1 = -1; 
变量c1的二进制位(补码)中只有8个比特位: 
1111111 
因为 char 为有符号的 char 
所以整形提升的时候,高位补充符号位,即为1 
提升之后的结果是: 
11111111111111111111111111111111 
//正数的整形提升 
char c2 = 1; 
变量c2的二进制位(补码)中只有8个比特位: 
00000001 
因为 char 为有符号的 char 
所以整形提升的时候,高位补充符号位,即为0 
提升之后的结果是: 
00000000000000000000000000000001 
//无符号整形提升,高位补0 

3.2.2算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。也称自动类型转换

如果一个运算符两侧的数据类型不同,则先自动进行类型转换,使二者成为同一种类型,然后进行运算。整型、实型、字符型数据间可以进行混合运算。规律为:

💡+、-、*、/运算的两个数中有一个数为float或double型,结果是double型,因为系统将所有float型数据都先转换为double型,然后进行运算。
💡如果int型与float或double型数据进行运算,先把int型和float型数据转换为double型,然后进行运算,结果是double型。
💡字符(char)型数据与整型数据进行运算,就是把字符的ASCII代码与整型数据进行运算。如果字符型数据与实型数据进行运算,则将字符的ASCII代码转换为double型数据,然后进行运算 。

如果赋值运算符两侧的类型一致,则直接进行赋值。 如果赋值运算符两侧的类型不一致,但都是基本类型时,在赋值时要进行类型转换。类型转换是由系统自动进行的,转换的规则是:

💡将浮点型数据(包括单、双精度)赋给整型变量时,先对浮点数取整,即舍弃小数部分,然后赋予整型变量,存在精度丢失。
💡将整型数据赋给单、双精度变量时,数值不变,但以浮点数形式存储到变量中。
💡将一个double型数据赋给float变量时,先将双精度数转换为单精度,即只取6~7位有效数字,存储到float型变量的4个字节中。应注意双精度数值的大小不能超出float型变量的数值范围;将一个float型数据赋给double型变量时,数值不变,在内存中以8个字节存储,有效位数扩展到15位。
💡字符型数据赋给整型变量时,将字符的ASCII代码赋给整型变量。
💡将一个占字节多的整型数据赋给一个占字节少的整型变量或字符变量时,只将其低字节原封不动地送到被赋值的变量(即发生“截断”)。