C语言诞生时间 1972年
C++语言诞生时间 1983年 better C
Java 1995年
> 学习成本比较低
> 去掉了很多C++的包袱, 程序员接受的更广泛的
> 开发效率更高, 执行效率
PC互联网
移动互联网
android
Java => JVM 一次编译,处处运行
对并发, 效率, 性能要求越来越高
NDK(C++)
ios
objective-c++
C++ Primer 当成字典去查 片段式, 适合具有一定基础的
C++ Primer Plus 完整程序
预处理-编译-汇编-链接
C是面向过程的语言,而C++是面向对象的语言
C和C++动态管理内存的方法不一样,C是使用malloc/free函数,而C++除此之外还有new/delete关键字
C中的struct和C++的类,C++的类是C所没有的,但是C中的struct是可以在C++中正常使用的,并且C++对struct进行了进一步的扩展,使struct在C++中可以和class一样当做类使用,而唯一和class不同的地方在于struct的成员默认访问修饰符是public,而class默认的是private;
C++支持函数重载,而C不支持函数重载,而C++支持重载的依仗就在于C++的名字修饰与C不同,例如在C++中函数int fun(int ,int)经过名字修饰之后变为 _fun_int_int ,而C是 _fun,一般是这样的,所以C++才会支持不同的参数调用不同的函数;
C++中有引用,而C没有;
C++全部变量的默认链接属性是外链接,而C是内连接;
C 中用const修饰的变量不可以用在定义数组时的大小,但是C++用const修饰的变量可以
C语言和C++的最大区别在于它们解决问题的思想方法不一样。C语言主要用于嵌入式领域,驱动开发等与硬件直接打交道的领域, C++可以用于应用层开发,用户界面开发等于操作系统打交道的领域。
内存泄漏:是指在堆区,alloc 或new 创建了一个对象,但是并没有放到自动释放池中,或者没有free 对象,导致这块内存一直被占用,换一种方法说,就是没有指针指向这块内存,再通俗点,开辟了一段空间,在没有被释放之前,结果找不到这块内存了,这样就会造成内存泄漏的问题。这块内存会直至程序运行结束才会被释放。
野指针:是指针指向已经delete 的对象,或者是未申请访问的受限制的区域的时候,会造成野指针指向,直接使程序奔溃。
内存踩踏:
内存访问越界
a) 数组访问越界;
b) 字符串操作越界;
非法指针
a) 使用了空指针;
b) 使用了释放掉的指针;
c) 指针类型转换错误;
栈溢出;
多线程读写的数据没有加锁保护;
多线程使用了线程不安全的函数。
malloc底层分配的是一个个chunk,这些chunk有header,里面有很多详细内容,比如前一个块的大小,本块的大小等。在释放的时候,地址 + 偏移就能得出本块的大小。关键是free函数,大部分情况下,并不会将内存归还给os,而是放入自己的memory poll也就是说,free的时候,释放内存块的大小,仅仅使用用来定位将该chunk放到哪个bin索引下面以及前后chunk进行merge。
为什么要使用命名空间?
一个大型的工程往往是由若干个人独立完成的,不同的人分别完成不同的部分,最后再组合成一个完整的程序。由
于各个头文件是由不同的人设计的,有可能在不同的头文件中用了相同的名字来命名所定义的类或函数,这样在程
序中就会出现名字冲突。不仅如此,有可能我们自己定义的名字会与C++库中的名字发生冲突。
名字冲突就是在同一个作用域中有两个或多个同名的实体,为了解决命名冲突 ,C++中引入了命名空间,所谓命名 空间就是一个可以由用户自己定义的作用域,在不同的作用域中可以定义相同名字的变量,互不干扰
,系统能够区
分它们。
在声明一个命名空间时,大括号内不仅可以存放变量,还可以存放以下类型:
变量
常量
函数,可以是定义或声明
结构体
类
模板
命名空间,可以嵌套定义
命名空间使用方法
using编译指令 using namespace std;
using声明机制 推荐使用 using std::cout;
作业域限定符 std::cout << “wd::display()” << std::endl;
4 . 不要在头文件中使用using namespace std
如果你的头文件(.h、.hpp)有被外部使用,则不要使用任何 using 语句引入其他命名空间或其他命名空间中的标识符。因为这可能会给使用你的头文件的人添麻烦。更何况头文件之间都是相互套的,假如人人都在头文件里包含若干个命名空间,到了第 N 层以后突然出现了一个命名冲突,这得往前回溯多少层才能找到冲突啊。而这个冲突本来是可以避免的。其实在源文件(.cpp)里面怎么 using 都是没关系的,因为 cpp 里的代码不影响到他人。甚至如果你的头文件(.h、*.hpp)只是自己用,那 using 也是没事的。但为了养成良好的习惯,很多人仍然建议不要随便 using,以防写顺手,届时在共享的头文件里也顺手 using 了,造成人祸
const int number1 = 10;//const关键字修饰的变量称为常量
int const number2 = 20;
const int val;//error 常量必须要进行初始化
// 除了这种方式可以创建常量外,还可以使用宏定义的方式创建常量
#define NUMBER 1024
常考题:const常量与宏定义的区别是什么?
1)编译器处理方式不同。宏定义是在预处理阶段展开,做字符串的替换;而const常量是在编译时。
2)类型和安全检查不同。宏定义没有类型,不做任何类型检查;const常量有具体的类型,在编译期会执行类型检
查。
在使用中,应尽量以const替换宏定义,可以减小犯错误的概率。
常量指针(pointer to const)
指针常量(const pointer)
int number1 = 10;
int number2 = 20;
const int * p1 = &number1;//常量指针
*p1 = 100;//error 通过p1指针无法修改其所指内容的值
p1 = &numbers;//ok 可以改变p1指针的指向
int const * p2 = &number1; //常量指针的第二种写法
int * const p3 = &number1;//指针常量
*p3 = 100;//ok 通过p3指针可以修改其所指内容的值
p3 = &number2;//error 不可以改变p1指针的指向
const int * const p4 = &number1;//两者皆不能进行修改
在C中用来开辟和回收堆空间的方式是采用malloc/free库函数,在C++中提供了新的开辟和回收堆空间的方式,即
采用new/delete表达式。
int * p = new int(1);
cout << *p << endl;
delete p;
int * p = new int[10]();//开辟数组时,要记得采用[]
for(int idx = 0; idx != 10; ++idx)
{
p[idx] = idx;
}
delete []p;//回收时,也要采用[]
常考题:new/delete表达式与malloc/free的区别是?
1)malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符或表达式 ;
2)new能够自动分配空间大小,malloc需要传入参数;
3)new开辟空间的同时还对空间做了初始化的操作,而malloc不行;
4)new/delete能进行对对象进行构造和析构函数的调用,进而对内存进行更加详细的工作,而malloc/free不能。
既然new/delete的功能完全覆盖了malloc/free,为什么C++还保留malloc/free呢?因为C++程序经常要调用C函
数,而C程序只能用malloc/free管理动态内存。
calloc - cppreference.com 分配内存、把内存清零。
malloc - cppreference.com 分配内存、不把内存清零。
realloc - cppreference.com 重新分配内存,把之前的数据搬到新内存去。
> 引用不能单独存在, 必须要绑定到一个实体
> 引用必须要进行初始化
> 引用一经绑定到一个实体之后,就不能再改变其指向
> 引用占据一个指针大小的空间
> 指针是一个独立的实体
> 指针可以进行初始化,也可以不初始化
int * p1 = nullptr;
在C++中,引用是一个已定义变量的别名。其语法是:
类型 & 引用名 = 目标变量名;
void test0()
{
int a = 1;
int & ref1 = a;
int & ref2;
}
引用的本质
引用作为函数参数
//用指针作为参数
void swap(int * pa, int * pb)
{
int temp = *pa;
*pa = *pb;
*pb = temp;
}
//引用作为参数
void swap(int & x, int & y)
{
int temp = x;
x = y;
y = temp;
}
语法:
类型 & 函数名(形参列表)
{ 函数体 }
int gNumber;//全局变量
int func1() // 当函数返回时,会对temp进行复制
{
temp = 100;
return temp;
}
int & func2()//当函数返回时,不会对temp进行复制,因为返回的是引用
{
temp = 1000;
return temp;
}
int & func3()
{
int number = 1;
return number;
}
int & func4()
{
int * pint = new int(1);
return *pint;
}
void test()
{
int a = 2, b = 4;
int c = a + func4() + b;//内存泄漏
}
> C++支持函数重载
> 实现原理: 名字改编(name mangling)
> 具体步骤: 当函数名称相同时,根据函数参数的类型、顺序、个数不同进行改编
> 按C++方式对函数进行调用
> 按C方式对函数进行调用
C++进行函数重载的实现原理叫名字改编(name mangling),具体的规则是:
进了4个新的类型转换操作符,他们是static_cast,const_cast,dynamic_cast,reinterpret_cast
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
> 作用是与带参数的宏定义相同,在编译时可以直接用函数执行体的语句替换
> 有类型检查,相比来说比带参数的宏定义更安全
> inline函数都要放在头文件里面
inline int max(int x, y)
{
return x > y ? x : y;
}
> 栈区
> 堆区: 位于其中的变量或对象可以跨函数调用
> 全局静态区: 在程序运行过程中一直存在 (数据读写段)
> 只读段:
> 文字常量区
> 程序代码区
申请方式
stack:系统自动分配,速度快,如声明int a; 系统自动在栈空间中为a开辟空间。
heap:程序员通过调用malloc函数或new表达式申请,速度相对较慢,且容易产生内存碎
片。
系统响应
stack:只要系统剩余空间大于申请空间就能申请,否则报错,栈溢出。
heap:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请
时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点
链表中删除,并将该结点的空间分配给程序。另外,对于大多数系统,首地址处会记录这
块内存空间中本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。
另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部
分重新放入空闲链表中。
申请大小的限制
stack:栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的
地址和栈的最大容量是系统预先规定好的。在WINDOWS下,栈的默认大小是1M;在
Ubuntu之下,默认栈空间大小是8M,如果申请的空间超过栈的剩余空间时,将提示
overflow。
heap:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存
储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。由此可
见,堆获得的空间受限于计算机系统中有效的虚拟内存,比较灵活,也比较大
栈区创建对象的方法:类名 对象名(构造实参),或者类名 对象名 = 类名(构造实参);
堆区内存创建方法:类名* 对象指针 = new 类名(构造实参)。也就是new的对象都是在堆区的。
class 类名
{
public://类对外提供的接口、功能、服务
//在类之外可以直接访问
protected:
private: //不能在类之外直接访问
};
默认访问权限不同:
class默认权限是private
struct默认权限是public
定义一个空类时, 系统会自动提供:
默认构造函数
复制构造函数
赋值运算符函数
析构函数
> 构造函数 可以重载
> 创建对象,并初始化数据成员
> 初始化表达式(列表)
> 默认构造函数
> 复制构造函数
> 形式: 类名(const 类名 & rhs);
> 形参的引用符号不能去掉,去掉之后,会无穷递归调用复制构造函数,直到栈溢出,程序崩溃
> const关键字不能去掉, 去掉之后,当传递过来的实参是一个临时对象(右值),无法绑定,会造成编译错误
> 调用时机:
> 用一个已经存在的对象赋值给新对象
> 当形参是对象,进行实参与形参的结合时(值传递)
> 当函数的返回值是对象,执行return语句时
> 编译器有优化操作,一般情况下看不到效果
> 在编译时要加上编译选项 -fno-elide-constructors
> 赋值运算符函数
> 类名 & operator=(const 类名 & rhs);
> 当类的数据成员中,有指针,指针也可以开辟了堆空间的资源,都要显式提供
> 三部曲:
> 自复制
> 回收左操作数申请的资源
> 进行复制
> 有参构造函数
> 隐式转换:
> 如果要禁用隐式转换,需要在构造函数前加上explicit关键字
销毁对象时
析构函数
只有一个
作用: 清理对象申请的资源
调用时机:
自动析构
栈对象
全局对象
静态对象
> 堆对象 -->new 出来的对象
> 手动调用delete表达式时,会调用析构函数
对于拷贝构造函数、赋值运算符函数、析构函数,这三者之中只要有一个要显式提供,
其他两个也必须要提供
> 放在初始化列表进行初始化
> const成员
> 引用成员
> 对象成员
> 静态成员在类之外进行初始化
> 放在全局静态区, 被整个类的所有对象共享
> 不占据对象的存储空间
> 如float computer::total_price = 0;
> 静态成员函数
> 没有隐含的this指针
> 在其内部只能访问静态成员(数据成员和成员函数), 不能访问非静态的成员
> 它可以直接用类名进行调用
> const成员函数
class point
{
public:
void print(/* Point const * const this */) const;
};
> this指针的*号左右两边都加上了const关键字进行修饰,所以只能调用,不能修改指向和值
> 只能访问数据成员,不能修改数据成员
> 只能调用const成员函数, 不能调用非const成员函数
> 以后只要某个成员函数不会对数据成员进行修改,就要将其设置为const成员函数
> 非const对象可以调用const成员函数
> const对象只能调用const成员函数
> 如打印函数
> const 对象
> const 对象只能调用构造函数、析构函数和const成员函数
> 只能被创建、撤销以及只读访问,改写是不允许的
> 要求:通过一个类只能创建出唯一的一个对象
> 步骤:
> 1. 将构造函数私有化 (回收对象时: 将析构函数私有化)
> 2. 定义一个静态成员函数, 返回一个指向堆对象的指针
> 3. 定义一个静态的指向单例对象的指针
Singleton s1;//error
> new表达式工作步骤:
> 先调用operator new库函数开辟未类型化的空间
> 调用构造函数初始化对象
> 返回一个指向相应堆对象的指针
> delete表达式工作步骤:
> 调用析构函数
> 调用operator delete库函数释放空间
> 创建栈对象的条件:
> 构造函数
> 析构函数
栈区创建对象的方法:类名 对象名(构造实参),或者类名 对象名 = 类名(构造实参);堆区内存创建方法:类名* 对象指针 = new 类名(构造实参)。也就是new的对象都是在堆区的。
> 定义一个类只能创建栈对象(不能创建堆对象)
> 把operator new库函数放入private区域
> 定义一个类只能创建堆对象(不能创建栈对象)
> 把析构函数放入private区域
> 还需要在类中定义一个回收堆空间对象的成员函数destroy
> 输入
> 把其他地方的数据要写入程序中的变量或者对象
> 输出
> 把程序中的变量或者对象输出到其他地方
> 流都有4种状态:
> badbit 系统级别的故障,不可恢复
> failbit 可以恢复的错误
> eofbit 到达了流的末尾
> goodbit 流处于有效状态
> 查询流的状态
> cin.bad()
> cin.fail()
> cin.eof()
> cin.good()
> 只有当流是处于有效状态时,才能正常工作
> clear() 重置流的状态
> ignore() 清空缓冲区
> 头文件
> std::cin
> std::cout
> std::cerr
> 头文件
> std::ifstream
ifstream ifs("test.txt", std::ios::in|std::ios::ate);
> 要求文件必须存在
> 读取指定数量的内容
> read
> 定位信息
> tellg
> seekg
> std::ios::beg
> std::ios::cur
> std::ios::end
> close
> std::ofstream
> 当文件不存在时,直接创建该文件
> 当文件存在时,默认情况下(std::ios::out)会直接清空文件的内容
> 如要在文件的末尾添加新的内容,创建ofstream对象时,需要采用std::ios::app模式
> 定位信息
> tellp
> seekp
> std::ios::beg
> std::ios::cur
> std::ios::end
> close
> std::fstream
###16.3 字符串IO
> 头文件
> 字符串IO流不需要关闭
> std::istringstream
> 当知道缓冲区中字符串的格式时,可以对其进行解析
> 由字符串类型转换成其他类型
> str() 获取字符串
> std::ostringstream
> 由其他类型转换成字符串类型
> str() 获取字符串
> std::stringstream
> str() 获取字符串
#include
#include
#include
int test0(void)
{
int ival = 512;
int ival2 = 1024;
std::stringstream ss;
ss << "ival= " << ival << " " << "ival2= " << ival2 << std::endl;
std::string str = ss.str();
std::cout << str << std::endl;
while(ss >> str)
{
std::cout << str << std::endl;
}
return 0;
}
ctrl + c 终结程序
ctrrl + d 结束流的输入
ctrl + z 将当前执行程序挂起到后台运行
C++标准: C++98 C++11 C++14
不同的公司有不同的实现(差异)
LLVM clang++
VC++
GNU GCC
互斥锁与条件变量的生命周期是彼此独立的。
pthread_mutex_t mutex;
pthread_mutex_lock(&_mutex);
在执行pthread_cond_wait(pthread_cond_t*, pthread_mutex_t* mutex)之前,要先加锁;
之后当前线程A阻塞; 在阻塞之前要先解锁;
当另外的线程B调用pthread_cond_signal(pthread_cond_t *) 之后,唤醒了线程A;
当线程A被唤醒时,要先加锁;
异常情况: pthread_cond_signal 会唤醒多个线程
条件变量必须要知道互斥锁的存在;而互斥锁本身是不知道有条件变量的
(单向的关联关系)
产品发布方式:
> .exe 二进制文件
> 源码发布
> 头文件 + 静态库
> 头文件 + 动态库
log4cpp 日志系统
服务器程序特点:
7 * 24 小时
守护进程 ==》 没有终端
日志系统是服务器程序必不可少的组件
> 日志信息
> 业务日志
> 系统日志
> 日志记录器
> Category
> Category本身有一个优先级
> 每一条日志也有自己的优先级
> 当日志的优先级别大于等于Category的优先级时,该条日志才会被记录
> 日志过滤器
> 日志优先级
> Priority
> 数值越小,优先级别越高
typedef enum {EMERG = 0,
FATAL = 0,
ALERT = 100,
CRIT = 200,
ERROR = 300,
WARN = 400,
NOTICE = 500,
INFO = 600,
DEBUG = 700,
NOTSET = 800
} PriorityLevel;
> 格式化器
> Layout
> BasicLayout
> PatternLayout ==> 对日志的格式定制化
> 输出目的地
> Appender
> OstreamAppender
> FileAppender
> RollingFileAppender
> 假定最多只能给1G空间来存储日志
1G = 32 * 32M
= 64 * 16M
> 当1G的空间使用完毕之后,应该采用循环滚动的方式继续记录最新的日志
> 循环滚动的日志文件 --> 回卷文件
day06 20190727
0. 复习
1. 版本控制系统Git
版本控制系统
> 集中式 SVN
> 分布式 Git Linus 2002年 Bitmover ==》 Bitkeeper(VCS)
2005年 2周时间写出雏形,一个月之后就用Git托管
2008年 github上线
2018年 被微软收购 75亿美元微软股票
> Git的原理:
> 工作区 ==> 暂存区 ==> 版本库
<==
> 将github之上的项目下载到本地:
> 第一次下载:
ubuntu: $ git clone https://github.com/haohb13/30.git
windows: GitExtensions 图形化界面
> 之后的更新:
ubbuntu:
$ cd project
$ git pull
day07 20190729
1. 友元
> 作用: 可以在类之外访问私有成员
> 形式:
> 函数
> 普通函数
> 成员函数
> 友元类
> 在一定程度上,确实是破坏了类的封装性;
> 限制: 单向的、不具备传递性、不能被继承
2. 运算符重载
> 总原则: 用户自定义类类型的操作在形式上要与内置类型的操作保持一致
> 规则:
> 不能重载的运算符有5个:
. .* :: ?: sizeof
> 重载运算符时,只能是自定义类型或者枚举类型 ==> (内置类型的数据不能重载运算符)
> 优先级和结合性还是固定的
> 逻辑运算符一旦被重载之后,不再具备短路求值特性
&& ||
> 不能臆造一个并不存在的运算符
@ # $
> 运算符重载的形式:
> 推荐以普通函数(成员是public的)
+ - * / %
==
!=
>
<
> 推荐以友元函数(成员是private的)
+ - * / %
==
!=
>
<
<< 输出流运算符, 第一个参数要求必须是流类型
>> 输入运算符
> 推荐以成员函数(执行完操作之后,对象本身发生改变)
= 三部曲
+= -= *= /= %=
++
前置形式: 返回值是引用, 其执行效率高于后置形式
后置形式: 返回值是对象, 其参数列表之中会多一个int,
该int并不真正传递参数,只是为了与前置形式进行区分
--
-> 指针访问运算符
* 解引用运算符
() 函数调用运算符
函数对象 ==> 重载了函数调用运算符的类创建的对象
闭包 ==> 携带状态的函数 ==> lambda表达式 ==> 匿名函数
[] 下标访问运算符, 第二个参数的类型可以是任意类型
new/delete
day08 20190730
0. 复习
1. 类型转换
> 由其他类型转换成自定义类型 Point pt = 1; Point pt2 = c1;
> 由构造函数执行隐式转换
> 由自定义类型向其他类型转换 int x = pt; double y = c1;
> 由类型转换函数
> 形式:
> 成员函数
> operator 类名() {}
> 特点: 1. 形式上没有返回值
2. 没有参数
3. 在函数体内要以传值形式返回一个目标类型的变量(对象)
> 违反常规思维的操作, 一般情况下不要使用它
2. 类域
> 全局类
> 嵌套类
> 设计模式PIMPL
> 一般情况下设计成private的,只为外部类进行服务
> 特点:
> 1. 实现信息隐藏(在公司与公司之间进行合作时, 产品以 “头文件 + 库”发布 )
2. 降低编译依赖
3. 只要头文件不变,可以以最小的代价实现库的平滑升级(只需要用新版本的库替换旧版本库)
> 局部类
(面试精华)
单例对象的自动释放:
Singleton::getInstance()->print();
Singleton::destroy();//
> 对于单例对象,如果忘了回收,就会产生内存泄漏(有可能导致程序崩溃)
> 单例对象的回收,一般情况下,都是程序执行结束的时候
> 需求:对程序进行内存泄漏检查 valgrind
> 单例对象自动回收(释放)
> 嵌套类 + 静态对象
> atexit + 静态destroy方法
> 平台相关性
> pthread_once + atexit
3. 写时复制
LLVM clang++
SSO
GNU GCC
4.X.X COW
5.X.X SSO
VC++
SSO
面试精华:
> std::string的底层实现
> std::string是一个字节流的字符串, 其功能是有限的
> 字符流的字符串 编码 GBK(2个字节表示一个字符) UTF8(3个字节表示一个字符)
> 三种实现方式:
> eager copy(深拷贝)
> cow(Copy-on-Write 写时复制)
> 当对字符串进行复制或者赋值时, 并不马上真正进行复制(没有进行深拷贝),
只是将引用计数加1, 时间复杂度为O(1)
> 只有当字符串内容要进行修改时,才真正进行深拷贝
> 对引用计数进行修改,需要使用原子操作
> sso(Short String Optimazation 短字符串优化 -- 多核时代)
> 当字符串的长度小于等于15个字节时,字符串内容作为对象的一部分
> 当字符串长度大于15个字节时,就用堆空间进行存储
例如facebook的folly库中, fbstring根据不同长度使用不同的拷贝策略, 最终每个fbstring对象都是24字节.
> 很短的用SSO(0-22), 23字节表示字符串(包括’\0′), 1字节表示长度.
> 中等长度的(23-255)用eager copy, 8字节字符串指针, 8字节size, 8字节capacity.
> 很长的(>255)用COW. 8字节指针(指向的内存包括字符串和引用计数), 8字节size, 8字节capacity.
day09 20190731
0. 复习
1. Cow_String区分出读写操作
day10 20190801
1. 继承
> 定义
class 派生类: pulic/protected/private 基类
{};
> 派生类对于基类成员的访问权限:
> 针对于派生类对象,只能以public继承方式访问基类的public成员
> 针对于基类私有成员, 不管以哪种方式继承,在派生类内部都不能直接访问
> 针对于基类非私有成员, 不管以哪种方式继承,在派生类内部都可以直接访问
> 使用public继承时,基类非私有成员,在派生类内部的访问权限与基类保持一致
> 使用protected继承时,基类非私有成员,在派生类内部的访问权限都是protected型
> 如果继承层次中,一直采用的都是protected继承,不管有多少层继承层次,
在任一层次中,都可以直接访问顶层基类的非私有成员
> 使用private继承时,基类非私有成员,在派生类内部的访问权限都是private型
> 有哪些是不能继承的:
> 构造函数
> 析构函数
> operator new/delete
> operator=
> 友元关系
> C++支持多重继承
> 成员名访问冲突的二义性问题 ==> 直接通过类作用域来访问某一个成员函数
> 菱形继承产生的存储二义性问题 ==> 采用了虚继承
> 派生类对象的创建
> 总原则: 先初始化基类部分,再初始化派生类部分
> 误解: 先调用基类构造函数,再调用派生类构造函数
> 正解: 先调用派生类构造函数,在执行派生类构造函数的初始化表达式过程中,再调用基类构造函数
> 派生类对象的销毁
> 先调用派生类析构函数
> 调用对象成员的析构函数
> 基类的析构函数被自动调用
> 基类与派生类的转换
> 向上转型: (合法) Point3D pt(1, 2, 3);
> 基类引用绑定到派生类对象 const Point & ref = pt;
> 基类指针指向派生类对象 Point * p = &pt;
> 派生类对象可以赋值给基类对象 Point pt2 = pt;
> 派生类与派生类对象的复制控制
> 派生类没有显式定义复制控制函数时, 基类部分会自动相应的复制控制函数
> 派生类有显式定义复制控制函数时, 基类部分不会自动相应的复制控制函数,
此时,都必须要手动进行调用
> 禁止派生类对象间的复制控制
> 将基类部分的复制控制函数删除掉
2. 多态
> 静态多态
> 函数重载
> 运算符重载
> 发生时机: 编译时
> 动态多态
> 发生时机:运行时
> 继承 + 虚函数
> 虚函数的实现原理:
> 虚函数表(虚表): 当类中定义了一个虚函数时,对象存储布局的开始位置会多一个虚函数指针vfptr,
该虚函数指针指向的就是一张虚函数表,虚函数表中存放的是虚函数的入口地址
> 动态多态被激活的条件:
> 基类有定义虚函数,派生类有覆盖虚函数
> 创建派生类对象
> 基类指针或者引用指向派生类对象
> 基类指针或者引用调用虚函数 (基类指针或者引用调用到了派生类函数)
> 重载(overload)
> 普通函数
> 同一个类, 当函数名称相同时,根据参数的类型、顺序、个数
> 隐藏(oversee)
> 父子类
> 只要函数名称相同
> 覆盖(override)
> 父子类
> 虚函数 ==> 覆盖发生在虚函数表里面
> 构造函数不能设为虚函数
>
day11 20190802
0. 复习
1. 多态
1>class B size(8):
1> ±–
1> 0 | ±-- (base class A)
1> 0 | | _ia
1> | ±–
1> 4 | _ib
1> ±–
// 测试一:单个虚继承,不带虚函数
// 虚继承与继承的区别
// 1. 多了一个虚基指针
// 2. 虚基类子对象位于派生类存储空间的最末尾
1>class B size(12):
1> ±–
1> 0 | {vbptr} ==> 虚基指针
1> 4 | _ib
1> ±–
1> ±-- (virtual base A)
1> 8 | _ia
1> ±–
// 测试二:单个虚继承,带虚函数
// 1.如果派生类没有自己的虚函数,此时派生类对象不会产生
// 虚函数指针
1>class B size(16):
1> ±–
1> 0 | {vbptr}
1> 4 | _ib
1> ±–
1> ±-- (virtual base A)
1> 8 | {vfptr}
1>12 | _ia
1> ±–
// 2.如果派生类拥有自己的虚函数,此时派生类对象就会产生自己本身的虚函数指针,
// 并且该虚函数指针位于派生类对象存储空间的开始位置
1>class B size(20):
1> ±–
1> 0 | {vfptr}
1> 4 | {vbptr}
1> 8 | _ib
1> ±–
1> ±-- (virtual base A)
1>12 | {vfptr}
1>16 | _ia
1> ±–
1>B::$vftable@B@:
1> | &B_meta
1> | 0
1> 0 | &B::fb2
1>B::$vftable@A@:
1> | -12
1> 0 | &B::f
// 测试三:多重继承(带虚函数)
// 1. 每个基类都有自己的虚函数表
// 2. 派生类如果有自己新的虚函数,会被加入到第一个虚函数表之中
// 3. 内存布局中, 其基类的布局按照基类被声明时的顺序进行排列
// 4. 派生类会覆盖基类的虚函数,只有第一个虚函数表中存放的是
// 真实的被覆盖的函数的地址;其它的虚函数表中存放的并不是真实的
// 对应的虚函数的地址,而只是一条跳转指令
1>class Derived size(28):
1> ±–
1> 0 | ±-- (base class Base1)
1> 0 | | {vfptr}
1> 4 | | _iBase1
1> | ±–
1> 8 | ±-- (base class Base2)
1> 8 | | {vfptr}
1>12 | | _iBase2
1> | ±–
1>16 | ±-- (base class Base3)
1>16 | | {vfptr}
1>20 | | _iBase3
1> | ±–
1>24 | _iDerived
1> ±–
1>Derived::$vftable@Base1@:
1> | &Derived_meta
1> | 0
1> 0 | &Derived::f
1> 1 | &Base1::g
1> 2 | &Base1::h
1> 3 | &Derived::g1
1>Derived::KaTeX parse error: Expected 'EOF', got '&' at position 32: …1> | -8 1> 0 | &̲thunk: this-=8;…vftable@Base3@:
1> | -16
1> 0 | &thunk: this-=16; goto Derived::f 跳转指令
1> 1 | &Base3::g
1> 2 | &Base3::h
1>class Derived size(32):
1> ±–
1> 0 | ±-- (base class Base2)
1> 0 | | {vfptr}
1> 4 | | _iBase2
1> | ±–
1> 8 | ±-- (base class Base3)
1> 8 | | {vfptr}
1>12 | | _iBase3
1> | ±–
1>16 | {vbptr} ==> 谁虚继承的基类,该虚基指针就跟着谁
1>20 | _iDerived
1> ±–
1> ±-- (virtual base Base1)
1>24 | {vfptr}
1>28 | _iBase1
1> ±–
// 测试四:钻石型虚继承,
1. 没有采用虚继承的情况
> 存储二义性问题
1>class D size(48):
1> ±–
1> 0 | ±-- (base class B1)
1> 0 | | ±-- (base class B)
1> 0 | | | {vfptr}
1> 4 | | | _ib
1> 8 | | | _cb
1> | | | (size=3)
1> | | ±–
1>12 | | _ib1
1>16 | | _cb1
1> | | (size=3)
1> | ±–
1>20 | ±-- (base class B2)
1>20 | | ±-- (base class B)
1>20 | | | {vfptr}
1>24 | | | _ib
1>28 | | | _cb
1> | | | (size=3)
1> | | ±–
1>32 | | _ib2
1>36 | | _cb2
1> | | (size=3)
1> | ±–
1>40 | _id
1>44 | _cd
1> | (size=3)
1> ±–
1>class D size(44):
1> ±–
1> 0 | ±-- (base class B1)
1> 0 | | {vbptr}
1> 4 | | _ib1
1> 8 | | _cb1
1> | | (size=3)
1> | ±–
1>12 | ±-- (base class B2)
1>12 | | {vbptr}
1>16 | | _ib2
1>20 | | _cb2
1> | | (size=3)
1> | ±–
1>24 | _id
1>28 | _cd
1> | (size=3)
1> ±–
1> ±-- (virtual base B)
1>32 | {vfptr}
1>36 | _ib
1>40 | _cb
1> | (size=3)
1> ±–
// 测试四:钻石型虚继承
//2. 采用虚继承
//虚基指针所指向的虚基表的内容:
// 1. 虚基指针的第一条内容表示的是该虚基指针距离所在的子对象的首地址的偏移
// 2. 虚基指针的第二条内容表示的是该虚基指针距离虚基类子对象的首地址的偏移
1>class D size(52):
1> ±–
1> 0 | ±-- (base class B1)
1> 0 | | {vfptr} GNU GCC中会把这两个指针合并成一个指针
1> 4 | | {vbptr}
1> 8 | | _ib1
1>12 | | _cb1
1> | | (size=3)
1> | ±–
1>16 | ±-- (base class B2)
1>16 | | {vfptr}
1>20 | | {vbptr}
1>24 | | _ib2
1>28 | | _cb2
1> | | (size=3)
1> | ±–
1>32 | _id
1>36 | _cd
1> | (size=3)
1> ±–
1> ±-- (virtual base B)
1>40 | {vfptr}
1>44 | _ib
1>48 | _cb
1> | (size=3)
1> ±–
1>D:: v b t a b l e @ B 1 @ : 1 > 0 ∣ − 41 > 1 ∣ 36 ( D d ( B 1 + 4 ) B ) 1 > 1 > D : : vbtable@B1@: 1> 0 | -4 1> 1 | 36 (Dd(B1+4)B) 1> 1>D:: vbtable@B1@:1>0∣−41>1∣36(Dd(B1+4)B)1>1>D::vbtable@B2@:
1> 0 | -4
1> 1 | 20 (Dd(B2+4)B)
1>D::$vftable@B1@:
1> | &D_meta
1> | 0
1> 0 | &D::f1
1> 1 | &B1::Bf1
1> 2 | &D::Df
1>D::$vftable@B2@:
1> | -16
1> 0 | &D::f2
1> 1 | &B2::Bf2
1>D::$vftable@B@:
1> | -40
1> 0 | &D::f
1> 1 | &B::Bf
day12 20190805
1. 作业RSS文件解析
强类型(静态)程序设计语言: C C++ Java C#
auto
弱类型(动态)程序设计语言: python javascript php
var/let
2. 模板
> 泛型编程 ==> 类型参数化
> 可以将模板看成是代码生成器 ==> 模板参数推导
> 在编译时,必须要看到模板的全部实现
> 第一种方式: 声明与实现全部写在一个文件中
> 第二种方式:
> 可以分成声明和实现的
> 当声明与实现出现在不同的文件时,一般要在声明的文件include实现文件
> template
> template
> 两种参数
> 类型参数
> 非类型参数, 常量表达式,整型数据
> 都可以默认值, 从右到左
> 函数模板 ==> 模板函数
实例化
> 显式实例化 add(1, 2)
> 隐式实例化 add(1, 2)
> 普通函数与函数模板可以重载, 普通函数要优先于函数模板
> 函数模板之间可以重载
> 可变模板参数
template 模板参数包
void func(Args... args) 函数参数包
> 在声明时,...必须要放在参数包的左边
> 在调用时,...必须要放在参数包的右边 ==> 拆包/解包
> 类模板 ==> 模板类
> vector
> vector
> vector
3. STL
> standard template library 标准模板库
> 六大组件:
> 容器 (container) 存储数据
> 线性容器
> std::array 静态数组
> std::vector 动态数组 + - += -= ++ --
> 支持随机访问迭代器
> std::deque 双端队列 + - += -= ++ --
> 支持随机访问迭代器
> std::forward_list 单链表
> 支持前向访问迭代器
> std::list 双向链表
> 支持双向访问迭代器 ++ --
> 特有操作
> sort
> merge
> reverse
> unique
> splice ==》 移动元素
> 移动整个链表
> 移动某一个元素
> 移动一对迭代器范内元素
> 构造
> 无参构造函数
> vector numbers(10, 1);
> vector numbers(arr, arr + 10);
> vector numbers{1, 2, 3, 4, 5};
> vector numbers2(numbers);
operations vector deque list
push_front 0 1 O(1) 1 O(1)
pop_front 0 1 1
push_back 1 1 1
pop_back 1 1 1
insert 1 O(N) 1 O(N) 1 O(1)
erase 1 O(N) 1 O(N) 1 O(1)
clear 1 1 1
shrink_to_fit 1 1 0
capacity 1 0 0
begin 1 1 1
end 1 1 1
> 关联式容器
> 底层实现: 红黑树 ==> 近似平衡二叉树 ==> 查找元素的时间复杂度为O(logN)
> 红黑树特征:
> 节点不是红色就是黑色
> 根节点是黑色的
> 叶子节点是黑色的
> 不能有两个连续的节点是红色的
> 从根节点到任意一个叶子节点路径上黑色节点的数目要相同
> 支持的迭代器: 双向访问迭代器
> 只有关键字
> set
> 不能存放关键字相同的元素
> 默认情况下是按照升序方式进行排列
> multiset
> 存放关键字相同的元素
> 判断两个元素是否相同,用的是等价的概念
<
a 不小于 b
b 不小于 a
> 不能修改其元素
> 只能添加元素或者删除元素
> insert
> erase
存储的是pair类型键值对
> map
> 不能存放关键字相同的元素
> 默认情况下对关键字按照升序方式进行排列
> multimap
> 存放关键字相同的元素
> insert
> erase
map cities;
cout << cities[1] << endl;
cities[2] = "杭州";
> 支持下标访问运算符operator[]
> 当关键字存在时, 输出关键字key对应的value
> 当关键字不存在时,直接创建一个新元素,给出对应value的默认值
> 当关键字存在时,可以修改对应的value
> 当关键字不存在时,可以添加一个新元素
> 查找元素时,
> find, 返回值是迭代器
> count, 返回相应关键字对应的个数
> 范围查找
> lower_bound
> upper_bound
> equal_range
> 无序关联式容器
> 底层实现: hash表
> 查找元素的时间复杂度为O(1)
> 支持前向访问迭代器
> 对于自定义类类型
> 定义hash函数
> 重载等于符号 ==
> 填充因子 能够利用的空间只有一半 不要超过0.5
> unordered_set
> unordered_multiset
> unordered_map
> 支持下标访问运算符 operator[]
> unordered_multimap
> 迭代器 (iterator) 对容器中的元素进行访问
> 五种迭代器
> 随机访问迭代器 O(1) RandomAccessIterator
> vector
> deque
> array
> 双向访问迭代器 BidirectionalIterator
> list
> 关联式容器
> 前向访问迭代器 ForwardIterator
> 无序关联式容器
> 输入流迭代器 InputIterator
> istream_iterator
> 读取元素 * == != ++
> (read)
> 输出流迭代器 OutputIterator
> (write)
> ostream_iterator
> 写操作 = ++
> 适配器 (adapter)
> 容器适配器
> stack
> queue
> priority_queue
> 底层实现: 采用堆排序
> 底层容器: vector 以O(1)的时间复杂度访问每一个元素
> 针对于自定义类类型,也需要重载比较方式
> 默认情况下,采用小于符号方式比较, 得到的是一个大顶堆
> 每一次添加新元素时,都用堆顶元素(左操作数)与新来元素进行比较,
比较之后,返回值为true时,就用新来元素替换堆顶元素;
> 迭代器适配器
> ostream_iterator
> istream_iterator
> 插入迭代器
> 插入元素时,不能使用容器的begin,end
> front_insert_iterator
> 在该迭代器内部实现时要调用push_front
> 适用的容器有 list,deque
> back_insert_iterator
> 在该迭代器内部实现时要调用push_back
> 适用的容器有 vector, list, deque
> insert_iterator
> 在该迭代器内部实现时要调用insert
> 适用于拥有insert的容器
> 反向迭代器
> reverse_iterator
> 函数适配器
> bind1st/bind2nd 被弃用了
> C++11提出的 std::bind
> 提前绑定任意个数的参数
> 对于不想提前绑定的参数,要使用占位符
> using namespace std::placeholders;
> _1, _2, ..., _29
> 对于函数形参的个数一般不要超过7个
> 占位符本身所在的位置代表的是形参所在的位置,
占位符的数字代表实参传递时的位置
> 对于提前绑定的参数采用的是值传递, 如果要采用引用传递,
需要使用引用包装器std::ref/std::cref
> std::bind可以绑定到普通函数,成员函数
> 函数模板, 其返回值就是一个函数对象
> 函数的容器
> void(...) 函数的返回值加形参列表构成了一个函数类型
> std::function<函数类型> f;
> 当std::function与std::bind结合,就可以取代虚函数的功能
> std::mem_fn 成员函数适配器
> 当容器中存放的是自定义类类型创建的对象时,如果要
使用某一个成员函数时,就可以采用mem_fn进行绑定
> 算法 (alogrithm) 对容器中的元素进行操作
> 采用的泛型算法
> 通过迭代器来对容器中的元素进行操作
> 迭代器相对于容器来说,是更高的抽象
> 非修改式序列操作
> for_each
> find
> count
。。。
> 修改式序列操作
> copy
> remove/remove_if
> 惯用法 erase-remove
> 迭代器失效
> 在遍历元素的过程中,不要轻易做删除或者添加元素的操作
> replace/replace_if
> 排序
> sort
> stable_sort
> 堆排序
> make_heap
>
> 二分查找 ==》 针对于有序序列 => O(logN)
> lower_bound
> upper_bound
> binary_search
> 集合相关的操作
> set_intersection
> 全排列
> next_permunation
> 最大值最小值
> 函数对象(functor -- 仿函数) 对元素进行定制化操作
> 普通函数
> 函数指针
> 重载了函数调用运算符的类创建的对象
> 配置器 (allocator) 内存管理 透明
day13 20190806
0. 复习
1. STL之容器
容器的萃取技术
旋转的方式:
本质特征:
左旋: 逆时针 被旋转的节点成为了左子树
右旋: 顺时针 被旋转的节点成为了右子树
结合代码
hash表 :
> 以空间换时间: 填充因子(装载因子) 50 / 100 = 0.5 能够利用的只有一半的空间
> hash函数
> 能够以O(1)查找到某一个元素
day14 20190807
0. 复习
1. STL之迭代器
POD类型
trival
class Example
{};
non-trival
class Example1
{
public:
Example1(int,int);
};
day15 20190808
0. 复习
1. STL之算法库
f
5 3 1 2 66 66 7 1 66 8 9 66
hello & world
class ExpessionParse
{
Query doParse(const string & expr);
};
day16 20190809
0. 复习
1. STL之空间配置器
std::allocator 接口层
> allocate
> deallocate
> construct
> destroy
空间分配的实现层
# ifdef __USE_MALLOC
typedef malloc_alloc alloc;
typedef malloc_alloc single_client_alloc;
# else
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;
#endif
class __malloc_alloc_template; //一级配置器
内存碎片 ==> 造成内存不够,从而导致程序崩溃
> 内部碎片
> 页式管理 512B 1KB 4KB
> 段式管理
> 段页式管理
> 外部碎片
> malloc
> 可以重新进行利用
> 杜绝外部碎片的产生
> 小于等于128字节
> 对于二级配置器来说,最少申请的空间也是320字节
class __default_alloc_template;//二级配置器
> 16个自由空闲链表
enum {_ALIGN = 8};
enum {_MAX_BYTES = 128};
enum {_NFREELISTS = 16};
union _Obj {
union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};
static _Obj* _S_free_list[_NFREELISTS];
> 内存池
static char* _S_start_free;
static char* _S_end_free;
static size_t _S_heap_size;
> 分配策略:
> 1. 当申请128字节以上的空间时,直接采用一级配置器malloc/free
> 2. 当申请128字节以下的空间时,就采用自由空闲链表+内存池的方式
template
char* __default_alloc_template<__threads, __inst>::_S_start_free = 0;
template
char* __default_alloc_template<__threads, __inst>::_S_end_free = 0;
template
size_t __default_alloc_template<__threads, __inst>::_S_heap_size = 0;
template
typename __default_alloc_template<__threads, __inst>::_Obj* __STL_VOLATILE
__default_alloc_template<__threads, __inst> ::_S_free_list[
# if defined(__SUNPRO_CC) || defined(GNUC) || defined(__HP_aCC)
_NFREELISTS
# else
__default_alloc_template<__threads, __inst>::_NFREELISTS
# endif
] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
28 29 30 31
static size_t _S_freelist_index(size_t __bytes) {
return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
}
29 + 8 - 1 = 36
36 / 8 = 4
4 - 1 = 3
//将申请的字节数向上取整得到8的倍数
static size_t
_S_round_up(size_t __bytes)
{ return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }
29 + 8 - 1 = 36
0000 0111
0010 0100
& 1111 1000
0010 0000 2^5 = 32
static void* allocate(size_t __n)
{
if (__n > (size_t) _MAX_BYTES) {
__ret = malloc_alloc::allocate(__n);
}
else {
_Obj* __STL_VOLATILE* __my_free_list
= _S_free_list + _S_freelist_index(__n);
_Obj* __RESTRICT __result = *__my_free_list;
if (__result == 0)
__ret = _S_refill(_S_round_up(__n));
}
template
void*
__default_alloc_template<__threads, __inst>::_S_refill(size_t __n)
{
int __nobjs = 20;
char* __chunk = _S_chunk_alloc(__n, __nobjs);
}
要申请的空间数分别是32, 64, 96
template
char*
__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size,
int & __nobjs)
{
//申请32字节空间时, 第一次调用chunk_alloc
size_t __total_bytes = __size * __nobjs = 32 * 20 = 640;
size_t __bytes_left = _S_end_free - _S_start_free = 0 - 0 = 0;
size_t __bytes_to_get = 2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
= 2 * 640 = 1280;
_S_start_free = (char*)malloc(__bytes_to_get);
_S_heap_size += __bytes_to_get; 1280
_S_end_free = _S_start_free + __bytes_to_get;
//递归调用chunk_alloc
size_t __total_bytes = __size * __nobjs = 32 * 20 = 640;
size_t __bytes_left = _S_end_free - _S_start_free = 1280;
if (__bytes_left >= __total_bytes) {
__result = _S_start_free;
_S_start_free += __total_bytes;
return(__result);
}
//申请64字节空间时, 第一次调用chunk_alloc
size_t __total_bytes = __size * __nobjs = 64 * 20 = 1280;
size_t __bytes_left = _S_end_free - _S_start_free = 640;
else if (__bytes_left >= __size) {
__nobjs = (int)(__bytes_left/__size) = 640 / 64 = 10;
__total_bytes = __size * __nobjs = 64 * 10 = 640;
__result = _S_start_free;
_S_start_free += __total_bytes;
return(__result);
}
//申请96字节空间时, 第一次调用chunk_alloc
size_t __total_bytes = __size * __nobjs = 96 * 20 = 1920;
size_t __bytes_left = _S_end_free - _S_start_free = 0;
size_t __bytes_to_get = 2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
= 2 * 1920 + 1280 / 16 = 3840 + 80 = 3920;
_S_start_free = (char*)malloc(__bytes_to_get);
_S_heap_size += __bytes_to_get; 1280 + 3920 = 5200
_S_end_free = _S_start_free + __bytes_to_get;
//递归调用chunk_alloc
size_t __total_bytes = __size * __nobjs = 96 * 20 = 1920;
size_t __bytes_left = _S_end_free - _S_start_free = 3920;
if (__bytes_left >= __total_bytes) {
__result = _S_start_free;
_S_start_free += __total_bytes;
return(__result);
}
}
day17 20190810
1. 移动语义
> 提出的背景: 临时对象带来了不必要的资源拷贝的浪费
> 希望的效果: 可以将临时对象直接转移到新对象之中去
> C++11之前,没有语法规则能直接识别出临时对象
> 只有const引用能够绑定到临时对象(右值)
> const引用又能绑定到左值
> const引用是一个万能引用
> 当const引用作为参数时,无法识别出传递过来的是左值还是右值
> C++11提出的解决方案:
> 右值引用 int && ref = 10;
> 只能绑定到右值,不能绑定到左值
> 具有移动语义的函数
> 移动构造函数
> 移动赋值运算符函数
> 具有移动语义的函数与具有复制控制语义的函数同时出现,
当传递临时对象(右值)时, 具有移动语义的函数会优先调用
2. 智能指针
> 资源管理的技术: RAII(Resource Acquisition Is Initialization)
> 特征:
> 当申请资源时,用对象托管资源
> 当对象销毁时,释放资源
> 一般情况下,表达对象语义(不能进行复制或赋值)
> 提供若干访问资源的方法
> 智能指针
> auto_ptr C++0X
> 在语法形式上可以进行复制或者赋值,
> 在执行该操作时,底层已经发生了所有权的转移
> 该指针存在缺陷 已经被弃用
> unique_ptr
> 独享所有权的智能指针
> 不能进行复制或赋值
> 具有移动语义, 可以作为容器的元素
> 默认情况下托管的堆空间的对象,回收资源时采用delete表达式
> 自定义删除器
> shared_ptr
> weak_ptr
面向对象思维方式
客观现实世界 程序世界
抽象 实例化
对象 ===》 类 类 ==> 对象
对象的创建和销毁
> 构造函数
> 析构函数
运算符重载、 友元
知识体系
泛化 ==> 自底向上 继承/泛化
继承 ==> 自顶向下 基类
派生类 > 派生类对象的创建和销毁
生物多样性 ==> 多态 > 对象
虚函数
对象 ==》 数据
> 对批量数据进行存储和操作
> STL(标准模板库) => 模板
> 数据结构和算法
> 对单个对象进行管理
> 资源管理(RAII)
> 智能指针
语言都是用来表达的
面向对象分析(OOA) ==> what 对需求进行分析
面向对象设计(OOD) ==> how (以最小的代价面对需求的变化)
面向对象编程(OOP) ==> 实现
测试
维护
day18 20190812
0. 复习
1. OOD
> 类与类之间的关系
> 继承(泛化)
> UML: 空心的三角箭头
> A is B
> 耦合性由弱到强
> 依赖
> UML: 虚线的箭头
> A use B 临时的,偶然的
> B作为A成员函数的参数
> 在A成员函数内部调用B的静态方法
> 在A成员函数内部创建B的对象(返回值是B)
> 关联
> UML:
> 单向的关联关系 实线箭头
> 双向的关联关系 直线
> 作为数据成员: 指针或者引用
> 彼此并不负责对方的生命周期
> A has B
> 聚合
> UML: 空心的菱形箭头
> 整体和局部, 整体部分并不负责局部的生命周期
> 作为数据成员: 指针或者引用
> A has B
> 组合
> UML: 实心的菱形箭头
> 整体和局部, 整体部分负责局部的生命周期
> 作为数据成员: 对象成员
> A has B
> 遵循设计原则
> SOLID五原则
> 单一职责原则
> 核心: 解耦
> 开放闭合原则
> 核心: 针对于接口、抽象进行编程
> 里氏替换原则
> 派生类一定要适应基类
> 接口分离原则
> 避免使用“胖”接口
> 依赖倒置原则
> 核心: 针对于接口、抽象进行编程
Web服务器
Windows: IIS nginx asp(小型企业)
tomcat jsp
Linux: apache2/nginx php (中小型企业)
tomcat jsp/Java (大型企业)
协议
Client:
PC
browser
android
ios
harmonyOS
设计模式:
创建型设计模式:
> 单例模式
> 工厂方法
> 抽象工厂
结构型设计模式
> 组合模式
> 适配器
> 代理模式
行为型设计模式
> 迭代器
> 观察者
day19 20190813
1. 面向对象TextQuery查询程序扩展
2. MVC设计模式–魔兽世界
hello & world
class Query
{
};
day20 20190815
0. 复习
1. 线程的封装
开辟的线程数量:
理论上来说,一个进程最多可以开辟多少线程? 32位系统 4G内存=1G内核+3G用户态
线程都有自己独立的栈空间 10M 300个线程
当线程越来越多时,CPU不断的切换时间片, 开销只会越来越大
多核时代 1核开辟1 - 2个线程