代码
#include "widget.h"
#include "ui_widget.h"
#include <QWidget>
#include <QPaintEvent> //绘图事件
#include <QDebug> //测试
#include <QPainter> //画家
#include <QPen> //笔
#include <QBrush> //画刷
#include <QPoint> //点
#include <QTime> //能系统时间
#include <QTimer> //定时器
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->setupUi(this);
//初始化定义
QTimer *timer = new QTimer(this);
connect(timer,SIGNAL(timeout()),this,SLOT(update()));
timer->start(1000); //每一秒更新
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *event){
QPainter painter(this); //画家
QPen pen;
pen.setColor(QColor(Qt::black)); //画刷
pen.setWidth(5); //笔大小
//画家拿笔
painter.setPen(pen);
//把中心点移到0 0 因为旋转是要围绕坐标(0,0)旋转
painter.translate(width()/2, height()/2);
// 设置画刷,用于填充颜色
QColor skyBlueColor(173, 216, 230); // 天蓝色的 RGB
QBrush brush(skyBlueColor); // 使用蓝色填充
painter.setBrush(brush);
//画圆
painter.drawEllipse(QPoint(0,0),160,160); //半径160圆
painter.drawPoint(0,0); //中心点
//画分钟刻度
painter.drawLine(0,-160,0,-140);
int line_h;
for(int i=1; i<=60; i++){
line_h = -150; //短刻度线
painter.save();
painter.rotate(i*6);
if(i % 5 == 0){
line_h = -140; //长刻度线
//qDebug()<<i;
painter.drawText(-5,-125,tr("%1").arg(i/5)); //画 数字
}
painter.drawLine(0,-160,0,line_h);
painter.restore();
}
//获取当前时间
QTime time = QTime::currentTime();
//***********************************秒针、分针、市镇
//画秒针
painter.save(); //先保存之前的操作
painter.rotate(6.0*time.second());
pen.setWidth(2); //设置笔粗细
painter.setPen(pen); //画家再次拿笔
painter.drawLine(0,0,0,-100); //(0,0)开始 到坐标(0,100)
painter.restore(); //恢复
//画 分针
painter.save();
painter.rotate(6.0*time.minute()+6.0*time.second()/60);
pen.setWidth(4);
painter.setPen(pen);
painter.drawLine(0,0,0,-80);
painter.restore();
//画 时针
painter.save();
painter.rotate(30*time.hour()+6.0*time.minute()/60);
pen.setWidth(6);
painter.setPen(pen);
painter.drawLine(0,0,0,-40);
painter.restore();
}
面试题:
1.指针和引用的区别:
1> 指针定义时需要使用*号,引用定义时需要使用&
2> 指针取值需要使用*号运算符完成,引用使用时直接跟目标使用方式一致
3> 指针定义时,需要给指针分配内存空间8字节,引用定义时不需要分配内存空间,引用使用的是目标的空间
4> 指针初始化后,可以改变指针的指向,但是引用初始化后,不能在改变目标了
5> 指针有二级指针,但是引用没有二级引用
6> 有空指针,但是没有空引用
7> 指针进行偏移运算时是对内存地址的偏移,而引用进行偏移时,就是对目标值的偏移
8> 指针不能指向右值,但是右值引用的目标可以是右值
9> 指针定义时可以不初始化(野指针),引用定义时必须初始化
10> 指针可以有指针数组,但是引用不能定义引用数组
2.c和c++的区别
1. 编程范式
- C语言:是一种过程式编程语言,强调面向过程的编程方式,主要关注函数的设计和调用。它遵循结构化编程的原则,使用顺序、分支和循环等基本结构来组织代码。
- C++语言:是一种支持多种编程范式的语言,既可以进行面向过程的程序设计,也可以进行面向对象的程序设计。它引入了类、对象、继承、封装、多态等面向对象编程的特性,使得程序的结构更加清晰、易于维护。
2. 语法特性
- C语言:语法相对简洁,但功能较为基础。它包含32个关键词和9种控制语句,运算符丰富,表达式能力强。
- C++语言:在C语言的基础上进行了扩展和增强,引入了新的语法特性,如类、对象、命名空间、模板、异常处理等。这些特性使得C++在编程时更加灵活和强大。
3. 类型检查
- C语言:对类型的检查较为宽松,允许进行一些隐式的类型转换,这可能导致一些不易察觉的错误。
- C++语言:具有更严格的类型检查机制,支持强类型的编程,对数据类型的转换更加严格,有助于减少因类型不匹配而导致的错误。
4. 面向对象编程支持
- C语言:不支持面向对象编程,仅支持过程式编程。
- C++语言:是一种支持面向对象编程的语言,提供了类、对象、继承、多态等面向对象编程的特性,使得程序的结构更加清晰、易于理解和维护。
5. 标准库
- C语言:标准库包含了基本的函数库,这些函数库比较松散,只是把功能相同的函数放在一个头文件中。
- C++语言:标准库不仅包含了C语言标准库的功能,还增加了许多面向对象编程的特性,如STL(Standard Template Library)等。这使得C++在编程时能够更加方便地利用这些高级特性。
3.qt中对哪些控件比较熟悉
QPushButton:最常见的按钮控件,用于执行用户点击操作。
QCheckBox:复选框控件,用户可以在多个选项中选择多个。
QLabel:标签控件,用于显示文本或图片。
QLineEdit:单行文本输入框控件,用户可以在其中输入文本。
QTextEdit:多行文本输入框控件,支持富文本编辑。
4、描述一下qt中信号与槽机制
Qt中的信号与槽(Signals and Slots)机制是Qt框架的核心特性之一,它提供了一种对象间通信的方式,使得Qt的部件(widgets)之间可以安全、灵活地相互通信。
1. 信号(Signals)
定义:信号是对象的成员函数,但它不能被直接调用,而是当特定事件发生时,由对象自动发出。信号可以是任何返回void且没有非默认参数的成员函数。
特点:信号在Qt中用于通知其他对象某个事件已经发生。信号可以有参数,这些参数将被传递给连接到该信号的槽函数。
语法:在Qt中,信号通过
signals
关键字在类的定义中声明(但在Qt 5及以后版本中,signals
关键字是可选的,但为了清晰起见,通常还是保留它)。2. 槽(Slots)
定义:槽是普通的成员函数,可以被正常调用,但更重要的是,它们可以被信号连接。当信号被发出时,所有连接到该信号的槽函数将被自动调用。
特点:槽函数和普通成员函数一样,可以有访问修饰符(public、protected、private),可以有参数,并返回任何类型的值(但通常返回void),也可以被其他函数直接调用。
语法:槽函数在类的定义中不需要任何特殊的标记,但Qt建议使用
slots
关键字(在Qt 5及以后版本中也是可选的)来明确标识它们是槽函数,以增加代码的可读性。3. 连接(Connections)
定义:连接是信号和槽之间的链接,它指定了当信号被发出时,哪个槽函数应该被调用。
特点:Qt的信号和槽机制是松耦合的,这意味着信号的发出者和接收者不需要知道对方的具体实现细节。这种机制提高了代码的灵活性和可重用性。
语法:使用
QObject::connect()
函数来建立信号和槽之间的连接。连接可以是直接的(即信号和槽在同一个线程中),也可以是跨线程的(通过信号和槽机制实现线程间的通信)。
5、描述一下qt中的对象树模型
1>在对象树模型中,子组件构造时,需要制定父组件而存在
2>自组建的生命周期由父组件进行管理
3>父组件展示时,会将子组件一并展示,当父组件释放空间时,会将加载在该组件上的所有子组件的空间全部释放
4>对象树模型保证了,各个组件的内存不会泄露
5>在战区申请的组件,程序结束后,由系统自动回收,在堆区申请的组件,结束后,由其父组件进行回收资源
6>父组件和子组件要用共同的祖先类,实现的理论基础多为多态
7>每一个组件中都会有父组件指针和子组件链表
6、什么情况下需要使用多线程
1. 并行计算
当程序需要进行大量的计算任务,并且这些任务可以并行处理时,使用多线程可以显著提高计算效率。例如,在分布式计算或大数据处理中,将任务分割成多个部分,每个部分由一个线程处理,可以显著缩短整体完成时间。
2. I/O密集型任务
在进行大量的输入/输出(I/O)操作时(如文件读写、网络通信等),CPU大部分时间处于等待I/O操作完成的状态。此时,使用多线程可以让CPU在等待I/O操作的同时执行其他任务,从而提高程序的整体效率。
3.异步处理
异步编程是现代软件开发中常见的需求,多线程是实现异步处理的一种有效方式。例如,在Web开发中处理并发请求、消息处理等场景,使用多线程可以确保每个请求或消息都能得到及时处理,同时不会相互干扰。
4. 提高系统吞吐量
对于需要处理大量并发请求的服务端程序(如Web服务器、游戏服务器等),使用多线程可以显著提高系统的吞吐量。每个线程可以处理一个或多个请求,从而增加同时处理的请求数量。
5. 复杂的多步骤任务处理
当一个业务逻辑包含多个复杂的步骤,并且这些步骤之间可以并行处理时,可以使用多线程来优化处理过程。例如,可以使用多个线程分别处理不同的步骤,最后再将结果合并。
7.多线程需要注意什么?
在多线程编程中,需要注意以下几个关键点来确保程序的正确性、效率和稳定性:
线程安全:确保程序中的数据结构和操作在多线程环境中是安全的,即不存在数据竞争或其他同步问题。
同步机制:使用互斥锁(mutexes)、读写锁(read-write locks)、信号量(semaphores)、条件变量(condition variables)等同步机制来协调线程间的操作。
死锁:避免死锁,即两个或多个线程永久阻塞彼此,等待对方释放资源的情况。确保获取锁的顺序一致,使用超时机制,或者设计无锁数据结构。
资源争用:尽量减少线程间对共享资源的争用,以降低性能开销和复杂性。
上下文切换:减少不必要的线程创建和销毁,因为上下文切换有开销。
内存管理:注意线程间的内存共享和隔离,避免内存泄漏和越界访问。
线程局部存储:使用线程局部存储(Thread-Local Storage, TLS)来为每个线程提供独立的数据副本。
竞态条件:识别并处理竞态条件,确保程序的输出不依赖于线程执行的顺序。
线程池:使用线程池来管理线程的创建和销毁,提高资源利用率和性能。
错误处理:确保线程中的错误能够被适当地捕获和处理,避免一个线程的失败影响整个程序。
数据一致性:确保在多线程访问共享数据时,数据的一致性和完整性。
原子操作:使用原子操作来保证对共享数据的简单操作是不可分割的。
避免共享:尽可能设计无共享的并发算法,减少线程间的交互。
8.epoll、select和poll的区别?
性能与效poll的区别?
1. 性能与效率
- select:
- 时间复杂度为O(n),即随着监视的文件描述符数量(fd)的增加,性能会线性下降。
- 每次调用时,都会对所有fd进行线性遍历,找出能读出数据或写入数据的fd。
- 单个进程可监视的fd数量有限,一般为1024(32位系统)或2048(64位系统)。
- poll:
- 本质上与select类似,也是对所有fd进行遍历。
- 没有最大连接数的限制,因为它基于链表来存储fd。
- 但同样存在大量fd的数组被整体复制于用户态和内核地址空间之间的问题,这会导致不必要的复制开销。
- epoll:
- 采用事件驱动的方式,只有活跃的连接才会触发回调,因此效率远高于select和poll。
- 没有最大并发连接数的实际限制(虽然理论上有上限,但远大于select和poll的限制)。
- 使用mmap()减少用户空间和内核空间之间的复制开销。
2. 工作原理
select:
通过设置或检查存放fd标志位的数据结构来进行处理。
当某个fd就绪时,select会通知应用程序。
poll:将用户传入的fd数组拷贝到内核空间,然后查询每个fd对应的设备状态。
如果设备就绪,则在设备等待队列中加入一项并继续遍历;如果遍历完所有fd后没有发现就绪设备,则挂起当前进程。
epoll:使用epoll_create创建epoll实例。
通过epoll_ctl注册和管理fd,指定要监视的事件类型。
使用epoll_wait等待事件发生,该函数会阻塞直到有事件发生或超时。
一旦有事件发生,epoll_wait会返回触发事件的fd和事件类型。
3. 触发模式
- select和poll:
- 它们的触发模式是“水平触发”(Level Triggered),即如果报告了某个fd后,没有被处理,那么下次调用时还会再次报告该fd。
- epoll:
- 支持两种触发模式:EPOLLLT(默认,水平触发)和EPOLLET(边缘触发)。
- 在边缘触发模式下,只有fd的状态发生变化时(例如从不可读到可读),才会触发一次通知,直到下次状态再次变化。
9.智能指针区别?
智能指针是C++中用来自动管理内存分配和释放的模板类,它们帮助程序员避免手动管理内存时可能引入的内存泄漏和其他错误。
常见的智能指针及其区别:
std::unique_ptr(独占智能指针):
- 用于表示对一个对象的独占所有权。
- 不允许复制(Copy),但可以移动(Move),这意味着你可以将所有权从一个
unique_ptr
转移到另一个,但原来的unique_ptr
将不再拥有该对象。- 当
unique_ptr
被销毁时,它所拥有的对象也会被自动删除。std::shared_ptr(共享智能指针):
- 表示对一个对象的共享所有权。
- 使用引用计数机制来管理对象的生命周期。每个
shared_ptr
都有一个与之关联的计数器,每当有新的shared_ptr
指向同一个对象时,计数器增加;当一个shared_ptr
被销毁时,计数器减少。当计数器降到0时,对象被删除。- 可以被复制,每次复制都会增加引用计数。
std::weak_ptr(弱智能指针):
- 用于观察
shared_ptr
而不影响对象的生命周期。- 不拥有对象,因此不会增加引用计数。
- 可以视为一种不控制对象生命周期的
shared_ptr
。它可以用来解决shared_ptr
可能导致的循环引用问题。std::auto_ptr(已在C++17中弃用):
- 曾经用于表示对一个对象的所有权,并且可以被复制,但复制后原始的
auto_ptr
将变为空。- 由于存在许多问题和潜在的误用,它在C++17中被标记为弃用,并在后续版本中移除
10共享内存和消息队列区别?
共享内存和消息队列是两种不同的进程间通信(IPC)机制,它们在多任务操作系统中用于在不同进程之间传递数据或信号。以下是它们的主要区别:
数据传输方式:
共享内存:是一种内存映射技术,允许多个进程共享同一块内存区域。进程可以直接在这块内存上读写数据,从而实现数据的共享。这种方式的数据传输速度非常快,因为它避免了数据在进程间的复制。
消息队列:是一种基于队列的数据结构,进程通过发送和接收消息来进行通信。每个消息通常包含一个标识符和消息体。消息队列提供了一种有序、异步的数据传输方式。
同步性:
共享内存:通常需要配合某种同步机制(如互斥锁、信号量等)来避免竞态条件和数据不一致的问题。
消息队列:本身就是一种同步机制,因为它确保了消息的有序性和原子性。发送者和接收者不需要额外的同步机制来协调消息的发送和接收。
数据完整性:
共享内存:由于多个进程可以直接读写同一块内存,如果同步不当,可能会导致数据不一致或损坏。
消息队列:每个消息都是独立的,发送者和接收者通过消息队列进行通信,不会直接操作对方的内存,因此数据完整性得到了保证。
使用场景:
共享内存:适用于需要快速、直接访问共享数据的场景,如实时数据共享、图形渲染等。
消息队列:适用于需要有序、异步通信的场景,如任务调度、事件通知等。
复杂性:
共享内存:使用起来相对复杂,需要处理好同步和并发的问题。
消息队列:使用起来相对简单,操作系统或消息队列服务通常提供了完整的API来管理消息的发送和接收。
资源管理:
共享内存:通常需要手动管理共享内存的分配和释放。
消息队列:由操作系统或消息队列服务管理,用户不需要关心底层的内存分配。
跨平台性:
共享内存:可能需要针对不同的操作系统实现不同的共享内存机制。
消息队列:通常有跨平台的实现,如 POSIX 消息队列。