
- 1.算术 *** 作符
- 2.移位 *** 作符
- 3.位 *** 作符
- 3.1 变态面试题
- 4.赋值 *** 作符
- 5.单目 *** 作符
- 5.1sizeof和数组
- 6.关系 *** 作符
- 7.逻辑 *** 作符
- 7.1 360笔试题
- 8.条件 *** 作符
- 9.逗号表达式
- 10.下标引用、函数调用和结构成员
- 11表达式求值
- 11.1 隐式类型转换
- 11.2 算术转换
- 11.3 *** 作符的属性
- 总结
1.算术 *** 作符
这类 *** 作符包括:+ - * / %.
这里需要注意的是除号和取余,有整形的除法: *** 作符两边的 *** 作数都是整数,那结果也是整数1/2 = 0。和小数除法, *** 作符的两个只有有一个 *** 作数是小数,结果就是小数,1.0/2 = 0.5。
取模 *** 作符的两个 *** 作数都必须是整数。
int main()
{
printf("%d\n", 7 / 2); //3
printf("%d\n", 7 % 2); //1
return 0;
}
2.移位 *** 作符
注:移位 *** 作符的 *** 作数只能是整数。
要搞懂这两个 *** 作符一定要理解二进制,移位 *** 作符移动的就是二进制位,整数在内存中有三种表示形式:原码、反码和补码。
正数的三码相同,而负数的三码需要计算,比如写出7的二进制表示就是111。
而7是个整形,整形占四个字节也就是32个比特位,所以完整的二进制序列为:00000000000000000000000000000111 这是原码(按照其正负值直接写出来的就是原码)又因为正数三码相同,所以反码补码也是这个。
那么-7的二进制该怎么写?
原码最高位为符号位,0代表正数,1代表负数,因此它的二进制序列为:10000000000000000000000000000111 原码,原码的符号位不变,其它位按位取反得反码:1111111111111111111111111111111111000 反码,反码最低位+1得补码:1111111111111111111111111111111111001 补码。
注意:整数在内存上是以补码的形式存储的。
因此移位 *** 作符移动的是内存里的补码得二进制序列。
那么 7 << 1的值是多少,下面上代码解释:
int main()
{
int a = 7;
//左移 *** 作符
int b = a << 1;
printf("b = %d\n", b);
return 0;
}
左移一位后7的二进制序列变成了:00000000000000000000000000001110,换算成10进制为14。
画图解释:
左移 *** 作符:左边丢弃,右边补0。
a改为-7呢?
int main()
{
int a = -7;
//左移 *** 作符
int b = a << 1;
printf("b = %d\n", b);
return 0;
}
结果为-14,解释:
这里有没有发现7->14或者-7->-14都有一个加倍的效果,所以左移有一个乘2的效果。
移位 *** 作符只能对整数进行左右移!
紧接着就是右移 *** 作符,右移 *** 作符分为两种:
- 算数右移:右边丢弃,左边补原符号位
- 逻辑右移:右边丢弃,左边补0
int main()
{
int a = 7;
//左移 *** 作符
int b = a >> 1;
printf("b = %d\n", b); //3
return 0;
}
正数没法分辨是采用的哪种右移,换成负数:
int main()
{
int a = -7;
//左移 *** 作符
int b = a >> 1;
printf("b = %d\n", b);
return 0;
}
结果:
VS 2019编译器采用的是算数右移。
对于移位运算符,不要移动负数位,这个是标准未定义的。
3.位 *** 作符
位 *** 作符有三个:
& —//按位与
| — //按位或
^ — //按位异或
注:他们的 *** 作数必须是整数。按位是按二进制位!
直接上代码:
int main()
{
int a = 3;
int b = -5;
printf("%d\n", a & b);
printf("%d\n", a | b);
printf("%d\n", a ^ b);
return 0;
}
既然是按照二进制位来运算,就先写出a和b的二进制序列,注意内存是以补码的形式存储的,%d是以一个有符号的整数打印,也就是原码。
先讲第一个a & b:
按位与是对应的二进制位相比,全1为1,有0为0:
a:00000000000000000000000000000011 - 补码
b:11111111111111111111111111111011 - 补码
按位与后得到这个二进制序列:
00000000000000000000000000000011
是个正数三码相同,因此打印的结果是3。
a | b:
按位或是对应的二进制位相比,有1为1,全0为0:
a:00000000000000000000000000000011 - 补码
b:11111111111111111111111111111011 - 补码
按位或后得到这个二进制序列:
11111111111111111111111111111011 - 补码
符号位为1是负数,需要-1取反得原码并打印:
10000000000000000000000000000101 - 原码
打印结果为-5。
a ^ b:
按位异或是对应的二进制位相比,相同为0,相异为1:
a:00000000000000000000000000000011 - 补码
b:11111111111111111111111111111011 - 补码
按位异或后得到这个二进制序列:
11111111111111111111111111111000 - 补码
转换成原码:
10000000000000000000000000001000 - 原码
打印结果为-8。
不能创建临时变量(第三个变量),实现两个数的交换。
都知道最简单交换变量的值就是创建一个临时变量来交换但是此题不允许,所以
变态吧?(doge)
其实就是使用按位异或 *** 作符来实现:
//记住同0异1
//比如3^3 = 0
//简单些一下二进制序列
//011
//011
//000
//相同的两个数字异或为0
//那么0^5 = 5
//000
//101
//101
//因此可以推断出
//a^a = 0
//0^a = a
//3^3^5 = 5
//3^5^3 = 5
//异或支持交换律
int main()
{
int a = 3;
int b = 5;
//这里不算结果直接带入表达式推断
a = a ^ b;//a=3^5
b = a ^ b;//3^5^5 -->3
a = a ^ b;//3^5^3 -->5
printf("a = %d b = %d\n", a, b);
return 0;
}
因此最后的结果:
注:位 *** 作符的 *** 作数也只能是整数。
4.赋值 *** 作符
赋值 *** 作符是一个很棒的 *** 作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。
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 main()
{
int a = 0;
a = a + 5;
a += 5;
//这两种写法完全等价
a = a >> 1;
a >>= 1;
//...
return 0;
}
5.单目 *** 作符
注意:单目 *** 作符只有一个 *** 作数。
上图 *** 作符一个个解释。
逻辑反 *** 作符:
int main()
{
int flag = 1;
//! *** 作符让flag由真变为假
//由假变为真
//flag为真进入if
if (flag == 0)
{ }
//flag为假进入if
if(!flag)
{ }
return 0;
}
C语言中0表示假,非0表示真。
正、负值 *** 作符:
int main()
{
int a = +10;
int b = +a;
int c = -a;
printf("%d\n", a);
printf("%d\n", b);
printf("%d\n", c);
return 0;
}
这就是正号负号的作用。
&取地址 *** 作符:
int main()
{
int a = 10;
printf("%p\n", &a);
return 0;
}
取出a在内存中的地址:
整形是四个字节,每个字节占一个地址,取地址取出的是第一个字节的地址。
int main()
{
int a = 10;
int* p = &a;
return 0;
}
p是用来存放地址的,所以p是指针变量。
sizeof *** 作符:
int main()
{
int a = 10;
//计算的是a在内存中所占的空间大小
//单位是4
printf("a = %d\n", sizeof(a)); //4
printf("a = %d\n", sizeof(int)); //4
return 0;
}
sizeof是个 *** 作符,计算的是类型所创建的变量所占空间内存的大小,也可以计算数组大小。
~按位取反 *** 作符:
int main()
{
int a = 0;
//~是按照二进制位按位取反
//0变1,1变0
//00000000000000000000000000000000 - 0的补码
//11111111111111111111111111111111 -> ~a
//11111111111111111111111111111110 - 反码
//10000000000000000000000000000001 - 原码
//-1
printf("%d\n", ~a);
return 0;
}
这里的按位取反 *** 作符是把包括符号位在内的全部取反。
前后置++、– *** 作符:
int main()
{
int a = 3;
int b = ++a;//前置++
//a=a+1,b=a
printf("a = %d\n", a); //4
printf("d = %d\n", b); //4
return 0;
}
前置++的计算特点:先++,后使用,a是3,先加1得4,在赋给b,此时a和b都是4。
int main()
{
int a = 3;
int b = a++; //后置++
//b=a,a=a+1
printf("a = %d\n", a); //4
printf("b = %d\n", b); //3
return 0;
}
后置++的特点:先使用,后++,a是3先赋给b后,a加1,此时a是4,b是3。
前后置–计算特点同上。
*解引用 *** 作符一般和指针同时使用:
int main()
{
int a = 10;
//整形指针类型
int* p = &a;
//存好a的地址是为了后面
//通过P里面的存放的地址找到
//它所指向的对象
//因此在p前面加上*解引用
*p = 20;
//此时就把a改了
//等价于a = 20;
return 0;
}
()强制类型转换:
int main()
{
int a = 3.14;
return 0;
}
把double类型的数据强制转换为int:
int main()
{
int a = (int)3.14;
//a = 3
return 0;
}
转换后把小数点后舍弃。
5.1sizeof和数组
#include
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)两个地方分别输出多少?
分别是;40 4 10 4
注意:下面两个整形和字符数组总大小分别是40和10,而数组传参,传过去的实际上是首元素的地址,形参看样子是数组形式,但实际上是个指针变量。而指针变量的大小只与编译环境有关(4/8),因此函数内部打印的大小相同。
6.关系 *** 作符
关系 *** 作符:
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
这些关系运算符比较简单,没什么可讲的,但是我们要注意一些运算符使用时候的陷阱。
注意:在编程的过程中== 和=不小心写错,导致的错误。
7.逻辑 *** 作符
逻辑 *** 作符有哪些:
&& 逻辑与
|| 逻辑或
int main()
{
//逻辑与两边的表达式都为真才为真
//有一边为假就为0
int a = 3 && 5; //1
int b = 5 && 0; //0
//逻辑或两边的表达式只要有一个为真就为1
//全为假才为0
int c = 1 || 0; // 1
int d = 0 || 0; //0
return 0;
}
区分逻辑与和按位与。
区分逻辑或和按位或。
7.1 360笔试题1&2 -----> 0
1&&2 ----> 1
1|2 -----> 3
1||2 ----> 1
有下面一段代码:
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = a++ && ++b && d++;
//i = a++||++b||d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
//程序输出的结果是什么?
结果:
这里触发了短路原则,a++是先使用,也就是0为假,逻辑与就不会再算后面表达式的内容,因为有0则0,最后其实只算了表达式a,在自增+1,所以最后的结果是1,2,3,4.
&& 左边为假,右边就不计算了
|| 左边为真,右边就不计算了
8.条件 *** 作符
也叫三目 *** 作符:
表达式1 ?表达式2 :表达式3
先判断1是否为真,如果为真,整个表达式的结果就是2的结果,否则就是3的结果。
1.
if (a > 5)
b = 3;
else
b = -3;
//转换成条件表达式,是什么样?
2.使用条件表达式实现找两个数中较大值。
int max = (a > b ? a : b);
9.逗号表达式
格式:exp1, exp2, exp3, …expN
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
//代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c是多少?
c = (0,12,13);
//最后一个表达式结果,c = 13.
注意:虽然结果是最后一个表达式,但是前面的值可能会影响最后的表达式。
10.下标引用、函数调用和结构成员
1 . [ ] 下标引用 *** 作符
*** 作数:一个数组名 + 一个索引值
int arr[10];//创建数组
arr[9] = 10;//实用下标引用 *** 作符。
[ ]的两个 *** 作数是arr和9。
既然是两个 *** 作数,那是否支持交换律,写成9[arr]?
答案是可以的,对于[ ]来说arr和9只是它的 *** 作数,位置并没有影响。
2 .( )函数调用 *** 作符
接受一个或者多个 *** 作数:第一个 *** 作数是函数名,剩余的 *** 作数就是传递给函数的参数。
//函数定义
int sum(int x, int y)
{
return x + y;
}
int main()
{
int a = 1;
int b = 2;
//函数调用
int sum = add(a, b);
//这里的小圆括号就是函数调用 *** 作符
// *** 作数:函数名add,a,b
//所以函数调用最少一个 *** 作数就是函数名
printf("%d\n", sum);
return 0;
}
3 .访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名
创建一个学生类型的结构体:
struct Stu
{
char name[20];
int age;
double score;
};
//形参用结构体类型接收
void set_stu(struct Stu ss)
{
strcpy(ss.name, "zhangsan");
ss.age = 20;
ss.score = 100.0;
}
void print(struct Stu ss)
{
printf("%s %d %lf\n", ss.name, ss.age, ss.score);
}
int main()
{
//创建结构体变量s
struct Stu s = { 0 };
//现在封装两个函数
//一个设置初值
set_stu(s);
//一个打印成员变量
print_stu(s);
return 0;
}
结果打印:
什么都没打印?
原理其实和函数里的传值调用和传址调用类似,这里只是设置形参ss,和实参s没有关系,因此这里应该取出s的地址传过去,并且形参部分用结构体指针接收:
struct Stu
{
char name[20];
int age;
double score;
};
//形参用结构体类型接收
void set_stu(struct Stu* ps)
{
//第一种写法比较啰嗦
/*strcpy((*ps).name, "zhangsan");
(*ps).age = 20;
(*ps).score = 100.0;*/
//第二种
strcmp(ps->name, "zhangsan");
ps->age = 20;
ps->score = 98.0;
}
void print_stu(struct Stu ss)
{
printf("%s %d %lf\n", ss.name, ss.age, ss.score);
}
int main()
{
//创建结构体变量s
struct Stu s = { 0 };
//现在封装两个函数
//一个设置初值
set_stu(&s);
//一个打印成员变量
print_stu(s);
return 0;
}
结果:
上面就用到了两个 *** 作符:
这里的ps->age 是完全等价于(*ps).age的。
11表达式求值
表达式求值的顺序一部分是由 *** 作符的优先级和结合性决定。
比如说:
int main()
{
//这里的表达式要根据优先级来确定谁先算
//明显乘优先级大于加因此先*后+
int a = 1+2*3;
//这里的优先级相同就不是根据优先级来算的了
int b = 1+2+3;
//根据结合性来决定
}
同样,有些表达式的 *** 作数在求值的过程中可能需要转换为其他类型。
11.1 隐式类型转换C的整型算术运算总是至少以缺省(默认)整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型 *** 作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的 *** 作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型 *** 作数的标准长度。
如何进行整体提升:
整形提升是按照变量的数据类型的符号位来提升的
int main()
{
char a = -1;
//10000000000000000000000000000001
//10000000000000000000000000000000
//11111111111111111111111111111111 ->补码
//char只能存8位,因此要截断
//11111111
//整形提升,按照第八位的符号位
//11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
//变量c2的二进制位(补码)中只有8个比特位:
//00000001
//因为 char 为有符号的 char
//所以整形提升的时候,高位补充符号位,即为0
//提升之后的结果是:
//00000000000000000000000000000001
//无符号整形提升,高位补0
return 0;
}
练习:
int main()
{
char a = 5;
char b = 126;
char c = a + b;
//结果是?
return 0;
//00000000000000000000000000000101
//00000000000000000000000001111110
//相加
//补符号位
//11111111111111111111111110000011 -> 补码
//11111111111111111111111110000010
//1000000000000000001111101
printf("%d\n", c);//-125
return 0;
}
a和b的值被提升为普通整型,然后再执行加法运算。
同样的:
int main()
{
char c = 1;
printf("%u\n", sizeof(c));//1
printf("%u\n", sizeof(+c));//4
printf("%u\n", sizeof(-c));//4
return 0;
}
在整形提升后结果按照四个字节输出。
11.2 算术转换如果某个 *** 作符的各个 *** 作数属于不同的类型,那么除非其中一个 *** 作数的转换为另一个 *** 作数的类型,否则 *** 作就无法进行。
下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
由下往上转换
如果某个 *** 作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个 *** 作数的类型后执行运算。
11.3 *** 作符的属性复杂表达式的求值有三个影响的因素。
- *** 作符的优先级
- *** 作符的结合性
- 是否控制求值顺序。
两个相邻的 *** 作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
如图:
即使清楚了 *** 作符的优先级、结合性和控制顺序也不一定能确定出一个表达式的唯一路径!
一些问题表达式:
//表达式的求值部分由 *** 作符的优先级决定。
//1
a*b + c*d + e*f
如果每个都换成表达式,那么两种计算步骤会有很大的差异。
//2
c + --c;
这里问题在于c是什么时候是准备好来使用的。
因此写代码的时候一定要写出有唯一计算路径的表达式!
总结
以上就是对C语言 *** 作符的一次详细介绍,弄清 *** 作符对写代码时的灵活性有很大帮助!
下期:指针
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)