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

201 阅读18分钟

​​​「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」。

六、关系操作符

📌 注意事项:在编程的过程中要小心 = 和 == 不小心写错,导致的错误;

💬 代码演示:一般用于条件语句中

int main()
{
    int a = 3;
    int b = 5;
    
    if(a < b) {
        ...
    }
    if(a == b) {
        ...
    }
    if(a <= b) {
        ...
    }
    if(a != b) {
        ...
    }
    
    return 0;
}

七、逻辑操作符

0x00 逻辑与 &&

📚 说明:逻辑与,a和b都为真时结果才为真;(都为真才为真)

💬 代码演示:

1. a和b都为真时,结果就为真,c = 1;

int main()
{
    int a = 3;
    int b = 5;
    int c = a && b; // 逻辑与 “并且” a和b都为真时才返回真
    printf("%d\n", c);
    
    return 0;
}

🚩 运行结果: 1(真)

2. a和b只要有一个为假,结果就为假,c = 0;

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

    return 0;
}

🚩 运行结构: 0(假)

0x01 逻辑或 ||

📚 说明:a和b有一个为真,结果就为真;(有真则为真)

💬 代码演示:

1. a和b只要有一个为真,结果就为真;

int main()
{
    int a = 0;
    int b = 5;
    int c = a || b; //逻辑与 “并且” a和b都为真时才返回真
    printf("%d\n", c);

    return 0;
}

🚩 运行结果: 1 (真)

2. a和b同时为假的时候,结果才为假;

int main()
{
    int a = 0;
    int b = 0;
    int c = a || b; //逻辑与 “并且” a和b都为真时才返回真
    printf("%d\n", c); // 0

    return 0;
}

🚩 运行结果: 0 (假)

0x02 练习

📃 笔试题:(出自360)

❓ 1. 程序输出的结果是什么

int main()
{
    int i = 0, a=0,b=2,c=3,d=4;
    i = a++ && ++b && d++;
    printf("a=%d\n b=%d\n c=%d\n d=%d\n", a, b, c, d);

    return 0;
}

🚩 运行结果: a=1;b=2;c=3;d=4

🔑 解析:

首先i的初始值是0,执行i = a++ && ++b && d++ 时,先执行的是a++,a初始值为0,因为是后置++的原因,此时a仍然为0,逻辑与碰到0,就不会再往下继续执行了,所以后面的++b,d++都不算数。打印时,因为刚才a++,所以此时a=1,打印出来的结果自然是a=1,b=2,c=3,d=4;

❓ 2. 程序的输出结果是什么

int main()
{
    int i = 0, a=0,b=2,c=3,d=4;
    i = a++ || ++b || d++;
    printf("a=%d\n b=%d\n c=%d\n d=%d\n", a, b, c, d);

    return 0;
}

🚩 运行结果: a=1;b=3;c=3;d=5

🔑 解析:

i=0,执行 i = a++ || ++b || d++ 时,先执行a++,因为是后置++所以此时a还是为0,但是因为是逻辑或,会继续往下走,++b为前置++,此时b为3,为真,就不会往下继续执行了,d++不算数。打印时,因为刚才a++,d++,所以此时a=1,打印出来的结果为 a=1,b=3,c=3,d=4;

🔺 总结:

1. 逻辑与:碰到假就停;(只要左边为假,右边就不算了)

2. 逻辑或:碰到真就停;(只要左边为真,右边就不算了)

八、条件操作符

📚 定义:

① 表达式1的结果如果为真,计算表达式2;

② 如果表达式1的结果为假,计算表达式3;

📌 注意事项:三目操作符不要写的过于复杂,否则可读性会很差;

💬 代码演示:

1. if...else写法:

int main()
{
    int a = 3;
    int b = 0;
    
    if (a > 5)
        b = 1;
    else 
        b = -1;
        
    return 0;
}   

2. 将上面代码转换成条件表达式:

int main()
{
    int a = 3;
    int b = 0;
    
    b = a>5 ? 1 : -1; // 条件操作符
    
    return 0;
}

💬 使用条件表达式实现找两个数中的较大值:

int main()
{
    int a = 10;
    int b = 20;
    int max = 0;
    
    max = (a>b ? a : b );
    printf("max = %d", max);

    return 0;
}

九、逗号表达式

❓ 什么是逗号表达式

💡 逗号表达式,顾名思义,用逗号隔开的多个表达式;

📚 定义:从左向右依次执行,整个表达式的结果是最后一个表达式的结果;

💬 代码演示:逗号表达式的用法

int main()
{
    int a = 1;
    int b = 2;
    int c = (a>b,  a=b+10,   a,    b = a+1);
//          无结果    12    无结果  12+1=13

    printf("%d\n", c);

    return 0;
}

🚩 运行结果: 13

💬 判断条件的逗号表达式

if(a = b + 1, c = a / 2, d > 0)  // 从左向右依次执行后,d>0则条件为真

💬 逗号表达式的应用:简化代码结构

十、下标引用、函数调用和结构成员

0x00 下标引用操作符 [ ]

💬 这个很简单,直接上代码:

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    //             0 1 2 3 4 5 6 7 8 9
    printf("%d\n", arr[4]); // 5
    //                 ↑ 这里的方块,正是下标引用操作符;
    // [] 的操作数是2个:arr,4

    return 0;
}

0x01 函数调用操作符 ( )

📚 作用:接受一个或者多个操作数;

① 第一个操作数是函数名;

② 剩余的操作数就是传递给函数的参数;

💬 代码演示:函数调用操作符

int Add(int x, int y)
{
    return x + y;
}

int main()
{
    int a = 10;
    int b = 20;
    int Add(a, b); // 此时()为函数调用操作符;

    return 0;
}

0x02 结构成员访问操作符 - 点操作符 .

📚 作用:访问结构体成员;

😢 如果忘了什么是结构体,可以去回顾第一章(初识C语言)

blog.csdn.net/weixin_5050…

💬 代码演示:点操作符的使用

struct Book {
    char name[20];
    char id[20];
    int price;
};

int main()
{
    struct Book b = {"C语言", "C20210509", 55};
    printf("书名:%s\n", b.name);
    printf("书号:%s\n", b.id);
    printf("定价:%d\n", b.price);
    
    return 0;
}

🚩 运行结果: 书名:C语言
书号:C20210509
定价:55

0x03 结构成员访问操作符 - 箭头操作符 ->

📚 作用:通过结构体指针访问成员;

💬 代码演示

1. 仍然可以用点操作符来写,但是略显冗琐;❎(可以但不推荐)

📌 注意事项: (*p).name ✅ *p.name ❌ 注意优先级问题!

struct Book {
    char name[20];
    char id[20];
    int price;
};

int main()
{
    struct Book b = {"C语言", "C20210509", 55};
    struct Book* pb = &b;
    printf("书名:%s\n", (*pb).name);
    printf("书号:%s\n", (*pb).id);
    printf("定价:%d\n", (*pb).price);
    
    return 0;
}

2. 使用箭头操作符,更加直观; ✅

struct Book {
    char name[20];
    char id[20];
    int price;
};

int main()
{
    struct Book b = {"C语言", "C20210509", 55};
    struct Book* pb = &b;
    printf("书名:%s\n", pb->name);
    printf("书号:%s\n", pb->id);
    printf("定价:%d\n", pb->price);

    return 0;
}

十一章、表达式求值

表达式求值的顺序一部分是由操作符的优先级和结合性决定。同样,有些表达式的操作数在求职过程中可能需要转换为其他类型。

0x00 隐式类型转换

❓ 什么是整型提升:

① C的整型算术运算至少以缺省整型的精度来进行的;

② 为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型这种转换,称为整型提升;

③ 整型提升:按照变量的数据类型的符号位来提升;

🔑 图解整型提升:

❓ 那么问题又来了,如何进行整型提升呢?

💡 整型提升是按照变量的数据类型的符号位来进行提升的;

📚 整型提升讲解(请仔细看注释的步骤):

int main()
{
    // 我们发现 a 和 b 都是 char 类型,都没有达到一个 int 的大小
    // 🔺这里就会发生整型提升
    char a = 3;
    //      00000000000000000000000000000011
    //      00000011 - a  因为是char类型,所以只能放8个比特位(截断)🔪
    char b = 127;
    //      00000000000000000000000001111111
    //      01111111 - b  同上,截断,存储的是8个比特位 🔪

    char c = a + b;
    // 首先看a符号:char有符号,是正数,按照原来变量的符号位来提升
    // 然后看b符号:char有符号,是正数,提升的时候也是补0
    //      00000000000000000000000000000011 (高位补0,提升完结果还是这个)
    //   +  00000000000000000000000001111111
    // -------------------------------------
    //      00000000000000000000000010000010 (这个结果要存到c里,c里只能存8个比特位)
    // 所以进行截断 🔪
    // 10000010 - c   (C里存的)   

    
    /* 这时我们要打印它 */    
    printf("%d\n", c);
    // 🔺这时,c要发生整型提升:
    // 我们看c的符号,char有符号,是负数,高位进行整型提升,补1
    //      10000010 - c  // 然后进行整型提升
    //      11111111111111111111111110000010 (补完1 之后的结果)

    // 🔺注意:这里是负数,原反补是不相同的!
    // 打印出来的是原码,内存里的是补码,现在开始反推:
    //      11111111111111111111111110000010 (补码)
    //      11111111111111111111111110000001 - 反码(补码-1)
    //      00000000000000000000000001111110 - 原码
    //      ==  -126

    return 0;
}

🚩 运行结果: -126

💬 整型提升的栗子1:下列代码运行的结果是什么(体会整型提升的存在)

int main()
{
    char a = 0xb6;
    short b = 0xb600;
    int c = 0xb600000;
    if(a == 0xb6)
        printf("a"); //无
    if(b == 0xb600)
        printf("b"); //无
    if(c == 0xb600000)
        printf("c"); //c
    return 0;
}

🚩 运行结果: c

❓ 为什么 a 和 b 不会被打印出来呢

🔑 解析:

① 因为表达式里的 a 是 char 类型,因为没有达到整型大小,所以需要进行整型提升;

② 提升后比较当然不会相等,所以不会打印a,short 同理,c也不会被打印;

③ 还有一种解释方式:char a 里面存不下,所以不是 0xb6 ,所以不打印;

💬 整型提升的栗子2:下列代码运行结果是什么(体会整型提升的存在)

int main()
{
    char c = 1;
    printf("%u\n", sizeof(c));  // 1
    printf("%u\n", sizeof(+c)); // 4  整型提升后等于计算一个整型的大小
    printf("%u\n", sizeof(-c)); // 1
    printf("%u\n", sizeof(!c)); // 4  gcc-4
    
    return 0;
}

🔑 解析:

① sizeof(c) ,c是char型,结果自然是1;

② sizeof(+c),+c参与运算了,就会发生整型提升,相当于计算了一个整型的大小,所以为4;

③ sizeof(-c),同上,一样的道理,所以为4;

③ sizeof( !c) ,这里值得一提的是,有些编辑器结果可能不是4,但是根据gcc为准,答案为4;

🔺 结论:

① 通过上面的例子ba,可以得到结论:到整型提升是确实存在的;

② 比 int 大的不需要整型提升,比 int 小的要进行整型提升;

0x02 算术转换

📚 定义:如果某个操作数的各个操作数属于不同的类型,

那么除非其中一个操作数的转换为另一个操作数的类型,否则操作无法进行;

📚 寻常算数转换:如果某个操作数类型在下面的这个表里,排名较低,

那么首先要转换为另外一个操作数的类型,然后才能执行运算;

🌰 举个栗子:(如果 int 类型的变量和 float 类型的变量放在一起,这时要把 int 转换成 float)

📌 注意事项:算数转换要合理,要不然会产生潜在的问题;

💬 精度丢失问题:

int main()
{
    float f = 3.14;
    int num = f; // 隐式转换,会有精度丢失
    printf("%d\n", num); // 3

    return 0;
}

🚩 3

0x03 操作符的属性

📚 复杂表达式的求值有三个影响的因素:

① 操作符的优先级;

② 操作符的结合性;

③ 是否控制求值顺序;

❓ 两个相邻的操作符先执行哪个?取决于他们的优先级,如果两者的优先级相同,

💬 代码演示:优先级决定了计算顺序

int main()
{
    int a = 3;
    int b = 5;
    int c = a + b * 7; // 优先级决定:先乘后加

    return 0;
}

💬 代码演示:优先级一样,此时优先级不起作用,结合性决定顺序

int main()
{
    int a = 3;
    int b = 5;
    int c = a + b + 7; // 先算左边,再算右边

    return 0;
}

📚 运算符优先级表:

操作符

描述

用法示例

结合类型

结合性

是否控制求值顺序

( )

聚组

(表达式)

与表达式相同

N/A

( )

函数调用

rexp(rexp, ..., rexp)

rexp

L-R

[ ]

下标引用

rexp[rexp]

lexp

L-R

.

访问结构成员

lexp.member_name

lexp

L-R

->

访问结构指针成员

rexp->member_name

lexp

L-R

++

后缀自增

lexp++

rexp

L-R

--

后缀自减

lexp--

rexp

L-R

!

逻辑反

!rexp

rexp

R-L

~

按位取反

~rexp

rexp

R-L

单目,表示正值

+rexp

rexp

R-L

-

单目,表示负值

-rexp

rexp

R-L

++

前缀自增

++lexp

rexp

R-L

--

前缀自减

--lexp

rexp

R-L

*

间接访问

*rexp

lexp

R-L

&

取地址

&lexp

rexp

R-L

sizeof

取其长度,以字节表示

sizeof rexp szieof(类型)

rexp

R-L

(类型)

类型转换

(类型)rexp

rexp

R-L

*

乘法

rexp*rexp

rexp

L-R

/

除法

rexp/rexp

rexp

L-R

%

整数取余

rexp%rexp

rexp

L-R

加法

rexp+rexp

rexp

L-R

-

减法

rexp-rexp

rexp

L-R

<<

左移位

rexp<<rexp

rexp

L-R

>>

右移位

rexp>>rexp

rexp

L-R

>

大于

rexp>rexp

rexp

L-R

>=

大于等于

rexp>=rexp

rexp

L-R

<

小于

rexp<rexp

rexp

L-R

<=

小于等于

rexp<=rexp

rexp

L-R

==

等于

rexp==rexp

rexp

L-R

!=

不等于

rexp!=rexp

rexp

L-R

&

位与

rexp&rexp

rexp

L-R

^

位异或

rexp^rexp

rexp

L-R

|

位或

rexp|rexp

rexp

L-R

&&

逻辑与

rexp&&rexp

rexp

L-R

||

逻辑或

rexp&&rexp

rexp

L-R

?:

条件操作符

rexp?rexp:rexp

rexp

L-R

=

赋值

lexp=rexp

rexp

N/V

+=

加等于

lexp+=rexp

rexp

R-L

-=

减等于

lexp-=rexp

rexp

R-L

*=

乘等于

lexp*=rexp

rexp

R-L

/=

除等于

lexp /= rexp

rexp

R-L

%=

以...取模

lexp %= rexp

rexp

R-L

<<=

以...左移

lexp <<= rexp

rexp

R-L

>>=

以...右移

lexp >>= rexp

rexp

R-L

&=

以...与

lexp &= rexp

rexp

R-L

^=

以...异或

lexp ^= rexp

rexp

R-L

|=

以...或

lexp |= rexp

rexp

R-L

,

逗号

rexp, rexp

rexp

L-R

❌ 问题表达式:

❌ 非法表达式:( 出自《C和指针》)

int main()
{
    int i = 10;
    i = i-- - --i * ( i = -3 ) * i++ + ++i;
    printf("i = %d\n", i);

    return 0;
}

🔑 解析: 堪比《茴香豆的一万种写法》,

这种代码,运行结果取决于环境,不要写出这种代码!

作业

0x00 选择题

❓ 下面哪个是位操作符 ( );

A. & B. && C. || D. !

0x01 分析代码

❓ 下列代码运行后的结果是什么

#include <stdio.h>

int main()
{
	int a, b, c;
	a = 5;
	c = ++a;
	b = ++c, c++, ++a, a++;
	b += a++ + c;
	printf("a = %d b = %d c = %d\n:", a, b, c);

	return 0;
}

0x02 交换两个变量(不创建临时变量)

💬 不允许创建临时变量,交换两个整数的内容;

0x03 统计二进制中1的个数

💬 输入一个整数,写一个函数返回该数32位二进制表示中1的个数,其中负数用补码表示。

( eg. 15 0000 1111 4个1 )

🐂 牛客网OJ链接:二进制中1的个数__牛客网

0x04 求两个数二进制中不同位的个数

💬 编程实现:两个 int(32位)整数的 m 和 n 的二进制表达中,有多少个位 (bit) 不同?

( eg. 输入 1999 2299 输出 7 )

🐂 牛客网OJ链接:两个整数二进制位不同个数__牛客网

0x05 打印整数二进制的奇数位和偶数位

💬 说明:获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列;

答案

0x00 第一题:选择题

💡 正确答案:A

🔑 解析:

0x01 第二题:下列代码运行结果

💡 正确答案:a = 9 b = 23 c = 8

🔑 解析:

0x02 第三题:不创建临时变量交换两个整数内容

💬 不允许创建临时变量,交换两个整数的内容;

💡 参考答案:

void Swap (
    int* pa,
    int* pb
    )
{
    *pa = *pa ^ *pb;
    *pb = *pa ^ *pb;
    *pa = *pa ^ *pb;
}

int main()
{
    int a = 10;
    int b = 20;
    printf("交换前:a = %d  b = %d\n", a, b);
    Swap(&a, &b);
    printf("交换后:a = %d  b = %d\n", a, b);

    return 0;
}

0x03 第四题:统计二进制中1的个数

💬 输入一个整数,写一个函数返回该数32位二进制表示中1的个数,其中负数用补码表示。

( eg. 15 0000 1111 4个1 )

💡 参考答案:

1. 模除法

int CountNum1(int n)
{
    int count = 0;
    while(n) {
        if(n % 2 == 1) {
            count++;
        }
        n /= 2;
    }
    return count;
}

int main()
{
    int num = 0;
    scanf("%d", &num);
    int ret = CountNum1(num);
    printf("%d\n", ret);

    return 0;
}

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

int CountNum1(int n)
{
    int count = 0;
    int i = 0;
    for(i=0; i<32; i++) {
        if( ((n>>i) & 1) == 1) {
            count++;
        }
    }
    return count;
}

int main()
{
    int num = 0;
    scanf("%d", &num);
    int ret = CountNum1(num);
    printf("%d\n", ret);

    return 0;
}

3. &=

int CountNum1(int n) 
{
    int count = 0;
    while(n) {
        n = n & (n - 1);
        count++;
    }
    return count;
}

int main()
{
    int num = 0;
    scanf("%d", &num);
    int ret = CountNum1(num);
    printf("%d\n", ret);

    return 0;
}

0x04 第五题:求两个数二进制中不同位的个数

💬 编程实现:两个 int(32位)整数的 m 和 n 的二进制表达中,有多少个位 (bit) 不同?

( eg. 输入 1999 2299 输出 7 )

1. >> & 移位按位与

int main()
{
    int m = 0;
    int n = 0;
    scanf("%d %d", &m, &n);

    int count = 0;
    int i = 0;
    for(i=0; i<32; i++) {
        if( ((m >> i) & 1) != ((n >> i) & 1) ) {
            count++;
        } 
    }
    printf("%d\n", count);

    return 0;
}

2. 异或法,然后统计二进制中有几个1

int NumberOf1(int n) 
{
    int count = 0;
    while(n) {
        n = n & (n - 1);
        count++;
    }
    return count;
}

int main()
{
    int m = 0;
    int n = 0;
    scanf("%d%d", &m, &n);

    int count = 0;
    int ret = m ^ n; // 相同为0,相异为1
    // 统计一下ret的二进制中有几个1,就说明m和n的二进制位中有几个位置不同
    count = NumberOf1(ret);
    printf("%d\n", count);

    return 0;
}

0x05 第六题:打印整数二进制的奇数位和偶数位

💬 说明:获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列;

int main()
{
    int n = 0;
    scanf("%d", &n);
    // 获取n的2进制中的奇数位和偶数位
    int i = 0;
    // 打印偶数位
    for(i=31; i>=1; i -= 2) {
        printf("%d ", (n >> i) & 1);
    }
    printf("\n");
    // 打印奇数位
    for(i=30; i>=0; i-=2) {
        printf("%d ", (n >> i) & 1);
    }

    return 0;
}

参考资料:

Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .

比特科技. C语言基础[EB/OL]. 2021[2021.8.31]. .

📌 本文作者: Foxny

📃 更新记录: 2021.6.9

勘误记录:

📜 本文声明: 由于作者水平有限,本文有错误和不准确之处在所难免,本人也很想知道这些错误,恳望读者批评指正!

本章完。