【维生素C语言】第五章 - 操作符(一)

简介: 本章将对C语言操作符进行深度的讲解,将每种操作符都单独拿出来精讲。最后添加了些简单的练习题,并配有详细解析。

前言


本章将对C语言操作符进行深度的讲解,将每种操作符都单独拿出来精讲。最后添加了些简单的练习题,并配有详细解析。


一、算术操作符


0x00 概览

463e26a90a8c9616926d2c4f50f7b926_20210527184804737.png


📌 注意事项:


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


     ② 对于 / 操作符,如果两个操作数 都为整数 ,执行整数除法;


     ③ 对于 / 操作符,只要有浮点数出现 ,执行的就是浮点数除法;


     ④ 对于 % 操作符的两个数 必须为整数;


0x01 整数除法

📚  定义:对于 / 操作数,如果两个操作数都为整数,执行整数除法;


❓   整数除法:即一个整数除以另一个整数结果为只保留整数;


💬 代码演示:


int main()
{
    int a = 5 / 2; // 5÷2 = 商2余1
    printf("a = %d\n", a); // 👈 输出的结果是什么?
    return 0;
}

🚩 运行结果: a = 2


0x02 浮点数除法

📚  定义:只要有浮点数出现,执行的就是浮点数除法;


❓   浮点数除法:结果会保留小数部分( 给定对应的%前提下 );


💬 代码演示:


int main()
{
    double a = 5 / 2.0; // 5÷2 = 2.5,有1个浮点数,条件就成立,执行浮点数除法
    printf("a = %lf\n", a); // 👈 输出的结果是什么?
    return 0;
}

🚩 运行结果: a  = 2.500000


0x03 取模操作符

📚  定义:取模运算即 求两个数相除的余数 ,两个操作数必须为非0整数;


📌  注意事项:


     ① 两个操作数必须为整数;


     ② 两个操作数均不能为0(没有意义);


💬 代码演示:

int main()
{
    int a = 996 % 10; // 996 mod 10 = 6
    int b = 996 % 100; // 996 mod 100 = 96
    printf("%d\n", a);
    printf("%d\n", b);
    return 0;
}

🚩  运行结果:6   96


❌  错误演示:


int main()
{
    double a = 5 % 2.0; // ❌ 操作数必须为整数
    printf("a = %lf\n", a);
    return 0;
}

🚩  运行结果:error: invalid operands to binary % (have 'int' and 'double')


int main()
{
    int a = 2 % 0; // ❌ 操作数不能为0
    printf("%d\n", a);
    return 0;
}

🚩  运行结果:warning: division by zero [-Wdiv-by-zero]


0x04 整除和浮点除的区分

💬 代码演示:我们想得到 1.2


int main()
{
    int a = 6 / 5;
    printf("%d\n", a);
    return 0;
}

 🚩 运行结果:  1 ( 但是运行结果为1 )


❓  难道是因为我们用的是 %d 打印的原因吗?


int main()
{
    float a = 6 / 5;
    printf("%f\n", a);
    return 0;
}

🚩 运行结果:  1.000000  ( 仍然不是想要的1.2,运行结果为1.000000 )


(气急败坏,无能狂怒)


💡   解析:其实问题不在于存到a里能不能放的下小数的问题,而是 6 / 5 得到的结果已经是为1了(执行的是整除);


🔑  解决方案:把6改成6.0,或把5改成5.0,也可以都改,让它执行浮点数除法;


int main()
{
    float a = 6 / 5.0;
    printf("%f\n", a);
    return 0;
}

🚩  运行结果:  1.200000


❓ 虽然代码可以运行,但是编译器报了一个 warning,让我们来瞅瞅是咋回事:

83cadfe18a01ec177f859685ed5ad8af_20210527074805563.png

🔑  解析:直接写出的这个数字(6.0或5.0),编译器会默认认为它是 double 类型


                那么计算后a的结果也会是 double 类型(双精度浮点数);


                如果双精度浮点数的值放到一个单精度浮点数里的话,可能会丢失精度,


                好心的编译器就发出了这样的一个警告,这个是正常的;


💡  如果你不想看到这样的警告,你可以这么做:


int main()
{
    float a = 6.0f / 5.0f; // 👈 “钦定” 为float单精度浮点数
    printf("%f\n", a);
    return 0;
}
int main()
{
    double a = 6.0 / 5.0;  // 👈 改成double
    printf("%lf\n", a);
    return 0;
}

📚  关于精度丢失的现象:


     ① 有效数字位数超过7位的时候,将会四舍五入,会丢失较多精度;


     ② 在运行较大数值运算的时候,将有可能产生溢出,得到错误的结果;


二、移位操作符


0x00 概览

📚  概念: 移位操作符分为 "左移操作符" 和 "右移操作符" ;

5590c51169325f3de9e919cec7bfee4a_20210527083439839.png

📌  注意事项:


     ① 移位操作符的 操作数必须为整数;


     ② 对于运算符,切勿移动负数位(这是标准为定义的行为);


     ③ 左移操作符有乘2的效果,右移操作符有除2的效果(左乘2,右除2);


0x01 左移操作符

📚  移位规则:左边丢弃,右边补0 ;(左边的数给👴爬,至于爬多远,还要看操作数是多少)


💬  代码演示:


int main()
{
    int a = 2;
    int b = a << 1; // 将a的二进制位向左移动1位;
    printf("b = %d\n", b); // 4 (左移操作符有乘2的效果)
    /*
           00000000000000000000000000000010
         0|000000000000000000000000000010+0  (左边丢弃,右边补0)
    */
    return (0);
}

🚩  运行结果: b = 4


🔑  图解左移操作符:

e21d89ac005f5d01dcd50d69a2807598_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


0x02 右移操作符

📚  移位规则:两种移位规则;


     ① 算术右移:右边丢弃,左边补原符号位(通常为算术右移);


     ② 逻辑右移:右边丢弃,左边补0;


📌 注意事项:


     ① C编译器中默认为算术右移,如果是 signed 有符号类型时,需要注意;


     ② 使用 unsigned 无符号类型时,算术右移和逻辑右移的结果是一样的;


int main()
{
    int a = 10;
    int b = a >> 1; // 把a的二进制位向右移动一位
    printf("b = %d\n", b); // 5 (右移操作符有除2的效果)
    /*
           00000000000000000000000000001010
          0+0000000000000000000000000000101|0
    */
    return 0;
}

🚩  运行结果:  b = 5


🔑  解析: 为了搞懂什么是算术右移,什么是逻辑右移,我们不得不了解整数的二进制表示方式:


0x03 整数的二进制表示方式(初步了解)

📚  负数-1要存放在内存中,内存中存放的是二进制的补码;


📌  整数的二进制表示形式(原反补):


     ① 原码:直接根据数值写出的二进制序列,即为原码;


     ② 反码:原码的符号位不变,其他位置按位取反,即为反码(如果不知道什么是按位取反,后面会讲);


     ③ 补码:反码 + 1,即为补码; (内存中存放的是补码)


📜  -1 的原码、反码、补码:

e3aa83d7fc3e70c08b0385e575e50752_20210527085903810.png

💬 此时回到上述问题,如果右移时采用逻辑右移:


int main()
{
    int a = -1;
    int b = a >> 1;
    printf("b = %d\n", b);
    return 0;
}

🚩  运行结果:  b = -1


🔑 图解逻辑右移与算数右移:

be3aa4e7c59e49a363d6da37b5f0d5c4_20210527091411140.png

❌ 错误演示:操作数不能是负数!


int main()
{
    int num = 10;
    num >> -1; // ❌  a<<1 ??  垃圾代码
    return 0;
}

🚩  运行结果:  warning: right shift count is negative [-Wshift-count-negative]

b8c50b4b79770ca02d39341380b7038a_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


三、位操作符


0x00 概览

📚   位操作符:按位与、按位或、按位异或;

d9f4afb91737959f019cec2f2d7b81b3_20210527093837139.png

📌 注意事项:位操作符的 操作数必须为整数;


0x01 按位与 &

📚  定义:按2进制按位与,只有对应的两个二进位都为1时,结果位才为1;(必须都为真,结果才为真)

97ed41ff5725ca437d2ba4af64cece14_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

💬  代码演示:按位与的用法


int main()
{
    int a = 3;
    int b = 5;
    int c = a & b;  // a和b都为真
    printf("%d", c);
    return 0;
}

🚩  运行结果:  1


0x02 按位或

📚 定义:只要对应的两个二进位有一个为1时,结果位就为1;(只要有一个为真,结果就为真)

e07878c5fe3027b7ab2fc25c2c4fd065_20210527100449417.png

💬 代码演示:按位或的用法


int main()
{
    int a = 0;
    int b = 5;
    int c = a | b; // a和b有一个为真
    printf("%d\n", c);
    return 0;
}

🚩  运行结果:  5


int main()
{
    int a = 0;
    int b = 0;
    int c = a | b; // a和b都为假
    printf("%d\n", c);
    return 0;
}

🚩  运行结果:  0


0x03 按位异或 ^

📚  定义:相同为0,相异为1;(上下相同就为假,不同为真)


💡  巧记:觉得按位异或不好记? 试着这么记 👇

" 这对恋人是异性恋吗?是回1,不是回0 "       0 1 是, 1 0 是, 1 1 不是, 0 0 不是;


※ 异或:a⊕b = (¬a ∧ b) ∨ (a ∧¬b) 如果a、b两个值不相同,则异或结果为1,反之结果为0;

fccd22485b09b669d7ac78f7759f9ecc_20210527100508497.png

💬  代码演示:按位异或的用法


int main()
{   
    int a = 3;
    int b = 5;
    int c = a ^ b; // a和b不同
    printf("%d\n", c);
    return 0;
}

🚩 运行结果: 6


int main()
{   
    int a = 3;
    int b = 3;
    int c = a ^ b; // a和b相同
    printf("%d\n", c);
    return 0;
}

🚩  运行结果:  0


0x04 位操作符的应用

📃 面试题:交换两个 int 变量的值,不能使用第三个变量;


 (即a=3,b=5,交换之后a=5,b=3)


1. 临时变量法 - 该题禁止了此方法,但是在工作中建议使用该方法;


int main()
{
    int a = 3;
    int b = 5;
    printf("交换前: a = %d, b = %d\n", a, b);
    int tmp = a; // 创建一个临时变量,存放a
    a = b; // a变为b
    b = tmp; // b变为原来的a
    printf("交换后: a = %d, b = %d\n", a, b);
    return 0;
}

🚩  运行结果: 交换前: a = 3, b = 5;交换后:a=5, b=3


2. 加减交换法 - 存在缺陷:可能会溢出(超过整型的存储极限)


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 = 3, b = 5;交换后:a=5, b=3


🔑  解析:第一步: 3 + 5 = 8,第二步: 8 - 5 = 3,第三步: 8 - 3 = 5,此时,a = 5, b = 3 ;


3. 异或交换法 - 缺点:可读性差,执行效率低下;


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 = 3, b = 5;交换后:a=5, b=3


🔑 解析:

fb2a6573e2f8121ad6b6b07aa3379f8d_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

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


1. 一般解法 - 模除


int main()
{   
    int num = 0;
    int count = 0;
    scanf("%d", &num);
    /* 统计num的补码中有几个1 */
    while(num != 0) {
        if(num % 2 == 1) {
            count++;
        }
        num = num / 2;
    }
    printf("%d\n", count);
    return 0;
}


🚩  运行结果: (假设输入3)  2


🔑  解析:

3509a96c43c1831a55ea6f1809bd065e_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


2. 移位操作符 + 按位与 结合的方式解决


💭 思路:


     ① 利用 for 循环,循环32/64次;


     ② 每次 if 判断,将 num 右移 i 位的结果与 1 按位与,为真则说明为1,count++;


     ③ 如果为假,进入下一次循环,最后打印出 count 即可;

int main()
{
    int num = 0;
    int count = 0;
    scanf("%d", &num);
    int i = 0;
    /* 32位系统,至少循环32次 */
    for(i=0; i<32; i++) {
        if( ((num >> i) & 1) == 1 )  // 如果num右移i位的结果和1按位与,为真
            count++;
    }
    printf("%d\n", count);
    return 0;
}

🚩  运行结果: (假设输入3)  2


相关文章
|
12天前
|
存储 编译器 C语言
爱上C语言:操作符详解(下)
爱上C语言:操作符详解(下)
|
24天前
|
算法 测试技术 C语言
【C语言】异或(^)操作符
【C语言】异或(^)操作符
16 0
|
1月前
|
存储 算法 程序员
【c 语言 】位操作符详解
【c 语言 】位操作符详解
36 0
|
1月前
|
存储 编译器 Linux
操作符详解【c语言】
操作符详解【c语言】
|
1月前
|
C语言
【C语言】位操作符详解
【C语言】位操作符详解
22 0
|
1月前
|
存储 编译器 程序员
c语言从入门到实战——操作符详解
C语言操作符指的是程序中用来进行各种计算、逻辑和条件操作的符号或符号组合。 操作符是编程中用于执行特定操作或比较数据的符号。它们根据操作类型分为算术、比较、逻辑和位操作符。算术操作符执行加、减、乘、除等数学运算;比较操作符比较两个值的大小或相等性;逻辑操作符连接多个条件,形成更复杂的逻辑判断;位操作符则直接对整数的二进制位进行操作。了解各种操作符的特性和用法,对于编写高效、准确的代码至关重要。
54 0
|
28天前
|
存储 编译器 程序员
C语言第十六弹---操作符(下)
C语言第十六弹---操作符(下)
|
26天前
|
编译器 C语言 索引
C语言操作符详解(上)
C语言操作符详解(上)
44 0
|
1月前
|
存储 安全 编译器
【 c 语言 】赋值操作符详解
【 c 语言 】赋值操作符详解
77 0
|
1月前
|
存储 算法 编译器
【c 语言】算术操作符详解
【c 语言】算术操作符详解
42 0