目录
一、算数操作符
- 除了% 操作符之外,其他的几个操作符可以作用于整数和浮点数。
- 对于/ 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除
法。 - % 操作符的两个操作数必须为整数。返回的是整除之后的余数。char类型也可以用%
1.1 类型转换
C语言中整数除以整数还是整数,直接把小数部分给舍去了,如果两个不同类型的变量进行算数运算,就会触发隐式类型转换,有一个内置的转换规则,短的往长的转(int 和 double 这两个,double长)
- 例子1
#include<stdio.h>
int main()
{
//5 -> int
//5.0 -> double
//5.0f ->float
int a = 5;
int b = 2;
printf("%d\n",a / b);
return 0;
}
- 例子2:
#include<stdio.h>
int main()
{
//5 -> int
//5.0 -> double
//5.0f ->float
double a = 5.0;
int b = 2.0;
printf("%f\n", a / b);
return 0;
}
- 例子3
#include<stdio.h>
int main()
{
//5 -> int
//5.0 -> double
//5.0f ->float
int a = 5;
int b = 2;
printf("%f\n", (double)a / b);
return 0;
}
这里涉及到了——强制类型转换(显示类型转换)
- 例子4——交换函数如果调用的时候没有解引用会发生什么?
void swap(int* x, int* y)
{
int tem = *x;
*x = *y;
*y = tem;
}
int main()
{
int a = 5;
int b = 2;
swap(a, b);
这里将swap(a,b)传入的的int型a,b,会在swap函数中隐式自动转换为int*
函数中就是(int * 5, int *2)下面 *5 和 *2,表示将地址为5的内存进行解引用 和地址为2的内存解引用了——这两块内存我们是不能用的,只有自己申请的内存我们才能使用(自己创建变量才会申请空间~)
编译器没有报错,但是最后程序会崩溃,说明编译器会自动把int隐式转换为 *int,这是一件非常让人讨厌的事情 。代码出错了但是并没有直接给出错误提示。
做出了这样的分类:
如果这个语言越支持隐式类型转换,称为类型越弱
如果这个语言越不支持隐式类型转换,称为类型越强~
一般总结——强类型好于弱类型;静态类型好于动态类型
1.2 运算符优先级
printf("%d\n",2+3*5);
结果是多少
二. 移位运算符
左移操作符:<<
右移操作符:>>
注:移位操作符的操作数只能是整数。
注:对于移位运算符,不要移动负数位,这个是标准未定义的。
例如:
int num = 10;
num>>-1;//error
补充知识(这里说的都是整数):
- 数值在计算机中存储的时候是以二进制存存储的,有三种表示方式,原码、反码和补码。
- 正数的原码、反码和补码是相同的
- 负数的原码、反码和补码需要计算,是不同的
- 数值在计算机中的存储是以补码的形式存放的。
- 源码转换为反码的转换规则是:符号位不变,其他位按位取反。补码就是在反码的基础上+1。
2.1 左移运算符 <<
移位规则:
左边抛弃、右边补0
#include <stdio.h>
int main()
{
int num = 10;//正数原、反补都一样 //00000000 00000000 00000000 00001010
int num2 = num << 1; //00000000 00000000 00000000 00010100
printf("%d %d", num, num2);
return 0;
}
2.2 右移运算符 >>
移位规则:
首先右移运算分两种:
- 逻辑移位
左边用0填充,右边丢弃- 算术移位(平常见到)
左边用原该值的符号位填充,右边丢弃
- -1的补码是32个1
#include <stdio.h>
int main()
{
int a = -1;
//原码:10000000 00000000 00000000 00000001
//反码:11111111 11111111 11111111 11111110
//补码:11111111 11111111 11111111 11111111
//
int b = a >> 1;
printf("%d\n", b);
printf("%d\n", a);
return 0;
}
三、位操作符
- & 对应的二进制位有0,则为0,两个同时为1,才为1
- | 对应的二进制位有1则为1,两个同时为0则为0
- ^ 对应的二进制位:相同为0,相异为1
异或的重要特性:
a^0 -> a
a^a ->0
注:他们的操作数必须是整数
- 练习
#include <stdio.h>
int main()
{
int a = 3; //0011
int b = 4;// 0100
int c = a & b;//同1为1 -》 0000
int d = a | b;//有1为1 -》 0111
int e = a ^ b;//相同为0 ,相异为1 -》 0111
printf("%d %d %d ",c , d, e);
}
- 练习:
编写代码实现:求一个整数存储在内存中的二进制中1的个数。
#include<stdio.h>
//如何计算一个数的二进制中有几个1
//转换为如何取出一个数字的每一位,判断是否为1
int bitoneCount(int num) {
int count = 0;
for (int i = 0; i < 32; i++)
{
if (num & (1 << i)) {
count++;
}
}
return count;
}
- 练习:不能创建临时变量(第三个变量),实现两个整数的交换
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;
}
四、 赋值操作符
注意区分一下赋值和初始化的区别~~
赋值:变量已经有了,进行修改
初始化:第一次创建变量,设置一个值
eg:
五、单目操作符
1.逻辑反操作:!
2.负值:- , 正值:+
3. 取地址:&
4. 间接访问操作符(解引用操作符):*
5.对一个数的二进制按位取反:~
6. 前置、后置- -, 前置、后置++
7.操作数的类型长度(以字节为单位):sizeof
8.(类型) 强制类型转换
5.1 逻辑反
- C语言中0表示假,非0表示真。!:表示逻辑反,
#include<stdio.h>
int main()
{
int flag = 0;
if (!flag) //!flag = 1
{
printf("hello,2025!\n");
}
return 0;
}
- C语言中,C99之后引入了布尔类型。就是用来表示真假的。
头文件是:#include<stdbool.h>
示示例:
#include<stdio.h>
#include<stdbool.h>
int main()
{
bool flag = 0; //按着Ctrl然后单击这个bool就可以跳到头文件里面了
if (!flag)
{
printf("hello,2025!\n");
}
return 0;
}
5.2 正值+ 和负值-
#include <stdio.h>
int main()
{
int num = 10;
printf("%d\n", +num);
printf("%d\n", -num);
return 0;
}
5.3 取地址:&
- 可以取出变量的地址
#include<stdio.h>
int main()
{
int a = 10;
printf("%d %p\n", a, &a);
return 0;
}
- &主要和*配合使用
5.4 解引用*
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a; //把a的地址取出来给指针p
*p = 20; //对指针p解引用就是找到a存放的值,对其进行操作会修改a的值 ,*p等价于 a
printf("%d \n", a);
return 0;
}
5.5 按位取反 ~
#include<stdio.h>
int main()
{
int a = 1; //00000000 00000000 00000000 00000001
int b = ~a; //按位取反得到11111111 11111111 11111111 11111110
printf("%d\n", b); //得到的原码需要变成补码。计算机中存储的都是补码。
// (1)符号位不变,其他位按位取反, 变成 10000000 00000000 00000000 00000001
// (2)再加1得到补码 10000000 00000000 00000000 000000010 最后得到 -2
return 0;
}
- 小练习
#include<stdio.h>
int main()
{
int a = 9;
//00000000 00000000 00000000 00001001
//00000000 00000000 00000000 00010000
//00000000 00000000 00000000 00011001
//把a的二进制第五位改成1
a |= (1 << 4);
printf("%d\n", a);
//把目前a的第5位改回去,由1变成0
//00000000 00000000 00000000 00011001
//00000000 00000000 00000000 00010000 1<<4
//11111111 11111111 11111111 11101111 ~(1<<4)
a &= ~(1 << 4);
printf("%d\n", a);
return 0;
}
5.6 前置后置++ 和前置后置- -
- 前置++和后置++
(1)前置++,先++再赋值:
#include<stdio.h>
int main()
{
int a = 10;
int b = ++a;
printf("a = %d b = %d", a, b);
return 0;
}
(2)后置++,先赋值再++
#include<stdio.h>
int main()
{
int a = 10;
int b = a++;
printf("a = %d b = %d", a, b);
return 0;
}
(3)++ – 带有副作用的 会影响其本身的值
#include<stdio.h>
//++ -- 带有副作用的 会影响其本身的值
int main()
{
//1
int a = 10;
int b = ++a;//b=11 a=11
//2
int a = 10;
int b = a + 1;//b=11 a=10
return 0;
}
- 前置 - - 和后置 - -与前置++ 和后置++原理一致,这里就不赘述了
5.7 sizeof和数组
#include <stdio.h>
void test1(int arr[])//形参这里写的数组,也可以写成指针 int*
{
printf("%zd\n", sizeof(arr));//(2)这里是指针长度,在64位机子上长度是8 在32位机子上长度是4和指针类型无关
}
void test2(char ch[])
{
printf("%zd\n", sizeof(ch));//(4) 这里是指针长度,在64位机子上长度是8 在32位机子上长度是4,和指针类型无关
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%zd\n", sizeof(arr));//(1) 求数组的长度是4*10 = 40
printf("%zd\n", sizeof(ch));//(3)求数组长度是2*10 =20
test1(arr);
test2(ch);
return 0;
}
5.8 强制类型转换
- 在C语言中的使用
int a = 10;
double b = (double)a;
int a = 0x11223344;
short b = (short)a;
//int 4个字节(-21亿~21亿也是0~42亿9千万) short 2个字节(0~65535 也是-32768-32768)
printf("%x\n", b);
这里强制类型转换有一个从int强转为short ,会出现直接截断现象
反过来:
short a = 0xF122;
int b = (int)a;
//int 4个字节(-21亿~21亿也是0~42亿9千万) short 2个字节(0~65535 也是-32768-32768)
printf("%x\n", b);
前面补的是符号位 short 0xF122-> 0000 0000 0000 0000 1111 0001 0010 0010
从short转到int 的时候 转换为 1111 1111 1111 1111 1111 0001 0010 0010
short a = 0x1122;
int b = (int)a;
//int 4个字节(-21亿~21亿也是0~42亿9千万) short 2个字节(0~65535 也是-32768-32768)
printf("%x\n", b);
前面补的是符号位 short 0x1122-> 0000 0000 0000 0000 0001 0001 0010 0010
从short转到int 的时候 转换为 0000 0000 0000 0000 0000 0001 0010 0010
小结:
从长的往短的强转的时候,直接截断高位,
从短的往长的强转的时候,填充的都是符号位
把short强转为int的时候,前面高位两个字节,填充的都是符号位;
把int强转为short的时候,前面高位的两个字节,就直接舍去不要了。
隐式类型转换也是类似的规则
六、关系操作符
=
< >
<=
!= 用于测试“不相等”
== 用于测试“相等”
这就是要在if条件判断的时候,注意不要少写等号。我作为初学者总是会犯这样的低级错误。要多练习!
七、 逻辑操作符
&& 逻辑与 :并且的意思
|| 逻辑或 :或者的意思
7.1 逻辑与:&&
&&操作符,左边有表达式为假,右边就不再计算,整个表达式为假
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++; //这里是&操作符,从左往右,一旦有一个表达式求出0,后面的都不会计算
//这里a++先赋值再给a自增1,已经得到第一个表达式是0 ,后面直接不计算了 i = 0&&不计算&&不计算
// 所以这里a = 1,b c d 都是原来的值
printf(" a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d);
return 0;
}
7.2 逻辑或
||操作符,一旦左边有表达式为真,右边就不在计算,整个表达式为假
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
//这里a先赋值,再给a自增1 ,第二个表达式先给b自增,再给赋值,所以第二个表达式是3 为真,那么第四个表达式不再计算。
//a = 1 ,b = 3, c = 3 d = 4
printf(" a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d);
return 0;
}
八、条件操作符
exp1 ? exp2 : exp3
比较简单,举例求两个数较大值
#include <stdio.h>
int main()
{
int a = 3, b = 4;
printf("%d\n",a > b ? a : b);
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); // a =12 b = 12+1 = 13
printf("%d\n", c);
return 0;
}
十、下标引用、函数调用和结构成员
10.1 [ ] 下标访问操作符
操作数:一个数组名 + 一个索引值
在我们C语言中数组和指针都可以进行[ ]操作。
- 对数组来说[ ] 的有效下标范围 是【0,length-1】,如果下标越界,就会出现未定义行为~
- 对于指针来说 【】范围就不好确定
10.2 ( ) 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
- 函数指针:是一个变量,里面存了个整数,是一个内存地址 ,那我们创建一个函数,是如何到内存中去的?
- 分析一下这个过程:
- 编译:把.c文件变成二进制exe文件
- 运行:双击 exe 文件,让系统执行程序。(exe文件中包含了CPU运行时要执行的指令,以及依赖的数据,原来在.c文件中写的函数就被转换为二进制的机器指令存放到exe文件中)
10.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;
}
十一、 表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
11.1 隐式类型转换
C的整型算术运算总是至少以缺省整型类型的精度来进行的。(缺省就是默认的意思C语言默认是int)
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
- 如何进行整形提升
整形提升是按照变量的数据类型的符号位来提升的
//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//无符号整形提升,高位补0
- 实例:
//实例1
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if(a==0xb6)
printf("a");
if(b==0xb600)
printf("b");
if(c==0xb6000000)
printf("c");
return 0;
}
实例1中的a,b要进行整形提升,但是c不需要整形提升
a,b整形提升之后,变成了负数,所以表达式 a== 0xb6 , b = = 0xb600 的结果是假,但是c不发生整形提升,则表达式 c==0xb6000000 的结果是真
12.2 算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类
型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
12.3 操作符的属性
复杂表达式的求值有三个影响的因素。
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。