C语言初阶——操作符part2

目录

七.关系操作符

八.逻辑操作符

1.简单用法

2.360面试题目

九.条件操作符

十.逗号表达式

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

1.下标引用

2.函数调用

3.结构成员

十二.表达式求值

1.隐式类型转换

 2.算术转换

3.操作符的属性

结束语


我们接着上节课的来讲

七.关系操作符

>

>=

<

<=

!=      用于测试“不相等”

==     用于测试“相等”

 这些关系运算符比较简单,没什么可讲的,但是我们要注意一些运算符使用时候的陷阱。

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

八.逻辑操作符

&&     并且的意思,同时为真的时候是真,其他是假

||        或者的意思,同时为假的时候是假,其他是真

位操作符是二进制中计算的,这俩就是用于判断

区分逻辑与和按位与

区分逻辑或和按位或

1 & 2----->0

1 && 2---->1

1 | 2----->3

1 || 2---- > 1

1.简单用法

int main()
{
	int a = 0;
	int b = 0;
	if (a || b)
	{
		printf("haha\n");
	}
}

判断闰年,多个判断

1. 能被4整除,并且不能被100整除
2. 能被400整除

int main()
{
	int y = 2048;
	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
	{
		printf("Yes\n");
	}

	return 0;
}

2.360面试题目

&&    操作符,左边为假,右边无需计算

||       操作符,左边为真,右边无需计算

对比下面四个,理解好什么时候短路,什么时候继续往下 ,理解好逻辑操作符的原理

#include <stdio.h>
int main()
{
	int i = 0, a = 0, b = 2, c = 3, d = 4;
	i = a++ && ++b && d++;
	//打印出来是1 2 3 4    因为我们的a是0,而且最开始还没++,所以直接判断为假了,后面就不计算了,直接短路了然后a++,所以是1234
	
	int i = 0, a = 1, b = 2, c = 3, d = 4;
	i = a++ && ++b && d++;
	//打印出来是2 3 3 5    因为我们a不是0,前面是真,我们后面继续去算,是真的,i的值就是1,后面都去计算
	
	int i = 0, a = 0, b = 2, c = 3, d = 4;
	i = a++ || ++b || d++;
	//打印出来是1 3 3 4    因为我们的a是0,最开始a先使用再加加,所以第一个a用好了,然后第二个b的时候,是真的了,所以后面就短路了,就算到b就行了
	
	int i = 0, a = 1, b = 2, c = 3, d = 4;
	i = a++ || ++b || d++;
	//打印出来是2 2 3 4    因为我们a就是1,先使用再加加,但是前面已经是真的了,后面不用看也是真的,所以后面都是真的了,就不计算了,直接短路了
	
	printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);

	return 0;
}

九.条件操作符

exp1 ? exp2 : exp3

1为真,2计算,3不计算,2是结果

1为真,2不计算,3计算,3是结果

int main()
{
	int a = 10;
	int b = 20;
	int m = 0;
	
	if (a > b)
		m = a;
	else
		m = b;
	//上面判断直接用条件操作符来写
	m = (a>b?a:b);

	return 0;
}

再看一个例子 

if (a > 5)
	b = 3;
else
	b = -3;
//上面的改写就是下面的
int main()
{
	int a = 0;
	int b = (a > 5 ? 3 : -3);
	return 0;
}

十.逗号表达式

exp1, exp2, exp3, …expN

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

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

int main()
{
	int a = 1;
	int b = 2;
	int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式
	//即使第一个是假的,也不影响后面的计算,最后一个是结果
	printf("c=%d\n", c);
	return 0;
}

输出结果是13,从前到后,尽管第一个判断不成立

再看一个改写为逗号表达式

a = get_val();
count_val(a);
while (a > 0)
{
         //业务处理
        a = get_val();
        count_val(a);
}
//如果使用逗号表达式,改写:
while (a = get_val(), count_val(a), a>0)
{
         //业务处理
}

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

1.下标引用

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

int main()
{
	int arr[10] = { 1,2,3,4,5 };
	printf("%d\n", arr[4]);//[] - 下标引用操作符,操作数是:arr , 4
	//3 + 4;
	return 0;
} 

2.函数调用

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

至少有一个,当函数没有参数的时候

int main()
{
	int len = strlen("abcdef");//()就是函数调用操作符,操作数:strlen, "abcdef"

	return 0;
}

3.结构成员

char       int       short          long          long long             float        double

内置类型——自定义类型——结构体——枚举——联合体

我们来看结构体,生活中有些对象要被描述的话,不能简单的使用单个内置类型

书:书名,作者,出版社,定价,...

学生:名字,年龄,学号..

所以我们先创建一个类型

struct Book
{
	char name[20];
	char author[30];
	int price;
};

我们主函数里面,我们上面创建好了类型,我们这里面就创建变量,然后初始化 

然后我们打印的时候,就需要引用我们创建的结构体,变量名.成员名来引用

int main()
{
	struct Book b1 = {"鹏哥C语言", "鹏哥", 66};//创建变量初始化
	struct Book b2 = { "杭哥C++", "比特杭哥", 88 };//创建变量初始化
	printf("《%s》 %s %d\n", b1.name, b1.author, b1.price);
	//然后我们打印,引用的时候就是变量名.成员名,也就是我们的结构体.成员名
	printf("《%s》 %s %d\n", b2.name, b2.author, b2.price);
	//结构体变量.成员名

	print1(&b1);
	//我们用函数调用指针接收也是一样的 
	return 0;
}

我们用函数打印也是一的,我们上面取了b1的地址,我们传到函数里面用指针取接收

也可以用变量名.成员名,注意指针需要解引用,*p就是b1

或者直接用地址来,结构体指针->成员名来引用,也是一样的

void print1(struct Book* p)
{
	printf("%s %s %d\n", (*p).name, (*p).author, (*p).price);
	//用指针接收的时候,*p就是我们指针解引用,也就是b1,然后加上  .成员名就能引用了,不加*是b1的地址,我们加上*解引用才是b1
	printf("%s %s %d\n", p->name, p->author, p->price);
	//或者我们直接用结构体指针来写也是一样的
	//结构体指针->成员名
}

我们再来看一个例子,加深印象,好好分析

#include <stdio.h>
struct Stu
{
	char name[10];
	int age;
	char sex[5];
	double score;
};

最开始是一样的先创建类型,注意格式,我们来看两个函数我拆来来看。

下面这个打印出来不能改为18,因为要改变里面的值,我们不能用传值,只是一份临时拷贝,不能达到改变值得效果,需要传址来进行,注意结构体的用法,要记得创建变量,用 . 来引用

void set_age1(struct Stu stu)
{
	stu.age = 18;
}
//第一个函数不能改变值,因为是地址,是传值,就改变不了,这里需要地址来改变值
int main()
{
	struct Stu stu;  //创建变量

	stu.age = 20;//初始化,结构成员访问
	set_age1(stu);
	printf("%d\n", stu.age);//这里打印还是20

	return 0;
}

 这一个就打印出来是18,我们创建结构体指针,然后用变量名->成员名,来引用,函数里面用结构体指针来接受,因为是地址,地址直接取改值,也可以用*解引用来改,但是必须传址

void set_age2(struct Stu* pStu)
{
	pStu->age = 18;//结构成员访问
}
//第二个函数可以,接收的是地址,后面也是指针,就可以改变
int main()
{
	struct Stu* pStu = &stu;//创建指针变量

	pStu->age = 20;//直接用指针来初始化,结构成员访问
	set_age2(pStu);
	printf("%d\n", stu.age);//这个打印就是18

	return 0;
}

十二.表达式求值

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

1.隐式类型转换

C的整型算术运算总是至少以缺省整型类型的精度来进行的。 为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

整型提升的意义:

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长 度。 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,然后才能送入CPU去执行运算。

char        short       int        long ...

1                2            4          ...

对应里面放的字节,一个字节八个比特位,本来是char就八个比特位,但是放的整型,我们只要后八个,然后计算的时候,就整型提升,然后计算,然后再变成八个

整型提升,提升的是是符号位,就是补上来的是符号位的值

int main()
{
	//char --> signed char
	char a = 3;
	char b = 127;
	char c = a + b;
	printf("%d\n", c);
	return 0;
}

我们一个一个来看

    char a = 3;
	//截断
	//00000000000000000000000000000011
	//00000011 - a
	char b = 127;
    //截断
	//00000000000000000000000001111111
	//01111111 - b

进行整型提升,补上符号位的值,然后进行计算 

	char c = a + b;
	//00000011
	//01111111
	//整型提升
	//00000000000000000000000000000011
	//00000000000000000000000001111111
	//00000000000000000000000010000010
	//10000010 - c    然后也只要后八位
	printf("%d\n", c);
	//%d 是打印十进制的整数,再次整型提升,然后求原码打印
	//11111111111111111111111110000010 - 补码
	//11111111111111111111111110000001
	//10000000000000000000000001111110 - 原码
	//-126

只要关乎计算表达求值的操作,类型和变量不符合的,要进行整型提升来解决,记得打印的是原码,我们整型提升的也是我们的符号位的值

char - 有符号的char的取值范围是:-128~127

无符号的char的取值范围是:0~255

再来看一个例子

int main()
{
	char a = 0xb6;
	short b = 0xb600;
	int c = 0xb6000000;

	if (a == 0xb6)//10110110
		printf("a");
	if (b == 0xb600)
		printf("b");
	if (c == 0xb6000000)
		printf("c");

	return 0;
}

实例1中的a, b要进行整形提升, 但是c不需要整形提升,a, b整形提升之后, 变成了负数, 所以表达式 a == 0xb6, b == 0xb600 的结果是假, 但是c不发生整形提升, 则表达式 c == 0xb6000000 的结果是真

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

实例2中的, c只要参与表达式运算, 就会发生整形提升, 表达式 + c, 就会发生提升, 所以 sizeof(+c) 是4个字节,表达式 - c 也会发生整形提升, 所以 sizeof(-c) 是4个字节, 但是 sizeof(c), 就是1个字节.

 2.算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。

下面的层次体系称为寻常算术转换。如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。跟上面整型提升就是一个意思。

>= int  的时候,算术转换

<= int  的时候,整型转换

long double

double

float

unsigned long int

long int

unsigned int

int

向上转换的,只要转换就往上走

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

3.操作符的属性

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

1. 操作符的优先级

2. 操作符的结合性

3. 是否控制求值顺序。

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

首先确定优先级,相邻操作符按照优先级高低计算,优先级相同的情况下,结合性才起作用拿不准的就加上括号,或者拆开去写

例子1

int main()
{
	int a = 1;
	int b = 2;
	int c = 4;

	int d = a + b + c;

	//int d = a * 4 + b / 3 + c;

	return 0;
}

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

例子2

c + --c;

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

例子3

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(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。函数的调用先后顺序无法通过操作符的优先级确定。

例子4

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

	return 0;
}

简单看一下汇编代码.就可以分析清楚.这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序。我们看vs里和vs code里也是不一样的值

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

结束语

我们把操作符讲完了,注意里面的细节还有要特别记住的地方。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值