引用
- 相当于给数据起别名
- 引用分配内存空间时,必须进行初始化,在初始化之后不可以改变
- 不能是多级引用
- 引用自增代表引用的变量值加1
- sizeof引用得到的是指向变量的大小
- 引用访问一个变量是直接访问
- 引用底层是通过指针实现的,本质上是一个指针常量,在内存中为引用开辟了一个指针型的内存单元
- 不能建立引用数组
- 不能建立引用的指针
- 可以对引用取地址
-
指针
记录地址,需要分配空间
- 指针访问一个变量是间接访问,解引用方式访问
- 32位系统4个字节, 64位系统8个字节
- 可以是多级指针
- 指针自增代表指向下一个空间
- sizeof 指针得到的是指针本身的大小
- 安全检查:使用指针前最好做类型检查,防止野指针的出现,引用可以防止野指针的出现
- int (*p)[3] p:一个指向元素个数为3的int数组的指针
数组做函数参数,会退化成指针(想想看,数组作为函数参数的时候经常是需要传递数组大小的)
智能指针
unique_ptr
实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象
不能更改所有权
unique_ptr<string> p3 (new string("xxx"));unique_ptr<string> p4;p4 = p3; // 此时会报错,编译器认为非法
shared_ptr (共享型,强引用)
实现共享式拥有概念,多个指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁时”释放
- 使用计数机制来表明资源被几个指针共享
- 可以通过use_count()来查看资源的所有者个数
- 可以通过new、传入unique_ptr、weak_ptr来构造
当我们调⽤ release() 时,当前指针会释放资源所有权,计数减⼀ 当计数等于 0 时,资源会被释放
weak_ptr(弱引用)
是一种不控制对象生命周期的智能指针,它指向一个shared_ptr管理的对象,它的构造和析构不会引起引用计数的增加或者减少 进行该对象的内存管理是那个强引用的shared_ptr
- 只是一个对对象的访问手段
- 只能从一个shared_ptr或另一个weak_ptr构造
-
auto_ptr(C++11已抛弃)
和unique_ptr类似
-
野指针
没有初始化的指针
- 其指向是不能确定的
函数指针
```cpp // 函数返回值类型 (* 指针变量名) (函数参数列表);include
using namespace std;
int (*p)();
int my_2() { return 2; }
int my_1() { return 1; }
int main(int argc, char const *argv[]) { p = my_1; cout << p() << endl; p = my_2; cout << p() << endl; return 0; }
<a name="Zxb6m"></a># 三种传递- 值传递- 引用传递 实质是传地址,传递的是变量的地址- 指针传递(将函数形参改为指针,可以减少内存空间,而且不会复制新的副本出来)- 实质是传值,传递的值是指针的地址<a name="Jgjxm"></a># 重载- 函数名相同,提高复用性- 条件:1. 同一作用域下1. 函数名称相同1. 函数参数类型不同, 或者个数不同, 或者顺序不同- 注意: 函数返回值不可以作为函数重载的条件- 函数重载碰到默认参数出现二义性, 要避免这种情况<a name="DAbtD"></a># sizeof- 是关键字,统计数据类型所占内存大小(字节数)- 语法: sizeof(类型)、sizeof(变量)、sizeof(fun())-->求的是函数返回值的数据类型的大小下列运算符不允许重载(5个):<br />“?:”、“.”、“::”、“sizeof”、“.*” “.”和“::”<br />下列只能通过成员函数来重载(4个);<br />=(赋值),[],(),->(指向并访问)<a name="BTqRX"></a>## sizeof(类)- 空类 == 1 因为有一个字节,才能保证类的地址独一无二。- 算上所有类的非静态成员数据的类型大小之和- >=1个虚函数由于要维护虚函数表,所以要占据一个指针大小- 静态数据成员和静态函数和成员函数不占空间- 存在继承关系时,空类不占用空间- 子类的成员变量同样占用空间- 需要考虑内存对齐<a name="rI3IP"></a>## 内存对齐- 数据成员按自己的大小和系统的两者最小数对齐- 结构体数据成员按照最大的成员进行对齐- 结构体大小必须是成员中所有对齐后的整数倍- 系统会每4字节(32位)或8字节(64位)的放,不能有数据成员放在一个地方的结尾和另一个地方的开头,要从开头开始- 类和结构体都要考虑内存对齐<a name="kojYB"></a># 面向对象三大特性:封装、继承、 多态<a name="scypW"></a>## 封装- 即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问权限;将抽象得到的数据和行为相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员- 封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,以特定的访问权限来使用类的成员- 对象有内部状态和外部的行为。封装是为了信息隐藏,通过封装来维护对象内部数据的完整性。使得外部对象不能够直接访问一个对象的内部状态,而必须通过恰当的方法才能访问- 对象属性和方法赋予指定的修改符(public、protected、private)来达到封装的目的,使得数据不被外部对象恶意的访问及方法不被错误调用导造成破坏对象的封装性<a name="DBzku"></a>## 空类编译器默认添加:空构造函数、值拷贝的拷贝构造函数、空的析构函数、值拷贝的赋值运算符<a name="Fr1Qd"></a>## 构造函数- 类名{}- 无返回值也不写void,函数名与类名相同- 可以有参数,可以有重载- 程序在创建对象时会自动调用构造函数,无须手动调用且只调用一次- 可以分为普通、拷贝、赋值运算符、转换构造函数 Student(int r) ;//形参是其他类型变量,且只有一个- 不能是虚构造<a name="Myetu"></a>### 执行顺序1. 基类的构造函数<br />2. 成员对象的构造函数<br />3. 派生类本身的构造函数<a name="NS3sM"></a>### 拷贝构造Person(const Person& p) {<br />xxxx;<br />}<a name="DlLFn"></a>#### 调用时机- 使用一个已经创建完毕的对象来初始化一个新对象- 值传递的方式给函数参数传值- 以值方式返回局部对象<a name="iW5PM"></a>## 析构函数- ~类名() {}- 不可以有参数,不可以重载,只能有一个- 程序在对象销毁前自动调用析构、无需手动调用、且只会调用一次- 执行顺序与构造函数相反- 可以是虚析构<a name="jfT2e"></a>## 访问权限- public:类内可以访问、类外可以访问- protected:类内可以访问、儿子可以访问父亲中的保护内容、类外不可以访问- private:类内可以访问、类外不可以访问<a name="EuN7x"></a>## 继承- class 子类:继承方式1 父类1,继承方式2 父类2....- 父类中所有非静态成员属性都会被子类继承- 父类private属性是被编译器给隐藏了,因此访问不到,但确实被继承了- 友元关系不能被继承,基类的友元对派生类没有特殊的访问权限<a name="SD6aV"></a>### 三种继承方式- public:除了private其他保持原来的权限继承- protected:public + protected-->protected private 不可访问- private:public + protected --> private private 不可访问<a name="O4tro"></a>### 多继承- 多继承可能会引发父类有同名成员出现,需要加作用域区分- 实际开发中不建议使用多继承- s.Base1::m_a<a name="qmkBm"></a>### 虚基类- 继承之前加上virtual- 解决菱形继承问题(子类继承两份相同的数据,导致资源浪费且毫无意义),这样只会继承一份数据- class Sheep : virtual public Animal{}; Animal称为虚基类<a name="zi5p6"></a>## 多态- 简而言之就是用父类型的指针指向其子类的实例,然后通过父类的指针调用子类的成员函数,这种技术可以让父类的指针有“多种形态”- 三个条件:1、存在继承;2、虚方法重写;3、父类指针或引用指向子类对象<a name="MYcb0"></a>### 动态多态- 派生类和虚函数实现- 函数地址晚绑定、运行阶段确定函数地址- 重写:函数返回值类型、函数名、参数列表要完全一致<a name="pGMut"></a>### 虚函数- 虚函数表、虚函数表指针- 每个有虚函数的类都会有虚函数表、这个表是一个在编译时确定的数组,存放了虚函数的地址- 虚函数指针,指向虚函数表- 可扩展性- 作用在于通过父类的指针或者引用来调用它的成员函数的时候,能够根据动态类型来调用子类相应的成员函数哪些不能是虚函数- 构造函数- 虚函数对应一个虚指针,虚指针其实是存储在对象的内存空间的。如果构造函数是虚函数,就需要通过虚函数表中对应的虚函数指针(编译期间生成属于类)来调用,可对象目前还没有实例化,也即是还没有内存空间,何来的虚指针,所以构造函数不能是虚函数- 虚函数的作用在于通过父类的指针或者引用来调用它的成员函数的时候,能够根据动态类型来调用子类相应的成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,所以构造函数不能是虚函数- 内联函数,内联函数表示在编译阶段进行函数体的替换操作,而虚函数意味着在运行期间进行类型确定,所以内联函数不能是虚函数- 静态函数,静态函数不属于对象属于类,静态成员函数没有this指针,因此静态函数设置为虚函数没有任何意义- 友元函数,友元函数不属于类的成员函数,不能被继承,对于没有继承特性的函数没有虚函数的说法- 普通函数 不属于类,不具有继承特性,因此普通函数没有虚函数<a name="vQLMg"></a>### 纯虚函数virtual 函数 = 0;<a name="CgFrt"></a>### 虚析构、纯虚析构- 可以用父类指针释放子类对象- 都需要有具体的函数实现- 纯虚析构需要声明,也需要实现- 有了纯虚析构也属于抽象类<a name="HEveG"></a>### 抽象类- 有纯虚函数的类就算抽象类,不能有实例- 子类必须重写纯虚函数,否则也是抽象类<a name="pHYbP"></a>### 静态多态- 函数重载- 运算符重载,复用函数名- 函数地址早绑定,编译阶段确定函数地址<a name="qLd3I"></a># 深拷贝浅拷贝- 浅拷贝:简单的赋值拷贝操作- 深拷贝:在堆区重新申请空间,进行拷贝操作- 程序有在堆区开辟内存,应自己实现深拷贝<a name="UTlCU"></a># 内存分区模型内存四区的意义:<br />不同区域存放的数据,赋予不同的生命周期,给我们编程更大的灵活性<a name="bL2QD"></a>## 代码区- 存放函数体的二进制代码,由操作系统进行管理- 代码区是共享的,目的是对于频繁被执行的程序,只需要在内存中有一份代码即可- 代码区是只读的,是为了防止程序意外的修改了它的指令<a name="YvL8j"></a>## 全局区- 存放全局变量、静态变量、常量- 该区域的数据在程序结束后由操作系统释放<a name="eGfBW"></a>## 栈- 由编译器自动分配回收,存放函数的参数值、局部变量等- 不会产生碎片- 是连续的空间- 注意事项:不要返回局部变量的地址<a name="zvVky"></a>## 堆- 由程序员手动new、delete、free、malloc进行分配和回收,若不释放,会造成内存泄漏(内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是[应用程序](http://baike.baidu.com/view/330120.htm)分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费)的问题,程序结束时由操作系统回收- 可能会产生内存碎片- 不连续的空间- 注意事项:释放数组时delete[] arr<a name="MrZzz"></a># C++代码到可执行二进制文件的过程大致与计算机语言的发展史相反(高级语言--> 编译-->汇编语言-->机器语言-->运行)<a name="rCyr3"></a>## 预编译1. 针对于源代码(.h .c .cpp)1. 将所有的#define删除,并展开所有的宏定义1. 处理所有条件预编译指令,如#if,#ifdef1. 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置1. 过滤所有注释1. 添加行号和文件名标识<a name="FKwnC"></a>## 编译1. 源代码-->汇编代码1. 词法分析:将源代码的字符序列分割成一系列的记号1. 语法分析:对记号进行语法分析,产生语法树1. 语义分析:判断表达式是否有意义1. 代码优化<a name="wSMfk"></a>## 汇编汇编代码-->机器可以执行的指令<a name="JISP9"></a>## 链接- 将不同的源文件产生的目标文件进行链接,从而生成一个可执行程序- 分为静态链接、动态链接- 库的好处- 代码保密- 方便部署分发<a name="o0pWF"></a>### 静态链接- 静态库在链接阶段被复制到程序中- 即使去把静态库删除也不会影响可执行程序的执行- 静态链接库Windows下.lib为后缀、Linux下以.a为后缀- 优点- 静态库被打包到了应用程序中加载速度快- 发布程序时无需提供静态库,移植方便- 缺点- 消耗系统资源、浪费内存- 更新、部署、发布麻烦<a name="KZtcc"></a>### 动态链接:- 动态库在链接阶段没有被复制到程序中,而是在运行时由系统动态加载到内存中供程序调用- 生成的可执行文件没有函数代码,只包含函数的重定位信息- 所以删除动态库时,可执行程序就不能运行- 动态链接库:windows下以.dll为后缀、Linux下以.so为后缀- 优点- 可以实现进程间资源共享(共享库)- 更新、部署、发布简单- 可以控制何时加载动态库- 缺点- 加载速度比静态库慢- 发布程序时需要提供依赖的动态库<a name="daFRe"></a># new 和 malloc的区别- new是操作符,而malloc是库函数(需要头文件支持)- new在调用的时候先分配内存,再调用构造函数,释放的时候调用析构函数;而malloc没有构造函数和析构函数- malloc需要给出申请内存的大小,返回的指针需要强转;new不用指定内存的大小,返回指针不用强转- new分配内存更直接和安全- new发生错误抛出异常,malloc返回nullmalloc底层是 当开辟的空间小于 128K 时,调用 brk()函数;当开辟的空间大于 128K 时,调用mmap()malloc采用的是内存池的管理方式,以减少内存碎片。先申请大块内存作为堆区,然后将堆区分为多个内存块。当用户申请内存时,直接从堆区分配一块合适的空闲块。采用隐式链表将所有空闲块连接,每一个空闲块记录了一个未分配的、连续的内存地址。<a name="jm5cH"></a># static改变修饰对象的生命周期或者作用域<a name="fTsY2"></a>## 修饰局部变量- 变量会存在静态区,生命周期会延续到程序结束,作用域还是在语句块内- 函数体内的静态变量内存只会被分配一次,在下次调用时依然延续上次的值<a name="wEP2a"></a>## 修饰全局变量由原来的整个工程可见--->本文件可见<a name="Kv9I2"></a>## 修饰普通函数改变了函数的作用域,由整个工程可见--->本文件可见<a name="CDDuj"></a>## 修饰类的成员函数- 该函数属于一个类,不属于任何对象- 没有this指针,只能访问类里面的静态成员- 不能被virtual修饰<a name="cSZs7"></a>## 修饰类的数据成员- 该变量为所有对象共有- 存储空间中只有一个副本可以通过类名和对象名调用- 在编译阶段分配内存- 类内声明,类外初始化<a name="jnl8e"></a># const- 用于定义常量- 生效于编译阶段- 定义的常量有类型<a name="aIeRQ"></a>## const修饰指针- int const *a; *a不变,a指向地址的内容不变 和 const int*a 一样.- int *const a; a不变,a指向的地址不变- int const* const a; a指向的地址和内容都不变<a name="TChNK"></a>## const修饰函数参数- 常用于参数为指针或者引用的情况,防止函数修改参数<a name="tJVlt"></a>## const修饰类成员变量- const成员变量只有在某个类的生命周期内是常量,对于整个类而言是可以变的<a name="tZpQx"></a>## const修饰类的成员函数- 防止成员函数修改类的内容,不能和static同时使用- const函数中只能调用其他const函数,不能调用非const函数- 不能在const函数中修改类对象的数据<a name="jaTzu"></a>## const修饰类对象- const对象只能调用const函数- 非const对象也可以调用const函数<a name="Lo9Z7"></a># C和C++区别- C++在C语言基础上引入了面对对象的机制,同时也兼容C语言、C++是一门面向对象的语言,C是面向过程的语言- C++支持函数重载,C不支持- C++可复用性更高、支持模板、内置强大的stl,C没有- C++更加健壮,更加安全,有trycatch、四种强制类型转换运算符、智能指针、const常量、引用,C没有- C++有新增的语法和关键字、新特性,比如引用,new,delete- C和C++的strcut不同<a name="hX7UQ"></a>## C和C++结构体的区别C结构体不能有成员函数、静态成员、访问权限默认是public且不可修改、不可以继承、C 中使用结构体需要加上 struct 关键字,或者对结构体使用 typedef 取别名,而 C++ 中可以省略 struct 关键字直接使用<br />C++结构体能有成员函数、静态成员、访问权限、可以继承<a name="FHeAq"></a># C++结构体和类的区别- 默认的访问权限不同,结构体是public、类是private- 默认继承方式不同,结构体是public、类是private<a name="AYzBG"></a># 类型转换- 分为显式转换和隐式转换- 推荐使用四种强制类型转换运算符<a name="euGTZ"></a>## static_cast- 不执行运行时类型检查(转换安全性不如 dynamic_cast)- 通常用于转换数值数据类型(如 float -> int)- 可以在整个类层次结构中移动指针,子类转化为父类安全(向上转换),父类转化为子类不安全(因为子类可能有不在父类的字段或方法)- 在c++ primer 中说道:c++ 的任何的隐式转换都是使用 static_cast 来实现```cppfloat f = 4.5;int b = static_cast<int>(f);cout << b << endl;
const_cast
- 用于删除 const、volatile 特性(如将 const int 类型转换为 int 类型 )
- const_cast可用于更改const成员函数内的非const类成员
- const_cast可用于将const数据传递给不接收const的函数
const_cast比简单类型转换更安全 从某种意义上讲,如果强制类型与原始对象不相同,则强制转换不会发生,这是比较安全的
reinterpret_cast
允许将任何指针—>任何其他指针类型、任何整数类型—>任何指针类型以及反向转换
- 它不检查指针类型、指针所指向的数据是否相同
reinterpret_cast是一种非常特殊且危险的类型转换,并且建议使用适当的数据类型时使用它,即(指针数据类型应与原始数据类型相同)
dynamic_cast
将expression转换为type-id类型,type-id必须是类的指针、类的引用或者是void *;
- 如果type-id是指针类型,那么expression也必须是一个指针;如果type-id是一个引用,那么expression也必须是一个引用
- 最简单的上行转换,比如Derived 继承自Basic,Derived 转换为Basic,进行上行转换时,是安全的,
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全
内联函数
所有成员函数默认为内联函数,包括构造函数
- 会直接展开,避免了调用函数的开销
-
volatile
volatile 告诉编译器,不要对我这个变量进行各种激进的优化
- 易变性:在汇编层面反映出来,就是两条语句,下⼀条语句不会直接使用上⼀条语句对应的 volatile 变量的寄存器内容,而是从内存中读取
顺序性:能够保证 volatile 变量之间的顺序性,编译器不会进行乱序优化
C++11新特性
nullptr
- auto变量
- Lambda表达式
- 右值引用
- 左值:可以取地址
- 右值不能取地址
- 右值引用 int&& b = 8;
- 左值引用 int& a = num;
- 需要注意的,和声明左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化
- 智能指针
move 本意为 “移动”,但该函数并不能移动任何数据,它的功能很简单,就是将某个左值强制转化为右值。基于 move() 函数特殊的功能,其常用于实现移动语义。move() 函数的用法也很简单,其语法格式如下move( arg ) //其中,arg 表示指定的左值对象。该函数会返回 arg 对象的右值形式。
STL
常见容器
vector
动态数组
- erase后(后边的每个元素的迭代器会失效),erase返回下一个有效的迭代器
- 如何扩容:另外开辟2倍空间,拷贝过去,再释放原空间
- 底层:三个迭代器
- 支持随机读写O(1)
-
list
双向链表
- erase后面元素的迭代器不会失效
- 不支持随机读写O(n)
-
set/multiset
集合,set不允许重复元素,multiset允许重复元素
- 基于红黑树
-
map/multimap
映射,红黑树,mulitmap可重复
-
unordered_map, unordered_set
底层哈希表
-
resize和reserve的区别
resize(n, element) // 改变size,resize变大时补齐,变小时去掉多余的元素
-
友元
友元本质上是普通函数,不在类范畴中,没有 this、成员的概念、也不能是虚函数
- 友元类不具有传递性、继承性、双向性
-
explicit
指定构造函数或转换函数 (C++11起)为显式,即它不能用于隐式转换和赋值初始化
其他
TrieNode* children = new TrieNode[26];
- 从技术上来说,堆(heap)是 C/C++ 语言和操作系统的术语,堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,使用malloc()、free() 来申请/释放内存。
- 自由存储是 C++ 中通过 new 和 delete 动态分配和释放对象的抽象概念。基本上,所有的 C++ 编译器默认使用堆来实现自由存储。也就是说,默认的全局运算符 new 和 delete 也许会使用 malloc 和free 的方式申请和释放存储空间,这时自由存储区就位于堆上。但程序员也可以通过重载操作符,改用其他内存来实现自由存储,例如全局变量做的对象池,这时自由存储区就不位于堆上了。
总结
因此,自由存储区和堆的区别是:堆是操作系统维护的一块内存,是一个物理概念,而自由存储是C++中通过new与delete动态分配和释放的对象的存储区,是一个逻辑概念。堆与自由存储区并不等价。
