C语言操作符详解(上)

简介: C语言操作符详解(上)

前言

一、算术操作符 + - * / %(运算符)

除法注意点:/ 除法 得到的是商

1.除法操作符的两个操作数都是整数的话,执行的是整数除法.用%d打印

2.除法操作符的两个操作数只要有一个浮点点数,执行的是小数除法.用%lf打印

% 取模(取余) 得到的是余数:

1.取模操作符的两个操作数必须是整数,返回的是整除之后的余数.

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

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main() {
  //printf("%lf\n", 10 / 3);   //不能这么写,因为两个操作数都是整数,应该用%d
  printf("%lf\n", 10 / 3.0);
  printf("%d\n", 10 / 3);
}

二、移位操作符(操作数只能是整数)

移位操作符移动的是二进制的位

左移操作符: 左边丢弃,右边补0

数值的表示形式:

2进制,8进制,10进制,16进制

10进制的数据中,都是0-9的数字组成的

2进制的数据中,0-1的数字组成的

8进制的数据中,0-7的数字组成的

16进制的每一位是:

0~15

0 1 2 3 4 5 6 7 8 9 a b c d e f

比如:

表示数值10

1010----2进制

12-------8进制

10-------10进制

a---------16进制

数据的二进制表示:

整数的二进制表示形式有三种: 原码反,码,补码

原码:

把一个数按照正负直接翻译成二进制就是原码

最高位表示符号位,0表示正数,1表示负数

正整数的原码反码补码相同,

负整数的原码反码补码是要计算的

反码:

原码的符号位不变,其他位按位取反就是反码

补码:

反码+1

举例说明:

5,-5是整数,整数是存放在整型变量中的

一个整型变量是4个字节~32比特位

5:

原码 00000000000000000000000000000101

反码 00000000000000000000000000000101

补码 00000000000000000000000000000101

-5:

原码10000000000000000000000000000101

反码 11111111 11111111 11111111 11111010

补码 11111111 11111111 11111111 11111011

整数在内存中存储的是:二进制补码

4个比特位可以作为一个16进制数

例如:-5

补码 11111111 11111111 11111111 11111011

4个一组表示成16进制:

FF FF FF FB

正整数左移操作符举例说明:

3的二进制位:

00000000 00000000 00000000 00000011

向左移动一位,左边丢弃,右边补0

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main() {
  int a = 3;
  //3的二进制位:(正数的原码反码补码相同)
  //00000000 00000000 00000000 00000011
  int b=a << 1;
  //向左移动一位,左边丢弃,右边补0
  //b是正数,原码反码补码相同
  //00000000 00000000 00000000 00000110  
  //b也就是6
  printf("%d\n", b);
  printf("%d\n", a);
  return 0;
}

负整数左移操作符举例说明:

原码------>补码:取反+1

补码------>原码:-1,取反 或者 取反+1

-3的二进制位左移一位:

由于整数在内存中存储的是补码,左移的时候实际上是将补码左移一位,右边补0.

首先求出-3的补码,左移补0之后得到的是结果的补码,还需要转换成原码,最后通过原码得出结果是多少

int main() {
  int a =-3;
  /*-3的二进制位:
  10000000 00000000 00000000 00000011   -3的原码
  11111111 11111111 11111111 11111100   -3的反码
  11111111 11111111 11111111 11111101   -3的补码*/
  // 
  int b=a << 1;
  //-3的补码向左移动一位,左边丢弃,右边补0得到结果的补码
  //11111111 11111111 11111111 11111010   b的补码      
  //11111111 11111111 11111111 11111001   b的反码
  //10000000 00000000 00000000 00000110   b的原码 -6
  printf("%d\n", b);
  printf("%d\n", a);
  return 0;
}

右移操作符:

1.>> 算术右移:

右边丢弃,左边用原来的符号来填充

2.>> 逻辑右移:

右边丢弃,左边直接用0来填充

右移的时候,到底采用的是算术右移还是逻辑右移,是取决于编译器的!!

由下面的代码可以看出,vs2019编译器采用的是算术右移.

int main() {
  int a = -5;
  int b = a >> 1;
  //10000000 00000000 00000000 00000101
  //11111111 11111111 11111111 11111010
  //11111111 11111111 11111111 11111011
  printf("b=%d\n", b);
  //算术右移
  //11111111 11111111 11111111 11111101
  //10000000 00000000 00000000 00000010
  //10000000 00000000 00000000 00000011     -3
  printf("a=%d\n", a);
  return 0;
}

三、位操作符(操作数必须是整数)

位:指的是二进制位

int main() {
  int a = 3;
  int b = -5;
  int c = a & b;
  //00000000 00000000 00000000 00000011  3的补码
  //10000000 00000000 00000000 00000101  -5的原码
  //11111111 11111111 11111111 11111010  -5的反码
  //11111111 11111111 11111111 11111011  -5的补码
  //00000000 00000000 00000000 00000011  3的补码
  //11111111 11111111 11111111 11111011  -5的补码
  //00000000 00000000 00000000 00000011   a&b的补码,由于是正整数,原码也是这个
  printf("%d\n", c);  //3
  int d = a | b;  //按位或
  //00000000 00000000 00000000 00000011  3的补码
  //11111111 11111111 11111111 11111011  -5的补码
  //11111111 11111111 11111111 11111011  a|b的补码,可以发现与-5的补码一样,所以他的原码数值等于-5
  printf("%d\n", d);
  int e = a ^ b;  //按位异或--对应的二进制位相同为0,相异为1
  //00000000 00000000 00000000 00000011  3的补码
  //11111111 11111111 11111111 11111011  -5的补码
  //11111111 11111111 11111111 11111000  a^b的补码
  //11111111 11111111 11111111 11110111  补码-1
  //00000000 00000000 00000000 00001000  取反得到原码,结果为-8
  printf("%d\n", e);
}

交换a和b的值:

方法一:创建临时变量

int main() {
  int a = 3;
  int b = 5;
  printf("交换前:a=%d b=%d\n", a, b);
  int tmp = 0;
  tmp = 0;
  tmp = a;
  a = b;
  b = tmp;
  printf("交换后:a=%d b=%d\n", a, b);
}

方法二:不创建临时变量

存在潜在风险,当数字过大时,加和之后可能超出整数的最大范围,有一定局限性

int main() {
  int a = 3;
  int b = 5;
  printf("交换前:a=%d b=%d\n", a, b);
  a = a + b;
  b = a - b;
  a = a - b;
  printf("交换后:a=%d b=%d\n", a, b);
  return 0;
}

方法三:使用异或操作符,不创建临时变量

注意两个公式:
a^a=0
0^a=a

3^ 3^5=5

011 3

011 3

000 3^3

101 5

101 3^ 3^5结果5

353=5

异或支持交换律的

异或只能用于整数操作

异或只会产生0和1,不会进位出现2,所以永远不会溢出

int main() {
  int a = 3;
  int b = 5;
  printf("交换前:a=%d b=%d\n", a, b);
  a = a ^ b;  
  b = a ^ b;      //b=a^b^b=a^0=a
  a = a ^ b;      //a=a^a^b=0^b=b   
  printf("交换后:a=%d b=%d\n", a, b);
}

编写代码实现:求一个整数存储在内存中的二进制中1的个数

思路点拨:

int main() {
  int a = 11;
  a & 1;
  //一个整数存储在内存中的二进制中1的个数
  //只要每次右移,每次和1按位与得到最后一位看一下是不是1
  a = a >> 1;
  //00000000 00000000 00000000 00001011
  //00000000 00000000 00000000 00000001
  //发现:最低位和1按位与,还是原来的数字
  //如果想要得到这个数的最低位是几,只要和1按位与就可以了
  //结果
  //00000000 00000000 00000000 00000001
}

四、赋值操作符

=

复合操作符

int main() {
  int a = 3;
  a = a + 3;
  a += 3;
  a = a ^ 5;
  a ^= 5;
  a = a >> 3;
  a >>= 3;
}

区分赋值和初始化:

int main() {
  int a = 3;//创建并初始化
  a = 5;//赋值
  return 0;
}

六、单目操作符

!的应用场景:

int main() {
  int flag = 5;
  if (flag) {  //flag为真做什么
  }
  if (!flag) {   //flag为假做什么
  }
  return 0;
}

&取地址操作符

int main() {
  int a = 10;
  &a;//&取地址操作符
  int arr[10];
  &arr;//取出数组的地址
}

解引用操作符(间接访问操作符)

*

int main() {
  int a = 10;
  int* p=&a;  //取地址操作符  p里面存的是a的地址
  *p = 20;   //解引用操作符(间接访问操作符)
  int arr[10];
  &arr;   //取出数组的地址
  return 0;
}

sizeof

int main() {
  int a = 10;
  printf("%d\n", sizeof(a));
  printf("%d\n", sizeof a);   //变量求长度时的小括号可以不写,但是int必须写
  printf("%d\n", sizeof(int));
  int arr[10] = { 0 };
  printf("%d\n", sizeof(arr));     //数组的长度
  printf("%d\n", sizeof(arr[0]));   //数组元素的长度
  int sz = sizeof(arr) / sizeof(arr[0]);    //数组元素的个数
  printf("%d\n", sz);
  return 0;
}

~的用法

对一个数的二进制位按位取反

int main() {
  int a = 0;
  //00000000 00000000 00000000 00000000
  //~a:按位取反
  //11111111 11111111 11111111 11111111 补码(内存里面的字都是补码)
  //11111111 11111111 11111111 11111110 反码
  //00000000 00000000 00000000 00000001  原码  -1
  printf("%d\n", ~a);   //-1
}

下面举例灵活应用单目操作符:

int main() {
  int b = 3;
  //00000000 00000000 00000000 00000011     b
  //如何把上面的数字倒数第四位变成1?
  //00000000 00000000 00000000 00000011      b
  //用b|
  //00000000 00000000 00000000 00001000     (1 << 3)
  b |= (1 << 3);
  //00000000 00000000 00000000 00001011     b=11
  printf("%d\n", b);  //11
  //如何将下面的数字
  //00000000 00000000 00000000 00001011
  //倒数第四位还原成0??
  //
  // 0&1=0 1&1=1
  // 所以将原数字与下面的数字按位与,就可以将倒数第四位还原成0
  //11111111 11111111 11111111 11110111  这个数字怎么表示呢?
  //我们发现它刚好是00000000 00000000 00000000 00001000     (1 << 3)取反可以得到
  //
  b &= (~(1 << 3));
  printf("%d\n", b);  //3
  return 0;
}

小知识点:

scanf读取失败的时候返回-1

原码

10000000 00000000 00000000 00000001

反码

11111111 11111111 11111111 11111110

补码

11111111 11111111 11111111 11111111

~按位取反之后:全0

00000000 00000000 00000000 00000000

此时就变成:

while(0)不进入循环,scanf读取失败

前置++ --:在使用之前++ - -
后置++ --:在使用之后++ - -

int main() {
  int a = 10;
  int b = a++;
  printf("%d\n", a);    
  printf("%d\n", b);  //后置++,先使用,再++
  //相当于int b=a;a=a+1;
}

后置++

int main() {
  int a = 10;
  printf("%d\n", a++);//先使用,后++
  printf("%d\n", a);
}

前置++

int main() {
  int a = 10;
  printf("%d\n", ++a);  //先++,再使用
  printf("%d\n", a);
}

前置- -和后置- -同上面一个道理

(类型) 强制类型转换

仅仅是临时改变数据类型的一个状态

int main() {
  float a = 3.14f;
  int b = (int)a;  //强制性类型转换
  return 0;
}

sizeof()和数组

1.sizeof()内部单独放一个数组名,表示整个数组,所以(1)(3)处的值为40,10

2.注意:数组在传参的时候,实际上是传过去首元素的地址

void test1(int arr[])  //整形的地址放到整形的指针里面,int本质是int*,存放地址,指针变量的大小都是四个字节或者八个字节
{
  printf("%d\n", sizeof(arr));//(2)X86环境下指针的大小是4,X64环境下指针的大小是8
}
void test2(char ch[])   //char本质是char*,存放地址
{
  printf("%d\n", sizeof(ch));//(4)X86环境下指针的大小是4,X64环境下指针的大小是8
}
int main()
{
  int arr[10] = { 0 };
  char ch[10] = { 0 };
  printf("%d\n", sizeof(arr));//(1)  计算的是整个数组的大小:40
  printf("%d\n", sizeof(ch));//(3)   计算char类型的数组大小:10
  test1(arr);
  test2(ch);
  return 0;
}

七、关系操作符

在编程中注意==和=不要使用错误

八、逻辑操作符

&& 逻辑与 并且的意思 只关注真假
| | 逻辑或 或者的意思 只关注真假

& 按位与 二进制位进行计算

| 按位或 二进制位进行计算

int main() {
  int a = 3 && 5;
  int b = 3 && 0;
  printf("%d\n", a);
  printf("%d\n", b);
  int c = 1 || 0;
  int d = 0 || 0;
  printf("%d\n", c);
  printf("%d\n", d);
  return 0;
}

&& 操作符左边为假,右边不再计算
| | 操作符左边为真,右边不再计算

举例说明:

int main() {
  int i = 0, a = 0, b = 2, c = 3, d = 4;
  i = a++ && ++b && d++;  //0&&...a=0,后面不用计算
  printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);
  return 0;
}

int main() {
  int i = 0, a = 1, b = 2, c = 3, d = 4;
  i = a++ && ++b && d++;  //1&&3&&4
  printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);//2 3 3 5
  return 0;
}

int main() {
  int i = 0, a = 1, b = 2, c = 3, d = 4;
  i = a++ || ++b || d++;  //1||...或运算,左边有一个1,右边不用计算
  printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);//2 2 3 4
  return 0;
}

int main() {
  int i = 0, a = 0, b = 2, c = 3, d = 4;
  i = a++ || ++b || d++;   //0||2...右边不用计算
  printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);//1 3 3 4
  return 0;
}


这篇文章的内容你是否有所收获呢?小伙伴们,记得点赞收藏博客,关注后续的C语言操作符(下)内容哦~😉😉💕

相关文章
|
3月前
|
存储 C语言 索引
【C语言篇】操作符详解(下篇)
如果某个操作数的类型在上⾯这个列表中排名靠后,那么⾸先要转换为另外⼀个操作数的类型后执⾏运算。
74 0
|
3月前
|
程序员 编译器 C语言
【C语言篇】操作符详解(上篇)
这是合法表达式,不会报错,但是通常达不到想要的结果, 即不是保证变量 j 的值在 i 和 k 之间。因为关系运算符是从左到右计算,所以实际执⾏的是下⾯的表达式。
257 0
|
27天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
34 10
|
1月前
|
存储 编译器 C语言
【C语言】简单介绍进制和操作符
【C语言】简单介绍进制和操作符
160 1
|
1月前
|
存储 编译器 C语言
初识C语言5——操作符详解
初识C语言5——操作符详解
173 0
|
3月前
|
C语言
C语言操作符(补充+面试)
C语言操作符(补充+面试)
45 6
|
3月前
|
存储 编译器 C语言
十一:《初学C语言》— 操作符详解(上)
【8月更文挑战第12天】本篇文章讲解了二进制与非二进制的转换;原码反码和补码;移位操作符及位操作符,并附上多个教学代码及代码练习示例
59 0
十一:《初学C语言》—  操作符详解(上)
|
4月前
|
C语言
五:《初学C语言》— 操作符
本篇文章主要讲解了关系操作符和逻辑操作符并附上了多个代码示例
44 1
五:《初学C语言》—  操作符
|
5月前
|
C语言
C语言逻辑操作符的短路问题
C语言逻辑操作符的短路问题
|
5月前
|
编译器 C语言
【C语言】:中移位操作符,位操作符详运算规则详解
【C语言】:中移位操作符,位操作符详运算规则详解
44 1