算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员
/:取整操作符
#include
int main()
{
int a = 3/ 5;
printf("%d\n", a);//0
float b = 3/ 5;
printf("%f\n", b);//0.000000
double c = 3 / 5;
printf("%lf\n", c);//0.000000
float d = 6/ 5;
printf("%f\n", d);//1.000000
double e= 6.0/ 5;
printf("%lf\n", e);//1.200000
return 0;
}
%:取模操作符
int main()
{
int f = 7 % 3;//取模操作符两端必须都是整数
printf("%d\n", f);//1
return 0;
}
<<:左移操作符
int main()
{
int a = 2;
int b = a << 1;//把a的二进制向左移动一位
printf("b=%d\n", b);//b=4
return 0;
}
代码解析:
>>:右移操作符
int main()
{
int a = 10;
int b = a >> 1;//把a的二进制向右移动一位
printf("b=%d\n", b);//b=5
return 0;
}
代码解析:
int main()
{
int a = -1;
int b = a >> 1;//把a的二进制向右移动一位
printf("a=%d\n", a);//a=-1
printf("b=%d\n", b);//b=-1
return 0;
}
整数的二进制表示形式:其实有3种
注意:对于移位运算符,不要移动负数位,这个是标准未定义的
eg:
int num = 10;
int b=num >> -1;//error
&:按位与
int main()
{
int a = 3;
int b = 5;
//&:按位与,这个位是二进制位
int c = a&b;
printf("c=%d\n",c);//c=1
}
代码解析:
|:按位或
int main()
{
int a = 3;
int b = 5;
//|:按位或,这个位是二进制位
int c = a | b;
printf("c=%d\n", c);c=7
}
int main()
{
int a = 3;
int b = 5;
//^:按位异或,这个位是二进制位
int c = a ^ b;
printf("c=%d\n", c);//c=6
}
代码解析:
一道面试题:交换两个int变量的值,不能使用第三个变量,即a=3,b=5,交换之后,a=5,b=3;
1.先使用变量写
int main()
{
int a = 3;
int b = 5;
//交换
int c = 0;
printf("a=%d b= %d\n", a, b);
c = a;
a = b;
b = c;
printf("a=%d b= %d\n", a, b);
return 0;
}
2.不使用第三个变量写
int main()
{
int a = 3;
int b = 5;
//交换
int c = 0;
/*printf("a=%d b= %d\n", a, b);
a=a + b;
b=a - b;
a=a - b;*/
//这样写其实有一定的缺点:数值太大就会溢出!
//应该用异或的方式写
printf("a=%d b= %d\n", a, b);
a = a^b;//a和b异或产生的结果放到a里面去011^101=110
b = a^b;//110^101=011
a = a^b;//110^011=101
printf("a=%d b= %d\n", a, b);
return 0;
}
3.易得结论:
int main()
{
int a = 3;
a^a = 0;
//011
//011
//000
0 ^ a = a;
//000
//011
//011
return 0;
}
练习:编写代码实现,求一个整数存储在内存中的二进制中1的个数
非完整代码
int main()
{
int a = 13;
//00000000000000000000000000001101(1:00:00)
//00000000000000000000000000000001
//00000000000000000000000000000001
return 0;
}
赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。
int main()
{
int a = 10;
a = 100;
a = a + 100;//1
a += 100;//2 1和2相同
}
注意:一个=叫赋值;两个==叫相等
单目操作符:只有一个操作数
!:逻辑反操作
int main()
{
int flag = 5;
//flag为真
if (flag)
{
printf("哈哈\n");
}
int a = 0;
printf("%d\n", !a);//1 前面加!,将真变为假
return 0;
}
sizeof:计算变量和类型的长度,单位为字节
#include
int main()
{
int a = 10;
printf("%d\n", sizeof(a));//4 计算a所占空间的大小,单位是字节
printf("%d\n", sizeof(int));
printf("%d\n", sizeof a);
int arr[10] = { 0 };
printf("%d\n", sizeof(arr));//单位是字节
printf("%d\n", sizeof(int [10]));//int [10]s是arr数组的类型
short s = 5;
int a = 10;
printf("%d\n", sizeof(s = a + 2));//2 此时计算的还是s所占空间的大小
printf("%d\n", s);//5
return 0;
}
~:按位取反
int main()
{
int a = -1;
//-1的二进制序列为: 10000000000000000000000000000001--原码
// 11111111111111111111111111111110--反码
// 11111111111111111111111111111111--补码
//~按位取反:包括符号位在内全部取反
//111111111111111111111111111111111
//000000000000000000000000000000000
int b = ~a;
printf("%d\n", b);//0
printf("%d\n", a);//-1
}
++ --操作符
int main()
{
int a = 10;
int b = a++;//后置++,先使用,再++
printf("%d\n", a);//11
printf("%d\n", b);//10
int b = ++a;//前置++,先++,后使用
printf("%d\n", a);//11
printf("%d\n", b);//11
//后置--,前置--,使用和++相同。
int b = 10;
printf("%d\n", b--);//10
printf("%d\n", b);//9
return 0;
}
&:取地址操作符
int main()
{
int a = 10;
printf("%p\n", &a);
int *pa = &a;//pa用来存放地址的,int * 说明pa就是一个指针变量
*pa=20;//* :解引用操作符/间接访问操作符
//*pa=20:就是把上面的a改成了20
printf("%d\n", a);
return 0;
}
强制类型转换:括号里面放个类型
int main()
{
int a = (int)3.14;//把3.14强制类型转换成int类型
return 0;
}
int main()
{
int a = 3;
int b = 5;
if (a==b)
if (a<b)
if (a >= b)
{
}
}
&&按位与
int main()
{
int a = 3;//真
int b = 0;//假
if (a&&b)//两个同时为真才为真,其他都为假
{
printf("hehe\n");//不打印
}
return 0;
}
|| 按位或
int main()
{
int a = 3;//真
int b = 0;//假
if (a||b)//两个只要有一个真就为真,两个同时为假才为假
{
printf("hehe\n");//不打印
}
return 0;
}
逻辑与和或的特点:程序输出的结果是什么?
#include
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\nd = %d\n", a, b, c, d);//1 2 3 4
i = a++||++b||d++;//||左边为真,后面就不需要算了
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);//2 2 3 4
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++||++b||d++;//||左边为假时,后面需要算;左边为真,后面就不需要算了
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);//1 3 3 4
return 0;
}
int main()
{
int a= 3;
int b = 0;
if (a > 5)
b = 1;
else
b = -1;
//三目操作符
b = a > 5 ? 1 : -1;//a是否大于5,如果大于输出1,否则输出-1;与if....else 语句逻辑益阳
return 0;
}
逗号表达式是由逗号隔开的一串表达式, 逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
int main()
{
int a = 3;
int b = 5;
int c = 0;
//逗号表达式要从左向右依次计算,整个表达式的结果是最后一个表达式的结果
int d = (c= 5, a = c + 3, b = a - 4, c += b);
printf("%d\n", d);//9
return 0;
}
操作数:一个数组名+一个索引值
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
printf("%d\n", arr[4]);//[]:下标引用操作符
//[]的操作数是2个:arr,4
return 0;
}
() 函数调用操作符 接收一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
//函数的定义
int Add(int x, int y)
{
return x+y;
}
void test()
{ }
int main()
{
int a = 10;
int b = 20;
//函数调用,调用add函数
int ret=Add(a, b);//()函数调用操作符,有参的话需要传参
//()操作数:函数名Add,参数a,参数b
test();//无参的话不需要传参,但是()必须写
return 0;
}
结构成员操作符:访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名
//结构体
//创建了一个自定义的类型
struct Book
{
//结构体的成员变量
char name[20];
char id[20];
int price;
};
int main()
{
//int num = 10;
//结构体变量名.成员名
struct Book b = { "C语言", "C20210709", 55 };
printf("书名:%s\n", b.name);//.操作符,用.的方式可以找出结构体变量里面的成员
printf("书号:%s\n", b.id);
printf("定价:%d\n", b.price);
return 0;
}
struct Book
{
//结构体的成员变量
char name[20];
char id[20];
int price;
};
int main()
{
//int num = 10;
struct Book b = { "C语言", "C20210709", 55 };
struct Book * pb = &b;//* 说明pb是一个指针,struck book 说明pb指向的那个b的类型是struct Book
//如何通过pb来访问b呢?
//用结构体变量名.成员名:就可以找到他的成员
printf("书名:%s\n", (*pb).name);
printf("书号:%s\n", (*pb).id);
printf("定价:%d\n", (*pb).price);
//这种方式是先解引用找到结构体对象,然后在通过.的方式找到
有没有更简洁的办法呢?
也可以这样写
//用结构体指针->成员名:就可以找到他的成员
//这种方式的意思是:pb是个结构体指针,它指向的那个对象的name、id、price
printf("书名:%s\n", pb->name);
printf("书号:%s\n", pb->id);
printf("定价:%d\n", pb->price);
return 0;
}
表达式求值的顺序一部分是由操作符的优先级和结合性决定。同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
C的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
通过代码理解:
int main()
{
char a = 3;
char b = 127;
char c = a + b;
printf("%d\n", c);//-126
return 0;
}
这个代码需要进行好好的分析!!!
代码解析:
如何进行整体提升呢?
整形提升是按照变量的数据类型的符号位来提升的
变量a的二进制位(补码)中只有8个比特位:
00000011
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000011
变量b的二进制位(补码)中只有8个比特位:
01111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000001111111
因为c=a+b
所以00000000000000000000000000000011
00000000000000000000000001111111
00000000000000000000000010000010
又因为c是char类型的只能放8个比特位所以也需要发生截断,所以使用最低位的8个比特位10000010
疑问:那结果为什么是-126呢?
因为c是一个char类型,但打印的是整型,所以需要将c进行整型提升
变量c的二进制位(补码)中只有8个比特位:
10000010
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111110000010(补码)
因为在内存里面放的是补码的形式,正数的原码、反码、补码都相同但负数的不同,需要通过计算求原码
打印出我们肉眼看到的数字
补码-1得反码:11111111111111111111111110000001(反码)
反码符号位不变其他按位取反得原码:
10000000000000000000000001111110(原码)
通过原码可以得出结果为-126
负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
11111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
对于无符号位的unsigned int(无符号整型)
无符号整形提升,高位补0
打印无符号整数用%u
下面我们来点整形提升的例子
实例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;
}
代码解析:
实例2
int main()
{
char c = 1;
printf("%u\n", sizeof(c)); //1
printf("%u\n", sizeof(+c)); //4
printf("%u\n", sizeof(-c)); //4
printf("%u\n", sizeof(!c)); //在vs上测试是1,在linux(gcc平台上)上测试是4,要以gcc为准
return 0;
}
代码解析:
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算,这也属于隐式类型转换的一种.
eg:
int main()
{
int a = 1;
float f= 3.14f;
a + f;//他们两个想要参与运算需要将int a转换成float类型(向精度更高的类型转换)
return 0;
}
复杂表达式的求值有三个影响的因素:
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
eg:
int main()
{
int a = 4;
int b = 5;
int c = a + b * 7;//优先级决定运算顺序
int d = a + b + 7;//此时优先级不起作用,结合性决定运算顺序
return 0;
}
附:操作符优先级和结合性一览表
问题代码1:
a*b + c*d + e*f
上述在计算的时候,由于*比+的优先级高,只能保证,的计算是比+早,但是优先级并不能决定第三个比第一个+早执行。
所以表达式的计算机顺序就可能是:
ab
cd
ab + cd
ef
ab + cd + ef
或者:
ab
cd
ef
ab + cd
ab + cd + ef
问题代码2
int fun()
{
static int count = 1;
return ++count; }
int main()
{
int answer;
answer = fun() - fun() * fun();
printf( "%d\n", answer);//输出多少?
return 0;
代码2中虽然在大多数的编译器上求得结果都是相同的。
但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。
函数的调用先后顺序无法通过操作符的优先级确定。
问题代码3:非法表达式
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i);
return 0; }
代码3在不同编译器中测试结果不同
总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。