zoukankan      html  css  js  c++  java
  • 面向对象高级编程补充内容

    C++面向对象程序设计_Part1
    C++面向对象程序设计_Part2
    作者:方阳,笔记地址:https://github.com/FangYang970206/Cpp-Notes
    课程除了将面向对象以外,还补充了一些其他内容,在这里进行介绍, 课程中有一部分涉及C++11, 我没有写出来,放到C++11部分专门去讨论, 另外, 下面有一些部分来自于hujiese的笔记, 我进行了一些补充, 减小工作量.

    图片无法查看请到GitHub上浏览,点击链接

    static与this

    1557714511998

    没有static类型的变量或者函数时,complex类的成员函数是只有一份代码,通过this指针(谁调用谁的地址就是this)来调用不同的对象,this指针是一个默认会传入的参数,比如在调用real这个函数时,直接返回的是re,但编译器会在前面加上this->,用来指明是调用对象的re。而加上static,static所声明的变量和函数与this是没有关系的,无法通过this调用static类型的变量和函数,那有什么用呢,比如,在银行存储不同用户的存款时,c1,c2和c3代表的是用户自身的数据,但有的数据跟用户的数据没有关系,比如利率,这时候就可以将利率声明为static,所有对象都可以访问(静态作用域为全局)。如下图:

    1557715259428

    静态的数据在类中声明后,要在外面进行定义,静态set_rate函数操作静态m_rate(静态变量一般通过静态函数进行修改),调用静态set_rate函数,有两种方法,如上所示,在通过object调用时,不会传入this指针,这点要注意。

    构造函数放入private(单例模式)

    1557715824622

    这个在part1提过,侯捷老师更加仔细地进行了讲解,这是一种有名的设计模式(单例模式),只能创建一个对象,由于构造函数放入private中,所以不能通过对象名直接创建,具体做法时在private里面创建一个static类型的对象,作用全局,通过public中的静态函数进行访问。然后就可以调用其他非静态的成员函数了。以下是上例更好的写法:

    1557716265949

    当没有调用getInstance,静态对象不会创建,只有调用getInstance时,才会有静态对象,这样写法更好。

    cout

    1557716414284

    cout为什么能够访问那么多不同类型的变量,这里给出了原因,图像右上角可以直到cout是ostream类的一种,与代码中用ostream吻合。然后看看ostream的定义,可以看到ostream里面重构了好多<<操作符,各种各样的类型,这也就是为什么cout可以打印各种不同类型的数据的原因。

    类模板

    1557716746695

    模板类是由于考虑到可能会存放不同类型的数据和函数,所以不先定义类型,例如,当上面出现complex, 编译器会将模板类的代码中的T替换为double,创造新的一份代码,遇到complex时,会将模板类的代码中的T替换为int,在创建一份代码。可以看到模板类的代码是一份多用,很方便。

    函数模板

    1557717032223

    函数模板与类模板差不多,不同的是编译器会对function进行实参推导,不需要指明类型,只要比较大小的对象重构了<操作符,这样设计是很合理的,比大小的函数都是那么写,而设计stone的人,它的责任就要重载<操作符,把责任分开是很好的。

    成员模板

    1560412186484

    成员模板在泛型编程里用得较多,为了有更好的可扩展性,以上图为例,T1往往是U1的基类,T2往往是U2的基类,可以看下面这个例子:

    1560413833493

    通过这种方法,只要传入的U1和U2的父类或者祖类是T1和T2,那么通过这样的方式可以实现继承和多态的巧妙利用,但反之就不行了。这样的方式在STL中用得很多:

    1560413886633

    namespace

    1557717432386

    上面的是使用命名空间的三种写法。namespace可以防止冲突,比如在开发一个项目时,不同的人可能会命名相同的函数以及相同的参数,这时候需要不同的人给各自的代码设立不同命名空间,这样即使函数重名也不必担心了。

    写测试案例可以使用namespace,防止命名冲突,很方便。

    1560404974745

    转换函数

    1560404584181

    转换函数是将对象类型转为另一个对象类型,如上面的Fraction类,它的数据为分子和分母,当Fraction类与double进行操作时,会自动调用double类型的转换函数(转换函数是operator开头,后面接要转换的类型然后加括号)。以下面的事例为例,编译器在分析double d = 4 + f过程中判断4为整数,然后继续判断f,观察到f提供了double()函数,然后会对f进行double()操作,计算得到0.6,再与4相加,最后得到double类型的4.6。(转换函数一般不会改变数据,通常要加const)

    explicit与隐式转换

    1560405283461

    上图中定义了一个类,叫Fraction,类里面重载了“+”运算符,在f+4操作过程中,“4”被编译器隐式转换(通过构造函数)为Fraction对象,然后通过Fraction重载的“+”运算符参与运算。

    1560405362848

    如上图所示,在Fraction中增加了double()函数,将Fraction的两个成员变量进行除法运算,然后强制转化为double类型并返回结果,在f+4重载过程中编译器将报错,可以做出如下分析:

    1、首先4被隐式转化(构造函数)为Fraction对象,然后通过重载的“+”运算符与“f”进行运算;

    2、首先f被转化(转换函数)为double,然后两个double进行相加;

    两种路径都可行,编译不知道执行哪一种,产生ambiguous error. 下面是解决方案.

    1560406299464

    通过在构造函数前面加上explicit, 就是告诉编译器, 不要在将int类型转成Fraction, 只能通过显式地进行构造.那么再执行那条语句是, 就是执行两个double相加, 但是由于double转Fraction的转换函数没有实现, 所以报错.

    1560406833869

    上面是STL的例子, 对于vector的operator[], 应该返回的是bool, 但是确实返回的是另一种类型, 那么这种类型应该有转换为bool的转换函数, 果不其然.

    pointer-like classes ---- 智能指针和迭代器

    c++类构建出来, 所产生出来的对象可能像一个指针, 或者一个函数, 这一节是讲述像指针.

    1560407154869

    上述例子是智能指针, 智能指针是对原始的指针进行了包装, 赋予更多的功能, 其中主要是对内存进行自动管理 防止内存泄漏. 由上图可以看出, 智能指针在语法上有三个很关键的地方,第一个是保存的外部指针,对应于上图的T* px,这个指针将代替传入指针进行相关传入指针的操作;第二个是重载“*”运算符,解引用,返回一个指针所指向的对象;第三个是重载“->”运算符,返回一个指针,对应于上图就是px。这里的重载"->"运算符很有意思, 返回的是px, sp->method就变成了pxmethod, 实则不是, 还是px->method, 原因是操作符"->"比较不一样, 它会一直以"->"指下去, 直到指到对象的方法或数据.

    迭代器也是一种智能指针,这里也存在上面提到的智能指针的三个要素,分别对应于下图的红色字体和黄色标注部分:

    1560410852393

    下面将仔细分析迭代器重载的“*”和“->”重载符:

    1560411003846

    创建一个list迭代器对象,list::iterator ite;这里的list用于保存Foo对象,也就是list模板定义里的class T,operator()返回的是一个(node).data对象,node是__link_type类型,然而__link_type又是__list_node类型,这里的T是Foo,所以node是__list_node类型,所以(node).data得到的是Foo类型的一个对象,而&(operator())最终得到的是该Foo对象的一个地址,即返回Foo* 类型的一个指针。

    function-like classes ---- 仿函数

    1560411429029

    从上图可以看到,每个仿函数都是某个类重载“()”运算符,然后变成了“仿函数”,实质还是一个类,但看起来具有函数的属性。每个仿函数其实在背后都集成了一个奇怪的类,如下图所示,这个类不用程序员手动显式声明。

    1560411583591

    标准库中的仿函数也同样继承了一个奇怪的类:

    1560411683785

    这个类的内容如下图所示,只是声明了一些东西,里面没有实际的变量或者函数,具体的内容将在STL中讨论。

    1560411846337

    模板特化

    1560419363053

    模板特化指的是模板中指定特定的数据类型, 这样在调用指定类型的模板时, 用的是特化的版本(如上面的hash, hash, hash), 而不是泛化的版本(原始版本hash).

    模板偏特化

    模板特化也有程度之分,可以部分类型指定,称之为偏特化, 偏特化有两种, 一种是个数的偏, 一种是范围的偏.

    1560419673831

    1560419917708

    第一个图是个数上的偏, 绑定部分类型, 第二个图是范围上的偏, 原始版本C是可以指向任何类型, 下面的C特指指针类型, obj1是class C构造的, obj2是 class C<T*>构造的.

    reference

    reference可以看做是某个被引用变量的别名。

    1560422076723

    1560422092804

    1560422107865

    new 和 delete

    在面向对象程序设计_part1谈到了一些new和delete的原理,重复的内容不再讲述,补充内容讲述了重载new和delete。这里有篇很不错的博客,也可以进行参考:https://www.jianshu.com/p/d2d5cdd7aa1d

    1560761309230

    上面是重载new和delete的范例,和运算符重载类似,先是operator,然后后面介绍new,new和delete有两种格式,基于单个的new,以及基于array的new,所以上面有两种写法。有中括号的是基于array的。重载new的函数参数是根据要分配的对象的size推导的。重载delete的函数参数则是被释放对象的地址。上图中的重载是直接重载全局的new和delete,影响很大,出现内存泄漏很危险。

    1560761942136

    上图中则是重载单个成员的new和delete操作符,更加适用。相应地,左边执行new和delete运算,将会映射到右边的步骤。

    1560762376012

    相应地,也有基于array的重载对象的。上面的分配N个Foo对象,可以看到sizeof(Foo)*N + 4,这里的4存放的是N这个数,这样到时候就知道要调用多少次构造函数和析构函数了。

    1560773568977

    下面是一个更具体地事例,对Foo类重载了new和delete操作符,Foo有三个数据,12个字节,上图中主要要注意重构类的new和delete要怎么写,另外,在new前面加上::, 代表的是强制使用全局的new和delete操作符。

    1560773903634

    调用左边的语句,中间是输出,上面的是无虚函数,下面是有虚函数,对于基于array的new和delete,会多出四个字节,用来保存长度信息,构造是从下到上,析构是从下到上。

    1560774047068

    对于全局的,过程类似。

    1560774481410

    允许重载placement new和delete。下面是事例:

    1560774759364

    定义了四个operator new函数。前两个标准库已提供。

    1560775017510

    对应上页幻灯片的operator delete操作。

    1560775583436

    标准库中的例子,多分配内存用来进行reference count。

    面向对象部分到此结束。

  • 相关阅读:
    Objective-C代码规范
    Http中Get/Post请求区别
    使用Vitamio打造自己的Android万能播放器
    Vitamio
    图片瀑布流
    TCP与UDP
    SQLite基本操作总结
    IOS文件操作的两种方式:NSFileManager操作和流操作
    JSON和XML
    一些iOS常用的第三方库和控件及第三方框架还有动画
  • 原文地址:https://www.cnblogs.com/fydeblog/p/11095850.html
Copyright © 2011-2022 走看看