C++程序设计1(侯捷video 7-13)
一、Big three(拷贝构造、拷贝赋值、析构函数)(video7)
Big three指三个特殊函数,分别是拷贝构造函数、拷贝赋值和析构函数。
什么时候需要拷贝构造、拷贝赋值、析构函数:
当类中的数据是指针时,例如string类中保存字符串使用char *,那么就不能直接使用编译器给的默认Big three。因为默认的函数是按字节拷贝的,这样拷贝后的对象中的指针指向的位置和被拷贝的对象一样,这样不是真正的拷贝。
class mystring {
public:
//普通构造函数
mystring(const char* chr = );
//拷贝构造函数
mystring(const mystring& mstr);
//析构函数
~mystring(); //拷贝赋值
mystring& operator = (const mystring& mstr) {
//检测是否为自我赋值
if (this == &mstr) {
return *this;
}
//先将已有的空间释放,否则会内存泄漏
delete[] mychar;
//创建新的空间,大小和mstr.mychar指向空间一样大
mychar = new char[strlen(mstr.mychar) + ];
//赋值内容
strcpy(mychar, mstr.mychar);
//返回等号左边的mystring对象,以防连续赋值
return *this;
}
private:
//变量是指针,必须实现拷贝赋值和拷贝构造
char * mychar; }; inline mystring::mystring(const char* chr) {
//判断传入的指针是否为空(或默认为空)
if (chr) {
//如果不为空,则按chr的大小分配空间,并让mychar指向该空间
mychar = new char[strlen(chr) + ];
//将chr的数据复制到mychar指向的空间中
strcpy(mychar, chr);
//如果指针为空
}else {
//创建一个大小为1的空间
mychar = new char[];
//只保存一个\0
*mychar = '\0';
}
}
inline mystring::mystring(const mystring& mstr) {
//分配一个和mstr.mychar字符串一样大的空间,并让mychar指向该空间
mychar = new char[strlen(mstr.mychar) + ];
//将内容复制到mychar指向的空间中
strcpy(mychar, mstr.mychar);
}
inline mystring::~mystring() {
//当对象生命周期要结束的时候,必须清理内存(堆空间),否则会内存泄漏
delete[] mychar;
}
在上述代码中,拷贝构造函数做的事情实际上是深拷贝,而默认的拷贝构造函数做的事情是浅拷贝(只复制mstr.mychar指针的4byte到mychar中)。如下图所示:
代码中,以下部分非常重要:
//检测是否为自我赋值
if (this == &mstr) {
return *this;
}
如果this和mstr是同一个对象,那么如果没有自我赋值的检测,可能会导致程序出错。
因为我们在拷贝数据之前,第一步就是先delete[] mychar,那么也就是删除了mstr.mychar指向的空间。
第二步我们要参照mstr.mychar指向空间的大小来开辟空间就会出问题,更别提复制其中的内容。
所以,自我赋值检测非常重要。
二、为mystring类添加<<重载函数(video7)
//定义成员方法,获取mychar指针,不修改数据,加上const
inline char * mystring::get_c_str() const {
return this->mychar;
}
//重载操作符<<,使可以直接cout<<mystring_obj;
inline ostream& operator << (ostream& os, const mystring& mstr) {
os << mstr.get_c_str();
return os;
}
三、stack和heap(video8)
Stack栈:存在于某个作用域(scope)的一块内存空间。例如当调用函数,函数本身即会形成一个stack用来放置它所接受的参数,以及返回地址。
Heap堆:是指由操作系统提供的一块global内存空间,程序可动态分配从中获得若干块(blocks)。当使用完毕后,有责任主动去释放这块内存。
//存在一个类(忽略定义)
class Complex {}; //Global Object,存在于全局作用域,直到程序结束
Complex c4(1.0, 2.0);
//某个作用域,例如某个函数内部
{
//Stack Object,出作用域时自动清理,也叫Auto Object
Complex c1(1.0,2.0);
//c2指向的对象存在于heap堆中,叫Heap Object
Complex * c2 = new Complex(1.0, 2.0);
//static Object,出作用域该对象仍然存在,直到程序结束
static Complex c3(1.0, 2.0); delete c2;
}
上述代码中:
c1对象是放在stack中的(c1中如果存在指针指向的空间,那么那块空间还是在堆里,但在c1生命周期完结的时候,有析构函数去释放他们)。
c2指针指向的对象是放在堆空间的,new关键字表示在堆空间中分配地址存在对象。该作用域的栈中只存放了c2这个指针(4 bytes)。记得使用完后在作用域内使用delete p;释放空间,否则会内存泄漏。
c3叫静态对象,该对象存在直到程序结束。
c4叫全局对象,该对象是在所有local scope外定义(全局作用域),存在直到程序结束。
四、new和delete关键字的工作流程(video8)
new的流程:
Complex * px = new Complex(, );
编译器自动转化为以下操作:
Complex * px; //栈中分配一个指针空间 void *mem = operator new (sizeof(Complex)); //堆中分配空间,底层使用的是C语言的malloc(n)
pc = static_cast<Complex*>(mem); //将void*转型为Complex*
pc->Complex::Complex(, ); //调用构造函数相当于Complex::Complex(pc,1,2) pc表示this指针
简单介绍static_cast:static_cast<new_type>(expression)和C语言的类型强转是一样的。他们的区别一句话总结:static_cast在编译时会进行类型检查,而强制转换不会。
delete的流程:
mystring *ps = new mystring("hello");
delete ps;
编译器自动转化为以下操作:
mystring::~mystring(); //先调用析构函数,将对象中指针变量指向的空间释放掉
operator delete(ps); //然后再释放自己所占的堆空间,底层调用的是C的free(ps)
使用delete释放空间时遵循层次释放,如果对象里存在指向其他对象的指针(甚至多层嵌套),那么delete必须从最里层开始调,逐级释放内存。
五、VC当中的内存分配(video8)
假设我们要new一个Complex对象,该对象大小为8bytes。在Debug和Release模式下,内存分配是不同的。
注:仅限new的情况下。
Debug(左)Release(右)模式下:
浅绿色:Complex对象所占实际空间,大小为8bytes。
上下砖红色:各4bytes,一共8bytes。是cookie,用来保存总分配内存大小,以及标志是给出去还是收回来。例如00000041,该数为16进制,4表示64,即总分配内存大小为64,1表示给出去(0表示收回来)。
灰色:Debug模式下使用的额外空间,前面32bytes,后面1bytes,一共36bytes。
深绿色:内存分配大小必须是16的倍数(这样砖红色部分里的数字最后都是0,可以用来借位表示给出去还是收回来),所以用了12byte的填充(padding)。
同样,String对象的空间分配,如图:(左Debug,右Release)
六、数组的内存分配(video8)
//使用new分配数组空间
char * m_data = new char[strlen(cstr) + ];
//使用delete[]来释放数据空间
delete[] m_data;
对数组的空间分配,new叫做Array new,delete[]叫做Array delete。这两个要搭配使用,否则会出错。
注:仅限new的情况下。
Debug(左)Release(右)模式下,数组空间的分配:
灰色:即3个Complex对象的大小,每个是8bytes,一共24bytes。
深绿色:填充为16的倍数。
前后白色:51表示80bytes,“给出去”。
黄色:Debug模式额外占用空间。
中间白色:用一个整数表示数组中对象个数。
Array new必须搭配Array delete使用,不然会有以下后果(内存泄漏):
使用Array delete,操作系统才知道,我要释放的是一个数组,那么会根据数组中元素的个数分别调用元素对象的析构函数,确保所有元素对象内部所指向的内存空间完全释放。然后再通过free来释放数组本身。
七、static静态关键字(video10)
前面所实例中,没有涉及static关键字。对象的属性会存放在每个对象相应的位置,也就是说,有几个对象,数据就有几份。但是类中的成员方法只有一份,那么不同的对象在调用一个成员方法的时候,是通过以下步骤来分别处理自己的数据的:
Complex c1, c2, c3;
//c1,c2,c3分别调用real()来返回实部的值
cout << c1.real() << endl;
cout << c2.real() << endl;
cout << c3.real() << endl;
相当于以下伪代码:
Complex c1, c2, c3;
cout << Complex::real(&c1); //this == &c1
cout << Complex::real(&c2); //this == &c2
cout << Complex::real(&c3); //this == &c3
即,谁调用real()成员方法,谁就是this指针指向的对象。以此来分别处理自己的数据。
当成员属性或成员方法前面加上static关键字,那么该属性或方法就和对象脱离了。
静态属性和一般的成员属性不同,一般的成员属性是每个对象各有一份,而静态数据一共只有一份,例如一个类叫账户,有100W个人来开户,每个人是一个账户对象,里面的金额等是普通属性,每个账户中都有这个属性。但是利率则应该是静态属性,全局只有一份,因为每个人的存款利率应该是一致的。
静态方法和一般成员方法是一样的,也只有一份。但是静态方法的不同点是,静态方法没有this指针。静态方法只能操作静态属性。
class Account{
public:
//m_rate的声明
static double m_rate;
static void set_rate(const double r) {
m_rate = r;
}
};
//m_rate的定义
double Account::m_rate = 0.89;
使用:
//第一种调用方式,使用类名直接调用
cout << Account::m_rate << endl;
//第二种调用方式,使用对象来调用
Account a;
a.set_rate(0.50);
cout << a.m_rate << endl;
八、利用static实现单例模式(Singleton)(video10)
class A {
public:
//对外接口,获取实例(该实例是唯一的)
static A& getInstance();
//任意函数,供单例对象调用
void setup() { cout << "here is A.setup" << endl; }
private:
//构造函数放在private下,外部无法直接使用构造函数实例化对象
A(){}
//用于保存那唯一的一份对象
static A a;
}; A& A::getInstance() {
//第一次调用时,产生静态的成员属性A a
static A a;
//返回
return a;
}
使用:
A& p = A::getInstance();
p.setup();
九、模板(video10)
类模板:
//创建模板T
template<typename T>
class Complex {
public:
Complex(T r=,T i=):re(r),im(i){} Complex& operator += (const Complex&);
T real() const { return re; }
T imag() const { return im; } private:
T re;
T im; friend Complex& __doapl(Complex*, const Complex&);
};
使用:
{
//当使用double类型时,编译器会将Complex类定义中的T全部替换为double,产生一份代码
Complex<double> c1(1.0, 2.2);
//当使用int时,全部替换为int,又产生一份代码
Complex<int> c2(, );
}
但是,为不同的类型产生不同的代码是必要的,并不是缺点。因为如果手工去按类型定义类的话,同样是多份代码。
函数模板:
当我们设计一个函数可以对多种类型的数据使用时,例如:
template<class T>
inline T& min(T& a,T& b){
return a < b ? a : b;
}
函数模板在调用时无需使用<type>来指定,因为要使用函数模板,一定是调用函数,那么就会传实参,编译器就会进行实参推导。
函数模板和类模板是相同的,template中使用的typename和class也是相通的,可以替换使用。
十、命名空间namespace(video10)
namespace主要用来解决对人协调工作时的命名冲突问题。相当于把自己的代码包装一层,别人使用的时候可以以以下三种方式使用,我们以std为例:
//将命名空间std全部打开
using namespace std;
int main() {
//直接使用其中的方法
cin << ...;
cout << ...;
}
//按需打开,指定拿出的方法
using std::cout;
int main() {
//未指定的需要使用std::来使用
std::cin << ...;
cout << ...;
}
//完全使用std::func_name来使用
int main() {
std::cin << ...;
std::cout << ...;
}
如何创建namespace:
//直接将代码包含在namespace leo内部
namespace leo { class Account {
public:
//m_rate的声明
static double m_rate;
static void set_rate(const double r) {
m_rate = r;
}
};
//m_rate的定义
double Account::m_rate = 0.89;
}
面向对象编程、面向对象设计
类与类之间的三种关系:复合、集成、委托。
十一、复合Composition(video11)
复合表示has-a。即A类里有B类的对象(非指针)。
复合的图形表示,及构造和析构顺序:
如下代码所示:
class Person {
public:
Person() {
printf("Class Person 构造\n");
}
~Person() {
printf("Class Person 析构\n");
}
void say() {
printf("Hello");
}
}; class Family {
public:
Family() {
printf("Class Family 构造\n");
}
~Family() {
printf("Class Family 析构\n");
}
Person& getPerson() {
return p;
}
private:
//Family中存在Person对象p
Person p;
};
Family类中,存在一个Person对象。那么在初始化Family对象的时候,会根据上图里的先后顺序来构建对象。
void test() {
Family f;
} int main() {
test();
return ;
}
打印的内容:
Class Person 构造
Class Family 构造
Class Family 析构
Class Person 析构
所以,我们可以看出,在定义Family对象f时,先调用的是Person的构造函数,然后再调用Family的构造函数,是一个由内而外的过程。
而在test()函数结束时,f对象的生命周期完结,是先调用了Family的析构函数,然后再调用Person的析构函数,是一个由外而内的过程。
十二、委托Delegation(video11)
委托表示A类里有B类的对象的指针。委托也可以叫 “ 基于引用的复合(Composition by reference)”。
当A拥有B对象的指针,那么在任何时间都可以通过指针来调用B对象做事情,所以叫做委托。
设计模式:Handle/Body
委托可以引申出一种非常出名的设计模式,叫Handle/Body,或叫Pointer to Implementation。即A为对外接口,B为具体实现,A中接口的操作全部调用B来完成,这样的好处就是A可以一直不变,B可以随意改变,甚至可以有多个不同的B实现方式。
委托的图形表示为:
一个典型的例子:
上图中有abc三个A类(假设是String类)的对象,三个对象中的rep指针可以指向同一个B对象(实际存放字符串的类对象),假设abc是互相复制得来的,那么abc中存放的字符串应该是一样的,那么我们就可以在B中实现reference counting,即引用计数,只需要一个B对象,就可以支撑abc三个A的对象,但前提是都不对字符串内容给进行修改。这样就可以节省内存空间。
当其中一个对象,例如a,要对字符串进行修改,那么可以单独再创建一个B对象给a单独修改,然后先前的B对象就从abc三个对象共享变为bc两个对象共享。这种操作叫做“Copy on write”。
十三、继承Inheritance(video11)
继承的图形表示为:(T表示使用了模板,未使用则去掉T)
继承(public继承)传达的逻辑是is-a,表示“是一种”。例如B继承A,则说明B是A的一种。
代码如下:
struct _List_node_base
{
_List_node_base* _M_next;
_List_node_base* _M_prev;
}; template<typename _Tp>
//_List_node继承_List_node_base(public方式继承)
struct _List_node : public _List_node_base
{
_Tp _M_data
};
继承,相当于子类中包含了父类的成分(具体包含了多少,主要看继承的方式,public、protected、private)。
class Female {
public:
Female() { printf("Class Female 构造\n"); }
~Female() { printf("Class Female 析构\n"); }
}; class Girl : public Female {
public:
Girl() { printf("Class Girl 构造\n"); }
~Girl() { printf("Class Girl 析构\n"); }
};
void test() {
Girl();
} int main() {
test();
return ;
}
打印结果:
Class Female 构造
Class Girl 构造
Class Girl 析构
Class Female 析构
在构建子类对象时,要先构建其内部的父类成分,所以要先调用父类的default构造函数,再调用自己的构造函数,由内而外。
在销毁子类对象时,要先析构自己,再析构父类对象,由外而内。
注意:父类的析构函数必须是virtual的。否则会出现undefined behavior。所以,在设计类的时候,如果一个类设计为父类,则析构函数就设计为虚拟函数,或者一个类以后有可能成为父类,那么也可以设计为虚拟析构函数。
十四、虚函数 Virtual function(video12)
子类继承父类的时候,在public继承方式下,子类继承了所有父类的数据(成员属性),而且还继承了所有父类的函数调用权(只是调用的权利,函数还是只有父类的那一份)。
父类方法分为三类:
1.non-virtual函数:不希望子类(派生类derived class)重新定义(override,覆盖)它。override这个概念不能乱用,只有当子类重新定义基类同名虚函数时,才叫override。
2.virtual函数:希望派生类去重新定义这个函数,而且已经对其有一个默认定义(默认实现)。
3.pure virtual函数:希望派生类一定要重新定义这个函数,因为现在完全没有定义它(无默认定义)。
class Shape {
public:
//纯虚函数,用virtual function = 0的形式
virtual void drow() const = ;
//虚函数,应该有定义(可以是inline的,也可以在外部定义)
virtual void error(const std::string& msg);
//非虚函数,即普通函数
int objectID() const; //Todo...
}; inline void Shape::error(const std::string& msg) {
cout << msg << endl;
}
inline int Shape::objectID() const {
printf("Function objectID\n");
return ;
} //定义一个长方形类,public继承Shape
class Rectangle :public Shape {
public:
Rectangle() {}
//实现drow(),这是必须实现的
void drow() const {
printf("Rectangle Drow\n");
}
//override error(),实现子类的个性化操作
void error(const string& msg) {
cout << "Rect " << msg << endl;
}
};
其中Shape类不能直接实例化对象,因为其中包含drow()方法,这是一个纯虚函数,必须由子类来override。
所以我们要使用这个类的话,只能创建一个子类来继承他,然后覆写(override)他的所有纯虚方法,一般的虚函数(非纯虚)可以根据需求决定是否覆盖。
十五*、overload override overwrite的区别(video12)
参考来自:https://www.cnblogs.com/kuliuheng/p/4107012.html 感谢VictoKu
1. Overload(重载)
重载的概念最好理解,在同一个类声明范围中,定义了多个名称完全相同、参数(类型或者个数)不相同的函数,就称之为Overload(重载)。重载的特征如下:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
2. Override(覆盖)
覆盖的概念其实是用来实现C++多态性的,即子类重新改写父类声明为virtual的函数。Override(覆盖)的特征如下:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数列表完全相同;
(4)基类函数必须有virtual 关键字。
3. Overwrite(改写)
改写是指派生类的函数屏蔽(或者称之为“隐藏”)了与其同名的基类函数。正是这个C++的隐藏规则使得问题的复杂性陡然增加,这里面分为两种情况讨论:
(1)如果派生类的函数与基类的函数同名,但是参数不同。那么此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。那么此时,基类的函数被隐藏(注意别与覆盖混淆)。
十六*、设计模式Template Method(video12)
用虚函数和继承,实现一个非常有名的设计模式,叫Template Method。
假设,我们要打开文件,并读取里面的内容,那么对于不同的文件,我们其实有很多步骤是相同的,例如找到文件目录,选中文件,打开文件,关闭文件等。但是可能我们有一个动作可能是各不相同的,例如读取其中的内容(各种文件内容格式不同,需要的处理也不同)。那么我们可以以下图中的做法:
步骤:
1.在父类CDocument中,实现共同的方法,例如OpenFile、CloseFile等。
2.CDocument中,将读文件内容的方法Serialize设计为虚函数或纯虚函数。
3.CMyDoc继承CDocument,实现Serialize()。
4.使用子类CMyDoc调用父类方法OnFileOpen(),按图中灰色曲线的顺序来调用内部函数。
这样就实现了关键功能的延迟实现,对于做应用架构(Application framework)的人来说,可以在1年前写好CDocument类,将这个架构卖给其他人,然后再由购买方自己来实现CMyDoc类。这就是典型的Template Method。
为什么会有灰色曲线的调用过程:
1.当子类myDoc调用OnFileOpen()的时候,实际上对于编译器是CDocument::OnFileOpen(&myDoc);因为谁调用,this指针就指向谁,所以调用这个函数,myDoc的地址被传进去了。
2.当OnFileOpen()函数运行到Serilize()的时候,实际上是运行的this->Serialize();由于this指向的是myDoc,所以调用的是子类的Serilize()函数。
代码示例:
class CDocument {
public:
void OnFileOpen() {
cout << "dialog..." << endl;
cout << "check file status..." << endl;
cout << "open file..." << endl;
Serialize();
cout << "close file..." << endl;
cout << "update status..." << endl;
}
//父类的虚函数,当然这里是纯虚函数也是可以的,virtual void Serialize() = 0
virtual void Serialize() {}
}; class CMyDoc :public CDocument {
public:
//这里实现了父类的虚函数Serialize()
virtual void Serialize() {
cout << "MyDoc Serialize..." << endl;
}
};
调用:
#include "CDocument.h" int main() {
CMyDoc mc;
mc.OnFileOpen();
return ;
}
输出:
dialog...
check file status...
open file...
MyDoc Serialize...
close file...
update status...
十七*、继承+委托的模式(video12)
假设我们要做一个类似PPT的多窗口功能,即多个窗口观察同一份数据,例如多窗口画图。也可以是不同类型的窗口(例如,数字、图标、饼图、直方图等)来观察同一份数据。
我们的数据设计在类Subject中,窗口(观察者)设计为Observer,这是一个父类,可以被继承(即可以支持派生出不同类型的观察者)。
用如下代码来实现:
//数据类
class Subject {
private:
int m_value;
//保存观察者指针(相当于开的窗口指针)
vector<Observer*> m_views;
public:
//添加观察者
void attach(Observer* obs) {
m_views.push_back(obs);
}
void set_value(int value) {
m_value = value;
notify();
}
void notify() {
//通知所有的观察者,数据有更新
for (int i = ;i < m_views.size();++i) {
m_views[i]->update(this, m_value);
}
}
};
//观察者基类
class Observer {
public:
//纯虚函数,提供给不同的实际观察者类来实现不同的特性
virtual void update(Subject*, int value) = ;
};
用图形来描述:
十八*、Composite设计模式(组合模式)(video13)
虽然文件夹和文件是不同的对象,但是他们都可以被放入到文件夹里,所以一定意义上,文件夹和文件又可以看作是同一种类型的对象,所以我们可以把文件夹和文件统称为目录条目(directory entry)。在这个视角下,文件和文件夹是同一种对象。
所以,我们可以将文件夹和文件都看作是目录的条目,将容器和内容作为同一种东西看待,可以方便我们递归的处理问题,在容器中既可以放入容器,又可以放入内容,然后在小容器中,又可以继续放入容器和内容,这样就构成了容器结构和递归结构。
这就引出了composite模式,也就是组合模式,组合模式就是用于创造出这样的容器结构的。是容器和内容具有一致性,可以进行递归操作。
图中Primitive代表基本的东西,即文件。Composite代表合成物,即文件夹。Component表示目录条目。
Primitive和Composite都是一种Component,而Composite中可以存放其他的Composite和Primitive,所以Composite中的Vector存放的类型时Component指针,也就包含了Primitive和Composite两种对象的指针。
代码框架如下:
//一个比较抽象的类,相当于目录条目
class Component { int value;
public:
Component(int val) :value(val){}
virtual void add(Component*) {}
};
//相当于 文件类
class Primitive {
public:
Primitive(int val):Component(val){}
};
//相当于 文件夹类
class Composite {
vector<Component*> c;
public:
Composite(int val) :Component(val){} void add(Component* elem) {
c.push_back(elem);
}
};
具体关于文件文件夹实例可以参照:https://www.jianshu.com/p/685dd6299d96 感谢:六尺帐篷
十九*、Prototype模式 原型模式(video13)
设计应用架构时,并不知道以后实现的子类名称,但有要提供给Client调用子类的功能怎么办?
例如十年前设计的架构,子类在十年后继承父类并实现功能。Client只能调用架构中的父类,如何通过父类调用到不知道名字的子类对象。使用Prototype模式:
#include <iostream>
#include <vector>
using namespace std; //可能是十年前写的框架,我们不知道子类的名字,但又希望通过该基类来产生子类对象
class Prototype {
//用于保存子类对象的指针(让子类自己上报)
static vector<Prototype *> vec;
public:
//纯虚函数clone,让以后继承的子类来实现,也是获取子类对象的关键
virtual Prototype* clone() = ;
//子类上报自己模板用的方法
static void addPrototype(Prototype* se) {
vec.push_back(se);
}
//利用该基类在vec中查找子类模板,并且通过模板来克隆更多的子类对象
static Prototype* findAndClone(int idx) {
return vec[idx]->clone();
} //子类实现自己操作的函数,hello()只是个例子
virtual void hello() const = ;
};
//定义静态vector,很重要,class定义中只是声明
vector<Prototype *> Prototype::vec; //十年后实现的子类,继承了Prototype
class ConcreatePrototype : public Prototype{
public:
//用于在Prototype.findAndClone()中克隆子类对象用
Prototype * clone() {
//使用另一个构造函数,为了区分创建静态对象的构造函数,添加了一个无用的int参数
return new ConcreatePrototype();
} //子类实现的具体操作
void hello() const {
cout << "hello" << endl;
} private:
//静态属性,自己创建自己,并上报给父类Prototype
static ConcreatePrototype se;
//上报静态属性给父类
ConcreatePrototype() {
addPrototype(this);
}
//clone时用的构造方法,参数a无用,只是用来区分两个构造方法
ConcreatePrototype(int a) {}
};
//定义静态属性,很重要,有了这句,才会创建静态子类对象se
ConcreatePrototype ConcreatePrototype::se;
步骤:
1.子类继承Prototype父类,定义静态属性的时候,自己创建一个自己的对象,此时调用的是无参数的构造函数。并将创建好的自己的指针通过addPrototype(this)上传给基类的vector容器保存。
2.基类定义好的纯虚函数clone(),由子类实现,并在其中通过另一个构造函数产生对象并返回。
3.在Client端,使用基类的findAndClone(),获取vector中的子类对象模板的指针,来调用子类对象的clone功能,返回一个新的子类对象,调用多次则可创建多个对象供用户使用。
4.创建出的子类对象可以调用在子类中实现的hello()方法,进行想要的操作。
Prototype* p = Prototype::findAndClone();
p->hello();
C++程序设计1(侯捷video 7-13)的更多相关文章
- C++程序设计2(侯捷video all)
一.转换函数Conversion function(video2) 一个类型的对象,使用转换函数可以转换为另一种类型的对象. 例如一个分数,理应该可以转换为一个double数,我们用以下转换函数来实现 ...
- C++程序设计1(侯捷video 1-6)
一.头文件的防御式声明(video2) #ifndef __COMPLEX__ #define __COMPLEX__ //内容 #endif 二.初步感受模板(video2) 定义的时候: //复数 ...
- C++标准库(体系结构与内核分析)(侯捷第二讲)
一.OOP和GP的区别(video7) OOP:面向对象编程(Object-Oriented programming) GP:泛化编程(Generic programming) 对于OOP来说,我们要 ...
- list源码4(参考STL源码--侯捷):transfer、splice、merge、reverse、sort
list源码1(参考STL源码--侯捷):list节点.迭代器.数据结构 list源码2(参考STL源码--侯捷):constructor.push_back.insert list源码3(参考STL ...
- 侯捷STL学习(九)--关联式容器(Rb_tree,set,map)
layout: post title: 侯捷STL学习(九) date: 2017-07-21 tag: 侯捷STL --- 第十九节 容器rb_tree Red-Black tree是自平衡二叉搜索 ...
- From COM to COM 侯捷 1998.06.12
摘要: 本文簡介 C++ Object Model 和 Component Object Model 的基本概念,並引介四本書籍: 1. Inside The C++ Object Model 2. ...
- 快笑死,侯捷研究MFC的原因
与我研究VCL框架代码的原因一模一样:就是N年了,感觉自己还是没有掌握Delphi,惊叹别人各种各样神奇的效果,自己却不会,更不知为什么这样做,离高手的距离还有十万八千里.而且编程的时候,就像侯捷说的 ...
- 侯捷C++ Type traits(类型萃取
泛型編程編出來的代碼,適用於任何「吻合某種條件限制」的資料型別.這已成為撰寫可復用代碼時的一個重要選擇.然而,總有一些時候,泛型不夠好 — 有時候是因為不同的型別差距過大,難以產生一致的泛化實作版本. ...
- 评侯捷的<深入浅出MFC>和李久进的<MFC深入浅出>
侯捷的<深入浅出mfc>相信大家都已经很熟悉了,论坛上也有很多介绍,这里我就不多说了. 而李久进的<mfc深入浅出>,听说的人可能就少得多.原因听说是这本书当时没有怎么宣传,而 ...
随机推荐
- 封装QtCore(在非Qt项目里使用QString,QJson,QFileInfo,QFile等类)
单独封装QtCore 一直以来使用QT的特性使用惯了,很多东西QT都封装得很好.如果突然有一天,不使用QT开发了,是不是不习惯. 比如我们经常使用QString很多方法,string,wstring之 ...
- virtualbox下ubuntu共享文件夹自动挂载
1.若想删除挂载,可执行命令 umount -f /mnt/share 2.若想开机自动挂载,可以在 /etc/fstab 文件末添加一项 sharing /mnt/share vboxsf ...
- USER_AGENT 知识
USER-AGENT 是 Http 协议中的一部分,属于头域的组成部分,User Agent也简称 UA,意为用户代理,当用户通过浏览器发送 http 请求时,USER_AGENT 起到表明自己身份的 ...
- NPM切换源
可以试试切换下你的NPM源.看是否能得到解决.国内的NPM有CNPM和淘宝的NPM源比较稳定.npm源切换和工具可参照站内贴 nrm工具的使用或者是直接用命令切换 npm config set r ...
- WPF DataGrid自定义列DataGridTextColumn.ElementStyle和DataGridTemplateColumn.CellTemplate
<Window x:Class="DataGridExam.MainWindow" xmlns="http://schemas.microsoft.c ...
- JavaScript 中的12种循环遍历方法
原文:JavaScript 中的12种循环遍历方法 题目:请介绍 JavaScript 中有哪些循环和遍历的方法,说说它们的应用场景和优缺点? 1.for 循环 let arr = [1,2,3];f ...
- 启动组织重整 Marvell追求创新文化
最近接任Marvell技术长的Neil Kim正是该公司亟需的人才——他在今年四月加入后,预计将为Marvell带来正面.积极的改革契机,有机会让该公司彻底改头换面... 迈威尔科技(Marvell) ...
- Win8 Metro(C#)数字图像处理--2.46图像RGB分量增强效果
原文:Win8 Metro(C#)数字图像处理--2.46图像RGB分量增强效果 [函数名称] RGB分量调整 RGBAdjustProcess(WriteableBitmap ...
- UWP 浏览本地图片及对图片的裁剪
原文:UWP 浏览本地图片及对图片的裁剪 1.前言 准备给我的校园助手客户端添加一个修改头像的功能,但是查了好多资料都没有找到裁剪图片的简单的方法,最后才找到这个使用Launcher调用系统组件的简单 ...
- 使用Chart控件进行实时监控
Chart作为微软提供绘制图表的控件,在刚开始使用时非常的迷茫,因为功能强大,涉及到的知识多, 一开始难以接收过来,但后天经过查找资料,耐心学习,终于还是有了一定的收获. Chart相当于一个大的图纸 ...