Python:从协议到抽象基类

news2024/11/24 4:38:44

本章话题是接口:鸭子类型代表特征动态协议; 使接口更明确、能验证实现是否副了规定的抽象基类ABC(Abstact Base Class).

Python语言诞生15年后,Python2.6中才引入了抽象基类,抽象基类。对于java、C#类似的语言,会觉得鸭子类型的非正式协议很新奇。

抽象基类于描述符和元类一样,是用于构建框架的工具。

其实很多时候Python开发者编写的抽象基类会对用户施加不必要的限制,做无用功。

Python文化中的接口和协议

引入抽象基类之前,Python已经很成功了,即便是现在也很少有代码使用抽象基类。

把协议定义为非正式的接口,是让Python这种动态类型语言实现多态的方式。

那么接口在动态语言中是怎么运作的呢?

首先,Python语言没有interface关键字。而且除了抽象基类,每个类都有接口:类实现或继承的公开属性(方法或者是数据属性),包括特殊方法,如__getitem__(实现切片slice) __add__(实现加法运算)。不要觉得把公开数据属性放入对象的接口中不妥,因为如果需要,总能实现读值和设值的方法,把数据属性变为特征,使用obj.attr句法的客户代码不会受到影响。比如前期实现的向量类Vector2d把x和y是公开数据属性,但是后期把xy变为了只读属性__x和__y但是用于依旧可以读取v.x和v.y因为使用特征实现了x和y 用到装饰器@property

关于接口补充定义:对象公开方法的子集,让对象在系统中扮演特定的角色。接口是实现特定角色的方法合集,这个理解正是所说的协议。协议和继承没有关系。一个类可能会实现多个接口,从而让实例扮演多个角色。

协议是接口,但不是正式的,能由文档和约定定义,因此协议不能像正式接口那样施加限制。一个类可能只实现部分接口,这是允许的。

对于Python程序员来说,“X类对象”、“X接口”、“X协议”都是一个意思。

Python序列:Sequence抽象基类

对于序列来说,即便是最简单的实现,Python也会力求做到最好。

上图是Sequence抽象基类和collections.abc中相关抽象类,剪头是子类指向超类,以斜体显示的是抽象方法。

比如下面的例子,Foo类没有继承abc.Sequence,只实现了序列协议的一个方法__getitem__,这样足够访问元素,迭代和使用in运算符了

class Foo:
    def __getitem__(self, item):
        return range(0, 30, 10)[item]


foo = Foo()
print(foo[1])
for x in foo: print(x)
print(20 in foo)
打印
10
0
10
20
True

可以使用for循环遍历,虽然没有实现__iter__方法,但是Foo实例是可迭代对象,因为Python发现有__getitem__方法,传入从0开始的整数索引,尝试迭代对象。(这是一种后备机制)

可以使用in运算符,虽然没有__contains__方法,但是Python足够智能,能迭代Foo实例,因此也能使用in运算符,Python会做全面的检查,看看有没有指定的元素。

综上,鉴于序列协议的重要性,如果没有__iter__和__contains__方法,Python会调用__getitem__方法,设法让迭代和in运算符可用。

Python会特殊对待看起来像序列的对象,Python中的迭代是鸭子类型的一种极端形式:为了迭代对象,解释器会尝试调用两个不同的方法。

猴子补丁:在运行时修改类或模块

属性在运行时的动态替换,叫做猴子补丁(Monkey Patch)

示例,一个实现序列协议的FrenchDeck类

class FrenchDeck:

    ranks = [str(x) for x in range(2, 11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()  # 花色

    def __init__(self):
        self._cards = [Card(rank, suit) for rank in self.ranks for suit in self.suits]

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, item):
        return self._cards[item]

    def __repr__(self):
        return str(self._cards)

上面这个纸牌类无法洗牌,如果FrenchDeck的行为像序列,那么random.shuffle(x)函数应该可用,它的作用是:就地打乱序列x

>>> import random

>>> li = list(range(6))

>>> random.shuffle(li)

>>> li

[0, 4, 5, 3, 1, 2]

但是作用在FrenchDeck实例中,就会出现异常:TypeError: 'FrenchDeck' object does not support item assignment

这个问题是原因是shuffle函数要调换序列中元素的位置,而FrenchDeck只实现了不可变序列协议。可变序列还需要提供__setitem__方法。

Python是动态语言,因此我们可以在运行时修正这个问题。

cards = FrenchDeck()


def set_card(deck, position, value):
    """猴子补丁,实现__setitem__"""
    deck._cards[position] = value  # 需要知道有个_cards属性


FrenchDeck.__setitem__ = set_card  # 打到类对象上,而不是实例
random.shuffle(cards)

print(cards[:5])
打印
[Card(rank='6', suit='clubs'), Card(rank='2', suit='clubs'), Card(rank='4', suit='spades'), Card(rank='A', suit='hearts'), Card(rank='3', suit='spades')]

知识点:

  1. 补丁函数要代替的是__setitem__这里没有使用self/key/value三个参数,因为Python方法也是普通函数,第一个参数命名为self只是一种约定。

  1. 关键点是,set_card函数需要知道deck对象有一个名为_cards的属性,而且_cards是可变序列。

  1. 打补丁是在类对象,不是实例对象

这种技术叫做猴子补丁:在运行时修改类或模块,而不改动源码。

上面的示例还强调了协议是动态的:random.shuffle函数不关心参数的类型,只要那个对象实现了部分可变序列的协议即可。即使对象一开始没有对应的方法也没关系,后来在提供也行。

这就是“鸭子类型”:对象的类型无关紧要,只要实现了特定的协议即可。

定义抽象基类的子类

现在我们重新实现一个FrenchDeck纸牌,通过继承mutableSequence实现

import collections

Card = collections.namedtuple('Card', 'rank suit')


class FrenchDeck(collections.MutableSequence):
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()

    def __init__(self):
        self._cards = [Card(rank, suit) for rank in self.ranks for suit in self.suits]

    def __getitem__(self, item):
        return self._cards[item]

    def __len__(self):
        return len(self._cards)

    def __setitem__(self, key, value):
        self._cards[key] = value

    def __delitem__(self, key):
        del self._cards[key]

    def insert(self, index, value):
        self._cards.insert(index, value)

在导入这个french_deck.py模块时,Python不会检查抽象方法的时候,在运行实例化的FrenchDeck类才会真正的检查,如果没有实现抽象类的全部抽象想法,就有抛出TypeError异常。

就是这样原因FrenchDeck不需要__delitem__和insert提供的行为,但是也必须要实现。

剪头由子类指向超类,斜体显示的是抽象类和抽象方法

FrenchDeck从Sequence继承了几个拿来即用的方法__contains__/__iter__/__reversed__/index/count

从MutableSequence继承了append/extend/pop/remove/__iadd__

ps:要想实现子类,我们可以覆盖从抽象基类中继承的方法,以更高效的方式实现。比如__contains__方法会全面的扫描整个序列,如果定义的序列是有序的,可以使用bisect函数做二分查找的逻辑,重写__contains__方法,实现更快的速度搜索。

标准库中的抽象基类abc、numbers

从Python2.6开始,标准库中提供了抽象类。大多数抽象类都在collection.abc中,还有在numbers和io包中也会有抽象类。

abc模块:

在标准库中有两个abc模块,其中一个是collection.abc,这里是各种已经实现好的抽象类,可以直接继承使用。

还有一个是abc,这里一般用到abc.ABC类,每个抽象基类都依赖这个类,用于定义新的抽象基类。

下图是在Python3.4中collection.abc模块定义的16个抽象基类:

Iterable、Container、Sized

各个集合都应该继承这三个抽象基类,或者至少实现兼容的协议。Iterable通过__iter__方法实现迭代、Container通过__container__方法实现in运算符、Sized通过__len__方法实现len()函数

Sequence、Mapping、Set

这三个是主要的不可变集合类型,各自都有自己可变的子类。

MappingView

Python3中,映射方法.items()/.keys()/.values()返回的对象分别是ItemView/KeysView/ValuesView的实例

Callable、Hashable

这两个抽象基类主要为了支持内置函数isinstance判断类型,以一种安全的类型安排对象能不能调用或者散列。(判断能否调用还可以用callable()函数,但是没有hashable()函数)

Iterator

这是Iterable的子类,后面会详细讨论

numbers模块:

numbers包的定义是“数字塔”(各个抽象基类的层次是线性的),最顶层的是超类,依次往下是子类

金字塔顺序:

  • Number

  • Complex 复合数 (包含复数)

  • Real :实数 int、bool、float、fractions.Fraction(分数)、Numpy的非复数类型

  • Rational 有理数(有理数是整数(正整数、0、负整数)和分数的统称,是整数和分数的集合。整数也可看做是分母为一的分数。)

  • Integral : int、bool(int的子类)

检查一个数是不是整数,可以使用isinstance(x, numbers.Integral)

检查一个数是不是浮点数类型,可以使用isinstance(x, numbers.Real)

定义一个抽象基类

声明抽象类最简单的方式是继承abc.ABC或者其他抽象类。

ABCMeta用来声明这个类只能被继承,不能实例化,实例化会报错

然后abc.ABC是在Python3.4中新增的类,如果是其他旧版本的Python,有不通的方式:

  • Python3.4以上:

class Tombola(abc.ABC):

  • Python3.0~Python3.4:

class Tombola(metaclass=abc.ABCMeta):

  • Python2:

class Tombola:

__metaclass__=abc.ABCMeta

把方法变为抽象使用装饰器@abc.abstractmethod,在所有Python版本中都一样。

注意如果方法还有用到其他装饰器,要保证@abc.abstractmethod在最里层!

class MyABC(abc.ABC):

@classmethod

@abc.abstractmethod

def an_cls_method(cls, x):

pass

实现一个抽象基类名为Tombola,这个一个符合抽奖机的特征,有四个方法,其中前面两个是抽象方法

  • load() 把元素放入容器

  • pick() 从容器中随机拿出一个元素,返回选中的元素。

  • loaded() 如果容器中至少有一个元素,返回True

  • inspect() 返回一个有序元组,由容器中的现有元素构成,不会修改容器内容(但是顺序不保留)

示例,Tombola抽象基类。

import abc


class Tombola(abc.ABC):

    @abc.abstractmethod
    def load(self, iterable):
        """从可迭代对象中添加元素"""

    @abc.abstractmethod
    def pick(self):
        """随机删除元素,然后将其返回
        如果实例为空,这个方法抛出异常LookUpError"""

    def loaded(self):
        """如果至少有一个元素,返回True,否则返回FALSE"""
        bool(self.inspect())

    def inspect(self):
        """返回一个有序元组,由当前元素构成。"""
        items = []
        while True:
            try:
                items.append(self.pick())  # 我们不知道子类如何存储元素,不过为了得到所有元素,只能不断的pick出所有的
            except LookupError:
                break
        self.load(items)  # 刚才的循环后,容器已空,再次加进去,虽然顺序已经和之前不同了
        return tuple(sorted(items))

知识点:

  1. 自己定义的抽象基类要继承abc.ABC

  1. 抽象方法使用@abstractmethod装饰器标记,而且定义体中通常只有字符串文档

  1. 抽象基类也可以包含具体的方法,但是具体方法里面的实现,只能使用其他的具体方法、抽象方法或特征。

其实抽象方法里面也可以有实现代码。即便实现了,子类也必须覆盖抽象方法,但是子类可以使用super()函数调用抽象方法。

inspect()方法实现起来有些笨拙,但也是无奈之举,因为不知道子类如何存储的元素,只能先把所有元素调出,然后再放回去。

后续继承的子类,必须实现两个抽象方法,否者就会异常:

class Fake(Tombola):
    def pick(self):
        return 12


print(Fake)
fake = Fake()
打印
<class '__main__.Fake'>
Traceback (most recent call last):
  File "D:/PycharmProjects/hello/test2.py", line 38, in <module>
    fake = Fake()
TypeError: Can't instantiate abstract class Fake with abstract methods load

创建Fake类并没有报错,尝试实例化Fake时抛出TypeError,报错信息也很明确,没有实现load方法。

定义抽象基类的子类

BingoCage类是Tombola的具体子类,实现了所需的抽象方法pike和load,继承了loaded方法,还增加了__call__方法

class BingoCage(Tombola):
    def __init__(self, items):
        self._randomizer = random.SystemRandom()
        self._items = []
        self.load(items)

    def load(self, iterable):
        self._items.extend(iterable)
        self._randomizer.shuffle(self._items)  # 类似于random.shuffle()就地随机打乱顺序

    def pick(self):
        try:
            return self._items.pop()
        except IndexError:
            raise LookupError('BingoCage is empty!')

    def __call__(self, *args, **kwargs):
        return self.pick()

知识点:

  1. random.SystemRandom() 使用的是os.urandom()函数实现的api,这个比random提供了更适合加密的伪随机数,用法和random相同

  1. random.SystemRandom().shuffle和random.shuffle一样,都是就地打乱可变序列的顺序。

  1. 以上子类都继承了抽象类的loaded和inspect方法,没有进行修改。

LotteryBlower是Tombola的具体子类,覆盖了继承的loaded和inspect方法,

主要区别是:pick方法没有使用pop出最后一个球,而是取出一个随机位置上的球

class LotteryBlower(Tombola):
    def __init__(self, items):
        self._items = list(items)

    def load(self, iterable):
        self._items.extend(iterable)

    def pick(self):
        try:
            position = random.randrange(len(self._items))  # 随机位置
        except ValueError:
            raise LookupError('LotteryBlower is empty!')
        self._items.pop(position)

    def loaded(self):
        return bool(self._items)

    def inspect(self):
        return tuple(sorted(self._items))

知识点:

  1. random.randrange ([start,] stop [,step]) 返回指定范围内的随机元素,start默认为0,step默认为1

虚拟子类

首先了解一下白鹅类型:

白鹅类型是指,只要clas是抽象基类,即cls的元类是abc.ABCMeta,就可以使用isinstance(obj, cls)

白鹅类型的一个基本特征:即便不继承,也有办法把一个类注册为抽象基类的虚拟子类。这样做时,我们保证注册的类忠诚地实现了抽象基类定义的接口,而Python会相信我们不做检查。如果我们没有做到,那么运行时异常会被我们捕获。

注册虚拟子类的方式是在抽象基类上调用register方法。

这样做以后,注册的类会变成抽象基类的虚拟子类,而且使用issubclass和isinstance等函数都能识别,但是注册的类不会从抽象基类中继承任何方法和属性。

虚拟子类不会继承注册的抽象基类,而且任何时候都不会检查它是否符合抽象基类的接口,即便是在实例化时也不会检查。为了避免运行时错误,虚拟子类要实现所需的全部方法。

  • issubclass(class, classinfo)函数用于比较 class是否是classinfo的子类。两个参数都是类对象。

  • isinstance(object, classinfo)函数用于比较 object是否是classinfo的子类。第一个参数的类的实例,第二个参数是类对象

声明虚拟子类的方式是在创建类时添加装饰器 @类名.register,也可以使用 类名.register(虚拟子类名)

示例,TomboList是list的真实子类和Tombola的虚拟子类

@Tombola.register  # 注册为Tombola的虚拟子类
class TomboList(list):
    def pick(self):
        if self:
            position = random.randrange(len(self))
            return self.pop(position)
        else:
            raise LookupError('TomboList is empty!')

    load = list.extend

    def loaded(self):
        return bool(self)

    def inspect(self):
        return tuple(sorted(self))


# Tombola.register(TomboList)  #python3.3之前的版本使用这种写法

print(issubclass(TomboList, Tombola))
t = TomboList()
print(isinstance(t, Tombola))
打印:
True
True

知识点:

  1. 在Python3.3或之前的版本,不能使用@Tombola.register这种语法糖形式,只能Tombola.register(TomboList) 用这种普通函数方式调用。

  1. load = list.extend 可以理解属性和方法可以混用啊,这样等于是load方法实现了

类的继承关系, 内省类继承关系

类的继承关系在一个特殊的类属性中指定__mro__,叫做方法解析顺序(Method Resolution Order)

这个属性的作用很简单,按照顺序列出类以及超类,Python会按照这个顺序搜索方法。

示例,查看TomboList类的__mro__属性,会看到只有真实的超类被列出来,也就是list和object,这也说明了虚拟子类TomboList没有在Tombola中继承任何方法。

print(TomboList.__mro__)

(<class '__main__.TomboList'>, <class 'list'>, <class 'object'>)

这两个类属性可以内省类的继承关系:

  • __subclasses__()

返回类的直接子类的列表,不含虚拟子类。

  • _abc_registry

只有抽象类有这个属性,值是一个WeakSet对象,即抽象类注册的虚拟子类的弱引用。

print(Tombola.__subclasses__())

print(list(Tombola._abc_registry))

[<class '__main__.BingoCage'>, <class '__main__.LotteryBlower'>]

[<class '__main__.TomboList'>]

子类检查的__subclassshook__

即便不注册为子类,抽象基类也能把一个类识别为虚拟子类。

下面是一个示例

class Str:
    def __len__(self):
        return 23

from collections import abc

print(issubclass(Str, abc.Sized))
print(isinstance(Str(), abc.Sized))
打印
True
True

上面可以看到经过issubclass和isinstance函数确认,Str是abc.Sized的子类,这是因为abc.Sized实现了一个特殊方法__subclassshook__

示例, Sized的源码

class Sized(metaclass=abc.ABCMeta):
    __slots__ = ()

    @abc.abstractmethod
    def __len__(self):
        return 0

    @classmethod
    def __subclassshook__(cls, C):
        if cls is Sized:
            if any('__len__' in B.__dict__ for B in C.__mro__):  # 在C及其超类中,如果类的__dict__属性有名为__len__属性
                return True  # 返回True,证明了C 是Sized的子类
        return NotImplemented  # 否者返回NotImplemented,让子类检查。

__subclassshook__在白鹅类型中添加了一些鸭子类型的踪迹。

我们可以使用抽象基类定义正式接口,可以始终使用isinstance检查,也可以完全使用不相关的类,只要提供特定的方法即可(或者一些符合__subclassshook__方法的特性),只有提供__subclassshook__方法的抽象基类才能这样做。

建议:不要自己定义抽象基类

不要自己定义抽象基类,除非你要构建允许用户扩展的框架---

日常使用中,我们与抽象基类的联系应该是创建现有的抽象基类的子类,或者使用现有的抽象基类注册。我们还可能在isinstance检查中使用抽象基类。

需要自己从头编写新抽象基类的情况少之又少。

尽管抽象基类使得类型检查变得功更容易了,但是不应该在程序中过度使用它。Python的核心是一门动态语言,它带来了极大的灵活性。如果处处都强制 实行类型约束,那么代码会变得更加复杂。我们应该拥抱Python的灵活性。

Python是强类型的动态脚本语言。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/189095.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DevSecOps敏捷安全技术金字塔V3.0正式发布

2022年12月28日&#xff0c;由悬镜安全主办&#xff0c;3S-Lab软件供应链安全实验室、Linux基金会OpenChain社区、ISC、OpenSCA社区联合协办的第二届全球DevSecOps敏捷安全大会&#xff08;DSO 2022&#xff09;已通过全球直播的形式圆满举行。本届大会以“共生敏捷进化”为主题…

HTB_Markup_xml注入读ssh私钥进程注入

文章目录信息收集xml注入ssh 私钥连接提权信息收集 使用如下参数可以探测具体版本&#xff0c;只使用-sV -v无此效果 nmap -sC -A -Pn 10.129.95.192是个登录页面 弱口令&#xff0c;只有admin-password成功登录 几个页面&#xff0c;只有order.php页面可以与后端交互并传递x…

【Kubernetes 企业项目实战】06、基于 Jenkins+K8s 构建 DevOps 自动化运维管理平台(中)

目录 一、基于 Jenkinsk8sGitDocker Hub 等技术链构建企业级 DevOps 容器云平台 1.1 安装 Jenkins 1.1.1 安装 nfs 服务 1.1.2 在 kubernetes 中部署 jenkins 1.2 配置 Jenkins ​1.2.1 获取管理员密码 1.2.2 安装插件 1.2.3 创建第一个管理员用户 1.3 测试 jenkins 的…

VMware Workstation中安装Kali 2022

VMware Workstation中安装Kali 2022 前言 开工了&#xff0c;笔记本中的相关工具该更新了&#xff0c;今天记录一下。 首先记录的是在VMware Workstation中安装kali&#xff0c;这个过程比较简单。 我只是想扩充一下自己的博客&#xff0c;另外可以给入门人员一个参考。 下载…

react的JSX语法

1.jsx基本使用 1 createElement() 的问题 繁琐不简洁。不直观&#xff0c;无法一眼看出所描述的结构。不优雅&#xff0c;用户体验不爽。 2 JSX 简介 JSX 是 JavaScript XML 的简写&#xff0c;表示在 JavaScript 代码中写 XML&#xff08;HTML&#xff09; 格式的代码。 优…

通信原理笔记—增量调制(∆M)

目录 概述&#xff1a; 简单增量调制(∆M)原理&#xff1a; 编码器与解码器 简单△M的过载问题&#xff1a; 增量总和(∆-Σ)调制 数字压扩自适应增量调制&#xff1a; 不同编码调制方式的误码性能分析&#xff1a; 概述&#xff1a; 最简单的DPCM是增量调制&#xff0c…

layui框架学习(4:导航)

layui官网教程采用html中的无序列表和定义列表来实现导航样式&#xff08;文章最后还有个关于导航所用元素的补充说明&#xff09;&#xff0c;主要包括水平导航、垂直/侧边导航&#xff0c;同时还支持用span和a元素实现面包屑导航样式。导航功能需要加载element模块&#xff0…

实验二:Linux主机漏洞利⽤攻击实践

&#xff08;一&#xff09;实验简介 实验所属系列&#xff1a;windows主机漏洞利用攻击实践 实验对象&#xff1a;本科/专科信息安全专业 相关课程&#xff1a;渗透测试 实验时数&#xff08;学分&#xff09;&#xff1a;2学时 实验类别&#xff1a;实践类 &#xff08;二&a…

如何集中式管理多个客户端节点传输任务-镭速

在一些生产制造企业或it部门&#xff0c;它们的生产机器设备每天都会有大量的生产数据&#xff0c;并且需要人为地对这些数据进行迁移或者归档备份到其他存储。这样重复性的操作&#xff0c;无疑大大提高了人工成本&#xff0c;而且运作起来的效率也不高。 镭速服务器集中式任务…

MySQL优化方案

一、MySQL 的优化方案有哪些&#xff1f;MySQL 数据库常见的优化手段分为三个层面&#xff1a;SQL 和索引优化、数据库结构优化、系统硬件优化等&#xff0c;每个大的方向中又包含多个小的优化点。1.SQL 和索引优化通过优化 SQL 语句以及索引来提高 MySQL 数据库的运行效率① 使…

【UE5】动画系统

title: 【UE5】动画系统 date: 2023-01-31 19:50:47 tags: UE5 categories: 学习笔记 password: abstract: message: 最近接触的项目涉及到动捕和动画&#xff0c;以前接触的范围主要是GamePlay以及C和蓝图的交互&#xff0c;很少接触动画&#xff0c;借此机会学习一下UE5的动…

【Jmeter】报错解决:JedisException: Could not return the broken resource to the pool

一、报错详情 (1)报错场景 使用 Jmeter 插件 Redis Data Set 配置连接 Redis 数据池时,运行出现报错 (2)报错日志

Python对liunx中mysql数据库进行单表查询 10个案例带你了解

关于Python连接liunx中mysql数据库的方式在这一篇文章 这里写目录标题1.在Liunx服务器中的mysql新建一个表2.插入数据3.连接liunx中的mysql数据库1、查询1946班的成绩信息2&#xff0c;查询1944班&#xff0c;语文成绩大于60小于90的成绩信息3&#xff0c;查询学生表中1到6行的…

QTabWidget 美化 qss

1. tab&#xff0c; tab-bar&#xff0c; pane属性分布 2. 使用qss美化时&#xff0c;tab标签上和pane中都能美化成功&#xff0c;但tab最右侧的tab-bar却始终没有成功。 /*设置控件的背景*/ QTabWidget {background-color:rgb(4,138,224); } /*设置控件下面板的背景颜色*/ QT…

C++11 常用的新特性

本篇介绍C11标准对比之前C标准的新特性&#xff0c;C11为C语言在2011年发布的版本&#xff0c;它改进幅度很大&#xff0c;影响至今。如加入auto 关键字、nullptr、移动语义&#xff08;move semantics&#xff09;、委托构造函数&#xff08;delegating constructors&#xff…

ChatGPT超详细注册与使用教程

文章目录前言一、ChatGPT账号注册二、SMS-ACTIVATE虚拟手机号验证三、ChatGPT使用总结前言 最近ChatGPT非常火爆&#xff0c;是一种革命性的技术&#xff0c;这也吸引来了很多人想尝试一下&#xff0c;但是由于并没有在国内开通这项服务&#xff0c;所以国内的用户无法使用Chat…

Javascript预解析

1.我们js引擎运行js 分为两步&#xff0c;1.预解析&#xff0c;2.执行代码 &#xff08;1&#xff09;预解析&#xff1a;js引擎会把js里面所有的var还有function提升到当前作用域发的前面 &#xff08;2&#xff09;执行代码&#xff1a;按照代码书写的顺序从上往下执行 2.预…

RK3588平台开发系列讲解(进程篇)进程的处理器亲和性

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、简介二、相关结构体三、函数接口四、cpuset的使用沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇介绍进程的处理器亲和性相关知识。 一、简介 Linux进程调度器为了充分利用CPU资源,会将进程在不同的…

如何养成整洁的代码习惯

如何养成整洁的代码习惯前言1.为什么要保持代码整洁?1.1 所以从一开始就要保持整洁1.2 如何写出整洁的代码?2.命名2.1 不好的命名方式1.没有任何意义的命名方式2.命名前后不一致3.命名冗余3.类3.1单一职责3.2 开闭原则3.3 内聚4.函数4.1 只做一件事4.2 函数命名1.函数名应见名…

春季开学必备物品清单、数码好物推荐篇

开学的脚步近了&#xff0c;近了&#xff0c;大学生返校&#xff0c;万物更新&#xff0c;大家迎接开学季的阵仗堪比迎接春天了。灵魂发问&#xff1a;开学装备备齐了吗&#xff1f;大学生们的情绪调整好了吗&#xff1f;自己要不要再回回炉&#xff0c;充充电&#xff1f;这次…