给定一个整型数组,有很多元素.
其中只有一个元素出现了一次,其他元素都出现了两次.写一个函数,把这个只出现一次的元素找到.
{1,2,1,2,3}
当前解决方案,针对数字太大,或者为负无法很好处理.等到数据结构"哈希表"更好解决.
运用数组:
#include <stdio.h>
#include <stdlib.h>
int Func(int arr[], int size){
int numCount[100] = { 0 };
for (int i = 0; i < size; i++){
numCount[arr[i]]++;
}
for (int i = 0; i < 100; i++){
if (numCount[i] == 1){
return numCount[i];
}
}
//数据存在问题,没有只出现一次的数字.
return 9527;
}
int main(){
int arr[5] = {1, 2, 1, 2, 3};
int n = Func(arr,5);
printf("%d\n",n);
system("pause");
return 0;
}
运用异或位:(还是运用异或的特性,如果一个数字接连出现两次就会异或抵消(所以也仅针对当前该数组同一个元素最多出现了两次)).
#include <stdio.h>
#include <stdlib.h>
int Func(int arr[], int size){
int ret = 0;
for (int i = 0; i < size; i++){
ret ^= arr[i];
}
return ret;
}
int main(){
int arr[5] = {1, 2, 1, 2, 3};
int n = Func(arr,5);
printf("%d\n",n);
system("pause");
return 0;
}
输出:一个整数的二进制数中有多少个"1"
(运用位操作和移位的特性)
#include <stdio.h>
#include <stdlib.h>
int BitOneCount(int num){
int count = 0;
for (int i = 0; i < 32; i++){
if ((num & (1 << i)) != 0){
count++;
}
}
return count;
}
int main(){
printf("%d\n",BitOneCount(10));
system("pause");
return 0;
}
而 -10 的二进制原码虽然包含3个"1"但计算机中存储负数是使用补码(即原码取反(反码)再加一)
所以当BitOneCount(-10)的输出为 30.
把某个数字的第 N 位设为1: num | (1 << N);
把某个数字的第 N 位设为0: num & ~(1 << N);
赋值操作符
初始化:变量创建的同时,设置初始值.
赋值:变量已经有值了,再修改值.
复合赋值符
+= ^= |= &= <<= >>=
num += 1 -> num = num + 1;
单目操作符
!(逻辑取反),~(按位取反)他俩有什么区别?
int main(){
int num = 1;
printf("%x\n",!num);
printf("%x\n",~num);
system("pause");
return 0;
}
逻辑取反就是非零都是真,零就是假.
sizeof 也是单目运算符而不是一个函数.sizeof之后的括号里可以是变量也可以是类型.
int main(){
int num = 1;
printf("%d\n",sizeof(int));
system("pause");
return 0;
}
思考:这个行为算不算下标越界未定义行为:
int main(){
int arr[] = {1, 2, 3, 4};
printf("%d\n",sizeof(arr[100]));
system("pause");
return 0;
}
这是sizeof的特性
数组下标越界 => 内存访问越界 => 未定义行为 => 程序运行时访问内存的时候发现是非法内存.
sizeof 是一个运算符,具有一个重要特性,编译期求职.
sizeof(arr[100]) => 程序编译过程中就算出结果了,4.
这个过程不涉及内存访问,也就没有越界,更不是未定义行为.
自增自减 ++ --前置后置是有区别的
int i = 0;
int ret = 0;
ret = ++i; // ret => 1
ret = i++; // ret => 0
关系操作符
> >= < <= != ==(测试相等)
关系运算符表达式返回值 要么是1,要么是0.(1真0假)
int main(){
int a = 10;
int b = 40;
int c = 30;
if (a < b < c){
printf("haha\n");
}
else{
printf("hehe\n");
}
system("pause");
return 0;
}
发现他输出"haha",因为首先判断"a < b"返回1再和 c做比较
所以应该加上逻辑与运算
if (a < b && b < c);
逻辑操作符: && ||
短路求值:
#include <stdio.h>
#include <stdlib.h>
int main(){
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && c++ && d++;
printf("a = %d\n b = %d\n c = %d\n d = %d\n",a,b,c,d);
system("pause");
return 0;
}
对于逻辑与运算来说,如果左侧表达式的值已经是假了,此时整个表达式的值已经确定了.右侧表达式不需要求值.
#include <stdio.h>
#include <stdlib.h>
int main(){
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ || ++b || c++ || d++;
printf("a = %d\n b = %d\n c = %d\n d = %d\n",a,b,c,d);
system("pause");
return 0;
}
对于逻辑或运算来说,如果左侧表达式的值已经是真了,此时整个表达式的值已经确定了.右侧表达式不需要求值.
后世的大部分编程语言也同样支持短路求值.
条件操作符:
exp1 ? exp2 : exp3 等价于 if else
int Max(int x, int y){
//if (x > y){
// return x;
//}
//return y;
return x > y ? x : y;
}
条件运算符是所有运算符优先级最低的
表达式求值
隐式类型转换:
范围小的往范围大的类型里转换,但没有char和short
int main(){
char a = 10;
char b = 20;
// a 从 char 转成 int
// b 从 char 转成 int
//计算两个int相加,结果还是int
//int 转为 char赋值给 c
char c = a + b;
system("pause");
return 0;
}
为什么要转换两次?
为了让硬件更简单,软件就要做让步.
CPU从内存读取数据的时候,并不是一个字节一个字节的读,而是四个字节四个字节的读.(整型提升)
类型转换的规则:
1.从大范围的变量转成小范围的变量:进行截断
例如long long 0x0000004411223344
long long -> int
0x11223344
2.从小范围转成大范围
char a = 0x112
char -> int
0x00 00 00 12
前面的几个字节要补符号位
正数补0,负号补1
对于无符号的整数来说,也是补0.
表达式的求值顺序.
#include <stdio.h>
#include <stdlib.h>
int main(){
int i = 1;
//如果结果是9,就是2+3+4
//如果结果是12,就是4+4+4
int ret = ++i + ++i + ++i;
printf("%d\n",ret);
system("pause");
return 0;
}
++i + ++i + ++i行为是未定义的.代码一定是错的(1.多次2.修改3.无序)
一个表达式中对某个值进行多次修改的时候,由于表达式求值的顺序是不确定的,就导致最终的值是无法确定的.
未定义行为的代码一定是错误代码.最终结果无法确定