1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 侯捷C++学习记录-面向对象高级编程下

侯捷C++学习记录-面向对象高级编程下

时间:2020-03-15 14:56:50

相关推荐

侯捷C++学习记录-面向对象高级编程下

本章谈到三大函数、stack堆、heap栈和内存管理、 new 操作 与 delete 操作,最后对String类实现进行复习。

学习static 关键字及类模板涉及到的设计模式有:单例模式、Adapter、pimpl、Template Method、观察者模式、Composite、Prototype;

补充学习了explicit 关键字(显式)、模板 template、namespace 命名空间、Composition 复合。

目录

7. 三大函数8. stack堆、heap栈和内存管理9. new 操作 与 delete 操作9. String类10. static 关键字及类模板11. 模板 template11.1 namespace 命名空间 12. Composition 复合13. Delegation 委托14.设计模式 - **pimpl**15. Inheritance 继承16. 虚函数17设计模式 - Template Method**17.1**复合 + 继承:** 18.设计模式 - 观察者模式**19.设计模式 - Composite**20.设计模式 - Prototype 原型**21. explicit 关键字(显式)

7. 三大函数

拷贝构造String s1(); String s2(s1);

拷贝复制String s1("hello"); String s2(); s2 = s1;

析构函数

String 末尾有 ‘/0’ 结束符 ->new char[ strlen(str.m_data) + 1]

Class中有指针,必须要有拷贝构造和拷贝赋值,并且多半要做动态分配,需要析构函数释放内存,以防内存泄漏

析构函数在离开函数作用域之前,释放内存

| 实现拷贝构造 与 析构函数:

在重载“=”赋值运算符时需要检查自我赋值,避免浅拷贝:

| 实现拷贝赋值,需要自我赋值检测,因为赋值时,需要先杀掉自己,再赋值

如果没有自我赋值检测,那么自身对象的m_data将被释放,m_data指向的内容将不存在,所以该拷贝会出问题。

8. stack堆、heap栈和内存管理

stack栈空间:存在于作用域的一块内存空间,作用域结束时会销毁

stack object 又称为 auto object,因为作用域结束时,自动调用其析构函数,自动清理static object:static Complex c2(1,2)加上关键字 static 成为静态对象,生命周期在作用域结束后仍然存在,程序结束时才会调用其析构函数清理global object:写在大括号之外的全局对象,类似static,作用域是整个程序

heap堆空间:一块global内存空间,关键字new会动态分配 heap,需要手动 delete

9. new 操作 与 delete 操作

new 的过程:先分配memory,再调用ctor构造函数 malloc 分配内存,sizeof 获取需要分配内存的大小转型调用构造函数 delete 过程: 先调用dtor析构函数:将构造函数里分配的动态空间删去再释放memory:调用 free(ps)

|内存分配情况

这里以两个类做出说明:

class complex{public:complex (double r = 0, double i = 0): re (r), im (i){ }complex& operator += (const complex&);double real () const { return re; }double imag () const { return im; }private:double re, im;friend complex& __doapl (complex*,const complex&);};

class String{public:String(const char* cstr = 0);String(const String& str);String& operator=(const String& str);~String();char* get_c_str() const { return m_data; }private:char* m_data;};

创建这两个对象后,编译器(VC)给两个对象分配内存如下:每一格一个byte四个字节

complex类:

debug调试模式下 编译器的内存分配:

红色部分 - 编译器给complex对象内存插入了头和尾- cookie

灰色部分 - 4*8 + 4大小的信息部分

绿色部分 - complex对象实际占用的空间,计算后有52字节

青色pad部分 - VC以16字节对齐,所以52最近的16倍数是64,还应该填补12字节的空缺

release模式 下编译器的内存分配:只添加了信息头和尾部分

string类:字符串对象本身只有一个指针,四个字节

数组对象 编译器的内存分配(VC):

类似的,编译器给对象增加了一些冗余信息部分,对于complex类对象,由于数组有三个对象,则存在8个double,然后编译器在3个complex对象前插入“3”用于标记对象个数。String类的分析方法也类似。

删除数组对象一定需要使用delete[ ]方法:array new 要搭配 array delete(调用相应次数的dtor,然后再删除整块内存),否则有内存泄漏

9. String类

String.h

#ifndef __MYSTRING__#define __MYSTRING__class String{public: String(const char* cstr=0); //初始化字符串指针 并赋初值 String(const String& str); //拷贝构造 String& operator=(const String& str); //拷贝赋值 传引用~String();//析构函数char* get_c_str() const { return m_data; } //获取字符串指针private:char* m_data; //字符串指针};#include <cstring> //strlen strcpyinlineString::String(const char* cstr) //构造函数{if (cstr) {m_data = new char[strlen(cstr)+1]; //传进来的字符的长度 + 结束符1,用于分配内存strcpy(m_data, cstr); //拷贝初值内容到新分配的空间}else { //未指定初值m_data = new char[1];*m_data = '\0';}}inlineString::~String() //析构函数{delete[] m_data; // delete array }inlineString::String(const String& str) //拷贝构造 const-不改变传入的string{m_data = new char[ strlen(str.m_data) + 1 ];strcpy(m_data, str.m_data);} inlineString& String::operator=(const String& str) //拷贝赋值 有返回值 String& 不能是 void // typename+& -> 引用{if (this == &str) //判断是否是自我赋值 // &+object -> 取地址return *this;delete[] m_data;m_data = new char[ strlen(str.m_data) + 1 ];strcpy(m_data, str.m_data);return *this;}#include <iostream>using namespace std; ostream& operator<<(ostream& os, const String& str){os << str.get_c_str();return os;}#endif

string_test.cpp

#include "string.h"#include <iostream>using namespace std;int main(){String s1("hello"); String s2("world");String s3(s2);cout << s3 << endl;s3 = s1;cout << s3 << endl;cout << s2 << endl; cout << s1 << endl;}

10. static 关键字及类模板

static + 函数/数据 -> 静态 函数/数据

静态数据:只有一份,在类class的外部需要定义,可以赋初值,可以不赋

静态函数:没有 this point

非静态成员函数,调用成员函数时,其实会传入一个调用者的地址c1.real() -> c1.real(&c1),返回调用者的实部real,即this->real,谁调用谁是real

静态成员函数,调用成员函数时,不会有地址的传递,调用的方式有以下两种

-单例模式- 构造函数放在private中,不允许外界构造对象,但可以通过public函数取得private成员,加上关键字static,该对象只能有一个

在函数getInstance写a,没有调用的时候直接释放

11. 模板 template

类模板 class template

函数模板 function template

11.1 namespace 命名空间

12. Composition 复合

| 类中包含其他类 < 黑色实心菱形(容器)+箭头:Container -> Component >

构造顺序:由内而外析构顺序:由外而内

设计模式 - Adapter

13. Delegation 委托

| 两个类中用指针相连 <黑色空心菱形(容器)+箭头:Container -> Component >

14.设计模式 -pimpl

pointer to implement - 由指针指向真正实现类功能的类 (Handle / Body)编译防火墙

Handle 部分用于对外接口

Body 部分用于内部实现

15. Inheritance 继承

| 子类完全继承父类的变量与函数 <空心三角形 + 箭头:子类->父类>

构造顺序:由内而外 - 先调用父类构造函数 再进入子类构造函数

析构顺序:由外而内 - 先调用子类的析构函数 再进入父类构造函数

父类的析构函数 必须是 虚函数,否则会出现 undefined behavior

继承常与虚函数搭配

16. 虚函数

| 在成员函数之前加上virtual 关键字

non-virtual 函数:不允许子类重新定义这个函数(override)virtual 函数:希望子类重新定义它,并且有默认定义pure virtual 函数(纯虚函数):子类一定要重新定义它,因为它没有默认定义

17设计模式 - Template Method**

- 父类只定义抽象的函数,子类重写具体实现

17.1复合 + 继承:

子类继承base类,并且子类含有复合Component类构造:先调用base的构造函数,再调用Component的构造函数析构:先执行自己,再调用Component的析构函数,再调用base析构函数

###17.2委托 + 继承:

18.设计模式 - 观察者模式**

子类中含有 observer 容器向量,需要先通过 attach 注册,每次调用需要 update 更新观察者

19.设计模式 - Composite**

两个子类 继承了 component ,其中一个类含有 component 的委托(指针)

20.设计模式 - Prototype 原型**

需要创建未来的class对象

下划线: static

— 负号:private

#井号:protected

21. explicit 关键字(显式)

C++中, 一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色。 1 是个构造器 ,2 是个默认且隐含的类型转换操作符。

所以, 有时候在我们写下如 AAA = XXX, 这样的代码, 且恰好XXX的类型正好是AAA单参数构造器的参数类型, 这时候编译器就自动调用这个构造器, 创建一个AAA的对象。

这样看起来好象很酷, 很方便。 但在某些情况下(见下面权威的例子), 却违背了我们(程序员)的本意。 这时候就要在这个构造器前面加上explicit修饰, 指定这个构造器只能被明确的调用/使用, 不能作为类型转换操作符被隐含的使用。

explicit构造函数是用来防止隐式转换的。请看下面的代码:

class Test1{public:Test1(int n){num=n;}//普通构造函数private:int num;};class Test2{public:explicit Test2(int n){num=n;}//explicit(显式)构造函数private:int num;};int main(){Test1 t1=12;//隐式调用其构造函数,成功Test2 t2=12;//编译错误,不能隐式调用其构造函数Test2 t2(12);//显式调用成功return 0;}

Test1的构造函数带一个int型的参数,代码23行会隐式转换成调用Test1的这个构造函数。而Test2的构造函数被声明为explicit(显式),这表示不能通过隐式转换来调用这个构造函数,因此代码24行会出现编译错误。

普通构造函数能够被隐式调用。而explicit构造函数只能被显式调用。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。