8.0、C语言——操作符

8.0、C语言——操作符

算数操作符:

        +     -     *     /     %

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

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

        3、% 操作符的两个操作数必须为整数,返回的是整除之后的余数

移位操作符【只能作用于整数】:

        << 左移操作符

        >> 右移操作符

右移位运算 分为以下两种:
1、算术右移:
        右边丢弃,左边补原符号位【正数补0,负数补1】,当前的编译器基本上用的都是 算术移位

2、逻辑右移:
        右边丢弃,左边补0【不挂正数还是负数都补0】

        其实总结一下:只有负数在算术右移运算时才会补 1 其他情况都是补 0;虽然有两种移位方式但是通常我们见到的都是算术右移;右移移位是除 2,左移一位是乘 2

位操作符:

        &(与):都是 1 则为 1 ,有 0 则为 0      

        |(或):有 1 则为 1 ,全 0 才为 0

        ^(异或):相同为 0 ,不同为 1 

来看几道面试题:

1、不准使用中间变量,然后交换两个变量的值,int a = 5; int b = 3;  实现代码如下所示:

方式一:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main() {
	int a = 5;
	int b = 3;
	a = a + b;
	b = a - b;
	a = a - b;
	printf("a = %d , b = %d",a,b);
	return 0;
}

该方法有一定的缺陷,如果我们的a ,b两个变量数值很大,相加后可能会溢出 

方式二:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main() {
	int a = 5;
	int b = 3;
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	printf("a = %d , b = %d",a,b);
	return 0;
}

        这里我们可以将第一次 a^b 看成一个译码 x,经计算后会发现 x ^ a 会得到 b 原来的值,x ^ a 会得到 a 原来的值

2、计算一个整数的二进制补码中有多少个 1

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main() {
	int input = 0;
	int count = 0;
	scanf("%d",&input);
	for (int i = 0;i < 32;i++) {
		if (1 == ((input >> i) & 1)) {
			count++;
		}
	}
	printf("count = %d",count);
	return 0;
}

举个例子:
        3 的二进制为:00000000 00000000 00000000 00000011

        1 的二进制为:00000000 00000000 00000000 00000001

        那么每次 右移 1 位 依次和 1 的二进制 进行 &(与) 运算,如果为 1 则说明是 1 则 count++,因为int类型为4字节32位,所以循环32次~

单目操作符:

        !                        逻辑反操作,正变假,假变真
        -                          负值                        
        +                         正数     
        &                         取地址
        sizeof                  操作数的类型长度(一字节为单位)
        ~                          对一个数的二进制按位取反
        --                          前置、后置 --
        ++                        前置、后置 ++
        *                           间接访问操作符
        (类型)                   强制类型转换    

!(取反),比如 int a = 10;  !a 等于 0【因为0为假,非0为真】,int a = 0;那么 !a 等于 1

sizeof(计算占用内存大小) ->
        1、sizeof(a);计算变量可以省略括号
        2、sizeof(int);计算类型不可以省略括号
        3、sizeof()括号里的表达式,是不参与运算的,例如:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

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

        1、第一个输出 sizeof(s = a + 5) 等于 2,因为最终的结果是存放到 short 里所以占用的内存空间是 2 byte

        2、第二个输出的 s 仍然是 0,因为 sizeof() 括号里的表达式不参与运算,所以 s 的值不会发生变化

知识扩展:当去掉数组名后剩下的就是数组的类型,比如 int arr[10]; 他的数组类型就是 int [10]

逻辑运算符中:

&&(与):如果 && 左边计算出来为 0(假) ,那么后面的将不再计算,直接得出 0(假)

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main() {
	int a = 0;
	int b = 2;
	int c = 3;
	int i = a++ && ++b && c;
	printf("%d%d%d%d", a, b, c, i);
	return 0;
}

直接输出1230

|| (或):如果 || 左边计算出来的结果为 1,那么将不再往后计算,直接得出结果 1(真)

三目操作符:
        表达式1 ? 表达式2 :表达式3           (如果表达式1为真,则整个表达式的结果为表达式2,如果表示式为假则整个表达式的结果为表达式3)

struct 结构体:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//创建了一个结构体类型-struct Student
struct Student
{
	int age;
	int phone;
	char name[10];
};

int main() {
	//使用 struct Student 这个类型创建了一个学生变量叫做student1
	struct Student student1 = {18,123456789,"小明" };
	return 0;
}

        可以把 struct Student 看成一个类型,就像是 int float char 类型一样都是一种数据类型,然后通过类型就可以定义变量 student1 就是由 struct Student 类型定义声明出来的变量,只不过赋值有多个属性值要用 { } 花括号括起来赋值。

        那么在我们声明一个struct类型的时候,不会占用内存的空间,但是一旦创建该类型的变量之后,就会向内存空间申请一块空间进行存放~

如果想要访问该对象 student1 的属性可以通过下面三种方式去访问:
        1、通过 “ . ” 去访问,例如 student1.age
        2、通过 取地址 去访问,例如 :

	struct Student* ps = &student1;
	printf("age = %d", (*ps).age);

        3.通过  -> (这是结构体指针的操作符)去访问【当然这种方式本质上和第二种是一样的,但是会更加简洁直观一些】,例如:

	struct Student* ps = &student1;
	printf("age = %d", ps->age);

表达式求值:

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

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

隐式类型转换:

        C的整数算术运算总是至少以缺省整型类型的精度来进行

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

        整型提升的意义:

        表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是cpu的通用寄存器的长度

        因此,即使两个char类型的相加,在cpu执行时实际上也要先转换为cpu内整型操作数的标准长度

        通用cpu(genneral-purpose cpu)是难以直接实现两个8bit直接相加运算(虽然机器指令中可能有这种字节相加指令)所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为 int 或 unsigned int,然后才能送入CPU去执行运算 

        

        如何进行整型提升呢?

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

 

        下面给大家举个例子:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

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

        a、b、c 三个变量都是 char 类型【8bit】,但是赋值确实 int 类型【32bit】,
        这时候会将 int 类型从最低位强行阶截断出8位,赋给变;
        比如 3 -> 00000000 00000000 00000000 00000011 会强行截断成 00000011;
        最后在 a + b 相加的时候,由于计算机无法进行 8bit 与 8bit 数据的运算,所以会将整型提升至 32bit ,然后再进行运算;
        所以 a + b 计算得到的就是 00000000 00000000 00000000 10000010,但是存到c中的依然是被截断后的八位二进制即 10000010;
        最后执行 printf() 打印语句的时候,由于要打印出整型但是c又是char类型,所以这时候又要整形提升,变成 11111111 11111111 11111111 10000010【因为最高位是 1 所以前面补 1,有符号数在整型提升时 符号位是0就补0、符号位是1就补1,但是如果是无符号数就直接补0】
        但要知道这是补码,我们得将其 减一 再 取反 转化为十进制后得到的就是 -126,打印出来的就是 -126 了 

        

算术转换:

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

        long double

        double
        float
        unsigned long int
        long int 
        unsigned int
        int

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

        警告:但是算术转换要合理,要不然会有一些潜在的问题

操作符的属性:

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

         1.操作符的优先级
         2.操作符的结合性
         3.是否控制求值顺序

两个相邻的操作符先执行哪个?
        取决于他们的优先级。如果两者的优先级相同,取决于他们的操作符优先级【优先级高的先执行,比如说 * 比 + 优先级高,所以先算 *】 、结合性【当优先级相同时,从左向右执行】

一些问题表达式:

        问题表达式一:a*b + c*d + e*f

        注释:代码1在计算的时候,由于比 + 的优先级高,只能保证计算是比 + 早,但是优先级并不能决定第三个 * 比第一个 + 早执行

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

        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 +   e * f

        我们不要只是将他看成一个运算式子,要将他看成一个表达式,如果我们的表达式前面计算的结果会影响到后面的话,那么如果这个表达式的优先级执行顺序没法确定就会出现问题;
        所以说如果我们即使拥有优先级、结合性、求值顺序,表达式依然无法确定唯一的计算路径,那么这就是一个有问题的表达式。

问题表达式二:c+ --c

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

 

问题表达式三:

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

这个表达式每个编译器算出来的都不一样

问题表达式四:

#define _CRT_SECURE_NO_WARNINGS 1
#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()函数谁先被调用执行,不同的调用顺序会导致最后计算的结果不同,所以这也是一个有问题的表达式

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值