侯杰C++ 面向对象高级开发—学习心得和笔记

类的规范写法

(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)构造函数特有的初始列和初始行,用于将值传递给变量reim: 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制造自己的一个副本

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值