Qt 模板类、模板函数、模板变量详细概念和源码示例

这篇文章的目的:

  • 学会使用模板,少写代码,提高运行效率;
  • 将项目使用模板封装,提高你的不可替代性;

Qt C++ 模板分类

Qt 模板类、模板函数、模板变量详细概念和源码示例

类模板

Qt 模板类、模板函数、模板变量详细概念和源码示例

在C++中,类模板(Class Template)是一种通用的类定义,允许在类的定义中使用一个或多个类型参数。通过类模板,可以定义一组相关的类,这些类具有相似的结构和行为,但可以在不同的类型上进行实例化。

类模板的定义使用关键字”template”,后跟一个或多个类型参数。这些类型参数可以在类定义中的成员函数、成员变量或基类中使用,从而使得类的定义可以适用于不同的类型。

下面是一个简单的类模板示例,展示了如何定义和使用一个通用的堆栈(Stack)类:

template <typename T>
class Stack {
private:
    static const int MAX_SIZE = 100;
    T data[MAX_SIZE];
    int top;

public:
    Stack() : top(-1) {}

    void push(const T& item) {
        if (top < MAX_SIZE - 1) {
            data[++top] = item;
        } else {
            // 处理栈已满的情况
        }
    }

    T pop() {
        if (top >= 0) {
            return data[top--];
        } else {
            // 处理栈为空的情况
            return T(); // 返回默认值
        }
    }
};

在上述示例中,Stack 是一个类模板,使用类型参数 T。通过使用 T,我们可以在定义中使用任意类型的数据。使用类模板时,可以通过指定具体的类型参数来实例化一个具体的类。例如,Stack<int> 将创建一个整数类型的堆栈实例,而 Stack<double> 将创建一个双精度浮点数类型的堆栈实例。

使用类模板可以提高代码的重用性和灵活性,因为它们允许我们编写通用的类定义,可以适用于不同的数据类型。

类模板的语法和使用方式如下:

1. 定义类模板:

template <typename T>
class ClassName {
    // 成员变量和函数的定义
};

在类模板定义中,使用关键字 template 后跟 <typename T><class T> 来指定类型参数。T 可以是任意合意合法的标识符,用于表示类型参数。

实例化类模板:

可以通过指定具体的类型参数来实例化类模板,从而创建一个具体的类。实例化的方式有两种:

  • 隐式实例化:根据使用时提供的类型参数自动实例化。

ClassName object;

  • 显式实例化:在代码中显式指定类型参数进行实例化。

template class ClassName;

使用类模板:

在实例化类模板后,可以像使用普通类一样使用它的成员变量和成员函数。

object.memberVariable; // 访问成员变量
object.memberFunction(); // 调用成员函数

类模板的成员函数和成员变量可以使用类型参数 T,并根据需要进行操作。

类模板的特化:

可以对类模板进行特化,为特定的类型参数提供特定的实现。特化允许针对某些类型参数编写特定的代码。

template <>
class ClassName {
// 具体类型的特化定义
};

特化的类模板可以提供不同于通用模板的实现,以满足特定类型的需求。

通过类模板,可以编写通用的类定义,使其适用于不同的数据类型,从而提高代码的重用性和灵活性。

类模板的偏特化(Partial Specialization)是对类模板进行特化的一种形式,它允许在特定条件下对某些类型参数进行特定的实现。偏特化可以进一步细化类模板的行为,以满足更具体的需求。

偏特化可以分为两种形式:主要偏特化(Primary Partial Specialization)和次要偏特化(Partial Specialization)。主要偏特化是指对类模板的一个或多个类型参数进行特化,而保持其余类型参数泛化。次要偏特化是指对类模板的一组类型参数进行特化,而保持另一组类型参数泛化。

以下是偏特化的示例:

template <typename T1, typename T2>
class ClassName {
    // 通用的类模板定义
};

template <typename T>
class ClassName<T, int> {
    // 对类模板进行主要偏特化,其中 T 为特定类型,T2 为 int 类型
    // 提供特定的实现
};

template <typename T1, typename T2>
class ClassName<T1*, T2*> {
    // 对类模板进行次要偏特化,其中 T1 和 T2 均为指针类型
    // 提供特定的实现
};

在上述示例中,我们对类模板 ClassName 进行了两种形式的偏特化。第一个偏特化对类型参数进行了主要偏特化,其中第二个类型参数被限定为 int。第二个偏特化对类型参数进行了次要偏特化,其中两个类型参数均为指针类型。

通过偏特化,我们可以根据特定的类型参数,为类模板提供更具体的实现。这使得类模板能够更好地适应不同的需求和类型组合。需要注意的是,偏特化只能应用于类模板,而不能应用于函数模板。

函数模板

Qt 模板类、模板函数、模板变量详细概念和源码示例

什么是函数模板?

函数模板是一种通用的函数定义,可以在其中使用参数类型的占位符。通过函数模板,可以定义一个函数,用于处理多种类型的数据,而不需要为每种类型编写不同的函数。
为什么使用函数模板?

使用函数模板有以下几个好处:

  • 代码复用性:函数模板可以处理多种类型的数据,避免了为每种类型编写重复的代码。
  • 提供通用的解决方案:函数模板可以作为通用的解决方案,适用于多种数据类型和操作。

函数模板的语法

函数模板的语法包括函数模板声明和使用模板参数:

  • 函数模板声明:使用template关键字和模板参数列表来声明函数模板。
  • 使用模板参数:在函数参数列表、返回类型或函数体中使用模板参数,用于表示通用的类型或值。

实例化函数模板的方式

函数模板可以通过以下方式进行实例化:

  • 显示实例化:在代码中显式在代码中显式指定模板参数的类型来实例化函数模板。
  • 隐式实例化:根据函数调用时的参数类型自动推导并实例化函数模板。
  • 显式具体化:针对特定的模板参数类型,显式定义特定的函数实现。

函数模板的特化和偏特化

函数模板的特化和偏特化用于针对特定的模板参数类型提供特定的实现:

  • 函数模板的特化:为特定的模板参数类型提供独立的实现,覆盖通用模板的行为。
  • 函数模板的偏特化:在特定的模板参数条件下,对部分模板参数进行特化,提供特定的实现。

注意事项和常见问题

在使用函数模板时,需要注意以下几个方面:

  • 模板函数的命名:函数模板的命名不能与其他函数冲突。
  • 避免重复实例化:如果多个代码文件中都包含了函数模板的定义,可能会导致重复实例化,需要使用extern关键字来避免。
  • 模板函数的调用方式:在调用函数模板时,可以显式指定模板参数类型,或者使用自动类型推导。
    4动类型推导。
  • 避免模板歧义:当存在多个函数模板可匹配时,需要注意避免模板歧义,可以使用函数重载或显式指定模板参数类型来解决

当我们编写函数模板时,可以使用模板参数来表示通用的类型或值。以下是一个简单的函数模板示例,用于交换两个值:

template <typename T>
void swapValues(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

在这个示例中,T 是一个模板参数,代表通用的类型。函数模板 swapValues 接受两个引用参数 a 和 b,并使用临时变量 temp 来交换它们的值。

通过这个函数模板,我们可以交换不同类型的值,例如整数、浮点数、字符等。下面是使用该函数模板的例子:

int main() {
    int num1 = 10, num2 = 20;
    swapValues(num1, num2);  // 交换整数

    double d1 = 3.14, d2 = 2.71;
    swapValues(d1, d2);  // 交换浮点数

    char c1 = 'A', c2 = 'B';
    swapValues(c1, c2);  // 交换字符

    return 0;
}

在这个例子中,我们分别交换了两个整数、两个浮点数和两个字符的值,而不需要编写针对每种类型的交换函数。

这只是函数模板的一个简单示例,实际上函数模板可以用于更复杂的操作和算法。希望这个例子可以帮助你更好地理解函数模板的用法。

变量模板

Qt 模板类、模板函数、模板变量详细概念和源码示例

C++变量模板是C++14引入的特性,它允许定义通用的变量模板,用于生成多种类型的变量。下面是关于C++变量模板的简介。

什么是变量模板?

变量模板是一种通用的变量定义,可以在其中使用模板参数来表示通用的类型或值。通过变量模板,可以定义一个模板变量,用于生成多种类型的变量实例。

变量模板的语法

变量模板的语法与函数模板类似,使用template关键字和模板参数列表来声明变量模板。变量模板的声明必须在命名空间或类作用域中。

下面是一个简单的变量模板示例,用于生成多种类型的零值变量:

template <typename T>
constexpr T zero = T(0);

在这个示例中,zero 是一个变量模板,它使用模板参数 T 表示通用的类型,并将其初始化为零值。

变量模板的实例化

变量模板可以通过以下方式进行实例化:

  • 隐式实例化:根据变量的使用上下文,自动推导并实例化变量模板。
  • 显示实例化:在代码中显式指定模板参数的类型来实例化变量模板。

下面是使用变量模板的示例:

int main() {
    auto intZero = zero<int>;  // 隐式实例化为 int 类型的零值变量
    auto doubleZero = zero<double>;  // 隐式实例化为 double 类型的零值变量

    int explicitIntZero = zero<int>;  // 显式实例化为 int 类型的零值变量

    return 0;
}

在这个例子中,我们通过隐式实例化和显式实例化,分别生成了 int 类型和 double 类型的零值变量。

变量模板的使用注意事项:

  • 变量模板的命名:变量模板的命名不能与其他变量冲突。
  • 变量模板的实例化:变量模板必须在使用之前进行实例化。
  • 变量模板的类型推导:在使用变量模板时,可以使用自动类型推导(auto)来推导变量的类型。

别名模板

Qt 模板类、模板函数、模板变量详细概念和源码示例

一个简单的项目实例

模板类头文件:

#ifndef TEMPLATESTL_H
#define TEMPLATESTL_H

#include <QDebug>
// 自制模板类
using namespace std;

template<class T1,class T2>
class templateSTL
{
public:
    templateSTL(T1 newKey,T2 newValue) : key(newKey),value(newValue){
        qDebug()<<"templateSTL 模板类构造函数:"<<" key : "<<key<<" value : "<<value;
    }

    // 自制操作运算符 比较两个数
    bool operator < (const templateSTL<T1,T2>&T3) const;

    T1 key;
    T2 value;

};


template<class T>
class templateSTL2
{
public:
    templateSTL2(T newValue) : value(newValue) {
        qDebug()<<" templateSTL2 模板类构造函数:"<<" value : "<<value;
    }

    // 自制操作运算符 比较两个数
    bool operator = (const templateSTL2<T>&T_equal) const;

    T value;

};

#endif // TEMPLATESTL_H

模板类源文件:

#include "templatestl.h"


template<class T1, class T2>
bool templateSTL<T1, T2>::operator <(const templateSTL<T1, T2> &T3) const
{
    return this->key < T3.key;
}

template<class T>
bool templateSTL2<T>::operator =(const templateSTL2<T> &T_equal) const
{
    return this->value == T_equal.value;
}

主头文件:

#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>
#include <QDateTime>
#include <QColor>
#include <QFont>
#include <QString>
#include <QVariant>
#include <QMap>


// C++
#include <iostream>
using namespace std;

#include "templatestl.h"

namespace Ui {
class Dialog;
}

class Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Dialog(QWidget *parent = nullptr);
    ~Dialog();

private slots:
    void on_PB_template_clicked();
    void on_PB_templatecpp_clicked();
    void on_PB_clear_clicked();
    void on_PB_templateQt_clicked();

    void on_PB_templateclass_clicked();

private:
    // Qt 模板举例函数  模块需要和对应的函数的分号;匹配
    template<typename T>
    T setCreateTemplateFunction(const T &value);
    QString createTemplateFunction(QString key);

    // Qt 简单的模板函数
    //头文件声明一个函数模板
    template<class T>
    void swap(T & a,T & b);

    // C++ 模板举例函数
    template<typename T>
    T baseCppFunction(const T &value , int falg);

private:
    Ui::Dialog *ui;
    QVariant myVar;
    QMap <QString, QString> map;
};

#endif // DIALOG_H

主源文件:

#include "dialog.h"
#include "ui_dialog.h"

#include "QDebug"

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    ui->setupUi(this);
}

Dialog::~Dialog()
{
    delete ui;
}

// 模板函数自定义实现
void Dialog::on_PB_template_clicked()
{
    // 传递整数
    setCreateTemplateFunction(1);
    setCreateTemplateFunction(-1);

    // 传递浮点数
    setCreateTemplateFunction(3.1415927);
    setCreateTemplateFunction(-3.1415927);

    // 传递字符
    QChar mychar = 'a';
    setCreateTemplateFunction(mychar);

    // 传递字符串
    QString str = "hello world";
    setCreateTemplateFunction(str);

    // 传递颜色
    QColor color(255,0,0);
    setCreateTemplateFunction(color);

    // 传递时间
    QDateTime dateTime = QDateTime::currentDateTimeUtc();
    setCreateTemplateFunction(dateTime);
    dateTime = QDateTime::currentDateTime();
    setCreateTemplateFunction(dateTime);

    // 传递字体
    QFont font;
    font.setItalic(true);
    font.setBold(true);
    font.setPointSize(20);
    setCreateTemplateFunction(font);
}

// 模板类型函数 关键:使用QVariant变量区分数据类型
template<typename T>
T Dialog::setCreateTemplateFunction(const T &value)
{
    myVar = value;

    qDebug()<<"value = "<<value<< " myVar = "<<myVar;
    // 使用switch可能效果更好哦
    // 恢复默认样式
    QTextCharFormat format;
    format.setForeground(Qt::black);
    ui->plainTextEdit_cout->setCurrentCharFormat(format);
    if (myVar.type() == QVariant::Type::Int) {
        ui->plainTextEdit_cout->appendPlainText("整数:" + myVar.toString());

        // 存储
        map.insert("Int",myVar.toString());
    }
    else if (myVar.type() == QVariant::Type::Double) {
        ui->plainTextEdit_cout->appendPlainText("浮点数:" + myVar.toString());

        // 存储
        map.insert("Double",myVar.toString());
    }
    else if (myVar.type() == QVariant::Type::Char) {
        ui->plainTextEdit_cout->appendPlainText("字符:" + myVar.toString());

        // 存储
        map.insert("Char",myVar.toString());
    }
    else if (myVar.type() == QVariant::Type::String) {
        ui->plainTextEdit_cout->appendPlainText("字符串:" + myVar.toString());

        // 存储
        map.insert("String",myVar.toString());
    }
    else if (myVar.type() == QVariant::Type::Color) {
        QTextCharFormat format;
        format.setForeground(QBrush(QColor(myVar.toString())));
        ui->plainTextEdit_cout->setCurrentCharFormat(format);
        ui->plainTextEdit_cout->appendPlainText("颜色:" + myVar.toString());

        // 存储
        map.insert("Color",myVar.toString());
    }
    else if (myVar.type() == QVariant::Type::DateTime) {
        ui->plainTextEdit_cout->appendPlainText("当前时间:" + myVar.toString());

        // 存储
        map.insert("Double",myVar.toString());
    }
    else if (myVar.type() == QVariant::Type::Font) {
        if (myVar.toString().split(',').size() > 1) // 防止下标越界 非法访问 导致程序崩溃
            ui->plainTextEdit_cout->setFont(QFont(myVar.toString().split(',').at(0),myVar.toString().split(',').at(1).toInt()));
        ui->plainTextEdit_cout->appendPlainText("字体:" + myVar.toString());

        // 存储
        map.insert("Font",myVar.toString());
    }

    return value;
}


void Dialog::on_PB_templateQt_clicked()
{
    // 通过键获取值
    QString val;
    QMap<QString, QString>::const_iterator i = map.constBegin();
    while (i != map.constEnd()) {
        qDebug() << i.key() << ": " << i.value() << Qt::endl;
        val = createTemplateFunction(i.key());

        ui->plainTextEdit_cout->appendPlainText(val);
        ++i;
    }

    // 模板函数交换两个数
    int num1 = 1;
    int num2 = 3;
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换前 num1 = "<<num1;
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换前 num2 = "<<num2;
    swap(num1,num2);//函数模板生成的模板函数
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换后 num1 = "<<num1;
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换后 num2 = "<<num2;

    double num3 = 2.5;
    double num4 = 3.6;
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换前 num3 = "<<num3;
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换前 num4 = "<<num4;
    swap(num3,num4);
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换后 num3 = "<<num3;
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换后 num4 = "<<num4;

    QChar num5 = 'a';
    QChar num6 = 'b';
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换前 num5 = "<<num5;
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换前 num4 = "<<num6;
    swap(num5,num6);
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换后 num5 = "<<num5;
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换后 num4 = "<<num6;

    QString num7 = "abc";
    QString num8 = "def";
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换前 num7 = "<<num7;
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换前 num8 = "<<num8;
    swap(num7,num8);
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换后 num7 = "<<num7;
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" 交换后 num8 = "<<num8;

}

QString Dialog::createTemplateFunction(QString key)
{
    qDebug()<< " key = "<<key;
    return map.value(key);
}

// 采用引用传参可以直接修改变量的值
template<class T>
void Dialog::swap(T &a, T &b)
{
    T temp ;
    temp = a;
    a = b;
    b = temp;
}


// 基于C++模板函数实例
void Dialog::on_PB_templatecpp_clicked()
{
    // 第一组 is_same
    // 传递整数
    baseCppFunction(1,0);
    baseCppFunction(-1,0);

    // 传递浮点数
    baseCppFunction(3.14,0);
    baseCppFunction(-3.1415927,0);

    // 传递字符
    char c = 'a';
    baseCppFunction(c,0);

    // 传递字符串
    std::string str = "hello world";
    baseCppFunction(str.c_str(),0);

    // 第二组 is_convertible
    // 传递整数
    baseCppFunction(1,1);
    baseCppFunction(-1,1);

    // 传递浮点数
    baseCppFunction(3.14,1);
    baseCppFunction(-3.1415927,1);

    // 传递字符
    char cc = 'a';
    baseCppFunction(cc,1);

    // 传递字符串
    std::string strr = "hello world";
    baseCppFunction(strr.c_str(),1);
}

template<typename T>
T Dialog::baseCppFunction(const T &value, int falg)
{
    qDebug()<<"value = "<<value;
    QVariant myVar = value;

    // 会将整数和字符混淆 不够准确
#if 0
    if (std::is_integral<T>::value) {
        std::cout << "is_integral: " << value << std::endl;
        ui->plainTextEdit_cout->appendPlainText("is_integral: " + myVar.toString());
    }
    if (std::is_floating_point<T>::value) {
        std::cout << "is_floating_point: " << value << std::endl;
        ui->plainTextEdit_cout->appendPlainText("is_floating_point: " + myVar.toString());
    }
#endif


    if (falg == 0)
    {
        //  使用same匹配类型是否相同
        if (std::is_same<T, int>::value) {
            std::cout << "整数: " << value << std::endl;
            ui->plainTextEdit_cout->appendPlainText("is_same int 整数: " + myVar.toString());
        }
        else if (std::is_same<T, float>::value) { // 未匹配
            std::cout << "浮点数值: " << value << std::endl;
            ui->plainTextEdit_cout->appendPlainText("is_same float 浮点数值: " + myVar.toString());
        }
        else if (std::is_same<T, double>::value) {
            std::cout << "double浮点数值: " << value << std::endl;
            ui->plainTextEdit_cout->appendPlainText("is_same double 浮点数值: " + myVar.toString());
        }
        else if (std::is_same<T, char>::value) {
            std::cout << "字符: " << value << std::endl;
            ui->plainTextEdit_cout->appendPlainText("is_same char 字符: " + myVar.toString());
        }
        else if (std::is_same<T, std::string>::value) { // 不太准确
            std::cout << "字符串: " << value << std::endl;
            ui->plainTextEdit_cout->appendPlainText("is_same string 字符串: " + myVar.toString() + "\n");
        }
    }
    else {

        if (std::is_convertible<T, int>::value) {   // 不太准确
            std::cout << "整数: " << value << std::endl;
            ui->plainTextEdit_cout->appendPlainText("is_convertible int 整数: " + myVar.toString());
        }
        else if (std::is_convertible<T, float>::value) { // 不太准确
            std::cout << "浮点数值: " << value << std::endl;
            ui->plainTextEdit_cout->appendPlainText("is_convertible float 浮点数值: " + myVar.toString());
        }
        else if (std::is_convertible<T, double>::value) { // 未匹配
            std::cout << "double浮点数值: " << value << std::endl;
            ui->plainTextEdit_cout->appendPlainText("is_convertible double 浮点数值: " + myVar.toString());
        }
        else if (std::is_convertible<T, char>::value) {// 未匹配
            std::cout << "字符: " << value << std::endl;
            ui->plainTextEdit_cout->appendPlainText("is_convertible char 字符: " + myVar.toString());
        }
        else if (std::is_convertible<T, std::string>::value) { // 匹配
            std::cout << "字符串2: " << value << std::endl;
            ui->plainTextEdit_cout->appendPlainText("is_convertible string 字符串: " + myVar.toString());
        }
    }

    return value;
}

void Dialog::on_PB_clear_clicked()
{
    ui->plainTextEdit_cout->clear();
}


// 模板类举例 【QString 类使用的是 Unicode 编码,因此在比较字符串时会考虑字符的 Unicode 值。比较的结果取决于所使用的排序规则和语言环境。】
void Dialog::on_PB_templateclass_clicked()
{
    // 创建模板类
    templateSTL<QString,int>T3("小明",23);
    templateSTL<QString,int>T4("菲菲公主",23);

    // 比较名字的长度
    if (T3.key < T4.key) {
        qDebug()<<T4.key + " size = "<<T4.key.size()<< " " + T3.key + " size = "<<T3.key.size();
    }
    else {
        qDebug()<<T4.key + " size = "<<T4.key.size()<< " " + T3.key + " size = "<<T3.key.size();
    }

    // 比较两个数是否相等
    templateSTL2<QString>T5("小明");
    templateSTL2<QString>T6("晓明");

    // 每个汉字的编码不同 类比16进制不相等
    if (T5.value == T6.value) {
        qDebug()<<"小明 = 晓明"<<"T5 size = "<<T5.value.length()<<"T6 size = "<<T6.value.length();
    }
    else {
        qDebug()<<"小明 != 晓明"<<"T5 size = "<<T5.value.length()<<"T6 size = "<<T6.value.length();
    }

}

ui设计文件:

Qt 模板类、模板函数、模板变量详细概念和源码示例

效果演示:

Qt 模板类、模板函数、模板变量详细概念和源码示例
Qt 模板类、模板函数、模板变量详细概念和源码示例
Qt 模板类、模板函数、模板变量详细概念和源码示例
Qt 模板类、模板函数、模板变量详细概念和源码示例
Qt 模板类、模板函数、模板变量详细概念和源码示例
Qt 模板类、模板函数、模板变量详细概念和源码示例
Qt 模板类、模板函数、模板变量详细概念和源码示例

作者:Qt历险记
原文:https://mp.weixin.qq.com/s/xDC4WXv_VqXlAnNjsLMVyw

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论