类的规范写法
(1)数据一定要放在private中
(2)参数尽量以reference来传,加不加const看状况
(3)返回值尽量以reference来传
(4)class的本体body里面的函数应该加const的都要加上,不加使用时可能报错
(5)构造函数的特殊语法initializer_list(初始化列表)要尽量使用它
头文件与类的声明
1、count<< “i=”<<i<<end1;相当于把东西都往左边丢,丢给count,让他输出出来
2、对头文件complex.h,首先要写出防卫式声明(guard):
#ifndef __COMPLEX__
#define __COMPLEX__
(内容)
#endif
只有 __COMPLEX__被定义过才执行下面的内容,防止头文件被包含后,里面的内容被重复定义。
3、内容的顺序:
(1)forward declarations(前置声明) 首先声明有什么东西
(2)class declarations(类-声明(本体))如何对类内部怎么样的描述,也可以称为对其的定义
有些函数在此直接定义,另一些在body之外定义。包括类的变量和变量类型。
(3)class definition(类-定义)包含函数的函数体定义
4、可先不指定类的变量的类型,用一个(模板)代替指代:如template<typename T>
5、inline函数:类似宏,有它的特性却没有它的缺点,快又好。
(1)函数在class body内定义完成会自动称为inline
(2)若在body之外,前缀要加上inline
6、class的声明(定义)分为public和private。
class complex
{
public:
。。。
private:
。。。
}
(1)public:可以为所有人可见
(2)private:只能由类自己可见。
构造函数(类特有的初始化列表)
7、构造函数位于public中,创建对象时自动调用:
class complex
public:
complex(double r = 0, double i = 0)
:re (r), im (i)
{ }
private:
double re, im;
(1)与class同名:complex
(2)参数为默认参数(用该类创建对象时,传的参数):double r ,double i
(3)可设置默认参数值(当未指定初值时):r = 0, i =0
(4)没有返回值类型,也不需要
(5)构造函数特有的初始列和初始行,用于将值传递给变量re和im:: re (r), im (i)
8、变量的赋值通常分为两阶段:初始化和赋值,类中若是在{ }函数体内才将初值给变量,相当于放弃了初始化这一阶段。
9、重载:构造函数可以有很多个。函数的实际名称取决于编译器:(可以重名,但类型等要不同)
double real ( ) const{ return re; } -> ?real@Complex@@QBENXZ (编译后的实际名称)
void real ( double r) { re = r ; } -> ?real@Complex@@QAENABN@Z(编译后的实际名称)
10、构造函数有时也放在private中,例如在设计模式中的Singleton,会有一个自己。当外界需要时,需要通过一个函数去取里面自己的那一份。
参数传递与返回值:
参数传递:(1)pass by value vs (2)pass by reference(to const)
11、class内的函数分为会改变数据和不会改变数据内容两种。
12、在定义函数时,确定不会改变数据内容的函数,一定要加上const来修饰它。(正规化)能够保证不会出错。
13、引用&在底部相当于就是指针,传引用就是传指针
14、参数传递尽量都传引用(占用空间少,够快)
返回值传递:(1)pass by value vs (2)pass by reference(to const)
15、返回值传递尽量都传引用(占用空间少,够快)
16、友元(friend):自由取得friend的private成员,不需要通过函数去拿。
17、相同class的各个objects互为friends(友元)
18、当是函数内创建的变量时,返回值不能是引用reference,此时的变量在函数结束后,会被删除,当返回值传回去时,数据已经不存在了。
操作符重载1,成员函数:
19、C++中操作符的定义被改变,相当于函数。操作符重载需要加上operator:
例如:complex& operator += (const complex&) ;
20、任何二元操作符(有两个操作数的) 作用于左数,并且左数有定义这个操作符的函数,编译器就会去调用该函数。该函数需要得到左数和右数。
21、任何的成员函数都有一个隐藏的东西:this (pointer指针),指向调用这个函数的调用者
22、在使用函数的时候,传递者无需知道接收者是以reference(任意) 形式接收。(即接收的类型是reference,传递给他的数值类型并不限制为reference)
23、c3+=c2+=c1; 连续操作符时,+=函数返回值不能是void,在c2+=c1后还要有值赋给c3
24、设计操作符函数时,当左边(this)是存在的,右边就可以传引用给他。
25、成员函数的声明必须在类的定义内部进行,但定义可以在类的内部或外部。在外部定义时,要使用类名限定符’ : : ’。如void MyClasss : : MyFunction( ){ }
操作符重载2,非成员函数(全局函数):外部定义
26、对于+的三种可能用法,对应开发了三个函数(复数的实虚部相加、实部相加、虚部相加)
27、该函数为全局、全域函数,没有this pointer
28、成员函数操作符重载将操作符作为类的一部分定义,可以直接访问类的成员,但左侧操作数受限于该类对象。非成员函数操作符重载是独立于类的全局函数,使用更灵活,但不能直接访问类的私有成员,且左侧操作数可以是非类类型。
29、临时对象(temp object)typename();即类名后面直接跟括号(),没有对象名,创建的对象在下一行会消失掉。
MyClass(10); 没有对象名 MyClass obj1(20);有对象名
30、函数内的临时对象,不能return by reference,只能by value,因为该对象下一行就会消失。
31、-减号和负号的区分,主要看参数的个数是否为1,只有一个参数说明为负号。
32、类似<<操作符只能写为全局的函数,因为类似复数可能10年前该函数定义时,并未出现
33、操作符重载一定是作用在左边的操作数上。cout<<c1;中c1只能位于右边,所以<<无法写成成员函数,即无法作用于对象c1,只能作用于cout,所以只能写为非成员函数。(全局函数)
拷贝构造:
有类 String class
34、两种都是拷贝:String s3(s1)和s3 = s2。
35、当类中的拷贝涉及到指针时,不能用编译器默认写好的(拷贝构造、拷贝赋值等),会有些问题,需要自己写这两个函数。
36、Big Three,三种特殊函数:当类中有指针时,必须要写
public:
String(const char* cstr = 0);
String(const String& str); 拷贝构造函数
String& operator=(const String& str); 拷贝赋值函数
~String( ); 析构函数
(1)上面的前三种都是构造函数,用于不同的情况。都还只是接口,没有定义。
(2)析构函数:用于释放掉已经用完的内存
如inline String::~String()
{
delete[ ] m_data;
}
37、当类有指针成员时,若使用编译器默认的拷贝构造copy ctor和拷贝赋值copy op=
直接把b=a
(1)导致得到的值不是我们想要的。应该是把指向的内存块内的内容进行赋值
(2)两个指针同时指向了一个字符串(浅拷贝),非常危险,一个内存块应该只有一个名字(一个指针),这样导致alias有别名。
(3)并且造成了内存泄漏memory leak,这块内存相当于丢失了
(4)编译器的拷贝,相当于直接把两个里的内容,一个位一个位的直接拷贝。
38、copy ctor(拷贝构造函数)
inline String : : String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
(1)函数名与类名相同:构造函数
(2)const String& str:传递的参数为该类型的对象object的引用&
(3)new char:深拷贝,创建空间,将另一个对象的内容拷贝进去。
(4)str.m_data:直接取另一个object的private data。(兄弟之间互为friend)
(5)strcpy(m_data, str.m_data):将对象的内容复制。
39、copy assignment operator(拷贝赋值函数):本身已存在,有自己的空间
inline String& String : : operator=(const String& str)
{
if (this == &str) 检测自我赋值(self assignment)
return *this;
a、delete[ ] m_data;
b、m_data = new char[ strlen(str.m_data) + 1 ];
c、strcpy(m_data, str.m_data);
return *this;
}
a、b、c三步骤
(1)首先删除自身的内存空间:delete[ ] m_data;
(2)创建和赋值内容相同大小的空间:m_data = new char[ strlen(str.m_data) + 1 ];
(3)完成内容复制:strcpy(m_data, str.m_data);
(4)自我检测模块:用于防止在拷贝赋值自己时,由于删除自己的内存空间,导致错误。
(5)&的意义:当出现在type name后面String& str时为引用。当出现在object前面为取地址this == &str,取的是str的地址,得到的是一根指针。
40、cout对字符串的输出只需要字符串的首地址,即可自动打印输出整个字符串。
堆、栈与内存管理
41、栈(stack):
(1)存在于某作用域的一块内存空间;例如当调用函数时,函数本身就会形成一个stack来放置它所接收的参数和返回地址。
(2)在函数本体(function body)内声明的任何变量,所使用的内存块都来自上述的stack
42、堆(heap):或称system heap,是指由操作系统提供的一块global内存空间,程序可动态分配,从中获得若干区块。、
43、堆和栈的创建例子:
class Complex{...};
...
{
Complex c1(1,2); c1所占的空间来自stack(栈)。
Complex* p = new Complex(3) Complex(3)是个临时对象,所占用的空间是以new自
} heap (堆)动态分配的,并由p指向。
44、static object:其生命在作用域(scope)结束之后仍然存在,直到整个程序结束。一般在函数体内的变量前加上static。
45、global objects:其生命在整个程序结束之后才结束。也可视为static object的一种,作用域为整个程序。在函数体外创建的变量,自动为global objects。
46、heap objects:生命在它被deleted之后结束。Complex* p = new Complex; P所指的便是heap object,用new创建。内存泄漏发生情况:
class Complex{...};
…
{
complex* p = new Complex;
}
当作用域结束时,指针p的生命结束了,但此时p所指的heap object仍然存在。
47、new:先分配memory,再调用ctor(构造函数)
Complex *pc = new Complex(1,2);
编译器转化为
//operator new实质在其内部调用malloc(n)
(1)void* mem = operator new(sizeof(Complex)); //分配内存(得到void型指针指向该块内存)
(2)pc = static_cast<Complex*>(mem); //转型(将void转为类的类型Complex)
(3)pc->Complex: :Complex(1, 2) //调用构造函数
//实质上为Complex: :Complex(pc, 1, 2);构造函数作用于pc指针
在pc指向的内存空间中创建对象。
48、delete:先调用dtor(析构函数),再释放memory
String* ps = new String(“Hello”);
…
delete ps
编译器转化为
(1)String: :~String(ps);//析构函数(在析构函数中的delete[ ]把动态分配存放数值的内存杀掉)
(2)operator delete(ps);//释放内存,此时才将字符串本身(就是个指针杀掉)
序号①~String()析构函数中用delete释放掉存放数据的内存。
49、当new创建时用了[ ],delete时也要对应用上[ ],这种叫做array new和array delete
否则,有的数组元素指向的内存块可能删除不完全(在数组元素为指针时,只将指针占用的内存块释放完,但是指针指向的数据内存并未释放完全)
50、动态分配所得的内存块
(1)Complex在VC,调式模式时,用new动态分配内存的情况,8+(32+4)+(4*2)
8:复数占8个字节
32+4:Complex上面的8个字节,重要,用于完成内存分配。+4为Complex的下面一个格子
4*2:上下两个红色的(cookie),表示字节的数量,0x40为64字节,0x41中1表示该数据是要给出去的
52->64:因为VC所给的内存块都需要是16个字节的倍数,所以补充了Complex下面的3个绿色小块。
上面的每一格代表着4个字节。
(2)不进入调试模式,拿掉灰色的重新计算。8+(4*2)
8+(4*2):8为复数的字节,4*2为cookie。
扩展补充:类模板、函数模板、及其他
补充static
51、使用static创建的成员函数,没有this pointer,输入的参数也只能是静态变量static。
52、对static静态对象的创建,要在类外具体定义,类内的只是声明
class Account{
public:
static double m_rate; //声明(位置在类内)
static void set_rate(conse double& x) { m_rate = x }
};
double Account: :m_rate = 8.0 //具体定义
53、调用static函数的方式有两种:
int main(){
Account : : set_rate(5.0); // (1)通过class name调用
Account a;
a.set_rate(7.0); // (2)通过object调用,但由于是静态函数,a的地址不会放
入函数中(没有this)
}
补充:function template
54、函数模板(function template):不指定函数的参数(对象)及返回值类型(不指定类的类型)。
(1)当执行操作符运算时,编译器会对函数模板进行引数推导,由传入的参数得知类,再去这个类找到相应的操作符定义完成运算。
template <class T> 格式为:T(类名) 函数名(T 参数名,T 参数名)
inline
const T& min(const T& a, const T& b)
{
return b < a ? b : a
}
(2)具体设计比大小的责任,不在这个函数身上,而在使用这个函数对象的Class身上。
(3)这种形式也被称作“算法”。C++标准库中的算法都是function template的形式
补充:namespace
55、namespace:将写好的东西包装在一个namespace中,避免和其他人写的东西冲突。指定namespace的名字:namespace std { … }
三种使用方式
(1)打开全部namespace,后续直接使用即可:using namespacest
(2)打开单行的namespace:using std : : cout <<...;使用时 cout << ...;
(3)前面没有先打开,后续每次使用都要加上namespace的名字:std: :cin<<;
复合、委托与继承
56、Composition复合:类中包含了另一个类。并且可以使用这个类的定义中已经写好的函数,把这个函数作为自己的函数。
57、容器:拥有别的那一个类。容纳和拥有了另一个东西。
58、Composite Adapter复合适配器:内部包含被适配类的一个对象,并通过委托调用该对象来实现接口的适配。也就是使用这个类的定义中已经写好的函数,把这些函数作为自己的函数。
59、复合的表示
(1)queue类中包含了deque类型的对象c
(2)deque类中又包含了Itr类型的对象start和finish
(3)对象start和finish各自包含了四个指针
60、复合关系下的构造和析构
(1)构造由内而外
Container的构造函数首先调用Component的default构造函数(因为不知道调用哪一个),然后才执行自己。
Container : : Container(...) : Component() { ... } ; // Component由编译器自动调用,不用写
(2)析构由外而内(先拆除最外层)
Container的析构函数首先执行自己,然后才调用Component的析构函数
Container : : ~Container(...) : { ... ~Component() } ;
(3)若是默认的Component不是想要调用的,则自己写出要调用的Component()及其中的参数
61、Delegation委托:一个类中包含了指向另一个类的指针。该类为handle接口声明,指向的类为body具体功能实现。(也可认为用指针的Composition)
62、Composition有了一个外部的就有一个内部的,生命是一起出现的。而委托生命存在的时间不一样,可能先有了自己,因为有其他人的指针,当需要另一个的时候才去创建它。
63、左边(该类)只负责声明,实现通过指针指向右边(另一个类),在另一个类中具体实现,pimpl。(编译防火墙)客户只需要看到声明左边,当需要实现改变时,改变右边。
64、Inheritance(继承):有三种,继承父类的东西。表示is-a关系(is-a是一种(和父类))
:public _List_node_base; 继承父类_List_node_base
65、C++中Class和struct非常像,将struct口语上称为class
66、继承关系下的构造和析构(实际上使用的顺序编译器已经帮忙完成了):
子类(Derived)的对象有父类(Base)的成分在里面。其中父类的析构函数(dtor)必须是virtual,否则会出现undefined behavior
(1)构造由内而外
子类的构造函数首先调用父类的default构造函数,才执行自己的构造函数
Derived : : Derived(...) : Base() {...};
(2)析构由外而内:
子类的析构函数首先执行自己的,然后才调用父类的析构函数
Derived : : ~Derived(...) : { ... ~Base() };
虚函数与多态
67、继承与虚函数:父类中,成员函数共有三种选择
(1)non-virtual非虚函数:不希望子类(derived class)重新定义它
(2)virtual虚函数:希望子类重新定义它,且对它已有默认定义
(3)pure virtual纯虚函数:希望子类一定要重新定义它,对它没有默认定义
68、在父类的成员函数前加上virtual就变成了虚函数
69、作为父类,成员函数有三种选择
Shape这个类可以作为各种形状的父类。
(1)纯虚函数:因为没有shape这种形状,必须要重新画(没有默认值,值为0)。
(2)虚函数:希望每种形状(三角形、四边形)都有自己相应的错误信息,但也可以用默认值。
(3)非虚函数:不管形状是什么,都给这个形状一个ID,ID是不需要变的。
70、Template Method:在父类中把通用的能写的方法都写了,剩下需要子类具体定义完成,设置为虚函数,让在子类中写。
子类中方法的调用
(1)首先创建一个子类对象
(2)通过子类对象调用父类的函数,调用动作的全名为:CDocument: :OnFileOpen(&myDoc);
谁调用的那个谁(myDoc)就会变为this pointer,地址就会被放入成为隐藏的参数&myDoc
(3)&myDoc就被传入OnFileOpen,通用的函数继续执行。
(4)当碰到虚函数Serialize,因为是通过this(myDod)调用,就会回到子类中定义的虚函数
(5)执行完后,继续执行通用的函数,直至程序结束。
71、Inheritance+Composition关系下的构造和析构
子类中既有父类的成分(part)又有Component在里头,子类同时构造。(右边为在内存中的关系)
子类中含有父类的成分(part),父类中又含有Component,则首先构造Component(最里面),然后构造父类Base。
72、Delegation(委托)+Inheritance(继承):设计模式Observer
当同一个视窗同时有好几个人观察,每个人从相同或不同的角度,下面的例子是不同的角度,当数据变化时,几个视窗都会发生变化
设计的结构:设计了一个subject包含数据,其中有容器(指针Delegation)指向了Oberver观察者(父类),观察者会有几种不同的角度,每个角度对应着一个子类。
(1)左手边容器存放m_views指针指向Observer
(2)提供注册和注销的动作:attach用以注册观察者,附着Oberver,把它放到容器里(注销这里没有写出)
(3)notify:遍历容器中的所有观察者,通知观察者要更新数据update
(3)Observer中对应写出虚函数update
73、Delegation(委托)+Inheritance(继承):设计模式Composite
(1)作为Composite应该是个容器,可以容纳很多Primitive(文件),同时也要容纳右边的(自己)
(2)容器中放的东西大小要相同,当要放不同东西时,则存放指针。
74、当还不知道未来的子类的时候(也不知道子类的名字):设计模式Prototype
(1)-的表示private,+(默认的,可不写)的表示public,#protected保护区域
(2)每一个子类有一个自己,然后把这个自己(原型)挂到上面去,然后通过原型调用clone制造自己的一个副本