声明:本文内容纯属博主自己查找和归纳的个人所需的知识点,仅作参考,如有错误,博主强烈希望您指出。如果您是某个知识点的原创博主,如有需要,可联系本人加上链接。本文内容会根据博主所需进行更新,希望大家多多关照。
面向对象
把数据及对数据的操作方法放在一起,作为一个相互依存的整体
OOP——面向对象的编程,OOD——面向对象的设计,OOA——面向对象的分析
三大特征:
封装
将对象的属性和方法封装到一个独立单元中,并且隐藏对象的属性和方法,仅对外提供公共访问方式,将变化隔离,便于使用。继承
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法。继承是多态的前提。多态
多态是面向对象的最后一个主要特征,它本身主要分为两个方面:
方法的多态性:重载与覆写
重载:同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同。
重写:子类中的方法与父类中的某个方法的名称和参数完全相同,子类对象调用这个方法时,将调用子类中方法对象的多态:父子类对象的转换。
向上转型:子类对象变为父类对象,格式:父类 父类对象 = 子类实例,自动;
向下转型:父类对象变为子类对象,格式:子类 子类对象 = (子类)父类实例,强制。
多态存在的三个必要条件:继承、重写、父类引用指向子类对象
c++类里面编译器默认生成的函数
1.构造函数
2.拷贝构造函数
3.赋值构造函数
4.析构函数
voliate关键字的作用
使用voliate声明变量值的时候,系统总是重新从它所在的内存读取数据;用voliate声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错.
使用场景:多任务环境下各任务间共享的标志应该加voliate;存储器映射的硬件寄存器通常也要加voliate,因为每次对它的对写都可能有不同意义;
const的用法
const修饰的内容不可改变,C++有5种用法:
定义常量
指针常量和常量指针:
指针常量——指针类型的常量
int *const p
本质上一个常量,指针用来说明常量的类型,表示该常量是一个指针类型的常量。在指针常量中,指针自身的值是一个常量,不可改变,始终指向同一个地址。在定义的同时必须初始化;常量指针——指向“常量”的指针
const int *p
,int const *p
常量指针本质上是一个指针,常量表示指针指向的内容,说明该指针指向一个“常量”。在常量指针中,指针指向的内容是不可改变的
const修饰函数传入参数
修饰函数返回值
const修饰成员函数(c++特性)
const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数;
const对象的成员是不能修改的,但是可以用另外的指针指向成员去修改;
const成员函数不可以修改对象的数据,不管对象是否具有const性质。编译时以是否修改成员数据为依据进行检查。
const和宏的区别
const是有数据类型的常量,而宏常量没有,编译器可以对前者进行静态类型安全检查,对后者仅是字符替换,没有类型安全检查
有些编译器可以对const常量进行调试, 不能对宏调试。
宏能作为卫哨来防止文件的重复包含,但const不可以
static的作用
隐藏。static修饰的变量只在当前文件生效,故使用static在不同的文件中定义同名函数和同名变量,不必担心命名冲突。
保持变量内容的持久。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。
static的第三个作用是默认初始化为0
new和malloc的区别
new和delete是c++的运算符,malloc和free是C语言的标准库函数
特征 | new/delete | malloc/free |
---|---|---|
分配内存的位置 | 自由存储区 | 堆 |
内存分配失败返回值 | 完整类型指针 | void* |
内存分配失败返回值 | 默认抛出异常 | 返回NULL |
分配内存的大小 | 由编译器根据类型计算得出 | 必须显式指定字节数 |
处理数组 | 有处理数组的new版本new[] | 需要用户计算数组的大小后进行内存分配 |
已分配内存的扩充 | 无法直观地处理 | 使用realloc简单完成 |
是否相互调用 | 可以,看具体的operator new/delete实现 | 不可调用new |
分配内存时内存不足 | 客户能够指定处理函数或重新制定分配器 | 无法通过用户代码进行处理 |
函数重载 | 允许 | 不允许 |
构造函数与析构函数 | 调用 | 不调用 |
C++存储机制
栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量。
堆区(heap): 存放malloc申请的内存,使用free释放
全局区(静态区):存放全局变量和静态变量,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后由系统释放。
常量区 :常量字符串就是放在这里的。 程序结束后由系统释放。
自由存储区:存放new申请的内容,使用delete
为什么会出现段错误
访问不存在的内存地址
访问系统保护的内存地址
访问只读的内存地址
栈溢出
引用和指针的区别
引用是变量的别名,一定有本体,指针是是存放地址的一个变量
引用声明时就必须初始化,指针可以暂时不初始化
指针的使用需要注意指向的内存,防止出现野指针的情况,引用则不需要
你所了解的c++常见的标准库
<iostream>、<fstream>、各种容器、<algorithm>、<ctime>、<memory>(智能指针)、<cstdio> 、<functional>(lambda表达式)、<complex> 、<cmath>、<cstring>(free,malloc需要包含<stdlib.h>)、<iterator>等等
三种智能指针
注意,智能指针只是指针本身自动释放,解决野指针的问题,但其对象一样要手动释放
unique_ptr:
一个 unique_ptr “拥有” 它所指向的对象。某个时刻只能有一个 unique_ptr 指向一个给定对象。当 unique_ptr 被销毁时,它所指向的对象也被销毁。不能拷贝和赋值,但可以用std::move、release、reset转移,或者用函数返回局部的unique_ptrshared_ptr
最终的实现是两个指针成员:一个指向数据成员,一个指向计数器成员,计数器维护的是一个指针,指向的实际内存在堆上,可以用make_shared<>()创建和初始化shared_ptr
释放方法:
如果该shared_ptr是唯一指向其对象的shared_ptr,则直接可以<shared_ptr> = nullptr或者<shared_ptr>.reset()释放对象weak_ptr
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。
作用:
可判断shared_ptr的对象是否存在(使用 lock方法),从而避免访问一个不存在的对象的情况。
解决shared_ptr循环引用问
虚函数原理
虚函数是用来实现动态绑定的。
C++中虚函数使用虚函数表和虚函数表指针实现,虚函数表是一个类的虚函数的地址表,用于索引类本身以及父类的虚函数的地址,假如子类重写了父类的虚函数,则在虚函数表中会把对应的虚函数地址替换为子类的函数的地址;虚函数表指针存在于每个对象中,它指向对象所在类的虚函数表的地址;在多继承环境下,会存在多个虚函数表指针,分别指向对应不同基类的虚函数表。
虚函数表是每个(有虚函数的)类对应一个。虚函数表指针是每个对象对应一个。
如果一个函数不是虚函数,那么对它的调用在编译阶段就会确定。而虚函数要运行时才能确定。
为什么需要虚析构函数
在存在类继承并且析构函数中需要析构某些资源时,析构函数需要是虚函数。否则若使用父类指针指向子类对象,在delete时只会调用父类的析构函数,而不能调用子类的析构函数,造成内存泄露。
如何解决类之间的相互依赖问题
写两个头文件A.h和B.h分别用于声明类A和B;
写两个.cpp文件分别用于定义类A和B,定义B的.cpp文件里包含A.h;
在A的头文件中导入B的头文件,在main文件包含A.h即可;
在B的头文件中不导入A的头文件,前置声明类A,并且,在B中使用A的时候要用指针的形式。
单例设计模式
保证一个类仅可以有一个实例化对象,并且提供一个可以访问它的全局接口。
实现单例模式必须注意一下几点:
- 单例类只能由一个实例化对象。
- 单例类必须自己提供一个实例化对象。
- 单例类必须提供一个可以访问唯一实例化对象的接口
2种方法:
- 双重判断+上锁
- 使用std::call_once函数
std::call_once()函数:第二个参数为函数名,保证函数只调用一次,比互斥量消耗小
注意:可以创建内部类进行释放对象内存,不能用析构函数,因为会出现一直调用析构函数的死循环
双重判断+上锁:
std::mutex resource_mutex;
class MyCAS
{
private:
MyCAS(){}//构造函数私有化,不能直接创建对象
static MyCAS *m_instance;
public:
static MyCAS *GetInstance()
{
if(m_instance == NULL)//双重判断+上锁,防止多线程多次new对象
{
std::unique_lock<std::mutex> mymutex(resource_mutex);
if(m_instance == NULL)
{
m_instance = new MyCAS();
static CGarhuishou cl;
}
}
return m_instance;
}
//此类用于自动删除内存,不能用析构函数,因为在delete了外部指针,会调用析构函数delete m_instance,
//而m_instance指向对象为MyCAS,然后又会再次调用析构函数,变成死循环
class CGarhuishou
{
public:
~CGarhuishou()
{
if(MyCAS::m_instance)
{
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};
};
MyCAS *MyCAS::m_instance = NULL;
MyCAS *p_a = MyCAS::GetInstance();//创建线程前先初始化单例对象
使用std::call_once函数:
std::once_flag g_flag;
std::mutex resource_mutex;
class MyCAS
{
private:
MyCAS(){}
static MyCAS *m_instance;
public:
static void CreateInstance()
{
m_instance = new MyCAS();
static CGarhuishou cl;
}
static MyCAS *GetInstance()
{
//第一个线程进来call_once执行函数后,第二个线程才能进来判断是否执行
std::call_once(g_flag, CreateInstance);//g_flag看成执行了函数的标记
return m_instance;
}
class CGarhuishou
{
public:
~CGarhuishou()
{
if(MyCAS::m_instance)
{
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};
};
工厂设计模式
- 简单工厂模式:由一个工厂判断类型然后创建不同的对象
- 工厂方法模式:多个工厂创建相应的对象
- 抽象工厂模式:多个工厂创建相应的对象组合
i++和++i的区别
- 注意不要只看问题表面,可以拓展
i++是返回原来的值再+1,++i是+1再返回值
i++不能作为左值,++i可以作为左值(左值:可以用&获取内存地址),因为重写前缀++的函数内容是+1操作,返回值为引用形式,而后缀++的函数内容是把原来的值赋给一个临时的变量,然后原来的值+1,返回临时变量
如果i是迭代器或者其他自定义的类,根据第二点的描述,++i的效率明显高于i++
引用和指针的区别
引用是变量的别名,一定有本体,指针是是存放地址的一个变量
引用声明时就必须初始化,指针可以暂时不初始化
指针的使用需要时刻注意指向的内存,防止出现野指针的情况,引用则不需要