C++ 面向对象初级
C++ 面向对象上
语言+标准库
头文件与类的声明
类的经典分类
- 带指针成员的类
- 不带指针成员的类
头文件的防范式声明
1 |
|
类要作提前引用声明
使用参数初始化列表来规范化类的构造函数
默认构造函数
创建类对象时无需给定参数的构造函数
- 无参构造函数
- 全部参数带默认值的构造函数
不可以有两个默认构造函数
私有构造函数
参数传递
函数传参尽量采用址传递
常量引用代替值传递
函数返回值的传递也尽量址传递
注意:若返回对象是在函数中创建的,不可将其引用传回,因为函数调用结束后,其生命周四结束,内存被释放,传回的引用指向的为垃圾数据
Return by reference 语法
传递着无需无需知道接收者是以reference形式接受的,即接收者以对应引用接收时,传递者可使用相应类型传递值
友元函数
相同class的各个对象互为friends
运算符重载
作成员函数
任何成员函数会有一个隐藏参数this指针指向(函数调用者)即该运算符的左操作数,不能在参数处显示声明,但是可以在函数体中使用
非成员函数
思考:重载运算符函数已经将运算结果保存到了对应参数中,为什么还要有返回值呢???因为运算符有连续操作,void完全ok,但是如果出现连=,+,-等操作呢??所以,还需要有返回值存储当前运算结果
匿名对象
1 | Typename() |
带指针成员的类
Big Three
带指针成员的类不得不写Big Three,默认的函数无法满足需求且会引发问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 class String {
public:
String(const char* el=0){
if(el) {
elem = new char[strlen(el)+1];
strcpy(elem,el);
}else {
elem = new char[1];
*elem = '\0';
}
}
String(const String& s) {
//深拷贝
elem = new char[strlen(s.elem)+1];
strcpy(this.elem,s.elem);
}
String& operator=(const String& s) {
if(this == &s) {
//判断是否为同一对象
return *this;
}
delete []elem;
elem = new char[strlen(s.elem)+1];
strcpy(this.elem,s.elem);
}
~String() {
delete[] elem;
}
private:
char* elem;
}
- 拷贝构造函数
从零开始创建一个与给定对象相同的对象
浅拷贝
- 仅仅为成员指针的拷贝,这样导致被创建的对象的指针成员指向的内存泄漏,且一个内存被两个指针指向,操作相互受影响
深拷贝
- 为指针指向内存的内容的拷贝
以字符串为例
拷贝赋值函数 对=赋值运算符的重载
将已存在的对象赋值为另一个对象
以A=B的赋值过程为例:
将A指针指向的内存清除
A中重新创建一个与B中待拷贝内存大小相同的内存
逐个将B中内存内容拷贝至A中
一般拷贝赋值函数考虑自我赋值,不作任何操作,返回本身即可
析构函数
栈
生命周期
- stack对象
离开栈的作用域,对象的生命周期就结束,系统自动清理对象内存
- static对象
static对象生命在作用域结束之后仍然存在,直到程序结束
- 全局对象
直到程序结束
堆
生命周期
- heap堆对象 指针指向的内存对象
当delete语句删除内存对象时,该内存对象生命期结束
内存泄漏
当动态创建对象后不存在delete语句释放动态创建的内存时,一旦超出指针作用域,指针失效后,内存空间便无法使用,却没有删除,造成内存泄漏
堆与栈的区别
栈内存由系统以函数为单位自动分配与释放,局部变量,函数参数均存于栈中
堆由人为动态创建与释放,c语言使用malloc/free函数,c++使用new/delete运算符
由于堆空间需要人为管理,容易造成内存泄漏
栈内存的分配释放速度快,效率高,内存连续
堆内存的分配释放速度效率较低,内存不一定连续
内存管理
new运算符
先分配内存后调用构造函数
1
2
3
4obj *p=new obj(); //实现过程
void* mem=operator new(sizeof(obj)) //内部调用malloc(n)分配内存
p=static_cast<obj*>(mem) //指针类型的转型
p->obj::obj(); //调用obj对象的构造函数初始化delete运算符
先调用析构函数再释放内存
1
2
3delete p; //实现过程
obj::~obj(p) //先调用obj对象的析构函数
operator delete(p) //内部调用free(n)释放内存- 先调用析构函数原因
delete删除动态创建的带有指针的类对象时,只会删除为该对象分配的内存空间,即为指针分配的4字节内存空间,但是指针指向的内存空间并没有被删除,但是指针已被删除,这样再次导致内存泄漏问题
内存分配状态 vc为例
new Object
1
2obj *p=new obj();
delete p;debug模式内存分配
cookie头 记录分配的内存空间长度 4+4
debug头部 32+4
对象
填充字节部分 -vc的内存需要为16的倍数,不满足取与实际内存最接近的适合大小
-
非debug模式的内存分配
cookie头 记录分配的内存空间长度 4+4
对象
填充字节部分 -vc的内存需要为16的倍数,不满足取与实际内存最接近的适合大小
new Arrays
1
2obj *p=new obj[size];
delete[] p; //[]用于指明为arrays内存模式,不会仅调用一次析构函数debug模式内存分配
相较于object下的内存,多分配了4byte的数组长度位
-
非debug模式的内存分配
相较于object下的内存,多分配了4byte的数组长度位
注意:Arrays内存分配下,delete时务必添加[]
原因:要将Arrays下的内存全部清理,需要执行size次类对象的析构函数清理各个对象元素的内存,而缺省[]的情况下,编译器将只执行一次首元素的析构函数,虽然arrays的内存空间被清理,但是其余对象元素的空间未被清理,导致内存泄漏
Static
普通成员变量
内存的分配跟随对象的创建,从属于某一特定对象,相互独立
普通成员函数
成员函数的内存单独占一份,有隐藏this指针,不同对象调用时可识别并操作特定对象的数据成员
静态成员变量
该成员变量的内存单独占一份,且被所有对象所共享,且类被声明即已存在
静态成员函数
该成员函数仍单独占一份内存,但是特殊在于无隐藏this指针参数,只能对静态数据成员进行操作
静态局部变量
该局部变量即使跳出局部作用域,其内存生命仍存在,但是局部作用域外无法访问
设计模式的应用
单例模式
1
//暂略
名称空间
防止名称冲突
using namespace name
“将封装的命名空间全部打开,直接使用该命名空间中的成员”
using name::member
“将封装的命名空间部分打开,可直接使用已打开的命名空间中的成员”
- 直接使用命名空间全名加::引用命名空间中的成员
OOP AND OOD
Composition 复合
has-a 一个对象成员直接包含另一类型的对象
黑盒复用角度解释
外部对象可以调用内部对象的方法来实现某些功能
Adaptor 适配器设计模式
内存角度解释
外部对象的内存为外部对象额外成员与内部对象成员之和
符号表示
实心菱形+箭头 箭头所指的为被组合的对象
组合中的构造与析构
构造函数
由内向外逐步构造,先调用内部对象的默认构造函数
析构函数
由外向内逐步析构,先调用外部对象的析构函数
Delegation 委托
Composition by reference
一个对象含有一个指向另一类型对象的指针成员
Handle/Body(pImpl)
符号表示
空心菱形+箭头 箭头所指的为被组合的对象
特点
在使用时动态创建复用对象,无需定义外层对象时便创建
继承
is-a 继承父类的部分数据成员与成员函数的调用权
符号表示
空心三角+直线 空心三角所指的为被继承的对象
继承中的构造与析构
构造函数
由内向外逐步构造,先调用父类的默认构造函数
析构函数
由外向内逐步析构,先调用子类的析构函数
继承与虚函数
纯虚函数
1
virtual void function()=0;
无函数体,函数必须被子类override重写
虚函数
1
virtual void function() {}```
有函数体可为空,即存在默认函数体,子类可重写也可不重写
Template method 模板方法设计模式
父类方法中延时调用子类重写的虚函数
继承+复用
继承+委托
Observer观察者设计模式
设计模式
- 应用场景
- 继承,组合,委托
- 类图
单例
adaptor适配器
Handle/Body(pImpl)
observer
Composite
文件系统
Prptotype
暂略
