面向对象编程(上)
零、无指针的类、有指针的类 设计要点
-
构造函数初始化尽量用列表初始化的方法
-
对于不会改变数据内容的函数,马上再函数后面加上const, 万一在定义的时候没有加,如果对象是常量,调用函数时就会出错。
-
最好所有的参数传递都传递引用,
-
如果不希望在函数内部修改外面的实参,只需要在形参前加上const。
-
最好所有的函数返回都返回引用,但是要注意不能返回局部遍历的引用
-
同一个类的不同对象可以互相访问对方的私有成员
-
重载<<只能写成全局形式,因为cout在程序中只能有一个
-
只要有指针,就必须重写拷贝构造函数,拷贝赋值,析构函数
- 拷贝构造函数:先给指针对象分配空间
- 拷贝赋值:先清空原有内容,再分配空间,接着赋值
- 析构函数:回收原来空间
-
注意,拷贝赋值要检测自我赋值,不然删掉原来的内容的时候可能会出错
-
由系统自动分配的再栈区;由程序员手动new的再堆区,需要手动删除
-
内存泄漏:指针死了,占用的空间还在
-
new的底层: 先在内部调用malloc()分配内存,然后进行指针转型,接着调用构造函数
-
delete的底层: 先调用析构函数,然后在内部调用free(),删除内存
-
真实创建对象的情况在内存中会由多的cookie内存用于寻址,debug版本比release版本分配更多地址。在VC中,内存块一定是16的倍数,当内存不是16倍数的时候会自动填充空间。因为是16的倍数,所以地址的后四位一定是0,在被分配初期后被设置成1,释放后又变成0。
-
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;
}
三、 补充内容
-
函数在内存中只有一份,处理多个对象依靠在成员函数中隐藏传入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; }
-
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模式:怎么和框架配合?我的妈耶,不是很明白。
提供自我复制的功能。设计一个父类,父类中写有克隆方法