开发踩坑小记


难用的 dash


#!/bin/dash

WBCHR_PIDS=$(pgrep wubi98-char)

string="${WBCHR_PIDS}"

echo $WBCHR_PIDS

if [ ${string}"abc" = "abc" ]
then
  echo "wubi98-char没有活动的进程"
else
  echo "检测到 wubi98-char 正在运行,即将杀掉!"
  kill $WBCHR_PIDS
fi

if [ -d /opt/wubi98-char ]
then
  echo "wubi98-char 文件夹存在,将会删了它!"
  sudo sudo rm -rf /opt/wubi98-char
fi

if [ -f /usr/share/applications/wubi98-char.desktop ]
then
  echo "wubi98-char 快捷方式存在,将会删了它!"
  sudo sudo rm -rf /usr/share/applications/wubi98-char.desktop
fi

只能以这种方式,才可以不报错。

  • if 条件句中,合并字符串,否则空值时报错。
  • dash 不支持常规逻辑运算符。
  • debian 全用 dash 了,想换 bash 需要用户额外操作,这对资源库从简从易的原则来说,非常蛋疼。

上述 ${string} 是排查过程中额外引入的量,主要是想看看进程 PID 及其操控,神特么有两个进程,最后用 pgrep 搞定。 ${string} 本无必要,仅此说明。

QT 中的指针

98五笔小精灵,已编译发布 Linux 版本,其中源码里有个函数是这样:

void MainWindow::openPic(const QString & num){
    
    QString sPicPath = ":/pic/" + num + ".jpg";
    QString toDir = QDir::currentPath();
    picTest = new QFile(toDir + "/" + num + ".jpg");

    bool ok = picTest->exists();

    if(!ok){
        QFile my_file(sPicPath);
        my_file.copy(toDir + "/" + num + ".jpg");
        picTest->setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner);
    }

    sPicPath = toDir+"/" + num + ".jpg";  
    if(QSysInfo::productType()=="windows"){
		sPicPath = sPicPath.replace("/", "\\");
    }

    QDesktopServices::openUrl(QUrl::fromLocalFile(sPicPath));  //For Linux;
}

windows 下, 转义成 \\ 风格的路径是保险的,在 Linux 下,这样的路径又是无效的。
只好加了个判断,不同系统,分流写法。

这里还有个坑,权限在 Linux 下,设成 QFileDevice::ReadOwner | QFileDevice::WriteOwner 是保险的。按 Qt 的帮助文档,出现了权限问题。

还有一个坑,就是内存泄漏:如果每次都生成一个新的 QFile 对象,就会出现内存泄漏。

研究了一下,给程序预设了一个 QFile 指针,用指针来管理对象,成功堵住内存泄漏的风险。

同样的思路,用指针来管理绑定函数:

void MainWindow::connectBtnToUI(){
	
	connect(ui->lineEditForSearch,&QLineEdit::returnPressed,this,&MainWindow::lineEditForSearch_returnPressed);
	
    void (MainWindow::*pr_clicked[5])()={
        &MainWindow::btnSearchChar_clicked,
        &MainWindow::btnOpenPIC1_clicked,
	    &MainWindow::btnOpenPIC2_clicked,
        &MainWindow::btnOpenWeb1_clicked,
	    &MainWindow::btnOpenWeb2_clicked,
    };

    QPushButton * pr[5]={
        ui->btnSearchChar,
        ui->btnOpenPIC1,
		ui->btnOpenPIC2,
        ui->btnOpenWeb1,
		ui->btnOpenWeb2,
    };

    for (int i = 0;i<5;i++) {
        connect(pr[i],&QAbstractButton::clicked,this,pr_clicked[i]);
    }
	
}

单实例判断

#if defined  Q_OS_LINUX   //for linux
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
bool checkOnly()
{
    const char filename[]  = "/tmp/lockfile_wubi98-char_202307";
    int fd = open (filename, O_WRONLY | O_CREAT , 0644);
    int flock = lockf(fd, F_TLOCK, 0 );
    if (fd == -1) {
        perror("open lockfile/n");
        return false;
    }
    if (flock == -1) {
        perror("lock file error/n");
        return false;
    }
    return true;
}
#endif

一时间,newReaderwubi98-char 不可共存,想了一下,原来是 lockfile 同名了。

回头想想,weasel-tool 的代码还有很多可以优化的地方。不过 c++ 最大的好处是,即使用最平庸的写法,也能获得优异的性能。

64位兼容

QStringList  MainWindow::getWindowsRegedit(){	
	QStringList RimeDirList_64;
	QStringList RimeDirList_32;
    bool ok1 =false;
    bool ok2 =false;
  /************* 64位 ↓ ***************/
    QString strReg_64 = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Rime\\Weasel\\";
    QSettings* setting_64 = new QSettings(strReg_64, QSettings::NativeFormat);
    QString appDir_64 = setting_64->value("WeaselRoot").toString();
	
	if(appDir_64.contains(":")){
        qDebug() << "检测到64位程序存在!" << '\n';
        ok1 = true;
		RimeDirList_64.append(appDir_64);
	}  
    	
  /************* 64位 ↑ ***************/
  
  /************* 32位 ↓ ***************/
    QString strReg_32 = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Rime\\Weasel\\";
    QSettings* setting_32 = new QSettings(strReg_32, QSettings::NativeFormat);
    QString appDir_32 = setting_32->value("WeaselRoot").toString();
	
	if(appDir_32.contains(":")){
        qDebug() << "检测到32位程序存在!" << '\n';
        ok2 = true;
		RimeDirList_32.append(appDir_32);
	}
  /************* 32位 ↑ ***************/	
  
    QString strReg = "HKEY_CURRENT_USER\\Software\\Rime\\Weasel\\";
    QSettings* setting = new QSettings(strReg, QSettings::NativeFormat);
    auto appDir = setting->value("RimeUserDir").toString();

   qDebug() << "获取到的 RimeUserDir 键值是: " << appDir << '\n';

    if(!appDir.contains(":")){

        appDir=(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
        appDir.replace("weasel-tool","Rime");
        qDebug() << "修改后 RimeUserDir 键值是: " << appDir  <<'\n';
    }
    RimeDirList_64.append(appDir);
	RimeDirList_32.append(appDir);


    if(ok1 && !ok2){
        return RimeDirList_64;
    }

    if(ok2 && !ok1){
        return RimeDirList_32;
    }

    if(ok1 && ok2){
    //检测到32位版本与64位版本同时存在
    QString strInfo = "请选择接管的版本";
    QMessageBox msgBox;
    msgBox.setText(strInfo);
    msgBox.setWindowTitle("选择版本");
    msgBox.setInformativeText("检测到系统中32位版本与64位版本同时存在,如果关掉该弹窗,默认选择32位。");
    msgBox.addButton("接管 32 位版本",QMessageBox::AcceptRole);
    msgBox.addButton("接管 64 位版本",QMessageBox::RejectRole);

    int ret = msgBox.exec();
    switch (ret) {
    case QMessageBox::AcceptRole:{
                 return RimeDirList_32;
                 }
    case QMessageBox::RejectRole:{
                 return RimeDirList_64;
                 }
    default:{
                 return RimeDirList_32;}
    }
    }
    return {};
}

weasel-tool 处理的对象数据量较少,结构较为单一,没有全面采用面向对象的方式。我其实很想把 rimetool 整个重写,但是眼下没有时间,等后面有机会,来个大整合。

本次玩 Qt 的一个感觉就是 QHash 真他娘的好用啊!

  • 一个典型的遍历
QHash<QString, QString>::const_iterator i = userData.cbegin();
while (i != userData.cend()) {
    qDebug() << i.key() << " 的值是: " << i.value() << '\n';
    ++i;
}
  • 一个典型的遍历覆写
bool QDiceThread::writeQhashToYaml(const QHash<QString,QString> userDataLatest){
   //把 QHASH 写入到 wubi98_ci.custom.yaml ,threadD在做这个
   QFile aFile(userFileName);
   if(!aFile.open(QIODevice::WriteOnly|QIODevice::Text))
        return false;
   QTextStream aStream(&aFile);
   aStream.setAutoDetectUnicode(true);
   QString str("patch:") ;
   aStream << str << '\n';
   QHash<QString, QString>::const_iterator i = userDataLatest.cbegin();
   while (i != userData.cend()) {
        qDebug() << i.key() << " 的值是: " << i.value() << '\n';
        str =  i.key()+":"+i.value();
        aStream << str << '\n';
        ++i;
   }
   aFile.close();
   return true;
}
  • 一个典型的遍历检查
for(int i = 0;i <45 ; i++){
        strKeyTemp = QString("  \"%1\"").arg(keyList[i]);
        QHash<QString, QString>::iterator it11 = userDataTH.find(strKeyTemp);
        bool ok(it11==userDataTH.end());
        if (ok){
				qDebug()<< "没有检测到参数项:" <<  strKeyTemp  << '\n';
				erroList.append(keyList[i]);
				ok_test = false;         
        }
}
  • 一个典型的填值
void MainWindow::comboBoxChangeTheVaule(const QString & strNEW,const int & i){
    QString strKeyTemp = QString("  \"%1\"").arg(userKeyList[i]);
    userDataBySelect.insert(strKeyTemp," " + strNEW);
}
  • 一个典型的读值
QString str12 = QString("  \"%1\"").arg("schema/full_icon");
QHash<QString, QString>::iterator it12 = userDataBySelect.find(str12);
if(it12!=userDataBySelect.end()){
    QString str = it12.value();
    str = str.replace("icons/","");
    str = str.replace("\"","");
    ui->label_49->setText(str);
    qDebug()<< it12.key() << "的值是 : " << it12.value() <<'\n';
}

for(int n=0;n< userStr.length();n++){
    QString str = userStr.at(n);
    str = str.trimmed();
    const QChar *ptr = str.unicode();
    uint singleCharUnicode = (*ptr).unicode();
    QString spelling;
    if(bashHash.contains(singleCharUnicode)){
        spelling = bashHash.value(singleCharUnicode);
		ui->textBrowser->append(str+'\t'+spelling);
    }else if(extraHash.contains(singleCharUnicode)){
		spelling = extraHash.value(singleCharUnicode);
		ui->textBrowser->append(str+'\t'+spelling);
	}else{
    QMessageBox::information(this, "仅支持国标字集", "不支持超集字查询!",
                             QMessageBox::Ok,QMessageBox::NoButton);
    }
}

QHash 的速度非常可观,尤其是将 key 经过数值化处理之后。

流式卡节点

void newDialog::autoReader(){
    QStringList fFileContent;
    QFile aFile(curTablePathDia);
    bool overPointMark = false;//界信号
    QString strTest;
    QString str;
    customYamlSTM.clear();
    if (aFile.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QTextStream aStream(&aFile); 
        ui->label->clear();
        while (!aStream.atEnd()){
            strTest=aStream.readLine();
            str = strTest;
			qDebug() << "读条:" << str <<'\n';
            if(( strTest =annotationClear(strTest)).startsWith("...")){//判断有没有到锚点
                overPointMark = true;
                customYamlSTM.append("...");
                continue;
            }
            if(!overPointMark){//尚未过界
                customYamlSTM.append(str);
                qDebug << "读条:" << str <<'\n';
            }else{//过界了
              str = str.trimmed();
              str = annotationClear(str);
              if(!str.isEmpty()){
                 fFileContent.append(str); //添加到 StringList
                 }
            }
        }
        aFile.close();
        ui->label->setText(curTablePathDia);
        iniModelFromStringList(fFileContent);//从StringList的内容初始化数据模型
    }
}

有了这次编写 Qt 程序的经验,本地数据处理类的需求是完全没问题了。

带参数启动

要让一个基于 QWidget 的 Qt 程序接受带参数启动,需要在程序的 main 函数中处理命令行参数。

这通常涉及解析命令行参数,并根据这些参数执行相应的操作。以下是一个基本的步骤:

  1. 修改 main 函数以接收参数

Qt 的 main 函数通常如下所示:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QWidget window;
    window.show();
    return app.exec();
}

这里的 argcargv 参数分别代表命令行参数的数量和实际参数值,使用这些参数来接收和处理命令行输入。

  1. 解析命令行参数

main 函数中,可以使用标准的 C/C++ 方法来解析 argcargv,或者使用 Qt 提供的 QCommandLineParser 类。

QCommandLineParser 解析命令行参数更加简单和直观:

使用 QCommandLineParser 的示例:

#include <QCommandLineParser>
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QCommandLineParser parser;
    parser.setApplicationDescription("My Qt App");
    parser.addHelpOption();
    parser.addVersionOption();
    parser.addPositionalArgument("source", "The source file to process.");
    // 可以添加更多选项和参数...

    parser.process(app);

    // 检查是否提供了必需的参数,并获取它们的值...
    QString sourceFilePath = parser.positionalArguments().at(0);
    qDebug() << "Source file:" << sourceFilePath;

    QWidget window;
    window.show();
    // 根据解析的参数执行其他操作...

    return app.exec();
}

在这个例子中,使用 QCommandLineParser 来解析命令行参数,并检查是否提供了一个名为 source 的位置参数。

然后,我们可以根据这些参数在程序中进行相应的操作。

  1. 处理参数并执行操作

一旦解析了命令行参数,可以根据这些参数执行任何必要的操作。
这可能包括改变程序的行为、加载特定的文件或数据、设置配置选项等。
确保程序能够适当地响应这些参数,并在必要时向用户提供反馈。

  1. 测试和调试

在实现带参数启动的功能后,确保充分测试程序以验证它是否正确响应各种命令行参数。
可以通过命令行界面手动运行程序并传递不同的参数来进行测试。
此外,使用调试工具可以帮助你跟踪和解决任何潜在的问题或错误。

上述的示例中,QString sourceFilePath = parser.positionalArguments().at(0) 这行代码做了以下几件事:

a. 调用 parser.positionalArguments()

QCommandLineParser 类的 positionalArguments() 方法返回一个包含所有位置参数的 QStringList

位置参数是那些在命令行中按照特定顺序出现的参数,它们不是以短横线(-)或双短横线(--)开头的。

在上面的例子中,我们添加了一个名为 “source” 的位置参数,这意味着用户需要在启动程序时提供这个参数。

b. 使用 at(0) 访问第一个位置参数

QStringList 类的 at(int index) 方法用于访问列表中指定索引位置的元素。
因为 positionalArguments() 返回的是一个列表,所以我们需要使用 at(0) 来获取第一个(也是唯一一个,在这个例子中)位置参数的值。索引 0 表示列表中的第一个元素。谨作为示例呈现。

c. 将获取到的值赋给 sourceFilePath

最后,获取到的位置参数的值(即用户提供的 “source” 参数的值)被赋值给 QString 类型的变量 sourceFilePath。这个变量之后可以在程序中被使用,例如,可能用于指定要打开的文件路径或其他需要基于用户输入的操作。

在实际应用中,你可能会根据需要对这个值进行进一步的处理或验证,以确保它符合你的程序的预期要求。例如,如果 source 参数应该是一个文件路径,你可能需要检查路径是否存在,或者它是否指向了一个有效的文件。


文章作者: 五笔小筑
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 五笔小筑 !
评论
  目录