操作符目录
1. 算术操作符
+ - * / %
-
除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
-
对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
-
% 操作符的两个操作数必须为整数。返回的是整除之后的余数。
2. 移位操作符
2.1 整数二进制的表现形式
- 有三种表现形式:原码、反码、补码
(正整数的原码、反码、补码都是相同的)
(负整数的原码、反码、补码是要计算的) - 原码:根据正负直接写出的二进制序列就是原码
(不管正整数还是负整数都可以直接写出二进制原码) - 反码:原码的符号位(0为正1为负)不变,其他的按位取反,得到的就是反码
- 补码:反码+1就是补码
(整数在内存中存储的是补码)
(计算机也是用补码计算的) - 1个整数是4个字节 = 32 bit位
正整数:10
原码:00000000000000000000000000001010
反码:00000000000000000000000000001010
补码:00000000000000000000000000001010
负整数:-10
原码:10000000000000000000000000001010
反码:11111111111111111111111111110101
补码:11111111111111111111111111110110
2.2 ‘’<<''左移操作符
<< —左移操作
- 算术左移:
将一个二进制数向左移动指定的位数,并在最低位补0。在算术左移中,符号位不变,因此可以保持负数的符号不变 - 逻辑左移:
将一个二进制数向左移动指定的位数,并在最低位补0。在逻辑左移中,符号位也会被移动,因此可能会改变负数的符号 - 左移 << 每移一位相当于乘2
警告⚠ :
对于移位运算符,不要移动负数位,这个是标准未定义的。
2.3 ">>''右移操作符
”>>"—右移操作符
- 算术右移:
将一个二进制数向右移动指定的位数,并在最高位补符号位。在算术右移中,符号位不变,因此可以保持负数的符号不变 - 逻辑右移:
逻辑右移是指将一个二进制数向右移动指定的位数,并在最高位补0。在逻辑右移中,符号位也会被移动,因此可能会改变负数的符号 - C语言中没有明月规定到时算术右(左)还是逻辑右(左),一般编译器上采用的是算术右(左)
- 右移 >> 每一移位相当于正整数除2取整(负数则取整后在减一)
警告⚠ :
对于移位运算符,不要移动负数位,这个是标准未定义的。
3. 位操作符
3.1 ‘’&''按位与
& —按位与
- 按位与:对应二进制有0则为0,俩个同时为1则为1
以上俩张图只有在计算的时候,要注意操作数的正负,原码、反码、补码和正负整数之间的关系
3.2 ‘’|''按位或
| —按位或
- 按位或:对应的二进制位有1则为1,俩个同时为0则为0
3.3 "^''按位异或
^ —按位异或
- 按位异或:对应的二进制位相同为0,相异为1
- 这里利用一个题目来促进理解:
定义俩个整型变量,并且交换俩个数的值
//这里只显示main()部分的函数内容
int main()//方法一
{
int a = 1;
int b = 2;
int tem;
//换值
tem = a;
a = b;
b = a;
printf("a=%d\n",a);
printf("b=%d\n",b);
return 0;
}
//这里只显示main()部分的函数内容
int main()//方法二
{
int a = 1;
int b = 2;
//换值
a = a + b;//将a+b(3)的值赋给a(3)
b = a - b;//用a(3)的值减b(2),得到1,再赋值给b
a = a - b;//用a(3)的值减b(1),得到2,再赋值给a
printf("a=%d\n",a);
printf("b=%d\n",b);
return 0 ;
}
这里附上方法二的结果显示:
接下来要用的就是我们刚刚学的这个"^"按位异或操作符的用法:
//这里只显示main()部分的函数内容
int main()//方法三
{
int a = 1;
int b = 2;
//换值
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a=%d\n",a);
printf("b=%d\n",b);
return 0;
}
你可能看到一个这样的代码很蒙,别急先看代码运行结果:
//这里只拿换值部分的代码来解读
值: 3 1 2
代码: a = a ^ b
二进制 :011 001 010
//根据''^''的使用规则,可以知道a ^ b == 001 ^ 010 == 011 == 3
————————————————————————————————————————————————————————————————————
值: 1 3 2
代码: b = a ^ b
二进制 :001 011 010
//根据''^''的使用规则,可以知道a ^ b == 011 ^ 010 == 001 == 1
————————————————————————————————————————————————————————————————————
值: 2 3 1
代码: a = a ^ b
二进制 :010 011 001
//根据''^''的使用规则,可以知道a ^ b == 011 ^ 001 == 010 == 2
其实"^"按位异或在这个代码中就是一个规律:
c = a ^ b
a = c ^ b
b = a ^ c
这样理解的话是不是就简单多了呢?不急接下来还有"^"按位异或的特性没说呢
代码: b == a ^ a ^ b
二进制: | 001 001 010
过程: | |_______________| |
| | |
| 000 |
| |______________________|
| |
|——————————————————————————————————010
———————————————————————————————————————————————————————————————————
代码: b == a ^ b ^ a
二进制: | 001 010 001
过程: | |_______________| |
| | |
| 011 |
| |______________________|
| |
|——————————————————————————————————010
- 根据这个流程图,应该就可以看出:
- "^"按位异或是支持交换律的
- 俩个相等的值按位异或的话得到的一定是0
- 任何数按位异或0得到都是那个数的本身值
4. 赋值操作符
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
以上就是c语言中所有的赋值操作符了,具体的使用也是非常的简单:
a += b / a = a + b
a -= b / a = a - b
a *= b / a = a * b
a /= b / a = a / b
a %= b / a = a % b
a >>= b / a = a >> b
a <<= b / a = a << b
a &= b / a = a & b
a |= b / a = a | b
a ^= b / a = a ^ b
以上就是赋值操作符的用法!
5. 单目操作符
5.1 单目操作符介绍
- 单目操作符就是只有一个操作数的控制的操作符
5.1.1 "!''逻辑反操作符
!—逻辑反操作符
- 逻辑反操作符:它的作用是将一个布尔值取反,即将 true 变为 false,将 false 变为 true。(如果一下子不知道是布尔值什么意思,别急你暂时就先理解成反义词的概念,本来是"大"加个 “!” 就变成"小"的意思了,后面的小结我会解释布尔值、false、true的意思)
- 来个代码促进理解:
#include<stdio.h>
int main()
{
int num = 0;
do
{
num++;
} while (!num);//num=0,!num就不为0,反而当num不为0时,!num就为0
//应为在C语言中0为假,非0为真
printf("num = %d\n", num);
return 0;
}
代码结果为:
num = 1
- 一个代码不够那我们就再来一个:
#include<stdio.h>
int main()
{
int num = 0;
while (num != 10)//当num不等于10的时候为真就进入循环,否则退出
{
num++;
}
printf("num = %d\n", num);
return 0;
}
代码结果为:
num = 10
这样俩个例子我觉得应该对"!"反逻辑操作符应该有个较为具体的认识了
5.1.2 ‘’-‘’、‘’+''负值、正值
- +
就和数学中的一样,加减的的操作符
5.1.3 ‘’&''取地址符
& -----取地址符
- 取地址符:配合scanf函数、指针、数组传参…的使用,向计算机获取变量的内存地址
- 配合scanf函数的使用:
#include<stdio.h>
int main()
{
int a;
scanf("%d", &a);
printf("a = %d\n", a);
return 0;
}
代码结果为:
5
a = 5
- 指针的使用:
#include<stdio.h>
int main()
{
int a;
int* p;//这是定义指针变量的,指针变量是专门存放地址的
scanf_s("%d", &a);
p = &a;//将a的地址取出来,存放在指针变量p中
printf("a = %d\n", a);
printf("&a = %p\n", p);
printf("p = %p\n", p);
return 0;
}
代码结果为:
5
a = 5
&a = 00000049984FF934
p = 00000049984FF934
- 数组传参:
#include<stdio.h>
int add(int arr[], int sz)//自定义函数
{
int i;
for (i = 0; i < sz; i++)
{
arr[i] = sz;
}
}
int main()
{
int i;
int arr[] = { '1','2','3','4','5','6','7','8','8','10' };
int sz = sizeof(arr) / sizeof(arr[0]);//sizeo是一个操作符,计算数组的内存大小
add(arr, sz);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
代码结果为:
10 10 10 10 10 10 10 10 10 10 10
5.1.4 ‘’~''对一个数的二进制(补码)按位取反
~ —对一个数的二进制(补码)按位取反
看代码:
#include<stdio.h>
int main()
{
int a = 0;
printf("%d", ~a);
return 0;
}
代码结果为:
-1
解析:
a = 0
补码:00000000000000000000000000000000
按位取反
补码:11111111111111111111111111111111
反码:11111111111111111111111111111110
原码:10000000000000000000000000000001
所以 ~a = -1
5.1.5 ‘’–‘‘前置、后置"–"、’’++''前置、后置"++"
--a 前置--
a-- 后置--
++a 前置++
a++ 后置++
- 前置与后置的区别:一个是先计算后–/++,一个是先–/++后计算
- 前置++看代码:
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
b = ++a;//前置++
printf("a=%d\n", a);
printf("b=%d\n", b);
return 0;
}
代码结果为:
a=1
b=1
- 后置++看代码:
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
b = a++;//后置++
printf("a=%d\n", a);
printf("b=%d\n", b);
return 0;
}
代码结果为:
a=1
b=0
- 根据上面俩个代码应该就可以看出前置与后置的区别了
5.1.6"*''间接访问操作符(解引用操作符)
* 解引用操作符
- 解引用操作符:搭配指针变量,创建指针变量时、访问指针变量中存储的内存地址所对应的值
*看代码:
#include<stdio.h>
int main()
{
int a = 9;
int* p;
p = &a;
printf("p=%p\n", p);
printf("a=%d\n", a);
printf("*p=%d\n", *p);
return 0;
}
代码结果为:
p=00000010DDAFFCF4
a=9
*p=9
- int*——>定义一个指针类型的变量
- int** ——>定义一个二级指针类型的变量
int* p——>创建(一级)指针变量p
**p——>"*"可以访问p中存储的地址,并且取出该地址中存的数据
5.1.7 "(类型)''强制类型转换
(类型)强制转换类型
看代码:
#include<stdio.h>
int main()
{
double a = 3.14;
printf("a=%d", (int)a);
return 0;
}
代码结果为:
a=3
- 要注意的是强制转换类型的使用格式是:
(变量名的前方)
(int)a
(char)a
(float)a
都是在变量前面加括号并且放入你要强制转换的类型名称,但是你要注意的时候,这种强制类型会使数据发生截断:
#include<stdio.h>
int main()
{
double a = 3.14;
printf("double a=%fl",a);
printf("a=%f", (int)a);
return 0;
}
代码结果为:
double a=3.140000l
a=3
可以看到a的数据在转换成int类型以后,就丢失了小数点后面的数值,这就是截断。
所有,非必要不使用强制转化类型
5.2 sizeof 和 数组
sizeof 操作符
数组 == 数组名 + [ ]
- sizeof 操作符:计算的是变量或类型所占用的内存字节数(包含’\0’)
(函数strlen():计算的是字符串中字符的个数(不包含’\0’)) - 数组:数组名 + [ ]
6. 关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
7. 逻辑操作符
7.1 "&&''逻辑与
&& 逻辑与
- 逻辑与:一假为假,两真为真
表达式1 && 表达式2
只要表达式1和表达式2中有一个为假,那么(表达式1 && 表达式2) 返还一个0(假)
- 看代码:
#include<stdio.h>
int main()
{
int a = 0;
int b = 1;
printf("%d", a && b);
return 0;
}
代码结果为:
0
在计算机中0表示假,非0表示真
- 但在真正的用途里面大多是这样的:
#include<stdio.h>
int main()
{
int a = 1;
int b = 10;
if (a && b)
{
printf("Hello !\n");
}
a--;
if (a && b)
{
printf("Hello Hi!\n");
}
return 0;
}
代码结果为:
Hello !
如果还不懂你还可以再看个代码:
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = 3;
int d = 4;
if (a > b && c > d)//假 && 假
{
printf("Hello !\n(假 && 假)");
}
if (a < b && c < d)//真 && 真
{
printf("Hello !(真 && 真)\n");
}
if (a < b && c > b)//真 && 假
{
printf("Hello !(真 && 假)\n");
}
return 0;
}
代码结果为:
Hello !(真 && 真)
&& 逻辑与操作符俩边但凡有一个是假的,那么最终的返还值就是0,必须俩边都是真的才能返回一个1
7.2 ‘’||''逻辑或
|| 逻辑或
- 逻辑或:一真为真,两假为假
表达式1 || 表达式2
只要表达式1和表达式2中有一个为真,那么(表达式1 || 表达式2)返回一个1(真)
- 看代码:
#include<stdio.h>
int main()
{
int a = 0;
int b = 1;
printf("%d", a && b);
return 0;
}
代码结果为:
1
- 但在真正的用途里面大多是这样的:
#inlcude<stdio.h>
int main()
{
int a = 1;
int b = 0;
if (a || b)
{
printf("Hello !\n");
}
b++;// a == 1、b == 1
if (a || b)
{
printf("Hello !!\n");
}
a--;// a == 0
b -= 1;//b == 0
if (a || b)
{
printf("Hello !!!\n");
}
return 0;
}
代码结果为:
Hello !
Hello !!
7.3 区分’‘&&’'与"&“、”||“与”|"的作用
- 其实就是
- "&&"俩边表达式有一个为假的,那么最后整个返回值就是0
- "||"俩边表达式有一个为真的,那么最后一个返回值就是1
8. 条件操作符(三目操作符)
exp1? exp2 : exp3
- 这个操作符什么意思呢?
- 就是说,如果表达式1为真,那么就输出表达式2,如果为假那就输出表示式3
- 我们就拿一个比大小的代码来看:
#include<stdio.h>
int main()
{
int a = 1;
int b = 0;
int c = a > b ? a : b;//用一个变量来接受该操作符的返回值
printf("c = %d", c);
return 0;
}
代码结果为:
a = 1
9. 逗号表达式
exp1, exp2, exp3, exp4
- 逗号表达式:
- 就是用逗号隔开的多个表达式。
- 从左向右依次执行。
- 整个表达式的结果是最后一个表达式的结果。
- 看代码:
#inlcude <stdio.h>
int main()
{
int add[] = { 1, 2, 3, 4, 5, 6,(7, 8), 9 };
int i;
int sz = sizeof(add) / sizeof(add[0]);
for (i = 0; i < sz; i++)
{
printf("第%d个元素为:%d\n", i + 1, add[i]);
}
return 0;
}
代码结果为:
第1个元素为:1
第2个元素为:2
第3个元素为:3
第4个元素为:4
第5个元素为:5
第6个元素为:6
第7个元素为:8
第8个元素为:9
- 我们可以看到的是,该数组中下标为6的元素在一个括号中有俩个数,最后代码也是显示下标为6的元素是8,这也说明了逗号表达式的一个运算规则就是,“整个表达式的结果是最后一个表达式的结果”
10. 下标引用、函数调用和结构成员
- 下标引用操作符
[ ]
- 下标引用操作符就是调用数组元素的操作符
操作数:数组名 +一个索引值
#include<stdio.h>
int main()
{
int add[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
printf("%d\n", add[3]);//数组名(add) + 索引值(3)
return 0;
}
- 函数调用操作符
( )
- 顾名思义就是配合调用函数的操作符
- 接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
- 这里拿printf函数举例子:
#include<stdio.h>
int main()
{
int add[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
printf("%d\n", add[3]);
return 0;
}
代码结果为:
4
- 在"printf()"函数调用操作符中,不光只有一个操作数,缺一个都不能完成最后的效果
- 访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名
这里就要讲一个结构体函数struct了,看代码:
#include<stdio.h>
struct book
{
char name[20];//书名
char author[20];//作者名字
int price;//价格
};
int main()
{
struct book s;
scanf("%s", s.name);
scanf("%s", &s.author);
scanf("%d", &s.price);
printf("书名:%s\n", s.name);
printf("作者名:%s\n", s.author);
printf("价格:%d\n", s.price);
return 0;
}
输入值为:
大明 小明 66
代码结果为:
书名:大明
作者名:小明
价格:66
- struct函数就是自定义一个结构体类型(类似 int char short float doudle,但是他更偏向于复合型的类型结构,和C语言自带的那些结构类型的单一性不全一样),然后在里面创建结构,将一些具体的有关联的数据联系在一起
这" . "的用法 - 接下来介绍"->"的用法:
#include<stdio.h>
struct book
{
char name[20];//书名
char author[20];//作者名字
int price;//价格
};
void get_s(struct book* p)
{
scanf("%s%s%d", p->name, p->author, &p->price);
//这里的name和author是数组名,所以不需要加取地址符号,数组名本身就是地址
}
int main()
{
struct book s;//
struct book* p = &s;
get_s(p);
printf("%s %s %d", p->name, p->author, p->price);
return 0;
}
代码结果为:
大明 小明 55
这个的访问方式就是倚靠地址的来访问,俩种方式都可以,自是使用的场景和方式不一样
11. 小结
10.1 移位操作符 和 位操作符使用小技巧:
1.如果我们要求一个二进制数的最后一位,我们应该怎么做?
首先我们知道我们要的是二进制数的最后一位,二进制数的最后一位无非要么是0要么是1,那怎么才能把它提取出来呢?我们要保证最后一位是0就反馈0,是1 就反馈1,我们刚刚有学过"&“、”|“、”^“运算符的用法和他们的使用规则,那么”&“的规则就是“有0则为0,俩个同时为1则是1”,当然我们还要排除其他的二进制有效位的干扰,那么我们给"那个数& 1"是不是就可以了,而且也只有”& 1"可以 ,接下来看代码
不论是正还是负,都不会影响求值方式的改变
- 如果我们要求一个数a的二进制数的任意一位,我们应该怎么做?
首先,我刚刚前面说到的那个计算方法不变,但是如果我现在要求的是a的二进制数的第5位(从右往左数),那我应该怎么办?我现在的方法只能求最后一位,或者说我一下子也想不出这样的一个数b的二进制是刚好在我要的第5位为1,其他都为0
a = 32
00000000000000000000000000100000
b = 32
00000000000000000000000000100000
//这个简单整个二进制就一个1
a = 25
00000000000000000000000000011001
b = ?... b = 16
//一下子看不出来吧...如果求的是第10位、第20位呢?
//而且你看的还是二进制,如果还没有二进制呢,那是不是太浪费时间了
00000000000000000000000000010000
所以,那有没有一种好方法。可以让我我要的求的那一位直接移到最后一位来,这不">>"右移操作符不就派上用场啦,我们可以让a的二进制右移四位,这样原本的第五位就到了最后一位,是不是就可以直接用我们之前的那个方法了
是不是很简单~
- “<<”、">>"这俩个操作符会不会改变操作数原本的值?
接下来给你看一个代码:
//这里只给一个main()函数的部分
int main()
{
int a = 16;//00000000000000000000000000010000
int b = a >> 4;//00000000000000000000000000000001
printf("a=%d\n",a);
printf("b=%d\n",b);
return 0;
}
OK,看执行结果:
从这里可以看出,">>“和”<<"这俩操作符只在计算的过程会临时改变a的二进制序列,当把改变后的二进制序列给另一个变量的时候,原先备份修改的a二进制序列的那块内存就会销毁,但因为他只是一个备份,所以不会影响到a的二进制序列
10.2 —>布尔值、false、true
10.3 关于操作符"&&“和”||"的短路现象
- "&&“和”||"的使用规则可以看上面的介绍
- '‘短路’'顾名思义就是一个事物在某件事情上进行到一半终止,未完成全部过程
- 那么"&&“和”||"是怎么短路的呢
- 看代码:
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = 3;
int sum = 0;
sum = a-- && ++b && c++;
printf("1 a=%d b=%d c=%d sum=%d\n", a, b, c, sum);
a = 1;
b = 2;
c = 3;
sum = 0;
sum = --a && ++b && c++;
printf("2 a=%d b=%d c=%d sum=%d\n", a, b, c, sum);
a = 1;
b = 2;
c = 3;
sum = 0;
sum = a-- || ++b || c++;
printf("3 a=%d b=%d c=%d sum=%d\n", a, b, c, sum);
a = 1;
b = 2;
c = 3;
sum = 0;
sum = --a || ++b || c++;
printf("4 a=%d b=%d c=%d sum=%d\n", a, b, c, sum);
return 0;
}
代码结果为:
1 a=0 b=3 c=4 sum=1
2 a=0 b=2 c=3 sum=0
3 a=0 b=2 c=3 sum=1
4 a=0 b=3 c=3 sum=1
- 我可以看到 1 和 2 的代码唯一区别就是"a–“和”–a",我们知道前置’‘–a"是先减减在计算的,而’‘a–’‘是先计算在减减的,所以这样也导致了一个一开始就是0一个一开始就是2,那么一开始为0的那个也就是2的代码,我们看到计算机在检测到’‘–a’‘为0时,后面的也就不会再运算了,应为’‘&&’'的运算规制就是一假为假
- 接下来我们主要看 4 的代码,首先我们知道"||"的运算规则就是一真为真,所以一旦第一个开头元素为真的话,那么后面计算机也不会再浪费算力,在进行计算