- 堆/栈/全局(静态)存储区/常量存储区/程序代码区
- 封装/继承/多态
- 封装
- 其实就是public 和 private
-
继承
-
多态:将基类类型指针或引用指向派生类的对象,接口重用。
- 重载:允许存在多个同名函数,这些函数的参数表不同,返回类型也可以不同。
- 覆盖(重写): 指定子类重新定义父类的虚函数的做法。在父类定义一个virtual方法,在子类重写这个方法。参数/返回类型都必须相同,如果只有返回类型不同,会报错;否者会视为普通的函数重载,虚函数特性丢失,访问修饰符可以不同(public/private)
- 重定义:子类中重新定义父类中相同名称的非虚函数,派生类的函数隐蔽类与其同名的基类函数。可以理解为发生在继承中的重载。
- 虚函数:虚函数的作用是允许在派生类中重新定义与积累同名的函数。
- 析构和构造函数和栈相似。
- 虚函数:在基类中冠以关键字virtual的成员函数,提供一种接口节面,允许派生类中对基类的虚函数重新定义。
- 纯虚函数:在基类中为其派生类保存一个函数的名字,以便派生类根据需要对它进行定义,作为接口而存在,纯虚函数不具备函数的功能,一般不能被直接调用。
- 包含至少一个纯虚函数的类为抽象类,抽象类必须作为其他派生类的基类,不能直接创建对象实例。
- 带有虚函数的每一个对象都有一个虚指针指向虚表。
- 引用不存在空引用,必须连接到一块合法的内存。必须初始化,指可以在任何时间被初始化。(不会分配地址)
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。
- 将“引用”作为函数参数有哪些特点:(1)效果和传递指针是一样的(2)指针调用时,需要用变量的地址,而引用则不需要。
- 常(const)引用:保护传递给函数的数据不在函数中被改变。
- 将“引用”作为函数返回值的格式,好处和需要遵守的规则
- 好处:在内存中不产生返回值的副本。
- 注意事项:(1)不能返回局部变量的引用。(2)不能返回函数内部new分配的内存的引用(函数返回的引用只是作为一个临时变量的出现,没有被赋予一个实际的变量,这个引用指向的空间无法被释放,造成内存泄漏。
- struc和union都是由多个不同的数据类型成员组成,但在任何同一时刻,union中只存放一个被选中的成员,而struct的所有成员都存在。在struct中,各成员都有自己的内存空间,同时存在,而union的不能同时存在,union变量的长度等于最长的成员长度。
- const / reference / 基类的构造函数
- 不是,两个不同类型的指针可以强制转换。
- 所有预处理器指令都以#开头,不是C++语句,所以不会以分号结尾。
- const与# define相比,优点:编译器可以对const进行类型安全检查
- 指针有自己的一块空间,而引用只是一个别名。
- 使用sizeof看一指针的大小是4,而引用则是被引用对象的大小。
- 指针可以初始化为NULL,引用必须初始化且必须是一个已有对象的引用。
- 可以有const指针,没有const引用
- 作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接引用的修改都改变引用所指向的对象。
- 返回动态内存非陪的对象或内存,需要指针,引用可能引起内存泄露。
- 操作系统和编译器通过内存分配的位置来区分全局变量和局部变量。
- 静态存储区分配,全局变量
- 在栈上创建
- 在堆上创建
- static 在类的实例之间共享
- static用于全局变量,只有本文件能访问
- static用于局部变量,延长局部变量周期(不要在头文件声明static全局函数,如果在多个cpp中复用该函数,就把他放在头文件,反之设置未static,只有当前文件可见,不能被其他文件所用)。
- static用于成员变量,所有对象共享该变量,且不需要生成对象就可以访问该成员
- static修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是static函数不能访问非静态成员。
- 对于一个进程的内存空间而言,可以在逻辑上分为:代码区,静态数据区(全局变量和静态变量)和动态数据区(本地变量)。
- 相当于把内联函数里面的内容写在调用内联函数处。
- 相当于不用执行进入函数的步骤,直接执行函数体
- 相当于宏,比宏多了类型检查
- 编译器一般不内联包含循环,递归,switch等复杂的内联操作
- 在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。
- 内联函数只是对编译器地建议,是否对函数内联,决定权在于编译器。
- C++面向对象,C面向过程
- 语法:
- C++具有封装,继承,多态。
- 增加许多类型安全的功能,比如类型强制转换。
- 范式编程,模板类,函数模板。
C++: static_cast, dynamic_cast, const_cast, reinterpret_cast
-
const_cast : 调用了一个参数为const的函数,但是传进去的参数不为const,但是我们知道这个函数是不会对参数做修改的,所以用const_cast去除const限定。只能作用在指针和引用上,一般指针不能指向const。
-
static_cast(静态转换,编译期间转换失败会抛出编译错误):
-
dynamic_cast: 向下转换会有安全检查,只能用于指针或引用。例如: A->B->C->D, 只有指向A的基指针指向D类时可以像下转换成B/C类的指针。
-
reinterpret_cast :一些static_cast不能完成的转换。
- 作用
- 智能指针的作用是管理一个指针,在函数结束后自动释放内存资源,智能指针的作用就是函数结束时自动释放内存空间,不需要手动释放内存空间。
- 是一个类。
- auto_ptr:
- 可以有多个auto_ptr同一个内存,不会报错,不过前面指向内存的指针访问时会报错。
- unique_ptr
- 如果多个unique_ptr指向同一个内存,会报错。
- weak_ptr:解决share_ptr相互调用引起的死锁问题,构造和析构不会引起对象的计数的改变。不能直接访问对象的方法,必须先通过.lock()转化成share_ptr (解决智能指针中内存泄漏的问题)
- share_ptr: 多个指针指向同一个对象,该对象会在最后一个引用被销毁时释放。用use_count返回引用计数的个数。
指针指向一个已删除的对象或者未申请访问受限内存区域的指针
- 用途:
- 用于调用函数和做函数的参数
- 修饰类里面的成员函数,表示该成员函数不会对对象做任何修改。
- 常量必须初始化,对于局部对象,存放于栈区;对于全局对象,存放于全局/静态存储区(全局变量和静态变量是放一起的)。对于字面值常量,存放于常量存储区(还有程序代码区)。
- 运行时检查:在C++层面主要体现在dynamic_cast和typeid,对于存在循环函数的类型,会先去查询虚函数表的Type_info
- 生成一个临时变量,把它的引用作为函数参数传入函数中。
-
Vector:
- 连续存储的容器,动态数组,在堆上分配空间
- 底层实现:数组
- 两倍容量增长:
- vector插入新元素时,如果空间够,在最后插入,调整迭代器(快)
- 没有剩余空间,开辟2倍空间,拷贝。(慢)
- 场景:随机访问,不经常对非尾节点进行插入和删除
-
list:
- 底层实现是双向链表
- 不支持随机访问
- vector是顺序内存,list不是
- vector在中间节点插入删除会导致内存拷贝,list不会
- list每次插入新节点都会进行内存申请
- 场景:高效的插入删除。
reisze: 改变含有元素数量,reverse():改变容器的大小。如果当前resize大于reverse,则会发生开辟新内存,值的拷贝。
- 同一个类能访问任意(public, protected, private)
- 外部只能范文public成员。
- protected: 只能被类中,子类成员函数,友元函数访问。
- C++中class默认问private, struct默认为public
-
左值可以寻址,右值不可以
-
左值可以被赋值,右值不可以被赋值,可以用来给左值赋值。
-
左值可变,右值不可变。
-
右值引用:int &&a = i + 2 ; const 引用可以绑定右值
- 预编译
- 编译
- 汇编
- 链接
- C++中,虚拟内存分为代码段,数据段,BSS段,堆段,文件映射区以及栈
- 代码段:包括只读存储区和文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码。
- 数据段:存储程序中已初始化的全局变量和静态变量。
- bss段:存储未初始化的全局变量和静态变量(全局+局部),以及所有初始化为0的全局变量和静态变量
- 堆区:new/malloc在堆区动态分配内存
- 映射区:存储动态链接库以及调用mmap函数进行文件映射
- 栈:使用栈空间存储函数的返回地址,参数,局部变量,返回值。
- new在申请空间的时候会调用构造函数,malloc不会
- new申请失败时返回bad_alloc(成功时是对象类型指针),malloc返回NULL(成功时是void*)
- new是C++关键字,需要编译器支持,malloc是库函数,需要加头文件
- set, map:红黑树
- vector: 数组 list: 链表
- const常量有数据类型,宏常量没有数据类型。编译器可以对前者进行安全检查,对后者没有只进行字符替换,没有类型安全检查,并且可能出现1*1+1和1*(1+1)的错误。
- 修改内容上的差别
- 用运算符sizeof可以计算数组的容量。数组作为函数的参数进行传递时,数组自动退化为同类型的指针。
- 操作对象不同,strcpy的两个操作对象均为字符串,sprintf的操作对象可以是多种数据类型
- 执行效率不同,memcpy>strcpy>sprintf
- 实现功能不同,strcpy主要实现字符串变量间的拷贝,sprintf主要实现其他数据类型到字符串的转化,memcpy主要是内存块间的拷贝。
- 常量指针指向常量
- 指针常量指的是这个指针是常量
- private, public, protected
- private: 1.该类的函数 2.友元函数
- protected 2. 该类的函数 2.子类的函数 3. 友元函数
- public: 1. 该类的函数 2.子类的函数 3. 友元函数 4. 该类的对象
- 友元函数包括3种:(1)设为友元的普通的非成员函数(2)设为友元的其他类的成员函数(3)设为友元类种的所有成员函数。
- 堆比栈快,因为栈是编译时分配的,堆是动态分配的。
- 堆是不连续的(由低地址到高地址),栈是连续的(相反)
- 堆容易产生内存碎片
- 申请方式不同:堆会遍历链表,寻找第一个空间中大于所申请空间的堆结点,然后将该节点从空闲节点链表删除,会在这块内存空间中首地址处记录本次分配的大小,方便删除。
- 申请效率不同(栈由系统分配,速度快)
- 大小限制不同(堆是链表,由系统虚拟内存限制)
- 栈:在函数调用时,第一个入栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,主函数的下一条指令。
- vptr是在构造函数中初始化的,如果构造函数为virtual函数,没有vptr指向虚表。
- 引用作为函数参数时效率高,但是可能被更改,如果不希望被更改,则需要用常引用。
- 临时对象(string func1(); "hello") 都是const型的,将const型复制给引用参数是非法的。
-
宏定义
- do{}while(0) 防止出现没有{}的情况
-
文件包含
-
条件编译
-
#ifdef语句
-
extern "C":
__cplusplus是C++的预定义宏
-
-
布局控制
- 成员函数不占用空间
- static成员不占空间
- 下标和insert的区别:下标能覆盖,insert不能覆盖map.insert(map<int, string>::value_type (1, "student"))
- erase有坑:map.erase(iter++) 必须iter++
- 红黑树查找,插入,删除都是O(logn)
- 每个结点要么是红的要么是黑的
- 根节点是黑的
- 每个叶节点(NULL)都是黑的
- 如果一个结点是红的,那么它的两个儿子都是黑的
- 任意节点到树尾端NIL指针的每条路径都包含相同数目的黑节点
- g++ -E xxx.cpp -o xxx.i //只进行编译
- 预编译:(1)将所有的#define删除,展开宏定义(2)处理所有预编译指令#if, #ifdef, #else #endif(3)处理#include预编译指令(4)过滤注释 (5)添加行号和标识名(6)保留所有的#pragma编译器指令
- 编译: g++ -S xxx.cpp -o xxx.s
- 链接:各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确的衔接。主要包括地址和空间的分配,符号决议和重定位这些步骤。
- g++ -o main.cpp -L. -lxxx 需要有头文件定义。
- 动态链接:把一些库函数的链接载入推迟到程序运行期间
- 静态库和动态库重名:先搜索动态库,再搜索静态库
- 动态库和静态库的区别
- 动态库有利于进程间资源共享:当某个程序在运行中要调用某个动态链接库函数,操作系统首先会查看所有正在运行的程序,看在内存里是否已有此库函数的拷贝,如果有,则让其共享那一个拷贝;这样可以多个程序共享同一个C语言标准库的代码段
- 使用静态库,库发生变化,使用库的程序要重新编译
- 静态库快,因为动态库函数必须在运行的时候被装载,所以程序在执行的时候,用静态库速度更快
- gcc(-lstdc++ 才行)不能自动和c++程序使用的库链接
- unordered_map:
- 哈希冲突(1)
- 不是的,被free回收的内存会首先被ptmalloc使用双链表保存起来,当用户下一次申请内存的时候,会尝试从这些内存中寻找合适的返回。这样就避免了频繁的系统调用,占用过多的系统资源。同时ptmalloc也会尝试对小块内存进行合并,避免过多的内存碎片。