前言
由于之前C语言写的博客很零散,写的也不够好,这里我打算重新整理关于C语言的博客,也方便自己复习C语言,C语言专栏看到旧版本的博客建议移步到新版本相关的博客上
----------------我是分割线---------------
一、操作符分类
操作符可以分为一下几类:
- 算术操作符
- 移位操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下标引用、函数调用和结构成员
二、算术操作符
算术操作符有:
+ - * / %
- 加减就不解释了
- 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数
- 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法
- % 操作符的两个操作数必须为整数。返回的是整除之后的余数
- 这几个算术操作符都是由两个操作数的,比如 1 + 2 ,左操作数是 1,右操作数是 2
测试代码:
#include <stdio.h>
int main()
{
//% 取模操作符的两端必须是整数
int a = 7 % 2;//取余,结果是 1
int b = 7 / 2;
printf("%d\n", a);//1
printf("%d\n", b);//3
return 0;
运行结果:
三、移位操作符
3.1 移位操作符简介
在C语言中,移位运算符有双目移位运算符:<<(左移)和>>(右移)
左移运算是将一个二进制位的操作数按指定移动的位数向左移动,移出位被丢弃,右边移出的空位一律补 0
右移运算是将一个二进制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位一律补 0 ,或者补符号位,这由不同的机器而定。在使用补码作为机器数的机器中,正数的符号位为 0 ,负数的符号位为 1
下面且听详解!!!
3.2 原码、反码、补码
在学习移位操作符之前,首先要了解原码、反码、补码
整数在内存中存储的形式是补码的二进制
整数的二进制表示:有3种(原码、反码、补码)(假设在 32 位的平台下)
原码:直接根据数值写出的二进制序列就是原码(32位)
反码:原码的符号位不变,其他位按位取反就是反码
补码:反码加1,就是补码
对于正整数的原码、反码、补码都相同;负数是存放在二进制的补码中,负整数的原码、反码、补码都不相同
例如:1(正整数的原码、反码、补码都相同)
原码:0000000 00000000 00000000 00000001
反码:0000000 00000000 00000000 00000001
补码:0000000 00000000 00000000 00000001
最高位为0 ,也是符号位(最左边的第一位)
例如:-1(负整数的原码、反码、补码都不相同)
原码:10000000 00000000 00000000 00000001
反码:11111111 11111111 11111111 11111110(按位取反,符号位不变)
补码:11111111 11111111 11111111 11111111(反码加1)
最高位为1,也是符号位(最左边的第一位)
简单了解到这里,可以继续学习移位操作符了
3.3 << 左移运算符
直接上代码(只演示负整数的,看完正整数的也会了,正整数的比较简单)
#include<stdio.h>
int main()
{
int a = -5;
int b = a << 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
运行结果是 -10
这是为什么呢,解释如下:
规则:左移运算是将一个二进制位的操作数按指定移动的位数向左移动,移出位被丢弃,右边移出的空位一律补0
简单说就是:左边丢弃,右边补0
先写出 -5 的补码
原码:10000000 00000000 00000000 00000101 (最高位为1)
反码:11111111 11111111 11111111 11111010 (按位取反,符号位不变)
补码:11111111 11111111 11111111 11111011 (反码加1)
补码向左移动一位,左边去掉,右边补0,如图:
此时得到的是补码,还要反推原码才能打印(正数原、反、补相同,补码就是原码,负数要反推回去)
补码:11111111 11111111 11111111 11110110
反码:11111111 11111111 11111111 11110101(补码 -1 得到反码)
原码:10000000 00000000 00000000 00001010(按位取反得到原码)
此时得到的原码就可以转换为十进制打印了,结果就是 -10
注意:%d 意味着打印一个有符号的整数
注:此时的 a 没有改变,依旧是 -5
3.4 >>右移运算符
直接上代码(只演示负整数的,看完正整数的也会了,正整数的比较简单)
#include<stdio.h>
int main()
{
int a = -5;
int b = a >> 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
运行结果是 -3
解释:
右移运算是将一个二进制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位一律补0,或者补符号位,这由不同的机器而定。在使用补码作为机器数的机器中,正数的符号位为 0 ,负数的符号位为 1
简单说就是:(分为 2 种)
- 逻辑右移:左边用0填充,右边丢弃
- 算术右移:左边用原该值的符号位填充,右边丢弃
到底是逻辑右移还是算术右移取决于编译器
我当前使用的编译器 VS2019,它采用的是算术右移
还是一样,先写出 -5 的补码
原码:10000000 00000000 00000000 00000101 (最高位为1)
反码:11111111 11111111 11111111 11111010 (按位取反,符号位不变)
补码:11111111 11111111 11111111 11111011 (反码加1)
补码向右移动一位,右边丢弃,左边补符号位,如图:
此时得到的是补码,还要反推原码才能打印
补码:11111111 11111111 11111111 11111101
反码:11111111 11111111 11111111 11111100(补码 -1 得到反码)
原码:10000000 00000000 00000000 00000011(按位取反得到原码)
此时得到的原码就可以转换为十进制打印了,结果就是 -3
注:此时的 a 没有改变,依旧是 -5,说明左移和右移操作符不改变原操作数
3.5 警告
对于移位运算符,不要移动负数位,这个是标准未定义的;左移和右移运算符只适用于正数,不支持浮点数
例如:
int a = 5;
int b = a >> -1 //error
float = 1.11;
float d = f << 1;//不支持
四、位操作符
位操作符有:
& //按位与
| //按位或
^ //按位异或
//注:他们的操作数必须是整数
4.1 & 按位与
老样子,直接上代码
#include<stdio.h>
int main()
{
int a = 5;
int b = -2;
int c = a & b;
printf("%d\n", c);
return 0;
}
运行结果是:4
解释如下:
& 按位与规则:两个二进制操作数对应位同为 1 ,结果位才为 1 ,其余情况为 0
也是先写出 5,-2 的补码
5 的补码:00000000 00000000 00000000 00000101
-2 的原码:10000000 00000000 00000000 00000010
-2 的反码:11111111 11111111 11111111 11111101
-2 的补码:11111111 11111111 11111111 11111110
按照规则,两个二进制操作数对应位同为 1 ,结果 位 才为 1 ,其余情况为 0
5 的补码:00000000 00000000 00000000 00000101
-2 的补码:11111111 11111111 11111111 11111110
5 & -2 的补码:00000000 00000000 00000000 00000100
此时得到的是补码,还要反推原码才能打印
5 &- 2 的原码:00000000 00000000 00000000 00000100
(正整数原、反、补相同)
此时得到的原码就可以转换为十进制打印了,结果就是 4
4.2 | 按位或
老样子,先上代码
#include<stdio.h>
int main()
{
int a = 5;
int b = -2;
int c = a | b;
printf("%d\n", c);
return 0;
}
运行结果是:-1
解释如下:
| 按位或规则:两个二进制操作数对应位只要有一个为 1 ,结果 位 就为 1 ,其余情况为 0
也是先写出 5,-2 的补码
5 的补码:00000000 00000000 00000000 00000101
-2 的原码:10000000 00000000 00000000 00000010
-2 的反码:11111111 11111111 11111111 11111101
-2 的补码:11111111 11111111 11111111 11111110
也是按照规则,两个二进制操作数对应位只要有一个为 1 ,结果 位 就为 1 ,其余情况为 0
5 的补码:00000000 00000000 00000000 00000101
-2 的补码:11111111 11111111 11111111 11111110
5 | -2 的补码:11111111 11111111 11111111 11111111
此时得到的是补码,还要反推原码才能打印
5 | -2 的补码:11111111 11111111 11111111 11111111
5 | -2 的反码:11111111 11111111 11111111 11111110
5 | -2 的原码:10000000 00000000 00000000 00000001
此时得到的原码就可以转换为十进制打印了,结果就是 -1
4.3 ^ 按位异或
老样子,先上代码
#include<stdio.h>
int main()
{
int a = 5;
int b = -2;
int c = a ^ b;
printf("%d\n", c);
return 0;
}
运行结果是:-5
原因解释如下:
^ 按位异或 规则:两个二进制操作数对应 位 相同为 0 ,不同为 1
也是先写出 5,-2 的补码
5 的补码:00000000 00000000 00000000 00000101
-2 的原码:10000000 00000000 00000000 00000010
-2 的反码:11111111 11111111 11111111 11111101
-2 的补码:11111111 11111111 11111111 11111110
也是按照规则来,两个二进制操作数对应 位 相同为 0 ,不同为 1
5 的补码:00000000 00000000 00000000 00000101
-2 的补码:11111111 11111111 11111111 11111110
5 ^ -2 的补码:11111111 11111111 11111111 11111011
此时得到的是补码,还要反推原码才能打印
5 ^ -2 的补码:11111111 11111111 11111111 11111011
5 ^ -2 的反码:11111111 11111111 11111111 11111010
5 ^ -2 的原码:10000000 00000000 00000000 00000101
此时得到的原码就可以转换为十进制打印了,结果就是 -5
----------------我是分割线---------------
五、赋值操作符
赋值操作符为:=
赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值
比如:
int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值
赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y+1;//连续赋值
但是这种代码不建议写,别人看了增加头发负担量
建议这么写:
x = y+1;
a = x;
赋值操作符也可以写成复合赋值符
- +=
- -=
- *=
- /=
- %=
- >>=
- <<=
- &=
- |=
- ^=
比如:
int x = 10;
x = x+10;
x += 10;//复合赋值,等价于 x = x+10
//其他运算符一样的道理,这样写更加简洁
----------------我是分割线---------------
六、单目操作符
6.1 单目操作符介绍
单目操作符意味着只有一个操作数
! 逻辑反操作符
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
6.2 ! 逻辑反操作符
int flag = 3;
//flag为真,进入if
if (flag)
{}
//flag为假,进入if
if(!flag)
{}
C语言中0表示假,非0表示真
6.3 - +(负值、正值)
//int a = -10;//负号
int b = -a;//负负得正
int c = +1;//正号一般不写出来
6.4 ~ 取反
直接上代码
#include<stdio.h>
int main()
{
int a = 5;
int c = ~a;
printf("%d\n", c);
return 0;
}
运行结果是:-6
原因解释如下:
~ 取反规则:一个二进制操作数,对应位为 0 ,结果位为 1 ;对应位为 1 ,结果位为 0(作用是将每位二进制取反)
先写出 5 的补码
5 的补码:00000000 00000000 00000000 00000101
按照规则,对应位为 0 ,结果位为 1 ;对应位为1,结果位为 0
5 的补码:00000000 00000000 00000000 00000101
取反:11111111 11111111 11111111 11111010
此时得到的是补码,还要反推原码才能打印
取反后(补码):11111111 11111111 11111111 11111010
反码:11111111 11111111 11111111 11111001
原码:10000000 00000000 00000000 00000110
此时得到的原码就可以转换为十进制打印了,结果就是 -6
6.5 -- 运算符和 ++ 运算符
-- 分前置--和后置--,++ 也分前置++和后置++
前置++,先++,后使用;后置++,先使用,再++
前置--,先--,后使用; 后置--,先使用,再--
测试代码,-- 同样道理
#include<stdio.h>
int main()
{
int a = 1, b = 1;
//前置++,先++,后使用
//后置++,先使用,再++
printf("前置:%d\n", ++a);
printf("后置:%d\n", b++);
return 0;
}
运行结果
6.6 & 取地址和 * 解引用
& 取地址操作符用于取变量的起始地址;* 叫做间接访问操作符,也叫解引用操作符,一般都是用于解引用指针,拿到指针所指向的内容
测试代码:
#include<stdio.h>
int main()
{
int a = 10;
printf("%p\n", &a);//&取地址, %p是打印地址
//取出的地址是变量的起始地址
int* p = &a;//把 a的地址拿给指针p,指针p 可以间接使用a
*p = 20;//p 是a的地址,*p就拿到了地址的内容,即 a的值10,指针p间接修改了a的内容
printf("%d\n", a);
return 0;
}
运行结果
指针后面讲,这里简单了解
6.7 sizeof 操作符
sizeof 操作符,可以求变量(类型)所占空间的大小,单位是字节
注意:sizeof是操作符,不是函数,strlen是库函数,是用来求字符串长度
测试代码:
#include <stdio.h>
int main()
{
int a = 1;
int arr[10] = { 0 };
printf("%d\n", sizeof(a));//可以计算变量的大小 4
printf("%d\n", sizeof(arr));//可以计算数组的大小,4*10
printf("%d\n", sizeof(int));//可以计算变量类型的大小 4
printf("%d\n", sizeof(char));//可以计算变量类型的大小 1
printf("%d\n", sizeof a);//这样写行不行?ok
//printf("%d\n", sizeof int);//这样写行不行?error
return 0;
}
运行结果
来看看 sizeof 和 数组的一个面试题
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//(2)
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//(4)
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr));//(1)
printf("%d\n", sizeof(ch));//(3)
test1(arr);
test2(ch);
return 0;
}
问:(1)(2)两个地方分别输出多少?(3)(4)两个地方分别输出多少?
(2)(4)本质上是指针,指针在 32位平台下是 4
运行结果
6.8 (类型) 强制类型转换
测试代码:
#include <stdio.h>
int main()
{
int a = (int)3.14;//把 3.14强制类型转换成 int
printf("%d\n", a);
return 0;
}
运行结果
七、关系操作符
关系操作符有:
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
这些我们都是很熟悉,不再解释了,要注意一点:在编程的过程中 == 和 = 不小心写错,导致的错误
----------------我是分割线---------------
八、 逻辑操作符
逻辑操作符有:
&& 逻辑与
|| 逻辑或
区分逻辑与和按位与
1&2----->0
1&&2---->1
区分逻辑或和按位或
1|2----->3
1||2---->1
看一道面试题
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf(" a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d);
return 0;
}
运行结果
说明:&& 左边为假,右边就不计算了
继续看
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++||++b||d++;
printf(" a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d);
return 0;
}
运行结果
说明:|| 左边为真,右边就不计算了
----------------我是分割线---------------
九、条件操作符
条件操作符也叫三目操作符
exp1 ? exp2 : exp3
表达式1 为真则取 表达式2 的结果,表达式1 为假则取 表达式3 的结果
测试代码
#include <stdio.h>
int main()
{
int a = 3;
int b = 0;
int max = (a > b ? a : b);
printf("%d\n", max);
/*if (a > 5)
b = 3;
else
b = -3;*/
//int max = (a > b ? a : b) 等价于上面的if语句
return 0;
}
运行结果
十、逗号表达式
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果
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("c=%d\n", c);
return 0;
}
运行结果
再如:
//代码2
a = get_val();
count_val(a);
while (a > 0)
{
//代码
a = get_val();
count_val(a);
}
如果使用逗号表达式,改写:
while (a = get_val(), count_val(a), a > 0)
{
//代码
}
//代码3
if (a =b + 1, c=a / 2, d > 0)//判断条件为整个表达式的结果是最后一个表达式的结果
----------------我是分割线---------------
十一、下标引用、函数调用和结构成员
11.1 [ ] 下标引用操作符
操作数:一个数组名 + 一个索引值
nt arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符
[ ]的两个操作数是arr和9
//arr[9] --> *(arr+9) --> *(9+arr) --> 7[arr]
//arr[9] 也可以写成 9[arr],但是创建数组就不可以
//arr是数组首元素的地址
//arr+9就是跳过9个元素,指向了第10个元素
//*(arr+9) 就是第8个元素
11.2 ( ) 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
#include <stdio.h>
void test1()
{
printf("hello\n");
}
void test2(char* str)
{
printf("%s\n", str);
}
int main()
{
test1(); //实用()作为函数调用操作符
test2("hello");//实用()作为函数调用操作符
return 0;
}
11.3 访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名
直接看代码
#include <stdio.h>
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
};
void set_age1(struct Stu stu)
{
stu.age = 18;
}
void set_age2(struct Stu* pStu)
{
pStu->age = 18;//结构成员访问
}
int main()
{
struct Stu stu;
struct Stu* pStu = &stu;//结构成员访问
stu.age = 20;// . 结构成员访问
set_age1(stu);
pStu->age = 20;// -> 结构成员访问
set_age2(pStu);
return 0;
}
----------------我是分割线---------------
文章就到这里,下篇即将更新