C++ QT 嘴试题--集锦

1、C++的默认函数都有哪些?

在C++中,默认函数(也称为特殊成员函数)是一组在类定义中不需要显式声明和定义的函数。这些函数在特定的情况下会被编译器自动合成(隐式生成)。以下是C++中的默认函数:

  • 默认构造函数(Default Constructor):当没有提供其他构造函数时,编译器会提供一个默认构造函数。它通常不做任何初始化操作或只执行默认的初始化。
class MyClass {  
public:  
    MyClass() {  
        // 默认构造函数  
    }  
};

如果没有提供任何构造函数,编译器也会生成一个默认的无参构造函数。

  • 拷贝构造函数(Copy Constructor):当用同类的一个对象来初始化另一个对象时,会调用拷贝构造函数。如果没有提供拷贝构造函数,编译器会生成一个默认的拷贝构造函数,执行成员变量的浅拷贝。
class MyClass {  
public:  
    MyClass(const MyClass& other) {  
        // 拷贝构造函数  
    }  
};
  • 拷贝赋值运算符(Copy Assignment Operator):当用一个对象给另一个同类对象赋值时,会调用拷贝赋值运算符。如果没有提供拷贝赋值运算符,编译器会生成一个默认的拷贝赋值运算符,执行成员变量的浅拷贝。
class MyClass {  
public:  
    MyClass& operator=(const MyClass& other) {  
        // 拷贝赋值运算符  
        return *this;  
    }  
};
  • 移动构造函数(Move Constructor):C++11引入移动语义后,当用一个临时对象或即将销毁的对象来初始化另一个对象时,会调用移动构造函数。如果没有提供移动构造函数,编译器会生成一个默认的移动构造函数,执行成员变量的移动语义初始化。
class MyClass {  
public:  
    MyClass(MyClass&& other) noexcept {  
        // 移动构造函数  
    }  
};
  • 移动赋值运算符(Move Assignment Operator):当用一个临时对象或即将销毁的对象给另一个同类对象赋值时,会调用移动赋值运算符。如果没有提供移动赋值运算符,编译器会生成一个默认的移动赋值运算符,执行成员变量的移动语义赋值。
class MyClass {  
public:  
    MyClass& operator=(MyClass&& other) noexcept {  
        // 移动赋值运算符  
        return *this;  
    }  
};
  • 析构函数(Destructor):当对象生命周期结束时,会调用析构函数。如果没有提供析构函数,编译器会生成一个默认的析构函数,它执行默认的清理操作。
class MyClass {  
public:  
    ~MyClass() {  
        // 析构函数  
    }  
};
  • 转换构造函数(Converting Constructor):这种函数允许一个类的对象被用作初始化另一个类的对象的构造函数的参数。这种构造函数不是由编译器自动生成的,而是由程序员在类定义中显式声明的。

这些默认函数在大多数情况下都足够使用,但在某些特定场景下,你可能需要自定义这些函数以满足特定的需求,例如执行深拷贝、禁止拷贝、启用移动语义等。此外,C++11之后还引入了= default和= delete语法来显式地要求编译器生成默认实现或删除这些特殊成员函数。

2、C++的虚函数实现机制?

虚函数是实现多态(动态绑定)/接口函数的基础。利用虚表实现。
C++对象的内存布局,对象的前8位(64位系统)为虚表指针(vtpr),指向对象所对应的虚表。虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针。
同一个类的不同实例共用同一份虚函数表,他们都通过一个虚函数表指针指向该虚函数表。
C++中的虚函数表实现机制以及用C语言对其进行的模拟实现

题目:

  • 讲一下C++的多态? —— 静态多态(函数重载),动态多态(虚函数)
  • 讲一下C++的虚函数的实现机制?
  • 多继承情况下,基类Base1与Base2都有虚函数,继承自Base1和Base2的子类Derived1有几个虚表?
    ----- 1个。Derived1的虚函数表仍然是保存到第1个拥有虚函数表的那个基类的后面的。(详情可见于上文博文链接)

3、C++的智能指针

C++11为C++标准库带来了三个智能指针,分别是shared_ptr,unique_ptr与weak_ptr。
C++智能指针的实现原理为引用计数。引用计数无法处理循环引用的情况。
shared_ptr实现原理是同一个内存空间每多一个指针指向就计数加1,如果计数变为0就释放内存空间。当用普通指针初始化的时候,只能使用一次普通指针。它还可以自定义释放函数。
unique_ptr是计数只能为1,没有拷贝构造函数。
weak_ptr只能指向该内存空间而没有所有权。主要用于辅助第一个指针,防止出现互锁。借助weak_ptr类型指针, 我们可以获取shared_ptr指针的一些状态信息,比如有多少指向相同的shared_ptr指针、shared_ptr指针指向的堆内存是否已经被释放等等。在构建weak_ptr指针对象时,可经常利用已有的shared_ptr指针为其初始化。
Modern C++ 智能指针详解

4、互斥锁、可重入锁、读写锁与自旋锁

  • mutex 互斥量

mutex是睡眠等待类型的锁,当线程抢互斥锁失败的时候,线程会陷入休眠。优点就是节省CPU资源,缺点就是休眠唤醒会消耗一点时间。
依据同一线程是否能多次加锁,把互斥量又分为如下两类:
是:递归互斥量recursive mutex,也称可重入锁,reentrant lock
否:非递归互斥量non-recursive mutex,也称不可重入锁,non-reentrant mutex

  • read-write lock 读写锁

又称“共享-独占锁”,对于临界区区分读和写,读共享,写独占。
读写锁的特性:
当读写锁被加了写锁时,其他线程对该锁加读锁或者写锁都会阻塞。
当读写锁被加了读锁时,其他线程对该锁加写锁会阻塞,加读锁会成功。
适用于多读少写的场景。

  • spinlock 自旋锁

自旋,更通俗的一个词时“忙等待”(busy waiting)。最通俗的一个理解,其实就是死循环。
自旋锁不会引起线程休眠。当共享资源的状态不满足时,自旋锁会不停地循环检测状态(循环检测状态利用了CPU提供的原语Compare&Exchange来保证原子性)。因为不会陷入休眠,而是忙等待的方式也就不需要条件变量。不休眠就不会引起上下文切换,但是会比较浪费CPU。

题目:

  • 讲一下可重入锁?
  • 讲一下自旋锁?自旋锁循环检测状态的时候如何保证原子性?

5、QT信号与槽的原理?

  • Qt信号槽的调用流程

MOC查找头文件中的signal与slots,标记出信号槽。将信号槽信息储存到类静态变量staticMetaObject中,并按照声明的顺序进行存放,建立索引。
connect链接,将信号槽的索引信息放到一个双向链表中,彼此配对。
emit被调用,调用信号函数,且传递发送信号的对象指针,元对象指针,信号索引,参数列表到active函数。
active函数在双向链表中找到所有与信号对应的槽索引,根据槽索引找到槽函数,执行槽函数。

  • 信号槽的实现:元对象编译器MOC

元对象编译器MOC负责解析signals、slot、emit等标准C++不存在的关键字,以及处理Q_OBJECT、Q_PROPERTY、Q_INVOKABLE等相关的宏,生成moc_xxx.cpp的C++文件(使用黑魔法来变现语法糖)。比如信号函数只要声明、不需要自己写实现,就是在这个moc_xxx.cpp文件中自动生成的。

moc的本质就是反射器。

  • Qt信号槽的链接方式(connect的第五个参数)

Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。
Qt::QueuedConnection:槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。
Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。

题目:

  • Qt connect的第五个参数(信号槽链接方式)?
  • Qt信号槽的调用流程?

6、QT核心机制–元对象系统

元对象系统 Qt的元对象系统(meta-object)提供了用于内部对象通讯的信号与槽(signals & slots)机制,运行时类型信息,以及动态属性系统(dynamic property system)。 整个元对象系统基于三个东西建立:

  • QObject类为所有对象提供了一个基类,只要继承此类,那创建出的对象便可以使用元对象系统。
  • 在声明类时,将Q_OBJECT宏放置于类的私有区域就可以在类中使能元对象特性,诸如动态属性,信号,以及槽。一般实际使用中,我们总是把Q_OBJECT宏放置在类声明时的开头位置,除此之外我们的类还需要继承QObject类。
  • 元对象编译器(Meta-Object Compiler,缩写moc),为每个QObject的子类提供必要的代码去实现元对象特性。我们可以认为Qt对C++进行了一些拓展,moc则是负责将这些拓展语法翻译成原生的C++语法,之后交给C++编译器去编译。

moc工具读取c++源文件。如果它找到一个或多个包含Q_OBJECT宏的类声明,它会生成另一个c++源文件,其中包含每个类的元对象代码。生成的源文件要么#include到类的源文件中,要么(更常见的情况)编译并链接到类的实现。

7、介绍QT中QObject基类、宏Q_OBJECT

QObject类是所有Qt对象的基类。QObject是Qt对象模型的核心。该模型的核心特征是一个非常强大的机制,用于无缝对象通信,称为信号和槽。
Q_OBJECT宏必须出现在类定义的私有部分,声明自己的信号和槽,或者使用Qt的元对象模型提供的其他服务。

注意:Q_OBJECT宏要求类是QObject的子类。使用Q_GADGET而不是Q_OBJECT来启用元对象系统对非QObject子类中的枚举的支持。

8、介绍QT Creator程序构建的流程

qmake 是 Qt 构建系统中的一个关键工具,它负责生成 Makefile 或其他项目文件,用于构建 Qt 项目。在构建过程中,qmake 会与 uic(User Interface Compiler)和 moc(Meta-Object Compiler)等工具协同工作,以确保 Qt 应用程序能够正确编译和链接。

以下是 qmake 如何与 uic 和 moc 进行工程构建的简要概述:

  1. qmake 解析项目文件
    首先,qmake 读取 .pro 文件(或其他格式的项目文件),解析其中定义的源文件、头文件、资源文件、库依赖等信息。这包括识别 Qt 特定的设置和配置。
  2. 处理 UI 文件
    如果项目包含 Qt Designer 创建的 UI 文件(.ui),qmake 会识别这些文件,并在构建过程中调用 uic。
    uic:uic 工具负责将 .ui 文件转换成 C++ 代码。这些代码通常包含界面元素的布局和初始化代码,可以直接在应用程序中使用。
    qmake 会将生成的 C++ 代码添加到构建系统的源文件中,确保在编译时被包含进去。
  3. 处理包含 Q_OBJECT 宏的类
    对于包含 Q_OBJECT 宏的 Qt 类,qmake 会识别它们,并在构建过程中调用 moc。
    moc:moc 工具处理包含 Q_OBJECT 宏的类,生成额外的 C++ 代码,用于实现 Qt 的元对象系统(如信号和槽机制)。
    qmake 会将 moc 生成的代码添加到构建系统的源文件中,确保在编译时被包含进去。
  4. 处理资源文件
    rcc工具主要用于将资源文件(如图片、文档等)编译成对应的独立源代码文件或二进制资源文件。具体来说,rcc工具会读取资源文件描述文件(如myresource.qrc),并根据其中的描述将图片、文档等资源编译成独立的源代码文件(如qrc_myresource.cpp)或二进制资源文件(如myresource.rcc)。
    对于太大的图片、音频、视频等文件,不适合集成到目标程序内部,rcc工具可以将这些文件单独编译成外挂资源(*.rcc)。这样,在程序运行时,可以动态加载这些资源,提高程序的灵活性和效率。
  5. 生成 Makefile 或其他项目文件
    基于上述解析和处理的结果,qmake 生成一个 Makefile 或其他构建系统所需的项目文件。这个 Makefile 或项目文件包含了编译和链接应用程序所需的所有命令和设置。
  6. 构建过程
    最后,开发者可以使用 make 命令(或其他构建工具)来执行 Makefile,开始实际的编译和链接过程。在这个过程中,编译器会处理所有源文件(包括 uic 和 moc 生成的代码),并最终生成可执行文件或库。

9. Qt Widgets、QML、Qt Quick

引入QML和Qt Quick是为了更好地满足现代化UI界面(要炫酷和复杂的UI)的需求,提高开发效率和代码可维护性。同时,Qt Widgets和QML/Qt Quick可以灵活地结合使用。
Qt Quick其实是QML的框架,我们使用Qt Quick其实是为了更好更方便地使用QML(本质上还是使用QML),只不过相对于直接使用QML来说,Qt Quick提供了更多的工具和库,使得开发更加的高效和便捷。
Qt Widgets是Qt框架中的传统UI组件库,它提供了大量的UI组件和工具,可以用于开发桌面应用程序。Qt Widgets具有很高的性能和稳定性,而且与C++语言紧密集成,非常适合开发大型、复杂的桌面应用程序(这也是相对与QML来说很大的一个优势)。
Qt Widgets、QML、Qt Quick 的区别对比

题目:

  • 有了Qt Widgets,为什么还要引入QML和Qt Quick
  • Widgets与Quick 的优缺点

10. C++11区别于C++98的特性

列表初始化

类型推导

  • auto

C++11中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,可以给程序的书写提供许多方便。auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。常用于范围for和迭代器命名。

  • decltype

根据表达式的实际类型推演出定义变量时所用的类型

  1. 推演表达式类型作为变量的定义类型
     int a = 1,b=2;
	 // 用decltype推演a+b的实际类型,作为定义c的类型
	 decltype(a+b) c;
  1. 推演函数返回值的类型

新增容器

  • array

常用的用[]定义的都是在栈上开辟的数组,array是在堆上开辟空间,它的基本用法和序列式容器差不多。

  • forward_list

与list不同,它使用的是单链表,虽然这样节省了空间,但是进行操作时的效率比list低。

  • unordered_set
  • unordered_map

有unordered_set和unprdered_map两种,和set和map相比,它们的底层使用的是哈希桶,效率比底层是红黑树的set和map高很多,多数情况下优先使用unordered系列的容器。

  • lambda表达式

lambda表达式实际是一个匿名函数,它能简化代码。

书写格式:

[capture-list] (parameters) mutable -> return-type { statement }
lambda表达式各部分说明:

  • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
  • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
    mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
    注意: 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。
  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值