操作符
单目操作符
单目操作符只有一个操作数。+、-等有左操作数和右操作数,叫做双目操作符。 单目赋值符包括如下:
! 逻辑反操作(真变为假,假变为真)
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(单位:字节)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
!逻辑反操作的使用:
#include <stdio.h>
int main()
{
int a = 5;
if (!a) //a为假(a=0时),进入循环(!a为真)
{
printf("呵呵");
}
}
&取地址的使用:
#include <stdio.h>
int main()
{
int a = 5;
int* p = &a; //&取地址,使用指针来接收
*p = 20; //*解引用,通过p的值,找到*p指向的对象,*p就是a。
return 0;
}
sizeof计算操作数的类型:
#include <stdio.h>
int main()
{
int a = 5;
char b = 'c';
char* p = &b;
int arr[10] = { 0 };
//sizeof计算变量所占内存空间大小,单位是字节。
printf("%d\n", sizeof(a)); //4(整型变量)
printf("%d\n", sizeof(int)); //4
printf("%d\n", sizeof(b)); //1(b中有一个字符)
printf("%d\n", sizeof(char)); //1
printf("%d\n", sizeof(p)); //8(指针大小为4(32位平台)或8(64位平台))
printf("%d\n", sizeof(char*)); //8
printf("%d\n", sizeof(arr)); //40(数组中有10个元素,每一个元素是一个整型(4个字节))
printf("%d\n", sizeof(int[10])); //40
return 0;
}
::: tip 对于数组来说,去掉数组名,剩下的就是类型。 上述代码中第16行,int arr[10]的类型是int [10]。 :::
::: warning 使用sizeof计算变量所占内存空间时,可以通过类型来计算大小,也可以通过变量名直接计算大小。 :::
#include <stdio.h>
int main()
{
short s = 0; //short短整型,占据两个字节的空间
int a = 10;
printf("%d\n", sizeof(s = a + 5));
printf("%d", s); //sizeof操作符处的表达式不会真实进行运算
return 0;
}
2
0
上述代码中,a和5相加放进s中,无论a是什么类型,相加过后都要按照s的类型存储。 sizeof计算表达式的大小就是s的大小,就是s的类型的大小,就是短整型2个字节。
~按位取反将0变为1,将1变为0。按位取反后也是补码。 printf打印的是源码。 需要从补码求得源码。 ::: tip 源码取反+1=补码。 补码取反-1=源码。 取反时符号位不变,其余位置取反。 符号位1为负,0为正。 :::
#include <stdio.h>
int main()
{
int a = 0;
//~按(二进制)位取反
//00000000000000000000000000000000 - 0的二进制数:
//11111111111111111111111111111111 - 按位取反(补码)
//11111111111111111111111111111110 - 反码(补码-1)
//10000000000000000000000000000001 - 源码(取反)
printf("%d",~a);
return 0;
}
将a的二进制位的第三位改为1:
#include <stdio.h>
int main()
{
int a = 11;
a = a | (1 << 2);
printf("a = %d", a);
//00000000000000000000000000001011 -
//00000000000000000000000000000100 - 在该位按位或1,其他位或0,如下:
//00000000000000000000000000000001 - 1
//00000000000000000000000000000100 - 1 << 2
//00000000000000000000000000001111 - a = 15
return 0;
}
将a的二进制位的第三位由1改为0:
#include <stdio.h>
int main()
{
int a = 11;
a = a | (1 << 2);
printf("a1 = %d\n", a);
//00000000000000000000000000001011 - 将a的二进制位的第三位改为1
//00000000000000000000000000000100 - 在该位按位或1,其他位或0,如下:
//00000000000000000000000000000001 - 1
//00000000000000000000000000000100 - 1 << 2
//00000000000000000000000000001111 - a = 15
//00000000000000000000000000001111 - 将a的二进制位的第三位改回0
//11111111111111111111111111111011 - 第三位按位与0,其他位与1,如下:
//00000000000000000000000000000100 - 1 << 2
//11111111111111111111111111111011 - 取反
//00000000000000000000000000001011 - a = 11
a = a & (~(1 << 2));
printf("a2 = %d\n", a);
return 0;
}
前置++和后置++:
#include <stdio.h>
int main()
{
int a = 11;
printf("a1 = %d\n", ++a); //前置++,先++再打印
printf("a2 = %d\n", a++); //后置++,先打印再++
return 0;
}
强制类型转换:
int a = 3.14; //程序提示“从double转换成int可能会丢失数据”
int a = (int)3.14; //强制将类型转换成int,不会报错
练习
::: tip char类型的sizeof是1个字节。 short类型的sizeof是2个字节。 int类型的sizeof是4个字节。 long类型的sizeof是8个字节。 ::: 主函数中数组名传参传过去的是首元素的地址,自定义函数使用指针接收这个地址,指针的大小是4个字节或8个字节(x86是4字节,x64是8字节)。
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr)); //8
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch)); //8
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr)); //40
printf("%d\n", sizeof(ch)); //10
test1(arr);
test2(ch);
return 0;
}
40
10
8
8
关系操作符
> >= < <= != ==
逻辑操作符
&& 逻辑与
|| 逻辑或
::: warning 按位与和按位或是拿二进制对应的位进行与和或操作。 逻辑与和逻辑或关注数字本身的真假。 ::: ::: tip &&逻辑与 同真为真,一假为假
||逻辑或 同假为假,一真为真
:::
#include <stdio.h>
int main()
{
int a = 0;
int b = 3;
int c = 0;
int d = 5;
printf("%d", c = a || b); //1
printf("%d", d = b && c); //1
return 0;
}
逻辑与中如果左边计算的结果为假(0),右边的都不在进行计算:
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4; //a = 1时(为真)
i = a++ && ++b && d++; //a++是后置++,先打印a的值
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
return 0;
}
a = 1 //2
b = 2 //3
c = 3 //3
d = 4 //5
逻辑或中如果左边计算的结果为真(1),右边的都不在进行计算:
#include <stdio.h>
int main()
{
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
return 0;
}
a = 2
b = 2
c = 3
d = 4
条件操作符
exp1?exp2:exp3
上述代码的含义是: 如果表达式1的结果为真,计算表达式2的结果,表达式2的结果是整行表达式的结果。如果表达式1的结果为假,计算表达式3的结果,表达式3的结果是整行表达式的结果。
int main()
{
int a = 5;
int b = 13;
int c = 0;
c = (a > b ? a - b : b - a); //8
printf("%d", c);
return 0;
}
逗号表达式
逗号表达式是用逗号隔开的表达式,从左向右依次执行。 整个表达式的结果是最后一个表达式的结果。
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1); //13
printf("%d", c);
return 0;
}
上述代码中,(a > b, a = b + 10, a, b = a + 1)依次计算,a>b不产生结果,a=b+10=2+10=12,b=a+1=12+1=13,作为表达式的结果赋给c。 逗号表达式的代码简化:
a = gat_val();
count_val();
while(a>0)
{
//业务处理
a = gat_val();
count_val();
}
使用逗号表达式:
while(a = gat_val(),count_val(),a>0)
//a = gat_val(),count_val()做计算,a>0做判断条件生效
{
//业务处理
}
::: warning 在可以熟练使用逗号表达式的情况下进行代码简化。 :::
下标引用操作符
下标引用操作符:[]。(对于数组而言)。 操作数是数组名+索引值。
int arr[10]; //创建数组
arr[5] = 10; //使用下标引用操作符
上述代码中,arr和5就是下标引用操作符[]的操作数。
函数调用操作符
函数调用操作符:()。 函数调用操作符接收一个或者多个操作数:第一个操作数是函数名,其余操作数是传递给函数的参数。
int get_max(int x, int y) //此处的()是函数定义时候的语法规则,不是函数调用操作符
{
return (x > y ? x : y);
}
int main()
{
int a = 5;
int b = 8;
int max = get_max(a, b); //函数调用时的()是函数调用操作符
printf("%d", max);
return 0;
}
访问成员操作符
.:结构体.成员名
struct Stu //创建一个结构体类型。类型名:struct Stu
{
//成员变量。用来描述学生的相关属性
char name[20];
int age;
char id[20];
};
int main()
{
int a = 10;
//使用struct Stu类型创建了一个学生对象s1,并初始化
struct Stu s1 = { "张三",20,"20220511" };
//.操作符访问结构体成员
printf("%s\n", s1.name);
printf("%d\n", s1.age);
printf("%s\n", s1.id);
return 0;
}
张三
20
20220511
上述代码中的struct Stu也是一种类型,类型是用来创建变量的,即struct Stu s1。 上图访问过程中只要打印时写出结构体创建的对象名加上访问对象操作符.编译器就可以提示出成员。即结构体变量.成员名。 ::: tip 结构体类型相当于建房子的图纸,先设置好所需的各项数据,如学生姓名、年龄、学号。s1是房子,存放了图纸中所需要的相关信息,并向内存中申请一块区域存放。 (只有结构体类型时候没有占用空间,创建了对象以后才有了实际占用的物理空间)。 :::
->:结构体指针->成员名
struct Stu //创建一个结构体类型。类型名:struct Stu
{
//描述学生的相关属性
char name[20];
int age;
char id[20];
};
int main()
{
int a = 10;
//使用struct Stu类型创建了一个学生对象s1,并初始化
struct Stu s1 = { "张三",20,"20220511" };
struct Stu* ps = &s1; //s1在内存中有实际存放的地址,
printf("%s\n", (*ps).name); //*ps实际就是s1
printf("%s\n", ps->name); //打印指针ps所指向的对象s1的成员name
return 0;
}
张三
张三
上述程序中第14行和15行代码没有任何区别。
表达式求值
表达式求值的顺序,一部分是由操作符的优先级和结合性决定的。有些表达式的操作数在求值的过程中需要类型转换。
隐式类型转换(整型提升)
C的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
char a,b,c;
a = b + c;
上述代码在计算过程中,b和c的值首先被提升为普通整型,然后再执行加法运算。加法运算完成后,结果被截断,再存储到char类型的变量a中。 ::: tip 一个整数所占空间应该是32个比特位,而char类型的变量a的大小是一个字节,只能放8个比特位。就会发生截断,截断的规则是将最低位的一个字节放进去。 :::
::: tip 程序中如果不通过unsigned指定(如unsigned int x 来指定x为无符号数),则x默认为有符号数。 无符号数整型提升在高位补0到整型大小。 ::: ::: warning 整型提升是按照变量的数据类型的符号位来提升的。 符号位是0,高位补0到整型大小。 符号位是1,高位补1到整型大小。 :::
int main()
{
char a = 3; //a实际存放值是00000011
//00000000000000000000000000000011 - 3。正整数的原码反码补码相同
char b = 127; //b实际存放的值是01111111
//00000000000000000000000001111111 - 127
char c = a + b;
//a和b相加时,因为a和b自身的大小都是char类型,大小没有达到整型的大小,为提升计算精度,需要整型提升
//00000011 - a
//01111111 - b
//00000000000000000000000000000011 - char a整型提升到int a
//00000000000000000000000001111111 - char b整型提升到int b
//00000000000000000000000010000010 - int c=a+b,二进制逢二进一
//10000010 - 存储进char c,需要被截断
printf("%d\n", c);
//11111111111111111111111110000010 - char c整形提升 此时还是内存中的值,即补码,打印出来的值需要是源码
//11111111111111111111111110000001 - 补码-1=反码
//10000000000000000000000001111110 - 反码取反=源码
}
-126
上述代码中,a和b和c都是char类型的变量,是有符号数,最高位是符号位。 a的二进制数是00000011,符号位是0,整型提升时高位全部补0,补成整型大小。 b的二进制数是01111111,符号位是0,整型提升时高位全部补0,补成整型大小。 整型提升后可以直接进行计算。 %d用来打印整型,char c需要整型提升。 c的二进制数是10000010,符号位是1,整型提升时高位全部补1,补成整型大小。
::: tip 取反:符号位不变,其他位按位取反。 符号位为1是负数,符号位为0是正数。 ::: ::: warning 在计算过程中,表达式中有一些操作数的大小达不到整型大小,会发生整型提升,先变成整型大小,再参与运算。 进行整型提升时首先检查变量类型,如果是有符号数,高位为符号位,使用符号位从高位填充到整型大小(32位字节)。 :::
整型提升举例:
负数的整型提升
char c1 = -1;
//10000001 - 源码
//11111110 - 反码
//11111111 - 补码
//11111111111111111111111111111111 - 整型提升
char c2 = 1;
//00000001 - 源码反码补码相同
//00000000000000000000000000000001 - 整型提升
//无符号数高位直接补0
int main()
{
char a = 0xb6;
//10110110 - a
short b = 0xb600;
//1011011000000000 - b
int c = 0xb6000000;
//10110110000000000000000000000000 - c
if (a == 0xb6) //比较运算,a需要整型提升
//11111111111111111111111110110110 - a,不等于0xb6
printf("a");
if (b == 0xb600) //比较运算,b需要整型提升
//11111111111111111011011000000000 - b,不等于0xb600
printf("b");
if (c = 0xb6000000) //比较运算,b需要整型提升
//10110110000000000000000000000000 - c,等于0xb6000000
printf("c");
return 0;
}
int main()
{
char c = 1;
printf("%u\n", (c)); //1。c是char类型的变量
printf("%u\n", (+c)); //4。+c进行了运算,需要把c做整型提升,c变为整型大小
return 0;
}
隐式类型转换(算术转换)
整型提升针对的是小于整型的类型。 如下是大于等于整型的类型:
//从下到上所占空间越来越大
long double
double
float
unsigned long int
long int
unsigned int
int
算术转换:如果一个表达式中出现上述类型中的其中两个类型(如int和unsigned int)进行运算,首先要把int类型转换成unsigned int类型然后再进行运算。 ::: warning 算术转换时较小的类型转换为较大的类型。 ::: 如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作无法进行。
复杂表达式的求值影响因素
::: tip 复杂表达式的求值影响因素:
- 操作符的优先级
- 操作符的结合性(优先级相同时考虑结合性)
- 是否控制求值顺序(如逻辑与,左边为假时,右边不进行运算)
:::
操作符的优先级:
如上,相邻操作符不相同时,考虑操作符的优先级。相邻操作符相同时,考虑操作符的结合性。int a = b + c * 3;
下图中从上到下,优先级由高到低。 N/A表示无结合性,L/R表示从左向右结合,R/L表示从右向左结合