python3是一种典型的动态的解释型语言;而C++则是一种典型的静态的编译型语言。
这两种语言都具有class这种数据类型,而两者对class的实现也分别是动态语言和编译语言的经典实现。 因为class的不同实现,它们的成员函数、成员变量也有着不同的表现。
先看从属于class的属性,我们简称class属性。 C++里的class属性,包括成员函数和静态成员函数、静态成员变量。
python3的class属性,包括类属性和三种方法,描述器也是一种类属性。
python3和C++的class属性都是关联到class的,在python3里,class属性记录在类的__dict__里,而C++里,class属性记录在一个数据栈里。在python3和C++里都有继承,假设A继承了B,B的class属性并不会复制到A里,两者的class属性是独立存在,假如A里有B里某个同名的class属性,B的该属性不会被覆盖,而只会被“遮盖”,B的该属性仍存在。可以看出来,python3和C++的class属性的表现是基本一致的。
再看从属于class实例的属性,我们简称实例属性。 python3只有成员变量一种,C++的实例属性除了成员变量,还有虚函数。
但是它们的表现,却是截然不同的。在C++里,假设A继承了B,则在A的class实例的内存段中,直接就包含了一段B的实例的完整数据段。也就是说,假如A里有B里某个同名的实例属性,继承自B的该属性也不会被覆盖,而只会被“遮盖”,来自B的该属性仍存在。但是要访问这必须通过B的成员函数,或者通过指针和类型转换来实现,再不行只好直接读取内存也行。因为这个,如果B的某个成员函数操作了B的某个实例属性,而A中有同名的实例属性的话,A的实例调用B的该成员函数,操作的仍然是A实例中B示例数据段的该属性,而非A中的同名实例属性。但是在python3里则完全不同。python3的实例并不是依靠结构体实现的,而是同样用字典实现。也就是说,假如python3里,假设A继承了B,A里有B里某个同名的实例属性,那么生成实例时,,继承自B的该属性会被A的同名实例属性确确实实的覆盖掉,B的该实例属性将“丢失”。因此,,如果B的某个成员函数操作了B的某个实例属性,而A中有同名的实例属性的话,A的实例调用B的该成员函数,操作的其实是A中的同名实例属性,因为B的该实例属性已经被覆盖了。
那么,C++里的虚函数,就本质上是一段不可直接访问的函数表。它伴随每个class的实例,同样也是会被同名变量(或者说函数标签相同的虚函数)覆盖的。C++通过这个方式,让不同class的实例,取指针并转换类型为基类指针之后调用虚函数仍然调用的是生成实例的class定义的虚函数。C++里的接口基本就是以这种别扭的方式实现的。
python3的class还有个特殊情况,那就是描述器。通常,从实例访问属性的话,实例的属性优先于同名class属性。而描述器是一种class属性,但如果是资料描述器的话,python3里它的优先级则还在实例属性前面。所以如果class里定义了描述器,它的实例其实无法定义同名的实例属性,因为定义的代码会被解释为对描述器的调用——然后要么报错要么成功调用了描述器。不仅如此,继承了该类的子类,也无法定义同名的实例属性,原因一样。但是既然描述器是一种class属性,那么它就可以被同名的class属性覆盖。所以如果在子类里定义一个同名的class属性,那么子类的实例也可以定义同名的实例属性了。但是如果将同名class属性删除,则该同名实例属性又无法访问了…………所以挺好玩的。