详细讲解 —— 操作符(C语言初阶)(万字长文)

Posted IT技术博主-方兴未艾

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详细讲解 —— 操作符(C语言初阶)(万字长文)相关的知识,希望对你有一定的参考价值。

1、算术操作符

+(加)  -(减)  *(乘)  /(除)    %(取余)

除法操作符 —— " / "

#include<stdio.h>
int main()
{
    int a = 5 / 2;  //对于/操作符,如果两个操作数都为整数,执行整数除法。
	float b = 5 / 2;
	float c = 5 / 2.0;   //对于/操作符,如果有浮点数,执行浮点数除法。
	printf("%d\\n", a);
	printf("%f\\n", b);
	printf("%f\\n", c);
	return 0;
}

取余操作符 —— %

#include<stdio.h>
int main()
{
	int a = 5 % 2;  //% 操作符的两个操作数必须为整数。返回的是整除之后的余数。
	//int b = 5.0 % 2.0; 这样写是错误的,编译器会报错的
	printf("%d\\n", a);
	return 0;
}

除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。

2、移位操作符

整型在内存中的储存:

整型有 —— 原码、反码、补码 —— 三种类型 —— 都是用二进制写出来的

正整数 :—— 原码、反码、补码相同。

负数:—— 原码、反码、补码不相同。

原码 —— 直接用二进制写出来的数字。

反码 —— 符号位不变,其它位按位取反。

补码 —— 反码+1。

在内存中储存的是补码

#include<stdio.h>
int main()
{
	int a = 5;
	//00000000000000000000000000000101 —— 原码 —— 反码 —— 补码

	int b = -1;
	//10000000000000000000000000000101 —— 原码
	//11111111111111111111111111111110 —— 反码 —— 符号位不变,其它位按位取反
	//11111111111111111111111111111111 —— 补码 —— 反码加一
	return 0;
}

左移操作符和右移操作符:

—— 左移操作符和右移操作符都是对二进制进行移位

<<(左移操作符)    >>(右移操作符)

左移操作符

—— 移位规则 —— 左边抛弃、右边补0

#include<stdio.h>
int main()
{
   int a = 5;
   int b = a << 1; //10
   //00000000000000000000000000000101 —— 原码 —— 反码 —— 补码
   //00000000000000000000000000001010 —— 左边抛弃,右边补零

   int c = -1;
   int d = c << 1; //-2
   //10000000000000000000000000000101 —— 原码
   //11111111111111111111111111111110 —— 反码 —— 符号位不变,其它位按位取反
   //11111111111111111111111111111111 —— 补码 —— 反码加一
   //11111111111111111111111111111110 —— 补码执行左移操作符 —— 补码
   //11111111111111111111111111111101 —— 反码 —— 补码-1
   //10000000000000000000000000000010 —— 原码 —— 反码符号位不变,其它位按位取反
   printf("%d\\n", b);
   printf("%d\\n", d);
   return 0;
}

右移操作符

—— 移位规则 ——

  1. 逻辑移位 —— 左边用0填充,右边丢弃
  2. 算术移位 —— 左边用原该值的符号位填充,右边丢弃

大多数的的编译器都是算术右移 —— VS2019也是用算术右移的

#include<stdio.h>
int main()
{
	int a = 5;
	int b = a >> 1; //2
	//00000000000000000000000000000101 —— 原码 —— 反码 —— 补码
	//00000000000000000000000000000010 —— 左边补原来数字的符号0,右边抛弃

	int c = -1;
	int d = c >> 1; //-1
	//10000000000000000000000000000101 —— 原码
	//11111111111111111111111111111110 —— 反码 —— 符号位不变,其它位按位取反
	//11111111111111111111111111111111 —— 补码 —— 反码加一
	// 
	//11111111111111111111111111111111 —— 补码执行右移操作符 —— 补码
	//11111111111111111111111111111110 —— 反码 —— 补码-1
	//10000000000000000000000000000001 —— 原码 —— 反码符号位不变,其它位按位取反

	printf("%d\\n", b);
	printf("%d\\n", d);
	return 0;
}


注:

  1. a >> 1 等价于 a / 2 ———— a << 1 等价于 a * 2
  2. a >> 2 等价于 a / 2 / 2 ———— a << 2 等价于 a * 2 * 2 —— 依次类推
  3. 对于左移,右移操作符,操作数的左边和右边都必须是整数 —— 不适用浮点型

警告:

对于移位运算符,不要移动负数位,这个是标准未定义的。
例如:

int num = 10;
num >> -1;  //error

3、位操作符

&(按位与)    |(按位或)   ^(按位异或)
注:他们的操作数必须是整数。

按位与 —— &

按位与是按照二进制位与 —— 两数都为1则为1,两数不都为1则为0。

#include<stdio.h>
int main()
{
	int a = 3;
	//00000000000000000000000000000011
	int b = -2;
	//10000000000000000000000000000010 —— 原码
	//11111111111111111111111111111101 —— 反码
	//11111111111111111111111111111110 —— 补码
	int c = a & b;
	//00000000000000000000000000000011 —— 3的补码
	//11111111111111111111111111111110 —— -2的补码
	//00000000000000000000000000000010 —— c的补码 —— 又因为c为正数,所以补码就是原码
	
	printf("%d", c);  //打印值为2
	//%d —— 表示打印有符号的整数
	//%u —— 表示打印无符号的整数
	return 0;
}

按位或 —— |

按位或是按照二进制位或 —— 两数都为0则为0,两数不都为0则为1。

#include<stdio.h>
int main()
{
	int a = 3;
	//00000000000000000000000000000011
	int b = -2;
	//10000000000000000000000000000010 —— 原码
	//11111111111111111111111111111101 —— 反码
	//11111111111111111111111111111110 —— 补码
	int c = a | b;
	//00000000000000000000000000000011 —— 3的补码
	//11111111111111111111111111111110 —— -2的补码
	//11111111111111111111111111111111 —— c的补码 —— c为负数
	//11111111111111111111111111111110 —— c的反码
	//10000000000000000000000000000001 —— c的原码

	printf("%d", c);  //打印值为-1
	return 0;
}

按位异或 —— ^

按异或是按照二进制位异或 —— 两数相同为0,不相同为1

#include<stdio.h>
int main()
{
	int a = 3;
	//00000000000000000000000000000011
	int b = -2;
	//10000000000000000000000000000010 —— 原码
	//11111111111111111111111111111101 —— 反码
	//11111111111111111111111111111110 —— 补码
	int c = a ^ b;
	//00000000000000000000000000000011 —— 3的补码
	//11111111111111111111111111111110 —— -2的补码
	//11111111111111111111111111111101 —— c的补码 —— c为负数
	//11111111111111111111111111111100 —— c的反码
	//10000000000000000000000000000011 —— c的原码

	printf("%d", c);  //打印值为-3
	return 0;
}

一道变态的面试题:

我们经常用的两个数交换的函数:

#include<stdio.h>
int main()
{
	int a = 3;
	int b = 5;
	int tem = 0;
	printf("%d %d\\n", a, b);
	tem = a;
	a = b;
	b = tem;
	printf("%d %d\\n", a, b);
	return 0;
}

不能创建临时变量(第三个变量),实现两个数的交换。
第一种方法:

#include<stdio.h>
int main()
{
	int a = 3;
	int b = 5;
	printf("%d %d\\n", a, b);
	a = a + b;
	b = a - b;
	a = a - b;
	printf("%d %d\\n", a, b);
	return 0;
}

第二种方法:

#include<stdio.h>
int main()
{
	int a = 3;
	int b = 5;
	printf("%d %d\\n", a, b);
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	printf("%d %d\\n", a, b);
	return 0;
}
解析:
//011 —— a
//101 —— b
//110 —— a = a ^ b
//011 —— b = a ^ b
//101 —— a = a ^ b

其中:

a ^ b 可以看做是一个钥匙

这个钥匙和 a 异或就是 b —— a ^ b ^ a = b

这个钥匙和 b 异或就是 a —— a ^ b ^ b = a

一些其他的 异或 公式:a ^ 0 = a b ^ b = 0。

4、赋值操作符

  =  (赋值操作符)

赋值操作符是一个很好的操作符,他可以让你得到一个你之前不满意的值。
也就是你可以给一个变量重新赋值。

int weight = 120;  //体重
weight = 100;  //不满意就赋值
double salary = 10000.0;
salary = 20000.0;  //使用赋值操作符赋值。

//赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y+1;  //连续赋值 —— 这样写不容易读懂 —— 可读性差
//下面是a = x = y + 1 的分部写法
x = y+1;
a = x;
//这样的写法更加清晰爽朗而且易于调试。

复合赋值符

+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=

上面的这些复合赋值符是为了写起来更加的方便和便捷,提升我们写代码的效率。

举例:

#include<stdio.h>
int main()
{
	int c1 = 0;
	int c2 = 0;
	int d1 = 10;
	int d2 = 10;
	c1 += 10;
	d1 <<= 1;
	printf("%d\\n", c1);   //打印值为10
	printf("%d\\n", d1);   //打印值为20
	c2 = c2 + 10;
	d2 = d2 << 1;
	printf("%d\\n", c2);   //打印值为10
	printf("%d\\n", d2);   //打印值为20
	return 0;
}

从上面代码中可知:
c += 10 和 c = c + 10 是等价的
c >>= 10 和 c = c >> 10 是等价的

5、单目操作符

—— 只有一个操作数的操作符

!(逻辑反操作)      -(负值)                      +(正值)
& (取地址)        sizeof(操作数的类型长度)      ~(对一个数的二进制按位取反)
--(前置、后置--++(前置、后置++* (间接访问操作符(解引用操作符)(类型) —— 强制类型转换

逻辑反操作 —— !

#include<stdio.h>
int main()
{
	int a = 1;
	a = !a;
	printf("%d\\n", a);  //打印值为0

	if (a)   //表示:a为真的时候打印hehe
		printf("hehe\\n");
	if (!a)   //表示:a为假的时候打印haha
		printf("haha\\n");
	return 0;
}
//注释:
//当a为非零数的时候 —— !a的值为0
//当a为零的时候 —— !a的值为1

正负值号 —— + -

#include<studio.h>
int main()
{
   int a = 10;
   a = -a;
   printf("%d\\n", a);   //打印值为-10
   a = -a;
   printf("%d\\n", a);   //打印值为 10
   return 0;
}

取地址和解引用操作符 —— & 和 *

取地址和解引用操作符一般都是一起使用的

#include<stdio.h>
int main()
{
   int a = 10;
   int arr[10] = {0};
   int* p = &a;  // *表示 p 是指针变量,int 表示p地址所指向的内容是整型
                 // & —— 表示取出a的地址  p = &a —— 表示把a的地址放在P指针中
   *p = 20;   //*是解引用操作符, 能从地址找到地址中的内容
   //int* p 和 *p = 20 中的*p是不一样的
   //其中的int* p中的 * 表示p是一个指针变量
   //*p = 20 中的 * 表示解引用,利用p中存放的地址找到地址中的内容
   &arr;//—— 表示取出整个数组的地址 —— &arr + 1 跳过40个字节(10个整型)
   arr;//—— 表示取出首元素的地址 —— arr + 1 跳过4个字节(一个整型)
   &arr[0];// —— 表示取出首元素的地址 —— &arr[0] + 1 —— 跳过4个字节(一个整型)
}

补充:

左值和右值 —— 放在等号左边的值是左值,放在等号右边的值为右值
左值表示地址所指向的空间,右值表示所指向空间的内容
例如:a = 10  b = a
a = 10 —— a 为地址空间,10为值
b = a —— b 为地址所指向的那块空间, a为a所指向那块空间的值10

sizeof 操作符

—— 强调sizeof是操作符,不是函数
—— 计算数据类型或者是变量在内存中所占的空间大小,单位是字节,和变量中所存的值的大小无关

  1. 数组名如果单独放在 sizeof() 中,数组名表示的是整个数组 —— 计算整个数组的大小
  2. &数组名 —— 也表示的是整个数组
  3. 除了上面两种情况数组名表示的是整个数组,其他的数组名都表示的是首元素地址。
#include<stdio.h>
int main()
{
   char ch[10] = "abcd";
   int a = 10;
   printf("%d\\n", sizeof(ch)); //打印值为10
   printf("%d\\n", strlen(ch));  //打印值为4 —— strlen是计算字符串的大小,遇见\\0停止
   printf("%d\\n", sizeof(int)); //打印值为4
   printf("%d\\n", sizeof(a)); //打印值为4
   printf("%d\\n", sizeof a); //打印值为4 —— 从这个就可以说明sizeof是操作符,不是函数
                             //如果是函数就必须有函数引用操作符 —— ()
   printf("%d\\n", sizeof int); //这种写法是错误的
   return 0;
}

例题:

  1. 第一题
#include<stdio.h>
int main()
{
   int a = 5;
   short b = 10;
   printf("%d\\n", sizeof(b = a + 2));
   printf("%d\\n", b);
}

答案:2 10
解析:因为 sizeof 内部的表达式不参与运算

  1. 第二题
#include <stdio.h>
void test1(int arr[])
{
   printf("%d\\n", sizeof(arr));//
}
void test2(char ch[])
{
   printf("%d\\n", sizeof(ch));//
}
int main()
{
   int arr[10] = {0};
   char ch[10] = {0};
   printf("%d\\n", sizeof(arr));//
   printf("%d\\n", sizeof(ch));//
   test1(arr);
   test2(ch);
   return 0;
}

答案:40 10 4/8 4/8
解析:
sizeof(arr)—— arr单独放在 sizeof 中表示的是 —— arr是整个数组 —— 整个数组的大小为40字节
sizeof ( arr ) —— ch单独放在 sizeof 中表示的是 —— ch表示是整个数组 —— 大小为10
函数传参传的是数组首元素的地址 —— 只要是地址就是4(32位平台下)\\8(64位平台下)个字节。

取反操作符 —— ~

—— 按二进制位取反 —— 原来为1的值取0,原来为0的值取1。

#include<stdio.h>
int main()
{
   int a = 0;
   a = ~a;
   printf("%d\\n", a); //打印值为-1
   //解析:
   //00000000000000000000000000000000 —— a的原码,反码,补码
   //11111111111111111111111111111111 —— ~a的补码
   //11111111111111111111111111111110 —— ~a的反码
   //00000000000000000000000000000001 —— ~a的原码 —— 值为:-1
}

++ 和 - - 操作符

#include<stdio.h>
int main()
{
    int num1 = 10;
	int num2 = num1++;  //后置++ —— 先赋值,后++ —— num2先等于num1(10),然后num1再++
	int num = 11;
	int num3 = --num;   //前置-- —— 先++,后赋值 —— num先--,然后在对num3赋值
	printf("%d\\n", num1);   //打印值为11
	printf("%d\\n", num2);   //打印值为10
	printf("%d\\n", num);   //打印值为10
	printf("%d\\n", num3);   //打印值为10
}

(类型) —— 强制类型转换

int main()
{
   int a = 3.14  //直接赋给变量一个浮点数,默认的类型为double类型的浮点数,这样直接赋给int类型
                 //的变量会出现警告, 为了不出现警告,用下面的写法
   int a = (int) 3.14 //其中(int)会把double类型的值转变成int类型的值
   return 0;
}

6、关系操作符

//关系操作符
>
>=
<
<=
!=     // 用于测试“不相等”
==     // 用于测试“相等”

这些关系运算符比较简单,没什么可讲的,但是我们要注意一些运算符使用时候的陷阱。

警告:
在编程的过程中== 和=不小心写错,导致的错误。

7、逻辑操作符

 && (逻辑与)     ||(逻辑或)
#include<stdio.h>
int main()
{
    int a = 0;
    int b = 1;
    int c = a && b;
    printf("%d\\n", c);  //打印值为0 —— a 和 b 同时为真才为真 
    c = a || b;
    printf("%d\\n", c);  //打印值为1 —— a 和 b 同时为假才为假
    return 0;
}

一道360的笔试题

热榜!万字长文第一篇:深入讲解python中一大数据结构之列表(叮叮当~小葵花课堂开课啦!)

深夜爆肝:万字长文3种语言实现Huffman树(强烈建议三连)

深夜爆肝:万字长文3种语言实现Huffman树(强烈建议三连)

C语言学习笔记-入门整合篇(十万字长文)

C语言学习笔记-入门整合篇(十万字长文)

❤️BitmapsHyperLogLogGeospatial❤️——Redis三大特殊数据类型详述(万字长文原理讲解,大厂面试高频知识点,一文尽收囊中)