1. 操作符(也叫运算符)分类
算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员
2. 算术操作符
+(加法) - (减法) *(乘法) /(除法) %(取余或取模)
加减乘操作符同数学一样,但需注意除法和取模!
2.1 '/'操作符
对于/操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。(即想计算机出小数,除号的两端至少有一个操作数是浮点数。)
/操作数计算的结果是商。
代码实例:
// ‘ /’ 操作符 // 备注:对于除法操作符来说,两边的操作数都是整数,执行的是整数除法 // 如果想计算机出小数,除号的两端至少有一个操作数是浮点数 #include<stdio.h> int main() { int ret1 = 10 / 3; double ret2 = 10.0 / 3; printf("ret1=%d\n", ret1); printf("ret2=%lf\n", ret2); //float与double用printf打印后面默认保留六位小数 return 0; }
运行结果:
2.2 %操作符
1、%取模(取余)计算的是整除之后的余数。
2、%操作符的两个操作数必须为整数。(操作符为小数报错!)
// '%'取模(取余) // 1、计算的是整除之后的余数 // 2、%操作数的两个操作数必须为整数。 #include<stdio.h> int main() { int ret = 10 % 3; printf("ret=%d\n", ret); return 0; }
运行结果:
2.3 总结
1. 除了%操作符之外,其他的几个操作符可以作用于整数和浮点数。
2. 对于/操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
3. %操作符的两个操作数必须为整数。返回的是整除之后的余数。
4. 想得到商用/操作符,想得到余数用%操作符。
3. 移(二进制)位操作符
<< 左移操作符(箭头向左)
>> 右移操作符(箭头向右)
注:1. 移位操作符的操作数只能是整数!
2. 移的是二进制数
知识补充:二进制
整形数据(整数)的2进制表示形式,其实有3种:
1. 原码:按照一个数的正负,直接写出它的二进制表示形式得到的就是原码。
2. 反码:反码是原码的符号位不变,其他位按位取反,就是反码。
3. 补码:补码是反码+1。
1. 正数的原码、反码、补码是相同的
2. 负数的原码、反码、补码要通过计算的
3. 区分正负数:最高位为符号位(0代表整数,1代表负数)
4. int占4个字节,就有32个bit位
代码实例:
int a=10;
// 10的2进制:1010
// 原码:0000 0000 0000 0000 0000 0000 0000 1010
int b=-10;
// 原码:1000 0000 0000 0000 0000 0000 0000 1010
// 反码:1111 1111 1111 1111 1111 1111 1111 0101
// 补码:1111 1111 1111 1111 1111 1111 1111 0110
由补码转原码:
// 补码:1111 1111 1111 1111 1111 1111 1111 0110
// 过程:1000 0000 0000 0000 0000 0000 0000 1001
// 原码:1000 0000 0000 0000 0000 0000 0000 1010
回顾进制:
进制只是数值的不同表达形式,它的本质是一样的。
如:数值——12
二进制:1100
八进制:014
十进制:12
十六进制:0xc
只是把数值用不同进制表示出来,但数值本质还是12.
//非十转十:按权展开(注:引导符不算)
//十转非十:整数部分——取余倒排序法
整形数据在内存中的存储形式:
整形数据在内存中存储的其实是:补码的二进制
所以在参与移位的时候,移动的都是补码 。
3.1 左移操作符
移位规则:
左边抛弃,右边补0。
图解:
代码实例:
#include<stdio.h> int main() { int a = 10; //补码:0000 0000 0000 0000 0000 0000 0000 1010 int b = a << 1; //补码:0000 0000 0000 0000 0000 0000 0001 0100 printf("a=%d\n", a); printf("b=%d\n", b); //a虽然向左移了一位,但实际上a在没被赋值的情况下,自身的值不会变化 return 0; }
运行结果:
注:
1. a虽然向左移了一位,但实际上a在没被赋值的情况下,自身的值不会变化。(即a<<1的结果是移位之后的效果,但是a是不变的(如b=a+2,a是不变的))。
2. 打印的时候打印的是原码。
3.2 右移操作符
移位规则:(分为两种)
1、算术右移(常见——如VS)
左边补原来值的符号位,右边抛弃。
2、逻辑右移
左边直接补0,右边抛弃。
代码实例:
#include<stdio.h> int main() { int a = -1; //原码;1000 0000 0000 0000 0000 0000 0000 0001 //反码:1111 1111 1111 1111 1111 1111 1111 1110 //补码:1111 1111 1111 1111 1111 1111 1111 1111 //补码全为1的为(数值)-1 //算术还是逻辑? int b = a >> 1; printf("a=%d\n", a); printf("b=%d\n", b); return 0; }
运行结果:
算术右移。
警告:
对于移位运算符,不要移动负数位,这个是标准未定义的。
例: int a=10;
int b=a>>-1;//error
4. 位操作符
位操作符有:
& //按位与
| //按位或
^ //按位异或
注:
1、他们的操作数必须是整数
2、也是针对二进制位进行运算的
运算规则;
&——按(2进制)位与
对应的二进制位:有0则为0,两个同时为1才为1.
|——按(2进制)位或
对应的二进制位:有1则为1,两个同时为0才为0.
^——按(二进制)位异或
对应的二进制位:相同为0,相异为1。
代码实例:
#include<stdio.h> int main() { int a = 3; //补码:00000000000000000000000000000011 int b = -5; //原码:10000000000000000000000000000101 //反码:11111111111111111111111111111010 //补码:11111111111111111111111111111011 int c = a & b; //a的补码:00000000000000000000000000000011 //b的补码:11111111111111111111111111111011 //a&b:00000000000000000000000000000011(正数) int d = a | b; //a的补码:00000000000000000000000000000011 //b的补码:11111111111111111111111111111011 //a|b:11111111111111111111111111111011(负数) int e = a ^ b; //a的补码:00000000000000000000000000000011 //b的补码:11111111111111111111111111111011 //a^b:11111111111111111111111111111000(负数) printf("c=%d\n", c); printf("d=%d\n", d); printf("e=%d\n", e); return 0; }
运行结果:
我们来做一道变态的面试题:
不能创建临时变量(第三个变量),实现两个整数的交换
代码实例:
//代码1——创建临时变量 #include<stdio.h> int main() { int a = 3; int b = 5; printf("交换前:%d %d\n",a,b); int t = a;//临时变量 a = b; b = t; printf("交换后:%d %d\n",a,b); return 0; } //代码2——计算得到(局限性——当a与b的值太大时int可能溢出) #include<stdio.h> int main() { int a = 3; int b = 5; printf("交换前:%d %d\n", a, b); b = a + b; a = b - a; b = b - a; printf("交换后:%d %d\n", a, b); return 0; } //代码3——使用按位异或操作符 #include<stdio.h> int main() { int a = 3; int b = 5; printf("交换前:%d %d\n", a, b); a = a ^ b; b = a ^ b;//b=a^b^b=a a = a ^ b;//a=a^b^a=b printf("交换后:%d %d\n", a, b); return 0; }
按位异或操作符加油站:
5. 赋值操作符
5.1 简单赋值操作符
1、作用:赋值操作符可以让你自己重新给变量赋值
注:是将右侧的表达式赋值给左侧的变量(左侧一定为变量)
一般格式:变量=表达式
例:
int weight=120;//体重
weight=89;//重新赋值
2、赋值操作符可以连用,但是不推荐。
例:
int a=2;
int b=0;
int c=5;
a=b=c+1;//连续赋值
但是这样的代码你感觉怎么样?
改为同样的语义,你再看看:
b=c+1;
a=b;
这样写法是不是更加清晰爽朗而且易于调试!
3、类型转换:
①转换条件:当赋值操作符两侧数据类型不一致时
②转换原则:转换为被赋值变量的类型
例:
int a=1;
double b=3;
a=3+0.3;//3.3转换为整形为3
5.2 复合赋值操作符
在赋值操作符之前可以加上算术操作符和移位操作符构成复合赋值操作符。C语言规定可以使用10种复合赋值操作符,分别为:
+=
-=
*=
/=
%=
<<=
>>=
&=
|=
^=
一般格式: 变量 双目操作符=表达式;
例:
a*=1-3;//a=a*(1-3),这样写更加简洁!
6 单目操作符
6.1 单目操作符介绍
! 逻辑反操作(相反:真变假,假变真。)
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
注意:单目操作符,只有一个操作数
例:
int b=a+1;//这里‘+’是加法操作符,因为它有两个操作数(1和a),是双目操作符。
int b=+1;//这里‘+’是正值,因为它只有一个操作数(1),是单目操作符。
代码实例:
1、
!——逻辑反操作(相反:真变假,假变真)
//!-逻辑反操作 //相反:真变假,假变真 //C语言中0表示真,非0表示假 #include<stdio.h> int main() { int flag = 5; if (flag)//if语句,如果flag为真,则执行if控制的语句,为假则不执行 { printf("hehe\n"); } if (!flag) { printf("haha\n"); } printf("flag=%d\n", flag); printf("!flag=%d\n", !flag); return 0; }
运行结果:
补充:加油站
布尔类型
1.布尔类型是C99中引入的
2.布尔类型就是专门用来表示真假的类型
3.真:true(本质是1),假:false(本质是0)
代码实例:
#include<stdbool.h>//预处理,对_Bool的声明 #include<stdio.h> int main() { _Bool flag1 = true;//真 if (flag1) { printf("hehe\n"); } _Bool flag2 = false;//假 if (flag2) { printf("haha\n"); } return 0; }
运行结果:
布尔类型的应用:
写一个函数判断是否为闰年
#include<stdio.h> #include<stdbool.h> bool is_leap_year(int y) { //判断是否为闰年 if (y % 4 == 0 && y % 100 != 0 || y % 400 == 0) { return true; } else { return false; } } int main() { int y = 0; scanf("%d", &y); bool ret = is_leap_year(y);//调用函数 if (ret) { printf("%d是闰年\n", y); } else { printf("%d不是闰年\n", y); } return 0; }
2、
- ——负值(就是正数变成负数,负数变成正数)
代码实例:
//-:负值 #include<stdio.h> int main() { int a = 3; int b = -6; printf("a=%d\n", a); printf("-a=%d\n", -a); printf("b=%d\n", b); printf("-b=%d\n", -b); return 0; }
运行结果:
3、
+ ——正值 (基本上忽略,不用,因为正数的正值还是正数,负数的正值还是负数)
#include<stdio.h> int main() { int a = 3; int b = -5; printf("a=%d\n", a); printf("+a=%d\n", +a); printf("b=%d\n", b); printf("+b=%d\n", +b); return 0; }
运行结果:
注意:
unsigned和signed
1. unsigned:无符号的,只能存放正数。
无符号——没有符号位,即最高位不是符号位。
2. signed:有符号的(最高位是符号位,正数负数均可),一般可省略不写。
例:signed int a=2;//与int a=2一样,signed省略。
代码实例:
#include<stdio.h> int main() { unsigned int a = 10; //补码:00000000000000000000000000001010(最高位0不是符号位) //如果写成负数怎么样 unsigned int b = -10; //原码:10000000000000000000000000001010 //反码:11111111111111111111111111110101 //补码:11111111111111111111111111110110(最高位1不是符号位) printf("a=%u\n", a); printf("b=%u\n", b); return 0; }
运行结果:
4、
& 取地址(取出一个对象(可以是变量、字符串)内存的地址)
* 解引用操作符(语法:只要是地址就可以解引用)
相当于快递:&找到地址,*送快递。
4.1 &取地址
//&取地址 #include<stdio.h> int main() { //变量可以是:整形变量、字符变量、数组变量、数组元素变量等等 int a = 10; int* pa = &a; char ch = 'w'; char* pc = &ch; int arr[10] = { 0 }; int* p1 = arr; int* p2 = &arr[0]; //字符串常量就是地址,产生的是首字符的地址 char* p = "abcd"; printf("%p\n", p); printf("%c\n", *p); return 0; }
运行结果:
4.2 *解引用操作——通过地址,从内存访问
//*解引用操作——通过地址,从内存访问 #include<stdio.h> int main() { int a = 10; int* pa = &a; *pa = 20; printf("a=%d\n", a); return 0; }
运行结果:
&取地址操作符与解引用操作符是一对,有因果的先取到一个地址,再解引用。
注意:我们不能自己捏造一个地址就对他解引用,虽然不报错但是它是非法代码!
例:
int main()
{
*(int*)0x0012ff40;
}
这就相当于,你知道了别人家的地址,就可以直接去他家吗?
5. sizeof 是关键字也是操作符(功能:计算操作数的类型长度)
注:函数调用的时候,要写括号,但是sizeof后边的括号可以省略(注:变量名可以,类型不可以),说明sizeof不是函数
//sizeof是关键字(如变量的命名不能和关键字一样) //也是操作符(功能:计算操作数的类型长度) //函数调用的时候,要写括号,但是sizeof后边的括号可以省略(注:变量名可以,类型不可以),说明sizeof不是函数 #include<stdio.h> int main() { int a = 10; printf("%d\n", sizeof(a)); printf("%d\n", sizeof a); printf("%d\n", sizeof(int)); int arr[10] = { 0 }; printf("%d\n", sizeof(arr)); printf("%d\n", sizeof arr); printf("%d\n", sizeof(int[10])); return 0; }
运行结果:
数组名是数组首元素的地址。
但是在数组我们说了有两个例外:
1、sizeof(数组名),这里的数组名是表示整个数组,计算的是整个数组的大小,单位是字节。
2、&数组名,这里的数组名是表示整个数组,&数组名取出的是数组的地址。
sizeof的特点:sizeof内部的表达式是不计算的!!!
//sizeof的特点:sizeof内部的表达式是不计算的!! #include<stdio.h> int main() { int a = 10; short s = 5; printf("%d\n", sizeof(s = a + 3)); printf("%d\n", s); return 0; }
运行结果:
6.
~ 按位取反操作符 (包括符号位)
注:①操作数为整数;②取的是二进制位
//~按位取反(注:包括符号位) #include<stdio.h> int main() { //00000000000000000000000000000000 //11111111111111111111111111111111—补码全是1的是-1 int a = 0; printf("%d\n", ~a); return 0; }
运行结果:
综合应用:& | ^ << >> ~
// & | ^ << >> ~的应用 //例:将9的二进制的第五位改成1,其他位不变 #include<stdio.h> int main() { int a = 9;//二进制:1001 //补码:00000000000000000000000000001001 //准备:00000000000000000000000000010000 1<<4 //结果:00000000000000000000000000011001 //将9的二进制的第五位改成1,其他位不变 a |= (1 << 4); printf("%d\n", a); //将9的二进制的第五位改回0,其他位不变 //补码:00000000000000000000000000011001 //准备:11111111111111111111111111101111 ~(1<<4) //结果;00000000000000000000000000001001 a &= ~(1 << 4); printf("%d\n", a); return 0; }
运行结果:
7.
++ 前置++:先++,后使用;后置++:先使用,后++ 。
-- 前置--:先--,后使用;后置--:先使用,后--。
代码实例:
//自增自减操作符 #include<stdio.h> void test(int b) { printf("%d\n", b); } int main() { //代码1 //int a = 10; //int b = a++;//后置++:先使用,后++ 相当于:①b=a;②a=a+1; //printf("%d\n", a);//11 //printf("%d\n", b);//10 //int a = 10; //int b = ++a;//前置++:先++,后使用 相当于:①a=a+1;②b=a; //printf("%d\n", a);//11 //printf("%d\n", b);//11 //代码2 //int a = 10; //printf("%d\n", a++);//10 //printf("%d\n", a);//11 //int a = 10; //printf("%d\n", ++a);//11 //printf("%d\n", a);//11 //代码3 int a = 10; //test(a++);//10 test(++a);//11 return 0; }
注:自增自减给自己带来的副作用:
代码实例:
//代码1
int a = 10;
int b = ++a;//a=11,b=11
//代码2
int a = 10;
int b = a + 1;//a=10,b=11
8.
(类型)强制类型转换
强制类型转换尽量少用,如果开始能把值设定好就不要故意再使用强制类型转换。
如结构体类型你就不能再强制类型转换。
代码实例:
//(类型)强制类型转换 //强制类型转换尽量少用,如果开始能把值设定好就不要故意再使用强制类型转换 //如结构体类型你就不能再强制类型转换 #include<stdio.h> int main() { //代码1 int a = (int)3.6;//强制类型转换,将浮点型3.6强制转换为整形int型 printf("%d\n", a); //代码2 //time的返回值类型是time_t,我们强制转换成无符号型 srand((unsigned int)time(NULL)); return 0; }
6.2 sizeof和数组
代码实例:
//sizeof和数组 #include<stdio.h> void test1(int arr[]) { printf("%d\n", sizeof(arr));//计算的是指针的大小 } void test2(char ch[]) { printf("%d\n", sizeof(ch));//计算的是指针的大小 } int main() { int arr[10] = { 0 }; char ch[10] = { 0 }; printf("%d\n", sizeof(arr));//计算的是数组arr的大小 printf("%d\n", sizeof(ch));//计算的是数组ch的大小 //数组名传参,形参可以写成数组,也可以写成指针 //形参本质上是指针 test1(arr); test2(ch); return 0; }
运行结果:
当用数组名传参的时候,实际上只是把数组的首元素的地址传递过去了。即使写成数组的形式,本质上也是指针。
arr[i] <------> *(arr+i)
&arr[i] <------> arr+i