C++整体回顾

12 篇文章 0 订阅
3 篇文章 0 订阅

进程虚拟地址空间

前提: 32 CPU 32 linux 内核
2^32 4G )的虚拟地址空间,分别包括用户空间( 3G )和内核空间( 1G ),每一个进程用户空间是私
有的,内核空间是共享的
用户空间: 0x08048000 开始 .text .rodata .data .bss heap 共享库区域 (*.so) stack 命令行参数 环
境变量 PATH
内核空间: ZONE_DMA(16M) Direct Memory Access ZONE_NORMAL(896M) .text .rodata. heap
stack
ZONE_HIGHMEM( 高端内存 用户空间采用的地址映射方式是二级页表映射,而内核空间的地址映射方
式采用的线性映射,那么 1G 以上的物理内存如何进行映射,就靠这块高端内存区域了 )
堆内存 heap 分配,从低地址 到 高地址;栈内存 stack 的分配,从高地址到低地址。 data 段的内存程序
启动时候分配,程序运行结束内存释放; heap 内存是在调用 new 或者 malloc 的时候分配,调用 delete
free 的时候释放;调用函数分配新的栈空间,函数出右括号占内存释放。
32 linux 创建进程 fork (资源划分的单位)的上限: 0-32767 进程间的通信(匿名管道、命令管道、
消息队列、共享内存、信号量);创建线程 pthread_create ,一个进程创建线程的上限数量是多少(线
程栈的大小是 8M ), 3G / 8M = 384 ,如果如何提高进程里面创建的线程的数量???用 limit 设置系统
创建线程默认的线程栈的大小
一个进程里面创建的线程,每一个线程都有自己的栈空间,但是各个线程共享当前进程的 data heap
堆空间。多线程对比多进程的应用,其好处是线程间通信方式( sem )简单,方便共享变量(放在
heap 或者 data ),而且线程占用的资源少,所以线程的上下文切换所耗费的系统性能也少。
虚拟内存
虚拟内存是操作系统管理内存的一种方案,该方案至少提供了以下几点功能:
1. 给系统运行的每一个进程,都分配 4G 的虚拟地址空间
2. 保证了所有进程虚拟地址空间的用户空间是隔离的,不能够互相访问
3. 放物理内存紧张的时候,再需要分配物理内存,系统会根据 LRU (最近最久未使用)算法,把相应
的物理页面( dirty 脏页面 - 数据被修改过的页面)写入磁盘的 swap 交换分区当中(产生磁盘
I/O ),后面如果再需要使用这一块页面的数据,又会从磁盘的 swap 交换分区当中把数据再读回物
理页面当中
函数详细的调用过程 main -> sum(int a, int b) 函数的运行,系统是需要分配一块栈空间的,系统通过 CPU ebp esp 指针
来标识一个函数栈的栈底和栈顶。
1. 从右向左的顺序把 sum 函数的实参一次入栈
2. call 指令的下一行指定的地址入栈
3.push ebp : 把 main 函数的栈底地址入栈
4.mov ebp, esp : 让 ebp 指针指向 sum 函数的栈底
5.sub esp,4Ch :给 sum 函数分配栈帧
6.rep stos for ):把 [ebp,esp] 栈初始化成 0xCCCCCCCC windows VS 编译器会做函数栈的初始
化) gcc/g++ 不会做栈的初始化
7. 执行 sum 函数的代码 ... 直到 sum 函数的右括号
8.mov esp, ebp : 把 sum 函数的栈帧归还给系统
9.pop ebp : 出栈(把 main 函数的栈底地址出栈了),把出栈的元素赋给 ebp ,让 ebp 指向 main 函数
的栈底地址
10.ret : 出栈(把 main 函数中刚才调用 sum call 指令的下一行指令地址出栈了),赋给 CPU PC
存器
11.add esp, 8 : 把 sum 函数的形参内存交还给系统
C&C++ 源码编译链接运行的详细过程
编译过程 :所有源文件都是分开编译
1. 预处理阶段:删除注释,处理所有以 # 号开头的指令,但除了 #progma(link, " 动态库 ") 这个指令是告
诉编译器当前程序需要链接哪些库,所以该指令需要持续到链接阶段
2. 语义,词法,语法分析,代码优化 (gcc/g++ -O3) ,最终生成汇编指令
3. 把汇编代码生成本地( windows/linux )机器码,并形成组成 .o 文件的各个段( text data 符号表 ...
4. 编译最终生成二进制可重定位目标文件
链接过程 :所有的 .o 文件 .a 文件是一起链接的
1. 把所有 .o 文件和 .a 静态库文件的所有段进行合并,开始做符号解析(符号表中对符号的引用,一定要
在其它的 .o 文件或者 .a 文件的符号表中找到符号的定义),符号解析完成,给所有的符号分配虚拟地
址。
2. 符号的重定向 ( 经过符号解析,所有的符号都有虚拟地址了,再去 text 段把所有访问符号时,符号的正
确地址更新上去 )
3. 产生可以运行在当前平台下的可执行文件 .exe elf 格式的可执行文件
运行过程
1. 程序开始运行,把 elf 格式的可执行文件的 text data bss 映射到当前进程的虚拟地址空间上
2. elf 文件的 hear 头信息中,读取 Entry Address main 函数第一行指令地址)写入 CPU PC 寄存器当
3.CPU 开始读取 PC 寄存器的内容(就是 main 函数第一行指令地址), CPU 发出的地址是虚拟地址,需要
经过 MMU Memory Management Unit )做页目录页表映射(虚拟地址 -> 地址映射 -> 物理地址),
此时的地址映射肯定会产生缺页异常,进入缺页异常处理程序 (do_page_fault) ,这个方法就会分配相应
的物理页面,把磁盘上可执行文件的 text 段加载到刚分配的这块物理内存页面上,然后把物理页面的信
息写入到相应的页表项当中,然后重启地址映射。
C C++ 的区别
1. 带默认值参数的函数
2.inline 函数
inline 函数省了普通函数的调用开销,函数调用效率更高了
inline 函数在编译阶段,在函数的调用点,把函数代码进行展开
inline 函数只在 release 版本下起作用, debug 版本下不起作用
inline 修饰函数,只是一个建议,最终还有由编译器决定是否处理成内联函数
inline 函数因为在编译阶段会被展开,所以 inline 函数是不产生符号的
inline 函数和用宏 #defifine 定义一个函数,区别有什么
inline 函数可以调试的,宏是无法调试的
inline 函数是一个独立的函数模块,而宏完全是字符串替换,不太安全
3.const
C 语言的 const 定义的是常变量,除了不能作为左值,其它的情况和普通的变量是一样的
C++ 语言的 const 定义的是常量,编译过程中,所有使用常量名字的地方,都会被常量的初始值进行
替换;但是 C++ const 常量有时候也会变成常变量 (const int a = b) ,此时就和 C 语言里面的常变量是一
样的
4.const 和指针的转换几大错误公式
const int * -> int *
const int ** -> int**
int ** -> const int **
int * const * -> int**
5. 函数重载
C 语言不支持函数重载,是因为 C 语言编译器生成函数符号,只根据函数名生成,所以在 C 语言当中,
不能定义名字相同的函数。
C++ 语言支持函数重载,是因为 C++ 编译器生成函数符号,是根据函数名 + 参数列表生成,那么满足:
函数名相同,参数列表不同的一组函数,就构成函数重载;一组函数要重载,首先必须处在同一个作
用域当中。
C++ 语言怎么调用 C 语言写的函数呢? 调用不了, sum_int_int, sum ,必须把函数的声明放在 extern
"C" 里面, sum sum
C 语言怎么调用 C++ 语言写的函数呢?调用不了, sum sum_int_int ,需要把 C++ 代码全部扩在
externc "C" 里面, sum sum #ifdef __cplusplus // C 项目开发的函数接口,都会在函数头文件声明中,添加这样的宏控制
extern "C"
{
#endif
int sum(int , int ) ; // 函数声明
#ifdef __cplusplus
}
#endif
6. 函数模板
可以做泛型编程,写一套代码,用任意的类型实例化,实例化的过程是发生在编译阶段的
7.new delete
new malloc 有什么区别?
malloc C 的库函数, new 是一个运算符重载函数 operator new
malloc 按字节开辟内存,内存开辟失败返回 nullptr new 按照类型大小开辟内存,内存开辟失败抛出
bad_alloc 类型的异常
malloc 只能开辟内存,无法做初始化; new 可以开辟内存 + 内存初始化
new 底层开辟内存,调用的就是 malloc
delete free 的区别?
free C 的库函数, delete 是一个运算符重载函数 operator delete
free 只能释放内存; delete 先调用析构函数,再释放内存
free 不区分单个元素内存和数组内存的释放; delete 是需要区分的, delete p; delete []p; 主要是因为
delete 需要直到调用多少次析构函数
delete 底层释放内存调用的就是 free
8.namespace 名字空间作用域
C 语言只有函数局部作用域和全局作用域
C++ 语言有函数局部作用域, namespace MyName{} 全局的名字空间作用域,类作用域
#include < iostream >
using namespace std; // using 指示符
using std::cout; // using 声明,只能声明一个符号
cout<<"hello world!"<<std::endl;
C++ 类和对象
1. 类的成员变量
普通的成员变量 构造函数的函数体、构造函数的初始化列表 const 常成员变量 构造函数的初始化列表
static 静态成员变量 只能在类外定义并初始化
2. 成员方法
普通的成员方法:编译会产生 类类型 *this ,调用必须以来一个对象
const 常成员方法:编译会产生 const 类类型 *this ,在方法中不能修改成员变量的值,调用也依赖对象
static 成员方法:编译不会产生 this 指针,用类作用域调用
this 指针有什么作用?
一个类产生的多个对象,它们有自己的一份成员变量,但是共享一套成员方法,在成员方法中,就是通
this 指针来区分,当前访问的到底是哪个对象的成员(包括访问其它的成员变量或者成员方法)
对象发生浅拷贝是不是一定会产生问题 ?不一定,主要看对象有没有占用除对象内存之外的其它资源。
如果浅拷贝有问题,造成多次析构时,对同一个资源释放多次,那么有如下解决办法:
1. 把拷贝构造函数和 operator= 函数声明到 private 里面
2. 利用 C++11 的语法,把拷贝构造函数和 operator= 函数直接 delete
3. 自定义拷贝构造函数和 operator= 赋值运算符的重载函数,也应当 提供相应的带右值引用参数 的拷贝
构造函数和 operator= 赋值运算符的重载函数
对象生命周期 ,把课堂代码拿出来看一下进行复习。
1. 函数调用传递参数的时候,如果传递的时对象,要按对象的引用进行传递
2. 函数返回对象的时候,应该优先返回一个临时对象
3. 接收返回类型是对象的函数调用时,优先按照初始化的方式接收
对象的拷贝构造函数的参数能不能按值传递呢 String(String str) String(const String &str) ,无法实
现的,编译器直接编译报错,因为调用拷贝构造函数,需要先生成形参对象,但是形参对象要调用拷贝
构造生成,所以拷贝构造函数是按照引用传递的!
模板
函数模板和类模板
模板的意义:可以进行泛型编程,只用注重算法的实现,不用关心数据的类型,只用写一套代码,就可
以用任意的类型进行实例化(编译阶段, 静态的多态:模板和函数重载)
函数模板和类模板 -> 调用点进行实例化 (根据用户传入的 <> 的类型进行实例化,但是函数模板不一定
需要指定实例化的类型参数,因为函数模板可以进行 模板的实参推演 ;类模板的 模板参数可以给默认
,这一点函数模板不支持) -> 编译器会根据实例化的类型,产生一份专门处理该类型的模板函数或
者模板类出来进行编译 -> 模板的实例化是发生在编译阶段的。
当编译器针对某种类型实例化的模板函数或者模板类,如果处理该类型的代码逻辑有问题,可以给相应
的函数模板或者类模板提供该类型的 模板特例化 实现。 运算符的重载
意义:让对象的运算表现得和内置类型一样。因为我们写得模板代码,将来用什么类型实例化是不知道
的,有可能是编译器的内置类型,也有可能是自定义的类类型,只要我们给类对象提供相应运算符的重
载函数,那么当用对象类型实例化模板以后,编译器就能够正常编译对象的各种运算了。
template < typename T >
T sum(T a, T b){return a + b;}
string 字符串类型
#include < string >
string str1 = "hello";
string str2 = str1 + "bbbbbb";
str1 > str2 str1 < str2 str1 == str2
char& operator[ ] ( int index ) char ch = str1[2] str1[2] = 'y'
str1.size()
const char* p = str2.c_str();
近容器(没有归纳标准容器里面) 支持迭代器 foreach C++11
for(char ch : str2){} =>
auto it = str2.begin();
for(; it != str2.end(); ++it){ *it; }
int index = str3.fifind_fifirst_of(','); // 找指定字符在 string 里面出现的位置的下标
fifind_last_fifirst_of
string str3 = str1.subString(1); // ello
string str4 = str1.subString(1, 4); // ello
智能指针
防止资源泄漏,智能指针可以保证资源一定会进行释放。利用栈上的智能指针对象出作用域自动析构的
特点,在析构函数里面添加释放资源的代码。
C++ 标准库 auto_ptr
Boost 库( C++11 以后,把常用的智能指针都纳入 C++ 标准库了)
不带引用计数的智能指针
不带引用计数:一个资源只能用一个智能指针指向 auto_ptr < int > ptr(new int); auto_ptr< int > ptr2(ptr1); ptr2 指向资源, ptr1 直接被置空
不建议使用 auto_ptr ,而且不能用 auto_ptr 来实例化容器 vector< auto_ptr< int >> vec;
scoped_ptr < int > ptr(new int); 直接把拷贝构造函数和 operator= 函数直接 delete C++11
unique_ptr < int > ptr(new int);
把带左值引用的拷贝构造函数和 operator=delete 掉,但是提供了带右值引用参数的拷贝构造和
operator= 函数,意味着可以用 临时的(或者局部的) unique_ptr 构造或者给另一个 unique_ptr 对象赋
值。
unique_ptr< int > func()
{
unique_ptr< int > ptr(new int);
return ptr;
}
int main()
{
unique_ptr< int > ptr = func();
return 0;
}
unique_ptr< int > func(unique_ptr< int > p)
{
unique_ptr< int > ptr(std::move(p);
return ptr;
}
int main()
{
unique_ptr< int > p(new int);
unique_ptr< int > ptr = func(std::move(p)); // 强转成 &&
return 0;
}
s't'd::move :移动语义函数,可以把一个左值类型专程右值类型
std::forward :类型完美转发(参考博客)
带引用计数的智能指针 带引用计数:一个资源可以用多个智能指针指向,为什么它不会重复释放资源呢?因为它会对资源的引
用计数进行线程安全你的 ++ -- ,智能指针 对于资源的引用计数的线程安全保证 ,是通过 互斥锁?
CAS AtomicInt 原子整型类型定义的整形变量实现的线程安全
shared_ptr :强智能指针,会改变资源的引用计数
weak_ptr :弱智能指针,不会改变资源的引用计数
weak_ptr -> shared_ptr -> 资源
代码上能不能直接都使用 shared_ptr 强智能指针呢?会造成强智能指针的交叉引用问题,结果就是智能
指针对象无法析构,那么资源也就无法释放,资源泄漏!!! 那应该怎么使用???
定义资源的时候,用强智能指针;其它地方引用资源的时候,用弱智能指针
通过弱智能指针能不能直接访问资源呢???不能 weak_ptr operator* operator->
shared_ptr< T > sp = wp.lock(); // 把弱智能指针提升成强智能指针,实际上是在资源的引用计数是否
0
if(sp != nullptr) // operator T*() {return ptr;}
{
// 资源还在, lock 提升成功!
}
new delete
new 的四种操作:
new int ; 抛异常
new nothrow int; 返回 nullptr
const char *p = new const int; 开辟常量
new (addr) int; 定位 new
new operator new 重载函数开辟内存( malloc )、调用相应的构造函数
delete :调用对象的析构函数、 operator delete free )释放内存
什么时候 new delete 不能混用?什么时候可以?
int *p = new int; delete p; delete []p;
int *p = new int[100]; delete p; delete []p;
如果 A 类型没有提供析构函数
A *p = new A; delete p; delete []p;
A *p = new A[100]; delete p; delete []p;
不能混用的前提:自定义类型,而且提供析构函数!!! A *p = new A; delete p; delete []p; // p-4 free(p-4)
A *p = new A[100]; delete p; delete []p; // p-4 free(p-4)
一个 A 4 404 4 个字节(记录对象个数) + (p)400
继承与多态
组织类和类之间的关系:组合( a part of... )、继承 (a kind of...)
继承的好处
1. 代码的复用
2. 基类里面可以给所有派生类提供统一的纯虚函数接口,派生类可以根据自己的情况进行不同的纯虚函
数的重写,实现不同的功能,方便使用多态
10 个派生类 设计接口函数 (全部使用基类类型)
重载、隐藏、覆盖
重载:
1. 必须处在同一个作用域
2. 函数名相同,参数列表不同的一组函数,构成函数重载
隐藏(派生类不仅仅继承了基类的成员,还继承了基类的作用域) 隐藏的是作用域
1. 基类和派生类的同名成员
2. 通过派生类对象调用同名成员,调用的永远都是派生类自己的同名成员(派生类的同名成员把基类的
同名成员给隐藏掉了),如果派生类对象想调用基类的同名成员,要在成员名字前面 添加基类的作用
覆盖:
1. 基类和派生类作用域当中
2. 基类和派生类的函数名字相同,函数返回值相同,函数参数列表也相同,而且基类的函数是虚函数,
此时构成同名覆盖(在 虚函数表中覆盖 )关系
虚函数
在成员方法名字前面添加 virtual 关键字,成员方法就成为一个虚函数,带虚函数的类是怎么编译的?
在编译过程中,会给这个类型产生一张 虚函数表 vftable vftable 中主要存放 [ 虚函数的地址, RTTI* ]
当运行时, vftable 加载的内存的 .rodata 区域,这个类型实例化的对象,都会增大 4 个字节,在对象头部
放一个 vfptr ,存储的是 vftable 虚函数表的地址,一个类型定义的 n 个对象,指向的是该类型唯一的虚函
数表。
一个类类型里面增加虚函数的个数,对象的大小不变,虚函数表的内存会增大
派生类在编译的时候,由于从基类继承来了虚函数,所有派生类类型也要产生对应的 vftable ,里面放了
基类继承来的虚函数的地址,但是如果派生类提供了同名覆盖方法的实现,此时就会用派生类同名覆盖
方法的地址,把从基类继承来的虚函数方法的地址在 vftable 中给覆盖掉。 构造函数能实现成虚函数吗?不可以,因为调用构造函数的时候,对象不存在,无法进行虚函数的动态
绑定
析构函数能实现成虚函数吗?可以,因为析构函数调用的时候,对象是存在的
static 成员方法能实现成虚函数吗?不可以,因为 static 方法不依赖对象,根本无法动态绑定
inline 方法能是现成虚函数吗?可以的, virtual inline 是可以共同出现的,如果 inline 函数添加 virtual
了,此时函数就不内联了,和普通函数一样, debug/release
静态绑定和动态绑定 绑定 = 》函数调用
静态绑定:指的是函数调用,在编译阶段就是确定的了,体现在汇编指令上,就是 call + 函数的地址,
全局函数调用,普通成员函数调用, static 成员函数调用,用对象本身调用虚函数 都是静态绑定。
动态绑定:指的是函数调用,在编译阶段无法确定,只能编译成通过指针或者引用,访问指向对象的头
4 个字节 vfptr ,再通过 vfptr 访问 vftable ,在 vftable 里面取相应的函数的地址,装入寄存器 eax ,生成汇
编指令 call eax ,因为 eax 寄存器放的是哪个函数的地址,只有在运行的时候才能取到,因此这样的函
数调用(函数绑定)是动态绑定,也叫运行时期的函数绑定。 动态绑定有一个前提:用指针或者引用调
用虚函数
解释多态
1. 静态(编译)的多态:模板和函数重载
2. 动态(运行)的多态:用基类的指针(同引用)指向不同的派生类对象,然后调用同名覆盖方法,基
类指针指向哪个派生类对象,就会调用哪个派生类对象的同名覆盖方法(底层依赖函数的动态绑定机
制),这就叫多态。我们在设计模块代码、对象的函数接口的时候,可以统一的用基类类型指针(或引
用)来进行设计,可以接收任意的派生类对象,还可以通过动态绑定,调用相应派生类的方法,能够做
到软件设计需要遵守的 - 原则,对扩展开放,对修改关闭。
基类的析构函数什么时候需要定义成虚析构函数
Dervice d;
Base *p = &d;
Base *p = new Derive();
delete p; // Base Base::~Base()
基类指针指向堆上创建的派生类对象的时候 ,当 delete 指针的时候,由于指针是基类类型,而且基类的
析构函数是个普通函数,造成派生类对象的析构函数无法调用,造成资源泄漏。此时需要把基类的析构
函数定义成虚函数,那么派生类的析构函数也就自动成为虚函数了,这时候 delete 基类指针,看到基类
类型的是虚析构函数,析构函数的调用就成动态绑定,因为基类指针指向的是派生类对象,所以最终访
问的是派生类的虚函数表,最终调用到派生类的析构函数,再自动调用基类的析构函数。
抽象类是什么?为什么要定义抽象类?一般哪些类被定义成抽象类?
抽象类是不能被实例化的类,因为拥有纯虚函数, virtual void func() = 0; 一般把不需要实例化的类,定义成抽象类,基类的作用, 1 是让派生类做代码复用, 2 是给所有 派生类保
留统一的纯虚函数接口,等待派生类重写 ,所以基类的定义并不是为了抽象某个实体类型而得来的,所
以基类一般不需要实例化,因为不代表任何实体,所以基类的接口方法也不知道怎么实现(直接定义成
纯虚函数),所以一般基类定义成抽象类。
多重继承 虚基类
C++ 是支持多重继承的,好处:复用更多的代码,坏处:使派生类拥有间接基类多份成员变量(菱形继
承),如何解决这个问题?
所有从间接基类直接继承的地方,都采用虚继承的方式,基类被虚继承以后,在派生类中,虚基类数据
只存储一份,存储在派生类内存的最后面,在原来放基类成员数据的地方补充一个 vbptr vbptr 指向一
vbtable vbtable 主要存放了当前位置和虚基类数据存储的位置的偏移量。这样就能保证,在派生类
中,只有一份间接基类(虚基类)的数据。
A
B C => 这一层的两个类的成员同名
D = 》 无法直接访问的,访问的时候,需要在成员名字添加基类( B/C )的作用域
C++ 的四种类型转换
const_cast : 去掉常量属性的
static_cast :只能做编译器认为类型安全的转换
reinterpret_cast :类似 C 语言的类型强转,没有安全保证
dynamic_cast :支持 RTTI 运行时类型信息的类型转换
什么时候识别静态类型?什么时候识别 RTTI 类型?
Base *p = new Derive();
typeid( * p ).name() typeid(p).name() Base*
主要看 Base 里面有没有虚函数!!!
Base 如果没有虚函数,那么上面识别的静态类型, Base
Base 如果有虚函数,那么上面识别的就是 RTTI 类型, p -> new Dervice(vfptr) -> vftable -> RTTI* ->
"Derive"
C 语言是否支持多态? C 语言如果让你实现多态,怎么实现?( 函数指针
C++ 的异常处理
异常处理涉及的关键字
try :把可能抛出异常的代码括起来 catch :必须紧跟在 try 后面,一个 try 可以有多个 catch ,主要用来捕获相应类型的异常,进行处理,处
理完成后,代码逻辑继续运行
throw :跟变量 / 对象,表示抛出了一个相应类型的异常
try
{
}catch(int err){}
catch(string err){}
catch(double err){}
什么是异常的栈展开过程
main->func1->func2..............funcn-1 -> funcn 函数的调用链
funcn 抛出一个异常,首先在 funcn 上找是否有处理该异常的 catch 块,如果有,处理完该异常,代码
继续向下执行,如果无法处理异常,沿着函数调用链向上抛,在每一个函数调用栈上都做相同的事情,
如果抛到 main 函数,也无法处理该异常,那么直接把异常抛给系统,系统发现当前进程有一个异常未
被处理,会调用 abort 系统函数,强制终止当前进行的执行。
如何防止抛出异常后造成的资源泄露
try
{
int *p = new int; // unique_ptr< int > p(new int);
// throw 异常了
delete p;
}catch(int err){}
catch(string err){}
catch(double err){}
采用智能指针保证出 try 块资源一定会得到释放。
构造函数能不能抛异常?析构函数能不能抛异常?
构造函数不能抛出异常,因为如果抛出,说明对象构造失败了,那么对象出作用域,是不会调用析构函
数的,造成资源泄露,如果全部采用智能指针管理资源,即就是对象的析构无法调用,也不影响资源的
释放。
析构函数如果抛出异常,造成当前对象的成员对象都无法调用析构函数了,造成资源泄漏。析构函数如
果真的抛出异常,只能在析构函数本身把异常处理掉!!!此时因为异常已经被处理了,所以当前对象
析构完,成员对象可以依次正常进行析构。
C++ STL 六大组件
标准容器(空间配置器 allocator ): 顺序容器、容器适配器、关联容器
迭代器
泛型算法
函数对象(仿函数)
顺序容器
vector :向量容器,内存可以 2 被扩容的数组 0-1-2-4-8-16 。。。 reserve(100) 内存空间预留函数 /
resize(100)
push_back/pop_back insert/erase back( 获取末尾元素的值 )
iterator
operator[]
swap 交换两个容器的元素
deque :双端队列容器,内存是一个动态开辟的二维数组,扩容主要扩容的是第 1 位( 2 倍扩容)
push_front/pop_front push_back/pop_back insert/erase back( 获取末尾元素的值 )
iterator
swap 交换两个容器的元素
list :双向链表容器,内存是一个循环的双向链表
push_front/pop_front push_back/pop_back insert/erase back( 获取末尾元素的值 )
iterator
swap 交换两个容器的元素
容器适配器 没有自己底层的数据结构,依赖顺序容器实现,不支持迭代器 iterator
stack deque ): push pop top size empty
queue(deque) push pop front back size empty
priority_queue(vector) :默认构建一个大根堆 push pop top size empty
哈希统计 + / 小根堆 找海量数据的 top k 元素或者第 top k 个元素
有序和无序关联容器
红黑树 O(log_2n)
set/multiset 集合,只存储 key
map/multimap 映射表,存储 [key,value]
链式哈希表 O(1) unordered_set/unordered_multiset
unordered_map/unordered_multimap
集合: insert(20), erase(20), fifind (成员方法), iterator
映射表: insert({key, value}) insert(make_pair(key,value)) erase(key) auto it = fifind(key) it->fifirst
it->second , iterator
近容器 char[] string bitset 位容器
容器的空间配置器 allocator ,主要负责管理容器底层的内存开辟,释放,对象构造和析构
allocate : 负责开辟内存
deallocate : 负责释放内存
construct : 定位 new 构造对象
destroy : 析构对象
语言类:
熟练使用 C C++ ,对 C 的内存管理和指针的应用比较熟悉,熟悉面向对象编程,熟悉继承多态、 C++
STL 组件(。。。。)、智能指针,以及常用的设计模式
操作系统类:
协议类:
数据结构类:
MySQL 数据库
关系型数据库表的基本设计
表用来描述实体的属性的,关系型数据库中存储的都是二维表,行称作记录,列称作属性 / 字段
实体和实体之间 ( 对应表与表之间的 ) 的关系有:
1. 一对一的关系
table1 父表
id name age sex
1 张三 20 ‘
2 李四 22 ‘
table2 子表
uid (外键约束) idcard email
2 10002 123@163.com 当查询 id=2 的人的所有信息时, name age sex idcard email
select name,age,sex from table1 where id=2
select idcard,email from table2 where id=2
select a.name,a.age,a.sex,b.idcard,b.email from table1 a inner join table2 b on a.id=b.uid
where a.id=2
select * from table1 where age=20 order by name asc/desc
2. 一对多的关系
用户 (user)
id name age sex
订单 (orderlist) 子表的外键关联的是父表的主键
uid (外键) orderid date address account
写一个 SQL 语句,把 id 2 的用户的基本信息 + 所有的订单信息输出一下
select * from user a inner join orderlist b on a.id=b.uid where a.id=2
user
id name pwd
friendlist
uid friendid
1 2
1 3
1 4
3. 多对多的关系 = 一定要产生一张的新的中间表
商品( product id name price
1 手机 1000.0
2 电脑 5000.0
订单( orderlist orderid date address account totalprice id number
productprice
100 2019.8.8 曲江校区 2 6000.0 1 2 2000.0
100 2019.8.8 曲江校区 2 6000.0 2 1 5000.0
上面订单表直接存储商品的详细信息,造成订单数据冗余,所以一定要生成中间表,如下:
productorderlist
orderlist id number productprice
1000 2 3 30.0
1000 4 1 50.0 关系型数据库表的范式设计
指导开发者,如何设计一个比较完美的二维表,综合了效率和数据冗余问题。
范式一:每一列保持原子特性
id name pwd sex addressid
1
id name pwd sex
id country provice city street
1 中国 陕西省 西安市 曲江街道
uid = 3 人的 address 信息
select b.country,b.provice,b.city.b.street from user a inner join address b on a.addressid = b.id
where a.id=3
范式二:属性完全依赖于主键 ( 主要针对联合主键 )
范式二主要是针对联合主键说的,意思是非主属性,一定要依赖联合主键的每一个字段,而不能只依赖
一部分主键字段,否则设计出来的表存在严重的数据冗余。
学号 , 姓名 , 年龄 , 课程名称 , 成绩 , 学分
100 20 C++ 开发 80.0 10
101 21 C++ 开发 90.0 10
学号 , 姓名 , 年龄,考试总成绩 = user
课程 id ,课程名称 , 学分 = course
userid courseid score = studentscore
select a.name,a.age,count(b.score) from user a join studentscore b on a.id = b.userid
范式三:属性不依赖于其它非主属性
表中所有的非主属性,都必须依赖于主键,而不能依赖于其它的非主属性,否则又要造成数据冗余。
学号( primary key , 姓名 , 年龄 , 学院 id
学院 id 学院地址 , 学院地点 , 学院电话
遵守范式设计,设计出来的表,其数据冗余程度是比较低的,在表进行 CURD 的时候,效率也是比较高
的,一般设计能达到范式三就可以了。 并不是说范式越高越好,范式越高,拆分的表也就越来越多,意
味着每一次的 CURD 都需要经常做多表的联合操作,比直接单表操作效率肯定要低一些 ,所以表的设
计,要综合数据冗余成都和 CURD 的效率,所以说一般达到范式三就可以了。
范式四:消除表中的多值依赖 id name age sex
1 22
2 23
skillid skillname
1 C
2 C++
3 Java
userid skillid
1 3
1 2
1 1
BC 范式:每个表中只有一个候选键
候选键的意思就是表中每一行记录该字段的值都不一样
userid username email/cardid
实际的项目开发中, 表的设计有的时候刻意的会设计一些字段存储冗余数据,就是为减少多表联合操作
的次数 ,提高效率。
存储引擎 MyISAM InnoDB
MySQL 区别于其它的关系型数据库( SQL Server Oracle SQLite (运行在当前进程内部的))最大
的一个特点,就是其 插件式的存储引擎
MySQL RDBMS = create database Chat; = create table user()
创建的二维表需要进行存储,存储在磁盘上,都要存储 user.frm desc user/show create table
user ), user 表的数据, user 表的索引。不同的存储引擎,指的是上面三个内容的存储方式不同。
MyISAM 特点:
1. 把数据和索引是分开存放的, user.frm,user.MYD,user.MYI 三个文件
2. 所以 MyISAM 支持的索引结构是: 非聚集索引
3.MyISAM 是不支持事务、外键
4.MyISAM 支持的事务并发操作锁的粒度:表锁,所以并发效率不高,但是它比较安全,不会出现高并
发带来的死锁问题
InnoDB 特点:
1. 把数据和索引放在一起的, user.frm,user.ibd 两个文件,在 ibd 文件中,存储了数据和索引
2. 所以 InnoDB 支持的索引结构是: 聚集的索引 3.InnoDB 支持事务处理、外键设置
4.InnoDB 支持的事务并发操作锁的粒度:行锁,所以并发效率高,但是有可能产生死锁问题,需要在
事务处理的过程中,仔细的考虑事务处理具体过程。
Memory :基于内存存储的存储引擎( frm- 磁盘,数据和索引 - 内存)
MySQL 的日志系统
二进制日志 binlog ):记录了当前数据库操作过程中所有的 CURD 语句。
查询日志 :记录了所有 select 语句的操作。
错误日志 :主要 mysql server 启动、关闭、运行过程中出现的一些严重的 error mysql server:3306
redis:6379
慢查询日志 slow_query_time 表示慢查询的时间设置,所有查询时间超过 slow_query_time select
语句,都会被记录在慢查询日志当中,然后可以通过 explain + SQL 语句,来查看 SQL 语句的执行计划,
根据打印的结果,查看索引是否使用正确,有没有额外的 fifile sort 等等,然后进行 SQL 语句优化。
SQL 和索引优化和实现原理
你项目中有没有做过 SQL 或索引优化?具体怎么做的?
开启慢查询日志,设置一个项目中能接受、合适的慢查询时间,那么运行项目一段时间,查看慢查询日
志里面所有查询超值指定时间的 select 语句,然后用 explain 查看以下 SQL 语句的执行计划 ,再做具体的
分析,是做了整表搜索(那就要考虑是否要创建索引),或者索引没用到(考虑 SQL 语句的书写是否正
确),再进行问题修改。
在业务执行过程中,查看 CPU 或者磁盘 I/O 如果过高,考虑表的索引创建太多了(需要优化索引结
构),另外考虑表的数据过大了(考虑分表操作)。
1. 对于单个的 SQL 语句,给作为条件过滤的列创建索引,如果当前 SQL 语句,除了 where 子句,还有排
order by 或者分组 group by ,考虑创建联合索引。
2. 创建的索引是不是一定会用到, MySQL Server 会进行语句优化,如果使用索引查询的结果过多,就
直接做整表查询。
3.select * from student where score >=90.0 or score <60.0 如果 score 创建过索引,是否能用到?也
可能会用到,因为 MySQL Server 会把上面的 or 子句,优化成
select * from student where score>=90.0 union select * from student where score<60.0 合并查询
4.select * from student where id in (1,2,3,4,5,6,7,8)
select * from student where id in (select uid from studentscore where score >= 60.0)
in 的子查询 , 可以使用索引
select * from student where id not in (select uid from studentscore where score >= 60.0)
not in 用不到索引,一般把上面的语句优化成 in 子查询,或者优化成外连接查询
5.select * from student where age>20 and score>80.0 and sex=' '; 注意:依次查询一张表,只能使用一个索引, MySQL Server 首先会通过 age , score, sex 的索引进行过
滤数据,哪个过滤出来的数据少,就用哪个索引
6.select * from user where score between 60.0 and 80.0 可以用索引
7.select * from user where address like ' 陕西省 *'; // * %
select * from user where address like '* 东大街 ';
like+ 通配符,如果通配符在最前面,无法使用索引;如果通配符在后面,可以使用索引。
select * from usermessage where message like ' 旅游: * 旅游 * ';
8.select * from user where cellphone = 18256781234; 如果 SQL 语句涉及了类型转换,那么索引就用
不上了。
9. 多表的查询
id name pwd sex addressid
1
id name pwd sex
id country provice city street
1 中国 陕西省 西安市 曲江街道
uid = 3 人的所有信息
select a.*,b.country,b.provice,b.city.b.street from user a inner join address b on a.addressid = b.id
where a.id=3 // a 20( 过滤以后成 5 ) b 10
多表连接查询的索引是怎么使用的?
在多表连接查询的时候,先区分大表和小表(这里的大小指的就是表的记录的个数),小表是整表搜索
的,上面假设 a 10 行, b 20 行,那么 a 就是小表了,把 a 进行整表搜索,找到所有的 addressid ,然
后在大表 b 里面进行查询,找符合 a.addressid = b.id 条件的 b 表的记录。
小表决定查询次数,大表决定查询时间,小表已经要整表搜索了,所以没必要创建所以,大表相应的条
件过滤字段一定要创建索引!!!!! 通过 explain 具体分析以下 SQL 的执行计划!!!
当你给一张表的某一列创建索引,会生成一颗 B+ 树(平衡树),会用添加索引的列作为排序的字段,生
成一颗 B+ 树,提高搜索的效率。
聚集索引 MySQL 采用的存储引擎是 InnoDB ,数据和索引是放在一个文件当中,可以理解为数据就直
接放在索引树上
非聚集索引 MySQL 采用的存储引擎是 MyISAM ,数据和索引是分开放在不同的文件当中,可以理解为
索引单独存储在索引的 B+ 树上,而数据是要单独访问的
主键索引 :因为设置 primary key 本身就会给主键创建索引,由主键构成的索引树,就称作主键索引
select * from user where id=3
InnoDB :通过搜索主键索引树,找到 id=3 的索引了,主键所在的该行记录的值都被拿到了
MyISAM :通过搜索主键索引树,找到 id=3 的索引了,此时拿不到数据,只能得到数据所在的地址(磁
盘的位置 MYD ),再去读取相应的数据记录。
辅助索引 :给除主键之外的其它的列创建索引。辅助索引树上,存储的是辅助索引 + 主键值 id name(name_index) age sex
select * from user where name="zhang san"
InnoDB :通过搜索辅助索引树,找到 name="zhang san" 的索引了,就得到这一行记录的主键值 id ,然
后再去主键索引树上,搜索对应 id 的用户的所有信息
select id from user where name="zhang san" select id,age from user where name="zhang san"
MyISAM :通过搜索辅助索引树,找到 id=3 的索引了,此时拿不到数据,只能得到数据所在的地址(磁
盘的位置 MYD ),再去读取相应的数据记录。
InnoDB 存储引擎:主键索引和辅助索引的存储方式是有区别的
MyISAM 存储引擎:主键索引和辅助索引的存储方式是一样的
索引的底层实现原理
MySQL Memory 存储引擎,采用哈希索引(哈希索引最大的缺点,就是对于 SQL 语句的区间范围查
找,只能做整表搜索)
MyISAM InnoDB 存储引擎,采用的都是 B+ 树索引。
MySQL 最终为什么要采用 B+ 树存储索引结构呢,那么看看 B- 树和 B+ 树在存储结构上有什么不同?
1.B- 树的每一个节点,存了关键字和对应的数据地址,而 B+ 树的非叶子节点只存关键字,不存数据地
址。因此 B+ 树的每一个非叶子节点存储的关键字是远远多于 B- 树的, B+ 树的叶子节点存放关键字和数
据,因此,从树的高度上来说, B+ 树的高度要小于 B- 树,使用的磁盘 I/O 次数少,因此查询会更快一
些。
2. B- 树由于每个节点都存储关键字和数据,因此离根节点近的数据,查询的就快,离根节点远的数据,
查询的就慢; B+ 树所有的数据都存在叶子节点上,因此在 B+ 树上搜索关键字,找到对应数据的时间是
比较平均的,没有快慢之分。
3. B- 树上如果做区间查找,遍历的节点是非常多的; B+ 树所有叶子节点被连接成了有序链表结构,因
此做整表遍历和区间查找是非常容易的。
MySQL 为什么不采用红黑树 /AVL 树做底层索引存储结构呢?而选用 B+ 树!
AVL 树: 2 阶的平衡树 1 个数据域 +2 个地址域
B+ 树: m 阶平衡树 m-1 个数据域 +m 个地址域( m 300-500 之间的 16K 4K 一般一次磁盘 I/O 读取一
个磁盘 block 块,就刚好放在了 B 树的一个节点上)
10000000 AVL 24 最差的情况下,从 AVL 上读取一个索引,需要花费 24 次磁盘 I/O
B+ m 400 3 (400^3) B+ 树上读取一个索引,最多花费 3 I/O
事务处理
事务:表示一组 SQL 语句,一个事务中的所有的 SQL 语句执行成功,最终事务的结果才能 提交 ,如果一
部分成功,一部分失败,事务最终要 回滚 binlog 重做日志)到事务开始之前的状态。
A 800 B 400
事务的 ACID 特性
1. 原子性:一个事务中的所有的 SQL 语句执行成功,最终事务的结果才能 提交 ,如果一部分成功,一部
分失败,事务最终要 回滚 binlog 重做日志)到事务开始之前的状态。
2. 一致性:事务处理完成后,数据库的数据状态从状态 A 转成状态 B ,但是数据的总量是不变的。事务并
发操作带来的数据不一致性,主要体现在三个方面: 脏读 不可重复度 幻读
脏读 :事务 B 读取了事务 A 还未提交的数据。(一定要防止的)
不可重复读 :一个事务的执行过程中,两次相同条件的查询,但是查询的结果不一样,因为两次查询的
中间,有其它事务对相同条件的数据进行了更新。( oracle
幻读 :同一个事务的两次查询,第二次查询出现了之前没有的数据,说明中间有其它事务增加了满足相
同条件的记录。
3. 隔离性 :为了让事务的并发执行更安全, MySQL 给事务提供了以下隔离级别的定义:
事务隔离级别 脏读 不可重复度 幻读
未提交读 x x x
已提交读 ok x x oracle 的事务隔离级别默认工作在 已提交读
别上
可重复度 ok ok x MySQL 的事务隔离级别默认工作在 可重复度
别上
串行化 ok ok ok
事务的隔离级别越高,效率越低,但是也更安全;隔离级别越低,效率越高,但是数据越不安全
4. 持久性 :事务更新数据成功以后,不管 MySQL 数据库发生任何异常,都要保证数据是能够恢复的。
隔离级别的原理 - S 锁, X 锁,间隙锁
S - 共享锁(读)
X - 排它锁(写)
串行化完全利用的就是 S 锁和 X 锁。
已提交读和可重复度都是通过 MVCC (多版本并发控制,给所有的记录都打了版本标签)
间隙锁 是给表的最后一行记录的后面,称作 间隙 的地方加锁, 防止串行化事务隔离级别下幻读的发
高级数据结构
BST :二叉搜索树
AVL :平衡二叉树,在 BST 树的基础上,引入了平衡(节点的左右子树高度差不超过 1
保持节点平衡,总共引入四种旋转操作:
1. 左孩子的左子树太高 = 》 右旋转操作 2. 左孩子的右子树太高 = - 右旋转操作(左平衡)
3. 右孩子的右子树太高 = 》 左旋转操作
4. 右孩子的左子树太高 = 》 右 - 左旋转操作(右平衡)
红黑树 (不是平衡树,任意节点的左右子树高度差,长的不超过短的 2 倍):
五个性质 1. 颜色 2.nullptr 是黑色 3.roog 是黑色 4. 不能右连续红色节点 5.root-leaf 每一条路径的黑
色节点的数量是一样的
红黑树的插入操作(先看叔叔节点的颜色):
1. 叔叔节点是红色,把父节点和叔叔节点都涂成黑色,把祖先节点涂成红色,从祖先节点开始继续向上
调整
2. 叔叔节点是黑色,祖先,父亲和新插入节点,在同一侧,交换父亲和祖先节点的颜色,一次旋转就完
3. 叔叔节点是黑色,祖先,父亲和新插入节点,不在同一侧,先以父节点为起始节点进行一次旋转,再
以祖先节点为根节点执行第 2
红黑树的删除操作(先看兄弟节点的颜色):删除的节点在左边(右边同理,方向相反)
1. 如果删除黑色节点,如果补上来一个红色节点,直接把这个红色节点涂黑就可以了
2. 如果兄弟是黑色节点,兄弟的两个孩子也是黑色节点,兄弟那里没有办法借黑色节点。此时把兄弟设
置成红色,指针指向父节点,继续调整(父节点红色,把父节点直接涂黑。)
3. 如果兄弟是黑色节点,兄弟的的孩子是左黑右红(左红右红),直接把兄弟节点和父节点的颜色进行
交换,再以父节点为根节点进行左旋转操作
4. 如果兄弟是黑色节点,兄弟的的孩子是左红右黑,需要把兄弟节点和它左孩子的颜色进行交换,然后
以兄弟节点为根节点,进行右旋转操作,然后再执行上面第 3
5. 如果兄弟节点是红色,兄弟节点此时无法借调黑色节点,但是红色的兄弟,其孩子一定都是黑色节
点,所以先以当前删除节点的父节点为跟,进行左旋转操作,就使红色兄弟的黑色孩子,成为当前节点
的一个黑色兄弟了,再执行上面的相应操作。
红黑树 = AVL 插入删除的旋转次数是可控的, 插入最多旋转 2 次,删除最多旋转 3 次。
分治算法:把大规模的数据,进行规模缩减,每一个子规模是不重复的,直到规模缩减到解是已知的,
然后再进行回溯,最终得到规模 n 问题的解。
动态规划:处理方式和上面的分支算法思想一样,但是能用动态规划解决的问题,有两个特点
1. 子规模划分的问题,规模是有重复(某些子规模的问题被重复求解多次, dp 数组)
2. 具有最优子结构的性质
回溯算法:
1. 子集树 在一个数组中,找三个数字,让他们的和为 0 ,找出所有的可能的解打印出来,不能出现重
复的解
1 0 -1 1 -1 0
2. 排列树 最终还是输出所有的元素,但是题目对于元素的排列顺序有一定的要求
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值