C_old

内存

class A{
    static int a , b;  '注意,static是不占“这个类A的内存“的!!!
}

class A{
private:
    int a:   ' 类A所占的内存,是要包含private类型变量的!!  即是否是private类型无关
}

class A : private/public B{
    ' 类A所占的内存,要包含sizeof(B)所占的内存!!!  是否是private继承,不影响
}   ' 即一个类的内存 =  (该类 + 父类 + 爷类 + 爷爷类) 的 
    '		"非static"  "private/public" 的变量 之和

虚函数

父子类

class FA{ ... }
class SON{ ... }	'没有继承关系!!
FA a;   SON b;
FA c = (FA)b;  ' 错! FA和SON这2个类,没有任何关系,不可以强制转换
SON d = (SON)a;  
'C语言的强转(T1 -> T2): 
' 1,T1和T2这两个类型,必须有关系的!!   比如上面的FA和Son,这两个类 就完全没有任何关系
' 2,比如有T1 大于 T2, 所谓“大于”是指:T1必须要比T2 “丰富”“大”
'    虽然T1和T2有关系,但如果T1很小(T1只有ABC属性) 而T2很大(T2有ABCDE属性)
'    所谓“属性”是: 组成该类 所“必需”的东西。  组成T2必须要有DE属性,而你T1就没有DE属性
'  	     这自然是转换不成的!!   即必须是“大的东西“  -> 转成 -> “小的东西”
'    这就是为什么: 子类可以转成父类,但父类不可以转成子类。因为父类东西很少,子类所需东西很多
    
    

class FA{
    void name(){ DE("I'm FA")E; }
};
class SON : public FA{
public:
    void name(){ DE("I'm SON")E; }   '继承的重载
};

' 不管是否有“虚函数”, 下面情况都符合
FA fa;  fa.name();  '假如,FA里没有name函数,就会报错。
SON s;  s.name();  '假如,SON里没有name函数,就会使用FA的name函数
fa = s; '可以,子类很丰富 子类里 一定有父类所需要的东西(即使子类是空,他会继承所有父类的东西)
  	'这个强转,只包含对”数据data“的强转。 即此时fa.data = son里的data(不等于原先fa的data值)
    '强转不包含对“函数”的强转。 即fa.func 一定是fa里定义的!! 不是son里的重载。
s = fa; '报错!!!  子类所需的东西很多, 父类的东西却很少。
    
    
' 不管是否有“虚函数”, 下面情况都符合
FA* fa = new FA;  SON* son = new SON;
son = fa;  '报错!!  fa父指针 不可以转为 子类指针
fa = son;  '可以, fa->data  是son里的data值。
    
    
' 不涉及“虚函数”的情况:
FA* fa = new SON;  ' fa是(FA*)类型, (*fa)即fa所指向的内存/对象 是FA类型!! 
  ' 这一点 很像”强转“,fa的func完全是FA定义的func; 但fa的data 是取决于SON初始化里的data
  ' 其实就可以理解为“强转”, 即将(new SON)的这块内存/对象,强转为 FA类型。
    
    
========== 此时,引入“虚函数”: 将上面的两个name都改为virtual===================
FA* fa = new SON;  ' fa是(FA*)类型, 关键是要讨论: fa所指向的对象!!
  ' 此时fa指向的对象,不再是FA。 fa指向的对象 是SON类型! 通过typeid(*fa)获取
  ' 但比如sizeof(fa) = 8,  sizeof(son) = 32; 
  ' 虽然,fa指向的内存/对象 是SON类型。  但不是真正的SON类型!! 他的sizeof 是和FA一样的!!!
  ' 即(*fa)这个对象/内存, 他的typeid是SON子类类型, 但sizeof = FA类的大小
  ' fa->XX,这个XX 必须是FA类里 有定义的!!  因为fa的sizeof是FA类的大小!!!
  ' 即SON里独有的data和func, fa是访问不到的!!!
  ' fa->data(这个data,一定是父类的!! 即使son有重名的,这个data也是父类的!!
  ' fa->func(他是子类的)   fa->fa::func() (可以调用父类的!!)
// 前提是,SON必须是 :public FA来继承的父类,否则son里的FA数据是private的 //
===即: fa可使用的函数有(自己的[X个] + 子类重写的)				 ===
===   而fa可使用的数据data,只能是他自己的 (和子类没有任何关系)!!! ===
// 还有一点,只要类里有“虚函数”,那么该类和其子类 的sizeof,就会变大 因为有虚表。

/*	多态就是: FA* fa = new SON;  和  FA& fa = s1; (SON s1)
为什么允许这种写法呢??  
因为子类son对象里,确实有“所有父类FA”的东西!!! 自然可以当成是父类对象来使用		*/  
    
FA* fa = new SON;  ' fa是FA*类型;  (*fa)是SON类型; (*fa)的大小是FA的大小
' 只要FA里有虚函数(不管SON是否重写),那么 (*fa)就是SON类型
' 只要FA里没有虚函数,那么(*fa)是FA类型
fa->xx;  ' xx必须是FA里定义的data/func; 但如果我们就是要访问“son里独有的data和func”呢??
	'需求: 通过父类FA*指针,来访问 子类独有的data和func
    '为什么会有这个需求呢? 因为fa所指的内容/内存 确实是SON类型!!!
    '只不过那块内存,有一些东西被“禁用”了(SON独有的被禁用),所以(*fa)的大小是FA的大小。
    '换句话说, 有一些东西 被你浪费掉了,空占内存!!!  你是可以使用它的
    
SON* son = (SON*)fa; '必须加(SON*)强转符号。 
son->XX; '可以访问到SOn的所有东西。  我们并没有新new内存!!!
	'我们做的:只是“指针的强转”!!  此时fa和son指向的内存地址 是完全相同的!!!
    '仅仅是一个“指针类型的强转”而已!!!  但本质已经发生的变化。
    '原来(*fa)虽然是SON类型,但他的大小sizeof是(FA的大小)
    '但现在(*son)虽然也是SON类型,但他的大小sizeof是(SON的大小)!!! 变大了非常多!!!
    '也说明,原来fa指向的内存,虽然他的sizeof是FA的大小,但其实是sizeof的问题...
    '他的指向的内存 就是SON的大小,因为你最开始 就是"new SON",肯定是SON大小的内存呀!!!
	'只不过,用FA* fa = new SON指向时, FA*会做一些封装,把SON独有的内存 给”禁用“掉!!
    '此时,我们简简单单的“指针类型转换“:(SON*)fa,其实是把 原来”禁用“掉的内存 重新打开了。
    
同理,FA* fa = new SON; 和 “SON s;   FA& fa = s;” 这两种写法,本质一样!!!
SON s; 
FA& fa = s; // 如果FA有虚函数,则fa是SON类型 fa.XX是子类的东西。 
			// 否则FA没有虚函数, 则fa是FA类型。

Dynamic_cast

FA* fa = new SON1;	' fa->不可以访问SON1独有的东西
SON1* s1 = (SON1 *)fa;	' s1->可以访问SON1和FA的东西
SON2* s2 = (SON2 *)fa;  '这不会报语法错误,但他的逻辑合理吗?
' s2->XX 和 TYPE(*s2),你可以发现: 这s2 还是SON1类型!!! 
' 因为,你根本没有新开内存/对象,你只是做了指针强转。 
' 而只有1块内存 他是SON1类型的; 他肯定是无法转成: SON2类型 (SON1和SON2,类完全不同)

// 为什么会出现这种情况呢? 因为(SON2 *)这种强转 是static静态的
// 他只能在编译期 判断合法性。 如果是new SON1,则(SON1 *)正确。
// 如果是new SON2,则(SON2 *)正确。  而究竟new是什么内存对象,这是“运行期”才能知道的!!!
// 所以, 我们需要在“运行期” 动态的去判断 合理性。
// 引入“dynamic_cast",他就是:===帮助开发者,做安全检查===
    
FA* fa = new SON1;
SON1* s1 = dynamic_cast<SON1 *>(fa); // s1 != nullptr,说明 fa指向的内存对象 是SON1类型
SON2* s2 = dynamic_cast<SON2 *>(fa); // s2 == nullptr
' 其实就是在判断: (*fa)的类型 只有是SON1时: 将fa -> SON1*类型,才是合理的。
    
SON1 s1;
FA& fa = s1;	'引用的本质是指针,所以(这种写法,和FA* fa = new SON1,本质一样!!)
try{    SON1& s1 = dynamic_cast<SON1 &>(fa);  // 正常
    	SON2& s2 = dynamic_cast<SON2 &>(fa);  // 报错!!
}catch(exception){}	 
    
' 注意,dynamic_cast必须应用于:虚函数和继承的情况!!! 
' 即你的fa类,必须有“虚函数“!!!  如果FA没有虚函数,dynamic_cast<XX>(fa); 编译就会报错
' dynamic_cast就是处理:FA* f = new SON 和 FA& fa = s1; 的情况!!
' 其实就是多态。    

静态类型、动态类型

FA* fa1 = new SON(123);   'FA里面有虚函数
FA& fa2 = son1; 
/*	fa1的“静态类型”是:FA*;     fa2的“静态类型”是:FA&
所谓“静态类型”,是在变量声明时 编译期间,就已经“得知”的!!!!

“动态类型“是: 他只针对于”指针、引用“来说的, 即该指针/引用 所指向的内存 的类型!!!

而对于非指针引用 或 不涉及虚函数: 他自然没有动态类型这一说,或者说他的静态动态都一样。

但这里, fa1/fa2的动态类型(含义是:fa1/fa2所指向的内存 的类型)是: SON类型。

只有涉及到虚函数: 才会有静态类型 和 动态类型,不一致的问题。		*/

基础

Son* son = new FA();  '错误, 子类指针 不可以指向 父类对象
Fa* fa = new Son();  '正确, 父类指针 可以指向 子类对象
fa->子类Son的成员;  '错误!! 虽然fa指向子类对象,但毕竟fa是FA类型,他不能访问子类的东西
			      '那么,fa不能访问子类的东西,为什么还允许fa 指向 子类对象呢???
    
class FA{
    void func(){ C("1111"); }
}
class Son1 : public FA{
    void func(){ C("2222"); }
}
class Son2 : public FA{
    void func(){ C("3333"); }
}
'如果我们要: 可能调用Son1的func函数(2222),也可能调用Son2的func函数(3333); 
' 你可能会这样写:
Son1 s1;	s1.func();
Son2 s2;	s2.func();    '这种方式,你定义了“2”个变量。

    
Fa* fa = new Son1;  '此时, fa->func() 他调用的 还是(1111),而不是(2222)
    				'因为,fa是FA类型,他自然只能调用自己FA的函数。
' 引入:虚函数
    
class FA{
    int a;
    virtual void func(){ C("1111"); } '虚函数的写法
} '===注意,虚函数 必须要“实现”出来!!! 这一点非常重要!!!! ===
  '如果:virtual void func() final {...} 虚函数加上了final,则不允许子类重写!!
class Son1 : public FA{
    int b; '规范问题: 子类重写父类的虚函数,最好前面写上virtual,这是规范 
    virtual void func() override{ C("2222"); }  
} '后面的override 同样是规范!! 因为“虚函数”的重写  必须是“同返回值,同参数列表”!!
  '你void func(int)就不是对应的父类的虚函数!!! 
  'override就是完全配合虚函数使用的,假如你子类 不是同返回值 同参数,override就会报错
  '即override修饰的函数,一定是个子类的函数,且该函数在父类里 一定是个虚函数。
class Son2 : public FA{
    virtual void func() override{ C("3333"); }
} 
Fa* fa = new Son1;
'1,fa->a这肯定是正确的 因为fa是FA类型,用人家自己的成员变量 肯定是可以的。
'2,fa->func()这是个难点!!!  fa如果要调用(111)的func,肯定是可以的,毕竟就是人家FA的东西
' 但这里,fa->func()是调用的(2222)   此时是“父类指针,调用 虚函数”!!!
' 那么,他是“动态绑定”,即运行到fa->func()这段代码行时,才知道 要调用的是 哪个son的func
' 可能是son1的,也可能是son2的。 即“运行”时 根据new的哪个子类 选择调用哪个函数
    
/*	虚函数:
1, 他是“重写”,即必须完全的“同返回值”“同参数列表”
	父类是: virtual void func(int){}	
	则子类的虚函数重写,必须是: virtual void func(int);类型  (前面的virtual可以略)
	  如果是void func(int,int),他就不是虚函数的重写, 因此要规范加上override 表明是重写
2, 父类的虚函数,必须要“实现”出来,不能只是声明出!!!   这是虚函数很重要的性质!!!z
	因为父类的虚函数,子类可以不重写!!! (虚函数,子类可以重写 也可以不重写!)
	如果是:FA* fa = new Son1;   如果子类没有重写,就会调用父类的虚函数
		假如son1没有重写,就会调用fa的虚函数 (此时,不允许fa的虚函数是没有实现的)
	这与普通函数不同,普通函数 即使不实现,只要你代码里没有使用该函数 也可以的
	因为在编译期,会检查 你是否使用了该函数。
	  但,这里是“动态绑定”,编译期间 是不知道 你究竟要调用哪个函数!!!
3, 虚函数,一定是配合“父类指针”使用的!!!
	如果是用“普通对象”来调用虚函数,则虚函数的作用就体现不出了
	因为对象是obj.的方式,他在编译期就知道 不涉及动态绑定,而new 在运行时才知道。
	 === 这里也拓展, new 即指针的ptr->xxx 方式,他是运行时才知道的。 ===		*/

多态

' 多态,就是针对虚函数的。  如果没有虚函数,有就没有多态这个概念了
'===记住“多态”的核心: FA* fa = new SON1/2/3; 这就是他的核心!!!!===
'  1,多态体现在“具有继承性质”的父类和子类之间,子类重新定义父类的虚函数
'  2,通过父类的指针,只有到了运行时(new的那段代码行),才能找到动态绑定在父类指针上的对象
'	  这个对象,可能是父类 也可能是子类,从而调用 父类/子类 的虚函数

纯虚函数

class FA{
    virtual void func() = 0;  '这个是“纯虚函数”,只能声明 不能实现!!!
} '一旦类中有了纯虚函数, FA a / FA* a=new FA; 都是错误的!!! 
  '带有纯虚函数的类 叫做:“抽象类”,他不能有实例化对象。
  '抽象类里, 也可以有“已经实现了的函数”  FA* a = new SON; 这是正确的!!
  '此时的a,他虽然是FA*类型,但他是指向的SON类型的, 
  '抽象类,用于做父类,所有继承的子类 都必须实现这些纯虚函数。
  '   “纯虚函数”他也是“虚函数”,因此 也是符合虚函数的性质的(即支持多态)。

析构函数

' 我们知道,虚函数的精髓 就是“ FA* f = new SON;” 这么一句话 (调用父类+子类的构造)
'   那么,new的内存 你肯定需要自己去释放; 
delete f;  '但这句话, 只会执行“FA的析构”(因为f是FA*类型)
    '即,你执行了2个构造 却只执行了1个析构!!   内存,一定没有清除干净!!!
    ' 比如有X个构造, 你就要执行X个析构。 这才是清除干净了!!
    ' 即,你此时 需要执行FA的析构 + SON的析构,才是正确的。  如何让他自动执行SON的析构呢??

' 涉及到“虚函数”, 就要把父类FA的析构函数 也写成virtual虚函数!!!
'我们思考: 将父类的~FA析构函数 写成虚函数,他会产生什么影响呢?
'  1,父类的析构是虚函数,所有他的子类的析构 也“自动的”变成了虚函数!!  
'	 (这类似于: 父类的void func(int)是虚函数,那么子类的void func(int)也就变成了虚函数)
'	 (当前,规范是: 最好子类的虚函数 也写上virtual。  虽然不写,也是完全一样的)
'  2,此时子类的~SON也自动的变成了“虚函数”。
'  	 FA* f = new SON;  虽然f是FA类型,但他可以f->func 从而调用“子类”实现的func函数
'	 同理这里也是一样,虽然f是FA类型,但因为~SON是虚函数 所以是可以调用到他的。

=== 即,只要涉及到继承, 就把父类FA的析构函数 设置为:虚函数!! '规定!!' 
===   virtual ~FA(){}   这是规定!!! 必须这样写,否则就无法执行“子类的析构” ===
=== 这样, FA* f = new SON;   但delete f时,他会调用FA和SON的析构 ===

继承

概念

class Son : Fa{
 	Son(){ DE(1); }      '可以看到,你Son的构造函数 是空的,就没有涉及到Fa的构造    
    Son(int){ DE(2); }   ' 那么,为什么 Fa的构造函数 总是先与Son一步呢???
} 
Son s(123);  '为什么Fa的构造,会先于Son的DE(2)呢? 
/* 其实,“子类的所有构造函数” 如果没有写 “父类Fa的构造”
比如,Son(){..} 他就等价于: Son() : Fa() {...},“必须放到:初始化列表里“!!!
比如 Son(){ Fa(123); } 他其实是形如: Son() : Fa() { Fa(123); }
即对于所有Son的构造函数,他一定会在进入 “Son构造函数的{...}”之前,执行Fa的构造!!!
要么你不显式规定,要调用哪个父类构造,即: Son(xx){ ... } 
	那么,他一定会在进入{...}之前,调用Fa的无参构造!! 
	=== 可能你的{...}又执行了Fa的构造,即Fa的构造函数 会多次调用 ===
	=== 但在{..}里写FA的构造,没有任何用!!! 即临时创建了FA的内存,然后又立刻释放了!!===
要么你显式的规定,即: Son(xx) : Fa(xx) { ... }
	这样的话,也是在进入Son的{...}之前,调用了Fa(xx)的构造函数,不会自动调用Fa()无参构造了 */

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

/*	我们思考: class Son : Fa 这句话,到底底层干了什么???
猜测: 单纯这简单的 继承“:Fa"一句话,他会执行: (比如是:Son -> Fa -> GFa)
	1, 递归到GFa (先创建GFa的内存 , 然后调用GFa的构造函数)
	2, 回溯到Fa  ( 先创建Fa的内存 , 然后调用Fa的构造函数)
	3, 回溯到Son (先创建Son的内存 , 然后调用Son的构造函数)
即你简单的一句: Son s; 他不仅只有Son的内存对象,还有Son的所有父类的内存对象
只不过,你看不到“Son的所有父类的内存对象” 只是你使用不到它,但它是真实存在的!!!
也就是,千万不要以为 你Son s的消耗 只是Son里的变量,(因为sizeof(Son)是非常大的,包含父类)

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

再思考,继承要干什么呢??  你Son,不就是想使用“父类Fa、GFa”的data和func嘛~~~   */
class Son : Fa{
'在这个Son类里面,通过Fa::data 或 FA::func()的方式,调用Fa/GFa的数据和函数!!!
'也就是,虽然此时是有Fa和GFa的对象obj的,但你得不到这个obj,无法通过obj.data来调用。
' 只能通过: “ FA:: ” 的方式,来使用FA和GFa的数据和函数。
}
Son s;  ' s.data/func的方式,可以得到:Son/Fa/GFa的数据和函数
    
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    
class Son : Fa{
    Son(Fa* f = 0);  '构造函数
} 
SOn::Son(Fa* f) : Fa(f){ ... }
'看这样的一份代码,到底在做什么....
/*	这个Son(..)是构造函数,他的参数 是一个父类型的指针,当然无需关注他是什么类型
只需知道: Fa类有一个构造函数 参数也是这个参数!!(即: Fa(Fa* f){})
然后,当我们调用Son的构造时,先调用了Fa的构造。
其实你这样说 非常不严谨。 比如Son s; 他是(创Fa内存 调用Fa无参构造 -> 创Son内存 调Son无参构造 */
    
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    
' 父类对象,不仅可以单独存在  还可以作为子类对象的一部分而存在;   这是什么意思呢??
FA fa;  'fa是个父类对象,这叫做单独存在。
    
Son son1;  'son1是个子类对象,但他里面 其实是有个“父类对象”的!!!!
' 为什么会这么说呢?  因为(FA)son1 这种类型转换是合法的。 说明son1里 肯定是有个FA对象
'  SON s;    FA fa(s); 他会调用: FA(const FA& f)的拷贝构造!!
'	  也说明: s他自动转换为了FA类型。
=== 即在必要的时候, 子类SON 是会自动的转换为 父类FA类型的!!! ===

访问权限

' protected: 在继承(涉及父类子类)时,才会用到他
'			  表示该变量,只能被当前类/当前类的子类,所访问到。
' private: 只有当前类能访问到,他的子类 不能访问到!!!

class FA{
    int a , b , c; 'a是public,  b是protected,  c是private
}
Fa fa;  '外界只能fa.a,不能访问b和c

class Son : _?_ FA {...}
Son son;
/*	其中的_?_,表示 以哪种“权限”来继承FA父类。
public FA:  Son类里可以访问到a和b, 且a在Son中是public类型,b在Son中是protected类型!!
			外界只能son.a,其他son.b和c都是错误的
protected FA:   Son类里可以访问到a和b.  且a和b在Son里 都是protected类型!!!
			虽然a在FA是public类型,但Son是通过protected方式继承的FA,a到Son里 变成了protected
				即Son的子类,是可以访问到祖先的a和b的
			外界通过son.  谁也访问不了son.abc都是错的
private FA:	   Son类里可以访问a和b.   且a和b在Son里 都是Private类型!!!
			即Son类里是可以访问ab,但Son的子类 访问不了祖先的ab。
			外界通过son.  谁也访问不了 son.abc都是错的		*/

函数遮盖

class FA{
	void func(){...}; 		'这3个重载函数,都是public的
	int func(int){...};
	LL func(int,int){...};
}
class Son : public FA{
	' 在子类Son中,没有关于FA的func的重写
	' 则无论是在Son这里类里,还是是在外界son.XX,都可以直接调用FA的3个重载函数!!!
}
class Son : public FA{
    Son(){                
        ' 但如果,你在Son中 重写了func函数(此时无关返回值和参数,只要重名即可)
        ' 则此时,则无论是在Son这里类里,还是是在外界son.XX, 只能访问 int func(double)
        ' 父类的那3个函数,就完全消失了!!!
		' 你现在在ST()构造里,只能访问string func(string)这1个函数!!!
		FA::func();  '如果你非要访问FA里的func,就要通过FA::的方式 来访问。
    }
    string func(string){...}     '重写了func,导致FA的3个func 都失效。
}
Son son;  'son.func只能访问 Son里定义的这func。
		 '通过son.FA::func()方式,你可以访问到 FA里的3个函数!!
    
' 还有一种最暴力的方式,来解决“函数遮盖”的问题: using
class Son : public FA{
    using FA::func;   '使得父类的这3个func函数,在子类Son中 再次定义出现(重载)!!
	Son(){
        '此时,直接func() 他可以调用4个不同的函数(3个FA,1个Son自己的)
        '同时,外界son.func() 也有4种选择。
    }
    string func(string){...}
}

可缺省参数

class ST{
	ST(int a, int b = 123);
}
ST::ST(int a, int b){ // 这个b,不可以有默认值!!
}

' 这上下两种写法,都是可以的。    声明和实现,只能有1个含有默认值

class ST{
    ST(int a, int b);
}
ST::ST(int a, int b = 123){ }

初始化列表

class ST{
    int a, b;
	ST(int a, int b = 0);  '注意这里,初始化列表 不能写在.h的函数声明里!!!
}	'初始化列表,必须写在.cpp的函数实现里!!
    
ST::ST(int a, int b) : a(a), b(b){}

Final

'如果一个类ST,他不想当“父类“  即不允许有子类继承他!!! 
class ST final{}  
class XX : ST{}   '错误
    
    
class ST final : FA{}
class XX : ST{}  '错误

Cast

static_cast

' static_cast静态转换,就当成最普通正常的转换即可,和C的括号()方式 差不多
' 是“编译”期间进行的,说明 他是不会进行正确/安全性的检查,你需要使用仔细
    
int a = (int)2.999;		' C的风格
int a = static_cast<int>(2.999); 	' C++的风格
    
static_cast<父类>(子类);  '不能颠倒,  父类转为子类 这是编译期间做不了的!!

dynamic_cast

' 在“运行”期间,进行类型检查   比如,将父类转换成子类!!

内存

/*	C++将内存分为了5个区域:
1,栈: “函数”内的局部变量
2,堆: 使用new申请,如果没有delete 程序结束后,OS会进行回收
3,全局/静态存储区:  全局变量,静态变量
4,常量存储区:  常量,不允许修改。 比如用""包含起的字符串 就是常量
5,程序代码区:  存你的程序代码

栈: 空间有限(这是系统所规定的),分配速度很快,你无法控制他的分配和释放。
堆: 你可以自己决定所要分配的内存大小,不要超过实际的物理内存大小即可。
	分配速度要慢,可以用new来分配,很灵活。			*/

malloc/free

' void* malloc(int bytes);     void free(void*);

int* p = NULL;
p = (int*)malloc(10 * sizeof(int));  '分配40字节
if(p != NULL){
    ...
    free(p);
}

new/delete

' new不用去关注,你要分配多大(bytes)的字节,这很麻烦。 
' 你如果只要1个,	就直接:  T* p = new T; 		 ->  delete p;
' 如果你要100个 即一个数组:  T* p = new T[100];	 ->  delete[] p;  注意这种写法
// 注意,new时是数组[],就要对应delete[]这种方式。 
// delete时,不用写具体的数组元素个数(100) 写了也会被忽略掉 
// ===系统有办法知道,这个数组的大小===
    
int* p = new int;  		' *p 为任意数
int* p = new int(123);  ' *p 设置初始值为123
    

Trick

i++

i = 123;
i ++, a = i; 	' 注意,a == 124!!!!!
// (后置i++)他是遇到“逗号,”就会让(i+=1)!!! 
    
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    
(i ++):  
    1,他会申请一个变量:int temp_i = i; 然后立刻返回!!!
        并且,这个temp_i会被标记为"右值",<不用管什么是左/右值,他只是个标记>
        	<什么叫标记呢? 给了你一个X变量(即T X = ??)
        	<如果告诉你X是个右值,就说明: X这个变量,"马上"就会被释放(临时变量)>
    2,立刻让i += 1;   (即,主要出了这个 {i ++}: 1,他会返回一个对象   2,i += 1)

' 比如: a = (i ++);     令(i ++)中,其内部产生的临时变量是temp_i (其值 == i)
' 首先,执行a = temp_i;  (时刻注意,temp_i也是个变量!!(不用管他是左值还是右值)
'      要么: int a = temp_i;    (temp_i是个int型的变量,自然可以用int来接受)
'	   要么: int&& a = temp_i;   (temp_i不仅是个变量,还是个"右值“变量)
'        		所以, 可以用“右值引用” 来 引用到这个 ”右值变量“上。
' 所谓”右值引用”,也不要把他想的太复杂。
'   首先1,他肯定是“引用”!!!  即int&& p = a;   (保证a是个右值变量)
'		 即修改(p = X)或者 修改(a = X); 都会使得 -> (p == a == X)
'		 即,这就是普通引用的用法。  ==没啥区别!!==
'  只不过,int& p = a(保证a是个左值)。 int&& p = a(保证a是个右值) 就这点差别
        
' 总之,执行完(a = temp_i)之后,再执行: i += 1;
==== 即,一共是做了“2”个事情!!====

++i

int i = 5;
int& p = (++ i);  '注意,(++i)返回的对象,就是(i这个对象)!!!
    ' 即p = (++ i): 第一步, 执行i += 1操作,并且(++ i)就是返回i这个内存。
    '				第二步, 执行p = i; 这个操作!!
	'  此时, p和i 是指向的同一块内存,因为p引用到了i

int&& p = std::move( (++ i) );
'  (++ i): 他会返回i这个内存(i = 6);
'	move(左值i): 他并不会返回一个temp_i,他就是返回的 i这个内存本身!!!
'	move,只是做了一个“强转”!!! 即把i,在这个语句内,强转为“右值”
'	即move的作用是: 告诉编译器,这个左值i 我以后不会再使用他,他即将被释放掉。
===此时, (&p) == (&i);   这两个p和i,指向同一个内存!! 
===不用管,p是左值/右值引用, '左值/右值'只是在类型匹配,绑定时,会使用到
=== "只要已经绑定成功了(现在p已经引用到i上了)",那么p和i,就是同一块内存
=== 此时, 修改(p = x)或者(i = x),都会使得: p == i == x

临时对象

void func(ST t){ ... }
' 如果是func( obj ); t是通过调用ST的拷贝构造函数(obj)的方式,创建出内存
' 然后,func结束后 t的析构执行。
' 如果是func( 123 ); 且ST有ST(int){}的构造函数。 “隐式类型转换”
'   t会调用ST(int){}的构造函数创建内存, 然后调用析构

sizeof

int array[ sizeof(int) ] 在C语言里,对吗?
	' sizeof()是编译期间的运算符,在编译期间就确定了

C和C++里的struct区别

				权限(私有/公用/保护) 		类里定义函数
C的struct:  			不能					  不能  'C里可以有:函数指针
C++的struct:			能					   能

判断一段程序是C/C++

' 判断一段程序,是由C编译器、C++编译的呢?
' 每种类型的编译器,在进行编译时,会“内置一些宏定义”
#ifdef __cplusplus
    DE("这是C++编译器")E;
#else
    DE("这是C编译器")E;
#endif

C++的class和struct区别

' 1,成员:   class的成员 默认是private的	  	struct是public
' 2,继承:	  class的继承 默认是private的		struct是public

static变量作用域

' 1.cpp里的static int A; 他的作用域“仅限于1.cpp”里!!! 即本文件内

' 我们知道: 1.cpp里有int A,  那么在2.cpp 就不可以有int A
' 你只能在2.cpp里,通过extern int A; 来“共用” 1.cpp里的int A
'  但,如果1.cpp里是static int A;  则这个A,就作用在1.cpp里。
'    你不能在2.cpp里 通过"extern"来引用1.cpp里的A。
'	 换句话说, 你在2.cpp里  可以定义int A;   这两者是不冲突的!!!

构造函数初始化列表

class ST{
    int a;
    STT b;
    ST() " : " { ... }
} '在构造函数的“ : ”时,就会给所有成员变量“分配内存”!!!
  '即b的无参构造会被调用!! 即还没有进入{...}之前,a和b就已经有内存了!!!
  ' 因为{...}就是干“赋值”的事情的!!! 如果连“内存”都没有,赋值肯定是不可行的。
  ' 因此,你也可以想到在{...}之前,ST的所有成员变量 就已经分配了内存 真实存在了!!!
  '即使你不写":" , 他也自动会有: ST() : b() { ... }   因为b要创建内存的呀~
' 但假如,你要给b自定义初始化,比如调用他的STT(int)的构造函数。
' 如果你写成: ST(){ b = STT(123); }   一共调用了“2次”STT的构造函数!!!
ST() : b(123) { ... }  '这样写的话: 他用调用“1次“STT构造函数!!!

类构造、拷贝构造 (VS)

' 构造函数: 他是用来“创建内存”的!!! 他的底层,非常复杂!!! 
'   不要以为,你单单通过调试,就可以看清楚“构造函数”的工作
'	即只要涉及到ST a...;这种形式, 有ST开头,说明这是在“定义”,即“需要创建内存。
'	  则一定会调用“构造函数”
'构造函数(创内存),他分为两种: 1,普通构造函数	2,拷贝构造函数

ST() : ... { ... }
ST(int a) : ... { ... }	'普通构造函数,可以写多个!!! 
ST(const ST& t , int a=1, int b=2) : ... { ... }
'拷贝构造,和普通构造,非常相像。 唯一区别是:他的第一个参数是“const ST&" 这是死的!!!
' 后面也可以继续有参数,但必须要有“默认值”。  

ST a(); '这是错误的!!!  他不会调用“无参构造”
ST a;   '这才会调用“无参构造”!!!
ST a(123)  ' 他会调用“有参构造”
    
'你只要是:ST a... 这就是“要么构造函数,要么拷贝构造”!!不会调用“赋值运算符”!!
ST a = b;   		'这两种方式,都绝对不会调用”赋值运算符“,因为连ST a这个对象还没有创建出!
ST a = ST(123);		'即便你有=等于号,但要么调用构造函数,要么是拷贝构造函数。 只有这2种可能
    
ST a = ST();  '不要这样写!!! 虽然也是调用“无参构造”,但他还会调用”拷贝构造“
	  '虽然你调试,他只会调用“无参”。 但如果你把“拷贝构造”设置为explicit,这句话是报错的
      '应该是: ST()调用无参构造。 然后这个tmp对象,调用ST a(tmp)拷贝构造。
      '反正你要知道:现在是在创建a(a连内存都没有),而构造函数(普通构造、拷贝构造)就是分配内存的
      ' 底层确实很乱,难以分析。  但绝不会调用“赋值运算符”,因为a连内存都没有。
      ' 因为很复杂,所以一定要牢记: 
    		'    		ST a = ST(..)这种写法: 既会调用构造,也会调用拷贝构造!!!

ST b(a);	'这是调用“拷贝构造”的最正规的写法!!!  
ST b{a};    ' 即便拷贝构造有explicit, 这两种写法,都是会调用“拷贝构造”的
    
ST b = a;	'这个"=号"就很影响,你会以为和“赋值运算符”有关... 和它没有任何关系!!
    		'因为,这里有ST开头,说明是在“定义变量,创建内存”,一定只和“构造函数”有关!!
    		'他会把ST b=a 变成: ST b(a); 这种形式!!!   即只会调用“拷贝构造"
    		'而这种ST b(a)形式,就是最正规的拷贝构造函数的调用方式。
    		' 这个(ST b=a)变成(ST b(a)),就是“隐式拷贝构造“ 你加上explicit就报错了。
ST b = ST(123);	 '这里和上面的(ST b = ST()一个问题。)
    		'你这样分析: ST(123)肯定是调用了“有参构造” 从而创建了tmp对象。
    		' 于是:ST b = tmp  再变为: ST b(tmp)  调用了“拷贝构造“
			' 虽然这个“拷贝构造”的调用,你调试不出。 但你改为explicit,就错了。

'explicit一般用于“单参数的普通构造函数”, 对于”拷贝构造函数“ 就不要加explicit了。
    
    
当然,”构造函数“肯定是和“创建内存”有相当密切的联系,所以你会以为 他俩是同一个东西。
你可以这样认为: 在“创建该类内存”后,会立刻的“调用构造函数“
  因为,我们思考: 构造函数 只有在“定义变量”时,才可以调用!!!
case1:  ST s;  '先创建内存,然后调用s的构造函数
cast2:  Son s(123);   其中,Son(int a) : Fa(a){ ... } // 注意,这个:Fa(a)是初始化列表!!!
		1,创建Fa内存 调用Fa的”有参“构造Fa(a)。   2,创建Son内存 调用Son的”有参“构造 */

SON s; '只要执行到这一行: 1,先执行FA的构造	 2,再执行SON的构造
	   '在程序执行到“return 0"时, 会自动销毁s这个局部变量!!
       ' 1, 先执行~SON的析构函数;   2, 然后执行~FA的析构函数(SON的父类)
	//  即构造是“从内到外”的,  析构是“从外到内”的
    //  这两者必须对应!!!  有X个构造,就应该有X个析构!!! (否则,一定是有问题的)

FA* f = new FA;  ' 构造函数,他是取决于“new FA"这句话的,和前面的FA*类型无关
    ' new FA: 只会调用FA的构造函数;  
    ' new SON:  1,调用FA父类的构造		2,调用SON的构造
    
FA* f = new SON;   
	'即使程序执行了“return 0” 也不会执行析构,即系统不会自动给你释放内存!!
	' 因为“new”出来的内存,是完全由“用户”所决定的!!! 你要负责他的申请和释放!!!
	' 所以,你造成了“内存泄漏”,
delete f;  ' 你必须执行: delete f(他才会执行析构函数!!)
    ' 因为f是FA类型的指针(虽然他指向SON内存), 所以只会执行“FA的析构函数!!!”
    ' 可以发现,这里有个大问题!!!  你new SON,即肯定是申请了一块SON的内存。
	' 而最终只是执行了FA父类的析构,就没有执行SON的析构; 说明,你肯定没有删除干净!!!

SON* f = new SON;
delete f;  '因为f是SON类型指针,在delete时: 1,先执行~SON的析构  2,再执行~FA的析构

类this

ST& func(int a){
  A = a;
  return *this;
} /*	*this可以返回“自身对象”的引用。
this是“成员函数”里 隐藏起来的 “函数参数”,表示指向当前对象的指针
*this自然是对指针取值,即该对象本身。
this只能在“成员函数”中 使用。

在调用成员函数时,编译器会将该函数“重写”成:
函数实际是:	ST& func(ST* const this , int a){}
调用实际是: obj.func( &obj , 123 );

这也就解释了: 为什么在成员函数里,可以直接调用”A“这个成员变量
	因为,你使用A这个成员变量, 其实他是this->A 的形式。
	
this是"指针常量” (注意区分“常量指针” const ST* 和 ST* const)
	即该this总是指向“该本体对象”,不能指向其他地方。
	
如果成员函数是const的:	func() const{}
那么他实际上是: func(const ST* const this){} 的形式。		*/

类static

' class里可以有:static成员变量,static成员函数。
class ST{
  static int A;	'这只是“声明”,该变量 并没有定义出来!! 可以当成,就没有他
  static void func(int);
}
int ST::A;  '这才是“定义”出来了,即分配了内存。 在定义时,无需写static
			'你通过: ST::A 和 obj.A 这两种方式,都可以修改A这个static变量。

void ST::func(int c){	 '静态函数的实现,也不需要写static
  B = c;  '这是非法的!! 静态函数,只能使用“静态变量”!!!
	A = c;  '这是可以的。
} ' 同样,你可以通过 ST::func(123) 和 obg.func(123) 两种方式,来调用静态函数

类const变量初始化

class ST{
  const int A = 123;
  ST(){}
  ST(int c) : A(c){}	' 对const的初始化,必须放在“初始化列表”里,不能通过赋值方式
}
ST st1(); ' 对于st1来说,他的st1.A就已经const了!! st1.A==123 永远不能变了。
ST st2(456); ' st2.A = 456!!! 且也是永远不能变了。
  		 ' 可以发现,虽然最初A=123,按说不能变了。 但这个const,他还是变了!!
  		 ' 说明,构造函数 他的权限是很大的
' const必须要初始化有值,如果没有A=123,那么st1就是非法的。

const成员函数

class ST{
    void func() const;  ' const的对象(const ST s),只能调用const的成员函数!!
}				' 非const的对象(ST s), 所有的函数(不管是否const) 都可以调用!!

mutable

class ST{
    mutable int A;
    void func() const;  '该函数里,你A = 123;肯定是报错的。 因为该函数是const的
}			'或者,const ST s;   你s.A = 123; 肯定是报错。 因为该对象是const的
' 但假如说,你就想修改A的值 (不管是在const函数里,还是const对象里,就要修改A)
' 	1种做法是:把func函数改为非const,但不好  ->  导致const对象,就调用不到func了!!!!
'   引入mutable,他就是用来修饰“某个成员变量”的!!! 
'   mutable的成员变量表示:无论什么情况下,他“永远”都是可以改变的。
const ST s; 
s.A = 123;  '这是合法的!!  可以看到,s虽然是const的,但const是一个很”虚“的概念。
void func() const{	A = 123; '这也是合法的。'	}

class里实现函数

class ST{
    void func(){
      	' 直接在class的定义里,实现成员函数  他会自动被当做“inline函数”来处理。
    }	' 当然,不一定成功。  
}		' 因此,如果你直接在类里 实现某个成员函数,尽量简单些 以增加inline成功的概率。

class/struct区别

/*	C++的struct 成员默认都是public的。
	而C++的class, 默认都是private的!!!
class ST{
	int a; '他是private的
};
	
C++的struct的继承,默认是public。   而class的继承,默认是private     */


class ST{		' 在h1.h中,头文件里 只写“声明”
  	int num;
    void init();
};

void ST::init(){	' 在1.cpp里,实现该类的函数
    num = 123;
}

' 在2.cpp里,就不能再对init()进行实现!!!  因为在1.cpp里 已经实现过了

explicit

' 隐式构造函数转换,即对于构造函数ST(int),不一定非要形如ST(123)来会创建对象
ST(int a){}
ST(int a, int b){}

ST a(123);     '这是最正规的写法。
ST a = {123};  '他的意思是: {123}他会“创建临时对象”ST(123),然后对象进行”=赋值“!!!
    			' 即{123}这个东西, 他会“隐式”的转换,转换成一个“ST的对象”!!!
ST a(123,456); 	'这是最正规的写法。
ST a = {123 , 456};   ' {123,456}会调用“构造函数”,创建出一个临时对象ST(123,456)
					  ' 这个右侧的临时对象tmp, 再赋值给a对象!!!

void f1(ST a){}  '你写: f1( {123} );  他通过ST(int)创建了ST的对象tmp,赋值给a了。
void f2(ST a){}	 '你写: f2( {1, 2} ); 他通过ST(int,int)创建了ST的对象,再赋值给a。
    
' 但我们的初衷,也许不是这样。 这是编译器自动帮我们做的。
' 为了防止这种“智能化”的做法。  引用explicit,写在”构造函数的前面“!!!
' 		表示,该构造函数  必须通过ST(int)的方式来构造,不能通过{int}来自动转换。

Iterator

vector<int>::reverse_iterator it = v.rbegin();
*it;  ' *it他返回的是: 引用!!!
    
    
const vector<int> v{1,2,3};  '表示:这个vector里面的元素,都是不能改变的
vector<int>::const_iterator it = v.begin();   ' *it = 12; 是错误的
	'而且,因为该vector是const类型的,所以v.begin() 其实是const类型的。 == v.cbegin()
vector<int>::const_iterator it = v.cbegin();  'cbegin()返回的是:const类型迭代器
    
    
vector<int*> v;
auto it = v.begin();   '注意,迭代器是“指针”!!  (*it)才是vector里的元素!!!
'*it 就是一个int*。    所以 *(*it) 才是一个int

String

const char* p = string.c_str();  '必须是const类型,c_str()他是以'\0'结尾的

Vector

vector<int&> v; '这是错误的!! 引用他只是个别名!! 不是一个对象!!
    

'不要在for循环里,添加/删除vector的元素!!! 因为vector一旦修改,他里面的迭代器就会“失效”!!
    
    
int* p = new int(123);
vector<int*> v{ st };
vector<int*>::iterator it = v.begin();
' p  ==  *it;    p是指针,指向(123)的地址。   it是迭代器(指针),*it是vector里的元素(指针)
' *p ==  *(*it)  == 123
delete (*it);  '这里不是删除vector的元素。  而是删除的(int* p)这块内存

后置函数

int func(double);
int func(double a){	... }	'函数的前置写法
    
auto func(double)->int;
auto func(double a)->int{ ... }	  '函数的后置写法

NULL/nullptr

/*	nullptr是C++11引入的
typeid(NULL) == int;  typeid(nullptr) == nullptr_t  这两个是不同的。

通常: 对“指针”的初始化/赋值,都用nullptr最好。		*/

void func(void* f) {		'重载函数
	' func( nullptr );会进入这里
}
void func(int f) {
	' func( NULL );会进入这里
}

const

' const是可以“运行时求值”的
const int A = 123 + func();

constexpr

' c++11引入的, 他是“编译期间”求值  自然会提高运行效率

对齐原理

struct{
    char A[X];   '他的sizeof == X  (不存在对齐问题)
}; 
struct{
    int A[X];   '他的sizeof == 4*X  (不存在对齐问题) 比如4*3=12 (不是8倍数) 也是合法的
}; 	'即,若结构体只存在一种类型变量,  不会产生“对齐问题”

struct{
	int A;		'但,只要有{>=2}种类型变量,就会产生“对齐问题”
    double B;   '即,此时他的sizeof == 8的倍数。   12 -> 18
};
/*	对齐问题: 	主要还是为了“效率”!!!
	在Windows下 是凑成"8"倍数。  而在Linux下 是凑成“4”倍数。
比如你的真正的sizeof是12.
	在windows下, 会补齐为16字节。    在linux下,会补齐为12字节。
	这会导致,在linux下  丢失数据了!!!  ->  引入#pragma pack()		*/

#pragma pack(1)		// 这句话是:告诉编译器,对齐方式为(1)字节对齐
struct{		       // 即,他实际是多少字节,就是多少。   不进行对齐!!!
    int A;	
    double B;	 '4 + 8 = 12,  那它就占用12字节
};
#pragma pack()    // 恢复,即让编译器的对齐方式
struct{
    int A;
    double B;	 '12字节, 但会对齐到16字节
};

auto

'auto的“自动类型推测”,发生在“编译期间“,不会降低程序的运行效率

输出缓冲区

/*	使用cout输出时,实际上是在往“输出缓冲区”中 输入内容
那么,输出缓冲区什么时候会把内容 输出到“屏幕”上去呢?
	1,要么 缓冲区满了
	2,要么 执行了main函数的return,程序要结束了
	3,要么 使用了endl  ( endl函数,他会调用flush()函数!!
		   而flush()会强制的将“输出缓冲区”的内容显示到“屏幕”上,然后将该缓冲区清空 )
		   
那么问题来了:   为什么要设置这个”输出缓冲区“呢? 
	1,当我们cout时,确实是要将“每个字符” 输出 到”屏幕“上
		  此时,这个输出缓冲区 确实也没有作用。
	2,但我们是在往“文件”里写入内容, 而文件是在“硬盘”上,速度太慢了。
								总不能,写入“每个字符”,就要写一次文件。
		而这个“输出缓冲区”,是在“内存”上,将数据临时保存到该“内存区域”上
		然后最后,一次性的把所有数据写入“文件硬盘”上。
	
*/

\r转义字符

' 不要把\r 和 \t给混了....  \t很简单,就是一个tab键而已
' 而\r是 “将当前的光标,移动到当前行的开头” !!!
    
puts("12345\rABC");   ' 他会输出: A B C 4 5  
    '即光标移到开头后,会把你原来开头的"123"给覆盖掉!!!

Goto

int a = 2;
func:
	if(a >= 1){
    	-- a;
	    goto func;	'循环
	}else{
	    C("我马上就要出循环了")E;   'func就是个语句!! 不可以使用break/continue
	}	


func1:
	int a = 123;		'不要以为a是局部变量,不要以为a和C("fun111")是属于func1的内容
	C("fun1111")E;		'就当做把func1去掉!!!  
func2:					'即这5行代码,如果你没有goto语句
	C("fun2222")E;		'就相当于:  func1: 和 func2 就等于没有写出来!!!
' 换句话讲, func1只是对代码的一个定位!!! 类似于光标的作用!!
' 一定要理解这里!!! 不要把func1: 当做是个命名区域 不是的!! 他就是一个定位,给goto看的!!
    
    
    
goto func2;	'他会直接:跳到C("3"); 然后紧接着到达“exit(0)",  
			'即对于 C("1") 和 C("2")直接跳过!!   
C("1");

func1:	
	C("2");
func2:
	C("3");
exit(0);

File

/*	往文件里写数据时,不会立刻往磁盘上写。
系统会往一个“缓冲区”里写数据,当该缓冲区满之后 才会将缓冲区内容写到磁盘里,再将缓冲区清空。
	假如,你直接退出程序,而没有关闭文件 fclose(..);
	会导致:缓冲区的数据没有写到磁盘里,造成数据丢失。
	而fclose(..)的好处是: 他的执行,会将“缓冲区“的数据 立刻写入到磁盘里!!!	*/

FILE* file = fopen("in.txt" , "w"); '不存在则创建,否则“清空内容”!!!
' fopen会开辟出一块内存(大小为sizeof(FILE)的大小,FILE是个结构体)
' File里会存放一些: 文件名,使用方式,还有一个char*型的字符指针!!
    ' 该字符指针表示: 当前文件,从哪里开始读/写数据
if(file == NULL){
 	puts("文件打开失败");
}else{
    char c = fputc('B' , file);  '往file里,写入一个字符;  B也可写成int
    if(c == EOF)
        puts("写入失败");
}


char a = fgetc(file);
while(a != EOF){
    putchar(a);
    a = fgetc(file);    '从file中,每次读取1个字符 (FILE结构体里的指针,会自己移动)
} '但弊端是: 1个字节=255;   而char=127;  如果某个字节值为255(对应char=-1) 和EOF冲突!

'因此,引入feof(FILE*)函数; 他会自动判断(FILE结构体里的指针,当前是否指向了文件末尾)
char a = fgetc(file);
while(feof(file) == 0){  '要么返回1(说明指向末尾了); 要么返回0
    putchar(a);
    a = fgetc(file);
}
/* 在windows OS下的文本文件txt,其二进制文件: 每行的末尾 会有连续的“0D 0A”
0D表示\r 即回到当前行的开头;    0A表示\n 即换行。
每一行,在末尾 都会有"0D 0A"来表示:  当前下标 换到下一行的开头。
	'虽然,我们使用时 直接用一个\n即可。   但对应最底层,是2个操作!!  */  

函数

/*		void func(int a){	...	}
其中,a称为“形参”, 在调用函数时,会被形参分配内存,调用结束后,形参的内存被释放

在真正调用该函数时:func(x)     其中,x称为“实参"				*/

函数指针

/*	函数的代码,也是要占用内存的, 每个函数都会占用内存,会有一个起始地址。
因此,可以用“指针”指向这个函数的起始地址。    函数名,就是该函数的起始地址。 
	定义形如: 函数返回值 (*p)(参数列表); 				
p指针,可以指向所有与其有相同的“返回值,参数列表”的函数			*/
int Max(int a, int b){
    return 123;
}
int (*p)(int , int); '定义了一个“函数指针变量”,注意这种规范,必须是两个括号!!
    				' 也可以写成: int (*p)(int a , int b); 都可以
p = Max;   '或p = &Max,都是让p指向Max函数的起始地址。 此时p和Max都指向函数的起始地址。

p(34 , 56);		' 以下这3种写法,都是正确的。 都相当于调用: Max(34 , 56)
(p)(34 , 56);	' 不要写成:p  、  *p  这些都是错误的。  
(*p)(34 , 56);	' p()也是错误的, 他等价于:Max()  这肯定是错的,必须要有参数
	'引申:你写成“ Max(int , double){ ... } ”的形式,说明我们不使用参数!!! 
    ' 但你也要必须传递“2个”参数!!!  Max()也是错误的!!!
    
printf("%x , %x", p , Max);   '输出Max函数的起始地址,都是一样的。 不能用cout来输出!!
    
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    
int (*F)(double) = &func;	'注意,F是个“变量”!!!  函数变量
F(3.33);
    
typedef int (*F)(double);
F f = &func;	'注意,F不是个变量 他只是个typedef!!! f是此时“函数指针”的变量!!!
f(3.33);

using F = int(*)(double);
F f = &func;	' F只是个typedef!!!
f(3.33);

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    
void func(int a){ ... }
void (*F)(int) = &func;   'F的返回值和参数,必须与func一致
' F的使用方式,和func是完全一样的!!!  那为什么还要写“F这个函数指针”呢?? 
/*	因为有些库函数,他接收的函数参数 就单纯一个函数名!!! 不特定他的返回值和参数!!
这种情况,比如你的func函数 是有重载的!!! 有func() 和 func(int)
那你的: 库函数(func),究竟func是哪个函数呢?? 不知道,你也无法分清楚。
  解决方式是:   void (*F1)() = &func;  void (*F2)(int) = &func;
  虽然后面都是&func,分不清。 但你的函数指针(F1,F2)的参数类型 是确定了的  */
    
    
' 当你要指向一个“全局函数”func时: 你要这样写。
void (* func1)(int) = &func;  

' 当你要指向一个“成员函数”func时: 你要这样写。  注意这里的写法!!! (ST::* func2)
void (ST::* func2)(int) = &ST::func;

函数命名

void XX(char , int , unsigned int , long long , double){
    return;
} '他的 typeid(XX).name()为: F v c i j x d E,  注意 与你的XX命名是没有关系的!!
' 不管XX是什么,其实编译器判断的依据是“返回值 和 参数列表”!!!
'void(v)  char(c)  int(i)  long long(x)  double(d)  LL(x) ULL(y)等命名规则
' 比如还有个YY函数,与上面的XX的返回值+参数列表都是一样的,那么他俩的命名是一样的!!!
' 不关注,具体这两个函数内部的代码是怎样的。  只要返回值+参数列表是一样的,他俩的命名就一样。

函数指针传参

int Max(int a, int b){	return max(a,b);  }
void func(int a , int (*p)(int,int)){
    C( p(12 , 45) );
}
func(123 , Max); '注意这里,传递的是Max,而不是Max(x, y)!!! 
    			'因为p是要指向某函数的指针/地址, 而函数名就是地址,传递地址!!!

函数指针typedef

'注意,typedef就只有个改名的功能!!!
    
void (*p)(int , double);   '这是在“定义”!!!定义了一个指针
typedef void (*FuncType)(int, double);  '这不是在“定义”, 而是换了个名字!!!
FuncType p;	'现在,你通过: FuncType p 就等价于“上面的第一个语句“ 定义了一个指针

void fun(int a , void (*p)(int, double)){ 'p是个指针,是个变量!!!  写的麻烦。。。
    p(1 , 2.333);  
}
void fun(int a , FuncType p){ '起到了 和”上面一模一样“的目的!!! 代码更简洁了~~
    p(1 , 2.333);  
}

函数声明

void dfs1(int);
void dfs2(int);

void dfs1(int a){
    if(a == 0)  return;
    dfs2(a - 1);
}

void dfs2(int a){
    if(a == 0)  return;
    dfs1(a - 1);
}
/*	由于函数的使用,必须要遵循"从上到下“的顺序!!!
因此,像上面的代码  如果没有函数声明,一定是使用不了的	*/

指针、引用

const

char str[] = "xx";
const char* p = str;  'const char*    ==    char const*
p[0] = '.';  '报错!!!	     若p 是 char*类型,则是正确的。
str[0] = '.';  '这肯定是正确的。   说明:const char* 不是说:他指向的内容不可以改变
    			' 而是: “他指向的内容,不可以通过p这个指针改变!!

const int& p = a;  '表示:不能通过p来修改a的值, 但a的值 是可以修改的    

char* const p = str;
int* const p = &a;
' 类型* const: 这种写法为,“指针常量”  含义为:该指针必须指向这个内存,不能指向其他!!
' 因此,他必须要初始化。  你进行p = &a的赋值 、++p,只要是改变指针的指向,都是错误的!!!
    
int& p = 123;  '错误!!!   因为123是常量。
const int& p = 123;  '这是可以的!!!		这一点,同样适用于“函数传参”
void func(int& p){}  'func(123)这是错误的!!  必须是const int& p的类型,才能传递“常量”
/*	程序内部会维护一张表,该表记录所有的“变量名”<->“其对应内存地址”的关系	*/

int a = 1;
int* p = &a;
*p = 2;    '注意,不要以为*p == 1,而是理解为:*p == a。 *p=2 -> 会导致a=2
    

p[3] == *(p + 3);  '指针先后移动3位

    
int A[1005] = {123};	'C(A) = 0x6011a0
int* p = A; 'C(p)=0x6011a0 , C(&p)=0x7fff32dba388 说明p也是个变量 , C(*p) = 123;
func(int a[] , int* b){
	' C(a) = 0x6011a0 , C(&a) = 0x7fff32dbaa90 说明a也是个变量
	' C(a) = 0x6011a0 , C(&b) = 0x7fff32dbaa98 说明b也是个变量
}

数组指针、指针数组

'指针数组, 是一个数组 存放很多个指针
int* p[10];
p[i] = &A[i];
const char* p[] = {"123" , "456" , "789"}; '这是他最大的应用;
    	'注意,p是个数组, 他里面是有3个指针!!! 3个const char*类型的指针变量!!!

'数组指针, 他是一个指针!!  指向一个[固定大小]的数组
int A[3] = {1 , 2 , 3};
int (*p)[3] = &A;    '注意这里, 虽然A和&A是一样的,但语法规定 要使用&A

C(p) == C(A) == C(&A) = C(&A[0]);   '这4个都是一样的。

FOR(i , 0 , 2){
    C(p + i) / C(p[i]);   '注意,这种用法都是错误的!!!
}	'p的类型是[3](int[3]=12字节),你让p + 1,就等于:让p往后偏移12字节(已经越过A数组了)

int* tmp = (int*)p;   '注意,这里要进行强转
FOR(i , 0 , 2){
    C( *(tmp + i) );	'这才是,正确把A数组的值给输出出来!!
}

'数组指针
int A[2][3] = {1,2,3,4,5,6};
int (*p)[3] = A;   '注意,这里又成A了;  即指向了A[0] 第一维度
FOR(i , 0 , 1){
    int* tmp = (int*)p;
    FOR(j, 0 , 2)
        C( *(tmp + j) );
    
   	p ++;   '注意这里的p++,他是[3]类型!! ++会导致+12字节,指向了A的第二维度A[1]
}

字符数组、字符指针

char s1[] = "ABC";
char s2[] = "ABC";
' C(&s1); C(&s2); 表示的是,该数组内存的值“ABC”的首地址(因为,数组名 = 数组元素首地址)
' 一定要牢记, &s1表示的是: s1数组内存里的“ABC”的首地址。  sizeof(s1) == 4
' 因为,数组这个名字 他不像指针,他是不占内存的,他不是个变量!!!
' 你想想,他s1就是个数组,表示一块内存区域,里面不是存的s1,存的就是直接是这个数组的值。
' 而且,s1 != s2,说明 “ABC”这两个串 占据不同的内存!! 
' 因为是把“ABC”放到不同的数组里,因为是不同的数组,自然内存区域是不同的。
    
const char* s1 = "ABC";
const char* s2 = "ABC";
' C(&s1) 他可不是“ABC”的地址!!! 他是s1这个指针变量的地址!!!
' 你可以通过: C( &"ABC" )这种方式,得到在常量区里的地址
/*	但通过调试得知, 这个两个“ABC”的内存是一样的!!! 
这里和上面,是完全不同的含义。   上面是,把“ABC”内存 放到了一个数组(内存)里面。
而这里,你是没有开辟一个数组/内存来 存储这个“ABC”内容的!!! 你只申请了2个指针而已。
	一个单纯的“ABC”字符串, 他是称为“字符串常量”,C语言专门有个内存 来存放字符串常量
	而且这一特定区域,因为是常量!! 即不能修改(只读) 所以要用const
		用const也意味着,你不能修改“这块内存区域”!! 即s1[1] = 'X'是非法的!!
	所以,这里的“ABC”就存放在这个特定的内存区域,有固定的内存地址,即s1和s2指向相同内存!!	*/

数组、指针

void func(int A[]){
    C(A); C(&A); C(&A[0]);
' 但这里,A和&A[0]肯定是一样的,等于main函数中的123的地址。
' 但重要在&A这里!!! 此时,因为是在传递参数!! 所以此时的A,他确实是一个变量!!!
' 即&A != A, 但不要以为 数组名也是个变量,不是的。  只不过在函数参数这里,确实是个变量。
}
main(){
    int A[] = {123, 456};
    C(A); C(&A); C(&A[0]); '这3个都是一样的,说明:这个数组名A 并不是一个变量!!!
}						   '这个A 并不占内存, 而是表示 该数组里的首元素A[0]的地址!!!
    

void func(int* p){
    C(p);	'p 为 &A的地址。
    C(&p);	'&p 他是个变量,而且是个临时变量,就指这个函数参数中p变量的地址。
}
main(){
    int* p = &A;
    C(p);	'p 为 &A的地址。
    C(&p);	'注意这里, 指针和数组名不同!! 指针p 他是个变量 占内存,&p就是这个变量的地址
}

编译期预处理

/*	多个cpp源文件 ->(编译)-> 多个.obj目标 ->(链接)-> 一个exe文件

在编译阶段: 
	1,预处理		2,编译		3,汇编	*/
' 预处理包含: 	1,宏定义		2,文件包含		3,条件编译

define

#define AB "123            456"
string s = AB; // s = "123              456";
'即,宏定义   他是不会”分割空格的”!!!   他是看后面的整个行!!!! 不会断开
    
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

#define A(X) ...     // ...里面,不可以有B(X),宏定义遵循从上到下的顺序
#define B(X) A(X)    // 这是可以的。

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

/*	#define PI 3.1415926     #define就是宏定义命令,在预编译期间 会进行“宏展开”
	宏展开,只耗用编译时间,不占用运行时间 
#define的作用域,就是该cpp文件!!! 其他cpp文件使用不到
可以用#undef PI来提前终止他的定义域		

在宏定义时,常会使用到 \ 来表示,这些代码是同一行的,只不过分行了
#define P printf \           \的意思,就相当于:printf("123456");
 ("123456");				*/


-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

    
#define C(x) 	cout << #x << " -> " << x << endl;
	'这个#x的意思是: 你x所代指的参数,而且是一模一样!!!
    '#的名称为: “字符串化”, 即传来的是什么  就把他转为string输出

C(123);  '123  ->  123
	
int a = 123;      '你传进入的“长什么样”, 那么你的#x 就会输出什么!!!
C(a);	'a  ->  123

FOR(i, 1 , 2)
    C(i);    'i  ->  1     和     i  ->  2
    
A[1] = 5, A[2] = 6;
FOR(i , 1 , 2)
	C(A[i]);    'A[i] -> 5    和    A[i] -> 6
    
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    
' #define是允许重复多定义的!!  
    
// 1.cpp里
#include "h1.h"

#define A 123
cout << A;  '即使h1.h里有(#define A 456),也是会选择当前1.cpp里的对A的define,所以输出123.
' 但如果,当前1.cpp里没有对A的define。 则会输出(h1里对A的define)
    
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    
__FILE__	'当前所在的文件。  (char s[X]类型)
__LINE__	'当前所在的行号。	(int类型)
__func__	'当前所在的函数。
    
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

#ifdef、ifndef、if

'第一种方式:
#define Windows         '这里可以不写后面的值

#ifdef Windows			'#ifdef 和 #ifndef 刚好是相反的意思。但使用是完全一样的
int main() {...}

#else				'这个else也可以不写
int main() {...}

#endif

'第二种方式:
int main(){
    
#ifndef Windows		
    C("yes");		'如果没有定义Windows,则输出Yes
#else
    C("no");
#endif

}

'第三种方式:
#define Win10 0
#define Linux -1
    
int main(){
    
#if Win10		 '从上往下走 #if/elif  XXX,只要XXX {定义define出,且他的值!=0} 就停止
	C("Win10");	 'Win10的值是0,不满足;   没有Win7的定义; 有Linux,且!=0,所以输出
#elif Win7
    C("Win7");
#elif Linux
	C("Linux");
#endif
    
}

文件重复include

#include "h1.h"    // 这一句话 很简单,就是将“整个h1.h"文件的代码 直接copy过来!!!
#include "h2.h"

' 比如,你的"h1.h"中 有#include "h2.h"。  
' 那么在这里cpp,你其实有2次的“#include <h2.h>”这个语句!!!
' 那么 代码重复不说,一定会导致: h2里的很多变量 多次的定义 -> 重定义,报错
    
// 因此, 涉及到书写”header.h"头文件时,要使用规范!!!  #ifndef
// 因为,#include非常容易发生 多重嵌套的情况,根本搞不清楚
#ifndef __H1__
#define __H1__ 		// 这里我们只定义即可, 给不给值都可以。

' ...写h1.h头文件的代码

#endif
'注意,这个__H1__定义,他在你的cpp文件里 他是可以使用到的!!!
' 其实,他就是定义在你的cpp文件里, 因为#include就是把代码copy到你的cpp里了!!!
    
/*	在1.cpp里,#include"B.h" 就意味着: 把B文件里的内存 添加到了当前文件中!!!
意味着,当前1.cpp文件里的代码行 会大大增加!!

#include的本质: 就是把要include的文件内容 复制到当前文件中
而且就是”你所写#include"xx.h"的这一行的位置上		


<xxx.h>   '他只会在“系统目录”下找
"xxx.h"	  '他首先会在当前cpp源文件目录下寻找,如果找不到 再去系统目录下找	*/

typedef

typedef int Int_array[105];
Int_array A;   '他就相当于: int A[105];

程序

/*	C语言的程序在内存的分布,由3个部分组成:
1,程序代码区
2,静态存储区
	在程序运行期间,分配“固定”的存储空间。  全局变量,会放在“静态存储区”里,在程序一开始时
	就会为全局变量分配内存,在整个程序执行期间,他们占据的内存区域地址  是不变的!!
3,动态存储区
	局部变量、函数形参、函数调用的数据。 函数执行时分配内存,执行完毕 释放内存,是动态的过程。 */

inline

/*	函数调用是会“耗费系统资源”的,因为要处理“函数调用,返回”等
	需要不断的“压栈,出栈”,开辟内存。

引用inline后,会在“编译”期间,完成对inline的处理,不再有“函数调用”的系统消耗
直接让“函数本体代码”去替换, 但究竟会不会成功 要看编译器。
	他类似于“#define宏展开”的过程。

inline成功后,会将该函数的代码 直接覆盖到 源代码里  ->  导致“代码膨胀”

constexpr函数,就是将该函数 写到 常量表达式里, 而且(constexpr函数,要写的简单些)
constexpr函数 他是自带inline性质的。			*/

cpp与h

/*	多个cpp文件,他的变量和函数,都是公用1个的!!!
1.cpp里有全局变量A;   在其他cpp文件里,通过extern int A; 来引用它
1.cpp里有函数func();  在其他cpp文件里,通过void func();  来引用它

也就是说,多个cpp 你就当做 他们就是“同一个cpp”文件就可以!!!

在.h里,你也是可以“实现函数”的!!!
但一般, h1.h里放的都是“声明” 比如有1个: void func(); 函数声明
	那么,你在1.cpp和2.cpp里,都去#include "h1.h"是没有问题的!!!
	因为只有声明,没有实现。
但你不可以,让1.cpp去实现了func(),又让2.cpp去实现了func(),这是错误的!!!
对于“所有.h文件”中的某个声明函数f1 ,你只能选择“1个”cpp文件 去实现他。
	如果是>=2个cpp,一定是错误的。
一定要牢记: 所有cpp,你就看成是1个cpp!!!!  而#include的本质,就是简单的代码copy

即在多个cpp和include合并一起之后,其实可以得知:
	一个函数是可以多次的声明的(你写100次也没问题)
	但他的定义(又称实现),只能是<=1次!!!

但如果,h1.h里放的是一个“实现”的函数: void func(){ .... }
那么,你在1.cpp和2.cpp里,都有#include "h1.h",就是错误的!!
	因为include的本质,就是代码copy。  而1.cpp和2.cpp,最终会合并一起
	意味着: 你定义了多个“已经实现”的函数。			*/

static

/*	1, 在局部变量(函数内) 使用static, 即func(){  static int A;  }
		 该A,仅限于func()函数内部!!!!  只不过是在静态存储区
	2, 在全局变量 使用static,  即static int A;
		该A,仅限于该.cpp源文件内 (其他cpp文件,仍可以定义A变量)  变成内部变量
	3, 对函数 使用static, 即static void func(){...}
		该函数,只能在该cpp文件内 (其他cpp文件,仍可以定义func函数) 变成内部函数     */

外部函数

/*	1.cpp里有个void func(){...}   那么在2.cpp里 有也有void func(){...}
这是不可以的!!! 只能有1个, 但如果2.cpp里 是int func(){...}这是可以的。
在2.cpp中,如果要使用1.cpp里的func()函数,则要通过void func(); 来声明出来。
(不管2.cpp里有没有声明<1.cpp里的func函数>,你都不可以重新定义一个void func(){...}    */

内部函数

/*	如果想让:1.cpp里的func函数,仅限于在1.cpp这个源文件区域里。
即,2.cpp里 也可以有完全相同的func()函数, 此时就可以让1.cpp的func函数 变成static类型!!

即在1.cpp里是: static void func(){...}
那么在2.cpp里, 就可以有 void func(){...} 这是正确的

*/

局部变量

/*	但我们希望,在函数调用完毕后,该局部变量不会消失 而是继续保存他的值。
即,他所占内存不释放。   此时引入“静态局部变量”,但注意 该变量,他的作用域还是该函数内!!	*/
void func(){
	static int A = 0;	'长期占用内存
    ++ A;
    C(A);   'A的值,取决于func函数的调用次数。  有几次调用,A的值就是多少。
}

cpp源文件

/*	一个程序,可以包含很多个.cpp源文件,但只能有1个cpp里面是有main函数的!!!
也就是说,其他的cpp文件 和 主cpp文件,写法规则都一样,唯一的区别是“没有main”函数。  */

全局变量

/*	全局变量的定义(可以有初始化,也可以没有) 
	只有定义在他下面的函数,可以使用到他。	
    
在多个cpp文件中,如果有多个int A的全局变量,这是不可以的!!
你在1.cpp里,有1个全局变量(int A),那么就意味着 在所有的cpp文件中  这个A变量都是共享的!!
不可以再次定义,只能声明出来使用
    
extern int A的意思是: 在该cpp、其他cpp文件里,已经有对全局变量A的定义(注意是定义,即分配内存)	
	也就是: 单纯extern int A; 他是不会分配内存的!!!  
	至于A这个全局变量的定义在哪里,不知道, 反正是在某个cpp文件里。
    该声明,一般是放在cpp文件的开头(#include的下面)	
    
如果你想要1个全局变量,该全局变量就在当前这个cpp文件中有效,对于其他cpp无效
比如在1.cpp里有个A的全局变量,你还想在2.cpp也有个A的全局变量 (且这两个是完全不同的)
这也是可以的,你可以在1.cpp中 把这A的变量,设置成static int A;
	这样,你在2.cpp里(extern int A;这是会报错的!!!) 1里的A,和其他cpp 没有任何关系		
	也就意味着: 加上“static”后,该变量就成了“内部变量”了,即只在该cpp有效。		*/

extern int A;	'注意,这个只是“声明” 不是定义!!! 更不能有初始值,他是不分配内存的!!
void func(){	C(A);	}   '如果没有上面的extern,是使用不到的!!!
int A = 123;

/*	全局变量,是可以在多个源文件cpp里共用的!!!
比如,你在1.cpp里有一个int A=123的全局变量,如果要在2.cpp里使用它。
则,在2.cpp中  只要有一个声明即可: extern int A;
*/

char[]

char s[] = {'a' , 'b' , '\0' , 'd'};
' 他的sizeof为4,就是为这4个数组元素的大小;  strlen为2 (遇到\0结束)
' 输出: cout << (s + 3); 是会错误的,因为末尾没有\0
    
char s[] = "abcd";	'数组就是数组,不是指针!!! 他的元素 是会被覆盖掉的!!!
' 他的sizeof为5!! 最后会补\0    所以cout << s是正确的。

strcpy

char s1[20] = "xx";   ' s1="ABC" 或 s1=s2; 这都是错误的,不能直接赋值!!
char s2[10] = "xx";   ' 只能在初始化时,设置初值="xx";  不能单独s2 = "xx"!!!
strcpy(s1 , s2);  '形象为:s1 = s2, 即s1的原内容 会被覆盖成s2 (连同s2末尾的\0字符)
    
' 不能通过:s1 == s2来比较!! 这是在比较地址
    
s1[0] = 0;   	'这两种写法的含义是一样的 , 因为'\0'的ASCII码 等于0
s1[0] = '\0';      '其实你可以理解为: 将s1这个字符数组,“清空”!!!

strcat

char s1[] = "hello";	'此时, sizeof(s1) == 6
char s2[] = "world";
strcat(s1 , s2);	's1的sizeof还是6!!, 但strlen已经是10了!!!
    
'最好的写法是:
char s1[100] = "xxx";
strcat(s1 , s2); '(char* s1 , const char* s2) s2是不变的  提前给s1设置大些

-=-=-=-=-=-=-=

Struct / Class

struct/class A{
    int data; '如果是class 默认是private!!  如果是struct 他默认是public!!
}
最经典的将class分类是: string(带有指针pointer) 和 complex(不带有指针)

complex复数: data(实部加虚部) + func(加减乘除,共轭,正弦... 来处理实部虚部)
string: data(其实就是一个指针,指向字符串) + func(拷贝,插入,...)
      
----------------------------------------------------------------------------------------
class ST{
    ST(int a):a(a){}   // 写了这个,就没有默认构造了!!!  此时只有这一个构造,没有默认构造
    ST(const ST&) = delete;  // 把编译器默认的拷贝构造给delete掉,即ST t1(t2) 禁用
    ST(ST&&) = default;	// 搬移构造
    ST& operator=(const ST&) = default; // 拷贝赋值,使用编译器默认的
    ST& operator=(const ST&&) = delete; // 搬移拷贝赋值
    virtual ~ST(){}   // 虚析构函数
}

-----------------------------------------------------------------------------------------
class ST{
    struct STT{
		char a;
        int b;
    };
};   // 此时ST是不占内存的!!! 他里面没有变量,STT只是个定义,并没有定义他的变量!!

-----------------------------------------------------------------------------------------
struct ST{
    ST(){	cout << "???";	}
}t;   // t调用的是默认的构造,此时main为空,会输出一个"???"
// 如果是t[100]; 就会输出100个"???";     {ST t}或{ST t = ST()},都会输出一个"???"

-----------------------------------------------------------------------------------------
struct ST{
    int A;	
    static int B;	'这里必须这样写,且不能赋予初值!!! 而且,你并没有申请一个变量
        '也就是:此时ST是没有B这个变量的!! 你必须在外面 int ST::B; 才是声明变量
        '且,static 是不占内存的, 即sizeof(ST)还是4!!
        
    void func1();  '普通函数的声明
    static void func2();  '静态函数的声明
}
int ST::B;	'这里必须要有!!  这是static变量的固定写法 (必须这么写,此时初值可有可无)

void ST::func1(){	'普通函数的实现
    ....
}
void ST::func2(){ 	'静态函数的实现
    他,只能使用“静态变量”!!!
}
    
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

拷贝构造、赋值

class A{
    A() : data(0){}     '无参构造
    A(const A& tmp){	'拷贝构造
        data = tmp.data;
    }
    A& operator=(const A& tmp){ '赋值运算符
        if(this == &tmp){ '比如,当A中有一个指针(指向某个内存) 当赋值时:如果tmp就是他自己
            return *this; '先释放掉原来这个指针的内存。然后memcpy,此时就会报错
        }
        
        data = tmp.data;
        return *this;
    }
    
}

基本流程

1.h(header files)	 类声明 、 标准库
2.cpp(main) 通过include来引用头文件

Complex (无指针)

#ifndef __COMPLEX__			// complex.h   ->    通过#include "complex.h" 来引用
#define __COMPLEX__  

​````````````````````````````````````````````// 正式开始写代码
#include<cmath>		
    
class complex;		// 1, 前置声明

complex& __doap1 (complex* ths , const complex& r);

class complex{    //  2,类的声明
    ....
};

complex::function ....		//  3,类的定义
    
​````````````````````````````````````````````
    
#endif
class complex{
public:
	complex (double r = 0 , double i = 0) : re(r) , im(i){}
/* 	构造函数: 就是来创建对象的, :re(r)是初始化  {}里面是赋值,两者是不同的	*/
    
    complex& operator += (const complex&);   // 只是函数的声明,没有实现
    double real() const{	return re; /* 编译器会变成: this.re; */	}    
/* 但最终是不是inline由编译器决定!!!  我们决定不了,一般非常复杂的函数 就不是inline
	注意const的位置 (函数的后面,{}的前面),如果不涉及改变对象的data,要加上const
*/

    // 不带指针的函数(也就是除了string),大多都不用写析构函数,因为编译器会自带他的析构
    
private:   // 所有的数据,都要是私有的!
    double re , im;

    friend complex& __doapl (complex* , const complex&);  // do assign plus
/* friend友元(朋友),朋友可以来拿数据data,其他不可以
*/
}

inline complex& __doapl (complex* ths , const complex& rig){
    ths->re += rig.re;  // ths是要拿data的 他会改变,  r是被取data的 是const
    ths->im += rig.im; 
    return *ths;
}
// 若把构造器放到private里,则外部不可以创建class   ->  Singleton设计模式

class A{
public:
    static A& getInstance();
    setup(){ .. }
    
private:
    A();
    A(const A& rhs);
    .....
};
A& A::getInstance(){
    static A a;
    return a;
}
// 通过:A::getInstance().setup();  的方式来获取对象
/*	如果传递参数太大了,就要使用引用传递;   引用的底层就是指针,尽量都用引用传递
*/
T& func1(const T& a, const T& b){
    T ans = a + b;
    return T的引用;   
} // 这样是不可以的,因为ans是在这个函数里的 我们返回ans的指针
// 但func1执行完毕后,他已经死掉了,自然他原来的指针地址也是失效的,此时不可以返回值为引用


/*
	当:T t2(t1);   但我们没有定义T(const T& t)的构造方法!!
	或 t2 = t3;  	我们同样没有重载等于=号 
编译器自带:拷贝构造(T)和拷贝赋值(=)!!!!  不用定义或重载			*/

Operator

/*
	运算符重载,有两种方式: 要么设计为成员函数,要么设计为全局函数
	两者都可以,没有谁好谁不好,但必须二选一,不可以有两个冲突
*/

// 1:  写为成员函数
a += b;    // b是参数,即下面的t ;   a是目标 即当前这个类对象 他会传递到下面隐含着的this里
T& operator += (const T& t){ // 所有成员函数,他的参数 其实都隐含着一个this (this , ..)
    this->val += t.get(); // 一定要注意,get必须是: get() const{..} 只要不修改,必须加const!!
// 因为此时t传来的是const,也就意味着必须不可以被修改,他调用的get()方法 也必须是const类型的。
    return *this;      // 注意this是指针,返回的是引用,*this是指针所指向的object数据
}  // 返回的是数据value,接收的是引用。 这就是引用的好处
// 传递者 无需知道接收者是不是以引用来接收的,你就直接传递即可。
// a += b;  b传过去,有可能接收的是引用 有可能接收的是数据value
T& T::operator += (const T& t){  // 他等价于上面的类里写法 
    ...  // 只要是看到:	类名::函数名 ,这就意味着他是成员函数
}

/*		对于简单的: a+=b;  如果返回值不是T& 而是void,值直接修改到this里,也是可以的
但对于: a += b += c;  这样会导致: 先b+=c完后,返回的是void,导致a+=b就为空了
 因此使用T&为返回值是最好的。		*/


// 2: 不是成员函数
T operator + (const T& a , const T& b){
    return T(a.get() + b.get());
}
// 但对于特殊的操作符,只可以是全局函数,而绝不可以是成员函数
// 例如,cout的<<符号,就必须是全局,不可以是成员
ostream& operator << (ostream& os , const T& t){
    return os << "value:  " << t.get();
} // cout << t1;     cout就对应于参数的ostream里的os, t1对应于参数的t
// ostream& os肯定不可以是const,因为把"..."丢到cout里, cout里肯定就改变了
/*  前置和后置, 都是碰到逗号, 就结束了!!  而不是碰到冒号;
b = 2 * ++a , XX;  即b = 2*(a+1);       
b = 2 * a++ , XX;  即b = 2*a;
无论是前置后置, 到XX之前 都运算结束了!!!			*/

struct ST{
    int a;
    ST(int a = 0):a(a){}
    
    ST& operator++(){	// 前置++i;   ->  没有参数
        ++ a; 	// 等于 ++ (this->a);  前置很简单,因为就是此时做完即可 返回的也是引用 当前对象
        return *this;
    }
    ST operator++(int){	// 后置i++;	->  带有参数int(但没意义)
        ST ans = *this;  // 不能返回引用 因为ans是临时对象!!! {}完就消失了,必须是值传递
        ++ (*this);  // 这就是后置的核心也是通用模板,即:后置调用前置!!  
        return ans;  // 之所以后置可以完成暂停, 是因为取指针的值*的底层原理 跟这代码无关
    }
};

转换函数

class Fraction{  // 存分子 和 分母
    int num , dem;
 	Fraction(int num , int dem = 1):num(num) , dem(dem){}   
};
Fraction f(1,3); 
double ans = 4 + f;    // 我们的期望是等于:4.3333...
/* 方式1,我们对Fracion进行操作符重载有用吗? 那它一定是:Faction + (T) 的形式,即Faction必定在前面
		但显然这里Fraction是在后面,因此对Fraction进行操作符重载是没有用的。
方式2,全局操作符重载; 即double operator+ (double a, Fraction b){	return a + b...;}
		这是可以的,因为操作符重载 要么是某个类的成员函数里,要么是全局函数
方式3,我们希望在必要的时候,把Fraction给转换成double类型,比如在此时 4+f 我们希望f转换为double
	那么这样,4+f就可以运算了,这个转换函数的调用是自动的 编译器做的,关键是这个转换函数如何设计
	规定: operator double() const{  return (double)num/dem;	}
	注意!! 这个opertor跟运算符重载没有关系!! 他必须要有空格分开 (运算符重载不需要空格)
	double是表示: 我们要将这个Fraction转换成什么类型
	()他不是运算符!! 跟运算符没有关系!! 这就是来指定参数的, ()自然是没有传参
	按说要指定返回值类型,即double,但因为我们转换为了double 这个指定了的,那自然返回值就是double
	因此返回值类型不用写,自然就是转换的类型。
*/

/*	一定要区别:运算符重载 和 转换函数!!
转换函数:	operator int() const{ ... }   他没有返回值,operator后有空格 空格后是类型(int)
操作符重载: T& operator->()	const{...}	他有返回值(void..), 没有空格
*/

Explicit

class Fraction{  // 存分子 和 分母
    int num , dem;
 	Fraction(int num , int dem = 1):num(num) , dem(dem){}   
    Fraction operator+ (const Fracion& t)const{
        return Fraction(num+t.num , dem+t.dem);
    }
};
Fraction f(1,3); 
double ans = f + 4; /* Fraction有+这个运算符重载,但这个4不是Fraction类型!!!
注意,此时编译器会自动的把4转换为Fraciton,为什么呢?  因为F的构造只有1个参数!!!
F的构造:2个parameter,1个argument; 我们认为就1个参数即可,4自动的变为Fraction!!!!!!!!!!!!!!!
就变成了: (1,3) + (4,1)  = (5,4)
*/
/*  此时我们得知: Fraction(int a, int b=1):a(a) , b(b){} 这个构造
不是说只有创建Fraction对象这一个作用!!!   在必要的时候,编译器会把一个int(1)给转成Fraction!!!
为了避免这种情况 避免编译器自动帮一些类型调用构造函数,我们加入explicit
*/
即: explicit Fraction(int a , int b=1):a(a),b(b){}  
这样,只有Fraction(a,..)才会创建对象, 避免其他情况会创建对象的情况

String (带指针)

/*	对于上面的Complex类型,编译器会默认提供一个自带的拷贝构造和拷贝赋值 T t1(t2); t1 = t2;
但对于这种class类里带有指针的,我们不要使用编译器自带的拷贝构造和拷贝赋值,因为带有指针 非常危险!!!	
如果使用默认编译器的拷贝,他是浅拷贝 只是按bit赋值而已,即两个指针同时指向同一个内存
我们需要做的是深拷贝,即两个指针指向不同的内存。
*/
    
/*	字符串:char*指针指向头 然后后面一整串,有两种规范形式:
	1,C/C++采用的, 最后有一个结束符合'\0'
	2,后面没有结束符号'\0',但最前面多一个整数 来标识该字符串的长度
*/

class Mystring{
private:
    char* data;

public:
    Mystring(const char* cstr = 0);  //构造 , 传入指针
    Mystring(const Mystring& str);   //拷贝构造 , 接收的是自己这个class
    Mystring& operator=(const Mystring& str);
    ~Mystring();  //析构 , 当该类的对象死亡时,调用析构

    char* get_c_str() const {   return data; } // 一定要养成习惯,不涉及修改 就加上const
};


inline Mystring::Mystring(const char* cstr){// 注意,原型有cstr=0, 这里不可以有cstr=0!!
    if(cstr){
        data = new char[strlen(cstr) + 1]; /* 注意这里!!传来的这个cstr,要么是空指针
要么代表一个字符串的头指针,这个cstr的末尾肯定有一个'\0',比如cstr是“a b \0"
那么他的stlen是2 不包含末尾的\0,这个\0就是cstr的一份子!!!
也就是strcpy肯定是会把这个cstr里的\0给赋值到data里!!! 即data的内存应该开辟3大小 而不是2
strlen会得到的是2,因此要加1   */
        strcpy(data , cstr);
    }else{  // 空指针,用户要创建一个空的字符串,注意这里的写法
        data = new char[1];
        *data = '\0';
    }
}

inline Mystring::Mystring(const Mystring& str){
    data = new char[ strlen(str.data) + 1 ];
    strcpy(data , str.data);
}

inline Mystring::~Mystring(){
    delete[] data;   // 数组的free,必须带有[]
}

inline Mystring& Mystring::operator=(const Mystring& str){
    if(this == &str)   // 自我赋值: a = a
        return *this;

    delete[] data;  // 杀掉自己
    data = new char[ strlen(str.data) + 1 ];
    strcpy(data , str.data);
    return *this;
}

ostream& operator<< (ostream& os , const Mystring& s){
    os << "(" << s.get_c_str() << ")"; //Mystring不符合cout,但char*是内置 他符合cout
    return os;
}

new

// stack
栈:他是存在于某个作用域(scope)内的一块内存空间(memory space)
	比如当我们调用函数,函数本身就会形成一个stack来存放他所要接收的参数,临时对象,返回的地址。
    临时对象,又称自动对象(自动清理,析构自动调用)   

    
// heap
堆:由操作系统提供的一块全局global内存空间,程序需要通过`动态new`的方式来获取若干个块(blocks)


// new
new: 先得到一块内存空间,然后调用构造函数
T* t1 = new T(1);
底层过程:
(1) T* t1;
(2) 得到内存:	void* memory = operator new( sizeof(T) );  
	 "operator new"是一个函数,其内部调用malloc(n)得到sizeof(T)这么大的一块内存
(3) 把2得到的内存,强转类型:	t1 = static_cast<T*>(memory);  // memory和t1的指针指向相同
(4) 通过3得到的t1指针,调用构造函数:	t1->T::T(1);    其实是(this, 1); this隐含


// delete
: 先调用析构函数,再释放内存空间
delete t1;
底层过程:
(1)调用析构函数:  T::~T(t1);   
(2)释放内存:  operator delete(this);
	"operator delete"内部调用free(this);



T t0(0);  // 全局变量,也可看成是static,整个程序结束后,才会消失
mian{  // 这个大括号{} , 就称为一个作用域
    T t1(1);  // t1所占用的空间是来自于stack 即临时栈对象,{}指向结束,t1自动就死亡了(调用析构)
    T* t2 = new T(2);  // 他是heap里的 即堆对象,必须我们手动给delete掉
    static T t3(3); // 当{}结束,他仍然存在,直到整个程序结束后(析构才会被执行)
    
    delete t2; // 如果不删,就造成内存泄漏(这个new T(2)对象还在内存里,但这个指针t2已经死亡了)
}

// 内存
比如我们T* t1 = new T(1);  这个T所占的是8字节
我们动态得到内存,其实不只是8个字节: 我们会多得到前面[front]32个字节 + 后面[back]4个字节
    然后再加个两个cookie,每个是4字节,值为000xxxx1,最后1表示这个内存给用户了
此时是:8 + 32 + 4 + 4*2 = 52
由于每一个内存块,都是16的倍数。 (52 -> 64) 最终占64字节,这就是实际的!!!
最终:[cookie(4)] [front(32)] [我们要的T(8字节)] [back(4)] [为补齐的额外pad(12)] [cookie(4)]

cookie的作用: malloc和free双方规定好:我们在这个内存区域的上面 放一个cookie
    cookie就是我们的new/delete对内存操作的根据,是根据cookie的值来执行的
    64的16进制是:0000040,因为所有16的倍数的0x进制的最后一位一定是0
    因为借用最后一位的0/1: 1表示借出去了 即给用户了,0表示收回来了 即在os手里
    如果我们new来得到内存,则这两个cookie的值为00000041
    如果我们delete还给os,即这两个cookie的值为00000040
    
// 动态开辟数组的内存
T* t1 = new T[3];
和上面的new T(1);有一点不同的是,他还会用4个字节 来存3这个数 标识数组元素个数
// 即new中的operator new(siz) , siz = sizeof(T) * 3 + 4, 这个4字节就来记录3这个数
[cookie(4)] [front(32)] [记录3(4字节)] [我们要的数组(8*3字节)] [back(4)] [cookie(4)] = 72
72 补齐-> 80;    cookie的值为:00000051
delete[] t1;  一定要带上[],这是删除数组的写法,delete是根据cookie的值删的
    因此即使是delete t1; 这个80的内存,也会被删除,但析构函数 只会调用一次!!!
    因此,delete[] t1; 他会调用3次析构

// 如果类不大,就不用开内存 使用栈空间即可。  但如果很大,必须要手动开内存!!!
class T{
    int A[1000][1000];   // 这么大的空间,栈空间是放不了的!!!!
}t;
main(){
	T t; // 这是不可以的!!!! 爆栈了!!  或者是我们把T t; 放到全局里,即别在方法里!!
    T* t = new T(); // 必须是这样!!   最好的方法就是: 直接放到class定义的后面 {...}t;
}

new/delete 重载

/* 重载: operator new/new[] 和 operator delete/delete[]
注意,我们不是重载的new,而是operator new !!! 
因为new: 1,得到内存 void* mem = operator new( sizeof(T) );  在这里重载
	2,指针转型 ptr = static_cast<T*>(mem);		3,构造 ptr->T::T(); 
*/
void* operator new(size_t siz){		return MyAlloc(siz);	}
void operator delete(void* ptr){	return MyFree(ptr);		}

void* MyAlloc(size_t siz){		return malloc(siz);		}
void MyFree(void* ptr){			return free(ptr);		}
// 即T* t = new T(123); 这就会调用我们重载的operator new()函数 来进行内存分配
// 但如果是:T* t = ::new T(123); 这是不会调用我们的重载函数!!!
// 因为::new 会调用的是 ::operator new()代表全局 

Static

类里

struct A{
    int data = 123123;
    A(){    C("A - constructor")E;  }
    ~A(){   C("A - destory")E;  }
};
struct B{
    static A a; // 静态成员变量的声明(没有定义),他是没有内存空间的!!
    int data = 123;
};
B b;
b.data;  '这是正确的。 也就是说,此时 只要不使用b.a 程序就是正确的
    	 '只有b.a这是错误的。  因为B里,其实就没有a这个对象,没有定义 没有分配内存
struct A{
    A(){    C("A - constructor")E;  }
    ~A(){   C("A - destory")E;  }
};
struct B{
    static A a; // 静态成员变量的声明(没有定义),他是没有内存空间的!!
};
A B::a;  // 对静态变量的定义

/*	main是空的。 但他会“调用A的构造,和A的析构” 。
说明:对于类静态变量,只要定义出来(即声明+定义),那么 即使你没有使用到他 但会被分配空间	*/
struct B{
    inline static A a;	
} ' 在c++17之后,可以使用inline (直接把“声明” + “定义”),此时a是定义出来了的
    
/*	这里a是一个“类class”类型的静态变量,那么不管程序是否使用到a
	编译器,都会为a分配内存!! 即A a他的构造函数,会直接被调用!!(全局变量也是这个模式)
	
但如果a,是一个普通的类型(非静态成员变量) 如果程序没使用a,编译器没有为他分配内存	  */

函数里

void func(){
    static A a;
}
/*	在函数里,与在类里不同。	你直接:static A a;  这就是定义。
但如果你没有执行func()函数,那么他和在类里不同。 这里他是不会自动为A分配内存的!!
只要调用func,才会为a分配内存。  且以后执行func,不会重复分配内存		*/
只要是在数据data 、 函数func的前面加上static,就变成了静态函数/数据
对于非静态的函数调用,比如t1.func / t2.func , 他的底层是: func(&t1) , func(&t2) 
# 注意这里,非常重要,一个对象t1这块内存 他里面肯定有他的data,因为每个对象的data都不一样的
# 但我们知道类里的func是不占这个类的大小,即他不是这个t1对象所特有的
# 即func他对于所有的对象 都是完全一样的,只是不同的data,因此注意!!! 一个类的func 在内存只有一个!!
# 而不是像data有多个,所有的对象t1,t2... 都公用一个func接口, 通过传入t1所在的内存地址来操作func
# 即t1.func(),不要想成是调用了t1对象里的func函数!! 其实是func调用了t1这个对象!!
# 即编译器会把t1.func() 转换为:func(&t1,..)  即我们得到的this指针 指向t1这个对象地址。
# 也就是类中的普通(非静态)函数,一定有一个this指针!!!  来标识要对哪个对象进行func操作

静态数据:比如银行的"汇率"所有人的汇率都要是一样的,因此要使用static
静态函数: 我们知道类的func都是带有this指针 来标识对哪个对象进行func,但静态func是没有this指针 
	 即静态函数与对象无关!! 显然得不到对象,那么他只能访问到静态数据 `静态函数只能调用静态data`
// 即静态的data/func都脱离与对象无关的,他是属于这个类class的
class T{
public:
    static int num; // 不可以在这里设置num的初值(编译错误),这就是C++的规定
    static void setNum(int t);
    
};
int T::num = 123;  // 一定要注意这里,必须前面带有类型int,不可以直接T::... = 123;
// 我们可以不设置num的初值; 即在这里写:int T::num;  必须这样声明出来!! 在类里不算作声明!!!

void T::setNum(int t){
    num = t;
}
/*	T t1;  // 与java不同,c是使用 :: 来取得static的
    cout << T::num << ' ' << t1.num << endl;   // 使用t1.num就等于是 T::num
    T::setNum(33)   // 使用t1.sutNum(33) 就等价于使用 T::setNum(33)		*/
# 单例
class T{
public:
    static T& getObj();
};

T& T::getObj(){   // 注意,要用引用!!!
    static T t;   // 不要把static T t;放到类里的private里!!!  放到这个函数里的好处是:
    return t;     // 如果用户就没有使用到t,就不会被创建,当getObj执行 t才会被创建。
}
// 单例是直接通过:T::getObj()来使用的,即T::getObj().xxx来使用这个对象的
// 不需要再:T t1 = T::getObj()来接收,这就不是单例的意义了
static const auto io_sync = []{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);   cout.tie(nullptr);
};
int main(){
    '只是,全局定义了“io_sync", 但他是不会自动执行的!!!
    io_sync();
}

顺序

一个项目里,有多个cpp。 每个cpp 里面都有一些“静态变量”{全局 、类里的静态对象(已定义)}
那么,其实这些变量的 初始化的顺序{谁先分配内存构造的顺序}, 我们是未知的!!!

Const

常量性分为两种: 1,物理常量(每个bit位不变)		2,逻辑常量(表现形式不变)
C++采用物理常量!!
struct ST{
    int* p;
};
int a = 123 , b = 456;   'const必须有初始值
const ST t = {&a};    // t.p的值,永远不变 为&a       即t.p = &b这肯定是报错的!!
*t.p = b;			  // 但,我们修改了他指针的值!! 即此时a == 456;
'这是违背了逻辑常量性的!!   即C++的const是物理常量性,通过mutable可以支持部分的逻辑常量性
    
-----------------------------------------------------------------------------------------
class A{
	void func() const{..}  // const函数必须是类的成员函数,全局函数是不能加const的
	// 非const的对象 肯定所有的函数都可调用;  const的对象 只能调用const函数,这必然的;
};

Constexpr

constexpr是C++11引入的,为常量表达式,即在编译器可以求值的表达式。

-----------------------------------------------------------------------------------------
constexpr int b = 43;
constexpr int a = b * 2 + 1;   // b必须是const/constexpr类型
const int a = func();   		// 返回值为int,这是可以的
constexpr int a = func(); 		// 返回值必须是:constexpr int func()

Cout

class cout : public ostream{ ... }; // 说明cout继承自ostream (o是output的意思,输出流stream)
class ostream : virtual public ios{
    ostream& operator<<(char/int/long/.... x){
        return (*this) << x;
    }
}

String

string使用了"reference counting"这种技巧,即他可以被共享 (共享:是设计模式的一部分)
比如有4个完全一样的字符串,他们其实是共享同一个内存的

Copy / inserter

int A[] = {1 , 2 , 3 , .....};   // 让这个数组, copy到vector里

// 1
vector<int> v1(A , A + n);

// 2
vector<int> v2(n);  // 需要提前分配好内存!!
copy(A , A + n , v2.begin());

// 3
vector<int> v3; // v3并没有内存!!!
copy(A , A + n , inserter(v3 , v3.begin())); // inserter会在指定位置后,插入元素 他帮开内存

-----------------------------------------------------------------------------------------
'即copy(容器头 , 容器尾 , 指针/迭代器)'
copy(begin(A) , end(A) , v.begin());   // [] -> vector;   v必须有空间!!
copy(A.begin() , A.end() , B);   // vector -> [];    B数组必须有空间!!

Move

Mystring(const char* p){...} // 构造
Mystring(const Mystring& p){...} // 拷贝构造
Mystring& operator=(Mystring& p){...} // 拷贝赋值

Mystring(Mystring&& p) noexcept{...}  // move迁移拷贝构造,普通和move版本,差别就多了一个引用&
Mystring& operator=(Mystring&& p) noexcept{...} // move拷贝赋值,只比普通多一个&
// 迁移拷贝(构造/赋值),都属于浅拷贝,即只是新拷贝了一份指针!然后把原来的指针置为(NULL)

virtual ~Mystring(){
    if(data)	delete data; // 以前是直接delete,现在有move(指针会置为NULL),要判断是否为空
}

T t(t1); // 普通的拷贝构造
T t(std::move(t1)); // 你必须保证以后不会再使用到t1这个对象

/*   move虽然非常快,但他是浅拷贝,只是指针的拷贝,会把原来的指针置为了NULL
应用1, 比如对应临时对象!!  xx.insert( T(123) ); 这是个右值,即临时对象
		编译器会自动的调用他的move版本的copy(如果有),因为这个对象以后不会再使用到
*/

Macro

'宏定义,就是单纯让代码变得简短 
#define macro_1(name)\
cout << name << endl;              // 在这一行下面写的,不属于宏; 除非这里末尾添加"\"

int main(){
    int abc = 123;
    macro_1(abc);    // 相当于:cout << abc << endl;
}

=default, =delete

// default和delete的用途1 ->  应用于 构造/析构/拷贝构造/拷贝赋值
struct ST{
public:
    ST() = default / delete;
    ST(const ST&) = default / delete;
    ST& operator=(const ST&) = default / delete;
    ~ST() = default / delete;
} 

// delete的用途2 -> 应用于 重载的operator new/...
struct ST{
    static void* operator new/delete/new[]/delete[](..) = delete;
}'删除的含义是: ST* t = new ST()就不可以使用了!!!

Template

用template<class/typename T>都可以用来定义函数/类模板,在使用上 两者没有区别
只要使用template,调用时,就必须是:XX<>的形式,即使里面是空,也得有<>

纯类型

/* 使用:ST<int> c1;  编译器会把ST的原生代码中 所有的T替换成int !!! 
 得到一份代码,看到ST<double> 又会产生一份新的代码;   导致会产生:代码的膨胀	*/

-----------------------------------------------------------------------------------------
template<class T = int>
struct ST{
    ST(T a){   cout << a << endl;   }
};
ST<int>(123);    // 输出: 123
ST<>(123); 		 // 输出: 123         类型,也可以是指定默认值

-----------------------------------------------------------------------------------------
template<typename/class T>  // 使用class和typename都可以
void func(T a){
    cout << a << endl;
}
func<int>(123);

'即template里,使用class/typename,则对应为<类型名int/double/...>

可变长

template<typename... types>
struct ST{	
};
ST<int, double, long long> s;

纯常量

template<int t> 里面放具体的量,即传入一个值作为参数,而不是类型。
但注意,这个t 只能是整数{int/ long},且是const常量!!!
' template<double t> 这是不允许的!!!  <class t = double> 而class放类型是任意的

----------------------------------------------------------------------------------------

template<int var1 , int var2 = 456>    // 对于有默认值的,必须放到最后面!!
struct ST{
    ST(){	cout << var1 << ' ' << var2 << endl;	}
}
ST<1,2>();		// 输出: 1 2
ST<1>();		// 输出: 1 456
ST<a, b>();		// 注意,ab必须是const类型的!! 即const int a,b;

'即template里,使用具体类型int/long(不能是double),则对应为<123、const变量>

可变长

template<int... vars>
struct ST{
    ST(){
        int A[] = { vars... };   // 可变长的名字,不是vars,而是vars...
        for(int* p = begin(A); p != end(A); ++p)
            cout << *p << endl;
    }
};
ST<1,2,3,4,5,6>();

混合

template<class T, T a>   '一定要注意,T a是一个值 只要是模板传入值,就不可以是double!!!
struct ST{
    ST(){    cout << a; }
};
ST<int, 123>();   // 输出: 123
ST<double , 10.33>();   // 这是错的!!! 模板常量,不可以是double

函数模板

实参推演

template<class T>
void func(T t){
	...
}
func(123);   '按理说,我们应该指定类型,但对于函数模板,他可以根据实参类型 来推断模板的类型
    		 '即我们可以像使用普通函数一般,使用模板函数。  他根据输出的123,推导出T为int

'也就是说: 函数参数是一定要按规矩来的(是几个参数就几个)!!!!
'但函数模板未必一定要带模板参数<..>,甚至模板<>号都可以不写。 他会根据函数参数来推导模板
'但类模板必须要模板参数<>, 类模板没有实参推导...
    
----------------------------------------------------------------------------------------
template<int... vals> struct ST{}; 'ST的类型,不是ST<int...>!!! 而是ST<int,...未知>
    
template<int... vals>      '这个函数的模板参数是一个int的可变长, int...
void sort_print( ST<vals...> t ){   '这里的t也可以不写,如果不会使用到的话
/*	注意这里!!! 我们想让函数参数来接收一个ST类型,但我们知道ST是类模板
(类模板没有实参推导),他必须要指定模板<>,由于ST是一个模板 我们不知道他是什么类型
他的类型,取决于这个函数的模板。
注意!!! 此时函数的模板是int...即他是常量 ,不是类型!!!! 即函数传来的是<1,2,3....常量>
而注意,ST类的模板他是需要的也是常量!!! 即比如是:ST<1,2,3,..>
我们正好可让函数的模板vals,去作为ST类的模板。      此时这个函数已经构造好了。

当调用时,我们可以不写函数模板 因为可以实参推导; 我们直接让一个ST对象:ST<1,2,3>() 作为函数参数
而不写函数的模板; 那么函数模板int...是什么呢?   
注意,不是说我们模板是vals 参数是vals... 两者对应就可以匹配,不是的!!
他是进行的函数模板推演。 即根据ST<1,2,3>() 会推导出,模板vals为(1,2,3)			*/

模板特化

template<class Key>
struct hash{  1111	};			 // 1

template<>
struct hash<char>{	2222	};		// 2

template<>
struct hash<int>{	3333	};		// 3

/*	   hash<int> a;    hash{1111}第一个没有指定类型,他是泛化的
	即如果没有2和3,则这个hash<int> a走的就是1的程序。
	但现在特别的定义了<int>的特化,则他不会走1的泛化, 而是走3的特化程序。
*/

偏特化

//   参数个数的特化
template<typename T , typename Alloc=...>
class vector{	....	};

template<typename Alloc=...>    // 第二个参数Alloc还是使用的泛化
class vector<bool , Alloc>{	  ....	};
/*	vector实际上是有两个参数的<类型 , 分配器>   我们的特化不一定要对两个都特化
	也可以只对第一个参数<类型>进行特化, 第二个参数<分配器>不进行特化 还是使用的泛化		*/


//	 参数类型的特化
template<typename T>
class My{	....	};

template<typename T>
class My<T* >{	...	 };
/*	 我们是对T类型进行的泛化,按理说特化应该是某个T类型,但我们也可以使用的他的指针
	My<string> m这肯定进入的是泛化;  My<string*> 这会进行特化		*/

可变参数 (2.0)

void print(){}   // 这个可变参数的终止函数,必须在可变函数的上面!!!!

template<typename T , typename... Types>   // typename...  就是数据类型,可以是不同的类型们
void print(const T& a , const Types&... params){ // 注意写法:Type&... 
    cout << a << ' ' << sizeof...(params);  // sizeof...()可以知道params到底是几个参数
    print(params...);  // 注意不是params,要有... params传参后,会分解为1个 + (n-1)个,以此类推
}

print(3 , 5.555 , "abc" , true);
// 先是(3 , ...)输出3,然后是(5.555 , ..)输出5.555,然后后("abc“, ...)输出abc ... 递归下去
//  最后是: (true , ..) 输出true后,是() 注意此时就没有参数了!!!!
//  因此,必须再写一个空参的处理函数,且在可变函数的上面 !!! 

Namespace

namespace std{
	... 	   
}
/*	1:using namepsace std;  本来是std::cout , 现在直接cout
	2:using std::cout;   我们只使用一个cout
*/

组合 Composition

class queue{  // queue的底层实现
protected:
    deque<T> c; // 底层容器; queue类里,有另一个deque类; 这就叫组合
public:
    reference front(){	return c.front();	}
    void pop(){	return c.pop_front();	}   // queue的功能,都是调用底层deque的
};

class deque{ // deque的底层实现
protected:
    Itr<T> start;    // 16字节
    Itr<T> finish;  // 这又是一次"组合composition" 
    T** map;	// 指针的指针,还是个指针 4字节
    unsigned int map_size;  // 4字节    
};

class Itr{
    T* cur;    // 四个指针,因此Itr的大小是 4*4 = 16
    T* first;
    T* last;
    T** node;
};

/* queue -> deque ;   
	构造是由内到外的!! 先调用deque -> queue的构造函数
	因此在queue中的构造 其实是这样的:queue::queue():deque();  这是编译器自动完成的
析构是由外到内的!!	 否则先把内部的析构掉,内部垮掉 整个就垮掉了
	因此在queue的析构中: queue::~queue(){	析构queue....;   最后再~deque();	}
*/

委托 Delegation

/* 和之前的Compositon有些相似,只不过他用的不是直接一个类,而是这个类的指针!!
 因此委托,又可以称为:引用方式的组合;  之前的组合,即queue里有deque,这两者的生命周期是完全一致的
 但我们这里用的是指针,只有在真正使用到它时,才会真正创建出来,因此这两者不是完全同时产生的
 */
class MyString{
private:
    StringImp* rep;  // 之前这里是:char*,这个StringImp是真正的实现类!
    // 好处是,我们可以指向不同的实现类; 这个MyString永远不用修改,动的是实现类
};
class StringImp{
    friend class MyString;
    char* data;
    int count;
};

MyString1 -----|
MyString2 ----->	StringImp1	  ->    "hello"
MyString3 -----|
/*		我们此时,有三个字符串(MyString123)共享同一个"hello world"
 StringImp里有一个count属性,就是来记录此时引用的个数 (count=3)
 这样做的目的是: 对于内容一样的,实现共享,这样很多对象共享同一个数据 节省内存
但当涉及修改时: 比如MyString1要修改,此时StringImp会单独拿一份"hello"给MyString1
然后StringImp1的count值就变成了2, 此时只有MyString23在共享“hello"
叫做:copyByWrite , 但写的时候 就复制一份		*/

继承 Inheritance

/*	上述的"组合和委托"很简单,因为在C的结构体里就可以实现	*/
struct Fa{      // 这里就把struct当成class就行,两者区别不大
    Fa* prev;
    FA* next;
   	void func1(){
        ...
        func2();  // 注意,这里func2只是个接口,他最终是会调用子类的实现,父类是没有实现的
        ...
    };
    virtual void func2() =0;
};
struct Son : public Fa{  // 这个:冒号,就是继承
    T data;
    virtual void func2(){.....}  // 纯虚函数,必须重写
}; 		// 这就是23个设计模式之一的: Template method
Son s;
s.func1();  /* 底层是: Fa::func1(&s) 因为子类没有func1
第一步:调用父类Fa的func1函数,因为成员函数都有隐含的this,此时func1虽然是父类的函数
	但在执行func1函数时,那个this指针 其实是子类s的对象
第二步:在执行父类func1时,遇到func2函数,看func2在子类中 有没有定义,有定义就执行子类的
	即会跳到子类的func2执行,然后再跳回完父类的func1函数。	*/

/*	
父类的数据,会被子类继承下来,(从内存角度看:继承下来的data,也会占内存) 
父类的函数,也会被子类继承下来(他不应该从内存角度看,因为函数的继承,继承的是"调用权")

	"继承的最大价值是: 和虚函数搭配一起使用"

子类Son: 构造,他会先调用父类的构造,然后再调用自己的构造!!  (构造,是由内到外)
		 析构,他会先调用自己的析构,然后再调用父类的析构!!  (析构,是由外到内)	
注意:父类Fa的析构函数,必须是virtual虚函数 否则不是很好		*/

/*	子类Son继承父类Fa,里面又有一个类Fri, 即既继承又组合
1,先调用父类Fa的构造	2,再调用组合Fri的构造	3,最后才调用自己Son的构造

如果Fri是在Fa类里,那么顺序是: 1,调用Fir的构造 2,调用父类Fa的构造	3,调用自己Son的构造

反正是: 构造的方式是,从内到外; 析构从:从外到内			*/
/*	爷爷类:A;   父类:B;   子类:C;    (C继承B,B继承A)
此时,我们设置一个容器,同时放A的对象,B的对象,C的对象。

方式1(wrong):  vector<A> v{A() , B() , C()};   放的类型是基类A的类型
		这会导致: 对于B和C的来说,因为是转成了A类型,自然BC的数据就丢失了!!!
		比如有func函数,本来BC调用的是自己的覆盖的函数实现,但最终BC对象调用的是A的func函数!!
方式2(多态): vector<A*> v{new A() , new B() , new C()};	放的是基类指针A*
		这样的话,for(auto i : v)		i->func();
		因为注意!! 我们强转的是指针,而不是数据!!!我们只是强转了指针类型,但他指向没有变!!
		比如对于C*这个指针,他原来指向new C(); 虽然他的转成了(A*)类型,但还是指向的C()!!!
		因此此时: i->func()调用的,就是子类自己覆盖实现的函数。	*/

虚函数

class Fa{
    int data;
    virtual void fun();
};
/*	这样,编译是正确的。    但一旦如果你有:Fa f 这就报错了
*/
/*	子类Son继承父类Fa的函数,其实继承的"调用权" ,从继承的角度看,成员分:
1,非-虚函数:  父类Fa和子类Son中,必须至少有1个 实现该函数; 如果都实现,用子类的
2,虚函数:	它必须在父类中实现!! 子类可以写也可不写, 子类写了用子类的,否则用父类的
3,纯-虚函数: 他必须不可以在父类中实现!! 就当做是"抽象方法",该类就变成了"抽象类" 不可以创对象!!	*/
class A{
public:
    void func1();
    virtual void func2();   // 必须从父类A,即必须要在"此时"实现出来,这就是普通的虚函数!!!
    virtual void func3() =0; /* 一旦有纯虚函数(抽象函数),这个A类就立刻变成了抽象类!!!
	即A类不可以创建对象!! A a;是错的!! 纯虚函数,必须只是个接口,不可以在此时实现!!
	如果没有func3,A a是可以的,即只要没有纯虚函数(抽象函数),则该类就不是抽象类	*/
}; 
void A::func1(){ // 普通函数,此时可以不实现,但父类和子类中 必须至少有1个实现了
    cout << "A -> func1() " << endl;
}
void A::func2(){  // 因为他是普通虚函数,必须在父类里实现出来
    cout << "A -> func2() " << endl;
}
// 不可以实现func3, 纯虚函数 必须不可以实现。

class B : public A{
public:
    void func1(){  // 普通函数的继承, 父类可以不写 子类可以不写,但父类/子类必须有>=1个实现了
        cout << "B -> func1" << endl;   
    }
    void func2(){  // 普通虚函数的继承, 父类一定已经实现了,此时可写可不写,优先使用子类的
        cout << "B -> func2" << endl;   
    }
    void func3(){  // 纯虚函数的继承,这个抽象函数一定没有实现,此时必须实现。
        cout << "B -> func3" << endl;   
    }  
};

Vptr、Vtbl

class A{
    int data1;
    
    virtual void v_func1(); 
    virtual void v_func2(); 
    void func1(); 
    void func2();
};	
/*	如果没有虚函数,即(data + func) 则A的大小是4*2 = 8; (普通非虚函数,不属于类的内存)
只要类里有虚函数(virtual),这个类就会多一个虚指针(Vptr),不管多少个虚函数 都是用的这一个指针(8字节)
即,此时A的大小为:2*2 + 8 = 12,但他会凑成8的倍数 -> 此时A占16字节		

即只要有虚函数,类里就会有一个Vptr虚指针,这个虚指针 会指向一个虚表(Vtbl)
虚函数表(虚表): 存放当前这个类的所有可使用的虚函数指针,指向各个虚函数(不包括非虚函数)
	他是一块连续内存,每个有虚函数的类 都有一个虚表,且所有该类的对象 都共享这个虚表。

A的内存:			{这个虚指针指向一个表格Vtbl(虚表)}
[ Vptr(8字节) ]----------> Vtbl虚表(如下)
[ data1 ]			     [ 虚函数指针1 ]------指向虚函数----->[ A::v_func1内存 ]
						 [ 虚函数指针2 ]------指向虚函数----->[ A::v_func2内存 ]	

{... 内存的某个角落里,有A::func1 和 A::func2函数,但他是普通函数,我们暂不关注}		*/

class B : public A{
    int data2 , data3;   
    
    virtual void v_func1(); 
    void func3();
}; 
/*	先不看B:A 不管继承,单看B:  4*2 + 虚指针(8) = 16 (满足8倍数)
此时加上了: B继承A ->  所谓继承: 1继承父类的数据   2继承父类的函数(注意,函数也会继承下来)
只不过函数继承的不是内存(况且一个函数所占内存是求不得的),而是调用权!!
先看继承: 4(A的data) + 8(A的Vptr);  然后看B: 8(B的data) + 8(B的Vptr)
但注意,Vptr只是一个指针(8字节) 不管有多少虚函数,因此A的Vptr和B的Vptr,会合并一起。
即: 4(继承的data) + 8(此类的data) + 8(Vptr) + <凑8倍数> = 24		
即一个类的size = (此类的data) + (父类/爷爷类/..的data) + 8(只要此类/父类/爷爷类存在虚函数)

因为所有的虚函数,不管的定义的 还是继承的 还是覆盖的,都是仅仅对应一个8字节的指针!!!
而究竟是如何调用的呢? 比如函数的覆盖问题是如何处理的呢? (暂不关注对于普通的函数的继承覆盖问题)
注意此时:A有虚函数v_func1和v_func2,B自然会把他俩给继承下来(在虚表里) 即有A::v_func1和A::v_func2()
	但因为此时B是重写/覆盖了v_func1,即虚表里,指向A::v_func1的指针会被干掉
	换成一个:指向B::v_func2的指针。 即最终虚表里的指针指向为:A::v_func2() 和 B::v_func1()

B的内存:			{这个虚指针指向一个表格Vtbl(虚表)}
[ Vptr(8字节) ]-----------> Vtbl虚表(如下)
[ A::data1 ]		    [ 虚函数指针1 ]------指向虚函数----->[ A::v_func2内存(继承) ]
[ B::data2 ]		    [ 虚函数指针2 ]------指向虚函数----->[ B::v_func1内存(覆盖) ]
[ B::data3 ]

{... 内存的某个角落里,有B::func3函数,但他是普通函数,我们暂不关注}		*/

class C : public B{
    int data4 , data5;
    
    virtual void v_func2(); 
    void func4();
};
/*	已知B类有: 4*3(B和A类的data) + 8(Vptr) ,  C类有:4*2(data) + 8(Vptr)
因此C的size = 4*3 + 4*2 + 8(Vptr) + <凑8倍数> = 32

他继承的B的Vtbl里的函数指向有: A::v_func2 和 B::v_func1
注意,此时C有v_func2的虚函数,即他会覆盖掉A::V_func2 !!! 即原来虚表里指向A::v_fun2的指向被干掉

C的内存:			{这个虚指针指向一个表格Vtbl(虚表)}
[ Vptr(8字节) ]-----------> Vtbl虚表(如下)
[ A::data1 ]		    [ 虚函数指针1 ]------指向虚函数----->[ C::v_func2内存(覆盖) ]
[ B::data2 ]		    [ 虚函数指针2 ]------指向虚函数----->[ B::v_func1内存(继承) ]
[ B::data3 ]
[ C::data4 ]
[ C::data5 ]

{... 内存的某个角落里,有C::func4函数,但他是普通函数,我们暂不关注}		*/

/*	只要是使用虚函数,编译器就会走这条路!! 即Vptr -> Vtbl -> 虚函数指针 -> 虚函数内存
obj.func();     
在C语言中: 会变成call xxx ,xx是目标函数的地址,编译器会跳到那个位置 (这种方式是:静态绑定)
c++调用虚函数:  他不是静态绑定,而是动态绑定! 即从Vptr指针 -> Vtbl -> 找目标虚函数指针内存
				不再是以往,直接跳到目标函数的内存地址 (多态,虚函数,动态绑定;都一个机制)

假如此时有: C* p = new C(); 即一个C对象的指针p
( *(p->vptr)[n] )(p)    等价于   (* p->vptr[n] )(p)
*(p->vptr) 
*/

委托 + 继承

/*	分屏逻辑,一个窗口 有多个可以操作的子窗口	*/
class Window{
	Vector<Sub*> views;
/* 单看这个Sub*:  这是委托,即类里存放其他类的指针
然后看这个Sub的背后深刻的意义, 到时候这里存放的,可不是Sub这个类,而是Sub这个类的子类实现类!!!	
在容器里放的东西,必须是同样的大小size,因此要放指针
*/
public:
    func.....
}
class Sub{
    virtual void update(Window* win, int value) =0;
};

pointer-like class

/* 1, pointer-like class   ->   智能指针
	智能指针是一个类 一个对象!!	指针能做的事情,这个类也要能做		*/
struct ST{
    ....
	void func1(){  ...	};
};
class shared_ptr{
private:
    T* px;
    long* pn;
public:
    T& operator*() const{      // 这是对应原来天然指针的使用方法,即*p 来取值 得到这个对象
        return *px;		}
    T& operator->() const{     // 这还是对应原来天然指针的用法,p->.. 取这个对象里的东西
        return px;		}	   // 这里是直接返回px,即返回一个指针, 变成了px->...
    shared_ptr(T* p):px(p){}   // 构造,即智能指针传入的是一个天然指针
}
shared_prt<ST> p1(new ST);  // T* t = new T() 用T*来接收,因此new得到的就是一个指针!!!
	// 我们要做的是:把这个new的天然的指针,包装到智能/聪明的指针里
ST t1(*p1);  // 拷贝构造, *p1是取值,即取上面(new ST)的这个对象,赋值到t1里
p1->func1();  // p1是智能指针,->返回px,即px->func1() 这是可以的,因为px就是ST*的指针。
/*   这里,不是应该p1->后,变成px 即pxfunc1() 这个->不是消耗掉了吗...
	箭头->符号,是特殊的,他和其他符号不同,他不会被消耗掉
*/



/*	2,pointer-like class   ->   迭代器
	迭代器,也可以看作是一种智能指针,只不过与上面不同的是:
	他不仅要处理*和->    他还要处理++ , -- , == , != 等			*/
T& operator*() const{
    return (*node).data;
}
T* operator->() const{
    return &( operator*() );   // operator*() 就是调用上面的函数
} // 可以看出: operator*()不仅是重载,在类内部 可以当成是函数来使用!!!!

function-like class

/*	让一个类,像是一个函数 (仿函数)		*/
struct ST:public unary_function<T,T> {   // 虽然是一个类,但其实是函数的作用
    const T& operator()(const T& t) const{
		.....  // 通过操作符()重载的方式, 来实现函数的作用
    }
};
struct unary_function{
    typedef Arg ..;  // 这是不占内存的,理论上这个类的大小就是0 实际(1), 问题不大
    typedef Result ..;
}

指针、引用

int cmp(const void* a , const void* b){
    return *((int*)a) - *((int*)b);
}
int a = 3 , b = 5;
cout << cmp(&a , &b);
`地址就是一种指针,指针就是一种地址的形式`
ST x = 123;								// x在内存位置1, 占80字节 sizeof(ST) == 80
LL* p = &x;	// p是一个变量,是ST*类型		 p在内存位置2, 占4字节
LL& y = x;  /* y是一个变量 是ST&类型		  y在内存位置3, 占80字节!!!!
 注意y是引用 虽然所有的编译器都是用指针来实现引用的,引用里有一个指针 指向x的地址
 虽然&y == &x, 但sizeof(引用) == sizeof(原T)!!!  即y的大小不是指针大小
 这个y就固定了!!! 比如y = st2; 不是说y指向了st2,而是x对象 = st2;  !!! 
 即引用不向指针 可以随意的指向,引用从一出生 他指向的对象就固定了!!! 他没有办法再指向别的
 Java里,所有的变量都是引用!!!		*/

/*	引用的用途就是:传参	func(int a)  和 func(int& a)  默认会使用(int a);
	func(int a)const{...}  func(int a){...} 这两者是不歧义的,加不加const也是区分的
比如string源码: operator[](pos) const{..}  和 operator[](pos){ 修改 } 
	说明,加不加const,也是合法的函数重载
*/
ListNode* cur = head;  // head->val==1,  head->next: [9,10,11]
cur->next = new ListNode(123);
// 此时:head->next->val == (123) 此时:cur->next指向 和 head->next指向,是同个内存
// 即head->next不再指向{9,10,11},而是指向了{123}

cur = new ListNode(123);
// 此时:head->val 还是 (1)  此时:cur的指针指向 和 head的指针指向,是不一样的!!!
// 表示: cur指针,指向了另一个内存,不再是head
// 即,如果我们先让head直接指向{123} (不是head->next指向{123),是无法通过cur来完成的..

# 从上可以知道!!!  T* cur = head;  cur可以操作head内存"里面"的东西 , 即cur->xxx
# 这样,cur修改了,就等于head修改了;  但直接修改cur,是对head无效的!!!
# 比如让cur指向head->next; 即此时cur==(9,head的儿子),但head还是(1)
# cur可以修改head"里面"的东西(通过->),但无法改变head本身所在的内存地址

-----------------------------------------------------------------------------------------
T* t = new T[12];   // 他不是调用了operator new(malloc)12次!!
// 而是只调用了一次的: operator new[] , 即只是一次的malloc !!
// t[i]他不是指针!!  t[i]就是个对象,必须通过t[i].data获取 (看似是指针,但实际是对象!!!)
 
T* t[12];  // 这是12个指针!!!
for(int i=0;i<12;++i)
    t[i] = new T();   // 这里是12次的malloc!!!
// t[i]他是指针!!!  必须通过t[i]->data获取 t[i]就是T*类型 (和上面不同,他是指针,不是对象!!)

-----------------------------------------------------------------------------------------
T* t = new T(123);
// t是一个指针!!! 他指向T(123)这个对象的内存地址!!
cout << t; // 这是在输出: T(123)所在的内存地址!!
cout << &t;  // 这是在输出: t这个变量(他是T*类型),输出这个变量所在的内存地址

动态/静态绑定

Son son;
Fa fa = (Fa)son;
fa.func();  // 静态绑定(不是指针,肯定是静态)  -> 汇编:  call xxx(地址)

Fa* p = new Son();     // Son 继承 Fa, 这是向上转型up-cast (Son*转为了Fa*)
p->func();  // 动态绑定(1,p是指针; 2,func是虚函数; 3,向上转型(B->A))
			// 他的汇编/底层逻辑是: (*(p->vptr)[n])(p) 根据虚指针找到虚表,取下标得到虚函数地址

p = &son;    // 这也是向上转型!!!(&son)是Son*的类型,转为Fa*  即此时p->func() 也是动态绑定!

Tricks

typedef double D[105];   // 要对double[105]命名:  不可以double[105] D; !!!  
D d;   // d是个数组!!  D d;   就相当于:  double[105] d;  

-----------------------------------------------------------------------------------------
继承,父类的typedef也会继承下来!!!  
这就是很多类只有typedef,可以让子类少写几个字符~

获取类型名称

导入: <typeinfo>头文件
cout << typeid(变量名/类型名,都可以).name();    // 比如(string) -> Ss

/*	比如定义一个类: class ABC{ ... };  
	其实在编译器之后,就不叫ABC了,名称会略有不同			*/

Function

比如已经定好了接口为: func(int a , int b);   也就是他的函数调用必须是:func(12, 45);
可以这样实现: func(int , int b){	...  } // 表示虽然传入了第一参数,但不用他!!
// 注意,函数调用时 第一个参数必须要写,只不过函数实现时 可以不接受

----------------------------------------------------------------------------------------
函数内定义一个递归函数:
auto func = [&](int a , string b){    '如果是普通函数,不用加&; 如果是递归 必须有[&]'
    return (double)10/3;
};
'这个auto,就等价于: std::function<double(int, string)>

Reinterpret_cast<>

T = reinterpret_cast<T>(X*);    // 将一个X类型的指针,转为T类型。 他是按位bit来强转的
T* head = reinterpret_cast<T*>(new char[512]);

Union

struct ST{
    union{ 
        char a;     // 整体是8字节,前1个字节 是a
        int b;      // 前4个字节为b
        double c;   // 前8个字节是c
    }; /* 虽然是3个成员,但其实他们是同一块内存  该内存大小为:max(1, max(4, 8)) = 8
任意时刻,该union只能存储一个成员的值,其他成员就变成"未定义的状态"	*/
t.a = 'c';  // 此时,只有t.a是正确的,t.b和t.c都是错误的
t.b = 456;  // 此时,只有t.b是正确的,t.a和t.c都是错误的
t.c = 3.33333;   // 同理,此时只有t.c是正确的

Begin()/End()

/*	他只能对数组[]操作,beg得到首元素的指针,ed得到某元素的下一个位置的指针
比如有:  T arr[] = {....}; 
如何得到arr的个数呢?      1,sizeof(arr) / sizeof(T)
2, C++11提供的begin函数   siz = end(arr) - begin(arr)			*/

int A[] = {1 , 2 , 3};
for(int* it = ::std::begin(A); it != ::std::end(A); ++it)
    cout << *it << endl;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值