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

文章目录

  • 一、操作符
    • 1. 算数操作符
    • 2. 移位操作符
      • (1)右移操作符
        • 举例
        • 补充
      • (2)左移操作符
        • 举例
        • 分析
      • (3)警告
    • 3.位操作符
      • (1)按位与
      • (2)按位或
      • (3)按位异或
      • (4)案例
        • 案例一
        • 案例二
          • 1)错误示范
          • 2)方法1
          • 3)方法2
    • 4.赋值操作符
      • (1)赋值操作符
      • (2)复合赋值符
    • 5.单目操作符
      • (1)逻辑反操作
      • (2)正值与负值
      • (3)取地址和解引用
      • (4)sizeof
      • (5)按位取反
      • (6)++和--
      • (7)强制类型转换
      • (8)小案例
    • 6.关系操作符
    • 7.逻辑操作符
    • 8.条件操作符
    • 9.逗号表达式
    • 10.下标引用、函数调用和结构成员
      • (1)下标引用操作符
      • (2)函数调用操作符
      • (3)访问一个结构的成员
  • 二、表达式求值
    • 1.隐式类型转换
      • (1)案例一
      • (2)案例二
      • (3)案例三
    • 2.算术转换
  • 三、操作符的属性
    • 1.操作符优先级
    • 2.有问题的表达式

一、操作符

1. 算数操作符

+ - * / %

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

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

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

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

C语言基础--操作符详解_第1张图片

C语言基础--操作符详解_第2张图片

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

C语言基础--操作符详解_第3张图片

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

C语言基础--操作符详解_第4张图片

2. 移位操作符

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

<<左移操作符

(1)右移操作符

举例

看一个例子:(正整数)

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

C语言基础--操作符详解_第5张图片

结果如何呢?

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

C语言基础--操作符详解_第6张图片

看一下运行结果:

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

补充

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

C语言基础--操作符详解_第7张图片

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


知识补充

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

存储到内存中的是补码

(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,当箭头走到这一行。

C语言基础--操作符详解_第8张图片

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

C语言基础--操作符详解_第9张图片

四个可以选择一个。

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

C语言基础--操作符详解_第10张图片

敲回车:

C语言基础--操作符详解_第11张图片

可以看见,有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。

来看一下输出结果:

C语言基础--操作符详解_第12张图片

(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。

输出结果看一下:

C语言基础--操作符详解_第13张图片

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

(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。

输出结果看一下:

C语言基础--操作符详解_第14张图片

(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。

输出结果看一下:

C语言基础--操作符详解_第15张图片

(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);

看一下结果:

C语言基础--操作符详解_第16张图片

上面我们创建了临时变量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。

注意:

在异或运算中,不可能产生溢出。因为没有进位的可能。相同为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。

看一下:

C语言基础--操作符详解_第17张图片

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

换一种方法。


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为例)

C语言基础--操作符详解_第18张图片

再看一下-1:

C语言基础--操作符详解_第19张图片

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:

C语言基础--操作符详解_第20张图片

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

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

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

看一下结果:

C语言基础--操作符详解_第21张图片

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

①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个字节

输出看一下:

C语言基础--操作符详解_第22张图片

当然,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

输出看一下:

C语言基础--操作符详解_第23张图片

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

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

变量名:

C语言基础--操作符详解_第24张图片

类型:

C语言基础--操作符详解_第25张图片


案例:

补充一个小例子:

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()里面的表达式不会参与运算。

看一下最后输出结果:

C语言基础--操作符详解_第26张图片

(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。

C语言基础--操作符详解_第27张图片


案例:

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

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:

C语言基础--操作符详解_第28张图片

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

别急,我们现在想让刚才变化的位置,就是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)

C语言基础--操作符详解_第29张图片

(6)++和–

– 前置、后置–

++ 前置、后置++

前置++:先加后使用。

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

输出结果:

C语言基础--操作符详解_第30张图片

后置++:先使用后加

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

输出结果:

C语言基础--操作符详解_第31张图片

前置–:先减后使用。

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

输出结果:

C语言基础--操作符详解_第32张图片

后置–:先使用后减。

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

输出结果:

C语言基础--操作符详解_第33张图片

(7)强制类型转换

(类型) 强制类型转换

这个很简单,举个例子。

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

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

int a=(int)3.14;

(8)小案例

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

#include
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平台)。第四空一样。

输出看一下结果:

C语言基础--操作符详解_第34张图片

6.关系操作符

> >=

< <=

!= 用于测试“不相等”

== 用于测试“相等”

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

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

7.逻辑操作符

&& 逻辑与

|| 逻辑或

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

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

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

①逻辑与

举个小例子:

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

分析:

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

C语言基础--操作符详解_第35张图片

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

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

看一下结果:

C语言基础--操作符详解_第36张图片

②逻辑或

同样来举个小例子:

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

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

看一下结果:

C语言基础--操作符详解_第37张图片


一起来看个练习:

#include
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,后边的都是原值。

看一下结果:

C语言基础--操作符详解_第38张图片

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

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

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

C语言基础--操作符详解_第39张图片

②再看逻辑或那一行。

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

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

看一下:

C语言基础--操作符详解_第40张图片

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。

输出看一下:

C语言基础--操作符详解_第41张图片

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;
}

输出看一下结果:

C语言基础--操作符详解_第42张图片


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;
}

看一下输出结果:

C语言基础--操作符详解_第43张图片


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;
}

看一下输出结果:

C语言基础--操作符详解_第44张图片

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

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

二、表达式求值

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

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

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

1.隐式类型转换

C语言基础--操作符详解_第45张图片

(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调出计算器。

C语言基础--操作符详解_第46张图片

调整到程序员模式

C语言基础--操作符详解_第47张图片

输入127:

C语言基础--操作符详解_第48张图片

可以看到二进制:

C语言基础--操作符详解_第49张图片

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

0000 0000 0000 0000 0000 0000 0111 1111

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

0111 1111

③再来看第三行代码:

char c=a+b;

a和b如何相加呢?


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

如何进行整型提升

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

<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):

C语言基础--操作符详解_第50张图片

现在得到这样的结果:

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(原码,符号位不要动)

用计算器看一下:

C语言基础--操作符详解_第51张图片

最高位是1,负数。

所以最终输出-126

在编译器中运行看一下:

C语言基础--操作符详解_第52张图片

(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本来就是整型,不需要进行整型提升。

所以最后可以输出。

看一下编辑器最后结果:

C语言基础--操作符详解_第53张图片

(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个字节。

看一下编辑器结果:

C语言基础--操作符详解_第54张图片

2.算术转换

C语言基础--操作符详解_第55张图片

简单来说,如果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;
}

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

C语言基础--操作符详解_第56张图片

(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
int main(){
    int i=1;
    int ret=(++i)+(++i)+(++i);
    printf("%d\n",ret);
    printf("%d\n",i);
    return 0;
}

C语言基础--操作符详解_第57张图片

C语言基础--操作符详解_第58张图片

C语言基础--操作符详解_第59张图片

最后,感谢比特鹏哥,视频地址。
如果觉得文章不错,欢迎,关注+收藏+转发!
C语言基础--操作符详解_第60张图片

你可能感兴趣的:(C语言基础,c语言,操作符)