1.头文件与类声明
1.1头文件声明
库中自带头文件 #include <xxx>
自定义头文件 #include "xxx"
1.2 防卫式声明
1.ifndef表示在文件内第一次没有定义时候,进行声明定义
2.在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件上时,就会出现大量“重定义”错误。
3.在头文件中使用#define、#ifndef、#endif能避免头文件重定义。
1.3 类声明
数据尽可能放在private中,函数绝大部分要放在public中
pubilc > protected > private(所有的数据都应该放在 private,打算被外界调用的函数应该是public)
1.4 inline内联函数
函数在class body本体里定义就是inline,在外面定义就不是inline,函数是inline function会速度很快,但有些function即使写上inline,编译器也不会做成inline,也没有能力做成inline,如果函数太复杂,就没有办法inline,函数简单,编译器就可能做成inline,程序员的inline只是对编译器的建议,是不是真的inline要由编译器决定,
inline关键字用来定义一个类的内联函数,解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题。
2.构造函数
2.1构造函数初始化
构造函数名与类名相同,可以有参数,参数也可以有默认值,没有返回类型,用于创建对象
构造函数和其他函数都可以有参数的默认值,初始列初值列只有构造函数有此功能,
构造函数中,成员变量初始化中使用":"函数方式列表初始化性能比直接在构造函数中赋值要快,建议使用这种方式。
因为一个变量的数值的设定有两个阶段,一个是初始化,第二个是赋值, 初值列属于初始化阶段,在大括号里赋值则表示放弃了初始化阶段,效率下降,
不带指针的类class大多不用写析构函数(内存泄漏)
2.2构造函数的重载
可以有很多重名的构造函数,即构造函数重载
编译器会把函数的名称,函数里的参数有几个以及参数的类型,编译成只有编译器可以看懂的内容,所以实际上不会有两个完全相同名称的函数同时存在,
重载real函数时,其实编译器会编译成两个函数,虽然同名但不重载
黄色标注的构造函数定义将出现问题,与上面的构造函数存在冲突,因为上面的构造函数有默认参数,有默认值,在无参初始化该类对象时,因为第一个构造函数已经有参数默认初始化列表了,定义该类对象时可以不加入参数,这就产生了冲突。
4参数传递与返回值
构造函数通常不能放在private里,因为不能被外界调用但是创建对象的时候又会调用,产生冲突,但是设计模式中的单例模式可以把构造函数放在private里,
单例模式表示外界只能有一份A,所以不允许外界创建,那一份A在static修饰的地方,外界调用的时候不能用传统的创建的方式,要通过A::的class 的函数getInstance去取得里面的他自己的那一份,
4.2 常量成员函数
在函数小括号的后面,大的花括号的前面加const,上图real()和imag()两个函数是要取得复数的实部虚部,所以这两个函数不会改变对象里的数据,只是拿出来而已,如果是写进去则改变了数据,
类class里面的函数分为会改变数据的和不会改变数据的这两种,不会改变数据内容的函数要加上const,const函数的意思是不改变数据的内容
const关键字:成员函数中没有改变成员变量的操作,如get函数,建议在该方法声明处加入const关键字
初始化类时,如果加入了const关键字,则该类不能被修改,只能读取属性,相应的,所调用的成员方法在定义时应该加上const关键字,否则编译器可能会报错,因为调用者用const修饰不能修改的创建的对象,但是调用的函数里可能修改值,
4.3 参数传递
引用在底部就是一个指针,所以传引用就相当于传递指针,速度很快,调用+=相当于把复数c1放入(const complex&)里,
最好所有的参数传递都传递引用,尽量不要传值,
传过去并且不希望对方改动,可以加const,如+=(const complex&)表示传一个复数过去,是引用的传递方式,但是在函数里不能修改这个内容,因为改动就会影响原来的部分,函数改了就会编译出错,
4.4 返回值传递
返回值的传递也尽量(如果可以)用引用,
4.5 友元
设计一个类:1.数据一定放在private里,2.参数尽可能以reference来传递,具体分析是否要加const,3.返回值尽量以reference来传递,4.在类的body里的函数应该加const修饰函数就应该加,如果不加const,使用者调用函数的时候编译器可能会报错 5.构造函数有初始化列表的语法,尽量用这个语法,
一个函数的运算结果,结果放的位置,情况1.函数创建一个地方让它放,情况2.放到某个已经存在的参数上,情况1返回的就是新创建出来的东西,会在函数结果后生命周期结束,此时不能返回引用reference
5 操作符重载
在c++里,操作符就是一种函数,是可以重新定义的,
+=是二元操作符,由于这是一个成员函数,所以编译器会把+=作用在左边,所以左边就成为一个隐藏的参数放进来,不一定是第一个,也可能是最后一个,所以+=如果为成员函数只需要写右边的参数就可以,如果左边有对+=定义,编译器就会调用定义的函数,因为右边要加到左边上面,所以右边不动,所以参数加const即const complex &r
所有的成员函数一定有一个隐藏的参数为this,谁调用这个函数,this pointer就指向调用者,编译器把+=作用在左边,所以c2就是this,this是一个指针,所以编译器会自动的把c2的地址传入,当成this pointer,成员函数不用写出来this,写出来就会报错,this pointer不一定是第一个参数,不同的编译器不一样,也可能在最后一个参数位置,
任何一个成员函数都有一个隐藏的 this指针,不需要显式声明,但是可以使用,指向调用者,C2是调用者
如上图所所示,在运算符重载过程中隐藏了this指针,该指针编译器会处理,使用者不能显式声明
操作符重载时,返回值不能是void,单个操作时不会报错,但连续操作是由于没有返回值会出现问题
5.1 return by reference 語法分析
把c1传到函数里,c1其实是一个value,它接收的时候是用reference引用接收,即const complex& r来接收,传递c1的传递者无需知道接收者是以引用或是值传递的形式接收,而c语言中如果使用point指针,传递者必须知道是用point指针类型
当操作符重载后,使用者没有链式调用操作符时,可以用void作为操作符重载的返回类型,但是链式调用的时候就不可以为void返回类型,
return *this就是返回object,而inline complex&接收端用什么形式。传递者不管,接收者用&接收,速度就会快,
5.2 class body 之外的各種定義
没有带类class的名称的函数就是全域函数global,注意1.类class complex 尽量用reference来传递,速度快,
5.3 临时对象
没有开辟新的对象让虚数和实数相加,直接用临时对象返回,不能用引用传递
相加的结果在函数里面创建一个来存放结果,此结果离开函数后就死亡了,所以此时不能传递reference引用进去,因为传入后函数结果后返回的引用是错误的东西,所以临时对象要return by value,
typename()就像写int(),黄色部分也是临时对象,没有参数就是默认值,构造函数有默认值,临时对象声明在下一行就没了,
标红的部分因为是正号,表示都不变,实际是可以返回引用类型,但此次标准库没有写,实际写引用不会报错,
操作符有两种写法,一种是成员函数,一种是非成员函数,对于特殊的操作符如<<只有一种写法即全局global的写法,
<<的重载,<<是把右边输出到左边,编译器看到cout<<conj(c1)编译器会把<<操作符作用在左边身上,是没有作用在右边身上的语法的,cout可以代表屏幕,要输出到屏幕,是标准库早就写好的,不可能认识现在的复数类型,只认识当时既有的内置的类型,所以对于<<操作符不可以写为成员函数,
operator后面与<<之间可以有几个空格,也可以没有,是自由形式写法,
第二参数是complex,用引用传递速度快,并且不想被改动所以加const,第一参数cout是一个对象object,cout的class名是ostream,是一种type,可以传递引用,所以ostream& os,此处不能在前面加const,因为加了后表示ostream& os传入后不可以改os,实际上向cout里每一次放东西的时候都会改变os的状态,返回值因为链式调用所以不能用void类型返回,cout<<c1<<conj(c1)的运行顺序是先运行前面cout<<c1输出c1后把得到的结果再接收<<conj(c1),所以这个函数执行完的结果还要是cout这种ostream类型,不能是void,再看引用传递还是值传递,因为os不是函数里的临时变量local,所以优先用reference引用,返回类型ostream&前面也不能加const,因为cout<<c1后还要接收右边的参数,即又要改变cout本身,所以cout不可以加const,
返回的时候如果返回传出的东西不是local object,即不是在这个函数本体里创建出来,可以传引用,
操作符重载一定作用在左边这个操作上,而c1<<cout把<<作用在c1上,违背习惯,可以写出来让使用者使用,但是要避免,所以要重载操作符,写成非成员函数,因为cout<<c1,此写的形式决定了上面的参数,所以上面函数写第一个参数是ostream& os,第二个参数是const complex &x,cout<<c1应该再传回一个ostream使得再次接收右边的<<endl,所以应该使得返回值是cout类型的即ostream,ostream并不这个操作符重载函数的local的object,是本来就有的,所以可以传回引用,
7 三大函数:拷贝构造,拷贝复制,析构函数
如果没有写,编译器会默认给拷贝构造和拷贝复制,相当于一个一个的拷贝复制过去,对于不带指针的类class如complex够用,但是对于带指针的类class如string,如果还是使用编译器给的一套拷贝构造和拷贝复制,会出现问题,比如,一个对象里面有一个指针指向另外一个地方,现在要产生新的对象,拷贝只是指针会拷贝过来,这两个会指向同一个地方,并不是真正的拷贝,所以类class里带指针,则不能使用编译器给的一套拷贝构造和拷贝复制,要自己实现,
字符串的长度c和c++用后面有\0结束符,Pascal用前面标明长度的写法,
如果传进来的指针是0就到else,虽然是空指针,但是还是会准备一个字符的位置放结束符,形成一个空字符串,
strlen(cstr)+1的+1是最后的结束符,准备了足够的空间存放传入的字符数组,
有指针的类不析构就会造成内存泄漏,class里如果有指针,多半就是要做动态分配,在对象要死亡之前前一刻,析构函数会被调用起来,必须在析构函数里把动态分配的内存释放还给系统,
大括号括起来的{}就是一个作用域,p是指针,所以离开作用域之前要释放,而上面的s1和s2在离开作用域的时候析构函数就会被自动调用,所以上图作用域{}里有三个字符串,离开的时候要调用三次析构函数,
如果没有自己写拷贝赋值,编译器会默认一个bit一个bit位的拷贝,a和b两个对象里面的data是一个指针,所以两个会指向一个地方,称为浅拷贝,只把指针拷贝过来,会造成内存泄漏,也会造成两个指向同一个地方,产生alias别名的危险,
传进来的蓝本不会改动,所以加const,拷贝构造函数应该创建一份足够的空间来放蓝本,然后把内容拷贝过去,蓝色的两行意思完全相同,第一行以s1为蓝本创建出s2,第二行是把s1赋值到s1,而s2也是新创建的对象,所以要调用构造函数,拷贝构造称为深拷贝,
拷贝赋值经典写法:1.先delete自己,要把自己先消除,2.然后重新分配一块与s1一样大小的(要加1加结束符)空间,3.把s1的内容拷贝过来给s2
如果类有指针,必须写拷贝赋值
检测自我赋值:s2=s1,检测是不是同一个,s1就是传进来的引用const String& str,s2就是自己,因为这个函数=现在是成员函数,所以会有一个this pointer传进来,可能是第一个参数或者最后一个参数,所以检查this指针与&str这两个指针指的是不是同一个东西,就知道是不是自己赋值给自己,如果是就直接return,效率高而且有正确性,但不写可能出错,因为如果开始左与右指的是同一个东西,此时进行赋值第一个动作是杀掉自己,此时就把唯一的一个杀掉了,就会出错,
上面参数const String&str里的&是引用,放在typename 的后面,下面的this==&str的&是取地址address of,放在object 的前面,得到一根指针,是不同的概念,
写在inline内联函数get_c_str()里,直接去取得里面的指针,现在通过要输出的字符串调用这个函数get_c_str(),就取得指针,传给cout
因为get_c_str()没有改动m_data这个数据,所以在小括号后大括号前加const,而上面的三个函数其实都会去改动m_data指针,所以他们是不可以加const,如果加就要加在末尾分号前,不仅仅是这个例子,所有的类里面的big three的函数都不可以加const,因为拷贝构造和拷贝赋值都是要从来源端写到目的端的,一定改变了目的端本身的数据,
8 堆,栈与内存管理
如果用new 的方式去从堆里面取得的内存,对象或者任何东西,都必须手动的delete,
生命在作用域结束的时候析构函数就会被自动的调用起来,所以叫做local object,又称为auto object,自动清理,
static修饰的local object的析构函数不会在离开大括号的时候被调用,到整个程序结束的时候被调用,是静态对象的特性
全局对象,在全局作用域中,任何大括号之外的,
new了一个复数要delete当初new的时候得到的指针,delete同时也调用起复数的析构函数,之前复数没有写析构函数因为编译器已经给一套默认的析构了,
内存泄漏:有一块内存经过某些时间和作用域之后对其失去了控制,无法还给操作系统,因为内存是一种很重要的有限的资源,
new之后得到的结果是一个指针p,指针在离开这个作用域后,这个指针本身就死亡了,但是指针所指的那块空间还在,
operator new只是一个名字特别的函数,这个函数名字就是operator new,中间有空格,这个函数内部会调用malloc,复数的两个double大小为8,②是转型,将void转换为complex类型,③是调用起构造函数,
构造函数是一个成员函数,所以一定有this pointer,③是通过指针来调用,谁调用这个函数,谁就是this,所以pc就是这里隐藏的参数,而这个pc就是这块内存的起始位置,
delete 先调用析构函数,然后释放内存,次序与new相反,delete被编译器转化为两个动作①首先调用析构函数,在复数里并没有写析构函数,用的是编译器给的析构函数,是不做事的,所以复数死亡的时候没清为0,因为马上就要死亡了,所以没有必要清,string的析构函数要把字符串里面的动态分配的内存给杀掉,至于字符串本身只是一个指针而已,②调用c++的一个特殊函数operator delete函数,里面是调用free,此时才把字符串本身杀掉,字符串本身只是一个指针而已,所以delete是有两个删除动作,一个是两步的第二步,一个是class本身有动态分配内存,所以它的析构函数要把动态分配的内存杀掉,
new一个复数,两个double获得8个byte字节,除了这8个字节,在调试模式下,每个灰色是4字节,上面还会得到32字节,下面也会得到4字节,除此以外还会带着上下两个红色的cookie,要内存,系统就会赋给cookie,总计在vc编译器下加起来52字节,而vc给的每一个内存块一定是16的倍数,最靠近52的16倍数是64,所以会有一些深绿色的填补物,
在非调试模式下,一个复数的真正大小是16字节,
上下cookie的作用,最主要的是要记录所给的整块内存的大小,因为将来系统回收的时候,只给一个指针,系统不知道要回收多大,所以会在某个地方记录这个长度,malloc和free约定好在每一个区块的上面放一个cookie,对于复数,64的16进制是40,40借用最后一个bit位0或1来表现这一块是给出去还是收回来,对于操作系统而言是给出去的,对于程序是获得了,所以是41,最后一个bit是1,16的16进制就是10,所以用11,因为16的倍数所以最后4个bit位都为0,所以可以借其中一个来用,对于字符串,字符串本身只内含一个指针,所以大小就是4字节,在调试模式下一共是48,是16的倍数,所以不需要加pad,48的16进制是30,在给出的时候是31,还给操作系统的时候是30,在非调试模式下一共12字节,调为16的倍数为16,转化为16进制为10,11表示给出去,
之前在new一个复数的时候,得到两个double 的一块空间,其实并不是这样,而是现在多得了很多的东西,是一种必要的浪费,因为之后回收的时候需要靠某些东西进行顺利的回收,
有中括号的叫做array new 和array delete, array new 要搭配array delete,不然会出错,会造成内存泄漏,
vc的编译器用一个整数记录三份东西,所以额外加4,72调整到16的边界为80,创建一个数组里面有三个复数,80的16进制就是50h
delete会被拆为两个动作,两个动作里面的第二个动作会把这块地址删掉,这一块删掉必须看cookie,cookie删掉这一块不会造成内存泄漏,因为cookie完整记录了,左边delete写了中括号或者右边delete不写中括号都不影响这一块的删除,因为这一整块的大小就记录在cookie里,这块不会发生内存泄漏。而只有delete[] p这么写,编译器才知道要删除的是一个数组,才会知道下面不是只有一个,而是有三个,就会调用三次析构函数,每一个对象死亡之前,析构函数都要被调用起来,这三次析构函数就是各自负责把各自的动态分配的内存杀掉,而写为delete p,没写中括号,编译器不知道下面有三个,以为只有一个,所以只调用一次析构函数,析构函数会把指针所指的这一块空间回收,不知道有二和三,析构函数执行完后来才会来删除整体的cookie里的整体部分,
复数没有指针,即使用array new而没有用array delete也可以,
cout就是console out,
10.扩展补充:类模板,函数模板,及其他
real函数全名是complex::real,而谁调用real谁就是this pointer,此时是c1,所以c1的地址,称为一个指针,就成为了this pointer,此时在调用相同的函数,但是传给它不同的地址,这样才可以通过不同的地址去处理到不同的数据,complex::real是一样的只有一份,没有使用static的时候成员函数只有一份,一份函数要处理很多份对象,一定要有某个数据告诉它去处理谁,靠的就是this pointer传进来,根据this pointer找到要处理的数据在哪里,
加了static后数据就和对象脱离了,静态数据只有一份,不属于对象,例如银行的利率,静态函数也只有一份,静态函数与一般成员函数差别在于静态函数没有this pointer,所以不能像一般的成员函数一样去存取访问对象里面的数据,显然只能存取静态的数据,
如果是静态的数据,一定要在class的外面设初值,严格来讲这一行叫做定义,使得里面的变量获得内存为定义,在类里面说这个变量是静态的只是声明,因为是脱离于对象之外的,是属于class,
通过对象调用静态函数要注意,之前通过对象调用一般函数时,对象的地址会被放进来变成this,现在由于是静态函数,所以不会有this放入参数中,
在private 里放一个静态的a,所以没有创建A对象的时候,这时已经有一个a在了,并且不想让外界来创建a,就把两个构造函数都放在private里,所以没有任何人能创建a,所以A只有一份自己,外界不能创建,要取得这个唯一的a就需要静态函数getInstance得到,但是此种写法不够完美,因为虽然写好了a,但是外界如果都没有需要用到,这个静态的a仍然存在,就有些浪费,
把静态的自己放在函数里,好处是如果没有任何人使用这个单例,这个单例就不存在,一旦有人用了一次,这个单例才出现,并且只有永远这一份,
template,template和typename都关键字,这一行告诉编译器目前T还没有绑定,还不知道T是什么,本来是一个class,现在就变成了一个class template类模板
template里面是class,之前类模板里面关键字是typename,其实两个关键字是相通的,都一样
引数就是实参,类模板使用的时候必须明确指出里面的type要绑定为什么,而function template不必明确指出来,因为编译器会做实参推导,就可以知道r1,r2都是stone,所以就会跑到右边,把所有的T替换为stone,stone比大小编译器不知道怎么比较,操作符重载,编译器发现小于运算符是作用在b上的,而b是一种T,T现在是stone,所以编译器就去找stone有没有定义小于这个函数,
不管什么东西比大小都是小于号,至于这个符号作用在stone或是其他东西身上,该怎么去比大小,设计这个比大小的责任不在此,在于设计stone或者其他class的时候定义,
在c++标准库里,算法algorithm全部都是function template函数模板,
东西被包装在一个单元namespace里,std就是标准库所有的东西都被包在std里,并不一定要写很长,在不同的文件里写一块std,最后会被结合在一起,所以可以分段写,使用标准库里的东西,第一种做法最简单:using directive,directive是命令。意思就是现在要使用std这个命名空间,using namespace std;等同于把包装全部打开,之后再里面不用写全名了,本来要在里面写std::cin表示std里的cin,
第二种是一行一行的打开,因为标准库里可能有很多东西,一次打开可能会造成后面混乱,using declaration表示using 的声明,是一条一条的声明的,因为最开始写了using std::cout,所以下面开始使用cout就不必写全名了, 全名就是加上namespace,但是cin没写,所以后面用到cin要写全名,
第三种就是写全名,
11.组合与继承
左边拥有右边,所以左边叫container,右边叫component组件,从内存的角度是右边的绿色,左边拥有右边,所以左边大一些,
构造函数的初值列,红色表示外部的构造函数调用里面的构造函数,执行完后才执行后面外部自己的事情,红色部分是编译器帮助加上的,
如果有一个外部的,就有内部的,二者的生命是一起出现的,
因为有指针,所以注意big three 拷贝构造,拷贝赋值,析构函数,
白色空心的菱形表示指针,左边仍有一个右边,只是有一个指针指向它,可以在任何一个时间点调用它做事情,把任务委托给它,
有by reference 没有by pointer,即使是用指针传,
委托是左边可能先创建出来,里面有指向右边的指针,需要右边的时候再创建出来,不同步
字符串本身怎么设计不在左边写出来,而是在右边设计出来,左边只是对外的接口,至于真正的实现都是在右边做,当左边需要动作的时候都调用右边类的函数来服务,pointer to implementation,有一根指针指向所有为自己实现所有功能的类,另外一个名字是左边是handle,右边是body,左边的指针未来也可以指向不同的实现类,外界看是看左边,此手法也叫做编译防火墙,左边永远不用再编译,要编译的是右边,
子类向父类画一个空心的三角, 有三种继承方式,c++有public继承 ,私有private继承,和protect继承,使用public继承就是在传达一种逻辑意义is a是一种,如人类是一种哺乳类,
derived class派生类,base class 父类,从内存看子类的对象里面有一个父类的成分part,外界在创建子类对象的时候,子类的构造函数首先调用父类的默认构造函数,然后再执行自己,父类的析构函数必须是virtual,不然不会有下面的析构由外而内,
12.虚函数与多态
虚函数,在任何一个成员函数前面加上virtual,就成为一个虚函数,
在继承的关系内,所有的东西都可以被继承下来,数据可以被继承下来,占用了内存的一部分,函数也可以被继承下来,函数的继承不应该从内存的角度去理解,函数的继承继承的是调用权,子类可以调用父类的函数,是继承了函数,
覆写一定用在虚函数被重新定义,
继承:父类对于其成员函数有三种选择,继承最重要的是搭配虚函数,
一个父类,有一些函数不能先写出来,要让子类去写,就必须设置为virtual function,
空函数与纯虚函数意义不同,纯虚函数是子类一定要去override,空函数是子类不一定要覆写,
在子类里写父类所完成不了的事情,serialize,将此函数写全,虚函数经典用法:通过子类对象调用父类的函数,下面灰色的是调用的父类函数的全名,onfileopen函数里做了一些固定的动作,把其中的一个关键部分延缓到子类去决定,在父类斜体写不出来的函数表示抽象,把这个函数的做法叫做template method,是23个设计模式之一,此处template不是c++的模板,是套用了概念,method是java的术语,java语言中函数就是method,所以template method就是c++中某一个函数即onfileopen,在应用程序的框架里面就可以大量的应用template method的手法,即把固定的函数先写好,留下的无法决定的函数让其成为一个虚函数,让子类去定义它,
myDoc.OnFileOpen调用动作全名是CDoument::OnFileOpen,而谁调用的谁就变成this pointer,所以myDoc就是调用者,它的地址就被放进来变成隐藏的参数this,而OnFileOpen函数在调用函数到serialize的时候,编译器会看成this->serialize,通过this来调用,this是myDoc,所以就到右边,
从内存角度画,子类里面有父类的成分part,又有component在里面,只需要知道这两个存在于子类的内部即可,
子类里面有父类的一个成分part,而父类对象里面又有一个component的成分在里面,画图为子类最大,里面有父类的成分,父类里面又有component的成分,所以当创建一个子类的时候,构造函数的次序是最里面的最先被调用,先component,再base,再derived,析构的时候刚好相反,
这三种关系里面功能最强大的是delegation+inheritance,subject用来存数据,observer用来观察数据,observer可以被继承,子类都是一种observer,所以子类创建出来都可以放到容器vector里,即几种不同的observer在看数据,
这样的一个结构或者这样的结构所要解决的问题,这是一个解法,就把这个解法叫做composite,是23个设计模式里的一个,右边add不能设计为纯虚函数,表示没有定义,子类就一定要去定义,而子类primitive定义无法做加的动作,所以add只能写为空函数,没有做事情,左边primitive可以继承下来不做事情,
要去创建未来的class对象,原型prototype说后面发生的子类自己创建,自己创建出来的东西可以被框架看到,就可以拿到当作蓝本复制很多份,未来的class名称还没有,
安排一个静态的对象:有下划线, 写代码的时候先写typename,再写objectname,画图的时候相反,先写objectname,再写typename,每一个子类都写就是自己创造自己,而这个原型必须登记到之前写好的框架让他看的到,应该由上面准备一个空间,下面的把原型放上去,把静态的自己创建出来会调用几个构造函数,负的代表private,正的代表public,正的默认可以不带正号如clone函数,#代表protected,故意的把构造函数设置为私有的,自己调用不是外界调用构造函数,所以LandSatImage构造函数会调用起来,借用私有的构造函数调用addPrototype把自己挂上去,addPrototype是父类型,会把得到的指针放到上面自己的容器数组里,所有的子类都这样做,子类还要准备一个函数clone即拷贝,就是new自己,所以刚刚的框架端可以通过原型这个对象调用clone函数,就做出一个副本,如果没有原型,就无法通过对象调用clone函数,
每一个子类自己有自己的个体,并且有一个构造函数把自己这个个体挂到上面,然后每一个子类有clone,让框架端通过挂上去的自己原型来调用clone制造副本,
静态的自己被创建出来的时候会调用构造函数,