当前位置: 首页 > wzjs >正文

网站建设唐山百度快照入口官网

网站建设唐山,百度快照入口官网,目前最好的免费网站,成都找人做网站目录 1.元对象系统的构成 2.QObject和QMetaObject的关系 3.Qt 元对象模型QMetaObject 3.1.基本信息 3.2.类信息classinfo 3.3.类构造函数constructor 3.4.枚举信息 enumerator 3.5.类方法method 3.6.类属性peoproty 4.MOS(Meta Object System)示例 5.总结 1.元对象系…

目录

1.元对象系统的构成

2.QObject和QMetaObject的关系

3.Qt 元对象模型QMetaObject

3.1.基本信息

3.2.类信息classinfo

 3.3.类构造函数constructor

3.4.枚举信息 enumerator

3.5.类方法method

3.6.类属性peoproty

4.MOS(Meta Object System)示例

5.总结


1.元对象系统的构成

1) QObject为所有需要利用元对象系统的对象提供一个基类。也就是说只有继承QObject类才能使用MOS。

2) Q_OBJECT宏,在类的声明体内定义在每一个类的私有数据段,用来启用元对象功能,比如,动态属性、信号和槽

3) 元对象编译器moc(Meta Object Compiler),如果一个头文件中包含Q_OBJECT宏定义,moc就会将该文件编译成C++源文件。该原文件包含了Q_OBJECT的实现代码,也被编译,最终链接到这个类的二进制代码中。因为它也是这个类的一部分。通常,这个新的C++原文件会再以前的C++原文件前面加上moc_作为新的文件名。

如下图所示:

4) QObject定义了从一个QObject对象访问meta-object功能的接口,Q_OBJECT宏用来告诉编译器该类需要激活meta-object功能,编译器在扫描一个源文件时,如果发现类的声明中有这个宏,就会生成一些代码来为支持meta-object功能——主要是生成该类对应MetaObject类以及对QObject的函数override(重载)。

2.QObject和QMetaObject的关系

        Q_OBJECT宏的定义如下:

#define Q_OBJECT \
public: \QT_WARNING_PUSH \Q_OBJECT_NO_OVERRIDE_WARNING \static const QMetaObject staticMetaObject; \virtual const QMetaObject *metaObject() const; \virtual void *qt_metacast(const char *); \virtual int qt_metacall(QMetaObject::Call, int, void **); \QT_TR_FUNCTIONS \
private: \Q_OBJECT_NO_ATTRIBUTES_WARNING \Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \QT_WARNING_POP \struct QPrivateSignal {}; \QT_ANNOTATE_CLASS(qt_qobject, "")

        staticMetaObject是静态常量,metaObject()是获取该对象指针的方法。所有QObject的对象都会共享staticMetaObject变量,靠它完成所有信号和槽的功能。        

        QMetaObject包含了QObject的所谓的元数据,也就是QObject信息的一些描述信息:除了类型信息外,还包含QT中特有的signal&slot信息。

virtual QObject::metaObject();

        该方法返回一个QObject对应的metaObject对象,如上文所说,如果一个类的声明中包含了Q_OBJECT宏,编译器会生成代码来实现这个类对应的QMetaObject类,并重载QObject::metaObject()方法来返回这个QMetaObject类的实例引用。这样当通过QObject类型的引用调用metaObejct方法时,返回的是这个引用的所指的真实对象的metaobject。

        如果一个类从QObject派生,确没有声明Q_OBJECT宏,那么这个类的metaobject对象不会被生成,这样这个类所声明的signal slot都不能使用,而这个类实例调用metaObject()返回的就是其父类的metaobject对象,这样导致的后果就是你从这个类实例获得的元数据其实都是父类的数据,这显然给你的代码埋下隐患。因此如果一个类从QOBject派生,它都应该声明Q_OBJECT宏,不管这个类有没有定义signal&slot和Property。

        这样每个QObject类都有一个对应的QMetaObject类,形成一个平行的类型层次。

3.Qt 元对象模型QMetaObject

        笔者环境:win平台vs2019,Qt 版本5.12.12,以下源码分析都是基于这个版本

3.1.基本信息

        Qt 元对象声明位于.\5.12.12\Src\qtbase\src\corelib\kernel\qobjectdefs.h中,位于代码的337行处,我们可以看到其中存放的元数据的结构:

struct Q_CORE_EXPORT QMetaObject
{  const char *className() const;const QMetaObject *superClass() const;。。。struct { // private dataconst QMetaObject *superdata; // 父类的元对象指针const QByteArrayData *stringdata;   // 元数据的字符串数据const uint *data;                  // 元对象的数据信息typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);StaticMetacallFunction static_metacall; // 一个函数指针,信号槽机制会用到const QMetaObject * const *relatedMetaObjects;void *extradata; //reserved for future use} d;
};

        上面的代码就是QMetaObject类所定义的所有数据成员,就是这些数据成员记录了所有的signal、slot、property、class information、enumerator等相关信息。

        const QMetaObject *superdata,该变量指向与之对应的QObject类的父类对象,或者是祖先类的QMetaObject对象。每一个QObject类或者其派生类可能有一个父类或者父类的父类。那么superdata就是指向与其最接近的祖先类中的QMetaObject对象。如果没有父类,那么该变量就是一个NULL指针。

3.2.类信息classinfo

提供额外的类信息-名值对。用户可以在类的生命中以Q_CLASSINFO(name,value)的方式添加。

int classInfoOffset() const;
int classInfoCount() const;
int indexOfClassInfo(const char *name) const;
QMetaClassInfo classInfo(int index) const;

示例如下:

class MyClass : public QObject
{Q_OBJECTQ_CLASSINFO("author", "xiaohe")Q_CLASSINFO("url", "http://www.baidu.com/")public:...
};

 3.3.类构造函数constructor

提供该类的构造方法信息

 int constructorCount() const;int indexOfConstructor(const char *constructor) const;QMetaMethod constructor(int index) const;

3.4.枚举信息 enumerator

描述该类声明体内所包含的枚举类型信息

int enumeratorOffset() const;
int enumeratorCount() const;
int indexOfEnumerator(const char *name) const;
QMetaEnum enumerator(int index) const;

3.5.类方法method

描述类中所包含方法信息:包括property,signal,slot等。

int methodOffset() const;
int methodCount() const;
int indexOfMethod(const char *method) const;
int indexOfSignal(const char *signal) const;
int indexOfSlot(const char *slot) const;
QMetaMethod method(int index) const;

3.6.类属性peoproty

描述类的属性信息

int propertyOffset() const;
int propertyCount() const;
int indexOfProperty(const char *name) const;
QMetaProperty property(int index) const;

注意:对于类里面定义的函数,构造函数,枚举,只有加上一些宏才表示你希望为方法提供meta信息。比如 Q_ENUMS用来注册宏,Q_INVACABLE用来注册方法(包括构造函数)。Qt这么设计的原因应该是避免meta信息的臃肿。

4.MOS(Meta Object System)示例

TestObject继承QObject,定义了两个Property:PropertyA和PropertyB;两个classinfo:Author,version;一个枚举:TestEnum。

CTestObject.h

#pragma once
#include <qobject.h>
class TestObject : public QObject
{Q_OBJECTQ_PROPERTY(QString propertyA  READ getPropertyA WRITE setPropertyA RESET resetPropertyA DESIGNABLE true SCRIPTABLE true STORED true USER false)Q_PROPERTY(QString propertyB  READ getPropertyB WRITE setPropertyB RESET resetPropertyB)Q_CLASSINFO("Author", "Liuyanghe")Q_CLASSINFO("Version", "TestObjectV1.0")Q_ENUMS(TestEnum)
public:enum TestEnum {EnumValueA,EnumValueB};
public:TestObject() {}Q_INVOKABLE TestObject(const QString& a, const QString& b) : m_A(a), m_B(b) {}QString getPropertyA() const {return m_A;}void setPropertyA(const QString& newValue) {if (newValue == m_A)return;m_A = newValue;emit propertyAChanged();}void resetPropertyA() {m_A = "";}QString getPropertyB() const {return m_B;}void setPropertyB(const QString& newValue) {if (newValue == m_B)return;m_B = newValue;emit propertyBChanged();}void resetPropertyB() {m_B = "";}Q_INVOKABLE int  doWork() {return 1;}
signals:void clicked();void pressed();void propertyAChanged();void propertyBChanged();
public slots:void onEventA(const QString&);void onEventB(int);
private:QString m_A;QString m_B;
};

TestObject的moc文件moc_CTestObject.cpp:

/****************************************************************************
** Meta object code from reading C++ file 'CTestObject.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.12.12)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/#include "../../../../CTestObject.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'CTestObject.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.12.12. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endifQT_BEGIN_MOC_NAMESPACE
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
struct qt_meta_stringdata_TestObject_t {QByteArrayData data[20];char stringdata0[182];
};
#define QT_MOC_LITERAL(idx, ofs, len) \Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \qptrdiff(offsetof(qt_meta_stringdata_TestObject_t, stringdata0) + ofs \- idx * sizeof(QByteArrayData)) \)
static const qt_meta_stringdata_TestObject_t qt_meta_stringdata_TestObject = {{
QT_MOC_LITERAL(0, 0, 10), // "TestObject"
QT_MOC_LITERAL(1, 11, 6), // "Author"
QT_MOC_LITERAL(2, 18, 9), // "Liuyanghe"
QT_MOC_LITERAL(3, 28, 7), // "Version"
QT_MOC_LITERAL(4, 36, 14), // "TestObjectV1.0"
QT_MOC_LITERAL(5, 51, 7), // "clicked"
QT_MOC_LITERAL(6, 59, 0), // ""
QT_MOC_LITERAL(7, 60, 7), // "pressed"
QT_MOC_LITERAL(8, 68, 16), // "propertyAChanged"
QT_MOC_LITERAL(9, 85, 16), // "propertyBChanged"
QT_MOC_LITERAL(10, 102, 8), // "onEventA"
QT_MOC_LITERAL(11, 111, 8), // "onEventB"
QT_MOC_LITERAL(12, 120, 6), // "doWork"
QT_MOC_LITERAL(13, 127, 1), // "a"
QT_MOC_LITERAL(14, 129, 1), // "b"
QT_MOC_LITERAL(15, 131, 9), // "propertyA"
QT_MOC_LITERAL(16, 141, 9), // "propertyB"
QT_MOC_LITERAL(17, 151, 8), // "TestEnum"
QT_MOC_LITERAL(18, 160, 10), // "EnumValueA"
QT_MOC_LITERAL(19, 171, 10) // "EnumValueB"},"TestObject\0Author\0Liuyanghe\0Version\0""TestObjectV1.0\0clicked\0\0pressed\0""propertyAChanged\0propertyBChanged\0""onEventA\0onEventB\0doWork\0a\0b\0propertyA\0""propertyB\0TestEnum\0EnumValueA\0EnumValueB"
};
#undef QT_MOC_LITERALstatic const uint qt_meta_data_TestObject[] = {// content:8,       // revision0,       // classname2,   14, // classinfo7,   18, // methods2,   69, // properties1,   75, // enums/sets1,   84, // constructors0,       // flags4,       // signalCount// classinfo: key, value1,    2,3,    4,// signals: name, argc, parameters, tag, flags5,    0,   53,    6, 0x06 /* Public */,7,    0,   54,    6, 0x06 /* Public */,8,    0,   55,    6, 0x06 /* Public */,9,    0,   56,    6, 0x06 /* Public */,// slots: name, argc, parameters, tag, flags10,    1,   57,    6, 0x0a /* Public */,11,    1,   60,    6, 0x0a /* Public */,// methods: name, argc, parameters, tag, flags12,    0,   63,    6, 0x02 /* Public */,// signals: parametersQMetaType::Void,QMetaType::Void,QMetaType::Void,QMetaType::Void,// slots: parametersQMetaType::Void, QMetaType::QString,    6,QMetaType::Void, QMetaType::Int,    6,// methods: parametersQMetaType::Int,// constructors: parameters0x80000000 | 6, QMetaType::QString, QMetaType::QString,   13,   14,// properties: name, type, flags15, QMetaType::QString, 0x00095107,16, QMetaType::QString, 0x00095107,// enums: name, alias, flags, count, data17,   17, 0x0,    2,   80,// enum data: key, value18, uint(TestObject::EnumValueA),19, uint(TestObject::EnumValueB),// constructors: name, argc, parameters, tag, flags0,    2,   64,    6, 0x0e /* Public */,0        // eod
};void TestObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{if (_c == QMetaObject::CreateInstance) {switch (_id) {case 0: { TestObject *_r = new TestObject((*reinterpret_cast< const QString(*)>(_a[1])),(*reinterpret_cast< const QString(*)>(_a[2])));if (_a[0]) *reinterpret_cast<QObject**>(_a[0]) = _r; } break;default: break;}} else if (_c == QMetaObject::InvokeMetaMethod) {auto *_t = static_cast<TestObject *>(_o);Q_UNUSED(_t)switch (_id) {case 0: _t->clicked(); break;case 1: _t->pressed(); break;case 2: _t->propertyAChanged(); break;case 3: _t->propertyBChanged(); break;case 4: _t->onEventA((*reinterpret_cast< const QString(*)>(_a[1]))); break;case 5: _t->onEventB((*reinterpret_cast< int(*)>(_a[1]))); break;case 6: { int _r = _t->doWork();if (_a[0]) *reinterpret_cast< int*>(_a[0]) = std::move(_r); }  break;default: ;}} else if (_c == QMetaObject::IndexOfMethod) {int *result = reinterpret_cast<int *>(_a[0]);{using _t = void (TestObject::*)();if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::clicked)) {*result = 0;return;}}{using _t = void (TestObject::*)();if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::pressed)) {*result = 1;return;}}{using _t = void (TestObject::*)();if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::propertyAChanged)) {*result = 2;return;}}{using _t = void (TestObject::*)();if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::propertyBChanged)) {*result = 3;return;}}}
#ifndef QT_NO_PROPERTIESelse if (_c == QMetaObject::ReadProperty) {auto *_t = static_cast<TestObject *>(_o);Q_UNUSED(_t)void *_v = _a[0];switch (_id) {case 0: *reinterpret_cast< QString*>(_v) = _t->getPropertyA(); break;case 1: *reinterpret_cast< QString*>(_v) = _t->getPropertyB(); break;default: break;}} else if (_c == QMetaObject::WriteProperty) {auto *_t = static_cast<TestObject *>(_o);Q_UNUSED(_t)void *_v = _a[0];switch (_id) {case 0: _t->setPropertyA(*reinterpret_cast< QString*>(_v)); break;case 1: _t->setPropertyB(*reinterpret_cast< QString*>(_v)); break;default: break;}} else if (_c == QMetaObject::ResetProperty) {TestObject *_t = static_cast<TestObject *>(_o);Q_UNUSED(_t)switch (_id) {case 0: _t->resetPropertyA(); break;case 1: _t->resetPropertyB(); break;default: break;}}
#endif // QT_NO_PROPERTIES
}QT_INIT_METAOBJECT const QMetaObject TestObject::staticMetaObject = { {&QObject::staticMetaObject,qt_meta_stringdata_TestObject.data,qt_meta_data_TestObject,qt_static_metacall,nullptr,nullptr
} };const QMetaObject *TestObject::metaObject() const
{return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}void *TestObject::qt_metacast(const char *_clname)
{if (!_clname) return nullptr;if (!strcmp(_clname, qt_meta_stringdata_TestObject.stringdata0))return static_cast<void*>(this);return QObject::qt_metacast(_clname);
}int TestObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{_id = QObject::qt_metacall(_c, _id, _a);if (_id < 0)return _id;if (_c == QMetaObject::InvokeMetaMethod) {if (_id < 7)qt_static_metacall(this, _c, _id, _a);_id -= 7;} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {if (_id < 7)*reinterpret_cast<int*>(_a[0]) = -1;_id -= 7;}
#ifndef QT_NO_PROPERTIESelse if (_c == QMetaObject::ReadProperty || _c == QMetaObject::WriteProperty|| _c == QMetaObject::ResetProperty || _c == QMetaObject::RegisterPropertyMetaType) {qt_static_metacall(this, _c, _id, _a);_id -= 2;} else if (_c == QMetaObject::QueryPropertyDesignable) {_id -= 2;} else if (_c == QMetaObject::QueryPropertyScriptable) {_id -= 2;} else if (_c == QMetaObject::QueryPropertyStored) {_id -= 2;} else if (_c == QMetaObject::QueryPropertyEditable) {_id -= 2;} else if (_c == QMetaObject::QueryPropertyUser) {_id -= 2;}
#endif // QT_NO_PROPERTIESreturn _id;
}// SIGNAL 0
void TestObject::clicked()
{QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}// SIGNAL 1
void TestObject::pressed()
{QMetaObject::activate(this, &staticMetaObject, 1, nullptr);
}// SIGNAL 2
void TestObject::propertyAChanged()
{QMetaObject::activate(this, &staticMetaObject, 2, nullptr);
}// SIGNAL 3
void TestObject::propertyBChanged()
{QMetaObject::activate(this, &staticMetaObject, 3, nullptr);
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE

QMetaObjectPrivate 

    1) qt_meta_data_TestObject:定义的正是QMetaObject::d.data指向的信息块。

    2) qt_meta_stringdata_TestObject:定义的是QMetaObject::d.stringdata指向的信息块。

    3) const QMetaObject TestObject::staticMetaObject:定义TestObject类的MetaObject实例,从中可以看出QMetaObject各个字段是如何被赋值的。

    4) const QMetaObject *TestObject::metaObject() const:重写了QObject::metaObject函数,返回上述的MetaObject实例指针。

    5) TestObject::qt_metacall()是重写QObject的方法,依据传入的参数来调用signal&slot或访问property,动态方法调用属性访问正是依赖于这个方法。

    6)TestObject::clicked()和TestObject::pressed()正是对两个signal的实现,可见,signal其实就是一种方法,只不过这种方法由qt meta system来实现,不用我们自己实现。

    TestObject类的所有meta信息就存储在 qt_meta_data_TestObject和qt_meta_stringdata_TestObject这两个静态数据中。 QMetaObject的接口的实现正是基于这两块数据。下面就对这两个数据进行分块说明。

    static const uint qt_meta_data_TestObject[] = { 
           // content:
           8,       // revision
           0,       // classname
           2,   14, // classinfo
           7,   18, // methods
           2,   69, // properties
           1,   75, // enums/sets
           1,   84, // constructors
           0,       // flags
           4,       // signalCount

           //...

    }

            这块数据可以被看做meta信息的头部,正好和QMetaObjectPrivate数据结构相对应,在QMetaObject的实现中,正是将这块数据映射为QMetaObjectPrivate进行使用的。

            QMetaObjectPrivate是QMetaObject的私有实现类,其数据定义部分如下(见头文件qmetaobject_p.h)。该数据结构全是int类型,一些是直接的int型信息,比如classInfoCount、
    methodCount等,还有一些是用于在QMetaObject的stringdata和data内存块中定位信息的索引值。

    struct QMetaObjectPrivate
    {// revision 7 is Qt 5.0 everything lower is not supported// revision 8 is Qt 5.12: It adds the enum name to QMetaEnumenum { OutputRevision = 8 }; // Used by moc, qmetaobjectbuilder and qdbusint revision;int className;int classInfoCount, classInfoData;int methodCount, methodData;int propertyCount, propertyData;int enumeratorCount, enumeratorData;int constructorCount, constructorData;int flags;int signalCount;static inline const QMetaObjectPrivate *get(const QMetaObject *metaobject){ return reinterpret_cast<const QMetaObjectPrivate*>(metaobject->d.data); }//...
    };

    第一行数据“8”:版本号;

    第二行数据“0”:类名,该值是qt_meta_stringdata_TestObject的索引,qt_meta_stringdata_TestObject[0](QT_MOC_LITERAL(0, 0, 10), "TestObject" 这个字符串不正是类名“TestObject”吗。

    第三行数据“2,14”,第一个表明有2个classinfo被定义,第二个是说具体的 classinfo信息在qt_meta_data_TestObject中的索引,

    qt_meta_data_TestObject[14]的位置两个 classinfo名值对的定义;这两对键值1和2、3和4分别

    qt_meta_stringdata_TestObject的索引,找到:

    这4个值正是那两个键值对。

    第四行数据“7,18”,指明method的信息,7是指方法的个数;18是指方法(包括信号、槽、Q_INVOKABLE标注的函数)开始的位置:

    信号signals在qt_meta_stringdata_TestObject的索引是5、7、8、9,找到位置如下图所示:

    5,    0,   53,    6, 0x06 /* Public */,是定义信号clicked,其它类似

    槽slots在qt_meta_stringdata_TestObject的索引是10、11,找到位置如下图所示:

    Q_INVOKABLE标注的函数在qt_meta_stringdata_TestObject的索引是12,找到位置如下图所示:

    第五行数据“2,69”,指明属性properties的信息,2是指属性的个数;69是指属性在qt_meta_data_TestObject索引开始的位置:

    开始位置的15和16正是qt_meta_stringdata_TestObject的索引,找到位置正是类属性:

    第六行数据“1,75”,指明enum信息信息,1是指enun信息的个数;75是指enum信息在qt_meta_data_TestObject索引开始的位置:

    开始位置17正是enum名称在qt_meta_data_TestObject索引开始的位置:

    其它分析类似

    第七行数据“1,84”,指明类声明Q_INVOKABLE的构造函数信息,1是指构造函数个数;84是指构造函数在qt_meta_data_TestObject索引开始的位置:

    0,    2,   64,    6, 0x0e /* Public */中的

    0是指构造函数名称在qt_meta_stringdata_TestObject[0]

    2是指这个构造函数的参数为两个

    64是指构造函数的具体参数在qt_meta_data_TestObject[64]:

    上图中的13和14是参数名的位置qt_meta_stringdata_TestObject[13]、qt_meta_stringdata_TestObject[14]:

    static const char qt_meta_stringdata_TestObject[] = {
    //这块数据就是meta信息所需的字符串。是一个字符串的序列。"TestObject\0Author\0Liuyanghe\0Version\0""TestObjectV1.0\0clicked\0\0pressed\0""propertyAChanged\0propertyBChanged\0""onEventA\0onEventB\0doWork\0a\0b\0propertyA\0""propertyB\0TestEnum\0EnumValueA\0EnumValueB"
    };

    5.总结

            Qt 中的元对象系统,简单的可以分为以下几步: 

            1.在继承 QObject 的类中使用 Q_OBJECT 宏,该宏定义了元对象和相关的方法

            2.进行 C++ 编译前,Qt 会运行 moc,解析带有 Q_OBJECT 宏的相关类的信息,生成moc文件,得到元数据并构造元对象

            3.将生成的文件和源文件一起编译

            Qt 元对象系统通过信号与槽机制、运行时类型信息、动态属性系统和国际化支持等功能,极大地增强了 C++ 的功能,使开发者能够更高效地构建复杂的应用程序。它不仅简化了对象间的通信,还提供了强大的运行时反射能力,是 Qt 框架不可或缺的一部分。

    http://www.dtcms.com/wzjs/223924.html

    相关文章:

  1. 做网站骗局免费做网站软件
  2. 无锡微信手机网站制作餐饮营销策划方案
  3. 一个ip两个网站怎么做网站ui设计
  4. 广州网站建设案例绍兴百度seo排名
  5. dedecms 网站搬迁 模板路径错误南宁优化网站收费
  6. 建设网站收费标准谷歌官方app下载
  7. 上海大型网站制作公厦门网站建设公司名单
  8. 网址大全汽车之家汕头seo计费管理
  9. 响应式布局网站开发北京发生大事了
  10. 网站开发教育类近期新闻事件
  11. 网站建设公司业务人员岗位职责工具大全
  12. 招聘网站怎么做推广做了5天游戏推广被抓了
  13. 本网站建设在美国网络零售的优势有哪些
  14. 怎样向搜索引擎提交网站银川网站seo
  15. 做公益网站的说明seo助理
  16. 手机微信网站怎么做的好seo排名赚app下载
  17. 做业务不花钱的网站有哪些谷歌收录查询工具
  18. 哪个网络公司比较好北京seo培训
  19. 微网站和网站的区别块链友情链接平台
  20. 建设银行怎么在网站设置限额神点击恶意点击软件
  21. 小说网页设计代码模板网站关键词优化推广
  22. 网络营销的网站建设报告周口网络推广哪家好
  23. 济宁网站制作公司官网制作开发
  24. 如何建设众筹网站厦门百度快速优化排名
  25. 为什么建新闻网站百度优化是什么
  26. 能免费做微信群推广的网站西安seo盐城
  27. 苹果网站用什么做的吗搜索引擎营销案例
  28. 瑞安做微网站网页设计工资一般多少
  29. 怎么做网站外贸快速搭建网站的工具
  30. 济南微信网站制作免费建站哪个网站最好