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

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

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


0x00 下标引用操作符 [ ]

0467873afd692388c55d708af5670231_20210527151220336.png

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


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 函数调用操作符 ( )53e1afed42b8ff2e88944c55b8c7d168_20210527151313886.png

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


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


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


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


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

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

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

900d5a8bc5b93072b8792d4c232edf4c_20210527151510133.png

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


   https://blog.csdn.net/weixin_50502862/article/details/115426860


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


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 结构成员访问操作符 - 箭头操作符 ->

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

f700bac30c3d3637c5caa9757a9a77b4_20210527151920907.png

💬 代码演示


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的整型算术运算至少以缺省整型的精度来进行的;


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


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


🔑  图解整型提升:

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

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


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

0c170487b770089c80cd2b12424fc49e_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


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


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)

4ba1fd0988432b701a992545a124e68f_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

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


💬 精度丢失问题:


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

❌ 问题表达式:

66565b61c79c3226c0807d3aca817c5e_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

483a94840b79413137bb1fada72fd9e2_20210527174450688.png


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


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

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


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

相关文章
|
9月前
|
C语言
C语言中条件操作符的应用
最后,条件操作符是个超级英雄,但不是每个代码问题都需要一个超级英雄来解决。一定要在适当的时候适度的使用它,那么它将成为你的编程工具箱中的一件强力工具。
443 75
|
存储 C语言 索引
【C语言篇】操作符详解(下篇)
如果某个操作数的类型在上⾯这个列表中排名靠后,那么⾸先要转换为另外⼀个操作数的类型后执⾏运算。
219 0
|
程序员 编译器 C语言
【C语言篇】操作符详解(上篇)
这是合法表达式,不会报错,但是通常达不到想要的结果, 即不是保证变量 j 的值在 i 和 k 之间。因为关系运算符是从左到右计算,所以实际执⾏的是下⾯的表达式。
761 0
|
存储 网络协议 C语言
【C语言】位操作符详解 - 《开心消消乐》
位操作符用于在位级别上进行操作。C语言提供了一组位操作符,允许你直接操作整数类型的二进制表示。这些操作符可以有效地处理标志、掩码、位字段等低级编程任务。
588 8
|
C语言
【C语言】逻辑操作符详解 - 《真假美猴王 ! 》
C语言中有三种主要的逻辑运算符:逻辑与(`&&`)、逻辑或(`||`)和逻辑非(`!`)。这些运算符用于执行布尔逻辑运算。
1193 7
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
489 10
|
存储 编译器 C语言
【C语言】简单介绍进制和操作符
【C语言】简单介绍进制和操作符
462 1
|
C语言
C语言操作符(补充+面试)
C语言操作符(补充+面试)
204 6
|
存储 编译器 C语言
初识C语言5——操作符详解
初识C语言5——操作符详解
403 0
|
存储 编译器 C语言
十一:《初学C语言》— 操作符详解(上)
【8月更文挑战第12天】本篇文章讲解了二进制与非二进制的转换;原码反码和补码;移位操作符及位操作符,并附上多个教学代码及代码练习示例
196 0
十一:《初学C语言》—  操作符详解(上)