文章目录
1、helloworld从开始到打印到屏幕的全过程
2、迭代器
- 迭代器模式又称游标模式,用于提供⼀种方法顺序访问⼀个聚合对象中各个元素, 而又不需暴露该对象的内部表示;
- 迭代器是类模板,模拟了指针的一些功能,表现的像指针但不是指针。重载了指针的一些操作符,本质是封装了原生指针,是指针概念的一种提升,提供了比指针更高级的行为,相当于一种智能指针,可以根据不同类型的数据结构来实现不同的++,–等操作;
- 迭代器返回对象的引用而不是对象的值,所以cout只能输出迭代器使用*取值后的值;
- 指针能指向函数而迭代器不行,迭代器只能指向容器。
什么时候迭代器失效
1、vector扩容时:去了新内存,迭代器所指的原有内存释放;
2、insert导致失效:扩容的话和1相同;或者插入位置之后的被移动部分的迭代器会失效;
3、erase导致失效:删除操作引起vector内元素移动,被移动部分的迭代器失效。
3、模板
编译器从函数模板通过具体类型产⽣不同的函数;
编译器会对函数模板进⾏两次编译:在声明的地⽅对模板代码本身进⾏编译;在调⽤的地⽅对参数替换后的代码进⾏编译。
这是因为函数模板要被实例化后才能成为真正的函数,在使⽤函数模板的源⽂件中包含函数模板的头⽂件,如果该头⽂件中只有声明,没有定义,那编译器⽆法实例化该模板,最终导致链接错误。
4、Lambda表达式
提供了一个类似匿名函数的特性,可以编写内嵌的匿名函数,⽤以替换独⽴函数或者函数对象,并且使代码更可读。
// 指明返回类型,托尾返回类型
auto add = [](int a, int b) -> int { return a + b; };
// ⾃动推断返回类型
auto multiply = [](int a, int b) { return a * b; };
int sum = add(2, 5); // 输出: 7
int product = multiply(2, 5); // 输出: 10
优点:
- 声明式编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或者函数对象。以更直接的方式去写程序,好的可读性和可维护性。
- 简洁:不需要额外再写一个函数或者函数对象,避免了代码膨胀和功能分散,让开发者更加集中精力在手边的问题,同时也获取了更高的生产率。
- 在需要的时间和地点实现功能闭包,使程序更灵活。(闭包是指将当前作用域中的变量通过值或者引用的方式封装到lambda表达式当中,成为表达式的一部分,它使你的lambda表达式从一个普通的函数变成了一个带隐藏参数的函数。)
5、右值引用
左值:可以取地址的、有名字的值。
右值:分为纯右值和将亡值。纯右值即临时变量和不限对象关联的字面量值;将亡值是跟右值引用相关的将要被移动的对象。
- int a = b+c, a 就是左值,其有变量名为a,通过&a可以获取该变量的地址;
- 表达式b+c、函数int func()的返回值是右值,在其被赋值给某一变量前,我们不能通过变量名找到它,&(b+c)这样的操作则不会通过编译。
右值引用:右值引用关联到右值时,右值被存储到特定位置,右值引用指向该特定位置,该地址表示临时对象的存储位置。
特点:
- 通过右值引用的声明,右值又“重获新生”,其生命周期与右值引用类型变量的生命周期一样长,只要该变量还活着,该右值临时量将会一直存活下去
- 右值引用独立于左值和右值。意思是右值引用类型的变量可能是左值也可能是右值
- T&& t在发生自动类型推断的时候,它是左值还是右值取决于它的初始化。
template<typename T>
void fun(T&& t)
{
cout << t << endl;
}
//---------------------
int getInt()
{
return 5;
}
//--------------------
int main() {
int a = 10;
int& b = a; //b是左值引用
int& c = 10; //错误,c是左值不能使用右值初始化
int&& d = 10; //正确,右值引用用右值初始化
int&& e = a; //错误,e是右值引用不能使用左值初始化
const int& f = a; //正确,左值常引用相当于是万能型,可以用左值或者右值初始化
const int& g = 10;//正确,左值常引用相当于是万能型,可以用左值或者右值初始化
const int&& h = 10; //正确,右值常引用
const int& aa = h;//正确
int& i = getInt(); //错误,i是左值引用不能使用临时变量(右值)初始化
int&& j = getInt(); //正确,函数返回值是右值
fun(10); //此时fun函数的参数t是右值
fun(a); //此时fun函数的参数t是左值
return 0;
}
6、委托构造函数
委托构造函数可以使用当前类的其他构造函数来帮助当前构造函数初始化。
就是可以将当前构造函数的部分或全部职责交给本类的另一个构造函数。
class myBase {
int number; string name;
myBase( int i, string& s ) : number(i), name(s){}
public:
myBase( ) : myBase( 0, "invalid" ){}
myBase( int i ) : myBase( i, "guest" ){}
myBase( string& s ) : myBase( 1, s ){ PostInit(); }
};
7、default和delete
声明自定义的类型之后,C++编译器会默认生成一些成员函数,这些成员函数被称为默认函数。如构造函数、拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符、析构函数、&*等一些操作符函数等。
**default:**用于6中特殊成员函数,显示指定编译器去生成该函数的默认版本;
delete: 禁止成员函数的使用,限制生成默认函数;还能避免编译器做一些不必要的隐式数据转换
class Example
{
public:
Example(int i) {}
Example(char c) = delete;
};
int main()
{
Example ex(1);
Example ex1('a'); // 无法通过编译
}
8、哈希冲突
哈希冲突:当两个不同的数经过哈希函数计算后得到了同一个结果,即他们会被映射到哈希表的同一个位置时,即称为发生了哈希冲突。简单来说就是哈希函数算出来的地址被别的元素占用了。
1.线性探测
使用hash函数计算出的位置如果已经有元素占用了,则顺序向后依次寻找,找到表尾则回到表头,直到找到一个空单元
2.开链法
每个表格维护一个list,如果hash函数计算出的格子相同,则按顺序存在这个list中
3.再散列
发生冲突时使用另一种hash函数再计算一个地址,直到不冲突
4.二次探测
使用hash函数计算出的位置如果已经有元素占用了,按照 1 2 1^2 12、 2 2 2^2 22、 3 2 3^2 32…的步长依次寻找,即跳跃式探测。如果步长是随机数序列,则称之为伪随机探测
5.公共溢出区
一旦hash函数计算的结果相同,就放入公共溢出区
9、内存对齐
1、平台原因:不是所有的硬件平台都能访问任意内存地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。为了同一个程序可以在多平台运行,需要内存对齐。
2、硬件原因:经过内存对齐后,CPU访问内存的速度大大提升。原先可能需要两次寻址,对齐后只需要一次寻址。
CPU读取内存不是一次读取单个字节,而是一块一块的来读取内存,块的大小可以是2,4,8,16个字节,具体取多少个字节取决于硬件。