【C++面向对象程序设计——侯捷大师】心得摘要

侯捷大师讲的真好,很多模糊的知识点,都得到了解决。感觉像是相见恨晚!非常感谢侯捷老师的启迪!谢谢啦!非常推荐萌新去学习学习!

下面是侯捷大师的《面向对象程序设计》课程的一些我觉得重要的摘要。记录下来,以便复习!

一、基于对象(class)

1. C++ vs C 关于数据和函数

对于C来说,数据和函数是分离的,也就是其他函数也可以调用之前的数据,会有一些弊端。

C++与C最大的区别是:将数据和函数组成类,达到封装效果,private成员变量只能被成员函数调用,其他函数无法访问。

class函数和数据的建议:成员变量和内部成员函数应该是private,只对外提供需要的功能接口。

2. class防御式声明

//complex.h
#ifndef __COMPLEX__
#define __COMPLEX__
... 
...
#endif // __COMPLEX__

良好的编程习惯,在头文件中加入防御式声明,防止头文件被重复包含。

3. 关于inline的要点

请记住,inline是程序员对编译器的建议,是否真的成为inline函数关键取决于编译器。

将大多数inline限制在小型、被频繁调用的函数身上,实际上构造函数和析构函数往往是inline的糟糕候选人。

4. 构造函数初始化

大气的程序员,构造函数的初始化都是使用初始化列表,相比于定义内赋值效率更高。

class  Person
{
public:
	Person(std::string name, int age);
	...
private:
	std::string mname;
	int mage;	
};
//使用构造函数特有的初始化列表
Person::Person(std::string name, int age)
:mname(name) ,mage(age)
{}

//定义内赋值,效率较低
Person::Person(std::string name, int age)
{
	mname = name;
	mage = age;
}

5. 构造函数放在private的例子

singleton 设计模式-单例模式

#include<iostream>

using std::cout;
using std::endl;


class Singleton
{
public:
	static Singleton& getInstance();// 2. 提供接口创建对象,静态函数,实现没有对象也可以调用


	Singleton(const Singleton&) = delete; // 禁止拷贝构造
	Singleton& operator=(const Singleton&) = delete; // 禁止赋值操作符
private:
	Singleton() = default; // 1. 构造函数私有
};


Singleton& Singleton::getInstance()
{
	static Singleton s; //类内唯一构造的对象
	return s;
}

int main()
{
	Singleton::getInstance(); //3. 通过静态函数调用
	system("pause");
	return 0;
}

6. 常量成员函数

class内的成员函数有两种,一种是改变成员变量;一种是不需要改变成员变量,仅仅是使用成员变量。切记不需要改变成员变量的一定要加上const

注意:const对象只能调用const成员函数

class complex
{
publiccomplex(double r = 0, double i = 0)
		: mre(r), mim(i){ }
	double real() const { return mre; }
	double imag()  { return mim; } // 没有const 成员函数
private:
	double mre, mim;
};

//调用
const complex c1(2, 1);
cout << c1.real(); // ok
cout << c1.imag(); // error, 成员函数imag()与类型限定符对象const complex不兼容

成员函数 const 的另一个用途:函数重载
成员函数是否加 const 可以构成函数重载,成员函数的const 是函数签名的一部分

// string里的两个函数
charT operator[](size_type pos) const
{  .../*不必考虑COW*/}
reference operator[](size_type pos)
{  .../*必须考虑COW*/ }
// COW :Copy On Write 写入时复制

当成员函数的const和non-const 同时存在时,
const对象只能调用const版本的成员函数,non-const对象只能调用non-const的成员函数。

7. 同一个class内的各个object互为friend

class complex
{
publiccomplex(double r = 0, double i = 0)
		: mre(r), mim(i){ }
	int func(const complex& param) //ok,同一个class内object互为friend
	{
		return param.mre + param.mim; // 在class内可以调用object的private变量
	}
private:
	double mre, mim;
};

8. 不能返回reference的情况

尽量pass by reference 和 return by reference

传输者无需知道接受者是以reference形式接收

什么情况不可以return by reference

  • 必须要在函数中创建的局部对象,不能返回引用。返回一个reference指向local对象,而这个local对象在函数退出前被销毁了,所引用的对象不复存在。立即变为“无定义行为”的恶地。
  • 返回的reference指向对象内部成员变量,不能返回引用。因为可以通过返回reference更改内部数据,破坏封装性。

总结

优秀的class至少满足以下几点:

  • class头文件防御式声明
  • 成员变量一定要private
  • 不需要改变成员变量的成员函数,要加const
  • 构造函数初始化列表
  • 参数传递尽量reference(包括形参和返回值)

优秀的程序员会周全的考虑client怎么用

9. new和delete同时存在

否则会出现内存泄漏。内存泄漏,是进过某些操作,对这片内存失去了控制

new

Complex *pc = new Complex(1, 2);

new动作的三步:

  1. 分配内存,调用malloc()
  2. 指针转型
  3. 调用构造函数
// 编译器转换为下面3步
Complex *pc;
void* mem = operator new(sizeof(Complex));  // 分配内存
pc = static_cast<Complex*>(mem); // 转型
pc->Complex::Complex(1, 2); // 调用构造函数

delete

String* ps = new String("Hello");
...
delete ps;

在这里插入图片描述

delete动作分为2步:

  1. 调用析构函数
  2. 释放内存memory ,调用free()
String::~String(ps); // 析构函数调用,将对象内的指针指向的内存杀掉
operator delete(ps); // 释放内存,将字符串本身指向的内存杀掉

10. new[] 和 delete[] 同时存在

new[] 内存分配机制

在这里插入图片描述
内存分配机制(重要):
在debug模式下,需要加上调试代码内存,(32 + 4)bit;每次new还需要这片内存的Cookie头尾,方便管理申请的这片内存,需要4* 2 bit;由于是new Complex[3],因此需要分配3个Complex对象的内存,需要 8 * 3,一般数组还需要记录数组个数,还需要 4 bit;在vc中,内存是16bit倍数,(pad) 为填充内存,因此80bit为51h = 5*16 + 1, 其中1代表是分配内存

为啥一定要同时存在?

在这里插入图片描述
delete[] p,首先是调用3次析构函数,然后再释放这片内存。由于String对象内部有new成员变量,需要调用析构函数,delete成员变量。如果是delete p,将只会调用一次成员变量,导致内部成员变量指向的内存泄漏,而p指向的内存可以释放。
其实,delete p,可以用在成员变量不需要调用析构函数来delete内存的对象,比如
new Complex[3],delete Complex。由于Complex内部使用的是默认析构函数,不需要delete成员变量。但是建议成对出现。

总结:它们都可以释放p指向的内存。不同点在于:对于内存成员变量的内存处理。delete[] p:可以调用相应数组个数的析构函数。而delete p,只能调用一次析构函数。建议:同时存在,避免成员变量内存泄漏。

11. 静态函数

静态函数没有 this pointer,只能访问静态数据

static 数据,在class外定义。

调用static函数的方式:

  • 通过object
  • 通过class name 调用

例如:单例模式,见第5点

12. 函数模板(function template)

格式

template <typename T>
inline const T& min(const T& a, const T& b)
{
	return b < a ? b : a;
}

编译器会对 function template 进行参数推导
不需要像类模板一样指明类型。

complex<double> c1; // 类模板创建对象,需要指明类型

int a, b, c;
c = min(a ,b); // 函数模板调用不需要指明

这里的T会直接推导为int。

标准库的算法,基本都是function template

二、面向对象的关系(类与类的关系)

  • 复合(Composition)
  • 委托(delegation)- composition by reference
  • 继承(inheritance)

1. 复合(Composition)表示 has-a

类内包含个另外个类
复合图例表示:
在这里插入图片描述

template <class T>
class queue
{
...
protected:
	deque<T> de;
public:
	// 以下完全利用de 的操作函数完成
	bool empty() const { return de.empty(); }
}
Adapter模式

Adapter模式,说明queue里面有deque,queue里的函数都可以用deque实现。queue被称为Adapter。

复合的构造是由内而外,会调用内部的默认构造函数析构是由外为内

// 构造函数
queue::queue(...): deque() {...} // 最先构造deque(),其中deque()是编译器给我加上去的

// 析构函数
queue::queue(...){...  ~deque();} //最后析构~deque(),同样~deque()是编译器加上去的,我们不必写

2. 委托(delegation) composition by reference

类内含有类的指针,指向实现(pointer to implementation,简称 pimpl).

委托图例表示:
在这里插入图片描述

class StringRep;
class String
{
public:
	... // 一些公有接口
private:
	StringRep* rep; //类内含有类的指针
}

class StringRep{...};

复合生命是一起出现的,有queue对象就有了deque对象。
委托,类内含有指针,只要需要用到类指针的时候才会有指向的实现类,不同步

Handle/body模式

Handle/body(桥接模式),handle(String)只有接口,body是具体的实现。

String 都不会变,要变的只有body,被称为编译防火墙

3. 继承(inheritance) 表示 is-a

父类和派生类被称为继承。

class _List_node_base{...}; // base class 

class _Lisr_node: public _List_node_base {...} // derived class 

继承图例表示:
在这里插入图片描述

继承构造同样满足由内而外,析构满足由外而内(virtual父类析构)

Template Method 模式

在这里插入图片描述
父类定义了应用程序的框架,允许子类延后重新定义某个关键方法。
让子类在不修改框架的前提下,修改某个方法。例如:MFC就大量利用Template Method 模式

4.委托和继承组合使用

Observer模式

当对象发生变化时,观察者都同步更新。

Composite模式

多级文件与文件夹目录

Prototype模式

不需要类名来创建对象

其中设计模式的详细内容,之后再补充更新。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值