一、关于整数对象

整数对象是Python中最简单的对象。

Python2中包含PyIntObject和PyLongObject两种类型。

而Python3取消了PyIntObject,仅保留PyLongObject。

以下是代码的头两行,可以感受作者在修改过程中的艰辛:

/* Long (arbitrary precision) integer object implementation */

/* XXX The functional organization of this file is terrible */

1.1 PyLongObject

除了定长对象和变长对象,根据对象维护数据的可变性,可将对象分为可变对象(mutable)和不可变对象(immutable)。

PyLongObject就是一个不可变对象。

Include/longobject.h

/* Long (arbitrary precision) integer object interface */

typedef struct _longobject PyLongObject; /* Revealed in longintrepr.h */

Include/longintrepr.h

/* Long integer representation.

The absolute value of a number is equal to

SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i)

Negative numbers are represented with ob_size < 0;

zero is represented by ob_size == 0.

In a normalized number, ob_digit[abs(ob_size)-1] (the most significant

digit) is never zero. Also, in all cases, for all valid i,

0 <= ob_digit[i] <= MASK.

The allocation function takes care of allocating extra memory

so that ob_digit[0] ... ob_digit[abs(ob_size)-1] are actually available.

CAUTION: Generic code manipulating subtypes of PyVarObject has to

aware that ints abuse ob_size's sign bit.

*/

struct _longobject {

PyObject_VAR_HEAD

digit ob_digit[1];

};

Python中的整数对象实际上是对C中原生类型long的简单包装。

Objects/longobject.c

PyTypeObject PyLong_Type = {

PyVarObject_HEAD_INIT(&PyType_Type, 0)

"int", /* tp_name */

offsetof(PyLongObject, ob_digit), /* tp_basicsize */

sizeof(digit), /* tp_itemsize */

long_dealloc, /* tp_dealloc */

0, /* tp_print */

0, /* tp_getattr */

0, /* tp_setattr */

0, /* tp_reserved */

long_to_decimal_string, /* tp_repr */

&long_as_number, /* tp_as_number */

0, /* tp_as_sequence */

0, /* tp_as_mapping */

(hashfunc)long_hash, /* tp_hash */

0, /* tp_call */

long_to_decimal_string, /* tp_str */

PyObject_GenericGetAttr, /* tp_getattro */

0, /* tp_setattro */

0, /* tp_as_buffer */

Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |

Py_TPFLAGS_LONG_SUBCLASS, /* tp_flags */

long_doc, /* tp_doc */

0, /* tp_traverse */

0, /* tp_clear */

long_richcompare, /* tp_richcompare */

0, /* tp_weaklistoffset */

0, /* tp_iter */

0, /* tp_iternext */

long_methods, /* tp_methods */

0, /* tp_members */

long_getset, /* tp_getset */

0, /* tp_base */

0, /* tp_dict */

0, /* tp_descr_get */

0, /* tp_descr_set */

0, /* tp_dictoffset */

0, /* tp_init */

0, /* tp_alloc */

long_new, /* tp_new */

PyObject_Del, /* tp_free */

};

PyLong_Type保存了PyLongObject相关的丰富元信息,其中:

long_dealloc # PyLongObject对象的析构操作

PyObject_Del # 释放操作

long_to_decimal_string # 转换为PyStringObject对象

(hashfunc)long_hash # 获得哈希值

long_richcompare # 比较操作

&long_as_number # 数值操作集合

long_methods # 成员函数集合

long_new # 创建整数对象

以整数比大小为例,可以看出实际上就是将C中的long值进行比较:

static int

long_compare(PyLongObject *a, PyLongObject *b)

{

Py_ssize_t sign;

if (Py_SIZE(a) != Py_SIZE(b)) {

sign = Py_SIZE(a) - Py_SIZE(b);

}

else {

Py_ssize_t i = Py_ABS(Py_SIZE(a));

while (--i >= 0 && a->ob_digit[i] == b->ob_digit[i])

;

if (i < 0)

sign = 0;

else {

sign = (sdigit)a->ob_digit[i] - (sdigit)b->ob_digit[i];

if (Py_SIZE(a) < 0)

sign = -sign;

}

}

return sign < 0 ? -1 : sign > 0 ? 1 : 0;

}

static PyObject *

long_richcompare(PyObject *self, PyObject *other, int op)

{

int result;

CHECK_BINOP(self, other);

if (self == other)

result = 0;

else

result = long_compare((PyLongObject*)self, (PyLongObject*)other);

Py_RETURN_RICHCOMPARE(result, 0, op);

}

二、创建整数对象

在PyLong_Type中,创建整数对象的入口函数为long_new:

Objects/clinic/longobject.c.h

static PyObject *

long_new_impl(PyTypeObject *type, PyObject *x, PyObject *obase);

static PyObject *

long_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)

{

PyObject *return_value = NULL;

static const char * const _keywords[] = {"", "base", NULL};

static _PyArg_Parser _parser = {"|OO:int", _keywords, 0};

PyObject *x = NULL;

PyObject *obase = NULL;

if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser,

&x, &obase)) {

goto exit;

}

return_value = long_new_impl(type, x, obase);

exit:

return return_value;

}

Objects/longobject.c

static PyObject *

long_new_impl(PyTypeObject *type, PyObject *x, PyObject *obase)

/*[clinic end generated code: output=e47cfe777ab0f24c input=81c98f418af9eb6f]*/

{

Py_ssize_t base;

if (type != &PyLong_Type)

return long_subtype_new(type, x, obase); /* Wimp out */

if (x == NULL) {

if (obase != NULL) {

PyErr_SetString(PyExc_TypeError,

"int() missing string argument");

return NULL;

}

return PyLong_FromLong(0L);

}

if (obase == NULL)

return PyNumber_Long(x);

base = PyNumber_AsSsize_t(obase, NULL);

if (base == -1 && PyErr_Occurred())

return NULL;

if ((base != 0 && base < 2) || base > 36) {

PyErr_SetString(PyExc_ValueError,

"int() base must be >= 2 and <= 36, or 0");

return NULL;

}

if (PyUnicode_Check(x))

return PyLong_FromUnicodeObject(x, (int)base);

else if (PyByteArray_Check(x) || PyBytes_Check(x)) {

char *string;

if (PyByteArray_Check(x))

string = PyByteArray_AS_STRING(x);

else

string = PyBytes_AS_STRING(x);

return _PyLong_FromBytes(string, Py_SIZE(x), (int)base);

}

else {

PyErr_SetString(PyExc_TypeError,

"int() can't convert non-string with explicit base");

return NULL;

}

}

从以上代码可以看出有如下几种情况

如果x(对象)为NULL == obase(底数) != NULL 调用 PyLong_FromLong

如果obase(底数) 为 NULL 调用 PyNumber_Long

如果x(对象) 和 obase(底数) 都!= NULL

当为PyUnicode时调用 PyLong_FromUnicodeObject,最终调用 PyLong_FromString

当为PyByteArray/PyBytes时调用_PyLong_FromBytes,最终调用 PyLong_FromString

三、小整数对象

在实际编程中,比较小的整数会非常频繁的使用,比如遍历。

由于在Python中,所有对象基于系统堆,如果不停地在堆上申请空间和free,会大大降低系统运行效率。

为此,Python对小整数使用了对象池,直接将小整数对象放到缓存中。

小整数的范围在-5 至 257。

Objects/clinic/longobject.c.h

#ifndef NSMALLPOSINTS

#define NSMALLPOSINTS 257

#endif

#ifndef NSMALLNEGINTS

#define NSMALLNEGINTS 5

#endif

#if NSMALLNEGINTS + NSMALLPOSINTS > 0

/* Small integers are preallocated in this array so that they

can be shared.

The integers that are preallocated are those in the range

-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).

*/

static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

#ifdef COUNT_ALLOCS

Py_ssize_t quick_int_allocs, quick_neg_int_allocs;

#endif

static PyObject *

get_small_int(sdigit ival)

{

PyObject *v;

assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);

v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];

Py_INCREF(v);

#ifdef COUNT_ALLOCS

if (ival >= 0)

quick_int_allocs++;

else

quick_neg_int_allocs++;

#endif

return v;

}

#define CHECK_SMALL_INT(ival) \

do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \

return get_small_int((sdigit)ival); \

} while(0)

宏 CHECK_SMALL_INT 会检查传入的数是否在小整数范围内,如果是直接返回。

小整数初始化过程:

Objects/longobject.c

int

_PyLong_Init(void)

{

#if NSMALLNEGINTS + NSMALLPOSINTS > 0

int ival, size;

PyLongObject *v = small_ints;

for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++, v++) {

size = (ival < 0) ? -1 : ((ival == 0) ? 0 : 1);

if (Py_TYPE(v) == &PyLong_Type) {

/* The element is already initialized, most likely

* the Python interpreter was initialized before.

*/

Py_ssize_t refcnt;

PyObject* op = (PyObject*)v;

refcnt = Py_REFCNT(op) < 0 ? 0 : Py_REFCNT(op);

_Py_NewReference(op);

/* _Py_NewReference sets the ref count to 1 but

* the ref count might be larger. Set the refcnt

* to the original refcnt + 1 */

Py_REFCNT(op) = refcnt + 1;

assert(Py_SIZE(op) == size);

assert(v->ob_digit[0] == (digit)abs(ival));

}

else {

(void)PyObject_INIT(v, &PyLong_Type);

}

Py_SIZE(v) = size;

v->ob_digit[0] = (digit)abs(ival);

}

#endif

_PyLong_Zero = PyLong_FromLong(0);

if (_PyLong_Zero == NULL)

return 0;

_PyLong_One = PyLong_FromLong(1);

if (_PyLong_One == NULL)

return 0;

/* initialize int_info */

if (Int_InfoType.tp_name == NULL) {

if (PyStructSequence_InitType2(&Int_InfoType, &int_info_desc) < 0)

return 0;

}

return 1;

}

四、整数的存储结构

为了更方便的理解整数的存储结构,可以修改long_to_decimal_string_internal函数:

Objects/longobject.c

static int

long_to_decimal_string_internal(PyObject *aa,

PyObject **p_output,

_PyUnicodeWriter *writer,

_PyBytesWriter *bytes_writer,

char **bytes_str)

{

PyLongObject *scratch, *a;

PyObject *str = NULL;

Py_ssize_t size, strlen, size_a, i, j;

digit *pout, *pin, rem, tenpow;

int negative;

int d;

enum PyUnicode_Kind kind;

a = (PyLongObject *)aa;

// 打印ob_size和ob_digit

printf("ob_size = %d\n", Py_SIZE(a));

for (int index = 0; index < Py_SIZE(a); ++index) {

printf("ob_digit[%d] = %d\n", index, a->ob_digit[index]);

}

...

}

重新编译后,打印:

# $29248*(2^30)^0 + 32029*(2^30)^1 + 114*(2^30)^2$

>>> print(123456123456)

ob_size = 3

ob_digit[0] = 29248

ob_digit[1] = 32029

ob_digit[2] = 114

123456123456

其中的30由PyLong_SHIFT决定,64 位系统中,PyLong_SHIFT 为 30,32位为15。

五、整数对象的数值操作

整数的对象操作在PyNumberMethodsz中:

Objects/longobject.c

static PyNumberMethods long_as_number = {

(binaryfunc)long_add, /*nb_add*/

(binaryfunc)long_sub, /*nb_subtract*/

(binaryfunc)long_mul, /*nb_multiply*/

long_mod, /*nb_remainder*/

long_divmod, /*nb_divmod*/

long_pow, /*nb_power*/

(unaryfunc)long_neg, /*nb_negative*/

(unaryfunc)long_long, /*tp_positive*/

(unaryfunc)long_abs, /*tp_absolute*/

(inquiry)long_bool, /*tp_bool*/

(unaryfunc)long_invert, /*nb_invert*/

long_lshift, /*nb_lshift*/

(binaryfunc)long_rshift, /*nb_rshift*/

long_and, /*nb_and*/

long_xor, /*nb_xor*/

long_or, /*nb_or*/

long_long, /*nb_int*/

0, /*nb_reserved*/

long_float, /*nb_float*/

0, /* nb_inplace_add */

0, /* nb_inplace_subtract */

0, /* nb_inplace_multiply */

0, /* nb_inplace_remainder */

0, /* nb_inplace_power */

0, /* nb_inplace_lshift */

0, /* nb_inplace_rshift */

0, /* nb_inplace_and */

0, /* nb_inplace_xor */

0, /* nb_inplace_or */

long_div, /* nb_floor_divide */

long_true_divide, /* nb_true_divide */

0, /* nb_inplace_floor_divide */

0, /* nb_inplace_true_divide */

long_long, /* nb_index */

};

1. 整数相加

Objects/longobject.c

static PyObject *

long_add(PyLongObject *a, PyLongObject *b)

{

PyLongObject *z;

CHECK_BINOP(a, b);

if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) {

return PyLong_FromLong(MEDIUM_VALUE(a) + MEDIUM_VALUE(b));

}

if (Py_SIZE(a) < 0) {

if (Py_SIZE(b) < 0) {

z = x_add(a, b);

if (z != NULL) {

/* x_add received at least one multiple-digit int,

and thus z must be a multiple-digit int.

That also means z is not an element of

small_ints, so negating it in-place is safe. */

assert(Py_REFCNT(z) == 1);

Py_SIZE(z) = -(Py_SIZE(z));

}

}

else

z = x_sub(b, a);

}

else {

if (Py_SIZE(b) < 0)

z = x_sub(a, b);

else

z = x_add(a, b);

}

return (PyObject *)z;

}

2. 整数相乘

Objects/longobject.c

static PyObject *

long_mul(PyLongObject *a, PyLongObject *b)

{

PyLongObject *z;

CHECK_BINOP(a, b);

/* fast path for single-digit multiplication */

if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) {

stwodigits v = (stwodigits)(MEDIUM_VALUE(a)) * MEDIUM_VALUE(b);

return PyLong_FromLongLong((long long)v);

}

z = k_mul(a, b);

/* Negate if exactly one of the inputs is negative. */

if (((Py_SIZE(a) ^ Py_SIZE(b)) < 0) && z) {

_PyLong_Negate(&z);

if (z == NULL)

return NULL;

}

return (PyObject *)z;

}

3. 整数求幂

Objects/longobject.c

/* pow(v, w, x) */

static PyObject *

long_pow(PyObject *v, PyObject *w, PyObject *x)

{

PyLongObject *a, *b, *c; /* a,b,c = v,w,x */

int negativeOutput = 0; /* if x<0 return negative output */

PyLongObject *z = NULL; /* accumulated result */

Py_ssize_t i, j, k; /* counters */

PyLongObject *temp = NULL;

/* 5-ary values. If the exponent is large enough, table is

* precomputed so that table[i] == a**i % c for i in range(32).

*/

PyLongObject *table[32] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,

0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

/* a, b, c = v, w, x */

CHECK_BINOP(v, w);

a = (PyLongObject*)v; Py_INCREF(a);

b = (PyLongObject*)w; Py_INCREF(b);

if (PyLong_Check(x)) {

c = (PyLongObject *)x;

Py_INCREF(x);

}

else if (x == Py_None)

c = NULL;

else {

Py_DECREF(a);

Py_DECREF(b);

Py_RETURN_NOTIMPLEMENTED;

}

if (Py_SIZE(b) < 0) { /* if exponent is negative */

if (c) {

PyErr_SetString(PyExc_ValueError, "pow() 2nd argument "

"cannot be negative when 3rd argument specified");

goto Error;

}

else {

/* else return a float. This works because we know

that this calls float_pow() which converts its

arguments to double. */

Py_DECREF(a);

Py_DECREF(b);

return PyFloat_Type.tp_as_number->nb_power(v, w, x);

}

}

if (c) {

/* if modulus == 0:

raise ValueError() */

if (Py_SIZE(c) == 0) {

PyErr_SetString(PyExc_ValueError,

"pow() 3rd argument cannot be 0");

goto Error;

}

/* if modulus < 0:

negativeOutput = True

modulus = -modulus */

if (Py_SIZE(c) < 0) {

negativeOutput = 1;

temp = (PyLongObject *)_PyLong_Copy(c);

if (temp == NULL)

goto Error;

Py_DECREF(c);

c = temp;

temp = NULL;

_PyLong_Negate(&c);

if (c == NULL)

goto Error;

}

/* if modulus == 1:

return 0 */

if ((Py_SIZE(c) == 1) && (c->ob_digit[0] == 1)) {

z = (PyLongObject *)PyLong_FromLong(0L);

goto Done;

}

/* Reduce base by modulus in some cases:

1. If base < 0. Forcing the base non-negative makes things easier.

2. If base is obviously larger than the modulus. The "small

exponent" case later can multiply directly by base repeatedly,

while the "large exponent" case multiplies directly by base 31

times. It can be unboundedly faster to multiply by

base % modulus instead.

We could _always_ do this reduction, but l_divmod() isn't cheap,

so we only do it when it buys something. */

if (Py_SIZE(a) < 0 || Py_SIZE(a) > Py_SIZE(c)) {

if (l_divmod(a, c, NULL, &temp) < 0)

goto Error;

Py_DECREF(a);

a = temp;

temp = NULL;

}

}

/* At this point a, b, and c are guaranteed non-negative UNLESS

c is NULL, in which case a may be negative. */

z = (PyLongObject *)PyLong_FromLong(1L);

if (z == NULL)

goto Error;

/* Perform a modular reduction, X = X % c, but leave X alone if c

* is NULL.

*/

#define REDUCE(X) \

do { \

if (c != NULL) { \

if (l_divmod(X, c, NULL, &temp) < 0) \

goto Error; \

Py_XDECREF(X); \

X = temp; \

temp = NULL; \

} \

} while(0)

/* Multiply two values, then reduce the result:

result = X*Y % c. If c is NULL, skip the mod. */

#define MULT(X, Y, result) \

do { \

temp = (PyLongObject *)long_mul(X, Y); \

if (temp == NULL) \

goto Error; \

Py_XDECREF(result); \

result = temp; \

temp = NULL; \

REDUCE(result); \

} while(0)

if (Py_SIZE(b) <= FIVEARY_CUTOFF) {

/* Left-to-right binary exponentiation (HAC Algorithm 14.79) */

/* http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf */

for (i = Py_SIZE(b) - 1; i >= 0; --i) {

digit bi = b->ob_digit[i];

for (j = (digit)1 << (PyLong_SHIFT-1); j != 0; j >>= 1) {

MULT(z, z, z);

if (bi & j)

MULT(z, a, z);

}

}

}

else {

/* Left-to-right 5-ary exponentiation (HAC Algorithm 14.82) */

Py_INCREF(z); /* still holds 1L */

table[0] = z;

for (i = 1; i < 32; ++i)

MULT(table[i-1], a, table[i]);

for (i = Py_SIZE(b) - 1; i >= 0; --i) {

const digit bi = b->ob_digit[i];

for (j = PyLong_SHIFT - 5; j >= 0; j -= 5) {

const int index = (bi >> j) & 0x1f;

for (k = 0; k < 5; ++k)

MULT(z, z, z);

if (index)

MULT(z, table[index], z);

}

}

}

if (negativeOutput && (Py_SIZE(z) != 0)) {

temp = (PyLongObject *)long_sub(z, c);

if (temp == NULL)

goto Error;

Py_DECREF(z);

z = temp;

temp = NULL;

}

goto Done;

Error:

Py_CLEAR(z);

/* fall through */

Done:

if (Py_SIZE(b) > FIVEARY_CUTOFF) {

for (i = 0; i < 32; ++i)

Py_XDECREF(table[i]);

}

Py_DECREF(a);

Py_DECREF(b);

Py_XDECREF(c);

Py_XDECREF(temp);

return (PyObject *)z;

}

python整型数据源码分析_大师兄的Python源码学习笔记(三): 整数对象相关推荐

  1. 【SemiDrive源码分析】【Yocto源码分析】02 - yocto/meta-openembedded目录源码分析

    [SemiDrive源码分析][Yocto源码分析]02 - yocto/meta-openembedded目录源码分析 一.meta-openembedded 目录 本 SemiDrive源码分析 ...

  2. 从源码分析RocketMQ系列-RocketMQ消息持久化源码详解

    导语   在上篇分析中,提到了一个概念处理器,并且在进入到最终NettyIO的时候看到了一个Pair的对象,这个对象存储了两个对象,一个是执行器,一个是处理器,在进入Runable对象的时候看到封装到 ...

  3. delphi查看源码版本_[Mybatis]-IDEA导入Mybatis源码

    该系列文章针对 Mybatis 3.5.1 版本 一.下载 Mybatis 源码 step1.下载 Mybatis-3.5.1 源码 Mybatis 源码仓库地址 下载版本信息如下: 下载后进行解压, ...

  4. python整型数据源码分析_Python2 基本数据结构源码解析

    Python2 基本数据结构源码解析 Contents 0x00. Preface 0x01. PyObject 0x01. PyIntObject 0x02. PyFloatObject 0x04. ...

  5. python 曲线分析_大数据分析之Python计算KS值并绘制KS曲线

    本篇教程探讨了大数据分析之Python计算KS值并绘制KS曲线,希望阅读本篇文章以后大家有所收获,帮助大家对相关内容的理解更加深入. python实现KS曲线,相关使用方法请参考上篇博客-R语言实现K ...

  6. python底层源码_大师兄的Python机器学习笔记:统计学基础之底层代码实现(一)...

    一.中心趋势度量(Measure of Central Tendency) 1.众数(mode) 符号: 一组数据中出现最多的值. 纯python代码实现: >>>def calcu ...

  7. 大数据_MapperReduce_Hbase_批处理batchMutate源码分析_数据的写入流程源码分析---Hbase工作笔记0032

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 我们知道hbase是用java语言编写的,所以我们再来看一下源码: 然后我们再去看一下这个batc ...

  8. java源码依赖分析_高德APP全链路源码依赖分析工程

    一.背景 高德 App 经过多年的发展,其代码量已达到数百万行级别,支撑了高德地图复杂的业务功能.但与此同时,随着团队的扩张和业务的复杂化,越来越碎片化的代码以及代码之间复杂的依赖关系带来诸多维护性问 ...

  9. python元胞自动机模拟交通_大师兄带你复现 -gt; 难度超高的二维CA元胞自动机模型...

    最近过上了在家躺着就为祖国做贡献的生活. 然而,热心的知友们找我私信,询问"怎么画二维CA(元胞自动机)模型的仿真界面呀?""菜鸟如何做CA仿真?" 刚交完稿子 ...

  10. 11没有源码注释_我们为什么要看源码、应该如何看源码?

    看源码的意义 看源码只是一种方法.手段,而不是目的.我也曾经给自己制定过"阅读xxx源码"的目标,现在看起来真的很蠢,一点不smart(specific.measurable.at ...

最新文章

  1. 毛边效果 html,Html5 Canvas画线有毛边解决方法
  2. 用系统滚动条实现NumericUpDown的原理
  3. SQL中删除重复的行(重复数据),只保留一行 转
  4. java技术系列(一) Enum
  5. 猫:君主般的眼神 监视领地。 狗
  6. 黑龙江对口学计算机上机,2009年黑龙江省职高对口升学计算机应用专业技能试卷十.doc...
  7. 《黑客》月刊中文版第一期正式发布,很给力!推荐围观!
  8. 非结构化数据和结构化数据提取
  9. 动易cms聚合空间最近访客访问地址错误解决方法
  10. 【asp.net】VS 2008中文版下载
  11. android自定义导航,Android 高德地图之自定义导航
  12. 尚德机构2020年Q4财报:净收入5.85亿元,管理费用同比大幅下降近五成
  13. PowerManagerService类大致解读
  14. 5.8 使用轮廓化描边命令制作心形艺术图标 [Illustrator CC教程]
  15. 出现错误(已解决)RuntimeError: CUDA error: no kernel image is available for execution on the device CUDA ker
  16. leetcode877
  17. UI自动化执行遇到的问题汇总
  18. windows下GPG的使用
  19. 联想yoga14s和小新pro14哪个好
  20. html5中怎么实现居中显示图片

热门文章

  1. 域猫(域名分享平台)
  2. 严重BS骗样本的骗子
  3. Active Diretory 全攻略(一)--目录服务
  4. /usr/include/pcap/pcap.h源码
  5. 如何使用ssh工具便于远程管理
  6. 第二季-专题16-LCD亮起来
  7. Power BI连接至Amazon Redshift
  8. self-sizing cell的一个问题
  9. Create a virtualbox Based CentOS 6 OpenStack Cloud Image
  10. ZeroMQ之Request/Response (Java)