ToB企服应用市场:ToB评测及商务社交产业平台

标题: 在代码的虚实架构里:操纵符与数据处理的灵魂和鸣(下) [打印本页]

作者: 泉缘泉    时间: 2025-1-26 06:47
标题: 在代码的虚实架构里:操纵符与数据处理的灵魂和鸣(下)

   大家好啊,我是小象٩(๑òωó๑)۶
我的博客:Xiao Xiangζั͡ޓއއ
    很高兴见到大家,希望可以或许和大家一起交换学习,共同进步

这一节我们继续来学习操纵符的相干知识,包罗单目操纵符,逗号表达式,下标访问操纵符,函数调操纵符,布局体的声明、定义、初始化,布局体成员访问操纵符,布局体成员的直接访问和间接访问,操纵符的优先级和结合性,表达式求值…
  
  
一、单目操纵符

在C语言中,单目操纵符(unary operator)是指只需要一个操纵数的操纵符。这些操纵符通常用于对单个变量或表达式举行操纵,如取反、递增、递减、获取地点、解引用、范例转换等。
单目操纵符有这些:
   !、++、–、&、*、+、-、~ 、sizeof、(范例)
  单目操纵符的特点是只有一个操纵数,在单目操纵符中只有 & 和 * 没有先容,这2个操纵符,我们放在学习指针的时间学习。
二、逗号表达式

   exp1, exp2, exp3, …expN
  逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次实行整个表达式的效果是末了一个表达式的效果
看几个例子:
  1. int main()
  2. {
  3.         int a = 1;
  4.         int b = 2;
  5.         int c = (a > b, a = b + 10, a, b = a + 1);
  6.         printf("%d", c);
  7.         return 0;
  8. }
复制代码
很显然,我们能得到效果13

再看一个例子:
  1. a = get_val();
  2. count_val(a);
  3. while (a > 0)
  4. {
  5.         //业务处理
  6.         //...
  7.         a = get_val();
  8.         count_val(a);
  9. }
复制代码
这里这么写也是可以的,当是过程会显得冗余
我们可以这样改:
  1. 如果使⽤逗号表达式,改写:
  2. while (a = get_val(), count_val(a), a>0)
  3. {
  4. //业务处理
  5. }
复制代码
三、下标访问[]、函数调用()

3.1 下标引用操纵符

在 C 语言中,下标引用操纵符 [] 用于访问数组元素或通过指针举行间接访问。
操纵数:一个数组名 + 一个索引值(下标)
  1. int arr[11];//创建数组
  2. arr[10] = 11;//实⽤下标引⽤操作符。
复制代码
注意:[ ]的两个操纵数是arr和10
3.2 函数调用操纵符

C 语言中,函数调用操纵符是一对圆括号 () ,它用于调用函数并传递参数(如果有)。
注意:接受一个大概多个操纵数:第⼀个操纵数是函数名,剩余的操纵数就是传递给函数的参数。
举个例子:
  1. #include<stdio.h>
  2. int main()
  3. {
  4.    printf("xiaofeixiang");
  5.    return 0;
  6. }
复制代码
这里括号的操纵数是printf和xiaofeixiang
   这里问个问题,像()这种函数调用操纵符,最少有几个操纵数
答案:一个,就是函数名
  四、布局成员访问操纵符

4.1 布局体

在 C 语言中,布局体(struct)是一种户自定义的数据范例,它允许将不同范例的数据组合在一起,形成一个单一的实体。
比如说,我们形貌一个门生的时间需要姓名、年龄、学号、身高、体重等;
这个时间如果使用单一的数据范例是远远不够用的。C语言为了办理这个问题,增加了布局体这种自定义的数据范例,让程序员可以自己创造得当的范例。
   布局是一些值的集合,这些值称为成员变量。布局的每个成员可以是不同范例的变量,如:标量、数组、指针,甚至是其他布局体。
我们常使用的数组则是一类元素的集合,和这里的布局体不一样
  4.1.1 布局的声明

我们来看一下:
  1. struct tag
  2. {
  3.        member-list;
  4. }variable-list;
复制代码
这里的关键字是struct,名字是tag,成员列表是member-list(这里是一个成员,也可以是多个成员),末了variable-list是变量列表(可有可无)
举个例子:我们来形貌一个门生
  1. struct Stu
  2. {
  3.         char name[20];//名字
  4.         int age;//年龄
  5.         char sex[6];//性别  ps:一个汉字占两个字符
  6.         char id[20];//学号
  7. }; //分号不能丢
复制代码
如果我们要创建布局体变量的话,就可以这样:
  1. int main()
  2. {
  3.       struct stu s1;//结构体变量1
  4.       struct stu s2;//结构体变量2
  5.       return 0;
  6. }
复制代码
当然我们也可以在外部创建全局变量
  1. struct stu s3;int main()
  2. {
  3.       struct stu s1;//结构体变量1
  4.       struct stu s2;//结构体变量2
  5.       return 0;
  6. }
复制代码
如果我们要创建变量列表的话,我们可以这么做:
  1. struct Stu
  2. {
  3.         char name[20];//名字
  4.         int age;//年龄
  5.         char sex[6];//性别  ps:一个汉字占两个字符
  6.         char id[20];//学号
  7. }s4,s5,s6
复制代码
像这样,我们可以创建一个大概多个列表,如s4,s5,s6
4.1.2布局体变量的定义和初始化

4.1.2.1 布局体变量的定义

首先,我们来看变量的定义:
  1. struct Point
  2. {
  3. int x;
  4. int y;
  5. }p1;
复制代码
4.1.2.2 布局体变量的初始化

布局体中是存放多个成员的,以是初始化也使用{ }
我们先来创建两个布局体变量:
第一个:
  1. struct Stu
  2. {
  3.         char name[20];//名字
  4.         int age;//年龄
  5. }; //分号不能丢
复制代码
第二个
  1. struct Point
  2. {
  3. int x;
  4. int y;
  5. }p1;
复制代码
对于第二个布局体的初始化,我们可以这么做:
  1. struct Point p3 = {10, 20};
复制代码
对于第一个布局体的初始化,我们可以这么做:
  1. struct Stu s1 = {"zhangsan", 20};//初始化
复制代码
一样平常来说,初始化要按照成员列表来举行初始化,如果我们不想按照次序初始化的话,我们可以这么操纵:
  1. struct Stu s2 = {.age=20, .name="lisi"};//指定顺序初始化
复制代码
在名字前面加上 . 即可
4.1.2.3 布局体嵌套初始化

像这样:
  1. struct Point
  2. {
  3. int x;
  4. int y;
  5. }p1;
  6. struct Data{        int n;        struct Point p;        double d;};
复制代码
这个时间我们如果要初始化的话,应该这么做:
  1. int main()
  2. {
  3.     struct Data s1 = {20,{10,20},100};
  4. }
复制代码
4.2 布局成员访问操纵符

4.2.1 布局体成员的直接访问

布局体成员的直接访问是通过点操纵符(.)访问的。点操纵符接受两个操纵数。
使用方式:布局体变量.成员名
  1. #include <stdio.h>
  2. struct Point
  3. {
  4.         int x;
  5.         int y;
  6. }p = { 1,2 };
  7. int main()
  8. {
  9.         printf("x: %d y: %d\n", p.x, p.y);
  10.         return 0;
  11. }
复制代码
再看一个例子
我们也可以输入自己想要的值:
  1. struct Stu
  2. {
  3.         char name[20];//名字
  4.         int age;//年龄
  5.         char sex[6];//性别  ps:一个汉字占两个字符
  6.         char id[20];//学号
  7. };
  8. int main()
  9. {
  10.     scanf("%s %d %s %s",s1.name,&(s1.age),s1.sex,s1.id);
  11.     return 0;
  12. }
复制代码
注意:除了age这里不需要使用取地点操纵符,因为数组名自己就是一个地点.
4.2.2 布局体成员的间接访问

有时间我们得到的不是一个布局体变量,而是得到了一个指向布局体的指针。如下所示:
使用方式:布局体指针->成员名
  1. #include <stdio.h>
  2. struct Point
  3. {
  4.         int x;
  5.         int y;
  6. };
  7. int main()
  8. {
  9.         struct Point p = { 3, 4 };
  10.         struct Point* ptr = &p;
  11.         ptr->x = 10;
  12.         ptr->y = 20;
  13.         printf("x = %d y = %d\n", ptr->x, ptr->y);
  14.         return 0;
  15. }
复制代码
五、操纵符的属性:优先级、结合性

C语言的操纵符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算次序。
5.1 优先级

在 C 语言中,运算符优先级决定了表达式中不同运算符实行的先后次序。优先级高的运算符先举行运算,只有当优先级高的运算完成后,才会举行优先级低的运算。
举个例子:
  1. 3 + 4 * 5;
复制代码
上面例子中,表达式 3 + 45 里面既有加法运算符( + ),又有乘法运算符()。由于乘法的优先级高于加法,以是会先计算 4*5 ,而不是先计算 3 + 4 。
5.2 结合性

在 C 语言中,结合性用于确定当表达式中出现多个优先级相同的运算符时,运算的实行次序。
如果两个运算符优先级相同,优先级没办法确定先计算哪个了,这时间就需要思量结合性,则根据运算符是左结合,还是右结合,决定实行次序。 大部分运算符是左结合(从左到右实行),少数运算符是右结合(从右到左实行),比如赋值运算符( = )。
举个例子:
  1. 5 * 6 / 2;
复制代码
上面例子中,和 / 的优先级相同,它们都是左结合运算符,以是从左到右实行,先计算 56 ,再计算 / 2 。
   下面是一些运算符的优先级次序 (按照优先级从高到低排列)
• 圆括号( ( ) )
• 自增运算符( ++ ),自减运算符( – )
•单目运算符( + 和 - )
• 乘法( * ),除法( / ) • 加法( + ),减法( - )
• 关系运算符( < 、 > 等)
•赋值运算符( = )
  由于圆括号的优先级最高,可以使用它改变其他运算符的优先级。

六、表达式求值

6.1 整形提升

在 C 语言中,整型提升(Integer Promotion)是一种隐式范例转换机制,它在表达式求值过程中自动发生。当一个整型范例的操纵数在参与运算时,其范例可能会被提升为更高级别的整型范例,以确保运算的准确性和一致性。
C语言中整型算术运算总是至少以
缺省(默认)整型范例
int)的精度来举行的。
为了获得这个精度,表达式中的字符和短整型操纵数在使用之前被转换为普通整型,这种转换称为整型提升。
注意:char范例也是属于整型家族的,因为字符在存储的时间,存储的是ASCII值。
   PS:整型提升的意义
表达式的整型运算要在CPU的相应运算器件内实行,CPU内整型运算器(ALU)的操纵数的字节长度一样平常就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char范例的相加,在CPU实行时实际上也要先转换为CPU内整型操纵数的标准长度。 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(固然呆板指令中可能有这种字节相加指令)。以是,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去实行运算。
  怎样举行整行提升呢?
对于有符号整数:有符号整数提升是按照变量的数据范例的符号位来提升的
对于无符号整数:无符号整数提升,高位补0
举个例子:
  1. int main()
  2. {
  3.         //int a = 10;
  4.         //short s = 5;
  5.         //int b = s + a;
  6.         char c1 = 5;
  7.         char c2 = 126;
  8.         char c3 = c1 + c2;//131
  9.         //00000000 00000000 00000000 00000101
  10.         //00000101 - c1
  11.         //00000000 00000000 00000000 01111110
  12.         //01111110 - c2
  13.         //
  14.         //00000000 00000000 00000000 00000101 - c1 - 发生整型提升
  15.         //00000000 00000000 00000000 01111110 - c2 - 发生整型提升
  16.         //00000000 00000000 00000000 10000011
  17.         //10000011 - c3
  18.         //%d 是打印10进制的有符号的整数
  19.         //11111111 11111111 11111111 10000011  -补码
  20.         //10000000 00000000 00000000 01111100
  21.         //10000000 00000000 00000000 01111101 -- 原码
  22.         //-125
  23.         printf("%d\n", c3);//-125
  24.         return 0;
  25. }
复制代码
PS:
  1. int -- signed int
  2. char 是signed char 还是unsigned char ?取决于编译器
  3. 在常见的编译器上char == signed char
复制代码
6.2 算术转换

在 C 语言中,**算术转换(Arithmetic Conversions)是指在涉及不同数据范例的算术运算表达式中,操纵数会被自动转换为一种共同的数据范例,以便举行运算。
** 如果某个操纵符的各个操纵数属于不同的范例,那么除非其中一个操纵数的转换为另一个操纵数的范例,否则操纵就无法举行。
下面的层次体系称为平常算术转换。
  1. long double
  2. double
  3. float
  4. unsigned long int
  5. long int
  6. unsigned int
  7. int
复制代码
如果某个操纵数的范例在上面这个列表中排名靠后,那么首先要转换为另外一个操纵数的范例后实行运算。
6.3 问题表达式解析

6.3.1 表达式1

  1. //表达式的求值部分由操作符的优先级决定。
  2. //表达式1
  3. a*b + c*d + e*f
复制代码
表达式1在计算的时间,由于" * " ⽐ + 的优先级高,只能包管," * " 的计算是比 + 早,但是优先级并不能决定第三个 * 比第一个 + 早实行。
以是表达式的计算机次序就可能是:
  1. a*b
  2. c*d
  3. a*b + c*d
  4. e*f
  5. a*b + c*d + e*f
复制代码
也可以
  1. a*b
  2. c*d
  3. e*f
  4. a*b + c*d
  5. a*b + c*d + e*f
复制代码
6.3.2 表达式2

  1. //表达式2
  2. c + --c;
复制代码
同上,操纵符的优先级只能决定自减 – 的运算在 + 的运算的前面,但是我们并没有办法得知, + 操纵符的左操纵数的获取在右操纵数之前还是之后求值,以是效果是不可预测的,是有歧义的。
6.3.3 表达式3

  1. //表达式3
  2. int main()
  3. {
  4.         int i = 10;
  5.         i = i-- - --i * (i = -3) * i++ + ++i;
  6.         printf("i = %d\n", i);
  7.         return 0;
  8. }
复制代码
表达式3在不同编译器中测试效果:非法表达式程序的效果。

6.3.4 表达式4

  1. #include <stdio.h>
  2. int fun()
  3. {
  4.         static int count = 1;
  5.         return ++count;
  6. }
  7. int main()
  8. {
  9.         int answer;
  10.         answer = fun() - fun() * fun();
  11.         printf("%d\n", answer);//输出多少?
  12.         return 0;
  13. }
复制代码
对于answer = fun() - fun( ) * fun( ); 我们只能通过操纵符的优先级得知:先算乘法,再算减法。
函数的调用先后次序无法通过操纵符的优先级确定。
以是这个代码也有问题。
6.3.5 表达式5

  1. //表达式5
  2. #include <stdio.h>
  3. int main()
  4. {
  5.         int i = 1;
  6.         int ret = (++i) + (++i) + (++i);
  7.         printf("%d\n", ret);
  8.         printf("%d\n", i);
  9.         return 0;
  10. }
复制代码
这段代码中的第一个 + 在实行的时间,第三个++是否实行,这个是不确定的,因为依靠操纵符的优先
级和结合性是无法决定第⼀个 + 和第三个前置 ++ 的先后次序。
6.4 总结

即使有了操纵符的优先级和结合性,我们写出的表达式依然有可能不能通过操纵符的属性确定唯一的计算路径,那这个表达式就是存在潜伏风险的,建议不要写出特殊复杂的表达式。
七、末了

这一课的内容就到这里了,下节课继续学习操纵符的其他一些知识
如果内容有什么问题的话欢迎指正,有什么问题也可以问我!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4