综述

Custom Classes

用户定义类:通过类定义创建

  • 每个类通过字典对象__dict__实现独立的命名空间

    • 类属性引用被转化为在此字典中查找
    • 其中未发现属性名时,继续在基类中查找
      • 基类查找使用C3方法解析顺序,即MRO列表
    • 也存在一些钩子对象允许其他定位属性的方式
  • 当类属性引用yield类方法对象时,其将转化为__self__ 属性为当前类对象的实例方法对象

  • 当类属性引用yield静态方法对象时,其将转换为静态方法 对象所封装的对象

  • 类属性复制会更新类字典,不会更新基类字典

  • 类对象可被调用产生类实例

特殊属性

  • __bases__:包含基类的元组,依在基类列表中出现的顺序

Class Instances

类实例:通过调用类对象创建

  • 每个类实例都有一个通过字典对象__dict__实现的独立命名 空间

    • 属性引用首先在此字典中查找
    • 其中未发现属性名时,继续在对应类属性中查找

      • 用户定义函数对象:其会被转化为实例方法对象
        • __self__属性即为该实例
      • 静态方法、类方法对象:同样会被转化
      • 描述器属性有特殊处理,实际存放在类__dict__中 对象不同
    • 若未找到类属性,对象对应类具有__getattr__()方法, 将调用该方法
  • 属性赋值、删除会更新实例字典,不会更新对应类字典

    • 若类具有__setattr____delattr__方法,将调用方法 而不是直接更更新对应实例字典

特殊属性

  • __class__:实例对应类

Classes

类:类对象通常作为“工厂”创建自身实例

  • __doc__:类的文档字符串
    • 类定义第一条语句、且须为字符串字面值
    • 没有则为None
    • 不会被子类继承

Class Instances

类实例:在所属类中定义__call__()方法即成为可调用对象

属性

属性访问.

  • A.attr被解释为type(A)中__getattribute__(A, attr)

    • .的行为由python解释器定义
    • type(A)中__getattribute__用于强调不会再从 type(type(A))继续获取调用__getattibute__
  • 定义在类命名空间中函数是为实例定义

    • 要为类定义方法应该自定义元类
  • 测试代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Meta(type):
    def __getattribute__(self, attr):
    print("--Meta--", attr)
    return super().attr

    class D(metaclass=Meta):
    def __getattribute__(self, attr):
    print("--Class--", attr)
    return super().attr
  • __getattribute__函数说明参见 cs_python/py3ref/special_methods

属性访问控制

  • python没有没有属性访问控制,不依赖语言特性封装数据,而是 遵循一定属性、方法命名规约达到效果
  • __开头属性:属性名称会被修改

    • 防止被派生类继承,此类属性无法通过继承覆盖
    • 即若清楚代码会涉及子类,且应该在子类中隐藏起来,考虑 使用双下划线开头
    • 通常是在属性名称前添加类名标记_cls
    • 但同时以__结尾属性名称不会被修改
  • _开头属性:应被视为私有属性,不应被外部访问

    • python无法真正防止访问内部名称,但是这样会导致脆弱的 代码
    • 此约定同样适用于模块名、模块级别函数

      • 默认情况下,通配符*不会导入模块私有属性,除非 在配置有__all__属性
      • 导入参见cs_python/py3ref/simple_stmt#todo
  • _结尾:避免定义的变量和保留关键字冲突

特殊属性

  • __dict__:命名空间包含的属性
  • __doc__:文档字符串
    • 第一条语句、且须为字符串字面值
    • 没有则为None
    • 不会被子类继承
  • __name__:名称
  • __qualname__qualified name,完整限定名称
    • 以点号分隔的名称
    • 显示模块全局作用域到模块中某个定义类、函数、方法的 路径
  • __module__:所属模块名称
    • 没有则为None

描述器属性

  • 描述器协议参见cs_python/py3ref/special_methods
  • 实例/类/静态方法:参见cs_python/py3ref/dm_gfuncs

@property

@property装饰器:为类的属性增加处理逻辑,如:类型检查、 合法性验证

  • property属性和普通属性实现迥异,但使用类似

    • property属性就是绑定有这些处理逻辑函数的类实例
    • 访问、赋值、解除绑定时会自动触发gettersetterdeleter处理逻辑
  • property属性(或者说有效描述器)为类属性

    • 一般需要通过在实例、或描述器命名空间 instance.__dict__中存储数据,以实现对实例操作逻辑 独立
    • 也可以实时计算属性值,此时无需为实例分别存储数据
    • 初始化时,不应该直接设置底层数据属性,会绕过setter 的参数检查
  • 过度使用@property时会降低代码可读性、效率,使用 get/set方法可能有更好的兼容性

代码实现

  • 代码是C实现,这里是python模拟,和help结果不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"

def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc

def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)

def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)

def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)

def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
# 返回描述器,可省略

def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
# 返回更新`fset`的描述器,同名所以覆盖前者

def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
  • @property是描述器类,接受方法返回同名资料描述器

创建property属性

  • @property[.getter]装饰getter-like方法得到同名资料 描述器

  • 返回描述器包含.setter().deleter()方法/装饰器进一步 完善描述器

    • @method.setter:为描述器完善赋值处理逻辑
    • @method.deleter:为描述器完善del处理逻辑
  • 可以直接使用已有类中函数创建property类实例,得到 property属性(描述器)

  • 派生类中property属性覆盖

    • 派生类中直接使用@property创建同名属性会覆盖基类 中property属性

      • 只有显式声明的处理逻辑被设置
      • 基类中逻辑位于基类相应同名property属性,不会 被“隐式继承”
    • @<basecls>.<method>.getter/setter/deleter单独覆盖 property属性方法

      • 但是basecls硬编码方式,必须知道定义 property属性的具体类(或其子类)
  • 描述器协议、实现参见cs_python/py3ref/special_methods

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Student(object):

def __init__(self, value):
self.birth = value
# 使用`self.birth`而不是`self._birth`,保证即使
# 实在初始化时仍然进行参数检查

@property
# 将一个getter-like方法变为属性
# `@property`同时会创建装饰器`@method.setter`
def birth(self):
return self._birth

@birth.setter
# `@property`对应,将setter-like方法变为属性
def birth(self, value):
if not instance(value, int):
raise ValueError("birth must be an integer")
if value < 1900 or value > 2020:
raise ValueError("birth must between 1900 ~ 2020")
self._birth = value

@birth.deleter
# 同`@property`对应,在`del`时调用
def birth(self):
del(self._age)
del(self._birth)

@property
# 只设置`@property`而没有设置对应`@birth.setter`
# 这样`birth`就成了只读属性
def age(self):
return 2018 - self._birth

def get_first_name(self):
return self._first_name

def set_first_name(self):
if not instance(value, str):
raise TypeError("expected a string")
self._first_name = value

def del_first_name(self):
raise AttributeError("can't delete attribute")

name = property(get_first_name,
set_first_name,
del_first_name)
# 在已有getter-like、setter-like方法上创建property
# 注意:这里就是应该定义类属性,本身使用`@property`
# 装饰器也是相当于创建类属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Person:
def __init__(self, name):
self.name = name

@property
def name(self):
return self._name

@name.setter
def name(self, value):
if not instance(value, str):
raise TypeError("expected a string")
self._name = value

@name.deleter
def name(self):
raise AttributeError("can't delete attribute")

class SubPersonAll(Person):
# 这个类继承、扩展了`name`属性的所有功能
@property
def name(self):
print("getting name")
return super().name

@name.setter
def name(self, value):
print("Setting name to", value)
super(SubPerson, SubPerson).name.__set__(self, value)
# 使用`super(SubPerson, SubPerson)`调用父类实现
# 将控制权传递给`.name.__set__`方法,委托给父类
# 中定义的setter方法

@name.deleter
def name(self):
print("deleting name")
super(SubPerson, SubPerson).name.__delete__(self)

class SubPersonPart(Person):
# 仅修改`name`属性的某个方法
# 需要知道定义`name`属性的基类,否则重新定义property属性
# 的所有方法,并使用`super`将控制权转移给父类
@Person.name.getter
# 使用硬编码的`Person`类名,这样会把之前已经定义的
# property属性方法复制过来,而对应的`getter`、
# `setter`、`deleter`方法被替换
# 这里如果直接使用`@property`装饰,那么`setter`、
# `deleter`方法将会消失
def name(self):
print("getting name")
return super().name

类继承

  • 类继承会获得基类的所有方法
    • 类里面的方法其实真的不是给类使用的,而是给实例使用
    • 类自身使用的方法是元类中的方法

Method Resolution Order

MRO/方法解析顺序列表:包含当前类所有超类的线性顺序表

  • MRO列表顺序通过C3线性化算法实现,对每个类按以下规则合并 所有父类的MRO列表

    • 子类先于父类检查
    • 多个父类根据其在列表中的顺序被检查
    • 若对下一个类存在多个合法的选择,选择第一个父类
  • 为了实现继承,python会在MRO列表上从左到右开始查找超类, 直到第一个匹配这个属性的类为止

  • 可以通过__mro__mro()访问

super

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class super:
super()
# 等同于:`super(__class__, <first_argument>)`
# `<first_argument>`常常就是`self`
super(type)
# 返回:未绑定super对象,需要`__get__`绑定
super(type, obj)
# 返回:已绑定super对象,要求`isinstance(obj,type)`
super(type, type2)
# 返回:已绑定super对象,要求`issubclass(type2, type)`
# 此时调用方法返回是函数,不是绑定方法,不会默认传入
# `type2`作为首个参数

def __get__(self, obj, type=None):


def super(cls, inst/subcls):
mro = inst.__class__.mro()
mro = subcls.mro()
return mro[mro.index(cls) + 1]
  • 参数

    • 第一个参数:在MRO列表中定位类搜索起点(不包括)
    • 第二个参数:提供MRO列表
      • 类:直接传递MRO列表
      • 实例:传递所属类的MRO列表
  • 返回:封装有两个参数的super实例

    • 类似于返回MRO列表中某个类的实例,取决于访问的属性
  • 用途:依次遍历MRO列表(指定位置开始)中类,查找指定属性

    • 可以使用指定超类创建super实例,跳过对部分类搜索
    • 只有MRO列表中每个类中的方法都super()调用,才能保证 列表中所有类的该方法都被链式调用

说明

  • 需要注意super(cls, inst).__getattribute__("meth")中 共有两段属性访问,两次访问调用不同__getattribute__

    • super(cls, inst).__getattribute__首先调用 super.__getattribute__type(inst).mro()中寻找 some_cls.__getattribute__

    • 然后调用some_cls.__getattrbibute__("meth")访问 meth属性

  • 应使用super访问基类属性,而不是直接使用基类名称,避免 多继承中出现问题

    • 继承链super保证方法只按找MRO列表顺序调用一次
    • 多继承中硬编码基类名称调用方法可能导致方法被调用多次
  • super访问的属性路线不够明确,所以需要遵循以下原则

    • 继承体系中,所有相同名字的方法拥有可兼容的参数名, 比如:相同参数个数、名称
    • 最好确保最顶层类提供这个方法的实现,这样保证MRO上的 查找链肯定可以找到该方法

抽象类

接口、抽象类

  • 抽象类无法直接实例化

    • 目的就是让别的类继承它并实现特定的抽象方法
    • 也可以通过注册方式让某类实现抽象基类
  • 用途

    • 通过执行类型检查,确保实现为特定类型、实现特定接口
  • 类型检查很方便,但是不应该过度使用,因为动态语言目的就是 灵活性,强制类型检查让代码更复杂
  • 使用abc模块方便定义抽象类

Mixins

Mixins:把一些有用的方法包装成Mixin类,用于扩展其他类的 功能,而这些类往往又没有继承关系

  • Mixin不能直接实例化使用
  • Mixin没有自己的状态信息,即没有定义__init__方法, 没有实例属性,因此Mixin中往往会定义__slots__ = ()
  • Mixins讨论参见cs_program/program_design/inheritation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class LoggedMappingMixin:
__slots__ = ()
def __getitem__(self, key):
print("getting:", str(key))
return super9).__getimte__(key)

def __setitem__(self, key, value):
print("setting {} = {!r}".format(key, value))
return super().__setitem__(key, value)

def __delitem__(self, key):
print("deleting", str(key))
return super().__delitem__(key)

class SetOnceMappingMixin:
__slots__ = ()
def __setitem__(self, key, value):
if key in self:
raise KeyError(str(key), "alreay set")
return super().__setitem__(key, value)

class StringKeysMappingMixin:
__slots__ = ()
def __setitem__(self, key, value):
if not isinstance(key, str):
raise TypeError("keys must be strings")
return super().__setitem__(key, value)

# 单独的Mixin类使用没有意义,也无法实例化

class LoggedDict(LoggedMappingMixin, dict):
# 把混入类和其他已存在的类结合起来使用
pass

from collections import defaultdict

class SetOnceDefaultDict(SetOnceMappingMixin, defaultdict):
pass

def test():
d = LoggedDict()
d["x"] = 23
print(d["x"])
del d["x"]

d = setOnceDefaultDict(list)
d["x"].append(2)

class

类:包含值、相应操作集的模板,将相关信息片段组织成复合值, 使得可以整体对其进行操作

概念

实体

  • object:对象,属于一个类的所有值

  • instance:实例,单个对象

  • field/instance variable:域/实例变量,类的分量

  • method:应用于类实例的操作

    • C++中方法的使用和传统函数类似,新名称只是为强调其同 所属的类紧密联系
    • 传统函数称为free function,不被约束于特定类

权限

  • private:私有,声明在private section中的域仅对该类 本身可见(默认)

  • public:公有,声明在public section中的域对所有用户 可见

    • 现代面向对编程不鼓励在类中声明public实例变量
  • protected:受限,声明在protected section中的成员对 所有子类都可以访问,但用户不能访问

类&结构体

C++中:结构类型和类基本上以同样方式实现,类是struct的扩展

  • 结构体:默认访问权限是public,类默认为private
  • 类:允许高级继承、方法,成员默认private
  • 对一般成员变量,类和结构体在内存布局上完全一致,如: 顺序、内存布局

消息传递模型

面向对象程序设计中,对象间通过信息发送、请求实现对象间通信, 将传递的这些信息称为消息

1
receiver.name(arguments)
  • 对象间消息发通常理解为一个对象调用另一个对象的方法
  • sender:发送方,初始化方法的对象
  • receiver:接收方,消息的目标对象

接口实现分离

  • C++中类接口、实现相分离时,类自身定义仅存在与其.h文件

  • 实现放在.cpp中作为独立方法定义,需要以类名作为限定符、 ::作为分隔的方式表明自己所属的类

类继承

1
2
3
class subclass: public superclass{
...code...
}
  • 子类subclass继承父类superclass所有publicprotected成员,private成员仍然保持私有特性

  • 可以继承模板类

    • 子类甚至可以不包含任何代码,仅为简化类型名
      1
      2
      class StringMap: public Map<string, string>{}
      // 创建不包含代码的子类,简化代码

子类局限

子类对象是其所属父类的实例,但是有其局限

  • 将子类对象赋值给父类对象会导致子类特有实例变量值被丢弃 (因为父类对象在栈中空间较小)

  • 常用解决方法是使用指针,指针变量大小相同

    • 但使用指针会使内存管理变得复杂
    • 为类定义析构函数可以管理内存,但是指针越界时,不能 保证析构函数会在合适的时间调用
  • 最好方法是避免使用类继承,并创建独立的类管理自身堆内存, 否则

    • 完全禁止拷贝:定义私有拷贝构造函数、重载赋值操作符, 但这会使得对象难以嵌入大型数据结构
    • 执行深拷贝:用户需要承担内存管理,重载拷贝构造函数、 重载赋值操作符

Multiple Inheritance

多重继承:类可以继承自多个父类

  • 多重继承在实际编程过程中可能导致程序复杂、模糊不清
    • 多重继承的多个父类可能拥有多个相同方法名、数据域名
  • C++中单继承已经足够复杂,最好避免使用多重继承

final

final:指定虚函数不能被派生类覆盖、类不能被继承

  • 语法

    • 虚函数:在声明符之后
    • 类:紧跟类名后
    1
    2
    3
    4
    5
    6
    7
    struct A final{}
    struct B{
    virtual void foo();
    }
    struct C: B{
    void foo() final;
    }

override

override:指定派生类虚方法覆盖基类虚方法

  • 语法:在成员函数声明之后 (其他情况下override甚至不是关键字)

  • 用途:有助于防止代码出现意外继承行为

    • 即不是强制性关键字,是辅助性、可选
    • 非虚函数可以通过类型转换调用基类方法,不算覆盖???

Iterator

迭代器:指向集合中一个特定元素,每次可以通过单步递进方式访问 其他元素

  • 迭代器使用*操作查找其指向的值

迭代器层次结构

iterator_hierarchy

  • InputIterator:允许读值
  • OutputIterator:允许给解析的迭代器赋新值
  • ForwardItertor:结合InputIteratorOutputIterator ,允许读写值
  • BidirectionIterator:在ForwardIterator基础上允许向后 迭代,增加--操作符
  • RandomAccessIterator:在BidirectionIterator基础上 允许向前、向后移动任意元素,包含全部关系符

指针作为迭代器

  • 指针类型已经实现了RandomAccesIterator提供的所有操作符 ,所以可以使用指针作为迭代器

  • 很多实现已经将iterator作为类中嵌套迭代器的类型,要用 typedef重命名指针类型为iterator

    1
    typedef ValueType * iterator;
    • 如:编译器内部利用迭代器,将基于范围for展开
  • 编译器内部iterator类型指针变量参见 cppc/basics/intro

类方法

Constructor

构造函数:创建对象

  • 构造函数名与类名完全一样

  • 构造函数无法被子类继承,只能在初始化列表中调用

    • 缺省编译器调用父类默认构造函数
    • 若没有构造函数,则必须显式在初始化列表中初始化

执行阶段

  • 初始化阶段:初始化列表

    • 调用父类构造函数初始化父类成员变量

      • 缺省调用默认构造函数
      • 若父类没有默认构造函数,必须在初始化列表中显式 调用构造函数
    • 初始化类自身数据域

      • 对一般类型,缺省类似于普通未赋值初始化
      • 对类类型成员变量,缺省没有显式初始化则调用默认 构造函数
  • 计算阶段:执行构造函数体

    • 在函数体中执行的都是赋值,不是初始化

Initializer List

初始化列表:初始化父类、自身数据域

  • 位于的构造函数体花括号前、参数列表之后,用:和参数列表 分隔
初始化父类
  • 父类名后用括号括起来的参数列表,参数列表必须和父类 某一构造函数原型相匹配

    1
    Foo(string name, int id):(name, id){};
  • 父类数据域后用括号括起的该数据域的初始化值(此方法 也可以用于初始化类自身数据域)

    1
    Foo(string name, int id): name(name), id(id){};
  • 调用父类构造函数

    1
    Derived(string name, int id):Base(name, id){};
初始化自身数据域
  • 类类型成员变量最好使用初始化列表初始化,可以避免 构造函数初始化阶段调用成员变量默认构造函数

  • 必须放在初始化列表中成员变量

    • 常量成员:常量只能初始化,不能赋值
    • 引用类型:引用必须在定义时初始化,不能重新赋值
    • 没有默认构造函数的类类型
  • 成员变量按照其在类中声明顺序初始化,而不是在初始化 列表中的顺序

    • 为避免因成员变量初始化依赖导致的未定义,应该按照成员 变量声明的顺序初始化

Default Constructor

默认构造函数:没有参数的构造函数

  • 若类没有默认构造函数,编译器会自行定义

Copy Constructor

1
Foo(const Foo &f){};

拷贝构造函数:使用同类实例初始化创建新对象

  • 用途

    • 复制对象
    • 传值调用时,隐式调用拷贝构造函数构造新对象传参
  • 类中没有定义拷贝构造函数时,编译器会自行定义一个

    • 若类中有指针变量、并有动态内存分配,则必须定义拷贝 构造函数

explicit

epxplicit关键字:声明为explicit构造函数不能在隐式转换中 使用

  • 用途
    • 希望函数参数只能是给定类型,可以禁止隐式类型转换

Destructor

析构函数:类的对象消亡时,析构函数被自动调用

  • 析构函数可以完成各种清理操作
    • 最重要:释放对象所创建的所有堆内存
    • 关闭对象打开的任何文件
  • 析构函数名称:类名前加上~
  • 析构函数特点
    • 没有返回类型
    • 不能重载
    • 每个类只有一个无参的析构函数
  • 良好设计的C++应用中,每个类都需要堆其对内存负责

Operator Overloading

操作符重载:扩展标准操作符以适应新的数据类型

  • 编译器遇到操作符时,会根据操作符操作数的类型确定其 操作语义

  • 重载操作符函数名由关键字operator后跟操作符构成

    • 操作符的左右操作数类型、传值方式
    • 操作符返回值
      1
      2
      3
      ostream & operator<<(ostream & os, Point pt);
      // 流对象不能拷贝,必须引用传递
      // 返回依然是流引用
  • 有些类型根本没有定义过某些操作符,虽然实际上是扩展该类型 操作符,但是也成为重载操作符

  • overload:重载,使用相同名字的不同版本类方法

重载方式

C++提供两种机制用以重载内置操作符使得可以适用于新定义类

  • 类方法:在类中用方法重载操作符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // .h
    Class Point{
    Public:
    bool operator==(Point rhs);
    // 类中声明操作符重载方法
    }

    // .c
    bool Point::operator==(Point rhs){
    // 实现中限定为`Point`方法
    }
    • 左操作数为该类型对象(声明、实现省略)
    • 右操作数作为形参传递
    • 编译器把左操作数视为接收者,将右操作数作为形参传递
  • free function在类外使用自由函数重载定义操作符

    1
    2
    3
    4
    5
    6
    // .h
    bool operator==(Point pt1, Point pt2);

    // .c
    bool operator==(Point pt1, Point pt2){
    }
    • 二元操作符的两个操作数都作为形参传递
    • 操作符重载自由函数一般需要声明为类的友元函数,使其 能够访问类的私有实例变量

++/--

重载++/--时,必须指明是重载前缀还是后缀形式

  • 额外传入无意义整形参数说明重载后缀形式
  • 常用于枚举类型,方便遍历枚举类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Direction operator++(Direction & dir){
// 重载前缀形式
dir = Direction(dir+1);
return dir;
}

Direction operator++(Direction & dir, int){
// 重载后缀形式
// 这也说明,C++中若不需要使用形参值,可以省略形参名,
// 即使是在函数实现中
Direction old = dir;
dir = Direction(dir + 1);
return old;
}

=

  • 赋值操作符被定义为返回其左值,所以重载赋值操作符时注意 返回值

  • 赋值操作符开头往往检查左、右操作数地址是否一样

    • 避免不必要拷贝操作、逻辑错误

()

  • 重载函数调用操作符()将可以像函数一样调用对象
1
2
3
4
5
6
7
8
9
10
11
class AddFunction{
Public:
AddFunction(int k){
this->k = k;
}
int operator(int x){
return x+k;
}
private:
int k;
}
  • 重载此方法类称为function class(函数类),其实例称为 function object/functor(函数对象、函数子)

const方法

C++允许在方法参数表后增加关键字const指定方法,声明 其不改变其对象的状态

  • 在接口原型、实现中都应该使用const关键字约束
1
2
3
4
5
6
7
// .h
int size() const;

//.cpp
int CharStack::size() const{
return count;
}

Friend

友元:允许访问类中私有实例变量的元素(不是类方法)

  • 友元函数:允许访问类中私有变量的自由函数

    1
    2
    3
    4
    5
    friend prototype;
    // 在类定义中声明友元函数
    class Point{
    friend bool operator==(Point p1, Point p2);
    }
  • 友元类:允许访问类中私有实例变量的类

    1
    2
    friend class cls_name;
    // 在类中定义中声明友元类
    • 友元类的声明不是双向的,需要两个类都显式声明另一个类 为友元类,才能相互对方私有变量

典型方法

  • Accessor/Getter:访问器/读取器:获取实例变量值的函数

    • 为方便,读取器的命名通常以get为前缀
  • Mutator/Setter:设值方法/设值器,为特定实例变量 设置值的方法

    • 为方便,设置器的命名通常以set为前缀
    • 将实例变量设为私有就是为了阻止用户不受限制访问变量, 所以读取器的设置更为常见
    • 事实上,immutable设计风格将类设置为完全不可变

类内存结构

  • 类的方法地址不会存储在实例的内存空间中

非虚函数

  • 编译时

    • 编译器根据变量类型(指针、引用类型)在调用函数 处写入类方法的地址
    • 编译时即确定执行的函数
    • 可以认为是普通函数,只是默认包含参数this指针
  • 运行时

    • 访问方法相应内存地址,传入this指针调用方法

(Pure)Virtual Method

  • 虚方法/虚函数:virtual修饰的方法,基类中有定义
  • 纯虚方法:没有函数体的虚函数,基类中无定义,实现只能由 其子类提供
  • 派生类中覆盖方法总为虚方法,无论是否有virtual关键字
    • 建议后代虚函数加上virtual以提升可读性
1
2
3
4
virtual double getpay();
// `virtual`:普通虚函数
virtual double getPay() = 0;
// `=0`:纯虚函数,没有函数体
  • 编译时

    • 编译器根据类的声明创建虚表,每个类维护一张虚表
    • 对象被构造时,虚表地址在实例内存的首个字
    • 编译器在调用函数处不直接写入函数地址(因为不确定 调用的函数)
  • 执行时

    • 从对象首读取虚表,在虚表中查询需要执行方法地址
    • 访问相应方法地址,传入this指针调用方法
    • 实现多态:subtyping polymorphism,同一语句具体调用 方法动态决定
  • 多态:参见program/program_design/language_design
  • vTable:虚表,存储一个类中所有虚函数地址
    • 虚表是依次存储当前类中所有虚方法,不是继承序列 中同一个虚方法

Abstract Class

抽象类:包含纯虚方法的类

  • 抽象类主要作用

    • 将有关类组织在继承层次结构中

    • 刻画一组子类的操作接口的通用语义,只描述派生类共同 操作接口,完整实现留给子类

  • 抽象类只能作为基类派生新类使用,不能创建抽象类对象

    • 所以抽象类也不能做参数类型、函数返回类型、显式转换 类型

    • 可以定义指向抽象类的指针、引用,可以指向其派生类, 进而实现多态

    • 此特性本身就是为了解决基类生成对象不合理的问题, 抽象类就是为了抽象设计目的而建立

    • 实际中,为了强调类是抽象类,可以将类的构造函数 放在protected区域

    • 其派生类必须实现基类中所有纯虚函数,才能成为非抽象类 ,否则仍然是抽象类

  • 构造/析构函数内不能使用纯虚函数(一般成员方法内可以)

    • 因为派生类构造/析构函数内会调用基类构造/析构函数

    • 纯虚函数没有函数体,不能被调用

Override

覆盖/重置:在派生类中签名一样的方法会覆盖父类的方法

  • 非虚方法:调用方法取决于指针、引用类型

    • 将派生类对象赋给基类指针、引用后,调用方法是基类方法 ,表现父类的行为
  • 虚方法:动态检查,调用方法取决于对象实际类型

    • 将派生类对象赋给基类指针、引用后,调用方法是派生类 方法
    • 即指针(引用)根据指向值决定行为,实现多态

Template

模板:parameterized class参数化类,包含base type规格说明 的类

  • 模板是C++多态的一种实现

  • 编译器遇到函数模板调用,会自动生成相应版本函数拷贝

    • 因此模板必须能实现,即不能将模板接口、实现分开, 否则编译器无法生成相应版本函数拷贝 (当然可以通过#include强行将接口、实现分开)
    • 模板不能节省空间

模板使用

在函数、类前添加

1
template <typename ValueType>
  • template关键字:表示此行后整个语法单位是模板模式一部分
  • ValueType:类型占位符,生成函数拷贝时被替换为相应类型

模板函数

1
2
3
4
template <typename ValueType>
ValueType max(ValueType x, ValueType y){
return (x > y) ? x: y;
}

模板类

1
2
3
4
5
6
7
8
9
10
11
// .h
template <typename T1, typename T2>
class Stack{
T1 d1;
T2 d2;
}

template<typename ValueType>
Stack<ValueType>::Stack(){
}
// 模板实现、接口在同一文件中

Template Specialization

模板特化:指定一个或多个有具体模板参数的模板

  • 全特化:给出所有模板参数
  • 偏特化:给出部分模板参数
  • 模板实例化时,优先使用模板参数最匹配的模板版本

  • 通过特化模板,可以对特定模板参数集合自定义当前模板

    • 最好特化模板的接口和普通模板一致

类模板特化

类模板可以全特化、偏特化

  • 特化的模板参数从模板参数列表中移除
  • 在类名后给出完整参数列表
1
2
3
4
template <typename T2>
class Stack<int, T2>{
...
}

函数模板特化

函数模板只能全特化

  • 若编译器可以通过返回值类型推断模板实参类型,可以省略 函数后模板参数列表,否则引起歧义报错
  • 函数模板“偏特化”可以通过函数模板重载实现
1
2
3
4
template <>
int max(int x, int y){
...
}

Python类说明

元类说明

type

type:元类,python中所有类都由type创建

1
2
3
4
5
class = type(
name=cls_name,
bases=(pls_name,..),
dict={attr1: val1,...}
)
  • 参数

    • cls_name:类名称
    • bases:父类元组
    • dict:类方法、属性
  • 返回值:类的别名

元类作用

  • 拦截类创建->修改类->返回修改后的类(创建类对象

  • 元类的作用和函数相似

    • python并不关心类对象是否是由真正的元类创建
    • 可以指定元类为一个函数,而非继承自type的元类
  • 但仍应尽量将元类指定为继承自type的对象

    • 元类应用场景一般比较复杂,使用类可以更好的管理代码
    • 默认元类是type类,类继承保持一致性意图比较明显,且 可以使用type中的方法
    • 元类可以使用一些类中特有的方法:__new____init__
  • 如果不确定是否需要用元类,就不应该使用

自定义元类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class UpperAttrMeta(type):
def __new__(cls, cls_name, bases, attrs):
upper_attrs=dict((name.upper(), val)
for name,val in attrs.items()
if not name.startswith('__')
);
return super(UpperAttrMeta,cls).__new__(
cls,
cls_name,
bases,
upper_attrs);
// 使用元类创建新类

class Foo(metaclass=UpperAttrMeta):
b=1;

使用自定义元类UppAttrMeta创建的类Foo中定义的__init____new__等函数无意义,因为该类不仅是通过元类创建,也是 通过元类初始化

  • Foo通过UpperAttrMeta创建,而UppAttrMeta本身没有 实现自定义__init__,默认继承于object

    因此Foo类的创建就有object的init完成 segmentfault.com/q/1010000004438156 这个是原话,不明白什么意思了

  • 但是如果元类仅仅是pass,如下:

    1
    2
    class MetaCls(type):
    pass;

    使用此自定义元类,类定义中的__init____new__有效

类创建

py2自定义元类

python创建类对象步骤

  • __metaclass__指定创建类使用的元类

    • 按照优先级:类定义内 > 祖先类内 > 模块内 > type, 查找__metaclass__,并使用其创建类对象,若前三者 均未定义__metaclass__,则使用type创建

    • 自定义元类就是为__metaclass__指定自定义值

    • python只是将创建类的参数传递给__metaclass__,并不 关心__metaclass__是否是一个

      • cls()返回一个类对象,是相当于调用cls.__new__
      • 所以可以指定__metaclass__为一个函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
		
def upper_attr(cls_name, bases, attrs):
upper_attrs=dict((name.upper(), val) for name,val in attrs.items());
return type(cls_name, bases, upper_attrs);

class Foo():
bar=1;
__metaclass__=upper_attr;

# 函数版本

class UpperAttrMeta(type):
def __new__(clsf, cls_name, bases, attrs):
upper_attrs=dict((name.upper(), val) for name,val in attrs.items());
return type(cls_name, bases, upper_attrs);

class Foo():
bar=1;
__metaclass__=UpperAttrMeta;

# 类版本1

class UpperAttrMeta(type):
def __new__(cls, cls_name, bases, attrs):
upper_attrs=dict((name.upper(), val) for name,val in attrs.items());
return type.__new__(cls, cls_name, bases, upper_attrs);

# 类版本2

class UpperAttrMeta(type):
def __new__(cls, cls_name, bases, attrs):
upper_attrs=dict((name.upper(), val) for name,val in attrs.items());
return super(UpperAttrMeta,cls).__new__(cls, cls_name, bases, upper_attrs);

# 类版本3

元类示例

缓存实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import weakref

class Cached(type):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__cache = weakref.WeakValueDictionary()

def __call__(self, *args):
if args in self.__cache:
return self.__cache[args]
else:
obj = super().__call__(*args)
self.__cache[args] = obj
return obj

class Spam(metaclass=Cached):
def __init__(self, name):
print("Creating Spam({!r})".format(name))
self.name = name

捕获类属性定义顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from collection import OrderedDict

class Typed:
_expected_type = type(None)
def __init__(self, name=None):
self._name = name

def __set__(self, instance, value):
if not instance(value, self_expected_type):
raise TypeError("expected" + str(self._expected_type))
instance.__dict__[self._name] = value

class Integer(Typed):
_expected_type = int

class Float(Typed):
_expected_type = float

class String(Typed):
_expected_type = str

class OrderedMate(type):
def __new__(cls, clsname, bases, clsdict):
d = dict(clsdict)
order = [ ]
for name, value in clsdict.items():
if isinstance(value, Typed):
value._name = name
order.append(name)
d["_order"] = order
return type.__new__(cls, clsname, bases, d)

@classmethod
def __prepare__(cls, clsname, bases):
# 此方法会在开始定义类、其父类时执行
# 必须返回一个映射对象,以便在类定义体中使用
return OrderedDict()

class Structure(metaclass=OrderedMeta):
def as_csv(self):
return ",".join(str(getattr(self, name)) for name in self._order)

class Stock(Structure):
name = String()
shares = Integer()
price = Float()

def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price

有可选参数元类

为了使元类支持关键字参数,必须在__prepare____new____init__方法中使用KEYWORDS_ONLY关键字参数

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyMeta(type):
@classmethod
def __prepare__(cls, name, bases, *, debug=False, synchronize=False):
pass
return super().__prepare(naeme, bases)

def __new__(cls, name, bases, *, debug=False, synchronize=False):
pass
return super().__new__(cls, name, bases, ns)

def __init__(self, name, bases, ns, *, debug=False, synchronize=False):
pass
super().__init__(name, base, ns)