Effective C++ 2e Item33

深入理解内联函数(C语言) 内联函数究竟隐藏着什么样的奥秘,是否总能提升程序的运行效率? 阅读详情

条款33: 明智地使用内联

内联函数------多妙的主意啊!它们看起来象函数,运作起来象函数,比宏(macro)要好得多(参见条款1),使用时还不需要承担函数调用的开销。你还能对它们要求更多吗?

然而,你从它们得到的确实比你想象的要多,因为避免函数调用的开销仅仅是问题的一个方面。为了处理那些没有函数调用的代码,编译器优化程序本身进行了专门的设计。所以当内联一个函数时,编译器可以对函数体执行特定环境下的优化工作。这样的优化对"正常"的函数调用是不可能的。

我们还是不要扯得太远。程序世界和现实生活一样,从来就没有免费的午餐,内联函数也不例外。内联函数的基本思想在于将每个函数调用以它的代码体来替换。用不着统计专家出面就可以看出,这种做法很可能会增加整个目标代码的体积。在一台内存有限的计算机里,过分地使用内联所产生的程序会因为有太大的体积而导致可用空间不够。即使可以使用虚拟内存,内联造成的代码膨胀也可能会导致不合理的页面调度行为(系统颠簸),这将使你的程序运行慢得象在爬。(当然,它也为磁盘控制器提供了一个极好的锻炼方式:))过多的内联还会降低指令高速缓存的命中率,从而使取指令的速度降低,因为从主存取指令当然比从缓存要慢。

另一方面,如果内联函数体非常短,编译器为这个函数体生成的代码就会真的比为函数调用生成的代码要小许多。如果是这种情况,内联这个函数将会确实带来更小的目标代码和更高的缓存命中率!

要牢记在心的一条是,inline指令就象register,它只是对编译器的一种提示,而不是命令。也就是说,只要编译器愿意,它就可以随意地忽略掉你的指令,事实上编译器常常会这么做。例如,大多数编译器拒绝内联"复杂"的函数(例如,包含循环和递归的函数);还有,即使是最简单的虚函数调用,编译器的内联处理程序对它也爱莫能助。(这一点也不奇怪。virtual的意思是"等到运行时再决定调用哪个函数",inline的意思是"在编译期间将调用之处用被调函数来代替",如果编译器甚至还不知道哪个函数将被调用,当然就不能责怪它拒绝生成内联调用了)。以上可以归结为:一个给定的内联函数是否真的被内联取决于所用的编译器的具体实现。幸运的是,大多数编译器都可以设置诊断级,当声明为内联的函数实际上没有被内联时,编译器就会为你发出警告信息(参见条款48)。

假设写了某个函数f并声明为inline,如果出于什么原因,编译器决定不对它内联,那将会发生些什么呢?最明显的一个回答是将f作为一个非内联函数来处理:为f生成代码时就象它是一个普通的"外联"函数一样, 对f的调用也象对普通函数调用那样进行。

理论上来说确实应该这样发生,但理论和现实往往会偏离,现在就属于这种情况。因为,这个方案对解决"被外联的内联"(outlined inline)这一问题确实非常理想,但它加入到C++标准中的时间相对较晚。较早的C++规范(比如ARM------参见条款50)告诉编译器制造商去实现的是另外不同的行为,而且这一旧的行为在现在的编译器中还很普遍,所以必须理解它是怎么一回事。

稍微想一想你就可以记起,内联函数的定义实际上都是放在头文件中。这使得多个要编译的单元(源文件)可以包含同一个头文件,共享头文件内定义的内联函数所带来的益处。下面给出了一个例子,例子中的源文件名以常规的".cpp"结尾,这应该是C++世界最普遍的命名习惯了:


 // 文件example.h
 inline void f() { ... }          // f的定义

 ...


 // 文件source1.cpp
 #include "example.h"             // 包含f的定义

 ...                              // 包含对f的调用


 // 文件source2.cpp
 #include "example.h"             // 也包含f的定义
  
 ...                              // 也调用f

假设现在采用旧的"被外联的内联"规则,而且假设f没有被内联,那么,当source1.cpp被编译时,生成的目标文件中将包含一个称为f的函数,就象f没有被声明为inline一样。同样地,当source2.cpp被编译时,产生的目标文件也将包含一个称为f的函数。当想把两个目标文件链接在一起时,编译器会因为程序中有两个f的定义而报错。

为了防止这一问题,旧规则规定,对于未被内联的内联函数,编译器把它当成被声明为static那样处理,即,使它局限于当前被编译的文件。具体到刚才看到的例子中,遵循旧规则的编译器处理source1.cpp中的f时,就象f在source1.cpp中是静态的一样;处理source2.cpp中的f时,也把它当成在source2.cpp中是静态的一样。这一策略消除了链接时的错误,但带来了开销:每个包含f的定义(以及调用f)的被编译单元都包含自己的f的静态拷贝。如果f自身定义了局部静态变量,那么,每个f的拷贝都有此局部变量的一份拷贝,这必然会让程序员大吃一惊,因为一般来说,函数中的"static"意味着"只有一份拷贝"。

具体实现起来也会令人吃惊。无论新规则还是旧规则,如果内联函数没被内联,每个调用内联函数的地方还是得承担函数调用的开销;如果是旧规则,还得忍受代码体积的增加,因为每个包含(或调用) f的被编译单元都有一份f的代码及其静态变量的拷贝!(更糟糕的是,每个f的拷贝以及每个f的静态变量的拷贝往往处于不同的虚拟内存页面,所以两个对f的不同拷贝进行调用有可能导致多个页面错误。)

还有呢!有时,可怜的随时准备为您效劳的编译器即使很想内联一个函数,却不得不为这个内联函数生成一个函数体。特别是,如果程序中要取一个内联函数的地址,编译器就必须为此生成一个函数体。编译器怎么能产生一个指向不存在的函数的指针呢?


inline void f() {...}            // 同上

void (*pf)() = f;                // pf指向f

int main()
{
  f();                           // 对f的内联调用

  pf();                          // 通过pf对f的非内联调用
  ...
}

这种情况似乎很荒谬:f的调用被内联了,但在旧的规则下,每个取f地址的被编译单元还是各自生成了此函数的静态拷贝。(新规则下,不管涉及的被编译单元有多少,将只生成唯一一个f的外部拷贝)

即使你从来不使用函数指针,这类"没被内联的内联函数"也会找上你的门,因为不只是程序员会使用函数指针,有时编译器也这么做。特别是,编译器有时会生成构造函数和析构函数的外部拷贝,这样就可以通过得到那些函数的指针,方便地构造和析构类的对象数组(参见条款M8)。

实际上,随便一个测试就可以证明构造函数和析构函数常常不适合内联;甚至,情况比测试结果还糟。例如,看下面这个类Derived的构造函数:


class Base {
public:
  ...
private:
  string bm1, bm2;   // 基类成员1和2
};
class Derived: public Base {
public:
  Derived() {}                  // Derived的构造函数是空的,
  ...                           // ------但,真的是空的吗?


private:
  string dm1, dm2, dm3;         // 派生类成员1-3
};

这个构造函数看起来的确象个内联的好材料,因为它没有代码。但外表常常欺骗人!仅仅因为它没有代码并不能说明它真的不含代码。实际上,它含有相当多的代码。

C++就对象创建和销毁时发生的事件有多方面的规定。条款5和M8介绍了当使用new时,动态创建的对象怎样自动地被它们的构造函数初始化,以及当使用delete时析构函数怎样被调用。条款13说明了当创建一个对象时,对象的每个基类以及对象的每个数据成员会被自动地创建;当对象被销毁时,会自动地执行相反的过程(即析构)。这些条款告诉你,C++规定了哪些必须发生,但没规定"怎么"发生。"怎么发生"取决于编译器的实现者,但要弄清楚的是,这些事件不是凭空自己发生的。程序中必然有什么代码使得它们发生,特别是那些由编译器的实现者写的、在编译其间插入到你的程序中的代码,必然也藏身于某个地方------有时,它们就藏身于你的构造函数和析构函数。所以,对于上面那个号称为空的Derived的构造函数,有些编译器会为它产生相当于下面的代码:

// 一个Derived构造函数的可能的实现
 Derived::Derived()
{
  // 如果在堆上创建对象,为其分配堆内存;
  // operator new的介绍参见条款8
  if (本对象在堆上)
    this = ::operator new(sizeof(Derived));


  Base::Base();                  // 初始化Base部分

  dm1.string();            // 构造dm1
  dm2.string();            // 构造dm2
  dm3.string();            // 构造dm3
}

别指望上面这样的代码可以通过编译,因为它在C++中是不合法的。首先,在构造函数内无法知道对象是不是在堆上。(想知道如何可靠地确定一个对象是否在堆上,请参见条款M27)另外,对this赋值是非法的。还有,通过函数调用访问构造函数也是不允许的。然而,编译器工作起来没这些限制,它可以随心所欲。但代码的合法性不是现在要讨论的主题。问题的要点在于,调用operator new(如果需要的话)的代码、构造基类部分的代码、构造数据成员的代码都会神不知鬼不觉地添加到你的构造函数中,从而增加构造函数的体积,使得构造函数不再适合内联。当然,同样的分析也适用于Base的构造函数,如果Base的构造函数被内联,添加到它里面的所有代码也会被添加到Derived的构造函数(Derived的构造函数会调用Base的构造函数)。如果string的构造函数恰巧也被内联,Derived的构造函数将得到其代码的5个拷贝,每个拷贝对应于Derived对象中5个string中的一个(2个继承而来,3个自己声明)。现在你应该明白,内联Derived的构造函数并非可以很简单就决定的!当然,类似的情况也适用于Derived的析构函数,无论如何都要清楚这一点:被Derived的构造函数初始化的所有对象都要被完全销毁。刚被销毁的对象以前可能占用了动态分配的内存,那么这些内存还需要释放。

程序库的设计者必须预先估计到声明内联函数带来的负面影响。因为想对程序库中的内联函数进行二进制代码升级是不可能的。换句话说,如果f是库中的一个内联函数,用户会将f的函数体编译到自己的程序中。如果程序库的设计者后来要修改f,所有使用f的用户程序必须重新编译。这会很令人讨厌(参见条款34)。相反,如果f是非内联函数,对f的修改仅需要用户重新链接,这就比需要重新编译大大减轻了负担;如果包含这个函数的程序库是被动态链接的,程序库的修改对用户来说完全是透明的。

内联函数中的静态对象常常表现出违反直觉的行为。所以,如果函数中包含静态对象,通常要避免将它声明为内联函数。具体介绍参见条款M26。

为了提高程序开发质量,以上诸项一定要牢记在心。但在具体编程时,从纯实际的角度来看,有一个事实比其余的因素都重要:大多数调试器遇上内联函数都会无能为力。

这不是什么新鲜事。你想,怎么在一个不存在的函数里设置断点呢?怎么单步执行到这样一个函数呢?怎么俘获对它的调用呢?除非你是个百年一遇的怪才,或者用了暗渡陈仓之类的伎俩,否则是不可能做到的。让人高兴的是,这一点倒是可以作为决定该不该对函数声明inline的决策依据之一。

一般来说,实际编程时最初的原则是不要内联任何函数,除非函数确实很小很简单,象下面这个age函数:

class Person {
public:
  int age() const { return personAge; }

  ...

private:
  int personAge;

  ...

};

慎重地使用内联,不但给了调试器更多发挥作用的机会,还将内联的作用定位到了正确的位置:它是一个根据需要而使用的优化工具。不要忘了从无数经验得到的这条80-20定律(参见条款M16):一个程序往往花80%的时间来执行程序中20%的代码。这是一条很重要的定律,因为它提醒你,作为程序员的一个很重要的目标,就是找出这20%能够真正提高整个程序性能的代码。你可以选择内联你的函数,或者没必要就不内联,但这些选择只有作用在"正确"的函数上才有意义。

一旦找出了程序中那些重要的函数,以及那些内联后可以确实提高程序性能的函数(这些函数本身依赖于所在系统的体系结构),就要毫不犹豫地声明为inline。同时,要注意代码膨胀带来的问题,并监视编译器的警告信息(参见条款48),看看是否有内联函数没有被编译器内联。

若能做到明智地使用,内联函数将是每个C++程序员百宝箱中的一件无价之宝。当然,正如前面的讨论所揭示的,它们并不象所想象的那样简单和直接。

c++中内联函数那点事 1 内联(inline)函数     c++在编译时可以讲调用的函数代码嵌入到主调函数中,这种嵌入到主调函数中的函数称为内联函数,又称为内嵌函数或内置函数。 定义内联函数时,在函数定义和函数原型声明时都使用inline,也可以只在其中一处使用,其效果一样。 内联函数在编译时用内联函数函数的函数体替换,所以不发生函数调用,不需要保护现场,恢复现场,节省了开销... 阅读详情

相关推荐

【C语言】内联函数

在使用内联函数时,记住它们最适合用在小函数,这些函数通常只有几行代码,并且在程序中频繁被调用时。5. 更改编译器:如果你使用的编译器不考虑内联请求或者对于内联的支持不充分,你可能需要考虑换一个有更好内联支持的编译器,或者使用支持更细粒度内联控制的编译器。4. 消除函数定义中的复杂性:编译器可能会选择不内联复杂函数,比如包含循环、递归或大量代码的函数。内联函数应当在它们被调用的文件中定义。4. 可移植性:依赖于编译器内联函数可能会影响代码的可移植性,因为不同编译器对内联的支持和实现方式可能不同。

数字人生 867

C++基础:inline函数

1.inline 函数 1.1定义:inline 函数由 inline 关键字定义,引入 inline 函数的主要原因是用它替代 C 中复杂易错不易维护的宏函数。 1.2编译器对 inline 函数的处理办法 编译器在编译阶段完成对 inline 函数的处理,即对 inline 函数的调用替换为函数的本体。但 inline 关键字对编译器只是一种建议,编译器可以这样去做,也可以不去做。从逻辑上来说,编译器对 inline 函数的处理步骤一般如下: (1)将 inline 函数体复制到inline..

weixin_58368590的博客 1万+

NC65增加自定义参照并且参照数据要根据选择的财务组织过滤

1、首先定义一个参照类 package nc.pub.jzcm.ref; import nc.ui.bd.ref.AbstractRefModel; import nc.ui.bd.ref.IRefDocEdit; import nc.ui.bd.ref.IRefMaintenanceHandler; public class PMPrPayDefaultRefModel extends AbstractRefModel { public PMPrPayDefaultRefModel(){ ...

xyl的博客 3495

C++ inline 函数

inline 函数由 inline 关键字定义,引入 inline 函数的主要原因是用它替代 C 中复杂易错、不易维护的宏函数。

Doublelv 的博客专栏。 4万+

Pcileech(DMA)开源项目简述

希望大DMA家能正确认识FPGA,正确使用DMA技术,对于市场上各类伪装成花里胡哨设备的FPGA板卡它的本质是不会变的,不要上当受骗。

2301_79773847的博客 5966

C++常见面试题总结

C++常见面试题总结

xu_fu_yong的专栏 8万+

Effective C++ 2e Item14

条款14: 确定基类有虚析构函数有时,一个类想跟踪它有多少个对象存在。一个简单的方法是创建一个静态类成员来统计对象的个数。这个成员被初始化为0,在构造函数里加1,析构函数里减1。(条款M26里说明了如何把这种方法封装起来以便很容易地添加到任何类中,“my article on counting objects”提供了对这个技术的另外一些改进)设想在一个军事应用程序里,有一个表示敌人目标的类

lostmouse的专栏 976

数据预处理的分箱操作

数据分箱 介绍 我们在建立模型前,一般需要对特征变量进行离散化,特征离散化后,模型会更稳定,降低模型过拟合的风险。尤其是采用 logsitic 建立评分卡模型时,必须对连续变量进行离散化。而特征离散化处理通常采用的就是分箱法,数据分箱(也称为离散分箱或分段)是一种数据预处理技术,用于减少次要观察误差的影响,提高泛化性。 数据分箱又分为有监督分箱和无监督分箱,是否使用标签进行离散化(分箱)决定了有监督还是无监督的离散化方法。 知识点 无监督分箱 split 分箱 merge 分箱 无监督分箱 这里为了

随笔记 5334

Effective C++ 2e Item46

条款46: 宁可编译和链接时出错,也不要运行时出错除了极少数情况下会使C++抛出异常(例如,内存耗尽 ---- 见条款7)外,运行时错误的概念和C++没什么关系,就象在C中一样。没有下溢,上溢,除零检查;没有数组越界检查,等等。一旦程序通过了编译和链接,你就得靠自己了 ---- 一切后果自负。这很象跳伞运动,一些人从中找到了刺激,另一些人则吓得摔成了残废。这一思想背后的动机当然在于效率:没有运

lostmouse的专栏 1167

【2026年华为暑期实习(AI)-6月3日-第一题- 选择题】(题目+思路+Java&C++&Python解析+在线测试)

本文摘要: 本文探讨了深度学习与计算机科学中的多个关键概念和技术,包括CUDA双缓冲技术、神经网络训练问题、优化算法、概率模型、Transformer架构特性、量化技术、损失函数选择等。通过20道选择题和多项选择题,涵盖了梯度消失/爆炸、神经元死亡、牛顿迭代法、朴素贝叶斯分类、Adam优化器、KV Cache压缩等核心知识点。特别关注了大模型训练中的计算效率、数值稳定性问题,以及实际应用场景如客服Agent的记忆机制优化。题目设计既考察基础理论(如概率分布、指数分布特性),又结合前沿技术(如MoE架构、注意

m0_73659489的博客 81

拆开它的“身体”,看懂割草机器人的五层系统架构

这篇文章深入解析了智能割草机器人的五层系统架构。机械层采用铝合金一体式车架和独立悬挂刀盘系统;电子层配备双处理器和多种传感器;固件层实现毫秒级安全响应;算法层融合多种定位技术实现厘米级精度;云服务层提供远程管理和AI优化。这套系统通过精密协同实现了智能割草的全流程闭环,展现了从机械结构到云端智能的完整技术栈,为户外智能设备设立了新的技术标杆。

智能硬件研发,欢迎交流 199

Effective C++ 2e Item20

条款20: 避免public接口出现数据成员首先,从“一致性”的角度来看这个问题。如果public接口里都是函数,用户每次访问类的成员时就用不着抓脑袋去想:是该用括号还是不该用括号呢?——用括号就是了!因为每个成员都是函数。一生中,这可以避免你多少次抓脑袋啊!你不买“一致性”的帐?那你总得承认采用函数可以更精确地控制数据成员的访问权这一事实吧?如果使数据成员为public,每个人都可以对它

lostmouse的专栏 989

微信小程序 - 详解苹果ios手机请求不到数据访问不了接口,但安卓却可以正常访问。wx.request苹果手机IOS请求失败、安卓可以请求成功(微信小程序上线后苹果手机不能访问接口,网络请求失败排查)

微信小程序上线后,苹果手机请求不到数据,安卓可以,微信小程序,安卓可以调接口,苹果调不了的解决方案,微信小程序:IOS访问不了接口,安卓可以正常访问,微信小程序ios版本连接不了服务器,小程序Android可以请求,ios请求失败wx.request,成功上线的小程序,安卓机可以正常访问使用,苹果微信小程序不执行请求,苹果系统无法加载数据,微信小程序 iOS 网络失败,微信小程序接口请求在ios端报错,苹果手机真机调试微信小程序出现网络请求失败,微信小程序 安卓正常 ios调接口显示网络请求失败,微信小程序

🏆 前端领域优质创作者 4369

Effective C++ Item 33 避免遮掩继承过来的名称

经验:derived classes 内的名称会遮掩 base classes 内的名称。在 public 继承下从来没有人希望如此。 C++ 的名称遮掩规则所做的唯一事情就是: 遮掩名称 derived class 作用域被嵌套在 base class 作用域里 class Base { private: int x; public: virtual void mf1() = 0; virtual void mf1(int); virtual void mf2(); void mf3(); v

953

RAG效果调优清单(命中率提升3倍)

本文提出了一套工程可落地的RAG(检索增强生成)模型组合方案,强调通过最优组合实现效果、成本和扩展性的平衡。核心架构包括Embedding模型、向量数据库检索、Rerank模型和LLM生成四个环节,推荐了四种具体组合方案:本地最优性价比(bge+reranker+Qwen 7B)、效果优先(bge-large+reranker+14B模型)、高并发(轻量模型+vLLM)和中英混合(多语言模型)。文章指出Embedding决定检索下限,Rerank提升准确率上限,LLM影响最终体验,并提供了不同场景的优化策略

刘一说的IT专栏 523

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

[翻译] Effective C++, 3rd Edition, 附录 B. 第二和第三版之间的 Item 映射

附录 B. 第二和第三版之间的 Item 映射作者:Scott Meyers译者:fatalerror99 (iTePubs Nirvana)发布:http://blog.youkuaiyun.com/fatalerror99/Effective C++ 的第三版在很多方面与第二版不同,其中最引人注目的是它包含很多新的信息。然而,第二版的大部分内容依然保留在第三版中,虽然经常会改变形式和位置

艺术编程 5819

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

[翻译] Effective C++, 3rd Edition, Item 33: 避免覆盖(hiding)“通过继承得到的名字”(上)

Item 33: 避免覆盖(hiding)“通过继承得到的名字”作者:Scott Meyers译者:fatalerror99 (iTePubs Nirvana)发布:http://blog.youkuaiyun.com/fatalerror99/莎士比亚有一个关于名字的说法。"Whats in a name?" 他问道,"A rose by any other name would sme

艺术编程 4661

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Effective C++ Item 36 绝不重新定义继承而来的 non-virtual 函数

经验:绝对不要重新定义继承而来的 non-virtual 函数 --> Item 7 "为多态基类声明 virtual 析构函数" 是本条款的特例 示例: class B{ public: void mf(); //... }; class D: public B{ public: void mf(); // 遮掩了B::mf,Item 33 名称遮掩规则 } D x; B *pB = &x; D *pD = &x; pB->mf(); // 调用 B::mf pD->mf(); // 调用

880

More Effective C++ Item M32:在未来时态下开发程序

2. 杂项我们现在到了接近结束的部分了,这章讲述的是一些不属于前面任一章节的指导原则。开始两个是关于C++软件开发的,描述的是设计适应变化的系统。面向对象的一个强大之处是支持变化,这两个条款描述具体的步骤来增强你的软件对变化的抵抗能力。然后,我们分析怎么在同一程序中进行C和C++混合编程。这必然导致考虑语言学之外的问题,但C++存在于真实世界中,所以有时我们必须面对这种事情。最后,我将概述C++语

taodm的专栏 1064

Effective C++ Item 36 绝不又一次定义继承而来的 non-virtual 函数

本文为senlie原创。转载请保留此地址:http://blog.youkuaiyun.com/zhengsenlie 经验:绝对不要又一次定义继承而来的 non-virtual 函数 --> Item 7 "为多态基类声明 virtual 析构函数" 是本条款的特例 演示样例: class B{ public: void mf(); //... }; class D: p...

weixin_34029680的博客 97

Effective c++ Item 28 不要返回指向对象内部数据(internals)的句柄(handles)

假设你正在操作一个Rectangle类。每个矩形可以通过左上角的点和右下角的点来表示。为了保证一个Rectangle对象尽可能小,你可能决定不把定义矩形范围的点存储在Rectangle类中,而是把它放入一个辅助结构体中,Rectangle中声明一个指向它的指针就可以了: 1 class Point { // class for repre

小小小程序员的博客 218

more effective c++——Item M29 引用计数(三)带引用计数的基类的实现

带引用计数的基类实现:Cstring类的实现与通过增加中间层来为不可修改源代码的实现引用计数功能

卢然小子的博客 812

读书笔记 effective c++ Item 43 了解如何访问模板化基类中的名字

1. 问题的引入——派生类不会发现模板基类中的名字 假设我们需要写一个应用,使用它可以为不同的公司发送消息。消息可以以加密或者明文(未加密)的方式被发送。如果在编译阶段我们有足够的信息来确定哪个信息会被发送到哪个公司,我们可以使用基于模板的解决方案: 1 class CompanyA { 2 public: 3 ... 4 void sendCleartext(cons...

weixin_34406086的博客 116
上一篇: Effective C++ 2e Item32
下一篇: Effective C++ 2e Item34
lostmouse
博客等级 码龄26年 22粉丝 63原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值