Qt元对象系统(Meta-Object)(四)、Moc源代码分析

前言

  前面讲了那么多,Qt的元对象系统是多么多么厉害,多么多么好,那么Moc是具体是怎么工作的,所谓源码面前,了无秘密,下面让我们一探究竟。

打开源代码

前提时安装qt的时候必须选择 源码。比如我的路径是

/opt/Qt5.9.5/5.9.5/Src/qtbase/src/tools/moc/moc.pro

当然我是以管理员的权限打开QtCreator的
在这里插入图片描述

源码追踪解析

打开main.cpp,找到main函数:

int main(int _argc, char **_argv)
{
    return QT_PREPEND_NAMESPACE(runMoc)(_argc, _argv);
}

调用了 runMoc 函数,先来看一部分:

int runMoc(int argc, char **argv)
{
    QCoreApplication app(argc, argv);
    QCoreApplication::setApplicationVersion(QString::fromLatin1(QT_VERSION_STR));

    bool autoInclude = true;
    bool defaultInclude = true;
    Preprocessor pp;
    Moc moc;
    pp.macros["Q_MOC_RUN"];
    pp.macros["__cplusplus"];
    ...

  注意Preprocessor和Moc类,这两个是比较关键的类。Preprocessor做一些预处理的工作,比如找到头文件中的Q_OBJECT、signals和slots等宏。然后Moc类负责分析文件,将结果输出到moc_xxx.cpp文件中去。这也就能解释为什么Q_OBJECT只能生命在头文件,而不能写到cpp文件中了,因为moc根本找不到,它只会扫描头文件。

  再往下是一堆 QCommandLineOption,也就是Qt元对象系统(Meta-Object)(二)、moc的使用一文中写到的moc所支持的一些命令行选项。

// Note that moc isn't translated.
// If you use this code as an example for a translated app, make sure to translate the strings.
QCommandLineParser parser;
parser.setApplicationDescription(QStringLiteral("Qt Meta Object Compiler version %1 (Qt %2)")
                                .arg(mocOutputRevision).arg(QString::fromLatin1(QT_VERSION_STR)));
parser.addHelpOption();
parser.addVersionOption();
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);

QCommandLineOption outputOption(QStringLiteral("o"));
outputOption.setDescription(QStringLiteral("Write output to file rather than stdout."));
outputOption.setValueName(QStringLiteral("file"));
outputOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(outputOption);

QCommandLineOption includePathOption(QStringLiteral("I"));
includePathOption.setDescription(QStringLiteral("Add dir to the include path for header files."));
includePathOption.setValueName(QStringLiteral("dir"));
includePathOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(includePathOption);

QCommandLineOption macFrameworkOption(QStringLiteral("F"));
macFrameworkOption.setDescription(QStringLiteral("Add Mac framework to the include path for header files."));
macFrameworkOption.setValueName(QStringLiteral("framework"));
macFrameworkOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(macFrameworkOption);

QCommandLineOption preprocessOption(QStringLiteral("E"));
preprocessOption.setDescription(QStringLiteral("Preprocess only; do not generate meta object code."));
parser.addOption(preprocessOption);
...

再接下来的话会是一些命令选项的解析,比如-D所指定的宏"-DQT_GUI_LIB",或者 -I 指定的头文件目录。
然后我们看关键代码,有一些qDebug语句是我自己加的:

// 1. preprocess
// 包含的所有头文件
const auto includeFiles = parser.values(includeOption);
// 依次扫面解析头文件
for (const QString &includeName : includeFiles) {
    QByteArray rawName = pp.resolveInclude(QFile::encodeName(includeName), moc.filename);
    if (rawName.isEmpty()) {
        fprintf(stderr, "Warning: Failed to resolve include \"%s\" for moc file %s\n",
                includeName.toLocal8Bit().constData(),
                moc.filename.isEmpty() ? "<standard input>" : moc.filename.constData());
    } else {
        QFile f(QFile::decodeName(rawName));
        if (f.open(QIODevice::ReadOnly)) {
            moc.symbols += Symbol(0, MOC_INCLUDE_BEGIN, rawName);
            /// 关键代码!!!预处理头文件
            moc.symbols += pp.preprocessed(rawName, &f);
            moc.symbols += Symbol(0, MOC_INCLUDE_END, rawName);
        } else {
            fprintf(stderr, "Warning: Cannot open %s included by moc file %s: %s\n",
                    rawName.constData(),
                    moc.filename.isEmpty() ? "<standard input>" : moc.filename.constData(),
                    f.errorString().toLocal8Bit().constData());
        }
    }
}
if (!pp.preprocessOnly) {
    // 2. parse
    moc.parse();
}

// 3. and output meta object code
qDebug() << "outputfile: " << output;

if (output.size()) { // output file specified
#if defined(_MSC_VER)
    if (_wfopen_s(&out, reinterpret_cast<const wchar_t *>(output.utf16()), L"w") != 0)
#else
    out = fopen(QFile::encodeName(output).constData(), "w"); // create output file
    if (!out)
#endif
    {
        fprintf(stderr, "moc: Cannot create %s\n", QFile::encodeName(output).constData());
        return 1;
    }
} else { // use stdout
    out = stdout;
}
qDebug() << "preprocessOnly: " << pp.preprocessOnly;
if (pp.preprocessOnly) {
    fprintf(out, "%s\n", composePreprocessorOutput(moc.symbols).constData());
    qDebug() << "outputfile: " << composePreprocessorOutput(moc.symbols).constData();
} else {
    if (moc.classList.isEmpty())
        moc.note("No relevant classes found. No output generated.");
    else
        moc.generate(out);
}

if (output.size())
    fclose(out);
moc.symbols += pp.preprocessed(moc.filename, &in);

Symbols这个类很简单,大家可以自己去看下,主要记录一些符号的信息,比如标记,行号等。pp.preprocessed(rawName, &f) 是很关键的一个函数,我们跳转进去一探究竟:

Symbols Preprocessor::preprocessed(const QByteArray &filename, QFile *file)
{
	// 读出文件的内容或者将内容映射到内存
    QByteArray input = readOrMapFile(file);

    if (input.isEmpty())
        return symbols;

    // phase 1: get rid of backslash-newlines
    // 将\r\n转换为\n
	// \r转换成\n (os9样式)
	// 反斜杠-换行换成成换行符
    input = cleaned(input);

    // phase 2: tokenize for the preprocessor
    index = 0;
    // 先标记需要预处理的地方,
    // 比如: #include, signals, slots...
    symbols = tokenize(input);

#if 0
    for (int j = 0; j < symbols.size(); ++j)
        fprintf(stderr, "line %d: %s(%s)\n",
               symbols[j].lineNum,
               symbols[j].lexem().constData(),
               tokenTypeName(symbols[j].token));
#endif

    // phase 3: preprocess conditions and substitute macros
    Symbols result;
    // Preallocate some space to speed up the code below.
    // The magic value was found by logging the final size
    // and calculating an average when running moc over FOSS projects.
    result.reserve(file->size() / 300000);
    preprocess(filename, result);
    mergeStringLiterals(&result);

#if 0
    for (int j = 0; j < result.size(); ++j)
        fprintf(stderr, "line %d: %s(%s)\n",
               result[j].lineNum,
               result[j].lexem().constData(),
               tokenTypeName(result[j].token));
#endif
    return result;
}

其中

symbols = tokenize(input);

的作用是先标记需要预处理的地方比如: #include, signals, slots…,比如有头文件:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

signals:
    void Sig();

private slots:
    void onSig();

private:
};

#endif // WIDGET_H

打印的结果是:

line 12: Widget(IDENTIFIER)
line 12: ((LPAREN)
line 12: )(RPAREN)
line 12: ;(SEMIC)
line 14: signals(SIGNALS)
line 14: :(COLON)
line 15: void(VOID)
line 15: Sig(IDENTIFIER)
line 15: ((LPAREN)
line 15: )(RPAREN)
line 15: ;(SEMIC)
line 17: private(PRIVATE)
line 17: slots(SLOTS)
line 17: :(COLON)
line 18: void(VOID)
line 18: onSig(IDENTIFIER)
line 18: ((LPAREN)
line 18: )(RPAREN)

再往下的话也是一个预处理函数 preprocess(filename, result) ,进一步处理。函数原型:

// 参数preprocessed传递的就是preprocessed函数中找到的一些需要预处理的标记信息等
void preprocess(const QByteArray &filename, Symbols &preprocessed);

依次判断preprocessed的token,

void Preprocessor::preprocess(const QByteArray &filename, Symbols &preprocessed)
{
    currentFilenames.push(filename);
    preprocessed.reserve(preprocessed.size() + symbols.size());
    while (hasNext()) {
        Token token = next();

        switch (token) {
        case PP_INCLUDE:
        {
            int lineNum = symbol().lineNum;
            QByteArray include;
            bool local = false;
            if (test(PP_STRING_LITERAL)) {
                local = lexem().startsWith('\"');
                include = unquotedLexem();
            } else
                continue;
            until(PP_NEWLINE);
            ...
        }
        // 熟悉的信号槽
        case SIGNALS:
		case SLOTS: {
		    Symbol sym = symbol();
		    if (macros.contains("QT_NO_KEYWORDS"))
		        sym.token = IDENTIFIER;
		    else
		        sym.token = (token == SIGNALS ? Q_SIGNALS_TOKEN : Q_SLOTS_TOKEN);
		    preprocessed += sym;
		} continue;
		...
}

小结:关于preprocessed函数大概有了个认识,它读取所有需要的头文件,处理保存所有读到的的宏定义信息,比如:#include, public, signals, slots, Q_OBJECT等具体的信息保存到Symbols的一个实例中。

再接下来就是main.cpp中的parse()函数。

if (!pp.preprocessOnly) {
    // 2. parse
    moc.parse();
}

根据读到的宏定义进行解析:

void Moc::parse()
{
    QVector<NamespaceDef> namespaceList;
    bool templateClass = false;
    while (hasNext()) {
        Token t = next();
        switch (t) {
            case NAMESPACE: {
                int rewind = index;
                if (test(IDENTIFIER)) {
                    QByteArray nsName = lexem();
                    QByteArrayList nested;
                    while (test(SCOPE)) {
                        next(IDENTIFIER);
                        nested.append(nsName);
                        nsName = lexem();
                    }
         ...
		}
		if ((t != CLASS && t != STRUCT)|| currentFilenames.size() > 1)
            continue;
        ClassDef def;
        // 判断是否是一个前置声明
		if (parseClassHead(&def)) {
            FunctionDef::Access access = FunctionDef::Private;
            for (int i = namespaceList.size() - 1; i >= 0; --i)
                if (inNamespace(&namespaceList.at(i)))
                    def.qualified.prepend(namespaceList.at(i).classname + "::");
            while (inClass(&def) && hasNext()) {
                switch ((t = next())) {
                case PRIVATE:
                    access = FunctionDef::Private;
                    if (test(Q_SIGNALS_TOKEN))
                        error("Signals cannot have access specifier");
                    break;
                case PROTECTED:
                    access = FunctionDef::Protected;
                    if (test(Q_SIGNALS_TOKEN))
                        error("Signals cannot have access specifier");
                    break;
                case PUBLIC:
                    access = FunctionDef::Public;
                    if (test(Q_SIGNALS_TOKEN))
                        error("Signals cannot have access specifier");
                    break;
                case CLASS: {
                    ClassDef nestedDef;
                    if (parseClassHead(&nestedDef)) {
                        while (inClass(&nestedDef) && inClass(&def)) {
                            t = next();
                            if (t >= Q_META_TOKEN_BEGIN && t < Q_META_TOKEN_END)
                                error("Meta object features not supported for nested classes");
                        }
                    }
                } break;
                case Q_SIGNALS_TOKEN:
                    parseSignals(&def);
                    break;
                case Q_SLOTS_TOKEN:
                    switch (lookup(-1)) {
                    case PUBLIC:
                    case PROTECTED:
                    case PRIVATE:
                        parseSlots(&def, access);
                        break;
                    default:
                        error("Missing access specifier for slots");
                    }
                    break;
                case Q_OBJECT_TOKEN:
                    def.hasQObject = true;
                    if (templateClass)
                        error("Template classes not supported by Q_OBJECT");
                    if (def.classname != "Qt" && def.classname != "QObject" && def.superclassList.isEmpty())
                        error("Class contains Q_OBJECT macro but does not inherit from QObject");
                    break;
                    ...
}                   

ClassDef类中保存了每一个类的具体数据信息,类名,借口id,构造函数列表,信号列表,槽函数列表等:

struct ClassDef : BaseDef {
    QVector<QPair<QByteArray, FunctionDef::Access> > superclassList;

    struct Interface
    {
        Interface() {} // for QVector, don't use
        inline explicit Interface(const QByteArray &_className)
            : className(_className) {}
        QByteArray className;
        QByteArray interfaceId;
    };
    QVector<QVector<Interface> >interfaceList;

    bool hasQObject = false;
    bool hasQGadget = false;

    struct PluginData {
        QByteArray iid;
        QMap<QString, QJsonArray> metaArgs;
        QJsonDocument metaData;
    } pluginData;

    QVector<FunctionDef> constructorList;
    QVector<FunctionDef> signalList, slotList, methodList, publicList;
    int notifyableProperties = 0;
    QVector<PropertyDef> propertyList;
    int revisionedMethods = 0;
    int revisionedProperties = 0;
};

再看parse函数,因为太多了,我们以信号和槽为例,当读到Q_SIGNALS_TOKEN和Q_SLOTS_TOKEN时分别调用了parseSignals和parseSolts函数。这两个函数大体一致,我们只看看parseSignals函数吧。parseSignals函数进行一些检查和信号函数解析保存的工作。

void Moc::parseSignals(ClassDef *def)
{
    int defaultRevision = -1;
    if (test(Q_REVISION_TOKEN)) {
        next(LPAREN);
        QByteArray revision = lexemUntil(RPAREN);
        revision.remove(0, 1);
        revision.chop(1);
        bool ok = false;
        defaultRevision = revision.toInt(&ok);
        if (!ok || defaultRevision < 0)
            error("Invalid revision");
    }

    next(COLON);
    while (inClass(def) && hasNext()) {
        switch (next()) {
        case PUBLIC:
        case PROTECTED:
        case PRIVATE:
        case Q_SIGNALS_TOKEN:
        case Q_SLOTS_TOKEN:
            prev();
            return;
        case SEMIC:
            continue;
        case FRIEND:
            until(SEMIC);
            continue;
        case USING:
            error("'using' directive not supported in 'signals' section");
        default:
            prev();
        }
        FunctionDef funcDef;
        funcDef.access = FunctionDef::Public;
        parseFunction(&funcDef);
        if (funcDef.isVirtual)
            warning("Signals cannot be declared virtual");
        if (funcDef.inlineCode)
            error("Not a signal declaration");
        if (funcDef.revision > 0) {
            ++def->revisionedMethods;
        } else if (defaultRevision != -1) {
            funcDef.revision = defaultRevision;
            ++def->revisionedMethods;
        }
        def->signalList += funcDef;
        while (funcDef.arguments.size() > 0 && funcDef.arguments.constLast().isDefault) {
            funcDef.wasCloned = true;
            funcDef.arguments.removeLast();
            def->signalList += funcDef;
        }
    }
}

其中的FunctionDef类与ClassDef的功能类似,FunctionDef保存的时函数的具体信息,比如参数列表,返回值等:

struct FunctionDef
{
    FunctionDef(): returnTypeIsVolatile(false), access(Private), isConst(false), isVirtual(false), isStatic(false),
                   inlineCode(false), wasCloned(false), isCompat(false), isInvokable(false),
                   isScriptable(false), isSlot(false), isSignal(false), isPrivateSignal(false),
                   isConstructor(false), isDestructor(false), isAbstract(false), revision(0) {}
    Type type;
    QByteArray normalizedType;
    QByteArray tag;
    QByteArray name;
    bool returnTypeIsVolatile;

    QVector<ArgumentDef> arguments;

    enum Access { Private, Protected, Public };
    Access access;
    bool isConst;
    bool isVirtual;
    bool isStatic;
    bool inlineCode;
    bool wasCloned;

    QByteArray inPrivateClass;
    bool isCompat;
    bool isInvokable;
    bool isScriptable;
    bool isSlot;
    bool isSignal;
    bool isPrivateSignal;
    bool isConstructor;
    bool isDestructor;
    bool isAbstract;

    int revision;
};

再接下来就是生成代码了,还是main.cpp中的函数,代码生成主要还是generate函数:

// 3. and output meta object code
    if (output.size()) { // output file specified
#if defined(_MSC_VER)
        if (_wfopen_s(&out, reinterpret_cast<const wchar_t *>(output.utf16()), L"w") != 0)
#else
        out = fopen(QFile::encodeName(output).constData(), "w"); // create output file
        if (!out)
#endif
        {
            fprintf(stderr, "moc: Cannot create %s\n", QFile::encodeName(output).constData());
            return 1;
        }
    } else { // use stdout
        out = stdout;
    }
    if (pp.preprocessOnly) {
        fprintf(out, "%s\n", composePreprocessorOutput(moc.symbols).constData());
        qDebug() << "outputfile: " << composePreprocessorOutput(moc.symbols).constData();
    } else {
        if (moc.classList.isEmpty())
            moc.note("No relevant classes found. No output generated.");
        else
            moc.generate(out);
    }

generate函数:

void Moc::generate(FILE *out)
{
    QByteArray fn = filename;
    int i = filename.length()-1;
    while (i > 0 && filename.at(i - 1) != '/' && filename.at(i - 1) != '\\')
        --i;                                // skip path
    if (i >= 0)
        fn = filename.mid(i);
    fprintf(out, "/****************************************************************************\n"
            "** Meta object code from reading C++ file '%s'\n**\n" , fn.constData());
    fprintf(out, "** Created by: The Qt Meta Object Compiler version %d (Qt %s)\n**\n" , mocOutputRevision, QT_VERSION_STR);
    fprintf(out, "** WARNING! All changes made in this file will be lost!\n"
            "*****************************************************************************/\n\n");


    if (!noInclude) {
        if (includePath.size() && !includePath.endsWith('/'))
            includePath += '/';
        for (int i = 0; i < includeFiles.size(); ++i) {
            QByteArray inc = includeFiles.at(i);
            if (inc.at(0) != '<' && inc.at(0) != '"') {
                if (includePath.size() && includePath != "./")
                    inc.prepend(includePath);
                inc = '\"' + inc + '\"';
            }
            fprintf(out, "#include %s\n", inc.constData());
        }
    }
    if (classList.size() && classList.constFirst().classname == "Qt")
        fprintf(out, "#include <QtCore/qobject.h>\n");

    fprintf(out, "#include <QtCore/qbytearray.h>\n"); // For QByteArrayData
    fprintf(out, "#include <QtCore/qmetatype.h>\n");  // For QMetaType::Type
    if (mustIncludeQPluginH)
        fprintf(out, "#include <QtCore/qplugin.h>\n");

    const auto qtContainers = requiredQtContainers(classList);
    for (const QByteArray &qtContainer : qtContainers)
        fprintf(out, "#include <QtCore/%s>\n", qtContainer.constData());


    fprintf(out, "#if !defined(Q_MOC_OUTPUT_REVISION)\n"
            "#error \"The header file '%s' doesn't include <QObject>.\"\n", fn.constData());
    fprintf(out, "#elif Q_MOC_OUTPUT_REVISION != %d\n", mocOutputRevision);
    fprintf(out, "#error \"This file was generated using the moc from %s."
            " It\"\n#error \"cannot be used with the include files from"
            " this version of Qt.\"\n#error \"(The moc has changed too"
            " much.)\"\n", QT_VERSION_STR);
    fprintf(out, "#endif\n\n");

    fprintf(out, "QT_BEGIN_MOC_NAMESPACE\n");
    fprintf(out, "QT_WARNING_PUSH\n");
    fprintf(out, "QT_WARNING_DISABLE_DEPRECATED\n");

    fputs("", out);
    for (i = 0; i < classList.size(); ++i) {
        Generator generator(&classList[i], metaTypes, knownQObjectClasses, knownGadgets, out);
        generator.generateCode();
    }
    fputs("", out);

    fprintf(out, "QT_WARNING_POP\n");
    fprintf(out, "QT_END_MOC_NAMESPACE\n");
}

然后核心代码是由 generator.generateCode() 来生成的。void Generator::generateCode()的代码非常多,我们这里只分析一下moc是如何生成信号的实现的。

void Generator::generateCode()
{
	...
//
// Generate internal signal functions
//
    for (int signalindex = 0; signalindex < cdef->signalList.size(); ++signalindex)
        generateSignal(&cdef->signalList[signalindex], signalindex);
	...
}
void Generator::generateSignal(FunctionDef *def,int index)
{
    if (def->wasCloned || def->isAbstract)
        return;
    fprintf(out, "\n// SIGNAL %d\n%s %s::%s(",
            index, def->type.name.constData(), cdef->qualified.constData(), def->name.constData());

    QByteArray thisPtr = "this";
    const char *constQualifier = "";

    if (def->isConst) {
        thisPtr = "const_cast< " + cdef->qualified + " *>(this)";
        constQualifier = "const";
    }

    Q_ASSERT(!def->normalizedType.isEmpty());
    if (def->arguments.isEmpty() && def->normalizedType == "void" && !def->isPrivateSignal) {
        fprintf(out, ")%s\n{\n"
                "    QMetaObject::activate(%s, &staticMetaObject, %d, nullptr);\n"
                "}\n", constQualifier, thisPtr.constData(), index);
        return;
    }

    int offset = 1;
    for (int j = 0; j < def->arguments.count(); ++j) {
        const ArgumentDef &a = def->arguments.at(j);
        if (j)
            fprintf(out, ", ");
        fprintf(out, "%s _t%d%s", a.type.name.constData(), offset++, a.rightType.constData());
    }
    if (def->isPrivateSignal) {
        if (!def->arguments.isEmpty())
            fprintf(out, ", ");
        fprintf(out, "QPrivateSignal _t%d", offset++);
    }

    fprintf(out, ")%s\n{\n", constQualifier);
    if (def->type.name.size() && def->normalizedType != "void") {
        QByteArray returnType = noRef(def->normalizedType);
        fprintf(out, "    %s _t0{};\n", returnType.constData());
    }

    fprintf(out, "    void *_a[] = { ");
    if (def->normalizedType == "void") {
        fprintf(out, "nullptr");
    } else {
        if (def->returnTypeIsVolatile)
             fprintf(out, "const_cast<void*>(reinterpret_cast<const volatile void*>(&_t0))");
        else
             fprintf(out, "const_cast<void*>(reinterpret_cast<const void*>(&_t0))");
    }
    int i;
    for (i = 1; i < offset; ++i)
        if (i <= def->arguments.count() && def->arguments.at(i - 1).type.isVolatile)
            fprintf(out, ", const_cast<void*>(reinterpret_cast<const volatile void*>(&_t%d))", i);
        else
            fprintf(out, ", const_cast<void*>(reinterpret_cast<const void*>(&_t%d))", i);
    fprintf(out, " };\n");
    fprintf(out, "    QMetaObject::activate(%s, &staticMetaObject, %d, _a);\n", thisPtr.constData(), index);
    if (def->normalizedType != "void")
        fprintf(out, "    return _t0;\n");
    fprintf(out, "}\n");
}

总结

  到这里基本上所有的主要函数已经分析完了,大致流程是:preprocessed函数读取所有的头文件,解析出来每一个宏和关键字,parse()函数再根据读出来的信息解析保存每一个类,函数等具体信息,然后generate负责将内容写入到文件。分析的比较粗糙,如果想一探究竟还是得自己研究每个细节,Qt的源码可读性也比较好,大家可根据我提到的几个函数自行去研究学习。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
用c++/qt写的项目,项目都经测试过,真实可靠,可供自己学习c++/qtQt是一个用标准C++编写的跨平台开发类库,它对标准C++进行了扩展,引入了对象系统信号、属性等特性,使应用程序的开发变得更高效。 Qt类库中大量的类以模块形式分类组织的,包括基本模块和扩展模块等。一个模块通常就是一个编程主题,如数据库、图表、网络等。 一、Qt核心特点 1.1.概述 Qt本身并不是一种编程语言,它本质上是一个跨平台的C++开发类库,是用标准C++编写的类库,它为开发GUI应用程序和非GUI应用程序提供了各种类。 Qt对标准C++进行了扩展,引入了一些新概念和功能,例如信号对象属性等。Qt对象编译器(Meta-Object Compiler,MOC)是一个预处理器,在源程序被编译前先将这些Qt特性的程序转换为标准C++兼容的形式,然后再由标准C++编译器进行编译。这就是为什么在使用信号机制的类里,必须添加一个Q_OBJECT宏的原因,只有添加了这个宏,moc才能对类里的信号代码进行预处理。 Qt Core模块是Qt类库的核心,所有其他模块都依赖于此模块,如果使用qmake来构建项目,Qt Core模块则是被自动加入的。 Qt为C++语言增加的特性就是在Qt Core模块里实现的,这些扩展特性由Qt对象系统实现,包括信号机制、属性系统、动态类型转换等。 1.2.对象系统 Qt对象系统Meta-Object-System)提供了对象之间通信的信号机制、运行时类型信息和动态属性系统对象系统由以下三个基础组成: 1.QObject类是所有使用对象系统的类的基类; 2.在一个类的private部分声明Q_OBJECT宏,使得类可以使用对象的特性,如动态属性、信号。 3.MOC对象编译器)为每个QObject的子类提供必要的代码来实现对象系统的特征。 构建项目时,MOC工具读取C++源文件,当它发现类的定义里有Q_OBJECT宏时,它就会为这个类生成另外一个包含有对象支持代码的C++源文件,这个生成的源文件连同类的实现文件一起被编译和连接。 除了信号机制外,对象还提供如下一些功能。 1.QObject::metaObject()函数返回类关联的对象对象类QMetaObject包含了访问对象的一些接口函数,例如QMetaObject::className()函数可在运行时返回类的名称字符串。 QObject obj=new QPushButton; obj->metaObject()->className(); 2.QMetaObject::newInstance()函数创建类的一个新的实例。 3.QObject::inherits(const charclassName)函数判断一个对象实例是否是名称为className的类或QObject的子类的实例。 1.3.属性系统 1.属性定义 Qt提供一个Q_PROPERTY()宏可以定义属性,它也是属于对象系统实现的。Qt的属性系统与C++编译器无关,可以用任何标准的C++编译器编译定义了属性的Qt C++程序。 2.属性的使用 不管是否用READ和WRITE定义了接口函数,只要知道属性名称,就可以通过QObject::property()读取属性值,并通过QObject::setProperty()设置属性值。 3.动态属性 QObject::setProperty()函数可以在运行时为类定义一个新的属性,称之为动态属性。动态属性是针对类的实例定义的。 动态属性可以使用QObject::property()查询,就如在类定义里用Q_PROPERTY宏定义的属性一样。 例如,在数据表编辑界面上,一些字段是必填字段,就可以在初始化界面时为这些字段的关联显示组件定义一个新的required属性,并设置值为“true"。 4.类的附加信息 属性系统还有一个宏Q_CLASSINFO(),可以为类的对象定义”名称——值“信息。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值