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

由QTableView/QTableWidget显示进度条和按钮,理解qt代理delegate用法

背景:

我的最初应用场景,就是要在表格上用进度条显示数据,以及放一个按钮。

qt-creator中有自带的delegate示例可以参考,但终归自己动手还是需要理解细节,否则不能随心所欲。

自认没那个天赋,于是记录下来以便日后参考。

qt自带示例:

打开qt-creator首页,点击左侧示例,搜索那里输入delegate就列出来几个,常见的也就是spinbox输入数值。

上图中的第一个例子是个意外,我觉得挺好玩的,关于QItemEditorFactory和QItemEditorCreatorBase的用法,可以设置item的编辑方式,和代理有异曲同工之妙。也许本质上有某种联系,但我没有深究,本次只想记录代理的用法。

用法宗旨:

按照资料以及网上的说法,无非就是继承QStyledItemDelegate,重写几个函数,然后设置为QTablev/QTableWidget的代理即可。但关键是,QStyledItemDelegate咋用。

我认为还是始终贯彻MVC模式的应用,比如一个view绑定了一个model,则model的数据是和view同步的。前后端分离的本质是:view是躯壳,model是灵魂。肉体感受可以影响灵魂,灵魂变化也会反应到肉体。打住!再分析就可以感悟人性了。

主要是继承QStyledItemDelegate之后,重写那几个函数的意义。我认为常用的几个:paint,createEditor,editorEvent,setEditorData,setModelData,updateEditorGeometry。看名字就知道,凡是带editor的都是需要编辑view时才用。

paint函数:

绘制代理区域,如果希望表格一显示,马上就要看到代理控件的时候用,比如我要显示进度条或者按钮,我希望界面一出来它就有。而不是编辑这个单元格的时候才出来。

至于paint调用的时机,如果要代码控制,就控制model好了,view同步显示数据时,会重绘界面。

延伸题外话:

我试过其它方式修改数据,然后总想别的方法来调用主动调用paint函数,没戏的。比如this->repaint(),不行的。除非把界面最小化再恢复,或者点一下单元格。反正这个paint别想着自己控制它,就用model的item间接更新就行了。所以,都说tableview比widget优化显示速度之类的说法,实际上是mvc内部优化的意思。

很早以前,大约十多年了,我用vs做了一个超大的电子表格,因为单元格太多,业务上又必须这样,所以ui更新效率很低。最后也是让grid绑定了datatable,然后通过更新datatable来间接更新grid才解决。其实所谓的mvc别说多先进,早期vb6.0时代就有这样的雏形了。隐约记得,那时候叫控件绑定数据源,道理类似。

另外,现在的扁平化风格,有时候看起来特别别扭,直接绘制出来的控件就是死的,例如按钮,看不到按下去的感觉。也许通过style能实现,但我没尝试。

通常paint函数重写的内容主要是定义QStyleOption和drawControl。比如:

void Debug_Delegate_Button::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{Q_UNUSED(index);QStyleOptionButton btnStyle;btnStyle.text = "exec";btnStyle.rect = option.rect;btnStyle.state = QStyle::State_Active;QPushButton btn;btn.setEnabled(m_bEnabled);btn.style()->drawControl(QStyle::CE_PushButton, &btnStyle, painter, &btn);
}

我亲测的感受是,QStyleOption是用于定义显示效果的,drawControl只是画出来。

上面的代码定义了一个按钮,下面我写了个setEnable,但我感觉意义不大,主要看上面的Style。

createEditor函数:

qt手册说是返回用于编辑item的控件指针。也就是编辑单元格时显示的控件实例,需要在这个函数里new出来并return。亦即,你得告诉代理,要用什么控件来编辑。

我的理解是,其它凡是带有editor入参的函数,都依赖这个函数,否则editor就是空指针,会报错的。这一点手册里我没看到哪里有提到,但亲测就是如此。

editorEvent函数:

相当于一个eventFilter。被代理的那个区域,如果发生事件,从这里写响应。

比如,我做了一个发送命令的界面:

首先那个exec按钮是使用paint函数画上去的,但就是死的一匹。然后使用editorEvent函数捕获了鼠标点击事件。cpp代码如下:


void Debug_Delegate_Button::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{Q_UNUSED(index);QStyleOptionButton btnStyle;btnStyle.text = "exec";btnStyle.rect = option.rect;btnStyle.state = QStyle::State_Active;QPushButton btn;btn.setEnabled(m_bEnabled);btn.style()->drawControl(QStyle::CE_PushButton, &btnStyle, painter, &btn);
}
bool Debug_Delegate_Button::editorEvent(QEvent *event, QAbstractItemModel *model,const QStyleOptionViewItem &option, const QModelIndex &index)
{Q_UNUSED(model);QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);if(option.rect.contains(mouseEvent->pos())){//Two signals will be send. Select one of them.//event->type() == QEvent::MouseButtonPress//event->type() == QEvent::MouseButtonReleaseif (event->type() == QEvent::MouseButtonPress && m_bEnabled){emit sigClicked(index.row());}}return true;
}

当捕获鼠标点击事件时,发送一个信号给外界,外界再处理这个信号,就知道是点击了哪一行的按钮。

setEditorData函数:

还是要提MVC模式的应用,比如一个view绑定了一个model,则model的数据是和view同步的。当编辑某个view对应的item时,如果使用了代理控件编辑它,控件就叫editor,用户操作editor,editor会把数据写入item。反之,用户更新item,editor也会同步更新。

所以,这个函数就是通过item更新editor。因为有editor入参,需要配合重写createEditor函数并返回editor。

setModelData函数:

通过操作editor更新item。因为有editor入参,需要配合重写createEditor函数并返回editor。

updateEditorGeometry函数:

手册提到:

Updates the geometry of the editor for the item with the given index, according to the rectangle specified in the option. If the item has an internal layout, the editor will be laid out accordingly. Note that the index contains information about the model being used.

我认为就是字面意思,用于更新区域显示布局效果,通常写一句editor->setGeometry(option.rect);即可。

官方实例spinbox:

如果要通过编辑view来更新model,也就是示例中spinbox用法,则看一段官方实例:

delegate.h

#ifndef DELEGATE_H
#define DELEGATE_H#include <QStyledItemDelegate>//! [0]
class SpinBoxDelegate : public QStyledItemDelegate
{Q_OBJECTpublic:SpinBoxDelegate(QObject *parent = nullptr);QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,const QModelIndex &index) const override;void setEditorData(QWidget *editor, const QModelIndex &index) const override;void setModelData(QWidget *editor, QAbstractItemModel *model,const QModelIndex &index) const override;void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,const QModelIndex &index) const override;
};
//! [0]

delegate.cpp

#include "delegate.h"#include <QSpinBox>//! [0]
SpinBoxDelegate::SpinBoxDelegate(QObject *parent): QStyledItemDelegate(parent)
{
}
//! [0]//! [1]
QWidget *SpinBoxDelegate::createEditor(QWidget *parent,const QStyleOptionViewItem &/* option */,const QModelIndex &/* index */) const
{QSpinBox *editor = new QSpinBox(parent);editor->setFrame(false);editor->setMinimum(0);editor->setMaximum(100);return editor;
}
//! [1]//! [2]
void SpinBoxDelegate::setEditorData(QWidget *editor,const QModelIndex &index) const
{int value = index.model()->data(index, Qt::EditRole).toInt();QSpinBox *spinBox = static_cast<QSpinBox*>(editor);spinBox->setValue(value);
}
//! [2]//! [3]
void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,const QModelIndex &index) const
{QSpinBox *spinBox = static_cast<QSpinBox*>(editor);spinBox->interpretText();int value = spinBox->value();model->setData(index, value, Qt::EditRole);
}
//! [3]//! [4]
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,const QStyleOptionViewItem &option,const QModelIndex &/* index */) const
{editor->setGeometry(option.rect);
}
//! [4]

createEditor函数定义了用于编辑单元格的spinbox控件,并返回editor控件指针,因为其它函数用到。

setEditorData函数把model中的item数据更新到spinbox。

setModelData函数把spinbox的操作结果更新到model的item。

updateEditorGeometry函数负责更新显示区域。

实践:

上面提到过放置button并可以响应点击的用法,只用到了paint和editorEvent函数。下面再记录进度条的用法。

因为进度条用于显示而不是编辑,需要界面一打开就能看到进度条,所以需要paint函数。而进度条又不像按钮那样需要用户操作,然后就没有然后了。

delegate_progressbar.h

#ifndef DELEGATE_PROGRESSBAR_H
#define DELEGATE_PROGRESSBAR_H#include <QStyledItemDelegate>
#include <QProgressBar>class Delegate_ProgressBar : public QStyledItemDelegate
{Q_OBJECT
public:Delegate_ProgressBar(QObject *parent = nullptr);enum EItemValue { eIV_Maxinum, eIV_Value };void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};#endif // DELEGATE_PROGRESSBAR_H

delegate_progressbar.cpp

#include "delegate_progressbar.h"Delegate_ProgressBar::Delegate_ProgressBar(QObject *parent): QStyledItemDelegate(parent)
{}
void Delegate_ProgressBar::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{//这里控制显示效果QStyleOptionProgressBar pbarStyle;pbarStyle.rect = option.rect;pbarStyle.state = QStyle::State_Active;pbarStyle.maximum = index.data(Qt::UserRole + eIV_Maxinum).toInt();//设置进度条最大值pbarStyle.progress = index.data(Qt::UserRole + eIV_Value).toInt();//设置当前进度//这里把它绘制出来QProgressBar pbar;pbar.style()->drawControl(QStyle::CE_ProgressBar, &pbarStyle, painter, &pbar);
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QStandardItemModel>
#include <QTimer>
#include "delegate_progressbar.h"namespace Ui {
class MainWindow;
}class MainWindow : public QMainWindow
{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void on_pushButton_clicked();void onTimerOut();private:Ui::MainWindow *ui;QStandardItemModel *m_model = nullptr;Delegate_ProgressBar *m_delegate = nullptr;QTimer *m_timer = nullptr;int m_i = 0;
};#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);m_model = new QStandardItemModel(2, 3, this);ui->tableView->setModel(m_model);m_delegate = new Delegate_ProgressBar;ui->tableView->setItemDelegateForColumn(2, m_delegate);QStandardItem *item = new QStandardItem;item->setData(100, Qt::UserRole + Delegate_ProgressBar::eIV_Maxinum);//这里给进度条设置最大值,相当于setMaximum。m_model->setItem(0, 2, item);//这是model的另一种用法,与直接操作item一样。
//    QModelIndex index = m_model->index(0, 2, QModelIndex());
//    m_model->setData(index, QVariant(50));m_timer = new QTimer(this);m_timer->setInterval(100);connect(m_timer, &QTimer::timeout, this, &MainWindow::onTimerOut);
}MainWindow::~MainWindow()
{delete ui;
}
void MainWindow::onTimerOut()
{m_model->item(0, 2)->setData(m_i++, Qt::UserRole + Delegate_ProgressBar::eIV_Value);//这里给进度条设置当前值,相当于setValue。if (m_i == 100){m_timer->stop();}
}
void MainWindow::on_pushButton_clicked()
{m_timer->start();
}

再提MVC模式。这里用到的tableview,当绑定model之后,即使model没有任何item,也可以设置行数和列数,运行效果依然能看到表格有行有列,但只能看不能操作。

给model添加item之后,每个item对应一个单元格,才能实现数据同步显示。

我这里需要单元格显示进度条,而进度条本质是max和value两个int数据,所以只要用代码控制响应item,让它存储的数据发生变化,则进度条就会响应变化了。

妥善起见,显示进度条的单元格设置为readonly。

item是个好东西,就像结构体一样,可以按角色字段划分,存储很多数据。而这些数据是QVariant类型,也就是任意类型,我还用它存过对象指针,基本上可以无限扩展,非常好用。

所以,如果要显示进度条的那个单元格能保存max和value两个int数据,非常简单,setData时,指定Qt:UserRole即可。比如:

item->setData(100, Qt:UserRole);

item->setData(50, Qt:UserRole + 1);

Qt:UserRole是枚举,后面加几都可以,那就根据需要定义成枚举,见名知意,比如:

item->setData(100, Qt:UserRole + eEnum_Max);

item->setData(50, Qt:UserRole + eEnum + Value);

就可以随便怎么玩了。

本文完。

相关文章:

由QTableView/QTableWidget显示进度条和按钮,理解qt代理delegate用法

背景&#xff1a; 我的最初应用场景&#xff0c;就是要在表格上用进度条显示数据&#xff0c;以及放一个按钮。 qt-creator中有自带的delegate示例可以参考&#xff0c;但终归自己动手还是需要理解细节&#xff0c;否则不能随心所欲。 自认没那个天赋&#xff0c;于是记录下…...

pthread_cond_timedwait 修改系统时间竟会导致其提前结束

pthread 条件变量使用注意 使用 pthread_cond_timedwait 等待条件变量时&#xff0c;其默认使用的为系统时间&#xff0c;若在其等待期间修改系统时间&#xff0c;则会导致其提前结束。 测试步骤 运行以下代码。 使用 date 命令查看系统时间&#xff0c;假设输出为 Thu Jan …...

Linux命令超详细

Linux基础命令 Linux的目录结构 /&#xff0c;根目录是最顶级的目录了Linux只有一个顶级目录&#xff1a;/路径描述的层次关系同样适用/来表示/home/itheima/a.txt&#xff0c;表示根目录下的home文件夹内有itheima文件夹&#xff0c;内有a.txt ls命令 功能&#xff1a;列出…...

物理机、虚拟机、容器

特征物理机虚拟机容器抽象级别物理硬件虚拟化的硬件和操作系统应用和依赖项&#xff08;在相同操作系统内核上运行&#xff09;隔离性高&#xff08;每个物理机独立运行操作系统&#xff09;高&#xff08;每个虚拟机独立运行操作系统&#xff09;适度&#xff08;共享操作系统…...

CSS画三角形(三种方法)

使用CSS画一个三角形&#xff0c;想必部分同学都有一个小疑问&#xff0c;css怎么做三角形&#xff0c;让我为大家介绍一下吧&#xff01; 第一种方法 div {width: 0;height: 0;border-style: solid;border-width: 50px;border-color: transparent transparent black transpa…...

(一)、ts 基础类型 及class类举例字符雨和实现vue的挂在#app

文章目录 前言环境执行依赖node.js一、基础数据类型二、任意类型三、接口和对象类型四、 数组类型五、函数重载六、类型断言断言联合类型交叉类型 七、内置对象-Promise基础对象DOM和BOMPromise ts化代码雨案例 八、Class类(派生类和抽象类)派生类 abstract抽象类 classClass简…...

C++对象的内存分布和虚函数表

Linux C/C 开发(后端/音视频/游戏/嵌入式/高性能网络/存储/基础架构/安全) c中一个类中无非有四种成员&#xff1a;静态数据成员和非静态数据成员&#xff0c;静态函数和非静态函数。 1.非静态数据成员被放在每一个对象体内作为对象专有的数据成员。 2.静态数据成员被提取出来…...

小白怎么学习性能测试?一文7个知识点带你成功入门!

1.什么是性能测试 性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。负载测试和压力测试都属于性能测试&#xff0c;两者可以结合进行。通过负载测试&#xff0c;确定在各种工作负载下系统的性能&#xff0c;目标是测试当负…...

Orcad属性过滤器的使用技巧

Orcad内置的属性过滤器可以完美的解决由于属性太多导致的不好整理的问题。下面简单介绍一下方法和过程。 1、打开过滤器 2、新建属于自己的过滤器 3、进行器件属性过滤及调整的顺序&#xff08;注这时一定关闭ORCAD&#xff0c;来操作&#xff09; 3.1 安装目录下找到\Cadenc…...

腾讯云向量数据库正式对外全量开放公测

11月1日&#xff0c;腾讯云对外宣布向量数据库正式全量开放公测&#xff0c;同时性能层面带来巨大提升。腾讯云数据库副总经理罗云表示&#xff0c;除了公测之外&#xff0c;腾讯云向量数据库单索引已经支持百亿级向量规模&#xff0c;支持百万级QPS毫秒级查询延迟&#xff0c;…...

Linux新建普通用户无法使用退格键与tab键

创建普通用户 useradd mulan passwd mulan 切换用户 su mulan 发现普通用户无法使用退格键与tab键&#xff0c;一直显示如图 如图&#xff0c;按退格键(Backspace)、删除键出现 ‘^H’ 符号&#xff0c;tab键也不能自动拼写 这是新用户下的普通bash配置都没有&#xff0c;从…...

【湘粤鄂车牌】

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

华大-HC32L130F8UA 内存使用注意事项

1,概念 本系统包含一块 64K 字节&#xff08; Byte &#xff09;容量的 FLASH 存储器&#xff0c;共划分为 128 个页&#xff08; Sector &#xff09;&#xff0c;每个页&#xff08; Sector &#xff09; 的容量为 512 字节&#xff08; Byte &#xff09;。 FLASH …...

怎样才知道一个单片机的性能到极限了?

怎样才知道一个单片机的性能到极限了&#xff1f; 就题主的问题&#xff0c;应该是想问CPU利用率的问题。可以看看Rt-thread中关于统计CPU利用率函数&#xff0c;其主要实现方式是在idle线程先关闭中断计数后&#xff0c;正常计数(可被其他线程打断)&#xff0c;最近很多小伙伴…...

Android Studio的笔记--SerialPort串口通讯学习和使用

SerialPort串口通讯学习和使用 SerialPortandroid-serialport-api源码下载 Android-SerialPort-API源码下载readme版本 Android-SerialPort-Tool源码下载 Android-Serialport源码下载使用方法readme android中使用串口通信使用android-serialport-api方式第1种 链接第2种 导入S…...

MySQL 启动选项和字符集

1. 客户端和服务器 1.1 服务器程序 数据库实例&#xff1a;代表 MySQL 服务器程序的进程&#xff08; mysqld 可执行文件&#xff09; mysqld_safe&#xff1a;启动脚本&#xff0c;会间接调用 mysqld 并监控服务器运行状态。出现错误时可以帮助重启服务器程序&#xff0c;输…...

社区投稿|解码Big Vector,开启Sui超扩展性的新篇章

* 本文是来自Sui生态项目Typus团队的投稿&#xff0c;文中「我们」均指代该项目团队&#xff0c;转载时修改部分不准确的用词。 本研究报告介绍了Big Vector的概念&#xff0c;这是一种我们用于 Typus V2 的新数据结构&#xff0c;以缓解 Sui 上数组和动态字段(dynamic field)…...

Linux根目录下的目录结构及其作用详解

Linux根目录是文件系统的最顶层&#xff0c;它包含了一些子目录&#xff0c;每个子目录都有特定的功能和存储的文件。只有了解了各个文件的使用功能&#xff0c;才能更好的去使用Linux系统。希望通过下面这张图能够让你更加了解根目录下的各个目录的功能。...

源码和SaaS账号:租房与自建房的区别

在当今数字化时代&#xff0c;软件已成为企业运营的重要支撑。然而&#xff0c;对于许多中小企业来说&#xff0c;获取和运营软件的方式有两种&#xff1a;源码和SaaS账号。这两者有何区别呢&#xff1f;让我们用租房和自建房的比喻来解释。 价格比较 源码&#xff1a;购买源码…...

Docker容器设置为自动重启

有时Docker服务出现异常&#xff0c;或者服务器出现异常&#xff0c;需要重启Docker服务或者服务器&#xff1b; 如果希望有一部分基础的或者常用的容器&#xff0c;在服务或者服务器重启的时候&#xff0c;可以实现自动启动&#xff0c;仅需使用命令进行简单配置即可实现。 D…...

Android Wi-Fi 连接失败日志分析

1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分&#xff1a; 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析&#xff1a; CTR…...

React第五十七节 Router中RouterProvider使用详解及注意事项

前言 在 React Router v6.4 中&#xff0c;RouterProvider 是一个核心组件&#xff0c;用于提供基于数据路由&#xff08;data routers&#xff09;的新型路由方案。 它替代了传统的 <BrowserRouter>&#xff0c;支持更强大的数据加载和操作功能&#xff08;如 loader 和…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

3-11单元格区域边界定位(End属性)学习笔记

返回一个Range 对象&#xff0c;只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意&#xff1a;它移动的位置必须是相连的有内容的单元格…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)

漏洞概览 漏洞名称&#xff1a;Apache Flink REST API 任意文件读取漏洞CVE编号&#xff1a;CVE-2020-17519CVSS评分&#xff1a;7.5影响版本&#xff1a;Apache Flink 1.11.0、1.11.1、1.11.2修复版本&#xff1a;≥ 1.11.3 或 ≥ 1.12.0漏洞类型&#xff1a;路径遍历&#x…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

【Linux】自动化构建-Make/Makefile

前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具&#xff1a;make/makfile 1.背景 在一个工程中源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;mak…...