C语言 操作符

简介: C语言 操作符

一、算数操作符



+       // 加
-         // 减
*         // 乘
/         // 除
%       //取模


程序清单:


#include <stdio.h>
int main() {
  int a = 10 / 3;
  printf("%d\n", a);
  float b1 = 10.0 / 3; // double / int
  printf("%f\n", b1);
  float b2 = 10 / 3.0; // int / double
  printf("%f\n", b2);
  int c = 10 % 3;
  printf("%d\n", c);
  return 0;
}


输出结果:

328a05cac023483da29845f4885dd750.png


注意事项:


① 对于除法操作符,如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。


② 观察第二个输出结果,实际上由于 double / int,所以产生的是 double 类型,那么在以格式化 "%f " 输出时,就会发生自 double 向 float 截断。(C语言 默认使用 double 类型)


476352e866cb4b82b44e1af05c77556a.png


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


二、移位操作符



<<        // 左移
>>        // 右移


注意:


① 移位操作符针对的是数据在内存中的二进制补码。

② 移位操作符的操作数只能是整数。


int a = 3 << 1      // √
int b = 3.5 << 1    // error
int c = 3 << 1.5    // error
int d = -3 << 1     // √


1. 数据在内存中的存储


计算机在存储数据的时候是以二进制存储的。二进制有多少位,根据数据的类型决定。比如 int 类型,即 4 字节,即 32 位,那么就有 32 个 0或1 的二进制数据。


① 整数的二进制有三种形式:原码、反码、补码。正整数的原、反、补码是相同的;但负整数的原、反、补码则需要计算。(原码符号位不变,其他位按位取反即可变成反码;反码再 +1 即可变成补码)


② 最终,整数在内存中存储的是补码的二进制。


③ 最高位表示符号位,0表示正号,1表示负号。在原码、反码、补码的转换过程中,符号位不能改变。


④ printf 格式化输出的是数据的原码。


2. 左移操作符


程序清单:


#include <stdio.h>
int main() {
  int a1 = 5;
  int b1 = a1 << 1;
  printf("a = %d, b = %d\n", a1, b1); // 5, 10
  int a2 = -5;
  int b2 = a2 << 1;
  printf("a = %d, b = %d\n", a2, b2); // -5, -10
  return 0;
}


输出结果:


a2012afdcdd74431adcf9e9717864a94.png


分析左移的过程:

5 << 1,5 的原、反、补码相同。


2ee7748a85e64bf09bc4379f4cfab7a3.png


-5 << 1,左移操作符对 -5 的补码进行操作。


4b54bbdd517146aeafdce909f0c996d8.png


总结:


① 左移操作符相当于为原数据乘以 2.

② 左移对数据的补码的二进制进行操作:左边丢弃,右边补0.

③ 左移不会对原数据进行直接改变。


如下:a 左移过后,把值赋给了 b,则 b 变成了 10,但 a 还是 5.


int a = 5;
int b = a << 1; // a = 5, b = 10


3. 右移操作符


程序清单:


#include <stdio.h>
int main() {
  int a1 = 5;
  int b1 = a1 >> 1;
  printf("a = %d, b = %d\n", a1, b1); // 5, 2
  int a2 = -5;
  int b2 = a2 >> 1;
  printf("a = %d, b = %d\n", a2, b2); // -5, -3
  return 0;
}


输出结果:


a2726f2daf5447b68f3f3f694105e293.png


分析右移的过程:

5 >> 1,5 的原、反、补码相同。


cca23f285fbe4684a5526c42a834b9e3.png


-5 >> 1,右移操作符对 -5 的补码进行操作。


b6c2731cf829482d9493ceb2518cd363.png


总结:


① 针对于正整数时,右移操作符相当于为原数据除以 2;针对于负整数时,不确定。

② 右移对数据的补码的二进制进行操作。它分为两种情况。

a. 算数右移:右边丢弃,左边补原符号位。

b. 逻辑右移:右边丢弃,左边补0.


一个程序到底是算数右移还是逻辑右移,取决于编译器的使用,例如我上面的程序就是放在 VS 底下运行的,所以它就采取了算数右移,我的分析过程也是如此。


③ 同样地,右移不会对原数据进行直接改变。


三、位操作符



&       // 按位与
|       // 按位或
^       // 按位异或


注意:


① 位操作符同样是针对的是数据在内存中的二进制补码。

② 位操作符的操作数只能是整数。


1. 按位与


& 规则:两个位都为1,则结果为1;其中一位为0,则结果为0.


#include <stdio.h>
int main() {
  int a = 3;
  int b = -5;
  int c1 = a & b;
  printf("%d\n", c1); // 3
  return 0;
}
//00000000 00000000 00000000 00000011   -> 3的原、反、补码
//10000000 00000000 00000000 00000101   -> 5的原码
//11111111 11111111 11111111 11111010 -> 5的反码
//11111111 11111111 11111111 11111011 -> 5的补码


8a45b07c70914dcd8f94083e6bcb5fc7.png


2. 按位或


| 规则:两个为都为0,则结果为0;其中一位为1,则结果为1.


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


83c40e9538b744ea800501983c7404fa.png


3. 按位异或


^ 规则:同为0;异为1.


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


d7faa99b170d4c00b2ae3841a892c725.png


异或的两个规律:


a ^ a = 0
0 ^ a = a


位操作符的应用


应用1


写一个程序,用来交换两个数。

方法一:


#include <stdio.h>
int main() {
  int a = 3;
  int b = 5;
  printf("%d, %d\n", a, b); 
  int tmp = a;
  a = b;
  b = tmp;
  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+b-b => b = a
  a = a - 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^b^b => a^0 => b = a
  a = a ^ b; // a^b^a => 0^b => a = b
  printf("%d, %d\n", a, b); 
  return 0;
}


统一输出结果:


055865179e4b457e821a4914d4048b65.png


总结:


① 方法一是创建一个新的变量来实现两数交换的,它最常用、效率高、可读性高。方法二和方法三则没有创建新的变量,虽然看似更高效,但也带来了缺点。


② 方法二,我们知道 int 类型是有范围的,当两数相加相减时超出了 int 类型的范围,就会产生意想不到的截断效果,所以在极端的情况下,这并不合理。


③ 方法三,异或本身对于操作数的要求就是必须为整数,所以对于两个浮点数的交换,也并不合理。


④ 综上所述,如果不是面试问到或者题目问到这样的两数交换,我们还是采用方法一,因为程序要么错,要么对,不能模棱两可。


应用2


写一个程序,求一个整数存储在内存中的二进制中1的个数。


#include <stdio.h>
int main() {
  int a = 13;
  int count = 0;
  for (int i = 0; i < 32; i++) {
    int result = (a >> i) & 1;
    if (result == 1) { // 某一位结果为1,代表是二进制的值为1
      count++;
    }
  }
  printf("整数 %d 在内存中二进制为1的个数为:%d\n", a, count); // 
  return 0;
}


输出结果:


3f65095bb0384dc7a1a328bfe9d065ac.png


思路: 让底层的二进制补码右移的同时,按位与1. 与的结果为 1,则说明当前二进制位是 1.


b16ac8ea960647e6914495d9fb3834b7.png


四、赋值操作符



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


五、单目操作符



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


单目操作符,顾名思义,它只有一个操作数。


sizeof 操作符的使用


程序清单:


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

输出结果:(32 位)


a91057248c33494895526a095906383a.png


总结:


① sizeof 是一个操作符,不是一个函数。


② sizeof 用来求类型 / 变量在内存中储存的大小。


③ sizeof 在操作于数组时,需要明白是针对于整个数组,还是针对于函数接收数组的形参。前者计算的是整个数组内元素所占内存的大小,后者是计算一个指针变量的所占内存的大小。


自增、自减


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


输出结果:


image.png


注意事项:


① 自增分为前置与后置,++前置表示:先自增,后使用;++后置表示:先使用,后自增。(自减也是如此)


② 自增自减会对当前操作的变量直接生效,也就是说,底层存储的二进制也被修改了。


③ 在日常程序中,自增自减正常使用即可。以前在学校的时候,C语言 期末考试会考那些逻辑非常怪的题目,其中就有多个自增自减放在一起使用的,其实没有必要深究,因为一个好的程序压根就不会那么写。


六、关系操作符



注意在字符串比较的时候,不能使用双等号作为比较,它需要特殊的字符串函数来操作两个字符串。


>
>=
<
<=
!=        // 用于测试“不相等”
==        // 用于测试“相等”


七、逻辑操作符



&&        // 逻辑与
||        // 逻辑或



程序清单:


#include <stdio.h>
int main()
{
  int i = 0, a = 0, b = 2, c = 3;
  i = a++ && ++b && c++;
  int j = 0, x = 1, y = 2, z = 3;
  j = x++ || ++y || z++;
  printf("a = %d, b = %d, c = %d\n", a, b, c);
  printf("x = %d, y = %d, z = %d\n", x, y, z);
  return 0;
}


输出结果:


ff6f7cdaf52f47ad8dca5792db0c1ac1.png


注意事项:


① 逻辑与表示的 " 两者都 ",所以当前者为否的时候,后面就不计算了。

② 逻辑或表示的 " 两者任意一个 ",所以当前者为真的时候,后面就不计算了。


八、条件操作符



a ? b : c 
// a 成立,执行 b,否则执行 c


程序清单:


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


输出结果:


image.png


九、逗号表达式



result = exp1, exp2, exp3...
// 从左向右依次执行,result 结果为最右边表达式的结果。


程序清单:


#include <stdio.h>
int main() {
  int a = (3, 5, 7); // a = 7
  printf("a = %d\n", a); 
  int x = 1;
  int y = 2;
  int z = (x > y, x = y + 1, y = x + 1); // z = y
  printf("x = %d, y = %d, z = %d\n", x, y, z);
  return 0;
}


输出结果:


image.png


十、其他操作符



[]            // 下标引用操作符
()        // 函数调用操作符
.       // 结构体变量.成员名
->        // 结构体指针变量->结构体成员


1. 下标引用操作符


程序清单:


#include <stdio.h>
int main() {
  int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
  printf("%d\n", arr[5]);
  printf("%d\n", 5[arr]); // 这样写不会出错,但没有人这么写
  return 0;
}


输出结果:


image.png


注意事项:


在我们平时写出 arr[5] 这样的代码时,看上去很平常,但实际上 [ ] 确实是一个操作符,arr 和 5 是它的两个操作数。


2. 函数调用操作符


swap(a, b);
print();


注意事项:


在我们平时写出上面那样的代码时,看上去也很平常,但实际上 () 确实是一个操作符。例如:

第一个 () 有三个操作数,swap、a、b.

第二个 () 只有一个操作数:print.


3. 结构体成员访问操作符


程序清单:


#include <stdio.h>
struct Student
{
  char name[20];  // 名字
  int age;    // 年龄
  int studentID;  // 学号
};
int main() 
{
  struct Student student1 = {"Jack", 18, 32};  
  struct Student student2 = {"Bruce", 20, 05};
  printf("%s %d %d\n", student1.name, student1.age, student1.studentID);
  struct Student* ps1 = &student1;
  printf("%s %d %d\n", (*ps1).name, (*ps1).age, (*ps1).studentID); // 先解引用再访问
  printf("%s %d %d\n", ps1->name, ps1->age, ps1->studentID);
  return 0;
}


输出结果:


fff0ec4bdc664028a818f640555af473.png

目录
相关文章
|
26天前
|
存储 C语言 索引
【C语言篇】操作符详解(下篇)
如果某个操作数的类型在上⾯这个列表中排名靠后,那么⾸先要转换为另外⼀个操作数的类型后执⾏运算。
|
26天前
|
程序员 编译器 C语言
【C语言篇】操作符详解(上篇)
这是合法表达式,不会报错,但是通常达不到想要的结果, 即不是保证变量 j 的值在 i 和 k 之间。因为关系运算符是从左到右计算,所以实际执⾏的是下⾯的表达式。
|
1月前
|
C语言
C语言操作符(补充+面试)
C语言操作符(补充+面试)
31 6
|
1月前
|
存储 编译器 C语言
十一:《初学C语言》— 操作符详解(上)
【8月更文挑战第12天】本篇文章讲解了二进制与非二进制的转换;原码反码和补码;移位操作符及位操作符,并附上多个教学代码及代码练习示例
43 0
十一:《初学C语言》—  操作符详解(上)
|
2月前
|
C语言
五:《初学C语言》— 操作符
本篇文章主要讲解了关系操作符和逻辑操作符并附上了多个代码示例
33 1
五:《初学C语言》—  操作符
|
3月前
|
C语言
C语言逻辑操作符的短路问题
C语言逻辑操作符的短路问题
|
3月前
|
编译器 C语言
【C语言】:中移位操作符,位操作符详运算规则详解
【C语言】:中移位操作符,位操作符详运算规则详解
27 1
|
3月前
|
存储 编译器 C语言
|
3月前
|
存储 C语言 索引
【C语言基础】:操作符详解(二)
【C语言基础】:操作符详解(二)
|
3月前
|
编译器 C语言
C语言学习记录——操作符详解知识点选记(算术操作符、单目操作符、移位操作符、关系操作符、逻辑操作符、条件操作符......)二
C语言学习记录——操作符详解知识点选记(算术操作符、单目操作符、移位操作符、关系操作符、逻辑操作符、条件操作符......)二
38 3