Python/C API 参考手册

有用的宏

在头文件当中对一些具备用途的宏进行了定义, 好多宏处在临近其参与使用的位置被拟定, 比如, 其他当中相对更具有通用性的宏在此处予以定义, 这里呈现出来的信息不是完备的清单。

声明扩展模块, 有初始化函数, 函数返回类型为*, 宏声明了平台所要求的任何特殊链接声明, 针对C++时将函数声明为"C"。

用于初始化的函数, 其必要的命名方式为, 其中提到的name, 它存在的意义是代表着模块的名称, 而且此name应当是在模块文件里被所定义的, 唯一的, 并非其他的 项。 比如说:。

复制代码
static struct PyModuleDef spam_module = {
    PyModuleDef_HEAD_INIT,
    .m_name = "spam",
    ...
};
PyMODINIT_FUNC
PyInit_spam(void)
{
    return PyModule_Create(&spam_module);
}

(x)

返回 x 的绝对值。

要使得编译器一直都将静态的内联函数实施内联, 可编译器却能够对其予以忽略, 进而决定不去内联这个函数。

它能够被用以, 在那种禁用函数内联的, 调试模式建构期间的情况下, 去内联那些严重对性能造成影响的, 静态内联函数。举例而言, MSC在以调试模式进行建构的时候被停用了函数内联。

有这样一种情况, 随意去使用标记内联函数, 这有可能致使性能变得极差, 比如说因为增加了代码量而造成的。而在关于成本和收益的分析这件事情上, 计算机通常来讲比开发者是要更加聪明一些的。

如果 是 (即定义了 宏),则 宏将不做任何事情。

它必须在函数返回类型之前指明。 用法:

复制代码
static inline Py_ALWAYS_INLINE int random(void) { return 4; }

(c)

参数必须为

-128, 127

或, 在一定范围之内的字符类型或者整数类型。这个宏指令会将c进行强制类型转换进而转变为char类型之后返回。

()

弃用声明。该宏必须放置在符号名称前。

示例:

复制代码
Py_DEPRECATED(3.8) PyAPI_FUNC(int) Py_OldFunction(void);

在 3.8 版本发生变更: 添加了 MSVC 支持。

(s)

和(s)相似, 不过要是从命令行传进去了, 那就返回NULL(参考)。

(x, y)

返回 x 和 y 当中的最大值。

(type, )

返回结构 (type) 的大小,以字节表示。

(x, y)

返回 x 和 y 当中的最小值。

使某个函数被启用内联, 举例来说, 它能够降低C栈的消耗,这适用于针对具备大量内联代码的LTO + PGO编译版本, (可参见条目bpo - 33720)。

用法:

复制代码
Py_NO_INLINE static int random(void) { return 4; }

(x)

把x转变成为C字符串, 举例来说譬如(123)会返回"123"。

()

你有一个, 无法从代码路径上抵达的设计时, 它可以被使用。比如说任意语句里, 所有值的可能性, 悉被 case 式子包容完结, 接着便好用在 : 子句之内。而一旦内心极渴望于某处地方, 搁下 (0)或者 abort()调用, 它一样能够派上用场。

在特定模式的状况之下, 此单个宏能够助力编译器进行代码优化, 并且防止给出代码沦为全然不可抵达情形时的一类警告。举例来看, 在GCC所具备一种特定模式的种种情况当中, 此这样一个宏借助e()得以实现。

什么的, 一个用法是, 调用一个, 不会返回, 然而却没有声明, 之后的, 函数。

要是有那么一个代码路径, 它不太具备成为正常代码的可能性, 然而却在那些特殊情形这下方可抵达, 那就不能够运用该宏, 举个例子来讲, 像是处于低内存的状况之下, 亦或是某个系统调用返回的是超出预期范围的数值, 诸如此类的情况, 最好是把错误报告给调用的一方。要是没办法将错误报告给调用者, 那就可以给予使用。

(arg)

把于函数定义里那个并没有被加以使用的参数拿来处理, 借此去消掉编译器所给出的警告呢。就像这样: 有一个整型定义了名为func的函数, 这个函数带有两个参数, 第一个参数名为a是整型, 第二个参数名为b是函数指针类型, 函数体里只有一个a。

(name, str)

构建出一个能于文档字符串里运用的变量, 其名为 name ,要是不与文档字符串一块儿进行构建, 那么该值会是空的。

按 PEP 7 所讲, 以 用作文档字符串, 用以去支持那种不和文档字符串一同构建的情形。

示例:

复制代码
PyDoc_STRVAR(pop_doc, "Remove and return the rightmost element.");
static PyMethodDef deque_methods[] = {
    // ...
    {"pop", (PyCFunction)deque_pop, METH_NOARGS, pop_doc},
    // ...
}

(str)

对于给定的字符串输入这一情况, 去创建一个文档字符串, 要不然的话, 也就是当文档字符串被禁用的时候, 就去创建一个空字符串。

按照PEP 7当中所讲述的那样, 运用指定的文档字符串, 以此来对不和文档字符串一块儿构建的情形予以支持。

示例:

复制代码
static PyMethodDef pysqlite_row_methods[] = {
    {"keys", (PyCFunction)pysqlite_row_keys, METH_NOARGS,
        PyDoc_STR("Returns the keys of the row.")},
    {NULL, NULL}
};

对象、类型和引用计数

大量/C API函数有着一个或者好几个参数, 还有一个*形式的返回值, 而这种类型乃是指向任意对象的不透明数据类型的指针, 鉴于所有对象类型在多数情形下都会被语言以相同方式予以处理, 像是赋值、作用域规则以及参数传递等方面, 所以用单一C类型去表示它们是颇为合适的, 几乎全部对象皆存在于堆里边, 你没办法声明一个类型为的自动或静态变量, 能够声明的仅仅是类型为*的指针变量。那种对象永远都不会被释放, 所以它们是静态对象, 唯一处在情形之外的是type对象。

一切对象, 哪怕是整数, 都具备一个type以及一个count。对象的类型用以明确它属于何种类型的对象, 就像整数、列表或者用户定义函数;还有更多别的, 如同文中某处所讲述的那样。对于每一种广为人知的类型, 都存在一个宏, 用于查验对象是否归属于该类型;举例来说, 当且只当a所指向的对象是列表的时候, (a)才会为真。

引用计数

引用计数有着其重要所在, 根源是现有计算机内存大小存有局限 , 而且常常限制得比较严苛 , 它会去计算到底有多少不同部位 , 针对同一个对象做了 , 这些不同部位可以是另外一个对象 , 或是能够成为全局 , 或者是静态C变量 , 又或者是某个C函数当中的局部变量 , 当某个对象的最后一个 被释放掉的时候 , 也就是它的引用计数变成零的时候 , 这个对象就会被取消分配操作 , 要是这个对象之中含有针对其他对象的引用的时候 , 那将会去释放掉这些被引用的对象。倘若不存在对别的对象的引用, 那些对象也会以相同方式被予以取消分配, 如此这般顺延下去。(于此处对象之间的彼此引用明显是个麻烦事儿;当下的解决途径呢, 便是"别这么搞")

显式执行操作, 针对引用计数而言。通常做法是, 借助宏来获取对象新引用, 也就是使引用计数加一 , 并且运用该宏来释放引用, 即令引用计数减一。此宏比彼宏复杂许多, 原因在于它得查验引用计数是否为零, 之后才调用对象释放器。释放器此为函数指针, 其含于对象类型结构体之中。要是对象乃复合对象类型, 像列表那样, 特定类型释放器会负责释放对象所包含其他对象引用, 还会执行所需其他终结化操作。不会发生引用计数溢出, 保存引用计数的位数起码会和虚拟内存里不同内存位置的位数一样多(假定() >= (void*)), 所以, 引用计数的递增是个简单操作。

对于每个有着指向对象指针的局部变量, 没必要去持有 (也就是增加引用计数)。从理论来讲, 一旦变量指向对象, 对象的引用计数就会加一, 可当变量离开其本身的作用域时, 引用计数就会减一。然而, 这两种情形会彼此抵消掉, 最终引用计数并未产生改变。使用引用计数的唯一真实缘由在于, 只要我们的变量对对象存在指向情况, 就能够防止对象被释放。只要我们晓得, 至少存在一个对着某对象的引用, 同我们的变量一块儿存在着, 那就没必要临时去获取一个新的, 也就是增加引用计数;有一种引用计数增加的十分重要的状况是, 对象当作 参数被传送给扩展模块里的C函数, 而这些函数又在其中被调用;调用机制能够确保在调用的期间, 对每一个参数都持有一个引用。

然而, 存在这样一个常见的情形, 那就是从列表里提取对象, 并且在不获取新的引用的状况下, 将其留存一段时间。某一个别的操作, 有可能会在不经意间从列表把该对象移除掉, 释放这个引用, 并且兴许撤销分配它的相关所有资源。真正说来危险就在这儿, 看似没有任何坏处的那个操作极有可能招呼来随便什么代码去做这件事;有一条代码路径是允许控制权从某个地方返回到用户那里的, 所以差不多任何操作都存在潜在的危险性。

靠谱的行径是一直运用泛型操控(那些名字以, ,,或开头的函数)。这些操控向来为其时其返回的对象创制一个全新的(亦即增添引用计数)。这致使调用者负有在得获结果之后调用的职责;这般做法很快就能习以为常水到渠成。

引用计数细节

"/C API 中函数的引用计数,最好是运用引用所有权来予以解释, 所有权关联到的是引用, 并非对象, 因为对象是不能被拥有的, 它们始终会被共享。 所谓'拥有一个引用', 意思是当不再需要该引用时, 必须在其上进行调用。 所有权还能够被转移, 这表明接受该引用所有权的代码, 在不再需要它的时候, 必须通过调用或者来最终释放它, 或者是继续转移这个责任, 一般是转给其调用方。"。当一个函数把引用所有权转达给其调用方之际, 就称调用方收到了一个新的引用, 当未进行所有权转移之时, 便称调用方是借入这个引用, 对于某方面情况而言不需要任何额外的操作。

与之相反, 在调用方函数传入一个对象引用这种情况下, 存在着两种状况: 其一为该函数窃取了一个对象引用, 其二是并未窃取, 窃取引用所指的是, 当把引用传入一个函数时, 此函数会认定自身拥有该引用, 并且你对其不再负有责任。

极少数函数会去窃取引用, 存在两个非常重要的例外情况, 那就是和, 它们会去窃取对条目的引用, 不过并非条目所在的元组或者列表, 这些函数之所以被设计成会窃取引用, 是源于在使用新创建的对象去填充元组或者列表时, 存在一个普遍的惯例, 比如创建元组(1, 2"three")的代码, 看起来可能会是这样子, 暂且先别管错误处理, 下面会展示更好的代码编写方式。

复制代码
PyObject *t;
t = PyTuple_New(3);
PyTuple_SetItem(t, 0, PyLong_FromLong(1L));
PyTuple_SetItem(t, 1, PyLong_FromLong(2L));
PyTuple_SetItem(t, 2, PyUnicode_FromString("three"));

在这儿, 返回出一个新的引用, 进而它马上就被窃取了。倘若你打算持续运用一个对象, 然而其引用会被窃取, 那么在调用窃取引用的那个函数以前, 要用 去抓取另外一个引用。

顺带简略提及一下, 这乃是设置元组条目的专有唯一方式;而另外的手法会拒绝执行如此操作, 原因在于元组属于不可变数据类型。你理应仅仅针对你自身所创建的元组来运用此方式。

等价于填充一个列表的代码可以使用 和 来编写。

但是, 于实践当中, 你极少会用到诸如此类构建以及填充元组或者列表的方式。存在一个通用的函数, 它能够依据 C 值去创建多数常用对象, 这是由一个格式字符串予以指明的。举例而言, 上面那两个代码块能够用下面的代码进行替代(并且还会承担错误检测的任务):。

复制代码
PyObject *tuple, *list;
tuple = Py_BuildValue("(iis)", 1, 2, "three");
list = Py_BuildValue("[iis]", 1, 2, "three");

等操作针对条目处理之时, 更常采用的方法为仅借入引用, 就好像把参数传递至你正在编写的函数那般 , 在这种状况下, 它们于引用方面的运行表现更为明晰, 原因在于你无需为了将引用转走而去获取一个新的引用(也就是"让它被偷取") , 举例来说, 这个函数会把列表(实际上是任何可变序列)里的所有条目都设定成给定的条目。

复制代码
int
set_all(PyObject *target, PyObject *item)
{
    Py_ssize_t i, n;
    n = PyObject_Length(target);
    if (n < 0)
        return -1;
    for (i = 0; i < n; i++) {
        PyObject *index = PyLong_FromSsize_t(i);
        if (!index)
            return -1;
        if (PyObject_SetItem(target, index, item) < 0) {
            Py_DECREF(index);
            return -1;
        }
        Py_DECREF(index);
    }
    return 0;
}

关于函数返回值的情形存在些许差异, 向多数函数传递引用时, 不会改变你对该引用的所有权责任, 然而, 诸多返回引用的函数会赋予你该引用的所有权, 原因相当简单, 在许多情形下, 返回的对象是临时创建的, 你所获取的引用属于该对象的唯一引用, 故而, 通用的返回对象引用的函数,比如函数一函数二, 总会返回一个新的引用, 调用方会成为该引用的所有者。

存在一个重点需了解, 即你是否拥有由函数返回的引用仅取决于调用的函数 --- 作为参数传给函数的对象类型这种附带物不会造成额外影响! 那么 , 要是你使用从一个列表提取条目 , 你不会拥有其引用 --- 然而要是你使用那个恰好接受完全相同参数的方式从同一个列表获取同样的条目 , 你便会拥有对所返回对象的引用。

下面是示例, 是关于说明你要怎样去编写一个用于计算一个整数列表中条目的函数的, 一个示例是使用 , 而另一个示例是使用。

复制代码
long
sum_list(PyObject *list)
{
    Py_ssize_t i, n;
    long total = 0, value;
    PyObject *item;
    n = PyList_Size(list);
    if (n < 0)
        return -1; /* Not a list */
    for (i = 0; i < n; i++) {
        item = PyList_GetItem(list, i); /* 不能失败 */
        if (!PyLong_Check(item)) continue; /* 跳过非整数 */
        value = PyLong_AsLong(item);
        if (value == -1 && PyErr_Occurred())
            /* 太大的整数无法适应 C long 类型,放弃 */
            return -1;
        total += value;
    }
    return total;
}
复制代码
long
sum_sequence(PyObject *sequence)
{
    Py_ssize_t i, n;
    long total = 0, value;
    PyObject *item;
    n = PySequence_Length(sequence);
    if (n < 0)
        return -1; /* 没有长度 */
    for (i = 0; i < n; i++) {
        item = PySequence_GetItem(sequence, i);
        if (item == NULL)
            return -1; /* 不是序列,或其他错误 */
        if (PyLong_Check(item)) {
            value = PyLong_AsLong(item);
            Py_DECREF(item);
            if (value == -1 && PyErr_Occurred())
                /* 太大的整数无法适应 C long 类型,放弃 */
                return -1;
            total += value;
        }
        else {
            Py_DECREF(item); /* 丢弃引用所有权 */
        }
    }
    return total;
}

类型

存在于/C API里用作关键作用的别的数据类型数量稀少;多数是像int、long, 以及char*这些简易的C类型。存有一些结构类型, 其用途是燃烧液体在对该模块所导出的函数列表中的一项, 或者某个新对象类型中的一项, 并且还存有一种结构类型, 用于描绘复数的值。这些结构类型会和运用它们的函数一同展开讨论。

属于 .

有一个能让 () 等于 () 的有符号整数类型, C99 并未直接对这样的事物进行定义( 属于无符号整数类型), 想要知晓详细情况的话请去查看 PEP 353 , 处于 类型里最大的正数值。

异常

程序员仅需应对特定要处理的错误异常, 未处理的异常会自动传至调用者, 接着传至调用者的调用者, 如此类推, 直至抵达顶级解释器, 于此处将它们呈报给用户并附带上堆栈回溯。

不过, 对于C程序员来讲, 错误检查始终得是显式开展的。/C API里的全部函数都能够引发异常, 除非在函数的文档当中另外进行显式声明的情况下不是这样。通常而言, 一旦一个函数碰到错误情形, 它就会去设置一个异常, 把它所拥有的任何对象引用给丢弃掉, 并且返回一个错误标示。要是没有关于例外的文档予以说明, 那么这个标示将会是NULL或者是 -1, 具体是哪种取决于函数的返回类型。有少量函数会返回一个布尔真/假结果值, 其中假值意味着错误。有数量极少的函数, 其没有显式的错误标识, 或是有着不明确的返回值, 并且需要用特定方式来进行显式的检测, 这些例外情况总是会被明确地记载到文档当中。

异常状态于各个线程的存储里进行维护, 这等同于在一个不存在线程的应用内运用全局存储。一个线程能够处于两种状态中的其一, 即异常已然发生, 或者并未发生。函数可用于查验此状态, 当异常发生之时它会返回一个借入的异常类型对象的引用, 在其他情形下则返回NULL。有多个函数能够设置异常状态, 而是最常遇到的(虽然并非最为通用的)设置异常状态的函数, 并且能够清除异常状态。

处于完整状态的异常, 是由三个对象构成的,这三个对象当中的每一个都存在为 NULL 的可能性, 它们分别是异常类型、与之相对应的异常值, 还有回溯信息。这些对象所具有的含义, 和中 sys.() 的结果是一样的;可是, 它们并非完全等同: 对象所代表的是经由... 语句处理的最后那一个异常, 而 C 层级的异常状态, 仅仅在异常被传入到 C 函数或者在这些函数之间进行传递的时候才会存在, 这种存在持续到其抵达字节码解释器的主事件循环, 该事件循环会承担起把它转移到 sys.() 等地的责任。

请注意, 自1.5起, 从代码访问异常状态, 首选且线程安全的方式是调用函数;该函数会返回代码的分线程异常状态。并且, 这两种访问异常状态方式的语义有变化, 所以捕获到异常的函数, 会保存并恢复其线程的异常状态, 以保留其调用方的异常状态;这能防止异常处理代码中, 因一个看似无辜的函数遮盖正在处理的异常而导致的常见失误;它还能减少在回溯由栈帧所引用对象时, 往往不必要地延长其生命周期。

按一般原则来讲, 有这样一个函数, 它会调用另外一个函数去执行某些任务, 这个函数要检查被调用的函数有没有引发异常。当引发异常的时候, 要把异常状态传递给调用它的一方。它还应当丢弃自己所拥有的任何对象引用, 并且返回一个错误标示。但是, 它不应该去设置另一个异常, 因为那样会覆盖刚引发的异常, 进而丢失关于错误确切原因的重要信息。

在上面的那个, 有着括号的示例之中, 它是一个能够检测异常, 并且还会将该异常传递出去的, 十分简单的例子。而碰巧恰好在这种情况下, 这个示例当检测到出现错误之时, 是不需要去清理其自身所拥有的任何引用的。再下面还有, 一个示例函数它展示出来了一些关于错误清理方面的操作举措和行为。首先呢, 为了能够起到提醒你在所呈现这个主题里受欢迎程度的作用, 我们进而展示了与之处于等价状态的代码:。

复制代码
def incr_item(dict, key):
    try:
        item = dict[key]
    except KeyError:
        item = 0
    dict[key] = item + 1

对应的 C 代码如下:

复制代码
int
incr_item(PyObject *dict, PyObject *key)
{
    /* 对象全部初始化为 NULL 用于 Py_XDECREF */
    PyObject *item = NULL, *const_one = NULL, *incremented_item = NULL;
    int rv = -1; /* 返回值初始化为 -1 (失败) */
    item = PyObject_GetItem(dict, key);
    if (item == NULL) {
        /* 只处理 KeyError: */
        if (!PyErr_ExceptionMatches(PyExc_KeyError))
            goto error;
        /* 清除错误并使用零: */
        PyErr_Clear();
        item = PyLong_FromLong(0L);
        if (item == NULL)
            goto error;
    }
    const_one = PyLong_FromLong(1L);
    if (const_one == NULL)
        goto error;
    incremented_item = PyNumber_Add(item, const_one);
    if (incremented_item == NULL)
        goto error;
    if (PyObject_SetItem(dict, key, incremented_item) < 0)
        goto error;
    rv = 0; /* 成功 */
    /* 继续执行清理代码 */
 error:
    /* 清理代码,由成功和失败路径所共享 */
    /* 使用 Py_XDECREF() 以忽略 NULL 引用 */
    Py_XDECREF(item);
    Py_XDECREF(const_one);
    Py_XDECREF(incremented_item);
    return rv; /* -1 表示错误, 0 表示成功 */
}

这个事例体现了C语言里goto语句一种被认可的运用方式!它表明了怎样运用以及怎样运用去应对特定异常, 还有怎样运用去处理或许为NULL的自有引用(留意名称里的'X';碰到NULL引用时将会崩溃)。关键的一点是用于保存自有引用的变量要被初始化为NULL方可发挥功效;同样地, 建议的返回值也要被初始化为-1(失败)并且唯有在最终执行的调用成功后才会被设置为成功。

嵌入

存在一项嵌入方专门要操心的重要任务, 此嵌入方是相对于扩展编写者来说, 那就是解释器的初始化, 说不定还得考虑它的最终化。解释器有着好些功能, 这些功能里头的大多数, 唯有在解释器完成初始化之后, 才能够得以使用。

最初阶段进行初始化操作的函数是。 这个函数会干初始化那已经被加载进来的模块表这件事, 并且还要创建基本模块 , 和。 它同样会去初始化模块可以进行搜索的路径(sys.path)。

无法进行"脚本参数列表"(sys.argv)的设置。要是后续即将执行的代码需用到这个变量, 那就得进行设置, 而且还得设置: 参考。

多数系统里, 尤其是Unix和, 虽细节有别, 会依据对标准解释器可执行文件位置的最佳推测来算出模块搜索路径, 且设定库能在相对于解释器可执行文件的固定位置被找到。特别之处是, 它会在shell命令搜索路径(环境变量PATH)上找到的名为的可执行文件所在父目录里, 去查找名为lib/.Y的目录。

首先, 举例来讲, 可执行文件若于 /usr/local/bin/ 所处, 它便会假定库于 /usr/local/lib/.Y 的位置。实际上情况可是这样子的, 这个特定路径还将变成"回退"所在之地, 会在没法在PATH里找到名为 的情况下作为可执行文件使用时被启用。然后, 用户能够透过设置环境变量, 或者借助设置把额外目录在标准路径之前插入的方式来将此行为给覆盖掉。

可以通过在调用之前, 调用(file), 来改变搜索次序的是嵌入的应用程序。请注意, 仍然会覆盖此设置, 并且, 仍然会被插入到标准路径之前。需要完全控制权的应用程序, 必须提供它自己的, , 和实现(这些函数均在/.c中定义)。

有的时候, 还得针对 去做出"反进行初始化"的操作。举例来说, 应用类型的程序 Maybe 会想要予以再次启动 ( 重新去进行调用 ), 又或者应用程序针对 的运用已然结束并且愿望就是去释放 所实施分配的内存。这能够依托去调用 达成既定目标。要是现如今 是处于已经被初始化的状态, 那么 这一函数将会返回真实的数值。关于这些函数的更为详尽的信息将会在后续的章节当中予以给出。需要留意的是, 并不会去释放全部经由 解释器所开展分配的内存, 像是由扩展模块所进行分配的内存时下是不会被释放这一种情况。

调试构建

用某些附加宏以便编译, 从而启用针对解释器以及扩展模块的更多检查。这些检查会在运行时候为其添加很多额外开销, 所以它们在没有特意设置的情况下是未被启用的。

见源代码颁发包中的Misc/.txt, 有各种调试构建版的完整列表, 可用的构建版支持追踪引用计数, 调试内存分配器, 或是对主解释器事件循环进行低层级性能分析等等, 本节剩余部分将只介绍最常用的几种构建版。

编译解释器在定义该宏的情形下, 会产生通常所说的那个, 在Unix编译版里, 是借助向点斜杠命令添加来启动的;它还能够经由提供并非专属的此类宏予以启动。当在Unix编译版时启动了它, 编译器那边的优化就会被禁止。

除了下文描述的引用计数调试,还会执行额外检查,请参阅 。

给出定义, 会启用引用追踪(可参见相关内容)。一旦定义了这个宏, 就会借由在每个上面增选增设两项额外的字段以维系一个有关活动对象的循环双链的列表。总的这分配量同样会被予以追踪。当退出的时候, 所有现有的引用都会被打印出来。(在交互模式之际这会于解释器运行每一条语句之后出现)。

有关更多详细信息,请参阅源代码中的 Misc/.txt 。

相关推荐
淡海水23 天前
01-02-运行时-MethodTable-一切类型的运行时身份证
windows·c#·对象·new·clr·methodtable
布兰妮甜1 个月前
原型链、this 指向闭包底层:手撕 bind/call/apply、深拷贝完整版
深拷贝·对象·原型链·this指向·bind/call/apply
潘潘的嵌入式日记1 个月前
一个宏在调试阶段挡住所有误操作
安全·嵌入式··调试·编译期·bring-up
ComputerInBook2 个月前
c 和 c++ 中的宏块(macro)
c语言·c++··宏块·宏指令
至乐活着2 个月前
深入Python内存管理与垃圾回收:从引用计数到分代回收的实战解析
python·性能优化·内存管理·垃圾回收·引用计数
一条泥憨鱼3 个月前
DTO、VO、PO、BO 到底该怎么区分?
java·数据库·状态模式·对象·印象笔记·对象类型
SunnyDays10113 个月前
使用 C# 添加、修改和删除 Excel VBA 宏 (无需 Microsoft Office Interop)
c#·excel··vba
布朗克1683 个月前
11 面向对象思想入门
java·对象
程序喵大人3 个月前
从内存/汇编角度看C与C++:指针、引用、对象的底层差异
c语言·汇编·c++·指针·引用·对象