1.操作符
1.1 算术操作符
+ - * / %
1.除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
2.对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
3.% 操作符的两个操作数必须为整数,返回的是整数之后的余数
下面是 / 操作符 和 % 操作符的代码示例
// / 除法 // 1.整数除法 (除号的两端都是整数) // 2. 浮点数除法 (除号的两端一个是小数就是浮点数除法) #include <stdio.h> int main() { int r = 7 / 2;//3 整数除法 printf("%d\n", r);//3 double d = 7 / 2;// 3.000000 整数除法 printf("%lf\n", d);// 3.0 double d1 = 7 / 2.0; // 3.5 浮点型除法 printf("%lf\n", d1); //被除数不能是 0 //int r = 5 / 0; 报错 return 0; } // % 取模操作符 #include<stdio.h> int main() { int r = 17 % 8;// % 得到的是整除以后的余数 printf("%d\n", r); // % 两个操作数必须都是整数 return 0; }
1.2 移位操作符
1. << 左移操作符 2. >> 右移操作符
移位操作符是对二进制位进行操作,计算机能处理的是二进制的信息。 所以在进行学习移位操作符是,我们先来学习一下整数在内存中的存储方式,即二进制的表示形式。
整数二级制有三种表示形式
1.原码
2.反码
3.补码
1.正整数的原码,反码,补码是相同的。
2.负整数的原码,反码,补码是要计算的。
首先不管是正整数还是负整数都是可以写出二进制原码,根据正负直接写出的二级制序列就是原码
// 1个整形是4个字节 == 32个bit位
// 一个二进制位是一个bit位
// 15
// 00000000000000000000000000001111 15的原码
//第一位是符号位 1 是负数 0是整数
// -15
//10000000000000000000000000001111 -15的原码
>> 右移操作符
右移操作有两种,
1.算术右移 (二级制右边丢弃,左边补原来的符号位)
2.逻辑右移(二进制右边丢弃,左边直接补0)
C语言没有明确规定到底是算术右移,还是逻辑右移,一般编译器采用的是算术右移,左边补符号位。
反码 由原码符号位不变,其他位按位取反得到
补码 由反码加一得到
整数是以补码的方式在内存中存储的。打印的时候才会以原码的方式打印
示例:
//正整数 #include<stdio.h> int main() { int a = 15; //00000000000000000000000000001111 - 原码 //正整数原码 反码 补码都相同 //整数在内存中存储的是补码 //计算的时候也是使用补码计算的 int c = a >> 1;//移动的是a中的 2进制 信息 a是不变的 printf("%d\n", c);//7 printf("%d\n", a);//a不变,还是15 //可以发现正整数右移有除2的效果 return 0; } //负整数 #include<stdio.h> int main() { int a = -15; //10000000000000000000000000001111 - 原码 //11111111111111111111111111110000 - 反码 (原码的符号位不变,其他位按位取反得到的就是反码) //11111111111111111111111111110001 - 补码 (反码+1) int b = a >> 1; //11111111111111111111111111110001 -15补码 //11111111111111111111111111111000 右移 左边补符号位 //11111111111111111111111111110111 反码 (补码-1) //10000000000000000000000000001000 原码 -8 printf("%d\n", a);//a不变 还是-15 printf("%d\n", b);//-8 //通过运行,可以发现VS采用的逻辑右移 return 0; }
<< 左移操作符
左移操作符 比较简单,规则是左边抛弃,右边补0
示例:
#include<stdio.h> int main() { int a = 7; //整数原码反码补码相同 //00000000000000000000000000000110 //左移 也是操作补码 左边丢弃 右边补0 int b = a << 1; //00000000000000000000000000001100 //12 printf("%d\n", a); printf("%d\n", b); //正整数负整数左移1位都有乘二效果 //C语言只能是移正数位 //a = a << 1; 等价于 a <<= 1; //a = a + 1; 等价于 a += 1; return 0; }
注意:对于移位运算符,不要移动符号位,这时C语言标准未定义的。例如:
int num = 10;
num>>-1; // error
1.3 位操作符
& //按位与
| //按位或
^ //按位异或
注:他们的操作数必须是整数,操作的是二进制位,补码
示例:
#include<stdio.h> int main() { int a = 3; //00000000000000000000000000000011 int b = -5; //10000000000000000000000000000101 原码 //11111111111111111111111111111010 反码 //11111111111111111111111111111011 补码 int c = a & b; //& -- 按(二进制)位与 -- 对应二进制位有0则有0,两个同时为1 才为1 //00000000000000000000000000000011 3 //11111111111111111111111111111011 -5 补码 //& 按位与 //00000000000000000000000000000011 3 printf("%d\n", c); //3 c = a | b; // | -- 按(2进制)位或 -- 对应的二进制位有1则为1,两个同时为0才是0 //00000000000000000000000000000011 3 //11111111111111111111111111111011 -5 补码 // | 按位或 //11111111111111111111111111111011 这是补码 //11111111111111111111111111111010 反码 (补码-1) //10000000000000000000000000000101 原码 (反码除符号位按位取反) printf("%d\n", c);//-5 c = a ^ b; // ^ 按(二进制位)位异或 -- 对应的二进制位相同为0,相异为1 //00000000000000000000000000000011 3 //11111111111111111111111111111011 -5 补码 // ^ 按位异或 //11111111111111111111111111111000 这是补码 //11111111111111111111111111110111 反码 (补码-1) //10000000000000000000000000001000 原码 (反码除符号位按位取反) printf("%d\n", c);//-8 return 0; }
异或的应用:一道面试题,不创建临时变量,交换两个变量的值
//不创建临时变量 交换两个整型变量 //方法一 #include<stdio.h> int main() { int a = 3; int b = 5; printf("交换前:a = %d b = %d\n", a, b); a = a + b; b = a - b; a = a - b; printf("交换后:a = %d b = %d\n", a, b); return 0; } //假设a特别大 b 特别大 a+b 可能超过int 大小 会发生截断,下面有更好的方法 //方法二 #include<stdio.h> int main() { int a = 3; int b = 5; printf("交换前:a = %d b = %d\n", a, b); a = a ^ b; b = a ^ b; //a^b^b == a a = a ^ b; //a^b^a == b printf("交换后:a = %d b = %d\n", a, b); return 0; } #include<stdio.h> int main() { int a = 3; int b = 5; printf("%d\n", a ^ b ^ a); printf("%d\n", a ^ a ^ b); //a^a -> 0; //a^0 -> a; //a^b^a -> b //a^a^b -> b //异或是支持交换率的 return 0; }
1.4 赋值操作符
=
赋值操作符是一个很棒的操作符,它可以让你得到一个你之前不满意的值,也就是你可以给自己重新赋值。
#include<stdio.h> int main() { int a = 10; int x = 0; int y = 20; //连续赋值 a = x = y + 1; //将y+1的值赋给x,再赋给a x = y + 1; a = x; //这种写法更加清晰 return 0; }
复合赋值
+= -= *= /= %= >>= <<= &= |= ^=
这些运算符都可以写成复合的效果,比如
int x = 10;
x = x + 10;
x += 10;//与上面一行相同 ,复合赋值
//其他运算符是一样的道理
1.5 单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
!逻辑反操作
#include<stdio.h> int main() { int flag = 0; if (flag == 0) { printf("hehe\n"); } if (!flag)//flag 为假进行 { printf("hehe\n"); } if (flag)//flag 为真进行 { printf("haha\n"); } return 0; }
- 负值 + 正值
#include<stdio.h> int main() { int a = 5;//+ 可以省略不写 int b = -a; //-5 return 0; }
& * 应用于指针
#include<stdio.h> int main() { int a = 10; //这里 * 是为了说明pa是指针变量 不是操作符 int* pa = &a;//&-取地址操作符 取出a的地址 // * 解引用操作符 (间接访问操作符) 单目操作符 *pa = 20;// 通过pa中存放的地址,找到指向的空间(内容) int c = *pa; printf("%d\n", c);//20; return 0; }
sizeof 操作符 不是函数
sizeof计算的是类型创建变量的大小 单位是字节
#include<stdio.h> int main() { int a = 10; printf("%d\n", sizeof(int));//4 printf("%d\n", sizeof(a));//4 printf("%d\n", sizeof a); //sizeof(变量) 变量的括号可以省略 可以说明sizeof不是函数 //因为函数调用时()不可以省略 return 0; }
sizeof 计算数组大小
#include<stdio.h> int main() { int arr[10] = { 0 }; printf("%d\n", sizeof(arr));//40计算整个数组的大小 单位字节 printf("%d\n", sizeof(int[10]));//去掉名字 就是数组类型 还是40 return 0; }
~ 按(二进制)位取反
#include<stdio.h> int main() { int a = 0; printf("%d\n", ~a);//-1 //00000000000000000000000000000000 // ~ 按位取反 补码按位取反 //11111111111111111111111111111111 补码 //11111111111111111111111111111110 反码 补码-1 //10000000000000000000000000000001 原码 -1 return 0; }
~ 在多组输入时的应用
#include<stdio.h> int main() { int a = 0; //1. //scanf读取失败返回 EOF while (scanf("%d", &a) == 1) { printf("%d\n", a); } //2. while (scanf("%d", &a) != EOF) { printf("%d\n", a); } //3. //假设读取失败 返回的是EOF ----> -1 //10000000000000000000000000000001 -1原码 //11111111111111111111111111111110 -1反码 //11111111111111111111111111111111 -1补码 //~-1 //00000000000000000000000000000000 0 为假 while (~scanf("%d", &a)) { printf("%d\n", a); } return 0; }
-- 前置 后置--
++前置 后置++
#include<stdio.h> int main() { int a = 1; int b = a++;//后置++, 先使用,后++ printf("a = %d b = %d\n", a, b);//2 1 b = a--; printf("a = %d b = %d\n", a, b);//1 2 return 0; } #include<stdio.h> int main() { int a = 1; int b = ++a;//后置++, 先使用,后++ printf("a = %d b = %d\n", a, b);//2 2 b = --a; printf("a = %d b = %d\n", a, b);//1 1 return 0; } #include<stdio.h> int main() { int a = 10; printf("%d\n", a++);//10 printf("%d\n", a);//11 return 0; }
() 强制类型转换
#include<stdio.h> int main() { //3.14是浮点型 int a = (int)3.14;//强制类型转换 只保留 整数部分 printf("%d\n", a);//3 return 0; }
sizeof和数组
#include<stdio.h> void test1(int arr[]) { printf("%d\n", sizeof(arr));//int * 指针的大小 8/4 } void test2(char ch[]) { printf("%d\n", sizeof(ch));//char * 指针的大小 8/4 不要以为是1 } int main() { int arr[10] = { 0 }; char ch[10] = { 0 }; printf("%d\n", sizeof(arr)); printf("%d\n", sizeof(ch)); test1(arr); test2(ch); return 0; }
1.6 关系操作符
> | |
>= | |
< | |
<= | |
!= | 用于测试“不相等” |
== | 用于测试”相等“ |
关系运算符比较简单,但要注意的是它们只能用到合适的类型上
比如,不能用于字符串比较,因为字符串比较有特定的函数。整形可以跟整形比较。
1.7 逻辑操作符
&& | 逻辑与 并且 两个条件都要满足才为真 |
|| | 逻辑或 或者 一个条件满足就为真 |
360笔试题
#include<stdio.h> int main() { int i = 0, a = 0, b = 2, c = 3, d = 4; i = (a++ && ++b && d++); //当计算完a为假 不在向后计算 i是0 printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);//1 2 3 4 return 0; }
注意:
|| 逻辑与操作符的左边为真,右边不再计算 为真
&& 逻辑或操作符的左边为假,右边不再计算 为假
逻辑操作符如果计算结果是真,用1表示,假用0表示
1.8 条件操作符
条件操作符又称三目操作符,有三个操作数
exp1 ?exp2 : exp3
当exp1为真时,计算exp2,否则计算exp3
示例:
#include<stdio.h> int main() { int a = 0; int b = 0; int max = 0; scanf("%d %d", &a, &b); if (a > b) max = a; else max = b; printf("%d\n", max); //与下面等价 max = (a > b ? a : b); printf("%d\n", max); return 0; }
1.9 逗号表达式
exp1,exp2,exp3,...expN
逗号表达式,就是用逗号隔开的多个表达式。逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
#include<stdio.h> int main() { int a = 1; int b = 2; int c = (a > b, a = b + 10, a, b = a + 1); printf("%d\n", c);//13 return 0; }
1.10 下标引用,函数调用和结构成员
[ ] 下标引用操作符
操作数:一个数组名+一个索引值
#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5 }; //下标 0 1 2 3 4 //数组是有起始下标的,下标是0开始的 printf("%d\n", arr[2]);//[] 下标引用操作符,arr和2是[]的两个操作数 return 0; }
() 函数调用操作符
可以接受一个或多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
#include<stdio.h> #include<string.h> int Add(int x, int y) { return x + y; } int main() { int len = strlen("abc");//() 函数调用操作符 // () 的操作数是 :strlen 和"abc" 函数名和参数 printf("%d\n", len); int c = Add(3, 5); //Add 3 5 函数调用操作符 printf("%d", c); return 0; }
printf() 函数的参数是可变参数列表,参数的个数是可以变化的。
cplusplus网站上对于printf()函数的介绍:
访问结构体成员操作符
结构体用来定义复杂数据类型,是自定义类型
而 char short int long float double 等都是内置类型
. 结构体.成员名
-> 结构体指针->成员名
示例:
struct Book//书结构体 { char name[30];//书名 char author[20];//作者 float price;//价格 }; #include<stdio.h> void Print(struct Book* p) { // . 结构体.成员名 printf("%s %s %.2f\n", (*p).name, (*p).author, (*p).price); //-> 结构体指针->成员名 printf("%s %s %.2f\n", p->name, p->author, p->price); } int main() { struct Book b1 = { "C语言详解","西兰花",66.66f }; struct Book b2 = { "数据结构","西兰花",88.88f }; printf("%s %s %.2f\n", b1.name, b1.author, b1.price); printf("%s %s %.2f\n", b2.name, b2.author, b2.price); Print(&b1); Print(&b2); return 0; }
2.表达式求值
表达式求值的顺序一部分是由操作符的优先性和结合性决定的
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型
2.1 隐式类型转换
2.1.1 整型提升
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整形提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU ( general-purpose CPU )是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
有符号整型提升,高位补符号位
无符号整型提升,高位直接补0
示例:
#include<stdio.h> int main() { char c1 = 5; //00000000000000000000000000000101 // 5整形 存到char中 发生截断 //00000101 char c2 = 127; //00000000000000000000000001111111 //截断 //01111111 char c3 = c1 + c2; //00000000000000000000000000000101 //c1 补码 //00000000000000000000000001111111 //c2 补码 //00000000000000000000000010000100 补码 c3 截断 //10000100 阶段后的c3 // 以%d打印整型提升 补码高位补符号位 //11111111111111111111111110000100 补码 //11111111111111111111111110000011 反码 //10000000000000000000000001111100 原码 printf("%d\n", c3);//-124 return 0; }
证明存在整型提升
#include<stdio.h> int main() { char a = 0xb6;//10110110 一个16进制数是4个比特位 b6刚好8个bit位,一个字节可以放下 //截断后符号位是1 整型提升后是负数 short b = 0xb600; int c = 0xb6000000; if (a == 0xb6)//整型提升后是负数 { printf("a\n"); } if (b == 0xb600)//整型提升后是负数 { printf("b\n"); } if (c == 0xb6000000) { printf("c\n");//不用整型提升,可以打印 } return 0; }
只要参与表达式运算就会整型提升:
#include<stdio.h> //%d 十进制的形式打印有符号的数 //%u 十进制的形式打印无符号的数 //%u 输出无符号整形数 因为sizeof是返回的无符号数 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.1.2 算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
2.2 操作符的属性
复杂表达式的求值有三个影响的因素。
1.操作符的优先级
2.操作符的结合性
3.是否控制求值顺序 , && || (?:) 四个操作符控制
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性
优先级 |
( ) 括号 |
( ) 函数调用 |
[ ] 下标引用 |
访问结构体操作符 |
单目操作符 |
算术操作符 |
移位操作符 |
关系操作符 |
位操作符 |
逻辑操作符 |
三目操作符符 |
赋值操作符 |
逗号 |
总结∶我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个
表达式就是存在问题的。