面试-QT版本

QT信号槽机制的优缺点

(1)问题:

为什么Qt使用信号与槽机制而不是传统的回调函数机制进行对象间的通信呢?

回调函数的本质是“你想让别人的代码执行你的代码,而别人的代码你又不能动”这种需求下产生的。

回调函数是函数指针的一种用法,如果多个类都关注某个类的状态变化,此时需要维护一个列表,以存放多个回调函数的地址。对于每一个被关注的类,都需要做类似的工作,因此这种做法效率低,不灵活。

(2)解决办法:

Qt使用信号与槽机制来解决这个问题,程序员只需要指定一个类含有哪些信号函数、哪些槽函数,Qt会处理信号函数和槽函数之间的绑定。当信号函数被调用时,Qt会找到并执行与其绑定的槽函数。允许一个信号函数和多个槽函数绑定,Qt会依次找到并执行与一个信号函数绑定的所有槽函数,这种处理方式更灵活。

(3)优点:

Qt信号与槽机制降低了Qt对象的耦合度.

 

 

多线程情况下, Qt中的信号槽分别在什么线程中执行, 如何控制?

通过connect函数的第五个参数connectType来控制。

connect用于连接qt的信号和槽,在qt编程过程中不可或缺。它其实有第五个参数,只是一般使用默认值,在满足某些特殊需求的时候可能需要手动设置。 

Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。 

Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。 

Qt::QueuedConnection:槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。 

Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。 

Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。

 

Qt 信号槽机制

自定义信号槽注意事项:

(1)发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);

(2)使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;

(3)槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;

(4)使用 emit 在恰当的位置发送信号;

(5)使用QObject::connect()函数连接信号和槽;

(6)任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数。

信号槽的多种用法:

(1)一个信号可以和多个槽相连

  如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。

(2)多个信号可以连接到一个槽

  只要任意一个信号发出,这个槽就会被调用。

(3)一个信号可以连接到另外的一个信号

  当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。

(4)槽可以被取消链接

  这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。

(5)使用Lambda 表达式

在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。

 

 

继承与派生的区别

1、角度不同

继承是从子类的角度讲的,派生是从基类的角度讲的。

2、定义不同

派生指江河的源头产生出支流。引申为从一个主要事物的发展中分化出来。继承 是面向对象软件技术当中的一个概念,与多态、抽象共为面向对象的三个基本特征。 继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。

 

 

单继承和多继承

单继承(派生类只从一个直接基类继承)时派生类的定义:

class 派生类名:继承方式 基类名

{

新增成员声明;

}

 

多继承时派生类的定义:

class 派生类名:继承方式1 基类名1,继承方式2 基类名2,…

{

成员声明;

}

注意:每一个“继承方式”,只用于限制对紧随其后之基类的继承。

 

 

三种继承方式:公有继承,私有继承和保护继承

公有继承(public)

1)继承的访问控制

基类的public和protected成员:访问属性在派生类中保持不变;

基类的private成员:不可直接访问。

2)访问权限

派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;

通过派生类的对象:只能访问public成员。

3)公有派生类对象可以被当作基类的对象使用,反之则不可。

派生类的对象可以隐含转换为基类对象;

派生类的对象可以初始化基类的引用;

派生类的指针可以隐含转换为基类的指针。

通过基类对象名、指针只能使用从基类继承的成员,派生类新增的成员就不能使用了。

 

 

Qt4与Qt5的三个区别

1.新增widgets模块

        在Qt4中,Qt提供的全部图形界面相关类都包含在Qt Gui模块中,但QT5将一些图形界面类移到了QT widgets模块中。所以在Pro文件中,需要增加一句话:

greaterThan(QT_MAJOR_VERSION, 4):QT += widgets

意思是如果Qt版本大于Qt4,则需要增加widgets模块。

2.信号与槽的语法

 Qt4中关联信号与槽一般这样写:

connect(sender, SINGAL(valueChanged(QString, QString)), receiver, SLOT(showValue(QString)));

        但这样写,没有编译器检查,有时编译器通过但应该调用的槽函数没有执行。这是编译器不能给出错误信息,只能在运行时看是否有警告。

Qt5中关联信号与槽是这样写:

connect(sender, &Sender::valueChanged, receiver, &Receiver::showValue);

         这种写法支持编译器检查,能够在编译时就发现错误;并支持类型的隐式转换。

3.对C++11的支持

         Qt5支持C++11,但有些编译器默认不开启。所以需要在Pro文件中增加一行:

CONFIG  +=  c++11

 

 

多态

多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。

C++中,实现多态有以下方法:虚函数抽象类,覆盖,模板(重载和多态无关)。

 

C++ 类(纯虚函数和抽象类)

a. 纯虚函数是一个在基类中只有声明的虚函数,在基类中无定义。要求在任何派生类中都定义自己的版本;

b. 纯虚函数为各派生类提供一个公共界面(接口的封装和设计,软件的模块功能划分);

c. 纯虚函数声明形式:

virtual void func()=0;  //纯虚函数

d. 一个具有纯虚函数的类称为抽象类。

结论:

(1). 抽象类对象不能做函数参数,不能创建对象,不能作为函数返回类型;

(2).可以声明抽象类指针,可以声明抽象类的引用;

(3). 子类必须继承父类的纯虚函数才能创建对象。

 

 

QMainForm是从哪里派生的?

QMainWindow::QWidget::QObject

 

Qwidget、Qobejct实现了哪些功能

QObject

  1. 信号和槽的非常强大的机制,使用connect()把信号和槽连接起来并且可以用disconnect()来破坏这种连接。为了避免从不结束的通知循环,你可以调用blockSignals()临时地阻塞信号。保护函数connectNotify()和disconnectNotify()使跟踪连接成为可能。
  2. QObject可以通过event()接收事件并且过滤其它对象的事件。详细情况请参考installEventFilter()和eventFilter()。一个方便的处理者,childEvent(),能够被重新实现来捕获子对象事件。
  3. 最后但不是最不重要的一点,QObject提供了Qt中最基本的定时器,关于定时器的高级支持请参考QTimer
  4. 注意Q_OBJECT宏对于任何实现信号、槽和属性的对象都是强制的。
  5. 所有的Qt窗口部件继承了QObject。方便的函数isWidgetType()返回这个对象实际上是不是一个窗口部件。它比inherits( "QWidget" )快得多。

 QWidget

  1. QWidget类是所有用户界面对象的基类。
  2. Widget是用户界面的基本单元:它从窗口系统接收鼠标,键盘和其他事件,并在屏幕上绘制自己。每个Widget都是矩形的,它们按照Z-order进行排序。

面试-QT版本_面试

 

 

C++指针和引用及区别 

1.变量

变量在内存中的操作其实是需要经过2个步骤的:

找出与变量名相对应的内存地址。

根据找到的地址,取出该地址对应的内存空间里面的值进行操作。

2.指针

指针的特殊之处在于:指针变量相对应的内存空间存储的值恰好是某个内存地址。这也是指针变量区别去其他变量的特征之一。

3.引用

引用是一种特殊的指针。引用是一个指向其它对象的常量指针,它保存着所指对象的存储地址。并且使用的时候会自动解引用,而不需要像使用指针一样显式提领。

4.指针和引用的区别总结

①指针有自己的一块空间,而引用只是一个别名;

②使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;

③指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象的引用;

④作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变引用所指向的对象;

⑤可以有const指针,但是没有const引用;

⑥指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能被改变;

⑦指针可以有多级指针(**p),而引用至于一级;

⑧指针和引用使用++运算符的意义不一样;

⑨如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。 

 

  

参数传值、指针、引用有什么区别,在什么场景常用哪种传递方式?

传值、传址、传引用的区别,哪个更高效?

1.传值

这种传递方式中,实参和形参是两个不同的地址空间,参数传递的实质是将原函数中变量的值,复制到被调用函数形参所在的存储空间中,这个形参的地址空间在函数执行完毕后,会被回收掉。整个被调用函数对形参的操作,只影响形参对应的地址空间,不影响原来函数中的变量的值,因为这两个不是同一个存储空间。

即使形参的值在函数中发生了变化,实参的值也完全不会受到影响,仍为调用前的值。

2.传址

这种参数传递方式中,实参是变量的地址,形参是指针类型的变量,在函数中对指针变量的操作,就是对实参(变量地址)所对应的变量的操作,函数调用结束后,原函数中的变量的值将会发生改变。

被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。

3.传引用

这种参数传递方式中,形参是引用类型变量,其实就是实参的一个别名,在被调用函数中,对引用变量的所有操作等价于对实参的操作,这样,整个函数执行完毕后,原先的实参的值将会发生改变。

被调函数对形参做的任何操作都影响了主调函数中的实参变量。

4.哪一种更高效?

在内置类型当中三种传递方式的效率上都差不多;

在自定义类型当中,传引用的更高效一些,因为它没有对形参进行一次拷贝

 

 

const与#define有什么区别

(1)const和#define都可以定义常量,但是const用途更广。

(2)const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
(3) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。

 

struct和class有什么区别?

C++中,class与struct都可以定义一个类。他们有以下两点区别:

1.默认继承权限,如果不指定,来自class的继承按照private继承处理,来自struct的继承按照public继承处理;

2.成员的默认访问权限。class的成员默认是private权限,struct默认是public权限。

以上两点也是struct和class最基本的差别,也是最本质的差别; 

但是在C++中,struct进行了扩展,现在它已经不仅仅是一个包含不同数据类型的数据结构了,它包括了更多的功能。

Struct能包含成员函数、有自己的构造函数、可以有析构函数、支持继承、支持多态、支持Private、Protected、Public关键字。 

如果是class的父类是struct关键字描述的,那么默认访问属性是什么?

当出现这种情况时,到底默认是public继承还是private继承,取决于子类而不是基类。

class可以继承自struct修饰的类;同时,struct也可以继承自class修饰的类,继承属性如下列描述:

class A{};

class B:A{}; // private 继承

struct B:A{}; // public 继承

最后,那么到底是使用struct,还是使用class呢?

一般来说,两个关键字都是可以的,但是由于编程规范的问题,如果要定义的是一种数据结构,那么用struct,如果是一种对象的话,那么用class。

 

 

tdcall、stdcall、pascall是什么?C++默认是哪种?

__cdecl、__stdcall是声明的函数调用协议。主要是传参和弹栈方面的不同。 

__cdecl:

一般c++用的是__cdecl

函数参数按照从右到左的顺序入栈

由调用函数者把参数弹出栈以清理堆栈

PS:那么为什么还需要_cdecl呢?当我们遇到这样的函数如fprintf()它的参数是可变的,不定长的。 

__stdcall:

windows里大都用的是__stdcall(API)

函数参数按照从右到左的顺序入栈

被调用的函数在返回前清理传送参数的栈 

__fastcall:

约定用于对性能要求非常高的场合

约定将函数的从左边开始的两个大小不大于4个字节(DWORD)的参数分别放在ECX和EDX寄存器,其余的参数仍旧自右向左压栈传送,

被调用的函数在返回前清理传送参数的堆栈 

 

static_cast, dynamic_cast, reinterpret_cast, const_cast区别比较

static_cast <new_type> (expression) 静态转换

static_cast最接近于C风格转换了,但在无关类的类指针之间转换上,有安全性的提升

该运算符把exdivssion转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:

①用于类层次结构中基类和子类之间指针或引用的转换。

  进行上行转换(把子类的指针或引用转换成基类表示)是安全的;

  进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。

②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

③把空指针转换成目标类型的空指针。

④把任何类型的表达式转换成void类型。

注意:static_cast不能转换掉exdivssion的const、volitale、或者__unaligned属性。

 

dynamic_cast <new_type> (expression) 动态转换

动态转换确保类指针的转换是合适完整的,它有两个重要的约束条件,其一是要求new_type为指针或引用,其二是下行转换时要求基类是多态的(基类中包含至少一个虚函数)。

该运算符把exdivssion转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;

如果type-id是类指针类型,那么exdivssion也必须是一个指针,如果type-id是一个引用,那么exdivssion也必须是一个引用。

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。

在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;

在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

 

reinterpret_cast <new_type> (expression) 重解释转换

这个转换是最“不安全”的,两个没有任何关系的类指针之间转换都可以用这个转换实现

 

const_cast <new_type> (expression) 常量向非常量转换

这个转换好理解,可以将常量转成非常量。

  

 

C++内存分配有几种方式?

内存的三种分配方式:

1. 从静态存储区分配:此时的内存在程序编译的时候已经分配好,并且在程序的整个运行期间都存在。全局变量,static变量等在此存储。

2. 在栈区分配:相关代码执行时创建,执行结束时被自动释放。局部变量在此存储。栈内存分配运算内置于处理器的指令集中,效率高,但容量有限。

3. 在堆区分配:动态分配内存。用new/malloc时开辟,delete/free时释放。生存期由用户指定,灵活。但有内存泄露等问题。

常见内存错误及对策

1. 内存分配未成功,却被使用。

对策:使用内存之前检查是否分配成功。用p!=NULL判断。

2. 内存分配成功,未初始化就被使用。

内存的缺省值没有统一的标准。大部分编译器以0作为初始值,但不完全是。

对策:内存初始化时赋初值。

3. 内存操作越界。

对策:只能是小心了。

4. 释放了内存,仍然使用。

(1) 使用显示delete和free的野指针。

对策:释放完内存,将指针置为NULL。

(2) 使用隐式delete和free的野指针。主要是指函数返回指向栈内存的指针或引用。

对策:当然是不要返回就可以了。

5. 未释放内存,导致内存泄露。

用new/malloc开辟了内存,没用delete/free释放.

对策:new和delete的个数一定相同;malloc和free的个数一定相同;new[]和[]delete一定对应。

 

 

模板的实现可以放在cpp里吗?为什么?

答:模板声明和实现要放在一个文件。因为放在CPP里面实现会编译不过。

 

C++中#ifndef, #define, #endif的作用和使用的注意事项

答:其实这几句代码的主要作用的官方解释是:为了防止头文件的重复包含和编译。

具体来说就是,当你在设计一个很大很大的工程时,可能很多文件里面都会包含同一个头文件,可能你需要使用该头文件的目的完全是相同的,可是该头文件在声明时,如果没有加上上面三句代码在代码段的前后,当你将整个工程统一编译,希望链接成一个完整的可执行文件时,就会出现大量错误,因为每一个相同的头文件都会进行所谓的“重定义”;而加上上面那三句,则不会出现“重定义”的情况。

通俗来说,我们可以认为以上语句是一个判决条件,即类似于if语句,当执行#ifndef XXX_H_语句时,去判断xxx.h头文件是否已经被定义过,如果是,就把#ifndef XXX_H_一直到#endif之间的代码段跳过,如果不是,则执行#ifndef XXX_H_一直到#endif之间的代码段。

 

对于编写这些语句时,一般习惯将头文件名全部使用相应的大写字母,然后把.改成_,在头文件名的前后都加下划线,即编写xxx.h的语句时,就如下定义:

#ifndef _XXX_H_

#define _XXX_H_

……

#endif 

注意事项:

#ifndef AAA

#define AAA

...

int i;

...

#endif

里面有一个变量定义

在vc中链接时就出现了i重复定义的错误,而在c中成功编译。

 

解决方法:

(1).把源程序文件扩展名改成.c。

(2).推荐解决方案:

.h中只声明 extern int i;在.cpp中定义

<x.h>

#ifndef __X_H__

#define __X_H__

extern int i;

#endif //__X_H__

<x.c>

int i;

 

注意问题:

变量一般不要定义在.h文件中。 

 

 

extern关键字在哪里使用?

A.置于变量或者函数前,以标示变量或者函数的定义在别处,提示编译器遇到此变量和函数时在其他地方寻找其定义。

B.可用来进行链接指定。