1、sizeof 运算符来计算 int, float, double 和 char 变量占用的空间大小
unsigned int n;
unsigned long long factorial = 1;
2、补码,反码,原码
- 原码: 符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值
- 反码: 正数的反码是其本身;负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.
- 补码: 正数的补码就是其本身;负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1.
输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示
思路:
把一个整数减去1,再和原整数做与运算,会把整数最右边一个1变成0.那么一个整数的二进制表示中有多少个1,就可以进行多次这样的操作
避免死循环,我们可以不右移输入的数字n。首先把n和1做与运算,判断n的最低位是不是为1。接着把1左移一位得到2,再和n做与运算,就能判断n的次低位是不是1…….这样反复左移,每次都能判断n的其中一位是不是1
3、快速幂算法
比如求3999,我们可以把999分解为(512+256+128+64+32+4+2+1),相当于对999的二进制1111110111的各位都求幂然后再相乘
class Solution {
public:
double Power(double base, int exp) {
int p;
double res;
float tmp;
if(exp0)return 1;
if(base0)return 0;
p = abs(exp);
res = 1;
tmp = base;
while§{
if(p&1){
res = restmp; //按位与运算,右移
}
p = p>>1;
tmp = tmptmp;
}
return exp<0?1/res:res;
}
};
4、const 常量折叠
所谓的常量折叠是编译器的一种优化技术,也就是代码编译时时 const 常量表达式直接替换成立即数。
不过需要注意的时,const 常量仍然会分配内存空间。
#include <iostream>
using namespace std;
int main(void)
{
const int a = 10;
int * p = (int *)(&a);
*p = 20;
cout<<"a = "<<a<<", *p = "<<*p<<endl;
int& c = const_cast<int&>(a);
c = 20;
cout << a<<" "<< c<<endl;
return 0;
}
输出的结果为:a的值是10,*p的值和c的值都是20。
5、柔性数组
所谓的柔性数组,是指结构体的最后一个成员的为长度为0的数组,这个数组本身不占结构体的内存,只是一个地址标记,相当于一个指针,标记结构体的结束地址和数组的起始地址。结合malloc 在堆上动态分配内存,相当于在结构体的末尾分配了动态数组,此之谓柔性。
struct XXX{
int size;
void data[0];
}
malloc(sizeof(XXX) + buff_len)
, 便相当于结构体的末尾分配了一个 data[buff_len] 的数组。
6、无符号和有符号的比较
int i = -1;
unsigned j = 1;
j < i; #表达式为真
while(a -b) #这个是死循环,永远为真,因为 不管a>b还是a<b,最后结果都是无符号数。无符号数永远大于0.
char i = -1;
unsigned char j = 1;
j < i; //表达式为假
7、关键字和关键字出现的次数构成的pair来构造map可以模拟multiset
map的operator [] 运算:
如果关键字存在, 返回值是value 类型, 如果关键字不存在, 会将关键字插入, 返回返回插入的value 的引用, 对于类类型, 调用默认构造函数来初始化这个value, 对于内置类型, 初始化为0. 所以对于const 类型的map, 不能进行operator[]运算,防止修改数据.
注意map的元素是pair 类型, 且pair<const key_type, mapped_type>也就是.first 成员是个const 类型, 不能被修改.
8、C++11允许按照对象的值类型来重载
9、折半法查找
折半法查找判断循环结束的条件一定是low <= high, 一定要有=;最后才能从两个元素中锁定要high的那个,否则只能够锁定到low。(二分法看区间定义)
10、参数的入栈顺序
this指针不入栈,其他参数从右往左入栈,由于栈是向下增长的,所以可以通过左边的参数的地址推算右边参数的地址,这也是C语言中可变参数的原理。函数内部,按申明的顺序入栈,所以先定义的是大地址。
11、拷贝构造函数只能传引用,不能传值
传值,拷贝构造函数的调用时,实参到形参的传递就会调用拷贝构造函数,这样会陷入无休止的递归调用当中。
12、++a的值就是a的值,a++是个临时值(a本身的值再增加1).
参数的计算顺序从右往左,跟入栈的输入相同: 如果是临时变量,直接用临时变量的值代替临时变量;如果是左值,需要全部计算完,带入最终的值。
13.数字转字符串
1.std::string to_string(int value); //C++11
2.sprintf(); //低版本
3.stringstream.str();
14、C++保存小数位数要引用头文件#include ,在要保存的小数变量之前加上“fixed << setprecision(x)”表示保存x位有效数字
setiosflags(ios::fixed)<<setprecision(x):保存小数
格式化
输出时间格式(01:08:31)
#include “stdlib.h”
#include
#include
using namespace std;
int main(){
int a=1;
cout.setf(ios::right);
cout.fill(‘0’);
cout.width(2);
cout<<a<<endl;;
system(“pause”);
return 0;//01
}
15、cin在遇到空格、回车时,都会认为当前输入已经结束,自动切换到下一输入。
cin.getchar()
可以读入
读入
string s; // 定义⼀个空字符串s
cin>>s;//不包括空格
getline(cin, s); // 读取⼀整⾏的字符串,包括空格
#include <stack>
stack<int> s;
s.empty() #如果栈为空返回true,否则返回false
s.size() #返回栈中元素的个数
s.pop() #删除栈顶元素但不返回其值
s.top() #返回栈顶的元素,但不删除该元素
s.push() #在栈顶压入新元素
e.g.
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> s; // 定义⼀个空栈s
for (int i = 0; i < 6; i++) {
s.push(i); // 将元素i压⼊栈s中
}
cout << s.top() << endl; // 访问s的栈顶元素
cout << s.size() << endl; // 输出s的元素个数
s.pop(); // 移除栈顶元素
return 0;
}
q.push(x):将x元素接到队列的末端;
q.pop() 弹出队列的第一个元素,并不会返回元素的值;
q.front()访问队首元
q.back()访问队尾元素
q.size()访问队中的元素个数
priority_queue<node> q;
q.empty() 如果队列为空,则返回true,否则返回false
q.size() 返回队列中元素的个数
q.pop() 删除队首元素,但不返回其值
q.top() 返回具有最高优先级的元素值,但不删除该元素
q.push() 在基于优先级的适当位置插入新元素
#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> s;
s.insert(2);//向集合添加元素
s.insert(3);//向集合添加元素
cout << *(s.begin()) << endl; //输出第一个元素
for (int i = 0; i < 10; i++) {//插入0 - 9
s.insert(i);
}
for (set<int>::iterator it = s.begin(); it != s.end(); it++) {
cout << *it << " ";//集合的遍历,it是一个迭代的指针
}
cout << endl << (s.find(2) != s.end()) << endl;//查找,元素
s.erase(3);//删除元素
cout << (s.find(3) != s.end()) << endl;
return 0;
}
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
map<string, string> m;
m["hello"] = "world"; // 存储键为 "hello" 值为 "world"的键值对
cout << m["hello"] << endl; // 访问map中key为"hello"的value,
cout << m["world"] << endl;// 如果key不存在,则返回0或空
m["world"] = "test"; // 将"world"键对应的值修改为3
m["key"] = "value"; // 设置键为 "key" 值为"value" 的键值对
//迭代器遍历,输出map中所有的元素,键it->first获取,值it->second获取
for (map<string, string>::iterator it = m.begin(); it != m.end(); it++) {
cout << it->first << " " << it->second << endl;
}
cout << m.begin()->first << " " << m.begin()->second << endl;
// 访问map的第一个元素,输出它的键和值
cout << m.rbegin()->first << " " << m.rbegin()->second << endl;
// 访问map的最后一个元素,输出它的键和值
cout << m.size() << endl;
// 输出map的元素个数
return 0;
}
vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。
vectortest; // 定义的时候不指定vector的⼤⼩
vectortest2(6); // 定义的时候指定vector的⼤⼩,默认test2⾥⾯元素都是0
vectortest3(6, 3); // 定义的时候指定vector的⼤⼩,默认test3⾥⾯元素都是3
尾部添加两个元素,0的位置存储5, 1 的位置存储8
test.push_back(5);
test.push_back(8);
test.insert(test.begin()+i,value);//在第i+1个元素前面插入value;
test.erase(test.begin() + 5);//删除第6个元素
test.begin();//起始地址
test.end();//结束地址
test.clear();
test.size();
sort(test.begin(),test.end());
reverse(test.begin(),test.end());//反转
遍历
for (vector<int>test::iterator it = m.begin(); it != m.end(); it++) {
cout << *it << endl;
}
#include <iostream>
#include <vector>
using namespace std;
vector<int> dynamicArray() {
int n = 0;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
return a;
}
int main() {
//输入数组元素时,回车与空格相同
vector<int> a = dynamicArray();
return 0;
}
动态数组的优点就是静态数组的缺点改正以后:
1、运行时创建,不必事先规定大小
2、数组越界会被debug到
3、可以作为函数返回值
STL六大部件:
容器 container:存储数据,用户不必关心数据在内存的存储形式,只需要知道怎么用容器接口
分配器 allocator:帮助容器在内存规划位置
算法 algorithm:与数据分离,能够执行某种操作
迭代器 iterator:连接容器(数据)和算法(操作)的桥梁,好像一种泛化指针
适配器 adapter:进行转换,可以针对容器、仿函数、迭代器
仿函数 Funtor:作用像一个函数
标准库规定,容器的头尾是前闭后开,即[ )类型
容器分类
序列式容器:线性排列≠大小排序
array:由C++本身的数组包装得到。大小固定,不能改变
vector:能够自动扩充的数组,只能在尾部扩充
deque:双向队列,可以在头尾扩充
–V&D访问效率高 在尾部增加或删除元素的效率最高(时间复杂度为 O(1) 常数阶),在其它位置插入或删除元素效率较差(时间复杂度为 O(n) 线性阶,其中 n 为容器中元素的个数)
list:双向链表
–任何地方都可以高效地增加或删除元素(时间复杂度都为常数阶 O(1)),但访问容器中任意元素的速度要比前三种容器慢,时间复杂度为 O(n) 线性阶
forward-list:单向链表
stack 和 queue 本质上也属于序列容器,只不过它们都是在 deque 容器的基础上改头换面而成,通常更习惯称它们为容器适配器
#include <list>
和array、vector、deque 容器的迭代器相比,list 容器迭代器最大的不同在于,其配备的迭代器类型为双向迭代器【链表】,而不再是随机访问迭代器。
支持使用 ++p1、 p1++、 p1–、 p1++、 *p1、 p1==p2 以及 p1!=p2 运算符,但不支持以下操作(其中 i 为整数):
p1[i]:不能通过下标访问 list 容器中指定位置处的元素。
p1-=i、 p1+=i、 p1+i 、p1-i:双向迭代器 p1 不支持使用 -=、+=、+、- 运算符。
p1<p2、 p1>p2、 p1<=p2、 p1>=p2:双向迭代器 p1、p2 不支持使用 <、 >、 <=、 >= 比较运算符。
erase()
成员函数是按照被删除元素所在的位置来执行删除操作,如果想根据元素的值来执行删除操作,可以使用 remove() 成员函数。remove(aim)是删除链表中的aim元素,若有多个aim,都会删除
unique()
去重
emplace()
每次只能插入一个元素,而不是多个
#include<algorithm>
:remove swap()
remove() 函数删除掉 demo 容器中的多个指定元素,该容器的大小和容量都没有改变,其剩余位置还保留了之前存储的元素。我们可以使用 erase() 成员函数删掉这些 “无用” 的元素。remove()用于删除容器中指定元素时,常和 erase() 成员函数搭配使用。
在调用erase()或clear()删除元素后,vector的size()确实减小了,但是被删除元素原先占用的内存却并没有得到释放,不进行释放的话,可能会造成内存上的浪费。使用swap()能够在不析构vector的前提下,释放多余的内存。
#include <deque>
和 vector 相比,额外增加了实现在容器头部添加和删除元素的成员函数,同时删除了 capacity()、reserve() 和 data() 成员函数。
如begin()、迭代器的功能是遍历容器,在遍历的同时可以访问(甚至修改)容器中的元素,但迭代器不能用来初始化空的 deque 容器。对于空的 deque 容器来说,可以通过 push_back()、push_front() 或者 resize() 成员函数实现向(空)deque 容器中添加元素。
关联式容器(有序)
有key和value,十分适合需要大量查找的数据
set/multiset:底层红黑树。key就是value,即红黑树的每个节点上只存储一个key值,该key值就=value值。
set表示存放元素的key不能重复,multiset表示存放元素的key可以重复
#include <set>
set 容器类模板中未提供 at() 成员函数,也未对 [] 运算符进行重载。
双向迭代器:假设 p 为此类型的迭代器,则其只能进行 ++p、p++、–p、p–、*p 操作,并且 2 个双向迭代器之间做比较,也只能使用 == 或者 != 运算符。
map/multimap:底层红黑树。key和value分开存放,即红黑树的每个节点上既要存储key值,还要存储value值。
map表示存放元素的key不能重复(键值不能修改),multimap表示存放元素的key可以重复
以红黑树为底层的关联式容器,key值都是有序存放的,因为红黑树本身就是有序的
#include <map>
在使用 map 容器存储多个键值对时,该容器会自动根据各键值对的键的大小,按照既定的规则进行排序。默认情况下,map 容器选用std::less排序规则(其中 T 表示键的数据类型),其会根据键的大小对所有键值对做升序排序。降序:std::greater
e.g. map<string, int, greater >myMap{ {“C语言教程”,10},{“STL教程”,20} };
Map访问:[ ]或者at()
但multimap 未提供 at() 成员方法,也没有重载 [] 运算符。
无序式容器
其实也是一种关联式容器,但是独立了出来
unordered set/multiset:底层哈希表
unordered map/multimap:底层哈希表
C++ STL 底层采用哈希表实现无序容器时,会将所有数据存储到一整块连续的内存空间中,并且当数据存储位置发生冲突时,解决方法选用的是“链地址法”(又称“开链法”)。
1、无序容器内部存储的键值对是无序的,各键值对的存储位置取决于该键值对中的键
2、和关联式容器相比,无序容器擅长通过指定键查找对应的值(平均时间复杂度为 O(1));但对于使用迭代器遍历容器中存储的元素,无序容器的执行效率则不如关联式容器。
#include<unordered_map>
通过 find() 方法得到的是一个正向迭代器,该迭代器的指向分以下 2 种情况:
当 find() 方法成功找到以指定元素作为键的键值对时,其返回的迭代器就指向该键值对;
当 find() 方法查找失败时,其返回的迭代器和 end() 方法返回的迭代器一样,指向容器中最后一个键值对之后的位置。
unordered_set容器模板类中没有重载 [ ] 运算符,也没有提供 at() 成员方法。不仅如此,由于容器内部存储的元素值不能被修改,因此无论使用那个迭代器方法获得的迭代器,都不能用于修改容器中元素的值。
C++11新规定的遍历方式
for (decl : coll) {
statement
}
//decl是declare,声明一个变量
//coll是collection,表示一个元素集合/容器
//示例1:遍历集合中的元素
for (int i :{2,3,4,5}) {
cout << i << endl;
}
//示例2:传值输出集合中的元素
for (auto e : vec) {
cout << e << endl;
}
//示例3:传址修改集合中的元素
for (auto& e : vec) {
e *= 3;
}
迭代器
常用的迭代器按功能强弱分为输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器 5 种。常用的就是后三种,输入迭代器和输出迭代器比较特殊,它们不是把数组或容器当做操作对象,而是把输入流/输出流作为操作对象。
前向迭代器(forward iterator):
假设 p 是一个前向迭代器,则 p 支持 ++p,p++,*p 操作,还可以被复制或赋值,可以用 == 和 != 运算符进行比较。此外,两个正向迭代器可以互相赋值。
双向迭代器(bidirectional iterator)
双向迭代器具有正向迭代器的全部功能,除此之外,假设 p 是一个双向迭代器,则还可以进行 --p 或者 p-- 操作(即一次向后移动一个位置)。
随机访问迭代器(random access iterator)
随机访问迭代器具有双向迭代器的全部功能。除此之外,假设 p 是一个随机访问迭代器,i 是一个整型变量或常量,则 p 还支持以下操作:
p+=i:使得 p 往后移动 i 个元素。
p-=i:使得 p 往前移动 i 个元素。
p+i:返回 p 后面第 i 个元素的迭代器。
p-i:返回 p 前面第 i 个元素的迭代器。
p[i]:返回 p 后面第 i 个元素的引用。
此外,两个随机访问迭代器 p1、p2 还可以用 <、>、<=、>= 运算符进行比较。另外,表达式 p2-p1 也是有定义的,其返回值表示 p2 所指向元素和 p1 所指向元素的序号之差(也可以说是 p2 和 p1 之间的元素个数减一)。