C++/Qt面试题

9 篇文章 0 订阅

文章目录

面试题汇总


1. 内存泄漏分析工具

C++内存泄漏检查工具——Valgrind(–tool = memcheck)

https://blog.csdn.net/qq_33271192/article/details/108696138

2. 发布后崩溃处理

core_ dump文件 包括dump文件怎么用
https://blog.csdn.net/weixin_43935474/article/details/113814220

3.创建子进程

QProcess

4.代码移植 windows->Linux 文本换行符 怎么解决 CRLF

  • Unix系统里,每行结尾只有“<换行>“,即”\n";windows系统里面,每行结尾是"<换行><回车>" ,即”\n\r"。

  • 一个直接的后果是,Unix的文件在windows打开的话,所有的文字会变成一行;windows文件在Unix里打开的话,在每行的结尾可能会多出一个^M符号。但这个符号通常是直接看不出来的,可以用命令cat -A filename 来查看。

注:主要在linux中解决由于文件结尾产生的错误

  • 1、确认代码无误
  • 2、利用命令“vi -b [filename] ”查看结尾是否多了“^M”
  • 3、文件格式转换
方式一:dos2unix
   
下载并安装dos2unix
利用命令“dos2unix [filename]”完成转换
 
方式二:替换(vim+正则表达式)

利用命令“vim -b [filename]”打开该文件
在命令模式下输入:“%s/^M//g”或者“g/\^M/s/\^M//”
保存并退出

注意:方式二中“^M”的输入方式:ctrl+v+m

5. 单元测试

Qt::QTestLib单元测试框架

https://blog.csdn.net/ipfpm/article/details/109852908?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-109852908-blog-111667673.pc_relevant_aa&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-109852908-blog-111667673.pc_relevant_aa&utm_relevant_index=1

6. Gdb调试

对core_dump进行调试

dump调试。

7. C++11对单例模式 的优化?

  • 懒汉模式
  • 饿汉模式(双检锁) 内存乱序

https://www.cnblogs.com/zxh1210603696/p/4157294.html

 atomic<Widget*> Widget::pInstance{ nullptr };
 Widget* Widget::Instance() {
     Widget* p = pInstance;
     if (p == nullptr) { 
        lock_guard<mutex> lock{ mutW }; 
        if ((p = pInstance) == nullptr) { 
            pInstance = p = new Widget(); 
        }
    } 
     return p;
}

可以看出上面的代码相比较之前的示例代码来说已经相当的简洁了,但是!!!有是但是!!!!在C++memory model中对static local variable,说道:The initialization of such a variable is defined to occur the first time control passes through its declaration; for multiple threads calling the function, this means there’s the potential for a race condition to define first.因此,我们将会得到一份最简洁也是效率最高的单例模式的C++11实现:

1 widget& widget::get_instance() {
2     static widget instance;
3     return instance;
4 }

8. QTableView 和 QTableWidget 的区别

QTableWidget是QTableView的子类。
主要的区别是QTableView可以使用自定义的数据模型来显示内容(也就是先要通过setModel来绑定数据源),而QTableWidget则只能使用标准的数据模型,并且其单元格数据是QTableWidgetItem的对象来实现的(也就是不需要数据源,将逐个单元格内的信息填好即可)。
这主要体现在QTableView类中有setModel成员函数,而到了QTableWidget类中,该成员函数变成了私有。
使用QTableWidget就离不开QTableWidgetItem。
QTableWidgetItem用来表示表格中的一个单元格,正个表格都需要用逐个单元格构建起来。

9. C++11 智能指针?

Shared_ptr
weak_ptr
unique_ptr
auto_ptr

10.TCP粘包怎么处理?

粘包的处理方式:

    a)	当时短连接的情况下,不用考虑粘包的情况
    b)	如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包
    c)	如果双方建立长连接,需要在连接后一段时间内发送不同结构数据
    d)	发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
    e)	发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
    f)	可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
    g)	等等

11. 析构函数不写virtual会出现什么问题?

11.1 为什么要用虚函数?

  • C++中的虚函数的作用主要是实现了 多态的机制。关于多态,简而言之就是 用父类类型的指针指向其子类的实例 ,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。Java中的多态是通过interface和abstract来实现的,java没有virtual一说!
  • 定义一个函数为虚函数,不代表函数为不被实现的函数。 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。而在Java中,只要子类实现了interface中定义的函数,那么接口声明的引用一定会调用子类的这个函数,如果子类没有定义,则调用接口中的函数!
  • 定义一个函数为纯虚函数,才代表函数没有被实现。定义他是为了实现一个接口,起到一个规范的作用,规范继承这个。类的程序员必须实现这个函数。

11.2 析构函数不写virtual会出现什么问题?

父类指针实例化子类对象时,析构父类指针,未进子类析构函数中,内存泄漏

12. I/O库的并发、 多路复用

https://blog.csdn.net/aozhan8497/article/details/102348065

13. 内存模型

https://blog.csdn.net/qq_41278986/article/details/117532897

https://www.codedump.info/post/20191214-cxx11-memory-model-1/
https://www.codedump.info/post/20191214-cxx11-memory-model-2/

《深度探索c++对象模型》

14. 信号量与互斥锁的关系

https://blog.csdn.net/feng__shuai/article/details/108531439

使用说明:

  • 如果涉及到多线程对同一个资源进行访问,比如一个全局变量,那么就使用mutex
  • 如果涉及到多个线程同步问题就用信号量semaphore,比如利用多线程做一个简单的数列加法

解释:

  • 很多人闹不清楚互斥锁和信号量的区别,觉得二者都是对资源加锁的操作,其实二者真正的区别是
    互斥量是实现一个原子操作,也就是避免不同线程同时访问一个共享资源,导致出现异常;而信号量则是为了线程同步所设计的。

  • 可以看出信号量是用来线程间同步用的,当信号量是二进制信号量的时候,也能用来充当mutex,

  • 但是强烈建议不要这么使用,比如下面这个也能起到线程互斥的效果,但是按设计初衷来使用最好最安全。

15. STL 分配器第二个参数

T *

16. 多线程数据同步问题

16.1 C++11 多线程学习

https://www.jianshu.com/p/81f0b071b3e0

  1. 线程 std::thread
#include <thread>

void func(){}

int main()
{
	std::thread thread(func);
	thread.join();	//程序等待线程处理接收后再结束

	//thread.detach();	//剥离线程 将子线程和主线程剥离开来	一般用join()
}

  1. 互斥锁 std::mutex 及 原子量 std::atomic

  2. 条件变量condition_variable 及 信号量 semaphore

std::condition_variable var	//条件变量

var.notify_all();  var.notify_one();	//通知其他
var.wait(lock);			//阻塞 相当于lock.unlock();   var.wait();


std::semaphore	//C++20 信号量
  1. 承诺未来: std::promise std::future

  2. std::packaged_task std::async

16.2 线程间数据同步的简单演示模型(std::condition_variable)
生产者消费者模型

条件变量做了如下操作:


//wait传入的mutex互斥量必须为已加锁的
condition::wait(mutex)
{
	...
	mutex.unlock();				//1.对mutex进行解锁
	WaitForSingleObjectEx(...)	//2. 调用操作系统API 进行事件循环 阻塞在此处

	//3.事件循环结束
	mutex.lock();				//再对mutex进行加锁
}

//唤醒所有线程
//
condition::notify_all()
{
	 // wake up the all threads in the queue
    QMutexLocker locker(&d->mtx);
    for (int i = 0; i < d->queue.size(); ++i) {
        QWaitConditionEvent *current = d->queue.at(i);
        SetEvent(current->event);
        current->wokenUp = true;
    }
}

https://blog.csdn.net/lirongrong128/article/details/124434165

#include <condition_variable>
#include <iostream>
#include <thread>
#include <mutex>
 
static int value  = 0;			// 产品数量
static bool ready = false;		// 产品准备好标志
std::mutex lock_mutex;			// 线程互斥量 
std::condition_variable c_var;  // 条件变量
 
// 工厂生产产品
void funB()
{
	while (true){
		std::unique_lock <std::mutex> lock(lock_mutex);
 
		// 生产产品过程
		std::cout << "生产了产品,库存为:" << ++value << std::endl;
 
		// 产品生产完毕
		ready = true;
		c_var.notify_all(); // 通知销售部可以卖产品了
		c_var.wait(lock);   // 假设最大库存为1个,工厂停工,仓库满了		
	}
}
 
// 销售产品
void funC()
{
	while (true){
		std::unique_lock <std::mutex> lock(lock_mutex);
 
		if (!ready) {
			c_var.wait(lock);  // 等待产品生产完毕
		}
 
		// 产品卖出去了
		std::cout << "卖出了产品,库存为:" << --value << std::endl;
 
		// 通知工厂赶紧生产,库存为空啦
		ready = false;
		c_var.notify_all();
 
	}
}
 
// 主函数
int main(int argc, char *argv[])
{
	std::thread fun_b_thread(funB);
	std::thread fun_c_thread(funC);
 
	fun_b_thread.join();
	fun_c_thread.join();
 
	return 0;
}

16.3 线程池的实现

17. 线程池

18. 内存模型,虚表,内存序,编译器乱序,分支预测

18.1 内存模型

https://www.codedump.info/post/20191214-cxx11-memory-model-1/
https://www.codedump.info/post/20191214-cxx11-memory-model-2/

18.1.1 顺序一致性内存模型(Sequential Consistency)。

它对程序的执行结果有两个要求:

  • 每个处理器的执行顺序和代码中的顺序(program order)一样。
  • 所有处理器都只能看到一个单一的操作执行顺序。
18.1.2 全存储排序(Total Store Ordering, 简称TSO)
  • 在处理核心中增加写缓存,一个写操作只要写入到本核心的写缓存中就可以返回.
  • SC是最简单直白的内存模型,TSO在SC的基础上,加入了写缓存,写缓存的加入导致了一些在SC条件下不可能出现的情况也成为了可能。
18.1.3 松弛型内存模型(Relaxed memory models)
  • 在松散型的内存模型中,编译器可以在满足程序单线程执行结果的情况下进行重排序(reorder)
  • 在松弛型内存模型中,程序的执行顺序就不见得和代码中编写的一样了,这是这种内存模型和SC、TSO模型最大的差异。
8.1.3.4 内存栅栏(memory barrier)
  • 由于有了缓冲区的出现,导致一些操作不用到内存就可以返回继续执行后面的操作,为了保证某些操作必须是写入到内存之后才执行,就引入了内存栅栏(memory barrier,又称为memory fence)操作。
  • 内存栅栏指令保证了,在这条指令之前所有的内存操作的结果,都在这个指令之后的内存操作指令被执行之前,写入到内存中。也可以换另外的角度来理解内存栅栏指令的作用:显式的在程序的某些执行点上保证SC。

18.2 虚表

https://blog.csdn.net/xiaoxiaoguailou/article/details/123274253?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-1-123274253-blog-89318943.pc_relevant_multi_platform_whitelistv1&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-1-123274253-blog-89318943.pc_relevant_multi_platform_whitelistv1&utm_relevant_index=1

18.3 内存序 memory order

https://zhuanlan.zhihu.com/p/45566448

见上 memory model

在 C11/C++11 中,引入了六种不同的 memory order,可以让程序员在并发编程中根据自己需求尽可能降低同步的粒度,以获得更好的程序性能。这六种 order 分别是:

enum memory_order {
    memory_order_relaxed,
    memory_order_consume,
    memory_order_acquire,
    memory_order_release,
    memory_order_acq_rel,
    memory_order_seq_cst
};

18.4 编译器乱序

https://blog.csdn.net/linuxweiyh/article/details/79139766

https://blog.csdn.net/MeRcy_PM/article/details/50496683?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-50496683-blog-79139766.pc_relevant_multi_platform_whitelistv2&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-50496683-blog-79139766.pc_relevant_multi_platform_whitelistv2&utm_relevant_index=1

编译器可以对访存的指令进行乱序,减少逻辑上不必要的访存,以及尽量提高 Cache 命中率和 CPU 的 Load/Store 单元的工作效率。因此在打开编译器优化以后,有时会看到生成的汇编码并没有严格按照代码的逻辑顺序。

18.5 分支预测

https://zhuanlan.zhihu.com/p/22469702

常见的分支预测器:

  • 静态分支预测器
    静态分支预测器有两个解码周期,分别评价分支,解码。即在分支指令执行前共经历三个时钟周期。详情见图:
  • 双模态预测器(bimodal predictor)
    也叫饱和计数器,是一个四状态状态机. 四个状态对应两个选择: token, not token, 每个选择有两个状态区分强弱:strongly,weakly。分别是Strongly not taken,Weakly not taken, Weakly taken, Strongly taken。

19. 深入理解Linux内核

https://www.bilibili.com/video/BV1ZQ4y1S7mM?spm_id_from=333.337.search-card.all.click&vd_source=70d661a9d0aa9c59c89ab35d8afa922d

19.1 tlb

19.2 高速缓存

19.3 内存屏障

20. Qt对象树

  • QT提供了对象树机制,能够自动、有效的组织和管理继承自QObject的对象。

  • 每个继承自QObject类的对象通过它的对象链表(QObjectList)来管理子类对象,当用户创建一个子对象时,其对象链表相应更新子类对象的信息,对象链表可通过children()获取。

  • 当父类对象析构的时候,其对象链表中的所有(子类)对象也会被析构,父对象会自动,将其从父对象列表中删除,QT保证没有对象会被delete两次。开发中手动回收资源时建议使用deleteLater代替delete,因为deleteLater多次是安全的。

https://blog.csdn.net/qq_51604330/article/details/122483674?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-122483674-blog-118361160.pc_relevant_aa2&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-122483674-blog-118361160.pc_relevant_aa2&utm_relevant_index=1

21. Qt 三大核心机制

https://blog.csdn.net/guuci/article/details/107410098

21.1 信号槽

  • 信号槽的五种连接方式
    enum ConnectionType {
        AutoConnection,     	// 默认连接方式 单线程 DirectConnection  多线程 DirectConnection
        DirectConnection,		// 单线程
        QueuedConnection,		// 多线程异步
        BlockingQueuedConnection,  // 多线程同步
        UniqueConnection =  0x80	// 唯一连接,用来防止重复连接
    };

21.2 元对象系统

元对象系统分为三大类:QObject类、Q_OBJECT宏和元对象编译器moc

Qt的类包含Q_OBJECT宏 moc编译器会对该类编译成标准的C++代码

21.3 事件模型

  • 事件的创建
    鼠标事件,键盘事件,窗口调整事件,模拟事件
  • 事件的交付
    Qt通过调用虚函数QObject::event()来交付事件。
  • 事件循环模型
    主事件循环通过调用QCoreApplication::exec()启动,
    随着QCoreApplication::exit()结束,
  • 本地的事件循环可用利用QEventLoop构建。
    一般来说,事件是由触发当前的窗口系统产生的,但也可以通过使用 QCoreApplication::sendEvent()
    QCoreApplication::postEvent()来手工产生事件。需要说明的是QCoreApplication::sendEvent()会立即发送事件, QCoreApplication::postEvent()则会将事件放在事件队列中分发。
  • 自定义事件

22. vector可以存智能指针吗

可以,shared_ptr可以复制,vector扩大

http://c.biancheng.net/view/482.html

std::vector<std::unique_ptr<std::string>> words;
words.push_back(std::make_unique<std::string>("one"));
words.push_back(std::make_unique<std::string>("two"));

vector 保存了 unique_ptr 类型的智能指针。make_unique() 函数可以生成对象和智能指针,并且返回后者。因为返回结果是一个临时 unique_ptr 对象,这里调用一个有右值引用参数的 push_back() 函数,因此不需要拷贝对象。另一种添加 unique_ptr 对象的方法是,先创建一个局部变量 unique_ptr ,然后使用 std::move() 将它移到容器中。然而,后面任何关于拷贝容器元素的操作都会失败,因为只能有一个 unique_ptr 对象。如果想能够复制元素,需要使用 shared_ptr 对象;否则就使用 unique_ptr 对象。

23. vector可以存引用吗

vector中元素的两个要求是:
1.元素必须能赋值
2.元素必须能复制

https://blog.csdn.net/virtual_func/article/details/49724135

不可以,引用不能复制

24. 线程同步

CLASS A 如何同步?
CLASS B

  1. 信号量
/*防止重复登录*/
QSystemSemaphore sema(mSystemFlag, 1, QSystemSemaphore::Open);
sema.acquire();// 在临界区操作共享内存  SharedMemory
QSharedMemory mem(appcationName);// 全局对象名
if (!mem.create(1))// 如果全局对象以存在则退出
{
	sema.release();// 如果是 Unix 系统,会自动释放。
	return 0;
}

...
sema.release();
  1. mutex + QWaitCondition
QMutex mutex;
QWaitCondition condition;
bool flag = false;
int bufferSize = 0;

Thread Producer
{
	public:
	void run()
	{
		mutex.lock();
		....
		flag = true
		condition.wait(&mutex);
		mutex.unlock();
	}
}

Thread Customer
{
	public:
	void run()
	{
		mutex.lock();
		while(!flag)
		{
			condition.wait(&mutex);
		}
		flag = false;
		condition.wakeAll();
		mutex.unlock();
	}
}

25. C++11 新特性

https://blog.csdn.net/TY113322/article/details/123343097

nullptr和NULL
lambda
for(a:b)
auto
decltype(针对表达式类型推导)
std::move
std::vector 声明再堆上
std::array 声明再栈上的,速度更快
std::tuple

26. C++单例上 用static关键字进行的 C++98可以用吗

C++98不能保证初始化线程的安全性

27. delete a; delete[]a 不写数组会出现什么问题

new出的对象数组必须要用delete[]删除,而普通数组delete和delete[]都一样

delete 结构体数组----都不会出问题!而delete 对象数组----报错。

https://blog.csdn.net/u010278318/article/details/8851724?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-8851724-blog-5930719.pc_relevant_multi_platform_whitelistv2&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-8851724-blog-5930719.pc_relevant_multi_platform_whitelistv2&utm_relevant_index=1

28. new 失败会出现什么问题?

。C++ 里,如果 new 分配内存失败,默认是抛出异常的。所以,如果分配成功,p == 0 就绝对不会成立;而如果分配失败了,也不会执行 if ( p == 0 ),因为分配失败时,new 就会抛出异常跳过后面的代码。如果你想检查 new 是否成功,应该捕捉异常:

   try {
		int* p = new int[SIZE];
		// 其它代码
	} catch ( const bad_alloc& e ) {
		return -1;
	}

标准 C++ 亦提供了一个方法来抑制 new 抛出异常,而返回空指针:

int* p = new (std::nothrow) int; // 这样如果 new 失败了,就不会抛出异常,而是返回空指针
if ( p == 0 ) // 如此这般,这个判断就有意义了
	return -1;
// 其它代码

29. 自定义控件 拖拽时闪烁,怎么解决?

30. shared_ptr 是线程安全的吗

shared_ptr中的引用计数是线程安全的 使用std::atomic原子锁
但是其内部的指针不是线程安全的 需要自己处理

31. shared_ptr作为unordered_map的key

https://blog.csdn.net/liang19890820/article/details/120465794

如果把 boost::shared_ptr 放到 unordered_set 中,或者用于 unordered_map 的 key,那么要小心 hash table 退化为链表。
http://stackoverflow.com/questions/6404765/c-shared-ptr-as-unordered-sets-key/12122314#12122314

直到 Boost 1.47.0 发布之前,unordered_set<std::shared_ptr > 虽然可以编译通过,但是其 hash_value 是 shared_ptr 隐式转换为 bool 的结果。也就是说,如果不自定义hash函数,那么 unordered_{set/map} 会退化为链表。
https://svn.boost.org/trac/boost/ticket/5216

Boost 1.51 在 boost/functional/hash/extensions.hpp 中增加了有关重载,现在只要包含这个头文件就能安全高效地使用 unordered_set<std::shared_ptr> 了。

32. 什么是协程?

https://blog.csdn.net/ThinPikachu/article/details/121325198

协程不是系统进程,很多时候协程被称为"轻量级线程"、“微线程”、"纤程(fiber)"等。简单来说协程是(用户态)线程间里的不同函数,这些函数之间可以相互快速切换。

我们可以将线程分为 “内核态 “线程和” 用户态 “线程。
一个 “用户态线程” 必须要绑定一个 “内核态线程”,但是 CPU 并不知道有 “用户态线程” 的存在,它只知道它运行的是一个 “内核态线程”。

33. malloc/free new/delete

malloc/free和new/delete的共同点是:都是从堆上申请空间,并而需要手动释放,申请连续的空间一般是2个G,不同点是:

1.malloc和free是函数,new和delete是操作符

2.malloc申请的空间不会初始化,new可以初始化

3.malloc申请空间时,需要手动计算空间大小并传递,new只需要在其后跟空间的类型.如果是多个对象,[]中指定对象个数即可.

4.malloc返回值类型为void* ,在使用时必须强转,new不需要,因为new后面跟的是空间的类型

5.malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常

6.申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数和析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调动析构函数完成空间中资源的清理

7.malloc/new申请空间本质上是把一块内存的实用权给你,不用空间了,free/delete释放内存空间的本质,是交还使用权给系统,那么系统就可以再分配给别人了.

  全局变量在数据段                                    静态全局变量在静态区

静态局部变量在静态区                                局部变量在栈区

使用 char* p = new char[100]申请一段内存,然后使用delete p释放,对于内置类型,delete就相当于free,而对于自定义类型则需要delete []来释放堆上的空间.

堆无法静态分配内存,只能动态申请,

34.delete 和 Qt5 deleteLater()的区别

https://blog.csdn.net/f110300641/article/details/106618519?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2aggregatepagefirst_rank_ecpm_v1~rank_v31_ecpm-3-106618519-null-null.pc_agg_new_rank&utm_term=qt%E7%9A%84deletelater&spm=1000.2123.3001.4430

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值