(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。
(2)可以有const指针,但是没有const引用;
(3)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
(4)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
(5)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
(6)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小;
(7)指针和引用的自增(++)运算意义不一样;
link
link
link
1.函数默认值
2.inline内联函数
3.函数重载
4.const
5.引用
6.malloc,free && new,delete
7.作用域
struct默认public类型,class默认private类型
一:区别
(1)就起作用的阶段而言: #define是在编译的预处理阶段起作用,而const是在 编译、运行的时候起作用。
(2)就起作用的方式而言: #define只是简单的字符串替换,没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。
(3)就存储方式而言:#define只是进行展开,有多少地方使用,就替换多少次,它定义的宏常量在内存中有若干个备份;const定义的只读变量在程序运行过程中只有一份备份。
(4)从代码调试的方便程度而言: const常量可以进行调试的,define是不能进行调试的,因为在预编译阶段就已经替换掉了。
二:const优点
(1)const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
(2)有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。
(3)const可节省空间,避免不必要的内存分配,提高效率
const作为类成员为什么只能在初始化列表中被初始化,C++11提供了类内初始化的方式
static 作为类成员,所有类对象共享该成员,只能在类外部的.cpp文件中被初始化,初始化时不用指明static属性;
非静态成员函数后面加const(加到非成员函数或静态成员后面会产生编译错误),表示成员函数隐含传入的this指针为 const指针,决定了在该成员函数中,任意修改它所在的类的成员的操作都是不允许的(因为隐含了对this指针的const引用);唯一的例外是对于 mutable修饰的成员。加了const的成员函数可以被非const对象和const对象调用,但不加const的成员函数只能被非const对象调 用。
link
底层const是代表对象本身是一个常量(不可改变);
顶层const是代表指针的值是一个常量,而指针的值(即对象的地址)的内容可以改变(指向的不可改变);
final关键字说明某个类不能被继承,或者某个虚函数不能被重写(必须是虚函数),放在函数后面。
override关键字在函数后面,显示说明这个函数是重写的。
link
拷贝初始化和直接初始化:(1)对于一般的内建类型,这两种初始化基本上没有区别。(2)当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象,然后使用复制构造函数将那个临时对象复制到正在创建的对象
初始化和赋值的区别:对象的初始化是说你在声明的时候就调用默认的或者非默认的构造函数进行初始化工作,而赋值指的是你用一个已经存在的对象去给另一个已经存在的对象赋值。
被extern “C”的段用c语言的方式编译。
顺序容器:vector,deque是随机访问迭代器;list是双向迭代器
容器适配器:stack,queue,priority_queue没有迭代器
关联容器:set,map,multiset,multimap是双向迭代器
unordered_set,unordered_map,unordered_multiset,unordered_multimap是前向迭代器
type_traits
iterator_traits
char traits
allocator_traits
pointer_traits
array_traits
在一个vector的尾部之外的任何位置添加元素,都需要重新移动元素。而且,向一个vector添加元素可能引起整个对象存储空间的重新分配。重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移到新的空间
link
link
link
link
虚函数表指针vptr的初始化时间:
父类构造函数之后,子类构造函数之前
多重继承
link
link
如果基类没有定义为虚函数,则delete 的时候,仅仅调用了父类的析构函数,子类的没有调用,如果在父类和子类的构造函数中都有动态内存分配,那么就会存在内存泄漏的问题。一般析构函数最好都写成虚函数,尤其是父类。
构造函数:对象不存在,没用别的对象初始化
拷贝构造函数:对象不存在,用别的对象初始化
赋值运算符:对象存在,用别的对象给它赋值
全都需要构造
虚函数调用是在部分信息下完成工作的机制,允许我们只知道接口而不知道对象的确切类型。 要创建一个对象,你需要知道对象的完整信息。 特别是,你需要知道你想要创建的确切类型。 因此,构造函数不应该被定义为虚函数。
= default:将拷贝控制成员定义为=default显式要求编译器生成合成的版本
= delete:将拷贝构造函数和拷贝赋值运算符定义删除的函数,阻止拷贝(析构函数不能是删除的函数 C++Primer P450)
= 0:将虚函数定义为纯虚函数(纯虚函数无需定义,= 0只能出现在类内部虚函数的声明语句处;当然,也可以为纯虚函数提供定义,不过函数体必须定义在类的外部)
link
因为对象的类型是确定的,在编译期就确定了
指针或引用是在运行期根据他们绑定的具体对象确定。
link
浅拷贝可能会析构两次
零拷贝
对象复用
赋值函数(重载),拷贝构造函数,移动构造函数,构造函数,移动赋值函数,析构函数
link
1 当用类的一个对象去初始化类的另一个对象时。
2 当函数的形参是类的对象,调用函数进行形参和实参的结合时。
3 当函数的返回值是对象,函数执行完成返回调用者时
link
平台移植性好,cpu效率高
1.什么是内存泄漏(Memory Leak)?
简单地说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。
2、如何检测内存泄露
第一:良好的编码习惯,尽量在涉及内存的程序段,检测出内存泄露。当程式稳定之后,在来检测内存泄露时,无疑增加了排除的困难和复杂度。使用了内存分配的函数,一旦使用完毕,要记得要使用其相应的函数释放掉。
第二:将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改链表。
第三:Boost 中的smart pointer。
第四:一些常见的工具插件,如ccmalloc、Dmalloc、Leaky等等。
link
shared_ptr:在析构函数中和赋值重载中,要判断引用计数,若为0,要释放自己的空间,在赋值时引用计数加一。
link
core dump
valgrind
mtrace
link
因为对于非内置类型,少了一次调用默认构造函数的过程。
link
link
static_cast
dynamic_cast
const_cast
reinterpret_cast
link
link
link
volatile
禁止编译器优化,每次从内存中读取数据,有利于线程安全。
volatile用在如下的几个地方:
优化方法
link
访问范围:
private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问.
protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问
public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问
注:友元函数包括两种:设为友元的全局函数,设为友元类中的成员函数
父类与其直接子类的访问关系如上,无论是哪种继承方式(private继承、protected继承、public继承)。
对于三种继承关系的不同:
public继承:public继承后,从父类继承来的函数属性不变(private、public、protected属性不变,)。
private继承:private继承后,从父类继承来的函数属性都变为private
protected继承:protected继承后,从父类继承过来的函数,public、protected属性变为protected,private还是private。
class默认为private,struct默认为public
链接
1, auto的作用
一般来说, 在把一个表达式或者函数的返回值赋给一个对象的时候, 我们必须要知道这个表达式的返回类型, 但是有的时候我们很难或者无法知道这个表达式或者函数的返回类型. 这个时候, 我们就可以使用auto关键字来让编译器帮助我们分析表达式或者函数所属的类型.
2, auto和const
auto会忽略掉顶层const, 保留底层const.
3, auto和引用
① 如果表达式是引用类型, 那么auto的类型是这个引用的对象的类型.
② 如果要声明一个引用, 就必须要加上&, 如果要声明为一个指针, 既可以加上也可以不加
1, decltype的作用
decltype只是为了推断出表达式的类型而不用这个表达式的值来初始化对象.
2, decltype和const
不论是顶层const还是底层const, decltype都会保留
3, decltype和引用
① 如果表达式是引用类型, 那么decltype的类型也是引用
② 如果表达式是引用类型, 但是想要得到这个引用所指向的类型, 需要修改表达式:
③ 对指针的解引用操作返回的是引用类型
④ 如果一个表达式的类型不是引用, 但是我们需要推断出引用, 那么可以加上一对括号, 就变成了引用类型了
内联和宏
(1)内联函数在编译时展开,宏在预编译时展开;
(2)内联函数直接嵌入到目标代码中,宏是简单的做文本替换;
(3)内联函数有类型检测、语法判断等功能,而宏没有;
(4)inline函数是函数,宏不是;
(5)宏定义时要注意书写(参数要括起来)否则容易出现歧义,内联函数不会产生歧义;
类型安全
c的类型安全只在局部上下文中体现,c只在结构体指针转换上会报错,并且有隐式转换,malloc和new相比也并不是类型安全的。
c++类型安全:
(1)操作符new返回的指针类型严格与对象匹配,而不是void*;
(2)C中很多以void*为参数的函数可以改写为C++模板函数,而模板是支持类型检查的;
(3)引入const关键字代替#define constants,它是有类型、有作用域的,而#define constants只是简单的文本替换;
(4)一些#define宏可被改写为inline函数,结合函数的重载,可在类型安全的前提下支持多种类型,当然改写为模板也能保证类型安全;
(5)C++提供了dynamic_cast关键字,使得转换过程更加安全,因为dynamic_cast比static_cast涉及更多具体的类型检查。
右值引用
1.通过右值引用的声明,右值又“重获新生”,其生命周期与右值引用类型变量的生命周期一样长,只要该变量还活着,该右值临时量将会一直存活下去。
2.常量左值引用是一个“万能”的引用类型,可以接受左值、右值、常量左值和常量右值,常量左值引用只能接受左值。
3.意思是右值引用类型的变量可能是左值也可能是右值。
4.T&& t在发生自动类型推断的时候,它是未定的引用类型(universal references),如果被一个左值初始化,它就是一个左值;如果它被一个右值初始化,它就是一个右值,它是左值还是右值取决于它的初始化。
5.以移动构造函数替代拷贝构造函数的深拷贝可以节省深拷贝的构造析构开销,把左值转为右值引用,转移资源所有权
6.完美转发:在函数模板中,完全依照模板的参数的类型(即保持参数的左值、右值特征),将参数传递给函数模板中调用的另外一个函数。C++11中的std::forward正是做这个事情的,他会按照参数的实际类型进行转发。
unique_ptr