笔记:侯捷老师:面向对象编程Part1

零、无指针的类、有指针的类 设计要点

  1. 构造函数初始化尽量用列表初始化的方法

  2. 对于不会改变数据内容的函数,马上再函数后面加上const, 万一在定义的时候没有加,如果对象是常量,调用函数时就会出错。

  3. 最好所有的参数传递都传递引用,

  4. 如果不希望在函数内部修改外面的实参,只需要在形参前加上const。

  5. 最好所有的函数返回都返回引用,但是要注意不能返回局部遍历的引用

  6. 同一个类的不同对象可以互相访问对方的私有成员

  7. 重载<<只能写成全局形式,因为cout在程序中只能有一个

  8. 只要有指针,就必须重写拷贝构造函数,拷贝赋值,析构函数

    • 拷贝构造函数:先给指针对象分配空间
    • 拷贝赋值:先清空原有内容,再分配空间,接着赋值
    • 析构函数:回收原来空间
  9. 注意,拷贝赋值要检测自我赋值,不然删掉原来的内容的时候可能会出错

  10. 由系统自动分配的再栈区;由程序员手动new的再堆区,需要手动删除

  11. 内存泄漏:指针死了,占用的空间还在

  12. new的底层: 先在内部调用malloc()分配内存,然后进行指针转型,接着调用构造函数

  13. delete的底层: 先调用析构函数,然后在内部调用free(),删除内存

  14. 真实创建对象的情况在内存中会由多的cookie内存用于寻址,debug版本比release版本分配更多地址。在VC中,内存块一定是16的倍数,当内存不是16倍数的时候会自动填充空间。因为是16的倍数,所以地址的后四位一定是0,在被分配初期后被设置成1,释放后又变成0。

  15. delete和delete[]的区别在于:对于数组的情况,必须用delete[]。如果用delete,整块存放指针的地址会被删掉,但是指针指向的空间只会删除一个,造成内存泄漏!内存泄漏是指针指向的空间,而不是整块地址本身。

一、复数代码

--------------------------MyComplex.h-----------------------

#pragma once
#include<iostream>
using namespace std;
class MyComplex{
public:
	friend ostream& operator << (ostream & out, const MyComplex & a);

	MyComplex(double real, double img);
	MyComplex(const MyComplex & mycmp);

	double real() const { return m_real; }
	double img() const { return m_image; }

	MyComplex& operator += (const MyComplex& mycmp);
	

private:
	double m_real;
	double m_image;
};

--------------------------MyComplex.cpp--------------------------

#include "MyComplex.h"
#include<iostream>
using namespace std;

MyComplex::MyComplex(double real = 0, double img = 0) 
	:m_real(real), m_image(img) {}

MyComplex::MyComplex(const MyComplex & mycmp) {
	m_real = mycmp.m_real;
	m_image = mycmp.m_image;
}

MyComplex& MyComplex::operator += (const MyComplex& mycmp) {
	m_real += mycmp.m_real;
	m_image += mycmp.m_image;
	return *this;
}

ostream& operator<< (ostream & cout, const MyComplex & a)
{
	cout << "real : " << a.real() << std::endl
		<< "real : " << a.img() << std::endl;
	return cout;
}

--------------------------Main.cpp--------------------------

#include <iostream>
#include "MyComplex.h"

using namespace std;
int main(int argc,char ** argv) 
{

	MyComplex a(1.2, 2.8);
	MyComplex b(a);

	a += b;
	cout << a;

	system("pause");
	return 0;
}

二、字符串代码

--------------------------MyString.h--------------------------

#pragma once
#include<string.h>
#include<iostream>

using namespace std;


class MyString
{
public:
	friend ostream& operator << (ostream& cout,const MyString &s);
	MyString(const char *s = 0);
	MyString(const MyString &s); 
	MyString& operator =(const MyString & s);

	char * get_c_str() const {
		return m_s;
	}

	~MyString();

private:
	char *m_s;

};

--------------------------MyString.cpp--------------------------

#define _CRT_SECURE_NO_WARNINGS

#include "MyString.h"
#include <iostream>

using namespace std;

MyString::MyString(const char *s)
{
	if (s) {
		m_s = new char[strlen(s) + 1];
		strcpy(m_s, s);
	}
	else
	{
		m_s = new char[1];
		m_s[0] = '\0';		
	}
}

MyString::MyString(const MyString &s){
	if (s.m_s)
	{
		m_s = new char[strlen(s.m_s) + 1];
		strcpy(m_s, s.m_s);
	}
	else
	{
		m_s = new char[1];
		m_s[0] = '\0';
	}

}

MyString& MyString::operator =(const MyString & s) 
{
	if (this->m_s == s.m_s)		return *this;
	
	delete[] m_s;
	m_s = new char[strlen(s.m_s) + 1];
	strcpy(m_s, s.m_s);
	return *this;
}

MyString::~MyString() {
	if (m_s)
	{
		delete[] m_s;
		m_s = nullptr;
	}
};

ostream& operator << (ostream& cout, const MyString &s) {
	cout << s.get_c_str() << endl;
	return cout;
}

--------------------------Main.cpp--------------------------

#include <iostream>
#include "MyString.h"

using namespace std;

int  main(int argc, char ** argv) {
	MyString s1("first");
	MyString s2(s1);
	MyString *str = new MyString("third");
	MyString s3;
	cout << "s2:"<<s2.get_c_str() << endl;
	s3 = s1;
	cout << "s3:"<<s3.get_c_str() << endl;
	cout << str->get_c_str() << endl;
	delete str;

	system("pause");
	return 0;
}

三、 补充内容

  1. 函数在内存中只有一份,处理多个对象依靠在成员函数中隐藏传入this指针,也就是成员对象的地址。this指针可以在函数内部使用。加了static的参数只有一份,但是static没有this指针,所以静态函数只能处理静态数据。注意static成员变量在类内声明,在类外定义(初始化)。

    #include <iostream>
    using namespace std;
    
    class Account
    {
    public:
    	static double m_rate;
    	static void set_rate(const double &x) { m_rate = x; }
    };
    
    double Account::m_rate = 0.8;
    
    int  main(int argc, char ** argv) {
    	Account::set_rate(0.5);
    
    	system("pause");
    	return 0;
    }
    
  2. namespace std{…} 防止重名,可以在不同文件中分段编写,最后会被拼在一起,所有std的东西都包含在其中。

四、继承 、复合、委托

Composition复合: A类中,有B类作为成员。符号:A黑色菱形+A指向B

 构造从内而外,先B后A;析构从外而内,先A再B

Delegation委托:A类中,有指向B类的指针作为成员。符号:A白色菱形+A指向B

什么时候创建对象是未知的(分配空间)

Inheritance继承:B类继承A类。符号:A+白色三角+B指向A

先调用父类的默认构造函数,然后再调用子类的构造函数,析构顺序相反
父类的析构函数必须是virtual的
父类函数的三种写法
- non-virtual : 不希望子类覆写              int objectID() const;
- virtual:希望子类覆写,但是父类有默认定义     virtual void error(const std::string& msg);
- pure virtual:子类必须覆写,没有默认定义     virtual void draw() const = 0;

Inheritance+Composition:

问:A类中 有B类作为成员,C类继承A类,ABC的构造和析构顺序?
答:构造:先调用Component,再调用Base,最后调用Child。 析构相反

问:B类继承A类,B类中 有C类作为成员,ABC的构造和析构顺序?
答:构造:先调用Base类,再调用Compnent,最后调用Child。 析构相反

Delegation+Inheritance:

Observer模式:三视图模型
Composite模式:目录文件模型
Prototype模式:

五、设计模式

一、Singleton(static+private)

需要用到static来直接通过类名或调用函数的方法 。

单例特点:构造函数放在private中,外界无法创建该类对象

静态单例最好放在函数中,只有当有人调用它,static对象才会被创建,并且在之后一直存在。

静态单例如果在构造函数中创建而没有使用,就会造成浪费

-------------------------------------该代码有问题,需要修改--------------------------

#include <iostream>
using namespace std;

class A {
public:
	static A& getA() {
		static A a;
		return a;
	}
	void setup(){
		cout << "setting up...." << endl;
	}
private:
	A();
	A(const A& rhs);
};

int  main(int argc, char ** argv) {

	A::getA();

	system("pause");
	return 0;
}
二、Adapter(Composition)

Adapter的模式:用于开放部分功能的设计。如果已经有成熟的类可以满足条件,通过Adapter设计模式进行修改以满足接口。比如deque是双向进出,可以通过改装后成为queue单向进出

template<typename T>
class queue
{
public:
	bool empty()const { return c.empty(); }
	void push(const T& x) { c.push_back(x); }
	void pop() { c.pop_front(); }
	T& front() { return c.front() };
	T& back() { return c.back() };

protected:
	deque<T> c;
};
三、pImpl(Delegation)

pImpl的模式:pointer to implementaiton:用于指针指向多种实现的设计

用一个指针指向具体实现的类,右边任何的改动都不影响左边,也就不影响客户端。当需要有不同的实现的时候,这个指针可以指向不同的实现类

class String{
public:
	...
private:
	StringRep* rep;
};
四、TemplateMethod(Inheritance)

TemplateMethod的模式:父类中无法写出的方法,等子类覆写

子类调用父类的方法,进入到父类中,发现子类覆写了父类的函数进入到子类中。

子类在调用父类的方法时,相当于子类的地址被隐式地传入父类函数中,从而进入到子类

class Base{
public:
	void OnFileOpen(){
	           .....
	           Serialize();
	           .....
	}
	virtual void Serialize(){};
};

class Child{
public:
	virtual void Serialize(){
		cout <<"balabala" <<endl;
	}
};

main(){
    Child child;
    child.OnFileOpen();
}
五、Observer(Delegation+Inheritance)

Observer的模式:设计一个类,这个类中存放指针队列,这些指针指向继承自同一父类的子类。比如说三视图模型:主题类中有一个指针队列,每一个指针都指向Observer的子类,Observer是父类,其每个子类都有各自的不同实现,可以用来观察主题的多个视角。

class Subject{
	vector<Observer * > m_views;
}

class Observer{
public:
	virtual void update(int value) =0;
};

class Observer1: public Observer{
public:
	Observer1(Subject * model){model->push_back(this)}
	void update(){...}
};
class Observer2: public Observer{
public:
	Observer2(Subject * model){model->push_back(this)}
	void update(){...}
};
class Observer3: public Observer{
public:
	Observer3(Subject * model){model->push_back(this)}
	void update(){...}
};
六、Composite(Delegation+Inheritance)

Composite模式:创建一个父类,多种子类继承这个父类,其中一个子类内部有一个指针数组,这些指针可以指向各种子类,实现这个子类存放其他子类类型和自身的功能。比如说创建一个组件父类,文件和目录继承组件类,目录类中设置一个队列,由此目录既可以存放目录也可以存放文件。其他例子:大窗口里面有小窗口等。

和Observer模式的区别在于,指针队列的位置不同。

class Component
{
	int value;
public:
	Component(int val) {value = val;}
	virtual void add(Component*){}
};

class Primitive:public Component
{
public:
    Primitive(int val):Component(val){}
}

class Composite: public Component
{
    vector<Component*> c;
public:
    Composite(int val):Component(val){}
    void add(Component * elem){
        c.push_back(elem);
    }
}
七、Prototype(Delegation+Inheritance)

Prototype模式:怎么和框架配合?我的妈耶,不是很明白。

提供自我复制的功能。设计一个父类,父类中写有克隆方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章