C++对C的加强
0. 面向过程–>函数+面向对象–>类和对象
1.namespace命名空间
2.实用性 增加 -- 整型变量用时再声明
3.Register关键字增强 -- register int a = 0
<1> register关键字请求 编译器 将局部变量存储于寄存器中
<2> C语言中无法获得register变量地址
C++中可以取得register变量地址
<3> C++编译器有自己的优化方式,不使用register也可能做优化。
4.变量检测增强(全局变量) -- C++编译器增加__重定义__检测
5.struct类型增强(定义时不用+struct关键字) -- 与class关键字 功能一样
6.C++中所有的变量和函数都必须有类型 -- 各种奇怪
7.新增bool类型关键字
8.三目运算符功能增强 -- (a> b ? a : b) = 30; // 当左值要有内存空间
C语言中表达式的结果放在寄存器中
<1> C中,表达式的返回 是变量的值。
<2> C++中,表达式的返回,是变量的本身。
*(a > b ? &a : &b)= 30// C中实现
9. C语言中const,可以通过指针修改,所以
const int a= 10;
int*p = (int *)&a;
*p = 20;
C++const符号表实现原理: 符号表 – Key:Value:
p = (int *)&a -- 会新开辟内存供p指向(编译时分配)
给const分配内存:
<1> 对常量取地址的时候 -- &a
<2> 作为全局变量的时候
10. const 和 #define -- C++中
类似:
const int a = 5;
#define a 5;
不同:
<1> const常量是 由编译器处理的,提供类型检查和作用域检查(同一函数)。
<2>宏定义 由预处理器处理,单纯的文本替换。(跨函数)
#define -- #undef控制作用域,const自己控制。
引用
<1> (变量是内存标号,引用是已定义变量的别名) – int &b = a;
<2> 普通引用必须要初始化+引用做函数参数声明时不进行初始化
本质:
<1> &a 和 &b相同 == a和b是同一块内存空间的标号
<2>引用的本质是 常量指针 -- C++编译器帮我们建立的关联(取地址,加*号)
<3> 因为栈对象在退出函数时候被销毁,所以不能用来赋值给引用 – int &a3 = getAA2();
但是int a2 = getAA2();可以
<4> 函数做左值
Tips:
在C中,左值指的是能够出现在等号左边及右边的变量(表达式),右值则指的是只能出现在等号右边的变量(表达式).
返回变量的本身 == 返回变量所标识的内存空间
二级指针:
普通变量取地址 --- 一级指针
一级指针取地址 --- 二级指针
同理,获得变量地址,通过指针修改其值。
获得一级指针地址,通过二级指针修改其值。
1. 指针的引用: -- *&t
2. 常量引用 -- const int &y = x // 让变量引用只读属性,不能通过y修改x
<1> 初始化:
引用:
3.C++对C的函数扩展:
<1> inline函数
定义在类内部的成员函数时自动inline的
宏定义 和 函数调用: --- 宏定义是直接替换
3.5
4. 函数重载:overload
<1> 判断标准:
名称、参数、返回值
名称相同,参数不同(个数/类型)
<2>重载函数在本质上是相互独立的不同函数 (静态链编)
<3>返回值 不是 判断函数重载的标准!!!
5. 函数指针:
6. 面向对象:
Tips: 用struct定义的类,默认属性是public,class默认是private
#pragmaonce VS #ifndef ~~ #define ~~ #endif
1. 构造函数 和 析构函数 – 没有返回值,类同名,可带参数(析构没有)
(C++11:Test() = default)
<2.5> 默认构造
默认无参构造函数
默认copy构造函数(简单的进行成员变量的值复制)
<3>构造函数调用规则
当类中定义了拷贝构造函数时,C++编译器不会提供无参数构造函数
当类中定义了有参数构造函数时,~
在定义类时,只要写了构造函数,必须要用。
2.深浅拷贝
(1) 浅拷贝:先释放obj2,堆中内存空间被释放,再次释放obj1的时候,p是野指针。
(2) 等号操作符 也是 浅拷贝 --- 两次泄漏
t3有自己的指针p,当被=浅拷贝之后,p指向和t1的p同一块内存空间,原来的内存空间没释放,发生泄漏。而且,t3先释放p,t1的p成为野指针。
3. 构造函数的初始化列表: -- 在B类中 组合了一个A类对象
(1) 新的语法 Constructor::Constructor():m1(v1), m2(v1, v2), m3(v3)
const必须使用列表初始化
(2) 执行顺序
<1> 先执行 被组合对象的构造函数
<2> 组合对象有多个,按照定义顺序
<3> 析构函数 与 构造函数 顺序相反。
Tips: <1> Test(1, 2, 3) // 匿名对象,没有人接,构造完直接析构
<2>
4. new + delete (操作符) (调用构造和析构) --- malloc + free (C函数)
<1> 基础类型,数组,类
<2> 三种类型,交叉使用
explicit:只能在声明中,阻止一个实参的构造函数,执行隐式转换。
C++11委托构造函数:
5. static变量和函数属于类
<1> 静态成员变量: -- 提供了一个同类对象的共享机制
<2> 静态成员函数:
6.
C++编译器如何管理类,对象,类和对象之间的关系
用 this 指针区分具体对象
<1> C++只是基于C语言做了一层面向对象的封装。
<2> C++中类的普通成员函数都__隐式包含__一个指向当前对象的this指针。
静态成员函数不包含this指针。
1. Const修饰: --- const修饰的是 this 指针 所指向的内存空间,修饰的是this指针。
this原本是一个常量指针:Test *const this;
常量成员函数(只读) ==(this->a || this->b不能修改)
PS:编译器分两步处理类:首先编译成员的声明,然后才轮到成员函数体。因此,成员函数体可以随意使用类中的其他成员而无须在意这些成员出现的次序。
1.5
返回自身Test&,链式编程
3. <1> 友元函数:--- 头文件应该为函数 提供独立的声明(除了类内部的友元声明之外)
友元函数通过对象参数 访问私有数据成员
令成员函数作为友元
<2> 友元类:
如果B类是A类的friend(在A类声明),B类可以访问A类所有private
为什么设计友元类函数:
<1> 1.java --- 1.class(字节码)==》 反射机制分析1.class找到类对象。直接修改类的私有属性。
<2> 1.cpp --- 汇编,找类就非常难
PS:类中持有 其它类对象 ,要注意!!!要么使用 构造函数的初始化列表,要么设置默认参数。
4. 运算符重载
全局函数 和 成员函数 的区别:相差一个this指针
(1) 友元函数 和 成员函数重载操作符+
(2) 前后置++ --
//后置–先保存,再++
(3) 左移右移<< >>属于cout,所以只能用友元函数
函数返回值当左值需返回引用
友元函数重载 常用于 运算符的左右操作数类型不同(除外= () [] ->)
(4) 等号=操作符
先释放旧内存 + 返回引用
(5) == !=
(6) 中括号[] --- 函数返回值当左值,需要返回一个引用
这里必须返回引用,因为有可能当左值和右值
(7) 小括号()
(8) && 与 || 可以实现运算符重载,但是无法实现短路规则
默认参数 只在 头文件中定义
1. 继承:
抽象,封装,继承,多态
<1> Public:类内和类外
<2> Protected:类内和子类内部
<3>Private:类内部
(1)`访问调用语句`在类内 OR 类外
(2)继承 限制 ppp
(3)父类中的限制 ppp
2. 类型兼容性原则
3. 继承中的构造和析构:
Tips:class中默认是private
4. 多继承:
<1> 二义性:一(属性b) --- 二(两份属性b) --- 一(属性b有二义性)
<2> Virtual - 虚继承 --- 加virtual,类多4个字节
根父类构造函数只执行一次
<3>二 --- 一 解决不了
5. 多态(一种调用语句,有多种表现形态): --- 指针,引用,函数调用
事先定义的框架,后来者遵循规范,所以框架可以调用未来的代码!!!
<1>继承 <2> virtual函数重写 <3> 父类指针(引用)指向子类对象
多态是设计模式的基础,是框架的基础。
6. 静态联编 -- 是程序的匹配,连接在编译阶段实现。重载函数
动态联编(迟绑定) -- 程序联编推迟到运行时。Switch和if
7. 虚析构函数:
基类指针释放 子类对象
父类指针释放 所有子类资源,执行所有子类的析构函数。
8. 重写, 重载, 重定义:
Tips:子类无法重载父类的函数,父类同名函数将被名称覆盖,与参数无关。
1. 多态的实现原理:
(1)当类中声明虚函数时,编译器会在类中生成一个虚函数表
(2)虚函数表是一个存储类成员函数指针的数据结构
(3)虚函数表由编译器自动生成与维护的
(4)Virtual成员函数会被编译器放入虚函数表中
(5)存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)
编译器确定 func 是否为虚函数:
(1)func不是虚函数,编译器可直接确定被调用的成员函数(静态链编,根据类 类型来确定)
(2)func是虚函数,编译器根据 对象p的 Vptr指针,所指的虚函数的表中查找func函数,并调用(查找和调用在运行时完成, 所谓的动态链编)
原理:C++编译器提前布局,在类对象中增加vptr指针、虚函数入口地址存虚函数表
C++编译器并不是区分子类对象和父类对象,而是根据对象指针、找vptr指针,再找虚函数入口,实现迟绑定,支持了多态。
2.
(1)函数:含有 纯虚函数的类 叫 抽象类 == 接口的封装和设计,软件模块功能划分
<1> 抽象类不能建立对象,不能当返回类型,参数类型 <2> 可定义指针和引用
(2)抽象类在多继承中的应用: --- 面向接口的编程 - 多继承抽象类
面向抽象类编程:本质是多态,只不过父类是抽象类
3. 面向对象:
继承 –- 组合 -- 功能增强
注入: 添加 引用…
控制反转:本来你去调用框架,框架反过来调用你
MVC
AOP:对继承思想有利的补充
1. 数组类型_指针 和 函数类型_指针
2. 函数指针 做 函数参数 ---- 任务的调用 和 任务的实现可以分开解耦合 – 多态
函数指针类型:把函数的参数,函数的返回值提前约定
3. 正向调用,load DLL到内存,拿到函数入口地址,调用。
C++进阶:
1. 泛型编程:
(1)函数模板 和 类模板
<1> 函数模板:template <typename T>
<2>
普通函数 的调用:可以进行隐式的类型转换
模板函数 的函数调用(本质:类型参数化):将严格的按照类型进行匹配,不会进行自动类型转换。
<2.5>
a) 函数模板可以像普通函数一样被重载
b) C++编译器优先考虑普通函数
c) 如果函数模板可以产生一个更好的匹配,那么选择模板
d) 可以通过 空模板实参列表<>的语法限定 编译器只通过模板匹配
<3> C++编译器模板机制剖析
a)编译器并不是把 函数模板 处理成能够处理任意类的函数
b)编译器从函数模板通过具体类型产生不同的函数
c)编译器会对函数模板进行两次编译
在声明的地方对模板代码本身进行(简易的)编译;在调用的地方对参数替换后的代码进行编译。
函数模板产生的 模板函数
(2)类模板:- 数据类型 和 算法的分离
<1> 模板类是抽象的,需要进行类型具体
<2> 类模板 做 函数参数 --- A &a
<3> 继承中的类模板
1)派生普通类
--- 模板类派生类时,需要具体化模板类(:public A),C++编译器需要知道 父类的数据类型。父类所占内存大小,数据类型固定,才知道如何分配内存。
子类初始化列表
2)派生模板类
2. <1>所有函数都写在类的内部:
<< >>用友元函数,其他操作符重载用成员函数
<2> 所有函数都写在类的外部:
i.
ii. 友元函数重载(<< >>)写在类外部会出问题:
本质是:模板是两次编译生成的第一次生成的函数头 和 第二次生成函数头 不一样。
iii. 滥用友元函数
友元函数只用来重载左移右移,其他不用。除非加类和函数的前置声明….
<3> .h和.cpp分离:
i. main函数要引入”.cpp”文件
ii. 滥用友元函数方法失效。
3. 类模板 与 static关键字:
i. 语法
ii. 每种数据类型(int, float)使用自己的static
4. 数组模板类 案例 -- 数据类型和算法的分离
理论提高:所有容器提供的都是值,不是引用。容器执行插入元素的操作时,内部实施拷贝动作。所以STL容器内存储的元素必须能够被copy(必须提供拷贝构造函数)
如果加入模板类的Teacher类中有指针:
<1>重写拷贝构造函数
<2>重载等号操作符
<3>重载左移操作符
1. 类型转换
<1> static_cast 静态类型转换 int -> char // 编译时C++编译器会做类型检查
C语言中,隐式类型转换的地方,可使用 static_case<>() 进行类型转换
<2> reinterpreter_cast 重新解释类型
<3> dynamic_cast 动态类型转换,子类和父类之间的多态类型转换
// 运行时类型识别RIIT
<4> const_cast 去 const 属性 // 确保指向内存可修改
TYPE B = static_cast(B)
2. <1> 异常 -- throw x; -- 异常的引发和处理不必在一个函数中
基本语法
发生异常之后,是跨函数
接受异常以后,可以不处理,再抛出
Catch异常时,按照类型进行catch
异常捕获严格按照类型匹配
<2> 栈解旋(unwinding)
在try{}中,throw出异常时,能释放函数所有的栈变量
<3> 异常接口声明:
void func() throw(int, char)
throw –任何
throw() –不抛出
<4>抛(intchar *类对象 引用 指针) 异常
如果接受异常的时候,使用一个对象,则进行拷贝构造
使用引用的话,会使用throw时的对象,没有拷贝构造
指针可以和引用、对象写在一起,对象和引用不能写一起
类对象,使用引用
3. 流类库 -- 分3类:键盘,文件,内存的输入输出
<1> 输入(从缓冲区读) + 输出缓冲区
4. 文件io流 -- 相对于应用程序的输出(写文件ofstream)+ 输入(读文件ifstream)
文本 二进制 打开关闭输入输出追加
STL标准模板库 – Standard Template Library
数据结构和 算法 的分离
算法, 容器, 迭代器
1. 容器
<1>string – 封装char*
初始化,遍历,字符指针和string的转换,连接,查找和替换,截断(区间删除)和插入
翻转和大小写转换
<2> vector<> -- 动态数组
front() back() push/popback() – 尾部插入删除快
<3> 迭代器
erase() 会让it自动下移
it = v.erase(it)
<4> deque -- 双端数组
push_front() pop_front()
push_back() pop_back()
<5> queue
<6> stack
<7> list双向链表
i. 不支持随机存取(不可以it + 5,可++--),不支持at()和[]
ii. insert()是插入到指针当前位置
<8> priority_queue<>默认最大值优先级队列
priority_queue< greater<int> >
<9> Set/multiSet元素唯一,自动排序(小到大),采用红黑树变体的数据结构实现,属于平衡二叉树。
自定义数据类型排序(仿函数应用)
<10> Map/multiMap
插入 — 4种方法 – 结果检查pair删除遍历.
find() –返回迭代器
1.5 容器的值拷贝:
无参,有参,拷贝构造,析构,重载=号
1.9 容器使用时机
2. STL算法中函数对象和谓词
(1)函数对象(重载函数调用操作符()的类,其对象称为函数对象)和 谓词
a) 函数对象做参数 和 返回值 --- t1=for_each(v.begin(), v.end(), t1);
b) 函数对象 与 普通函数: 调用相似,函数对象是类对象能保持调用的状态
(2)一元(参数)函数对象 一元(参数)谓词 –(返回值bool,可以是一个仿函数或者回调函数)
find_if()
(3)二元对象 和 二元谓词
transform(v1.begin,v1.end,v2.begin,v3.begin,fun)
sort(begin,end,fun)
(4) 二元谓词在set中的应用
(5) 预定义函数对象 --- 算术,关系,逻辑
(6) 函数适配器
isGreater() count() bind2nd() not1
3. STL容器算法 迭代器的设计理念
1)STL容器通过类模板技术,实现数据类型和容器模型的分离
2)STL的迭代器技术实现了遍历容器的统一方法;也为STL的算法提供了统一性奠定了基础
3)STL的算法,通过函数对象实现了自定义数据类型的算法运算;所以STL的算法也提供了统一性
核心思想:其实函数对象本质就是回调函数,回调函数的思想:就是任务的编写者和任务的调用者有效解耦合(函数指针做函数参数)
具体例子:transform算法的输入,通过迭代器first和last指向的元素作为输入,通过result作为输出,通过函数对象来做自定义数据类型的运算
4. STL算法分类:
for_each V Stransform -- 如果目标和源相同,他们基本相同
for_each 速度快不灵活
transform 速度慢非常灵活
transform对函数对象的要求:有返回值
查找
adjacent_find() – 查找一对相邻重复元素 的 第一个
distance()
binary_search() -- 二分法查找
count() count_if()
find() find_if()
排序
merge()
sort()
random_shuffle()
reverse()
拷贝和替换
copy()
replace() replace_if()
swap()
算术
accumulate()
fill()
集合算法
set_union( )set_intersection() set_difference()