第五节-操作符详解

目录

1.操作符分类

2.算术操作符

3.移位操作符

4.位操作符

5.赋值操作符

7.关系操作符

8.逻辑操作符

 9.条件操作符

10.逗号表达式

11.下标引用操作符、函数调用操作符和结构成员

12.表达式求值

12.1隐式类型转换

12.2算术转换

12.3 操作符的属性


1.操作符分类

  • 算数操作符
  • 移位操作符
  • 位操作符
  • 赋值操作符
  • 单目操作符
  • 关系操作符
  • 逻辑操作符
  • 条件操作符
  • 逗号表达式
  • 下标引用、函数调用和结构成员

2.算术操作符

  +     -     *    /    %

  • 除了%操作符之外,其他的几个操作符可以作用于整数和浮点数,计算的是整除之后的余数,比如,5%2,得到的结果是1,%取模操作符的两端必须是整数
  • 对于  操作符,如果两个操作数都为整数,执行整数除法。并且只要有浮点数执行的就是浮点数除法,举个例子,5/2的结果是2
  •  % 操作符的两个操作数必须为整数。返回的是整除之后的余数

3.移位操作符

<<  左移操作符

>>  右移操作符

注:移位操作符的操作数只能是整数,并左移和右移操作符移动的都是二进制位

数值可以用多种进制表示,进制只是一种表示形式而已

2进制

0~1

8进制

0~7

10进制

0~9

整数的二进制表示有三种

  1. 原码
  2. 反码
  3. 补码

正整数的原码、反码、补码相同

负整数的原码、反码、补码:

  • 原码:先写它对应的正整数的原码,然后将符号位(第一位为符号位,符号位为0是正整数,符号位为0是负整数)改为1,就是它的原码
  • 反码:符号位不变,其他位按位取反
  • 补码:反码+1

 整数在内存中存储的是补码

用printf打印的是原码的值

1.左移操作符:左边丢弃,右边补0(左移有×2的效果)

这里的移动不会改变a本来的值

整数占 4个字节=32个比特位 

 2.右移操作符:

  • 算术移位:右边丢弃,左边补原符号位
  • 逻辑移位:右边丢弃,左边补0

是算术移位还是逻辑移位取决于编译器,多数编译器进行的是算术移位 

 注:

对于移位操作符,不要移动负数位,这个是标准未定义的,比如,a<<-1,这样的写法是错误的

4.位操作符

&        //按(二进制)位与

|         //按(二进制)位或

^        //按(二进制)位异或

注:它们的操作数必须是整数

&:对应的二进制位,两个同时为1,才为1(对补码进行操作)

%d 意味着打印一个有符号的整数

 | :对应的二进制位只要有1,就为1 ;两个同时为0,才为0;

^:对应的二进制位,相同为0,相异为1

一道面试题:

不能创建临时变量(第三个变量),实现两个数的交换

//方法1:

这个方法能实现两个数的交换,但是存在缺点,因为a和b都是在整型,当a和b的数太大时会发生栈溢出的现象

//方法2:这种方法不会出现栈溢出的现象,但是按位异或的方法只适用于整数,浮点数是没法进行按位异或

a^a=0;

a^1=a;

异或操作符支持交换律

但是在实际的开发中,多数还是会选择创建临时变量来交换两个数,多创建一个变量不会占用很大的空间,并且执行效率是最快的

5.赋值操作符

赋值操作符可以给变量重新赋值

#include<stdio.>
int main()
{
    int a=3;//这里是初始化
    a=5;//这里是赋值
}

赋值操作符可以连续使用,

#include<stdio.h>
int main()
{
    int a=10;
    int x=0;
    int y=20;
    a=x=y+1;//连续赋值
}

连续赋值是允许,但是我们一般不会选择连续赋值这种使用方法,不便于调试,在调试的过程不便于观察值的变化,比如上面的代码x的值就不便于观察,所以多数时候选择分开写更好

复合赋值符

+=

-=

*=

/=

%=

>>=

<<=

&=

|=

^=

这些运算符都可以写成复合的效果

6.单目操作符

 单目操作符是指:只有一个操作数的

整形占四个字节,取地址,取的是四个字节中的第一个字节的地址

sizeof操作符

#include<stdio.h>
int main()
{
    int a = 10;
	int n = sizeof(int);

	printf("%d\n", n);
	return 0;
}

sizeof是一个操作符

计算的是变量所占内存空间的大小,单位是字节

计算类型所创建的变量占据空间的大小,单位是字节

sizeof()里面放数组名时,计算的是整个数组的大小

注意:

sizeof(a);

sizeof a;//这两种写法是一样,对计算a的大小没有影响

//这个()可以删去说明sizeof是操作符不是函数

但是

sizeof(int);//这里的()是不能删去的

strlen也是用来求字符串长度的,但是strlen是库函数

问下面这段代码(1)(2)(3)(4)分别的打印的是什么(这里有陷阱)

#include<stdio.h>
void test1(int arr[])
{
    printf("%d\n",sizeof(arr));//(2)

}
void test2(char ch[])
{
    printf("%d\n",sizeof(ch));//(4)
}
int main()
{
    int arr[10]={0};
    char ch[10]={0};
    printf("%d\n",sizeof(arr));//(1)
    printf("%d\n",sizeof(ch));//(3)
    test1(arr);
    test2(ch);
    return 0;
}

  (1)40 (2)4 (3)10 (4)

这里的char ch[ ]和int arr [ ]都是指针,指针的大小都是4/8个字节

~ 按位取反,对二进制位进行取反,也是对补码进行操作

 想要将二进制位中某个数由0变为1,由1变为0

前置++、后置++(++和--是一样的)

#include<stdio.h>
int main()
{
    int a=3;
    int b=++a;
    printf("%d\n",a);//4
    printf("%d\n",b);//4

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

前置++,先++,后使用

后置++,先使用,再++

#include<stdio.h>
int main()
{
    int a=10;
    printf("%d\n",a--);//10
    printf("%d\n",a);//9
    return 0;
}

for循环中前置和后置区别不大(控制循环变量的i)

*(解引用操作符)和指针一起用

#include<stdio.h>
int main()
{
    int a=10;
    int*p=&a;//这里的*告诉我们这个p是指针变量
    *p=20;//*是解引用,通过p里存的a的地址找到它所指向的内容,将a的值改为20
    // 相当于a=20
    printf("%d\n",a);//20

}  

(类型)  强制类型转换

这个其实我们之前就已经遇到,在猜数字游戏中,产生随机数就有遇到强制类型转换

#include<stdio.h>
int main()
{
    srand((unsigned int)time(NULL));//这里将time强制类型转换成unsigned int
//这里time返回的类型是time_t,而srand函数的参数是unsigned int类型,所以需要强制类转换
}
#include<stdio.h>
int main()
{
    int a=(int)3,14f;
    printf("%d\n",a);//3
    return 0;
}

这里如果没有强制类型转换,编译器会默认转换为double类型

7.关系操作符

>

>=

<

<=

!=                用于测试”不相等“

==                用于测试“相等”

在编程的过程中==和=不小心写错,导致的错误

不是所有的东西都可以用==比较相不相等的

比如,字符串的比较应该使用库函数strcmp进行比较

"abc"=="abcdef"//这样比较的是两个字符串的首字符的地址 

8.逻辑操作符

&&                逻辑与                //两边同时为真才是真

| |                  逻辑或                //两边只要有一个是真就是真

逻辑操作符只关注真假 

区分逻辑与和按位与

区分逻辑或和按位或

1&2------->0   

1&&2----->1

1 | 2------>3

1 | | 2---->1

#include<stdio.h>
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;
}

问上面这段代码的结果是?

//1 2 3 4

a++先使用再++,此次a=0,此条式子为假,不再继续进行(右边的式子不再进行),然后a开始++

&& 左边为假,右边就不计算了

| | 左边为真,右边就不计算了

 9.条件操作符

exp1?exp2:exp3

条件操作符又称为三目操作符(有三个操作数)

1.if ( a>5 )

                b=3;

   else

                b=-3;

上面的代码改写为条件操作符的形式

b=( ( a>5 )? 3 : -3 );

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

a > b ? a : b ;

10.逗号表达式

expt,exp2,exp3,...expN

逗号表达式,就是用逗号隔开的多个表达式

逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果

11.下标引用操作符、函数调用操作符和结构成员

1)[ ] 下标引用操作符

操作数:一个数组名+一个索引值

int arr [10];//创建数组

arr[10]-->*(arr+10)-->*(10+arr)-->10[arr]

访问的时候可以这么写,但是定义的时候不能这么写

//arr是数组首元素的地址

//arr+10就是跳过10个元素,指向了第11个元素

//*(arr+10)就是第11个元素

arr [ 9 ];//使用下标引用操作符

[  ]的两个操作数是arr和9  

//arr [9] 也可以写成 9 [arr]

2) 函数调用操作符

int Add(int x,int y)

{

        return x+y;

}

int main()

{

        int a=10;

        int b=20;

        int c=Add(a,b);//这里的()就是函数调用操作符,这个()不能省略掉

//操作数:Add,a,b,操作数至少是一个

}

3)结构成员

//不能将字符串赋给一个地址,name是数组名,代表首元素地址,所以不能将字符串直接赋给数组,应该使用库函数strcpy,将字符串拷贝放到name所指向的空间中

#include<stdio.h>

#include<string.h>

struct Stu

{

        char name[20];

        int age;

        double score;

};

void set_stu(struct Stu* ps)

{

        //strcpy(ss.name,"ming");

        //ss.age=19;

        //ss.score=100.0;

        //这里只是临时拷贝,不能更改实参,写成指针的形式

        //strcpy((*ps).name,"ming");

        //(*ps).age=20;

        //(*ps).score=100.0;

         //但是上面这种写法还是有一些冗余

        strcpy(ps->name,"ming");

        ps->age=19;

        ps->score=100.0;

}

void print_stu(struct Stu ss)

{

        printf("%s %d %f\n",ss.name,ss.age,ss.score);

}

int main()

{

        struct Stu s={0};

        set_stu(&s);

        print_stu(s);

        

        return 0;

}

//s传给ss时,实参传给形参,形参只是实参一个临时拷贝并不会改变实参的内容,所以应该用指针struct Stu*ss

结构体指针->成员

结构体对象.成员

ps->age等价于(*ps).age

12.表达式求值

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

12.1隐式类型转换

C的整型算术运算总是以缺省(默认)整型类型的精度来进行的

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

整型提升的意义:

 

 如何进行整型提升?

 整型提升是按照变量的数据类型的符号来提升的

整型提升的实例

 字符类型也是整型家族,字符在存储的时候存储的是ASCII码值,只要是整形在内存中存储就是补码

 这里的a和b都要发生整型提升,所以只能打印出c

 c只要参与表达式运算,就会发生整型提升,表达式+c,就会发生提升,所以sizeof(+c)是4个字节,表达式-c也会发生整型提升,所以sizeof(-c)是4个字节,sizeof(c)没有参与表达式运算,所以计算的结果就是1个字节,虽然这里的+c,-c什么都没有干,但是参与了表达式的运算,就要发生整型提升

12.2算术转换

如果莫格操作数的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作数就无法进行。下面的层次体系称为寻常算术转换

long double

double

float

ussigned long int 

long int

unsigned int 

int 

类型向上转换,向大的类型转换

当int和unsigned int相遇时,int转换为unsigned int

unsigned int 转换为long int

long int转换为 unsigned long int

.... 

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另一个操作数的类型后执行运算

警告:

算术转换要合理,不然会有一些潜在问题

12.3 操作符的属性

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

1. 操作符的优先级

2. 操作符的结合性

3. 是否控制求值顺序。

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

从上到下,优先级由高到低

N/A是没有结合性

L-R是从右向左结合

R-L是从左向右结合

     

一些问题表达式

//表达式的求值部分由操作符的优先级决定

//代码1

a*b+c*d+e*f

这里只是知道  *  的优先级高于  +  ,  *  的计算比  +  ,早,但是并不能决定第二个 * 比第一个 * 先执行

举个例子

3+2*4+5;

相邻的两个运算符只能说明+和*中*先计算 ,但是前面的+和后面的+谁先运算,优先级说了不算,优先级一定是相邻间的两个操作符,结合性是相邻操作符的优先级相同的前提下,这个时候是结合性说了算

如果我们已经确定里各种操作符的优先级,结合性和是否控制求值顺序这些属性,写出一个表达式并不一定计算表达式的唯一值

a*b+c*d+e*f这个表达式的计算顺序就可能是:

a*b    c*d    a*b+c*d    e*f     最后a*b+c*d+e*f

 这个计算顺序是不确定的,谁先执行可能会引起后面值得变化,这样得代码存在潜在问题

//代码2

c + --c;

注释:同上,操作符得优先级只能决定自减  - -  得运算在+得前面,但是我们并没有变法得知,+操作符得左操作符的获取在右操作数之前还是右操作数之后求值,所以结果是不可预测的,这里有歧义

c什么时候准备好的,这个是不知道的,比如,现在c=2,c可能进行--就准备好了,+前的c是2,然后进行--,c=1;然后再相加,此时表达式的结果是3

另外一种情况,c是--之后才准备好的,此时表达式的结果是2

//代码3-非法表达式

#include<stdio.h>
int main()
{
    int i=10;
    i=i-- - --i*(i=-3)*i++  + ++i;
    printf("i=%d\n",i);
    return 0;

}

这段代码在不同的编译器下测试结果是不同的

//代码4

//输出结果是?

#include<stdio.h>
int fun()
{
    static int count = 1;
    return ++count;
}
int main()
{
    int answer;
    answer = fun() - fun() * fun();
    printf("%d\n",answer);
    return 0;

}

我们只能确定*和-中*是先进行运算的,但是没办法确定fun函数哪个先调用

函数的调用顺序无法通过操作符的优先级确定 

//代码5

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

这段代码也是,在不同的编译器下,有不同结果

 


代码五转反汇编

ebp是寄存器

ebp中存放的是地址

ebp-8也是地址

把1放到ebp-8这块地址中,其实就是放到a中

 eax、ebx、ecx、edx是寄存器

上面这段代码的第一个+在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个+和第三个前置 ++的先后顺序

总结:我们写的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达就是存在问题的

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux的sed命令是一个用于文本处理的强大工具。它可以根据指定的编辑命令对输入文件进行操作,并输出结果。sed命令的基本语法如下: sed [选项] sed编辑命令 输入文件 其中,选项是可选的,用于控制sed命令的行为。sed编辑命令是指对文本进行操作的具体命令,可以是单个命令,也可以是多个命令组合在一起。输入文件是要进行处理的文本文件。 sed命令还支持通过管道将shell命令的输出作为输入进行处理,具体语法如下: shell命令 | sed [选项] sed编辑命令 此外,sed命令还可以通过-f选项指定一个sed脚本文件来进行处理,具体语法如下: sed [选项] -f sed脚本文件 输入文件 在使用sed命令时,还可以通过使用一些小技巧来实现特定的功能。例如,可以使用命令连接符";"来输出多行不连续的内容。例如,以下命令会输出passwd文件中的第1行、第3行和第5行: sed -n '1p;3p;5p' passwd 这样,你就能够根据需要使用sed命令来处理文本文件,并实现各种需要的操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [【Linux篇】sed命令详解](https://blog.csdn.net/weixin_45842494/article/details/124699219)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Linux - sed命令详解](https://blog.csdn.net/qq_48391148/article/details/125711532)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值