1.顺序容器
1.顺序容器:vector,deque,list,forward_list,array,string。其中除list和forward_list外,其它都支持快速随机访问。
deque a = { 1, 2, 3, 4, 5, 6 };
cout << a[4] << endl ;
2.通常使用vector
forward_list和list的额外内存开销很大。
3.若容器的元素类型没有默认构造函数,在使用容器大小参数构造顺序容器时,必须提供一个元素初始化器。(P294-P295)
4. reverse_iterator 按逆序寻址元素的迭代器
5.迭代器范围:标准库的基础
包含两个迭代器,元素范围是[begin,end)
6.只有顺序容器(array除外)的构造函数才能接受大小参数
7.顺序容器是assign操作(array不支持assign)
用参数指定的元素的拷贝替换左边容器的所有元素——相当于左边容器清空后再用右边指定元素赋值。
a.assign(b);
a.assign(iter1,iter2); 某个容器的两个迭代器
a.assign(n,t); //a的size变成n,每个元素都设为t。
assign会使迭代器,指针和引用失效。
8.swap 两个swap的容器要相同类型 (array的类型包括元素类型和size)
除array外:swap不对任何元素拷贝,删除,插入,移动等操作。 仅交换了容器的名字。O(1)时间复杂度。
array:swap会交换元素,O(n)时间复杂度。
array和string的迭代器,指针和引用会失效。其他容器则不会。
2.指针问题
- char*c[]={"ENTER","NEW","POINT","FIRST"};
- char**cp[]={c+3,c+2,c+1,c};
- char***cpp=cp;
- intmain(void)
- {
- printf("%s",**++cpp);
- printf("%s",*--*++cpp+3);
- printf("%s",*cpp[-2]+3);
- printf("%s ",cpp[-1][-1]+1);
- return0;
- }
3.右值引用
左值和右值:
左值是表达式结束后依然存在的持久对象。
右值是表达式结束时就不再存在的临时对象。
区分左值右值的快捷方法:看能不能对表达式取值,如果能是左值,不能则是右值。
左值引用:
分为非常量左值引用和常量左值引用
非常量左值只能绑定到非常量左值,不能绑定到常量左值,非常量右值和常量右值。
常量左值引用可以绑定到所有类型的值,包括非常量左值,常量左值,非常量右值和常量右值
右值引用:
非常量右值:只能绑定到非常量右值,不能绑定到非常量左值、常量左值和常量右值。
常量右值:可以绑定到非常量右值和常量右值,不能绑定到非常量左值和常量左值。
如果提供了move版本的构造函数,就不会生成默认构造函数。
编译器永远不会自动生成move版本的构造函数和复制函数,需要手动显示添加。
选择构造和赋值的重载函数优先级顺序:
1.常量值只能绑定到常量引用上,不能绑定到非常量引用上。
2.左值优先绑定到左值引用上,右值优先绑定到右值引用上。
2.非常量值优先绑定到非常量引用上。
在move版本的构造函数或赋值函数内部,都直接移动了其内部数据的指针(因为它是非常量右值,是一个临时对象,移动了其内部数据的指针不会导致任何问题,它马上就要被销毁了,我们只是重复利用了其内存 ),这样节省了拷贝数据的大量开销。
右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。临时对象的维护 ( 创建和销毁 ) 对性能有严重影响。
转移语义是和拷贝语义相对的,可以类比文件的剪切与拷贝,当我们将文件从一个目录拷贝到另一个目录时,速度比剪切慢很多。
通过转移语义,临时对象中的资源能够转移其它的对象里。
转移构造函数:
1. 参数(右值)的符号必须是右值引用符号,即“&&”。
2. 参数(右值)不可以是常量,因为我们需要修改右值。
3. 参数(右值)的资源链接和标记必须修改。否则,右值的析构函数就会释放资源。转移到新对象的资源也就无效了。
标准库函数std::move
编译器只对右值引用才能调用转移构造函数和转移赋值函数。而所有命名空间都只能是左值引用。
std::move用来将左值引用转换为右值引用。
精确传递perfect forwarding
将一组参数原封不动的转递给另一个函数
原封不动是指:参数值不变,还有左值/右值,const/not-const属性都不变。
接受一个右值引用的参数,就能够将所有的参数类型原封不动的传递给目标函数。
们在设计类的时候如果有动态申请的资源,也应该设计转移构造函数和转移拷贝函数。在设计类库时,还应该考虑 std::move 的使用场景并积极使用它
4.拷贝构造函数、析构函数
定义一个类是,要显示或者隐式的指定该类型的对象拷贝、移动、赋值和销毁时做什么。
拷贝构造函数的第一个参数必须是一个引用类型:如果不是引用类型,则调用永远不会成功。因为想要调用拷贝构造函数,必须拷贝实参,单位了拷贝实参,有需要调用拷贝构造函数,无限循环。
即使定义了其他构造函数,编译器默认生成合成拷贝构造函数。
explicit:抑制狗杂函数的隐式转换。
重载运算符本质上是函数,起名字由operator关键字后接表示要定义的运算符的符号组成。
赋值运算符通常返回一个指向其左侧运算对象的引用。
析构函数:释放对象使用资源,销毁对象的非static数据成员。
不接受参数,不能被重载,一个给定类只有一个析构函数。
当指向一个对象的引用或指针离开作用域时,析构函数不会执行。
需要析构函数的类也需要拷贝和赋值操作
需要拷贝操作的类也需要赋值操作,需要赋值操作的类也需要拷贝操作
但需要拷贝构造函数或需要拷贝赋值运算符的类不意味着也需要析构函数。
当累的程艳中有指针类型,需要在析构函数中显示delete来释放空间,并且拷贝构造函数和拷贝赋值运算符要求深拷贝而不是浅拷贝。
=default修饰成员声明,显示要求编译器生成合成的版本。
类内用=default表示合成的函数隐式声明为内联
类外用=default表示何晨固定成员不是内联函数
5.内存溢出、内存泄漏
内存泄漏最终会导致内存溢出
内存溢出:申请了一个int却存放了一个long long
内存泄漏:已申请的内存无法释放
3、内存溢出 vs 内存泄漏
【关系】内存泄露是导致内存溢出的原因之一;内存泄露积累起来将导致内存溢出。
【out of memory:】要求分配的内存超出系统能给你的,系统不能满足需求。
例如:申请了一个integer,但存入了一个long,会导致内存溢出。
申请了10个字节的空间,但是写入11个字节的数据。
可能的原因:
(1) 使用非类型安全(non-type-safe)的语言如 C/C++ 等。
(2) 以不可靠的方式存取或者复制内存缓冲区。
(3) 编译器设置的内存缓冲区太靠近关键数据结构。
注意:
1. 内存溢出问题是 C 语言或者 C++
语言所固有的缺陷,它们既不检查数组边界,又不检查类型可靠性(type-safety)。众所周知,用 C/C++
语言开发的程序由于目标代码非常接近机器内核,因而能够直接访问内存和寄存器,这种特性大大提升了 C/C++
语言代码的性能。只要合理编码,C/C++ 应用程序在执行效率上必然优于其它高级语言。然而,C/C++
语言导致内存溢出问题的可能性也要大许多。其他语言也存在内容溢出问题,但它往往不是程序员的失误,而是应用程序的运行时环境出错所致。
2. 当应用程序读取用户(也可能是恶意攻击者)数据,试图复制到应用程序开辟的内存缓冲区中,却无法保证缓冲区的空间足够时(换言之,假设代码申请了 N
字节大小的内存缓冲区,随后又向其中复制超过 N 字节的数据)。内存缓冲区就可能会溢出。想一想,如果你向 12 盎司的玻璃杯中倒入 16
盎司水,那么多出来的 4 盎司水怎么办?当然会满到玻璃杯外面了!
3. 最重要的是,C/C++
编译器开辟的内存缓冲区常常邻近重要的数据结构。现在假设某个函数的堆栈紧接在在内存缓冲区后面时,其中保存的函数返回地址就会与内存缓冲区相邻。此时,恶意攻击者就可以向内存缓冲区复制大量数据,从而使得内存缓冲区溢出并覆盖原先保存于堆栈中的函数返回地址。这样,函数的返回地址就被攻击者换成了他指定的数值;一旦函数调用完毕,就会继续执行“函数返回地址”处的代码。非但如此,C++
的某些其它数据结构,比如 v-table 、例外事件处理程序、函数指针等,也可能受到类似的攻击。
【memoryleak:】系统无法再给你提供内存资源(内存资源耗尽),通常是没能及时清理内存垃圾。
可能的原因:
程序逻辑问题,申请的内存无法释放(如死循环);对象的引用被无意识的保留起来,使得该对象无法被GC;缓存,一旦把对象引用放入缓存中,它很容易被忘掉。(当所需要的缓存项的声明周期由该键的外部引用而不是值决定时,应使用WeakHashMap代表缓存。)监听器和其他回调,如果对于某个接口,客户端在其中注册了回调,却没有显式地取消注册,则它们就会积聚。(应该只保存其弱引用)
预防:
1) 尽早释放无用对象的引用,尤其是大对象。
好的办法是使用临时变量的时候,让引用变量在退出活动域后自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄露。
2) 程序进行字符串处理时,尽量避免使用String,而应使用StringBuffer。
因为每一个String对象都会独立占用内存一块区域
3) 尽量少用静态变量。
因为静态变量是全局的,GC不会回收。
4) 避免集中创建对象尤其是大对象,如果可以的话尽量使用流操作。
JVM会突然需要大量内存,这时会触发GC优化系统内存环境;如使用jspsmartUpload作文件上传,运行过程中经常出现java.outofMemoryError的错误
5) 尽量运用对象池技术以提高系统性能。
生命周期长的对象拥有生命周期短的对象时容易引发内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略。
典型的例子就是android中AdapterView对应的Adapter中bindview会重用已有的对象元素。
6) 不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。
可以适当的使用hashtable,vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次new之后又丢弃。
7) 优化配置
在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。
6.sizeof malloc/free new/delete strlen inline 关键字
数据对齐:
指数据所在内存地址必须是该数据长度的整数倍。
sizeof:
float的大小是4字节,double大小是8字节
malloc/free和new/delete的区别
都可以申请动态内存和释放内存。
malloc/free是库函数,只能对内置类型的对象做操作。
new/delete是运算符,在分配内存时会自动执行构造函数,释放内存的的时候执行析构函数。
sizeof和strlen的区别:
sizeof是运算符,strlen是函数。
sizeof可以用类型做参数,strlen只能用char *做参数,而且必须是以“