C语言基础--操作符详解

简介: C语言基础--操作符详解

一、操作符

1. 算数操作符

+ - * / %

1. 除了%操作符之外,其他的几个操作符可以作用于整数和浮点数。

2. 对于/操作符,如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。

3. %操作符的两个操作数必须为==整数==。返回的是整数之后的余数。

若除数与被除数都是整数,结果也是整数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XhKk2jBh-1672370650000)(D:\Typora图片\clip_image001.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f8Ob2rWq-1672370650002)(D:\Typora图片\clip_image002.png)]

若除数与被除数有一个是浮点数,得到的就是小数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uPg5QoNF-1672370650002)(D:\Typora图片\clip_image003.png)]

%操作符两侧有一个不是整数时

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8O1lyCgi-1672370650002)(D:\Typora图片\clip_image004.png)]

2. 移位操作符

\>>右移操作符 -->移动的是二进制位

<<左移操作符

(1)右移操作符

举例

看一个例子:(正整数)

    int a = 16;
    int b = a >> 1;
    printf("%d\n",b);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z3beIjwV-1672370650003)(D:\Typora图片\clip_image005.png)]

结果如何呢?

我们先来分析一下(内存中以补码存储):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PqrR2Zso-1672370650004)(D:\Typora图片\clip_image006.png)]

看一下运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zWzclOO8-1672370650004)(D:\Typora图片\clip_image007.png)]

#### 补充

正数无论是算术右移还是逻辑右移,左侧补得都是0,我们用负数来测试一下电脑是算术右移还是逻辑右移:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oZUX0VFy-1672370650004)(D:\Typora图片\clip_image008.png)]

可以看到,结果是-1,即:使用的是算术移位。


:cake:知识补充

整数的二进制表示有:原码、反码、补码。

==存储到内存中的是补码。==

(1)正数的原码、反码、补码都是一样的,没有差别。

(2)来看一下负数:(以-1为例)

最高位是符号位,正数为0,负数为1。

那么就可以写出-1的原码:(int类型4个字节,1个字节8个bit,那么int类型就是32bit)

1000 0000 0000 0000 0000 0001

反码就是原码的符号位(最高位)不变,其余取反(1变0,0变1):

1111 1111 1111 1111 1111 1110

补码就是反码+1:

1111 1111 1111 1111 1111 1111

我们不妨去内存中看一下-1的补码:

按一下F10,调出调试面板:

继续按F10,当箭头走到这一行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qhRUnZFc-1672370650005)(D:\Typora图片\image-20221228093412637.png)]

然后我们调出内存。(调试-->窗口-->内存

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l7uJhqTX-1672370650006)(D:\Typora图片\image-20221228093547253.png)]

四个可以选择一个。

点开之后输入&a,将a的地址提取出来:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7bY6INcI-1672370650006)(D:\Typora图片\image-20221228093828606.png)]

敲回车:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jvaOdQZk-1672370650007)(D:\Typora图片\image-20221228093934236.png)]

可以看见,有8个f。

一个f是15,15换成二进制是1111。

8个f,就是32个1。


再回到之前的运算。

-1的补码是1111 1111 1111 1111

然后右移一位,最右边的1溢出,最左边补符号位(前面已经验证电脑默认算术右移),然后就可以得到:

1111 1111 1111 1111

所以最终输出-1。(输出的是原码)

(2)左移操作符

举例

同样来看个例子:

    int a = 5;
    int b = a << 1;
    printf("%d\n",b);

分析

写出5的二进制:

0000 0000 0000 0000 0000 0000 0000 0101

现在将它左移一位,==最高位溢出,右边补0即可。==

现在就可以得到:

0000 0000 0000 0000 0000 0000 0000 1010

转为十进制,就是10。

来看一下输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-unp42XFz-1672370650007)(D:\Typora图片\image-20221228095917556.png)]

(3)警告

对于移位运算符,不要移动负数位,这个是标准未定义的。

例如:

int num=10;
num>>-1;

Error!!!

3.位操作符

& 按位与

| 按位或

^ 按位异或

它们的操作数必须是整数。“位”是“二进制位”。

(1)按位与

举个例子:

    int a = 3;
    int b = 5;
    int c = a & b;

运算:

==两个有一个为0即为0,两个同时为1才为1。==

先写出3和5的二进制位:

3:0000 0000 0000 0000 0000 0000 0000 0011

5:0000 0000 0000 0000 0000 0000 0000 0101

​ 0000 0000 0000 0000 0000 0000 0000 0001

计算结果为1。

输出结果看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nqog2D14-1672370650007)(D:\Typora图片\image-20221228101400943.png)]

:no_entry_sign:我们都是用补码运算,如果给的是负数,需要算出负数的补码,然后再进行运算。

(2)按位或

举个例子:

int a =3;
int b=5;
int c=a | b;

运算:

==两个有一个为1则为1,同时为0才为0。==

先写出3和5的二进制位:

3:0000 0000 0000 0000 0000 0000 0000 0011

5:0000 0000 0000 0000 0000 0000 0000 0101

​ 0000 0000 0000 0000 0000 0000 0000 0111

计算结果为7。

输出结果看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vfj1OzXW-1672370650008)(D:\Typora图片\image-20221228101945378.png)]

(3)按位异或

举个例子:

int a=3;
int b=5;
int c=a^b;

运算:

==对应二进制位若相同则为0,不同则为1。==

先写出3和5的二进制位:

3:0000 0000 0000 0000 0000 0000 0000 0011

5:0000 0000 0000 0000 0000 0000 0000 0101

​ 0000 0000 0000 0000 0000 0000 0000 0110

计算结果为6。

输出结果看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cNojyLBT-1672370650008)(D:\Typora图片\image-20221228102300074.png)]

(4)案例

案例一

这里讲一道面试题:

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

如果创建临时变量,是最基础的运算:

int a=3;
int b=5;
int temp=0;
printf("before:a=%d b=%d\n",a,b);
temp=a;
a=b;
b=temp;
printf("after:a=%d b=%d\n",a,b);

看一下结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L7nchxpT-1672370650009)(D:\Typora图片\image-20221228102831680.png)]

上面我们创建了临时变量temp来进行运算。

现在不用临时变量,该如何计算呢?

1)加减法计算

int a=3;
int b=5;
int temp=0;
printf("before:a=%d b=%d\n",a,b);
a=a+b;
b=a-b;
a=a-b;
printf("after:a=%d b=%d\n",a,b);

其实原理很简单。

将a与b全部倒进a,此时a就是它们的

这时候拿这个,减去b,就可以得到a的原来的值,存入b中。

然后再拿这个,减去此时的b(最开始的a值),就得到了b最开始的值,存入a中。

2)异或法计算

上面我们用的是整形值,占4个字节,有限空间。

当a与b的值特别大,相加之后超出了整形的最大值,这时候就会有值丢失。

所以第一种方法会有溢出的可能。

int a=3;
int b=5;
int temp=0;
printf("before:a=%d b=%d\n",a,b);
a=a^b;
b=a^b;
a=a^b;
printf("after:a=%d b=%d\n",a,b);

写出二进制,这里就写后三项了。

  • 第一步:

a: 011

b: 101

a=a^b:110

  • 第二步:

a: 110

b: 101

b=a^b:011 ---> 3

  • 第三步

a: 110

b: 011

a=a^b:101 -->5

可以这样理解:

a与b异或产生密码,存入a。

这个密码如果和b异或可以得到原来a的值,这个密码如果和现在的b(原来的a)异或可以得到原来的b。

:notebook_with_decorative_cover:注意:

在异或运算中,不可能产生溢出。因为没有进位的可能。相同为0,相异为1,产生的结果无非是1或者0。

现实中写代码,这种方法,执行效率不高,可读性差。不建议用这种方法。

为了应付面试,只能了解啦。(可以不用,但不能不会)

案例二

再来看一道题:

编写代码实现:求一个整数存储在内存中的二进制中1的个数。
1)错误示范

乍一看有点懵,不过之前我们会遇到这种题目:

输出123的每一位。

123是十进制数,我们可以用123%10得到123的个位数3,然后用123/10可以得到12(将3去除)。

然后循环操作,就可以分别输出1、2、3了。

那么这一题,可以输出二进制数,然后做一个判断,如果是1,就计数。输出方法和上面一样,只不过除的是2而已。

int num =0;
int count =0;
scanf("%d",&num);    //3--011

//统计num中的补码有几个1
while(num){    //num不为0就继续执行
    if(num%2==1){
        count++;
        num=num/2;
    }
}
printf("%d\n",count);

如果输入3,那么最终输出(二进制1的个数)就是2。

看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Q6EmAzo-1672370650009)(D:\Typora图片\image-20221228113745496.png)]

==其实上面的方法是有缺陷的,如果是负数,那么就会出错。==(不解释了,自己算一下就知道了)

换一种方法。


2)方法1

既然要数出来二进制的1,那么能否将二进制都拿到手呢?

以3为例,3的二进制为:0000 0000 0000 0000 0000 0000 0000 0011

此时将它和1按位与运算。

1的二进制为:0000 0000 0000 0000 0000 0000 0000 0001

3&1 :0000 0000 0000 0000 0000 0000 0000 0001

注意观察最后一位的结果变化。

可以发现一个规律:如果该位置与1按位与运算,如果得到结果为1,那么该位置就一定是1。如果该位置是0,那么结果就是0。

计算第一次就可以判断最后一位是不是1。(num>>0)

接下来想看一下倒数第二位,就可以让该二进制右移一位(num>>1),再与1做按位与运算。

然后想看一下倒数第三位,就可以让该二进制右移两位(num>>2),再与1做按位与运算。

如果按位与的结果是1,就表示该位置是1,记录一下即可。

代码:

int num =0;
int count =0;
scanf("%d",&num);    //3--011
int i=0;
for(i=0;i<32;i++){
    if(1==((num>>i)&1)){ 
        count++;
    }
}
printf("%d\n",count);

看一下最后结果:(以3为例)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9JbDex3N-1672370650010)(D:\Typora图片\image-20221228115618068.png)]

再看一下-1:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GQld2IGh-1672370650010)(D:\Typora图片\image-20221228115702288.png)]

3)方法2

其实还有一种方法。

int num=-1;
int i=0;
int count=0;//计数
while(num){
    count++;
    num=num&(num-1);
}
printf("二进制中1的个数=%d\n",count);

4.赋值操作符

(1)赋值操作符

赋值操作符是一个很棒的操作符,它可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。

变量在创建的时候给它一个值,叫初始化。当变量已经有了的时候,再给它一个值,叫赋值

==初始化会开辟新的空间,而赋值不会。==

举个最简单的例子:

int weight=120;
weight=89;//不满意就赋值
double salary=10000.0;
salary=20000.0;//使用赋值操作符赋值

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

int a=10;
int x=0;
int y=20;
a=x=y+1;//连续赋值

不建议连续赋值,不易于理解。(连续赋值语法上是支持的)

这样的代码更加简洁易于调试:

x=y+1;
a=x;
赋值是一个 =,而判断是两个 ==

(2)复合赋值符

+= -= *= /= %= >>= <<= &= |= ^=

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

比如:

int a=10;
a=a+2;
a+=2;//复合赋值符(和上面一行的意思一致)
a=a>>1;
a>>=1;//复合赋值符(和上面一行的意思一致)

5.单目操作符

! 逻辑反操作

- 负值

+ 正值

& 取地址

sizeof 操作数的类型长度(以字节为单位)

~ 对一个数的二进制按位取反

-- 前置、后置--

++ 前置、后置++

* 间接访问操作符(解引用操作符)

(类型) 强制类型转换

单目操作符:==只有一个操作数。==

双目操作符:==有两个操作数。==比如a+b+左右两侧都有数值。

(1)逻辑反操作

! 逻辑反操作(真变假,假变真)

例如:

int a=0;
printf("%d\n",!a);

得到的结果就是1:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-twP6Seqf-1672370650010)(D:\Typora图片\image-20221228122748044.png)]

这里判断的数值不是局限于1和0,非假(0)即真。

比如此时a等于10,那么!a的数值也是0:

int a=10;
printf("%d\n",!a);

看一下结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-susSZQ28-1672370650011)(D:\Typora图片\image-20221228123019938.png)]

所以这里的!可以这样来用(为语句条件做判断):

①a为假打印输出:

if(!a){    //a为假,!a为真。如果a为假,就可以打印输出。
    printf("haha");
}

②a为真打印输出:

if(a){
    printf("haha");//a为真就打印输出
}

(2)正值与负值

正值 +

负值-

这个很简单,正值一般不写。

int a=-5;
a=-a;

(3)取地址和解引用

& 取地址(取出任意变量或符号的地址)

* 解引用

取地址:

int a=10;
&a;    //取地址操作符

解引用:

int a=10;
int* p=&a;
*p;//解引用操作符

取地址操作符一般和指针一起使用。

int a=10;
int* p=&a;    //将a变量的地址拿出来,存储到p变量里面。p是存放地址的变量,叫指针变量,类型为int*
*p=20;//解引用操作符,通过p找到所指的对象,即a。然后将20赋值给a。

(4)sizeof

sizeof 操作数的类型长度(以字节为单位)

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

int a=10;
char c='r';
char* p=&c;
int arr[10]={0};
printf("%d\n",sizeof(a));    //4
printf("%d\n",sizeof(c));    //1
printf("%d\n",sizeof(p));    //指针大小4字节(32平台)或者8字节(64平台)
printf("%d\n",sizeof(arr));    //数组里面10个元素,每个元素是整形,一个整形是4个字节,10个就是40个字节

输出看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0yARXBHB-1672370650011)(D:\Typora图片\image-20221228125207290.png)]

当然,sizeof里面也可以写类型。

比如:

printf("%d\n",sizeof(int));    //4
printf("%d\n",sizeof(char));//1
printf("%d\n",sizeof(char*));//4或8
printf("%d\n",sizeof(int[10]));    //数组类型就是去除数组名字之后 4*10=40

输出看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xWqCs1fl-1672370650011)(D:\Typora图片\image-20221228125707360.png)]

可以通过 类型或者 变量名来计算大小。

:notebook_with_decorative_cover: 注意:如果sizeof计算的是变量名,则可以省略后面的括号。如果计算的是类型,则不可以。

变量名:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E3VvLBkq-1672370650012)(D:\Typora图片\image-20221228130054026.png)]

类型:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ghvDKYxn-1672370650012)(D:\Typora图片\image-20221228130208074.png)]


案例:

补充一个小例子:

short s=0;
int a=10;
printf("%d\n",sizeof(s=a+5));
printf("%d\n",s);

不管a是什么类型,加了5之后,放进s里面,都是2个字节。

sizeof表达式不会真实运算,仅仅只是摆设。

s的值不会发生变化。

printf()会参与运算, sizeof()里面的表达式不会参与运算。

看一下最后输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sCcXkzV1-1672370650012)(D:\Typora图片\image-20221228161247720.png)]

(5)按位取反

~ 按位取反

按位取反,对于负数的符号位也要取反!

看个例子:

int a=0;
printf("%d\n",~a);

来分析一下:

~按位取反,是按照二进制取反。

a的值为0,二进制表示为:0000 0000 0000 0000 0000 0000 0000 0000

在C语言中,0相当于正数,原码、反码、补码都一样。

然后我们取反就得到:1111 1111 1111 1111 1111 1111 1111 1111(补码)

当我们输出打印的时候,是按照原码打印输出的。

所以现在要求出它的原码。

==原码--(0变1,1变0)-->反码---(反码+1)-->补码==

反过来求得上面的反码是:1111 1111 1111 1111 1111 1111 1111 1110(反码)

原码就是:1000 0000 0000 0000 0000 0000 0000 0001(原码)--> -1

最终输出结果就是-1。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xbOpjyvO-1672370650013)(D:\Typora图片\image-20221228164050248.png)]


案例:

接下来,我们来看一个小案例。

11的二进制是:0000 0000 0000 0000 0000 0000 0000 1011

现在想让倒数第三位变成1。

之前学了按位或,==两个有一个为1则为1,同时为0才为0。==

那么我们可以让11按位或这个:0000 0000 0000 0000 0000 0000 0000 0100

既然这样,我们就可以轻松改变一个二进制位置上的数。

按位或的这个,相当于把1左移了两位:

0000 0000 0000 0000 0000 0000 0000 0000(1)

0000 0000 0000 0000 0000 0000 0000 0100(1<<2)

所以代码可以这样来写:

int a=11;
a=a|(1<<2);

最终得到结果:

0000 0000 0000 0000 0000 0000 0000 1111(15)

输出看一下就是15:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YaTKnytK-1672370650013)(D:\Typora图片\image-20221228165700609.png)]

看到这里,有小伙伴就要问了,这和按位取反有什么关系?

别急,我们现在想让刚才变化的位置,就是15倒数第三个位置上的1,变回去,变成0。

怎么办呢?

之前学了按位与,==两个有一个为0即为0,两个同时为1才为1。==

我们将这个位置,按位与一个0,就可以变回去了!

那么我们可以让15按位与这个:1111 1111 1111 1111 1111 1111 1111 1011

而按位与的这个,相当于把1左移了两位,然后按位取反

0000 0000 0000 0000 0000 0000 0000 0001(1)

0000 0000 0000 0000 0000 0000 0000 0100(1<<2)

1111 1111 1111 1111 1111 1111 1111 1011(~(1<<2))

所以代码可以这样来写:

b=a&(~(1<<2));

最终得到:

0000 0000 0000 0000 0000 0000 0000 1011(11)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oK8rkUrE-1672370650013)(D:\Typora图片\image-20221228171200049.png)]

(6)++和--

-- 前置、后置--

++ 前置、后置++

前置++:先加后使用。

int a=10;
printf("%d\n",++a);

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WcCyKmTb-1672370650014)(D:\Typora图片\image-20221228171828907.png)]

后置++:先使用后加

int a=10;
printf("%d\n",a++);

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q1RilAiO-1672370650014)(D:\Typora图片\image-20221228172026180.png)]

前置--:先减后使用。

int a=10;
printf("%d\n",--a);

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7BlK9v5v-1672370650014)(D:\Typora图片\image-20221228172302129.png)]

后置--:先使用后减。

int a=10;
printf("%d\n",a--);

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2PkWo4wZ-1672370650015)(D:\Typora图片\image-20221228172330235.png)]

(7)强制类型转换

(类型) 强制类型转换

这个很简单,举个例子。

将3.14存入int类型的变量中,直接存是会报错的。

这时候,我们需要将它强制类型转换成int类型。

int a=(int)3.14;

(8)小案例

巩固一下刚才学到的东西,我们做一个小案例。

#include<stdio.h>
void test1(int arr[]){
    printf("%d\n",sizeof(arr));    //(3)
}
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));    //(2)
    test1(arr);
    test2(ch);
}

经过上面的学习,这一道题目变得非常简单。

第一空,arr数组里面有10个元素,每个元素都是int类型(4个字节),10个元素就是40字节。答案是40。

第二空,ch数组里面有10个元素,每个元素都是char类型(1个字节),10个元素就是10字节。答案是10。

第三空,将数组传递到函数,数组名相当于数组首元素地址,test1接收的时候,是用指针接收的,那么指针的大小是4(32平台)或8(64平台)。第四空一样。

输出看一下结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sGlfJrpb-1672370650015)(D:\Typora图片\image-20221228174630097.png)]

6.关系操作符

\> \>=

< <=

!= 用于测试“不相等”

== 用于测试“相等”

这些关系运算符比较简单。

在编程的过程中,要小心===混淆出错。

7.逻辑操作符

&& 逻辑与

|| 逻辑或

区分逻辑与按位与逻辑或按位或

按位与按位或是拿它们的二进制对应的位置进行

逻辑与逻辑或是关注这个数的本身,是真还是假。

①逻辑与

举个小例子:

int a=3;
int b=5;
int c=a && b;

分析:

a为真,b也为真。用逻辑与运算之后,结果为真(1)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SvizO4Xj-1672370650015)(D:\Typora图片\image-20221228175737854.png)]

==有一个为假,结果就是假。两个同时为真才为真。==比如:

int a=0;
int b=5;
int c=a && b;

看一下结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2PZoKeBh-1672370650016)(D:\Typora图片\image-20221228175806023.png)]

②逻辑或

同样来举个小例子:

==有一个为真,结果就为真。两个同时是假才为假。==

int a=0;
int b=5;
int c=a || b;

看一下结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BhJPSDKH-1672370650016)(D:\Typora图片\image-20221228180029788.png)]


一起来看个练习:

#include<stdio.h>
int main(){
    int i=0,a=0,b=2,c=3,d=4;
    i=a++ && ++b && d++;
    //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,后边的都是原值。

看一下结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9mkgVmMt-1672370650016)(D:\Typora图片\image-20221228181622918.png)]

如果将a的初始值换成1,像这样:

#include<stdio.h>
int main(){
    int i=0,a=1,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;
}

最终所有表达式都要运算。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GkCN9pQ8-1672370650016)(D:\Typora图片\image-20221228181945761.png)]

②再看逻辑或那一行。

对于逻辑或,==左边的表达式如果计算结果是真,右边表达式无论算不算都是真。==

第一个a++结果是0,需要继续算。第二个++b结果是3,为真,后边不用算了。

看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HAHojRo1-1672370650017)(D:\Typora图片\image-20221228182517433.png)]

8.条件操作符

三目操作符。

exp1?exp2:exp3

表达式1结果为真,就计算表达式2的结果。

如果表达式1结果为假,就计算表达式3的结果。

做个小练习:

if(a>5){
    b=3;
}else{
    b=-3;
}

将上面的代码用条件运算符来写:

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

再举个例子:

int a=10;
int b=20;
int max=0;
if(a>b){
    max=a;
}else{
    max=b;
}

将上面的代码用条件运算符来写:

max=(a>b?a:b);

9.逗号表达式

exp1,exp2,exp3,...expn

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

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

来看代码:

int a=1;
int b=2;
int c=(a>b,a=b+10,a,b=a+1);

上面代码结果,c是多少呢?

第一个表达式a>b执行没有结果,第二个a=b+10算出结果a=12,第三个 表达式也是执行了没有结果,第四个得出b=13。

整个逗号表达式的结果,是最后一个表达式的结果,即13。

输出看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sqtQx0Bh-1672370650017)(D:\Typora图片\image-20221229100131598.png)]

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

### (1)下标引用操作符

[] 下标引用操作符

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

看个小案例:

int arr[10];    //创建数组
arr[9]=10;    //下标引用操作符
[]的两个操作数是arr和9。

这个操作符的用法在数组广泛应用,可以移步去(31条消息) C语言基础--数组_雨翼轻尘的博客-CSDN博客那一节。

### (2)函数调用操作符

() 函数调用操作符

接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。

看个小案例:

get_max(int x,int y){
    return x>y?x:y;
}
int main(){
    int a=10;
    int b=20;
    //调用函数的时候()就是函数调用操作符
    //()的操作数有三个:函数名get_max和a和b
    int max=get_max(a,b);
    printf("max=%d\n",max);
    return 0;
}

这个操作符在函数广泛应用,后期再对函数专门写一篇,别着急,先了解一下。

(3)访问一个结构的成员

.结构体,成员名

-> 结构体指针->成员名

1)

举个小例子:

//创建一个结构体类型--struct Stu
struct Stu{
    //成员变量
      char name[20];
    int age;
    char id[20];
};
int main(){
    //使用struct Stu这个类型创建了一个学生对象s1,并初始化
    struct Stu s1={"张三",20,"2020036745"};
    printf("%s\n",s1.name);
    printf("%d\n",s1.age);
    printf("%s\n",s1.id);
    //结构体变量.成员名
    return 0;
}

输出看一下结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n67T99CI-1672370650018)(D:\Typora图片\image-20221229103020025.png)]


2)

刚才我们是使用结构体变量.成员名来调用成员变量的。

还有一种方法:

ps=&s1;    //将s1的地址找出来,存入指针变量ps中。
struct Stu* ps    //ps是指针变量,类型是struct Stu。

合起来就是:

struct Stu* ps=&s1;

既然存好了,就拿出来用。

怎么调用呢?

(*ps).name    //ps是指针变量,*ps就是解引用。然后我们用它.成员变量,就可以调用了

看一下整体的代码:

//创建一个结构体类型--struct Stu
struct Stu{
    //成员变量
      char name[20];
    int age;
    char id[20];
};
int main(){
    //使用struct Stu这个类型创建了一个学生对象s1,并初始化
    struct Stu s1={"张三",20,"2020036745"};
    struct Stu* ps=&s1;
    printf("%s\n",(*ps).name);
    printf("%d\n",(*ps).age);
    printf("%d\n",(*ps).id);
    //结构体变量.成员名
    return 0;
}

看一下输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V9LxXFUY-1672370650018)(D:\Typora图片\image-20221229104106540.png)]


3)

刚才(*ps).name的调用方式非常繁琐。

C语言给我们提供了另外一种方法:

ps->name;    //指针->对象

和刚才的方法没有任何区别。

然后我们就可以这样调用了:

printf("%s\n",ps->name);

看一下整体的代码:

struct Stu{
    //成员变量
      char name[20];
    int age;
    char id[20];
};
int main(){
    //使用struct Stu这个类型创建了一个学生对象s1,并初始化
    struct Stu s1={"张三",20,"2020036745"};
    struct Stu* ps=&s1;
    printf("%s\n",ps->name);
    printf("%d\n",ps->age);
    printf("%d\n",ps->id);
    //结构体指针->成员名
    return 0;
}

看一下输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VoqRZPda-1672370650018)(D:\Typora图片\image-20221229104545598.png)]

这个操作符在结构体那一节广泛应用,后期再对结构体专门写一篇,别着急,先了解一下。

需要的小伙伴可以关注后续文章。

二、表达式求值

学了这么多操作符,主要还是用于表达式求值。

表达式求值的顺序一部分是由操作符的优先级结合性决定。

同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。

1.隐式类型转换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qRO1ACjA-1672370650019)(D:\Typora图片\786918930569505142.jpg)]

(1)案例一

来看一个案例:

char a,b,c;
...
a=b+c;

b和c的值被提升为普通整型,然后再执行加法运算。

加法运算完成之后,结果将被截断,然后再存储于a中。

这样可能比较抽象,不易于理解,下面再来看一个例子:

int main(){
    char a=3;
    char b=127;
    char c=a+b;
    printf("%d\n",c);
}

输出结果是多少呢?

①来看第一行代码:

char a=3;

3是一个整数,32bit位,那么我们就可以写出它的二进制表达形式:

0000 0000 0000 0000 0000 0000 0000 0011

但是现在我们要将3存入char类型的变量中,char类型是1个字节,8bit位。

所以我们只能将32bit位截断,规则是==挑最低位的字节==。

那么就可以得到:

0000 0011

②再来看第二行代码:

char b=127;

现在将127转为二进制表示。

可以借助电脑来帮我们计算,按一下Windows+R键,输入calc调出计算器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rRj6RtvA-1672370650019)(D:\Typora图片\image-20221229110948749.png)]

调整到程序员模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gG9lO8g9-1672370650019)(D:\Typora图片\image-20221229111221899.png)]

输入127:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-McoXDpoR-1672370650019)(D:\Typora图片\image-20221229111352959.png)]

可以看到二进制:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IEPzIqqw-1672370650020)(D:\Typora图片\image-20221229111438641.png)]

所以127的二进制的表示为:

0000 0000 0000 0000 0000 0000 0111 1111

但是现在我们要将127存入char类型(1字节--8bit)的变量中,截断之后得到:

0111 1111

③再来看第三行代码:

char c=a+b;

a和b如何相加呢?


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

:point_right: 如何进行整型提升

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

<1>负数的整型提升

char c1=-1;

变量c1的二进制位(补码)中只有8个比特位:1111 1111
因为char是有符号的char,所以整型提升的时候,高位补充符号位,即1。

提升之后的结果是:

1111 1111 1111 1111 1111 1111 1111 1111

<2>正数的整型提升

char c2=1;

变量c2的二进制位(补码)中只有8个比特位:0000 0001

因为char是有符号的char,所以整型提升的时候,高位补充符号位,即0。

提升之后的结果是:

0000 0000 0000 0000 0000 0000 0000 0001

<3>无符号整数提升

高位补0。


再回到本案例。

既然现在的char是有符号的char

那么现在我们就可以认为8bit位的==最高位是符号位==。

例如a截断之后是:0000 0011

最高位是符号位,是0。

那么进行整型提升的时候,提升的是符号位(这里是0),所以前面24bit位补0。

就成了这样:

0000 0000 0000 0000 0000 0000 0000 0011

b截断之后是:0111 1111

最高位是符号位,是0。

整型提升之后:

0000 0000 0000 0000 0000 0000 0111 1111

然后再将a与b相加。

0000 0000 0000 0000 0000 0000 0000 0011

+

0000 0000 0000 0000 0000 0000 0111 1111

=

0000 0000 0000 0000 0000 0000 1000 0010

粗略看一下进位(二进制里面逢2进1):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cf59WUWL-1672370650020)(D:\Typora图片\image-20221229114134972.png)]

现在得到这样的结果:

0000 0000 0000 0000 0000 0000 1000 0010

再将这个二进制放入c中。

c也是char类型变量,只能存8个bit位。

截断之后:1000 0010

④最后输出c

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

最后我们输出的c是以%d形式输出。

刚才得到的c是:1000 0010

最高位是符号位,即1。

现在需要对它整型提升,前面要补符号位1,就得到:

1111 1111 1111 1111 1111 1111 1000 0010(补码)

现在得到的是内存中存储的补码

真正打印出来的是原码

==原码--(取反)--->反码---(+1)-->补码==

算一下:

1111 1111 1111 1111 1111 1111 1000 0001(反码)

1000 0000 0000 0000 0000 0000 0111 1110(原码,符号位不要动)

用计算器看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6l29XjJd-1672370650020)(D:\Typora图片\image-20221229115147415.png)]

最高位是1,负数。

所以最终输出-126

在编译器中运行看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mTko1T9O-1672370650021)(D:\Typora图片\image-20221229115317431.png)]

(2)案例二

上面我们已经了解了整型提升的知识,下面来做个案例巩固一下。

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

①先看a

 char a=0xb6;

对于十六进制(0\~9,a\~f),a是10,那么往后数,b就是11。

11的二进制为:1011

6的二进制为: 0110

前面的0x是十六进制的标志而已,不用管。

这样合在一起,就可以得到a的二进制:1011 0110

下面a要与0xb6比较。

if(a==0xb6){
    printf("a");
}

比较也是一种运算。

a也要发生整型提升

a的二进制最高位是1,进行整型提升的时候,前面补最高位(符号位),所以得到:

1111 1111 1111 1111 1111 1111 1011 0110

这样的话,怎么可能和0xb6一样呢?

不能输出。

②再看b

short b=0xb600;

b是short类型,最后比较的时候也要进行整型提升。

所以后边也不可能和0xb600相等。

也不能输出。

③再看c

int c=0xb6000000;

c本来就是整型,不需要进行整型提升。

所以最后可以输出。

看一下编辑器最后结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-helmOyN5-1672370650021)(D:\Typora图片\image-20221229121626202.png)]

(3)案例三

最后再来看一道例题。

int main(){
    char c=1;
    printf("%u\n",sizeof(c));
    printf("%u\n",sizeof(+c));
    printf("%u\n",sizeof(-c));
    printf("%u\n",sizeof(!c));
    return 0;
}

c只要参与表达式运算,就会发生整型提升

表达式+c,会发生整型提升,计算的就是整型大小,所以sizeof(+c)是4个字节。

表达式-c,会发生整型提升,所以sizeof(-c)也是4个字节。

但是sizeof(c)就是1个字节。

看一下编辑器结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GTRoFHcZ-1672370650021)(D:\Typora图片\image-20221229122356757.png)]

2.算术转换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YSmRYOht-1672370650021)(D:\Typora图片\208508462434531823.jpg)]

简单来说,如果float类型和double类型运算,需要先把float转换为double类型,再进行运算。

float f=3.14;
int num=f;    //隐式转换,会有精度丢失

三、操作符的属性

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

1.操作符的优先级

2.操作符的结合性

3.是否控制求值顺序

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

1.操作符优先级

比如:

int main(){
    int a=10;
    int b=20;
    int c=b+a*3;
}

很明显,在表达式b+a*3中,乘法优先级高于加法,所以先计算a*3,再计算加法。

然后再来看:

int main(){
    int a=10;
    int b=20;
    int c=b+a+3;
}

在表达式b+a+3中,优先级相同,自左向右。

初级运算符( )、[ ]、->、. 高于 单目运算符 高于 算数运算符(先乘除后加减) 高于 关系运算符 高于 逻辑运算符(不包括!) 高于 条件运算符 高于 赋值运算符 高于 逗号运算符。

位运算符的优先级比较分散。

除了赋值运算符、条件运算符、单目运算符三类的平级运算符之间的结合顺序是从右至左,其他都是从左至右。

C语言运算符优先级

优先级 运算符 名称或含义 使用形式 结合方向 说明
1 [] 数组下标 数组名[常量表达式] 左到右 --
() 圆括号 (表达式)/函数名(形参表) 左到右 --
. 成员选择(对象) 对象.成员名 左到右 --
-> 成员选择(指针) 对象指针->成员名 左到右 --
2 - 负号运算符 -表达式 右到左 单目运算符
~ 按位取反运算符 ~表达式 右到左
++ 自增运算符 ++变量名/变量名++ 右到左
-- 自减运算符 --变量名/变量名-- 右到左
* 取值运算符 *指针变量 右到左
& 取地址运算符 &变量名 右到左
! 逻辑非运算符 !表达式 右到左
(类型) 强制类型转换 (数据类型)表达式 右到左 --
sizeof 长度运算符 sizeof(表达式) 右到左 --
3 / 表达式/表达式 左到右 双目运算符
* 表达式*表达式 左到右
% 余数(取模) 整型表达式%整型表达式 左到右
4 + 表达式+表达式 左到右 双目运算符
- 表达式-表达式 左到右
5 << 左移 变量<<表达式 左到右 双目运算符
>> 右移 变量>>表达式 左到右
6 > 大于 表达式>表达式 左到右 双目运算符
>= 大于等于 表达式>=表达式 左到右
< 小于 表达式<表达式 左到右
<= 小于等于 表达式<=表达式 左到右
7 == 等于 表达式==表达式 左到右 双目运算符
!= 不等于 表达式!= 表达式 左到右
8 & 按位与 表达式&表达式 左到右 双目运算符
9 ^ 按位异或 表达式^表达式 左到右 双目运算符
10 \ 按位或 表达式\ 表达式 左到右 双目运算符
11 && 逻辑与 表达式&&表达式 左到右 双目运算符
12 \ \ 逻辑或 表达式\ \ 表达式 左到右 双目运算符
13 ?: 条件运算符 表达式1?表达式2: 表达式3 右到左 三目运算符
14 = 赋值运算符 变量=表达式 右到左 --
/= 除后赋值 变量/=表达式 右到左 --
*= 乘后赋值 变量*=表达式 右到左 --
%= 取模后赋值 变量%=表达式 右到左 --
+= 加后赋值 变量+=表达式 右到左 --
-= 减后赋值 变量-=表达式 右到左 --
<<= 左移后赋值 变量<<=表达式 右到左 --
>>= 右移后赋值 变量>>=表达式 右到左 --
&= 按位与后赋值 变量&=表达式 右到左 --
^= 按位异或后赋值 变量^=表达式 右到左 --
\ = 按位或后赋值 变量 =表达式
15 逗号运算符 表达式,表达式,… 左到右 --

说明:
同一优先级的运算符,运算次序由结合方向所决定。

简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符

2.有问题的表达式

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

(1)表达式1

a*b+c*d+e*f

该表达式在计算的时候,由于*+优先级高,只能保证*的计算比+早,但是优先级并不能决定第三个*比第一个+早执行。

所以表达式的计算机顺序可能是:

a*b

c*d

a*b+c*d

e*f

a*b+c*d+e*f

或者:

a*b

c*d

e*f

a*b+c*d

a*b+c*d+e*f

(2)表达式2

 c + --c;

操作符的优先级只能决定自减--的运算在+运算的前面,但是我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值。

所以结果是不可预测的,有歧义。

(3)表达式3

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

该表达式,在不同编译器中的结果:(非法表达式程序的结果)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dKrRDPjo-1672370650025)(D:\Typora图片\200269785358760078.jpg)]

(4)表达式4

int fun(){
    static int count=1;
    return ++count;
}
int main(){
    int answer;
    answer=fun()-fun()*fun();
    printf("%d\n",answer);    //输出多少?
    return 0;
}

虽然在大多数编译器上求得结果相同,但是这是存在问题的。

上述表达式answer=fun()-fun()*fun()中,我们只能通过操作符的优先级得知:先算乘法,再算减法。

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

(5)表达式5

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KhRfeDPr-1672370650025)(D:\Typora图片\4510896989216589.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e2IVsBrE-1672370650026)(D:\Typora图片\28683948008057457.jpg)]

在这里插入图片描述

最后,感谢比特鹏哥,视频地址
如果觉得文章不错,欢迎👍,关注+收藏+转发!
请添加图片描述

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