C++面试题目汇总三

1.main()之前执行的代码

GCC
gcc中可以使用attribute关键字,声明constructor和destructor函数。
vc
vc中不支持attribute,可插入函数进入初始化函数列表__xi_a,__xi_z__xc_a,__xc_z由初始化CRTInit()调用。
C++
全局变量的构造函数在main之前;
int g_iValue=func;写在func里面然后去初始化全局变量。

2.缓冲区溢出

  缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量溢的数据覆盖在合法数据上。
  危害:在当前网络与分布式系统安全中,被广泛利用的 50%以上都是缓冲区溢出,其中最著名的例子是 1988 年利用 fingerd 漏洞的蠕虫。 而缓冲区溢出中, 最为危险的是堆栈溢出,因为入侵者可以利用堆栈溢出,在函数返回时改变返回程序的地址,让其跳转到任意地址,带来的危害一种是程序崩溃导致拒绝服务, 另外一种就是跳转并且执行一段恶意代码, 比如得到 shell,然后为所欲为。通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其它指令,以达到攻击的目的。
  造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数 。
解决方法
1.在程序的自由存储区中创建并使用动态分配的数组,在C语言中使用malloc(C++中为new)操作符实现,在自由存储空间中创建的动态数组对象是没有名字的,程序员只能通过其地址间接访问堆中的对象。如果程序员能够准备计算出运行时需要的数组长度,就不必再担心因数组变量具有固定的长度而造成的溢出问题。
2. 在C++程序中,采用vector类型和迭代器取代一般的数组和指针访问。利用end操作可以返回迭代器指向vector“末端元素的下一个”,从而充当一个“哨兵”的作用,防止越界调用。

3.Unicode,UTF-8和UTF-16的区别

UTF-16比较好理解,就是任何字符对应的数字都用两个字节来保存。
UTF-8表示一个字符是可变的,有可能是用一个字节表示一个字符,也可能是两个、三个…反正是根据字符对应的数字大小来确定。

4.构造函数中为什么不能调用虚函数

  构造函数中调用一个虚函数的情况,被调用的只是这个函数的本地版本。也就是说,虚机制在构造函数中不工作。
  在构造函数中,虚拟机制不会发生作用,因为基类的构造函数在派生类构造函数之前执行,当基类构造函数运行时,派生类数据成员还没有被初始化。如果基类构造期间调用的虚函数向下匹配到派生类,派生类的函数理所当然会涉及本地数据成员,但是那些数据成员没有被初始化,而调用涉及一个对象还没有被初始化的部分自然是很危险的,所以C++会提示此路不通。因此,虚函数不会向下匹配到派生类,而是直接执行基类的函数。

5.程序崩溃的原因

1.读取未赋值的变量
一个变量未初化、未赋值,就读取它的值。( 这属于逻辑问题,往往是粗心大意的导致的 )
2.函数栈溢出
(1)定义了一个体积太大的局部变量,当变量体积太大时,应该用malloc或new来动态分配内存;
(2)函数嵌套调用,层次过深(如无穷递归)
3.数组越界访问
访问数组元素时,下标越界
4.指针的目标对象不可用
(1)空指针
(2)野指针:指针未赋值;free/delete释放了的对象;不恰当的指针强制转换。

6.进程间通讯的四种方式

1.管道(pipe)
  管道是一种具有两个端点的通信通道,一个管道实际上就是只存在在内存中的文件,对这个文件操作需要两个已经打开文件进行,他们代表管道的两端,也叫两个句槟,管道是一种特殊的文件,不属于一种文件系统,而是一种独立的文件系统,有自己的数据结构,根据管道的使用范围划分为无名管道和命名管道。
无名管道用于父进程和子进程之间,通常父进程创建管道,然后由通信的子进程继承父进程的读端点句柄和写端点句柄,或者父进程有读写句柄的子进程,这些子进程可以使用管道直接通信,不需要通过父进程。
命名管道,命名管道是为了解决无名管道只能在父子进程间通信而设计的,命名管道是建立在实际的磁盘介质或文件系统(而不是只存在内存中),任何进程可以通过文件名或路径建立与该文件的联系,命名换到需要一种FIFO文件(有先进先出的原则),虽然FIFO文件的inode节点在磁盘上,但仅是一个节点而已,文件的数据还是存在于内存缓冲页面中,和普通管道相同。
2.信号
  信号,用于接受某种事件发生,除了用于进程间通信之外,进程还可以发送信号给进程本身。除了系统内核和root之外,只有具备相同id的进程才可以信号进行通信。
3.消息队列
  消息队列是消息的链表,包括Posix消息队列和system v消息队列(Posix常用于线程,system常用于进程),有权限的进程可以向消息队列中添加消息,有读权限的进程可以读走消息队列的消息。
消息队列克服了信号承载信息量少,管道只能承载无格式字节流及缓冲区大小受限等缺陷。
4.共享内存
  共享内存使多个进程可以访问同一块内存空间,是最快的IPC形式,是针对其他通信方式运行效率低而设计的,往往与其他进程结合使用,如与信号量结合,来达到进程间的同步与互斥。传递文件最好用共享内存的方式。

7.内联函数失效,使用内联函数的一些限制

类的构造函数不能是虚函数;
类的静态成员函数不能是虚函数;
类的虚函数不能是内联函数;
内联函数不可以做为虚函数(内联函数,构造函数,静态函数时都不能为虚函数的)
内联函数的特点:

一、关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前面不起任何作用。

二、定义在类声明之中的成员函数将自动地成为内联函数。

内联函数的作用:

  1. 替代宏,增加代码可读性。

  2. 提高代码执行效率。这点和宏的作用相同。原因在于,省略了函数参数压栈出栈和跳转指令了。

  3. 使用于部分特殊场合。例如,软件注册码检测。这时候,必须多次检测,而又不能使用一个非内联的检测函数,因为那样一来,破解一处,就等于把软件彻底破解了,所以,使用内联检测,增加破解难度。

内联函数的缺点:增加了编译后的二进制文件的大小。

使用内联函数的一些限制:
1、内联函数中不可含有循环;
2、内联函数中不可含有switch语句;
3、内联函数中不可能含有静态变量;
4、内联函数不可为递归函数;
5、内联函数中不可含有错误处理;
6、如果内联函数调用了其他函数也不会被内联。
7、虚拟函数一般不会内联,但是如果编译器能在编译时确定具体的调用函数,那么仍然会就地展开该函数;
8、如果通过函数指针调用内联函数,那么该函数将不会内联而是通过call进行调用。

使用内联函数的一些注意事项:
1、只在Release版本生效;
2、是基于实现,不是基于声明。

8.什么函数不能是虚函数

类的构造函数不能是虚函数;
类的静态成员函数不能是虚函数;
类的虚函数不能是内联函数;
内联函数不可以做为虚函数(内联函数,构造函数,静态函数时都不能为虚函数的)

9.迭代器失效的情况

10.指针和引用的区别

1、指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元,即指针是一个实体;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已;
2、可以有const指针,但是没有const引用;
3、指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的);
4、指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
5、”sizeof引用”得到的是所指向的变量(对象)的大小,而”sizeof指针”得到的是指针本身的大小;

总的来说,在以下情况下你应该使用指针:
1、是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空);
2、是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。
3、还有一种情况,就是当你重载某个操作符时,你应该使用引用。

11.析构函数可以抛出异常吗

调用栈
别让异常逃离析构函数

12.指出下面中断服务函数的错误

__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf(“/nArea = %f”, area);
return area;
}

这段代码基本上把上面提到的4大注意点全都无视了
第一行,传入参数了;
第二行,在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算;
第三行,使用printf()函数带来的重入性和性能问题;
第四行,ISR中不能有返回值;
整体上,ISR应该是短小精悍的,所以在ISR中做浮点运算是不明智的;

13.TCP/IP粘包问题解决

  • 怎么理解TCP是面向字节的, TCP是面向字节流的协议,就是没有界限的一串数据,本没有“包”的概念,“粘包”和“拆包”一说是为了有助于形象地理解这两种现象。
  • 为什么UDP没有粘包?
    粘包拆包问题在数据链路层、网络层以及传输层都有可能发生。日常的网络应用开发大都在传输层进行,由于UDP有消息保护边界,不会发生粘包拆包问题,因此粘包拆包问题只发生在TCP协议中。
  • 粘包拆包发生场景
    因为TCP是面向流,没有边界,而操作系统在发送TCP数据时,会通过缓冲区来进行优化,例如缓冲区为1024个字节大小。如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,这就形成了粘包问题。如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包。
  • 对于粘包和拆包问题,常见的解决方案有四种:
    发送端将每个包都封装成固定的长度,比如100字节大小。如果不足100字节可通过补0或空等进行填充到指定长度;
    发送端在每个包的末尾使用固定的分隔符,例如\r\n。如果发生拆包需等待多个包发送过来之后再找到其中的\r\n进行合并;例如,FTP协议;
    将消息分为头部和消息体,头部中保存整个消息的长度,只有读取到足够长度的消息之后才算是读到了一个完整的消息;
    通过自定义协议进行粘包和拆包的处理。
  • 发送端为了将多个发往接收端的包,更加高效的的发给接收端,于是采用了优化算法(Nagle算法),将多次间隔较小、数据量较小的数据,合并成一个数据量大的数据块,然后进行封包
    TCP粘包原因及解决办法

14.内存泄漏

什么是内存泄漏

简单来说就是:在程序中申请了动态内存,却没有释放,如果程序长期运行下去,最终会导致没有内存可供分配。

如何检测

检测内存泄露的方法:

  • 手动检查代码:仔细检查代码中的内存分配和释放,确保每次分配内存后都有相应的释放操作。比如 malloc和free、new和delete是否配对使用了。
  • 使用调试器和工具:有一些工具可以帮助检测内存泄露。例如:
    Valgrind(仅限于Linux和macOS):Valgrind是一个功能强大的内存管理分析工具,可以检测内存泄露、未初始化的内存访问、数组越界等问题。使用Valgrind分析程序时,只需在命令行中输入valgrind --leak-check=yes your_program即可。
    Visual Studio中的CRT(C Runtime)调试功能:Visual Studio提供了一些用于检测内存泄露的C Runtime库调试功能。例如,_CrtDumpMemoryLeaks函数可以在程序结束时报告内存泄露。
    AddressSanitizer:AddressSanitizer是一个用于检测内存错误的编译器插件,适用于GCC和Clang。要启用AddressSanitizer,只需在编译时添加-fsanitize=address选项。
如何避免内存泄露
  • 使用智能指针(C++):在C++中,可以使用智能指针(如std::unique_ptr和std::shared_ptr)来自动管理内存。这些智能指针在作用域结束时会自动释放所指向的内存,从而降低忘记释放内存或者程序异常导致内存泄露的风险。

  • 异常安全:在C++中,如果程序抛出异常,需要确保在异常处理过程中正确释放已分配的内存。使用try-catch块来捕获异常并在适当的位置释放内存。 或者使用RAII(Resource Acquisition Is Initialization)技术(关于RAII可以看这篇文章: 如何理解RAII (opens new window),将资源(如内存)的管理与对象的生命周期绑定。

  • 一般会对返回值进行报错,出错就goto到最后面去释放空间。

作者: 编程指北
链接: https://csguide.cn/cpp/memory/memory_leak.html#%E5%A6%82%E4%BD%95%E6%A3%80%E6%B5%8B
来源: https://csguide.cn

15.程序出现段错误你是如何解决的?

我说了C++常用的try,但我用的最多的还是printf。
段错误的解决方法

dump用windbg

c语言段错误的解决方法

在C语言中,任何操作指令都离不开对内存的操作,所以即便编译的时候没有语法操作,但是在实际运行中有可能对内存进行非法操作,这种情况就会产生段错误Segmentation fault (core dumped)!要解决段错误就要先找到段错误的地方。
如何在程序中寻找段错误?
段错误不是语法错误,所以在编译时不会提示出错,只有等到运行时才会提示出现段错误,但是段错误不会提示在哪一行,可以通过printf()函数来寻找段错误位置,只要发生段错误,那么程序就会马上结束。

举个例子:
printf(“11111!\n”);
xxxx;
printf(“22222!\n”);
yyyy;
printf(“33333!\n”);
zzzz;

假如运行上述代码得到的执行结果为:

11111!
22222!
Segmentation fault (core dumped) -> 说明段错误是出现"yyyy;"
段错误一般是指针指向有问题,找到段错误的地方最好打印出指针内容看看是不是自己预期的指针内容再进行修改,如果是链表就画图查看自己的链表逻辑有没有出问题
总结解决段错误的步骤:
1.使用printf()函数寻找段错误的地方
2.打印出现段错误的指针,链表或者打开文件目录的返回值看看是不是自己的预期结果
3.根据结果现在修改代码重新编译

16.单例模式

单例类的特点

  • 构造函数和析构函数为私有类型,目的是禁止外部构造和析构。
  • 拷贝构造函数和赋值构造函数是私有类型,目的是禁止外部拷贝和赋值,确保实例的唯一性。
  • 类中有一个获取实例的静态方法,可以全局访问。

懒汉模式

  • 线程不安全
///  普通懒汉式实现 -- 线程不安全 //
#include <iostream> // std::cout
#include <mutex>    // std::mutex
#include <pthread.h> // pthread_create

class SingleInstance
{

public:
    // 获取单例对象
    static SingleInstance *GetInstance();

    // 释放单例,进程退出时调用
    static void deleteInstance();
	
	// 打印单例地址
    void Print();

private:
	// 将其构造和析构成为私有的, 禁止外部构造和析构
    SingleInstance();
    ~SingleInstance();

    // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
    SingleInstance(const SingleInstance &signal);
    const SingleInstance &operator=(const SingleInstance &signal);

private:
    // 唯一单例对象指针
    static SingleInstance *m_SingleInstance;
};

//初始化静态成员变量
SingleInstance *SingleInstance::m_SingleInstance = NULL;

SingleInstance* SingleInstance::GetInstance()
{

	if (m_SingleInstance == NULL)
	{
		m_SingleInstance = new (std::nothrow) SingleInstance;  // 没有加锁是线程不安全的,当线程并发时会创建多个实例
	}

    return m_SingleInstance;
}

void SingleInstance::deleteInstance()
{
    if (m_SingleInstance)
    {
        delete m_SingleInstance;
        m_SingleInstance = NULL;
    }
}

void SingleInstance::Print()
{
	std::cout << "我的实例内存地址是:" << this << std::endl;
}

SingleInstance::SingleInstance()
{
    std::cout << "构造函数" << std::endl;
}

SingleInstance::~SingleInstance()
{
    std::cout << "析构函数" << std::endl;
}
///  普通懒汉式实现 -- 线程不安全  //

// 线程函数
void *PrintHello(void *threadid)
{
    // 主线程与子线程分离,两者相互不干涉,子线程结束同时子线程的资源自动回收
    pthread_detach(pthread_self());

    // 对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取
    int tid = *((int *)threadid);

    std::cout << "Hi, 我是线程 ID:[" << tid << "]" << std::endl;

    // 打印实例地址
    SingleInstance::GetInstance()->Print();

    pthread_exit(NULL);
}

#define NUM_THREADS 5 // 线程个数

int main(void)
{
    pthread_t threads[NUM_THREADS] = {0};
    int indexes[NUM_THREADS] = {0}; // 用数组来保存i的值

    int ret = 0;
    int i = 0;

    std::cout << "main() : 开始 ... " << std::endl;

    for (i = 0; i < NUM_THREADS; i++)
    {
        std::cout << "main() : 创建线程:[" << i << "]" << std::endl;
        
		indexes[i] = i; //先保存i的值
		
        // 传入的时候必须强制转换为void* 类型,即无类型指针
        ret = pthread_create(&threads[i], NULL, PrintHello, (void *)&(indexes[i]));
        if (ret)
        {
            std::cout << "Error:无法创建线程," << ret << std::endl;
            exit(-1);
        }
    }

    // 手动释放单实例的资源
    SingleInstance::deleteInstance();
    std::cout << "main() : 结束! " << std::endl;
	
    return 0;
}
  • 线程安全,要同步影响效率和并发
///  加锁的懒汉式实现  //
class SingleInstance
{

public:
    // 获取单实例对象
    static SingleInstance *&GetInstance();

    //释放单实例,进程退出时调用
    static void deleteInstance();
	
    // 打印实例地址
    void Print();

private:
    // 将其构造和析构成为私有的, 禁止外部构造和析构
    SingleInstance();
    ~SingleInstance();

    // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
    SingleInstance(const SingleInstance &signal);
    const SingleInstance &operator=(const SingleInstance &signal);

private:
    // 唯一单实例对象指针
    static SingleInstance *m_SingleInstance;
    static std::mutex m_Mutex;
};

//初始化静态成员变量
SingleInstance *SingleInstance::m_SingleInstance = NULL;
std::mutex SingleInstance::m_Mutex;

SingleInstance *&SingleInstance::GetInstance()
{

    //  这里使用了两个 if判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,
    //  避免每次调用 GetInstance的方法都加锁,锁的开销毕竟还是有点大的。
    if (m_SingleInstance == NULL) 
    {
        std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
        if (m_SingleInstance == NULL)
        {
            m_SingleInstance = new (std::nothrow) SingleInstance;
        }
    }

    return m_SingleInstance;
}

void SingleInstance::deleteInstance()
{
    std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
    if (m_SingleInstance)
    {
        delete m_SingleInstance;
        m_SingleInstance = NULL;
    }
}

void SingleInstance::Print()
{
	std::cout << "我的实例内存地址是:" << this << std::endl;
}

SingleInstance::SingleInstance()
{
    std::cout << "构造函数" << std::endl;
}

SingleInstance::~SingleInstance()
{
    std::cout << "析构函数" << std::endl;
}
///  加锁的懒汉式实现  //

  • 内部静态变量的懒汉单例
///  内部静态变量的懒汉实现  //
class Single
{

public:
    // 获取单实例对象
    static Single &GetInstance();
	
	// 打印实例地址
    void Print();

private:
    // 禁止外部构造
    Single();

    // 禁止外部析构
    ~Single();

    // 禁止外部复制构造
    Single(const Single &signal);

    // 禁止外部赋值操作
    const Single &operator=(const Single &signal);
};

Single &Single::GetInstance()
{
    // 局部静态特性的方式实现单实例
    static Single signal;
    return signal;
}

void Single::Print()
{
	std::cout << "我的实例内存地址是:" << this << std::endl;
}

Single::Single()
{
    std::cout << "构造函数" << std::endl;
}

Single::~Single()
{
    std::cout << "析构函数" << std::endl;
}
///  内部静态变量的懒汉实现  //

  • 饿汉式单例 (本身就线程安全)
// 饿汉实现 /
class Singleton
{
public:
    // 获取单实例
    static Singleton* GetInstance();

    // 释放单实例,进程退出时调用
    static void deleteInstance();
    
    // 打印实例地址
    void Print();

private:
    // 将其构造和析构成为私有的, 禁止外部构造和析构
    Singleton();
    ~Singleton();

    // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
    Singleton(const Singleton &signal);
    const Singleton &operator=(const Singleton &signal);

private:
    // 唯一单实例对象指针
    static Singleton *g_pSingleton;
};

// 代码一运行就初始化创建实例 ,本身就线程安全
Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton;

Singleton* Singleton::GetInstance()
{
    return g_pSingleton;
}

void Singleton::deleteInstance()
{
    if (g_pSingleton)
    {
        delete g_pSingleton;
        g_pSingleton = NULL;
    }
}

void Singleton::Print()
{
    std::cout << "我的实例内存地址是:" << this << std::endl;
}

Singleton::Singleton()
{
    std::cout << "构造函数" << std::endl;
}

Singleton::~Singleton()
{
    std::cout << "析构函数" << std::endl;
}
// 饿汉实现 /

17.TCP的拥塞控制

  • 慢启动算法
    从发送窗口设置成拥塞窗口和对方接收窗口的最小值,指数型增加 c w n d = i n i t c w n d ∗ 2 n cwnd=initcwnd*2^n cwnd=initcwnd2n
  • 拥塞避免算法
    维护一个慢启动阈值ssthresh,当cwnd<ssthresh时,拥塞窗口使用慢启动算法,按指数级增长。当cwnd>ssthresh时,拥塞窗口使用拥塞避免算法,按线性增长。
    当发生拥塞的时候(超时或者收到重复ack),RFC5681认为此时ssthresh需要置为没有被确认包的一半,但是不小于两个MSS。此外,如果是超时引起的拥塞,则cwnd被置为initcwnd。
  • 快重传算法
    当发送方收到3个或以上重复确认(Dup Ack)时,就意识到相应的包已经丢了,从而立即重传它。这个过程称为快速重传。
    TCP在发送重复的Ack包的时候,会告诉接收方收到的已经收到包的序号。
  • 快恢复算法
    如果在拥塞阶段发生了快速重传就没有必要像超时重传那样处理拥塞窗口了,因为此时的拥塞并不是很严重。RFC5681建议此时的慢启动阈值ssthreh设置为没有被确认包的1/2,但是不小于2个MSS。拥塞窗口设置为慢启动阈值加3个MSS。这个过程被称为快速恢复。

进程终止方式

  • 从main返回
  • 调用exit
  • 调用_exit
  • 调用abort
  • 由一个信号终止

线程和进程的区别

进程的创建

  • fork函数以父进程为蓝本复制一个进程,在linux的环境下,fork()是以写复制实现,只有
    fork作为一个函数被调用。这个函数会有两次返回,将子进程的PID返回给父进程,0返回给子进程。(如果小于0,则说明创建子进程失败)。再次说明:当前进程调用fork(),会创建一个跟当前进程完全相同的子进程(除了pid),所以子进程同样是会执行fork()之后的代码。
    写时复制的原理
    ork()之后,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会 把触发的异常的页复制一份,于是父子进程各自持有独立的一份。
  • system 开启进程执行shell命令
  • exec()

线程的创建

  • pthread_create()
  • pthread_join()
  • pthread_exit()
  • 分离线程
    线程退出后,需对其进行pthread_join操作,否则无法释放资源,从而造成系统泄露。
    如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

直线拟合

free指向

在c语言中,通过malloc函数动态申请的内存空间,若申请成功,则会返回一个指向该内存地址的指针,如果申请失败,则会返回NULL;若不再使用,则用free函数释放掉该指针指向的内存。
可以看出,通过调用函数free()释放后,指向动态开辟地址的指针p还存在,并且仍指向原地址;但所指地址原来的内容10已经被free()释放掉了,现在是乱码了。

栈溢出

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值