二进制位的综合运用:
#include<stdio.h> int main() { int a = 13; /00000000000000000000000000001101 ① /00000000000000000000000000010000 ②//1左移4位得到② /把①倒数第五位数字加上1变成00000000000000000000000000011101③ a |= (1<<4);//①按位或② printf("%d\n", a); /00000000000000000000000000011101 ③ /变成③之后又想变回① /11111111111111111111111111101111 ⑤ / 则③要按位与&⑤得到① /00000000000000000000000000001101① /而⑤是怎么得呢?是②~按位取反得到的 a &= (~(1 << 4)); printf("%d\n", a); return 0; }
--前置、后置 -- ++前置、后置++
后置++
int main() { int a = 1; int b = a++;//后置++,先使用,后++(先把a的值赋给b,a再+1) //b=a,a=a+1 printf("a=%d b=%d\n", a, b);//2 1 return 0; }
后置- -
int main() { int a = 1; int b = a--;//后置--,先使用,后--(先把a的值赋给b,a再-1) //b=a,a=a-1 printf("a=%d b=%d\n", a, b);//0 1 return 0; }
前置++
int main() { int a = 1; int b = ++a;//前置++,先++,后使用(a先+1,再把a的值赋给b) //a=a+1,b=a printf("a=%d b=%d\n", a, b);//2 2 return 0; }
前置- -
int main() { int a = 1; int b = --a;//前置--,先--,后使用(a先-1,再把a的值赋给b) //a=a-1,b=a printf("a=%d b=%d\n", a, b);//0 0 return 0; }
(类型) 强制类型转换
建议能不用则不用,强扭的瓜不甜。
int main() { int a = (int)3.14;//3.14是double类型,在前面加(类型),就转换类型了 printf("%d\n", a);//打印3,因为是整型,不保留小数 // int a = int(3.14)//err return 0; }
7.关系操作符
> >= < <= != 用于测试“不相等” == 用于测试“相等”
8.逻辑操作符
&& 逻辑与(并且) || 逻辑或(或者)
练习①
int main() { int a = 0; int b = 0; scanf("%d %d", &a, &b); //a 和 b 都是5,打印 hehe if (a == 5 && b == 5) { printf("hehe\n"); } //a 或者 b是5 打印哈哈 if (a == 5 || b == 5) { printf("haha\n"); } return 0; }
练习②
打印闰年 int main() { int y = 0; scanf("%d", &y); //1. 能被4整除,并且不能被100整除 //2. 能被400整除是闰年 if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)) { printf("闰年\n"); } else { printf("不是闰年\n"); } return 0; }
练习③
int main() { int i = 0, a = 0, b = 2, c = 3, d = 4; i = a++ && ++b && d++; printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);//1234 return 0; }
打印结果依然是1234,为什么bcd的值没变呢?
注意:&&操作符有一个规则,左边为假,右边就不计算了!
int main() { int i = 0, a =1 , b = 2, c = 3, d = 4; i = a++ && ++b && d++; printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d); return 0; }
打印结果为2335,因为a=1为真,右边才会计算,b=3,又因为a,b都为真,d才会计算,d=5
int main() { int i = 0, a = 1, b = 2, c = 3, d = 4; i = a++||++b||d++; printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);// return 0; }
a++,先使用,后++,所以a=1,为真,进入下面的打印,后面的便不再计算。
注意:|| 这个操作符的左边为真,右边不再计算
int main() { int i = 0, a =0, b = 2, c = 3, d = 4; i = a++||++b||d++; printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);//1334 return 0; }
a为0,a++(先使用,后++)所以a为假,计算b,++b(先++,后使用),b=3,为真,a||b为真,d不计算,进入打印。
注意:逻辑操作符 && || ! 计算结果是真,使用1表示
int main() { int a = 3 && 5; printf("%d\n", a);//a=1 return 0; }
9.条件操作符
也叫三木操作符(有三个操作数)
像a+b,有两个数a和b,就是双目操作符
!a,是单目操作符
看下面的例子
判断较大值 int main() { int a = 0; int b = 0; int m = 0; scanf("%d%d", &a, &b); if (a > b) m = a; else m = b; printf("%d\n", m); return 0; }
而用三木操作符表示会更加简洁明了
int main() { int a = 0; int b = 0; int m = 0; scanf("%d%d", &a, &b); m = (a > b ? a : b); printf("%d\n", m); return 0; }
10.逗号表达式
exp1,exp2,exp3,…expN
逗号表达式:从左向右计算,整个表达式的结果是最后一个表达式的结果!
举例:
int main() { int a = 1; int b = 2; int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式 printf("%d\n", a); printf("%d\n", b); printf("%d\n", c); return 0; }
a = get_val(); count_val(a); while (a > 0) { a = get_val(); count_val(a); }
如果使用逗号表达式,改写:
while (a = get_val(), count_val(a), a > 0) { }
11.下标引用,函数调用和结构成员
11.1 [ ]下标引用操作符
操作数:一个数组名+一个索引值
举例:
int main() { int arr[10] = { 1,2,3,4,5 }; // 0 1 2 3 4 5 6 7 8 9 //数组的起始是有下标的,下标是0开始的 printf("%d\n", arr[2]);//[] 下标引用操作符,arr 和 2 是[] 的两个操作数 //3 + 5 // return 0; }
11.2()函数调用操作符
接受一个或多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
举例:
#include <string.h> int Add(int x, int y) { return x + y; } void test() { printf("hehe\n"); } int main() { //int len = strlen("abc");//() 函数调用操作符 //() 的操作数是:strlen "abc" //printf("%d\n", len); int c = Add(3, 5);//() 函数调用操作符 //Add 3 5 //对于函数调用操作符来说,最少有1个操作数 printf("%d\n", c); test(); return 0; }
11.3 访问一个结构的成员
. 结构体成员名 -> 结构体指针->成员名
结构体叫复杂类型也叫自定义类型
目前C语言给我们提供了char、short、long long、int long、float、double类型,这些类型我们也可以叫内置类型,是C语言本身就具有的这些类型,但是这些类型不能表达我们生活中所有的事情,如果我们要描述一本书,要知道书名,定价,作者,分类,出版社,书号等等。要涉及这么多复杂类型,所以C语言就给出了一个结构体类型,允许我们可以创造一个类型, 它描述的对象是复杂的,我们也可以叫做自定义类型。
那么就可以给一个struct,这是C语言给的一种语法形式,然后定义一个书的类型,起名叫Book,下面就给一个大括号,里面就可以输入一些描述书的属性。比如书名是一个字符串,一个字符串要存起来,要放到一个字符数组里面,那么就可以输入char name[20]; 作者亦如此,输入char author[20];定价是一个价格,有整数小数,那么可以用一个float类型,输入float price;
创建好了结构体,就要创建变量了,struct Book就是类型名,拿起它创建一个变量b1,就是一本书,因为里面有很多属性,假设给结构体初始化,给一个大括号{},里面就可以输入书名,作者,定价,特别要注意,如果定价输入小数则要加一个f,如66.5f,表示这个数字是float类型接下来就是打印,书名、作者是字符串用%s,定价用%f,要写清打印哪本书,在属性前面写上b1.,如 b1.name。这时候就运用到了.操作符
如果只要小数点后一位就在%f前面输入.1,保留两位就加上.2
如下所示:
.操作符
struct Book { char name[30];//成员 char author[20]; float price; }; int main() { struct Book b1 = {"C语言第一课", "鹏哥", 66.5f};//书 struct Book b2 = {"数据结构第一课", "杭哥", 88.6f};//书 printf("%s %s %.1f\n", b1.name, b1.author, b1.price); printf("%s %s %.1f\n", b2.name, b2.author, b2.price); //结构体变量.成员名 return 0; }
上面是第一种方式,还有第二种方式:
分装一个函数print();打印b1里面的信息,&b1的地址取出来给print();函数大括号里面就写上b1的类型struct Book,取出来的地址用* p接收,*表示p是一个指针,知道p指向b1时,要找到p,那么对指针解引用就是找到指针所指向的那个对象,所以用(*p).name。也有更简便的写法:p->name,意思时p所指向的那个对象
如下所示:
->操作符
struct Book { char name[30];//成员 char author[20]; float price; }; void Print(struct Book * p) { printf("%s %s %.1f\n", (*p).name, (*p).author, (*p).price); printf("%s %s %.1f\n", p->name, p->author, p->price); //-> //结构指针->成员名 } int main() { struct Book b1 = { "C语言第一课", "鹏哥", 66.5f };//书 struct Book b2 = { "数据结构第一课", "杭哥", 88.6f };//书 Print(&b1); Print(&b2); return 0; }
12.表达式求值
操作符时如何影响表达式求值的?
表达式求值的顺序一部分时由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
要考虑两个问题:
1.表达式在计算的过程中,有哪些类型转换?
2.表达式的求值顺序是怎样的?
12.1隐式类型转换
类型转换分为两种:整型提升和算术转换
整型提升:
C的整型算术运算总是至少默认以整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
举例;
int main() { char c1 = 5; char c2 = 127; char c3 = c1 + c2; printf("%d\n", c3); return 0; }
c1,c2,c3都是char类型,char类型是一个字节,而为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,普通整形是4个字节,感觉它变大了,提升了,
所以要注意,所谓的整型提升是针对字符和短整型,这件事情是隐形发生的,我们没有看到而已。
整型提升的意义
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度-般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU (general-purpose
CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned
int,然后才能送入CPU去执行运算。
也就是说:一个char类型的数据或者short类型的数据,当我们把它放在一个整型的空间里的时候,足以能放得下,而且这个过程中会产生静位移向,char类型是8个bit位,两个char类型的数据进行相加的时候,如果产生静位,有些位可能就丢了,但如果把一个char类型的数据或者一个短整型的数据放在一个整型的空间里面去,整型是32个bit位,在计算的过程中及时产生静位,它也不会丢,他只会放到更高位上去,空间是足够的,这样使得计算的精度会更高更准确些。
如何进行整型提升呢?
整型提升是按照变量的数据类型的符号来提升的
int main() { char c1 = 5; //00000000000000000000000000000101 //00000101 - c1(补码) char是8个bit位,所以只存了后面8个,叫做截断 char c2 = 127; //00000000000000000000000001111111 //01111111 - c2 (补码)亦如此 char c3 = c1 + c2; //c1,c2是char类型,是有符号的char,所以要进行整型提升, //那存的8个bit位里最高位就是符号位了,按照符号位补,所以就咔咔补够32个bit位 //00000000000000000000000000000101 -c1 //00000000000000000000000001111111 -c2 //00000000000000000000000010000100 -整型提升后相加 //char类型c3又放不下这么多,又发生截断⬇ //10000100 - c3(补码) printf("%d\n", c3); //%d - 10进制的形式打印有符号的整数,c3是cahr类型, //所以要进行整型提升,符号位是1,咔咔补完1上去,但这是补码,计算的是原码 //11111111111111111111111110000100 - 补码 //11111111111111111111111110000011 - 反码 //10000000000000000000000001111100 --> -124 return 0; }
12.2 算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另一个操作数的类型,否则操作就不进行。下面的层次体系称为寻常算数转化。
long double double float unsigned long int long int unsigned int int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算
上面这些类型都是>=4个字节
这些类型的表达式在计算的时候,是向上转换的, 如果表达式中有int和float类型,会把int转换成float
比如:
float f=3.14f;
int n =10;
f+n,会把n转换成float,算出float的结果
这就是寻常算数转化
如果是float和double在计算的时候会把float转换成double
long int和double在计算的时候会把long int 转换成double
12.3 操作符的属性
复杂表达式的求值有三个影响的因素
1.操作符的优先级
2.操作符的结合性
3.是否控制求值顺序
1、2 操作符的优先级和结合性
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
比如:
/ 相邻操作符优先级高的先算,低的后算 /相邻操作符的优先级相同的情况下,结合性起作用 int main() { int a = 2 + 3 + 5; return 0; }
3.是否控制求值顺序
两个加号无法得知优先级,加法结合性是L-R,所以是从左往右计算。
逗号表达式,从左向右依次计算,只有最后一个表达式的结果才是整个表达式的结果。
逻辑与&&,左边为假,右边不计算了,在一定程度上影响求值顺序。
逻辑或||,左边为真,右边不计算了。
条件操作符?:,表达式一为真,表达式二算,表达式三不算;表达式一为假,表达式二不算,表达式三算。
这些都会影响求值顺序
但是按照表达式这三个属性就一定能求出某个表达式唯一的计算路径吗,一定能算出一个固定的结果吗?
答案是不一定
所以就产生了一些问题代码
/表达式1 a*b+c*d+e*f
计算顺序有两种:
①
②
注释:代码1在计算的时候,由于比+的优先级高,只能保证 * 的计算是比+早,但是优先级并不是能决定第三个比第一个+早执行
/表达式2 c + --c;
算出两种结果
假设int c=5;
①如果先准备左边的为5,那么5+4=9;
②如果先计算右边的c,- -c,c变成4,4+4=8;
/表达式3 int fun() { static int count =1; return ++count; } int main() { int answer; answer = fun() - fun() * fun(); printf("%d\n",answer);/输出多少? return 0; }
调用fun函数,函数里有static修饰的变量,这个变量的特点是:出了函数不销毁,继续保持上次留下的值,所以三次调用count返回为2、3、4 ,但先调用那个函数却产生了歧义
第①种结果: 4-2 * 3 = -2
第②种结果:2-3 * 4 = -10
总结
我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。
₍ᵋꏿ৺ꏿᵌ₎
操作符知识点到此结束啦(撒花撒花~)
★,°:.☆( ̄▽ ̄)/$:.°★ 。