前言
回顾操作符和一些表达式方面的知识。
表达式及操作符
- 前言
- 算术操作符 : + - * /
- 位操作符
- >>、<<
- >>
- 算数右移
- 逻辑右移
- <<
- 小结
- &、|、~
- &:有0则为0,两个1才为1
- |: 有1则为1,两个0才为0
- ~(按位取反)
- 符号位要参与吗? 要参与。
- ^ (按位异或)(一个很强的操作符)
- 规律
- 单身狗问题
- LeetCode
- 单目操作符
- !
- &、|
- sizeof(也可分为关键字)
- (强制类型转换)
- +(正号)、-(负号)
- ++,--
- 深入了解后置++
- 逻辑运算符
- &&
- &&短路问题
- ||
- || 短路问题
- 三目操作符
- ?: (利用好了很省事)
- 以归并排序中的Merge过程为例
- 逗号表达式
- ,
- 下标引用、函数调用、结构体引用
- []、()、->
- \
- 1.续行
- 2.转义字符
- 奇奇怪怪表达式
- 你了解'abcd'吗?
- ++i + ++i + ++i = ?
- 隐式转换,以及算数转换
- 隐式转换--整型提升
- 算术转换
算术操作符 : + - * /
整除除以整除得到的是整数,如果其中有浮点数那么结果是浮点数。
位操作符
位操作符都是作用在二进制上的。
>>、<<
>>
>>
:右移操作符,又分为逻辑右移和算数右移。
当变量是有符号的,执行>>
是算数右移动。
当变量是无符号的,执行>>
是逻辑右移。
算数右移
最高位补符号位,右边丢弃
逻辑右移
最高位补0,右边丢弃
<<
<<
:左移操作符。左边丢弃,右边补0
小结
1.右移:相当于除以2,左移:相当于乘以2。
左移和右移改变的是二进制位,每个二进制对应的权值是不同的。
2.左移右移之后,原来的变量是没有改变的。
为什么原来的变量值没有改变?计算都是在CPU内进行的,位运算也不例外。数据是存在内存中的。当在CPU内进行计算的时候,这个结果并没有写回内存,因此原来的变量的值是不会改变的。
3.左移、右移不要移动负数位,因为在c语言标准中并没有规定。例如
i>>-1 ; i<<-1
&、|、~
&:有0则为0,两个1才为1
&1
:检测比特位是否为1
|: 有1则为1,两个0才为0
| 1
:将比特位置为1
~(按位取反)
符号位要参与吗? 要参与。
~
:按位取反,顾名思义。1–>0,0–>1。符号位是要参与运算的
^ (按位异或)(一个很强的操作符)
^
:同为0,异为1
规律
1.满足交换律和结合律
2.a^a = 0
3.a^0 = a
单身狗问题
先拿单身狗练练手在做leetCode
题目:
一个数组中只有一个数字单独出现了一次,其它重复出现,请找出这个数。
利用^
的规律很容易得到结果。将所有的数异或就能找到了
int main()
{
int arr[] = { 1,2,3,4,1,2,3 };
int sz = sizeof(arr) / sizeof(arr[0]);
int ret = 0;
for (int i = 0; i < sz; i++)
{
ret ^= arr[i];
}
printf("%d", ret);
return 0;
}
LeetCode
消失的数
同样也是异或怎么做呢?第一次遍历一遍数组并且异或一次,因为这个是连续的,只要把个数加1,在异或一遍就能找到这个缺失数了。
例如: 3 0 1,第一次先异或了。
第二次 0 1 2 3 异或,那很容易找到2了。
int missingNumber(int* nums, int numsSize){
int i = 0;
int ret = 0;
for(i = 0;i<numsSize;i++)
{
ret ^=nums[i];
}
for(i=0;i<numsSize+1;i++)
{
ret ^=i;
}
return ret;
}
单目操作符
!
逻辑取反
!true == false
!false == true
&、|
sizeof(也可分为关键字)
求元素类型的大小。
要与strlen
区分,sizeof
会把'\0'
也会计算在内,strlen
不会。
另一个要注意的是
strlen
括号内的表达式是不会计算的。在程序的链接过程前,编译器就可以将类型大小计算出来并且替换。
返回值是无符号整形的
(强制类型转换)
什么时候会使用?类型不匹配的时候就需要使用强转。
+(正号)、-(负号)
++,–
前置:先自增后使用
后置:先使用后自增
深入了解后置++
int a = 1;
int b = a++;
先使用
b = a ;
,再a = a + 1;
。
那么问题来了如果只有a++;
,怎么去理解先使用呢?
“怎么使用”:存入寄存器中。
逻辑运算符
&&
&&
:逻辑与。 并且的意思
&&短路问题
多个条件逻辑与的时候,如果有一个条件为假整个结果都为假了,后面的条件是不会执行的。
小明爸爸说,小明你只要语文数学都考了95+,就可以奖励你100元。
当小明知道语文考了90分,他还需要知道数学的成绩吗?不需要了,已经不可能了。
||
||
:逻辑或。或者的意思
|| 短路问题
多个条件逻辑或的时候,如果有一个条件为真整个结果都为真了,后面的条件是不会执行的。
小明爸爸说,小明你只要语文或者数学考了95+,就可以奖励你100元。
当小明知道语文考了96分,他还需要知道数学的成绩吗?不需要了,奖励已经可以到手了。
三目操作符
?: (利用好了很省事)
以归并排序中的Merge过程为例
void Merge(int* arr, int left,int mid, int right)
{
int i = left;
int j = mid + 1;
int* help = (int*)malloc(sizeof(int) * (right - left + 1));
int k = 0;
while (i <= mid && j <= right)
{
help[k++] = arr[i] > arr[j] ? arr[j++] : arr[i++];
}
while (i <= mid)
help[k++] = arr[i++];
while (j <= right)
help[k++] = arr[j++];
for (i = 0; i < k; i++)
{
arr[i+left] = help[i];
}
free(help);
}
void MergeSort(int* arr, int left, int right)
{
if (left >= right)
return;
int mid = (left + right) / 2;
MergeSort(arr, left, mid);
MergeSort(arr, mid + 1, right);
Merge(arr, left, mid ,right);
}
下面一段代码的三目很精髓。如果arr[i] 大于 arr[j],就把arr[j]的值赋值给help[k],再进行,j、k的自增,否则就把arr[i]的值赋值给help[k],再i,k的自增
while (i <= mid && j <= right)
{
help[k++] = arr[i] > arr[j] ? arr[j++] : arr[i++];
}
逗号表达式
,
逗号表达式,就是用逗号隔开的多个表达式。逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。其中每个表达式都是会计算的。
为什么要带
()
呢?=
的优先级是大于,
的,不加()
,会把f = b + a
当成一个表达式。
每个表达式都会计算出结果的
下标引用、函数调用、结构体引用
[]、()、->
\
有两个作用
1.续行
当续行来用时,后面是不能加空格的。
2.转义字符
奇奇怪怪表达式
你了解’abcd’吗?
看到
'a'
你肯定会想到字符,然后会想到char
,认为'a'
是一个字节大小的。可真是这样吗?当然不是。
char
类型才是1个字节大小。char a = 'ab';
这个过程发生了截断。
''
中最多放4个字符。
++i + ++i + ++i = ?
在不同编译器下同一个复杂表达式,所算出的结果是不同这是为什么?和编译器的处理机制有关。写回的时机不同。
VS:先进行自增3次,并写回内存这个时候i = 3;
相加结果就为12。
Linux:前两个
i
自增并且写回内存,这时i = 3
,3 + 3 = 6
,在执行自增i = 4
,最后6 + 4 = 10
隐式转换,以及算数转换
隐式转换–整型提升
类型大小小于4字节的都会发生整形提升
char,short
。
整形提升是按照变量类型的符号为提升的。
char a = -1;
提升前11111111
提升后11111111 11111111 11111111 11111111
char a = 1;
提升前00000001
提升后00000000 00000000 00000000 00000001
在计算时发生整形提升。
算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。
如果某个操作数的类型在下面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算
如果将一个高级的类型赋值给低级的类型,会有精度的丢失。
比如:int a = 5.13;
实际上a == 5;