文章目录
C++基本数据类型
类型 | 位 | 范围 |
---|---|---|
char | 1 个字节 | -128 到 127 或者 0 到 255 |
unsigned char | 1 个字节 | 0 到 255 |
signed char | 1 个字节 | -128 到 127 |
int | 4 个字节 | -2147483648 到 2147483647 |
unsigned int | 4 个字节 | 0 到 4294967295 |
signed int | 4 个字节 | -2147483648 到 2147483647 |
short int | 2 个字节 | -32768 到 32767 |
unsigned short int | 2 个字节 | 0 到 65,535 |
signed short int | 2 个字节 | -32768 到 32767 |
long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
signed long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
unsigned long int | 8 个字节 | 0 到 18,446,744,073,709,551,615 |
float | 4 个字节 | 精度型占4个字节(32位)内存空间,+/- 3.4e +/- 38 (~7 个数字) |
double | 8 个字节 | 双精度型占8 个字节(64位)内存空间,+/- 1.7e +/- 308 (~15 个数字) |
long double | 16 个字节 | 长双精度型 16 个字节(128位)内存空间,可提供18-19位有效数字。 |
wchar_t | 2 或 4 个字节 | 1 个宽字符 |
16位编译器
类型 | 位 | 范围 |
---|---|---|
char | 1 个字节 | -128 到 127 或者 0 到 255 |
char(即指针变量)* | 2 | |
short int | 2 | |
int | 2 | |
unsigned int | 2 | |
float | 4 | |
double | 8 | |
long | 4 | |
long long | 8 | |
unsigned long | 4 |
32位编译器
类型 | 位 | 范围 |
---|---|---|
char | 1 个字节 | -128 到 127 或者 0 到 255 |
char(即指针变量)* | 4 | |
short int | 2 | |
int | 4 | |
unsigned int | 4 | |
float | 4 | |
double | 8 | |
long | 4 | |
long long | 8 | |
unsigned long | 4 |
64位编译器
类型 | 位 | 范围 |
---|---|---|
char | 1 个字节 | -128 到 127 或者 0 到 255 |
char(即指针变量)* | 8 | |
short int | 2 | |
int | 4 | |
unsigned int | 4 | |
float | 4 | |
double | 8 | |
long | 8 | |
long long | 8 | |
unsigned long | 8 |
位运算基础知识
基本运算符有与&、或|、非~、异或^、左移运算<<、右移运算>>。
左移运算
高位丢弃低位补零。
右移运算
对于无符号数,高位补0低位丢弃;对于有符号数,高位补符号位,低位丢弃。
3条基本定理
- 任何数
&1
都是任何数。
1 & 1 = 1
0 & 1 = 0
- 任何数
|0
都是任何数。
1 | 0 = 1
0 | 0 = 0
- 任何数
^0
都是任何数。
1 ^ 0 = 1
0 ^ 0 = 0
常见位运算应用
乘除法
a >> 1 表示除以2
a << 1 表示乘以2
交换两数
a ^= b;
b ^= a;
a ^= b;
判断奇偶数
(a & 1) == 0 表示偶数
(a & 1) == 1 表示奇数
交换符号
(~a) + 1
正数取反+1变成对应的负数,负数取反加一变为其原码,变成对应正数。
+9 0000 1001
1111 0110
1111 0111 ==> -9
+11 0000 1011
1 111 0100
1111 0101 -11
0000 1010 + 1 = 0000 1011 ==> +11
取绝对值
- 方法1
该方法主要是针对32为整型数,将该数向右移动31位,若此时得到的位为1,则表示是负数,否则是正数。
int abs(int a){
int bit = (a >> 31);
return bit == 0 ? a : (~a + 1);
}
- 方法2(优化之后)
原理就是
任何数与0异或都是任何数,任何数与-1异或相当于对原数取反
-1 == 0xffffffff
int abs(int a) {
int bit = (a >> 31);
// 上述表达式中32位整型数向右移动31位,正数则为0 负数则为-1 0xffffffff
return ((a ^ i) - i);
}
class Solution {
public:
string toHex(int num) {
if(num == 0) return "0";
string hex_ = "0123456789abcdef";
int b = (num >> 31);
unsigned int abs_num = (unsigned)(num ^ b) - b; // 先求绝对值 负数转换为正数
unsigned int num_ = num > 0 ? abs_num : ~abs_num + 1;
// 要是num是负数 就取反+1 变成一个无符号正数 实际意义上它还是负数
// 变成无符号正数方便用于计算对应负数的十六进制表示
// 要是num是正数 就直接用绝对值即可
cout << num_ <<endl;
string temp = "";
while(num_ != 0) {
temp += hex_[num_ % 16];
num_ /= 16;
}
string ans = "";
for (int i = temp.size() - 1; i >= 0; i--) {
ans += temp[i];
}
return ans;
}
};
无符号整数的高低位交换
原始整数为a = 1011 0010 1111 0101
交换前后八位b = 1111 0101 1011 0010
a >> 8 == 0000 0000 1011 0010
a << 8 == 1111 0101 0000 0000
b = (a >> 8) | (a << 8)
二进制逆序
原整数为 a = 1011 0010 1111 0101
逆序之后的二进制整数 b = 1010 1111 0100 1101
二进制逆序的过程中,可以对二进制数进行分组,然后交换顺序,更加方便处理。
- 2bit为一组组内高低位交换
01 11 00 01 11 11 10 10
- 4bit为一组组内高低位交换
1101 0100 1111 1010
- 8bit为一组组内高低位交换
01001101 10101111
- 16bit为一组组内高低位交换
1010111101001101
注意,在上述第一步过程中,两两交换高低位比较繁琐,因此可以取奇数位和偶数位出来,其余位用0填充,
奇数位 a_ji = 1010 0010 1010 0000
偶数位 a_ou = 0001 0000 0101 0101
a_ji 右移一位 a_ji >> 1 = 0 101 0001 0101 0000 0(舍弃) # 高位填0 低位舍弃
a_ou 左移一位 a_ou << 1 =0(舍弃) 0010 0000 1010 101 0 # 高位舍弃 低位填0
a_ji | a_ou = 0111 0001 1111 1010
a的奇偶位交换成功
代码表示:
unsigned short a = 0b1011001011110101;
// 取奇数位, a & 0b1010101010101010 ==> a & 0xAAAA;
// 取偶数位, a & 0b0101010101010101 ==> a & 0x5555;
a = ((a & 0xAAAA) >> 1) | ((a & 0x5555) << 1);
a = ((a & 0xCCCC) >> 2) | ((a & 0x3333) << 2);
a = ((a & 0xF0F0) >> 4) | ((a & 0x0F0F) << 4);
a = ((a & 0xFF00) >> 8) | ((a & 0x00FF) << 8);
return a; // 逆序成功
统计二进制中1的个数
统计二进制中1的个数,最简单的方法就是利用位运算,每次将该数与0x1进行与操作,若结果不为0,说明该位为1,计数器+1,否则该位为0,然后该数右移一位,直到该数为0。但是这样的操作效率并不算高,还有一种就是利用n&(n-1)操作来找到二进制中最右边的一个1,比如n = 1010 0100, 那么n-1 = 1010 0011,那么进行与操作之后 n = 1010 0000,可以看出n最右边的1被消除了,这也是利用了 10...00 - 1 = 01...11的特性,这两个数进行与操作必然为0;因此要想判断二进制中有几个1,只需要看n & (n-1)能进行几次使得n = 0.
int cnt = 0;
while (n) {
n = n & (n - 1);
cnt++;
}
return cnt;
扩展应用
整数a和b,将a修改为b需要修改多少个bit,通过异或操作我们知道,两个位相同异或结果为0,否则为1。 因此只需要将a^b的结果传入上述循环即可,求出异或结果中1的个数。
两数之和
异或运算的别名叫不进位加法,那么a、b相异或之后,其结果就是a+b不进位的结果,之后再考虑需要进位的地方,也就是a和b里面都是1的位需要进位,即
a & b
之后需要进位的地方为1,不需要进位的地方为1,不需要的地方为0,(a & b) << 1
即为进位之后的结果,再加上之前异或(不进位)的结果,就得到了最终结果a + b = (a ^ b) + ((a & b) << 1)
。模拟实现代码如下:
int getSum(int a, int b) {
while (b != 0) {
// 不需要进位了
int a_ = a ^ b; // 不进位的数
unsigned int b_ = (unsigned int)(a & b) << 1;
// 例如 1111 1010 << 1 要是一字节有符号数 则会溢出 若是无符号数 则变成 1111 0100
// 进位之后的数 可能超出有符号数的范围发生溢出 使用无符号数则可以避免
// 无符号数的话 在C++/C内部会直接进行取模操作 而有符号数则不会 视编译器而定
// 溢出后的数会以2^(8*sizeof(type))作模运算
a = a_;
b = b_;
}
return a;
}
a = 1010
b = 1111
1. a ^ b = 0101 (a & b) << 1 = 1010 << 1 = 1 0100
2. a ^ b = 1 0001 (a & b) << 1 = 0 0100 << 1 = 0 1000
3. a ^ b = 1 1001 (a & b) << 1 = 0 0000 << 1 = 0 0000
循环结束
2的幂次
若某个数是2的幂次则该数必然大于0,且二进制表示中只有最高位是1,其他位是0.
return n >0 && (n & (n - 1) == 0)
子集枚举
给定一个含不同整数的集合,返回其所有的子集。
S = {1,2,3}
N bit Combination
0 000 {}
1 001 {1}
2 010 {2}
3 011 {1,2}
4 100 {3}
5 101 {1,3}
6 110 {2,3}
7 111 {1,2,3}
class Solution {
public:
/**
* @param nums: A set of numbers
* @return: A list of lists
*/
vector<vector<int>> subsets(vector<int> &nums) {
int n = nums.size();
sort(nums.begin(), nums.end());
vector<vector<int>> ans;
// 共2^n - 1个子集
for (int i = 0; i < (1 << n); i++) {
vector<int> subset;
for(int j = 0; j < n; j++) {
if ((i & (1 << j)) != 0) {
subset.push_back(nums[j]);
}
}
ans.push_back(subset);
}
return ans;
}
};
只出现一次的数字
我的题解
T C − O ( n ) S C − O ( 1 ) TC-O(n) SC-O(1) TC−O(n)SC−O(1)
class Solution {
public:
int singleNumber(vector<int>& nums) {
int n = nums.size();
//任何数和0异或不变
int onceNum = 0;
for(int i = 0; i < n; i++){
onceNum ^= nums[i];
}
return onceNum;
}
};
只出现一次的数字 III
我的题解
class Solution {
public:
//TC O(n) SC-O(1)
vector<int> singleNumber(vector<int>& nums) {
int n = nums.size();
int ans = 0;
for(int i = 0; i < n ; i++){
ans ^= nums[i];
}
//ans是两个不同的数字ab异或之后的结果;
int x = 1;
while((x & ans) == 0){
x <<= 1;//x左移一位
}//直到找到ans为1的位
int a = 0;
int b = 0;
//分组
//nums[i]与x相与=1 表示nums[i]该位不为0 而原数组中两个不重复的数在该位一定不同 所以必定分到两个不同的组中 两个组中的元素数目不一定相同 这取决于原数组元素的分布 而相同的数必定会分到同一组中
for(int i = 0; i < n; i++){
if(x & nums[i])
a ^= nums[i];
else
b ^= nums[i];
}
return vector<int>{
a,b};
}
};
UTF-8 编码验证
我的题解
class Solution {
public:
bool validUtf8(vector<int>& data) {
int n = data.size();
int flag = 0;
int cnt = 0;
for(int i = 0; i < n; i++){
if(cnt == 0){
if(data[i] >> 5 == 0b110) cnt = 1;
else if(data[i] >> 4 == 0b1110) cnt = 2;
else if(data[i] >> 3 == 0b11110) cnt = 3;//剩余需要考虑的数字
else if(data[i] >> 7) return false;
}
else {
if(data[i] >> 6 != 0b10) return false;
--cnt;
}
}
//cnt != 0说明还没考察结束 就退出循环了 说明10xxxx不够 不符合题意
return cnt == 0;
}
};