QT傳送HTTP請求的最小Demo

最近在籌劃一個開源專案,為了儘可能壓榨伺服器效能,所以部分程式碼使用C++編寫。程式涉及到對外的HTTP請求,正好發現QT自帶這方面的庫,那就直接引用吧!

下面給出一個最小的Demo,不做額外介紹,每個類的具體用法請查閱官方文件。

注意

跑Demo前需要在.pro檔案中加上如下程式碼,以連結網路庫。

QT += network

Demo:

#include <QCoreApplication>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 構建及傳送請求
    QNetworkAccessManager *manager = new QNetworkAccessManager();
    QString url = "https://www.ibadboy.net";
    QNetworkRequest request;
    request.setUrl(QUrl(url));
    QNetworkReply *pReply = manager->get(request);

    // 開啟一個區域性的事件迴圈,等待頁面響應結束
    QEventLoop eventLoop;
    QObject::connect(manager, &QNetworkAccessManager::finished, &eventLoop, &QEventLoop::quit);
    eventLoop.exec();

    // 獲取網頁Body中的內容
    QByteArray bytes = pReply->readAll();
    qDebug() << bytes;

    return a.exec();
}

QT快速學習筆記——介紹窗體

QT中的窗體

在上一節中,我允諾會在本節介紹一個空白元件來作為窗體的“襯底”,也就是所有其他元件的父元件。

這個空白的元件的名字是QWidget,我不知道上一節中我貼出來的QT中類的結構簡圖你有沒有仔細看。假如仔細看過應該會記得QPushButton以及其他所有的可視的窗體元件都是繼承自QWidget的。

這應該很好理解,QWidget是所有可視元件的基類,擁有一個元件的基本屬性——可以設定尺寸、設定標題、設定是否允許最大化及最小化等。

是不是被我說暈了?潛意識裡認定明明只有窗體才擁有標題以及最大最小化這些按鈕,為什麼元件也可以?

這是因為在QT中其實並沒有嚴格意義上的“窗體”,前面兩節中咱們執行的程式展現出的窗體實際上都是元件本身——當一個不存在父元件的元件被呼叫show方法時,他就會顯示出窗體的外邊框。

下圖是上一節中編寫的程式,他的窗體外邊框(標紅部分)來自不存在父元件的button1物件,而button2物件的父元件是button1,所以button2的窗體外邊框被隱藏了。

簡而言之,QWidget擁有一個元件的基本能力,同時他預設不顯示任何內容也不提供任何功能。

雖然QT中沒有專門作為窗體的東西,但我們完全可以把QWidget這個元件當成純粹的窗體來使用。

改進上一節的程式

非常簡單,我們只需要把button1物件換成一個來自QWidget類的物件就好啦!

#include <QApplication>
#include <QPushButton>
#include <QWidget> // 引入QWidget元件

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    // 例項化一個QWidget物件window
    QWidget *window = new QWidget();
    // 注意這裡將button2的父元件修改為window
    QPushButton *button2 = new QPushButton("Hi", window);

    // 這裡修改要顯示的元件為window
    window->show();

    return a.exec();
}

執行程式

還是很簡陋,但是比上一節中那個兩個按鈕疊羅漢的程式可是好多了哈~

下一節中我將介紹QT元件的方法和屬性,那時我們就可以利用新學的知識再度美化一下介面了。

QT快速學習筆記——在元件之上顯示另一個元件

Parenting system介紹

這是我要介紹的QT程式的第一個重要的概念——Parenting system,原諒我不知道怎麼翻譯=_=。

這個機制的實現由QObject類提供,該類是QT中絕大部分元件的基類。QT中類的關係簡圖如下:

可以看到,我們在上一節中所使用的QPushButton元件也繼承自QObject類,所以該元件也支援Parenting system機制。

程式碼演示

接下來我將透過一段程式碼來演示Parenting system機制的作用:

#include <QApplication>
#include <QPushButton>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QPushButton *button1 = new QPushButton("Hello World");
    // 例項化button2,在建構函式的第二個引數中填入button1的地址
    // 此時button2就是button1的子元件
    QPushButton *button2 = new QPushButton("Hi", button1);

    // 只需要顯示父元件,其下的子元件就會一同顯示
    // 另外子元件也不需要單獨呼叫delete(),他會隨著父元件一起被釋放
    button1->show();

    return a.exec();
}

執行後顯示是這樣的:

我們將窗體拉大,變成如下:

解讀

可以看到,button2是顯示在了button1的上面。這就很實際了,因為我們常見的Windows窗體都是在一個元件上疊加其他元件來顯示的,例如:

每一個顏色的框框代表一個元件(沒有把所有元件都標記出來,只摘取部分說明問題)。可以發現這樣一個很複雜的窗體,就是一個面積很大的元件上面無限層次的疊加小元件來最終實現的。

而維護這種元件之間的父子關係的方法就是我們本篇文章所介紹的——Parenting system機制。

但是我們剛剛的程式還存在一個問題,就是父元件button1是一個按鈕,試想一下如果把button1換成一個顯示空白的元件是不是會更好一些?相關內容將在下一篇文章中詳述。

QT快速學習筆記——編寫第一個QT程式

剛新建完的QT專案包含五個檔案,分別是:

  • main.cpp
  • mainwindow.cpp
  • mainwindow.h
  • mainwindow.ui
  • untitled.pro

嘗試執行一下這個預設專案,執行的方法如下所示。

執行起來的程式:

是一個標題為MainWindow的空窗體,裡面什麼也沒有。即便如此,這個窗體的程式碼對於初學者而言依然過於複雜,接下來我們刪除掉窗體顯示相關程式碼,只保留QT程式最基本的框架,隨後再一點一點向裡面填充內容。

刪掉除了main.cpp和untitled.pro之外的其他檔案。最後整個專案變成這樣:

我們看main.cpp,這個檔案的初始內容如下:

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

下面簡單修改一下這個檔案,刪除掉對mainwindow.h檔案的引用和對MainWindow類的例項化相關程式碼。我們在之前看到的那個標題為MainWindow的窗體實際上就是這個類生成的。刪除後的程式碼如下所示:

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    
    return a.exec();
}

現在看到的就是一個QT程式的最基本框架了。下面我們填充一個按鈕到這個QT程式裡,讓按鈕顯示Hello World。

#include <QApplication>
#include <QPushButton> // QPushButton這個類提供按鈕元件

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    // 例項化一個按鈕,這裡傳給建構函式的值會被設定為按鈕顯示的文字
    // 除此之外還可以透過按鈕類提供的setText方法來設定,後面會講
    QPushButton *button = new QPushButton("Hello World"); 
    button->show(); // 呼叫show方法,顯示元件。

    return a.exec();
}

之後執行程式就可以看到第一個QT程式跑起來了~他可能很簡陋,但戰略意義遠大於戰術意義。

QT快速學習筆記——開發環境部署及專案建立

QT開發環境安裝

在下面的地址中找到適合你的作業系統的最新的安裝包,下載之。

http://download.qt.io/archive/qt/

安裝步驟就是一路點選“下一步”,直到出現如下介面的時候需要手工勾選一下,只勾選我標記出來的選項即可。

隨後繼續下一步,直到安裝完成。

建立一個新專案

QT自帶的開發工具叫QT Creator,是一個C++ IDE,你也可以使用微軟的VS來開發QT,這裡只介紹QT Creator。

QT安裝好後QT Creator的快捷方式並不會建立在系統桌面上,需要你去開始選單裡找。

QT Creator啟動後長這個樣子:

建立專案的流程是:檔案->新建檔案或專案,然後調出如下介面,按照我選的選一下,之後一路無腦下一步。選的過程中可以簡單看看介面的文字介紹瞭解一下這一步大概做了哪些工作,實在搞不懂的也別去糾結,該懂的以後自然會懂~

這樣一個QT專案就建立好了,如下所示:

QT快速學習筆記——導語

前言

為什麼稱之為“快速學習筆記”?是因為我本人學東西就是一個“莽”字,啥也不會的時候就橫衝直撞觀其大略習其思想,瞅的差不多了直接融入到專案裡應用,再在開發過程中對細節問題隨時查缺補漏。當然這種學習方法並不適合所有人,有的自律性強耐得住寂寞的人就比較喜歡穩紮穩打,從基礎開始一點點學。

哦,對了——你可能想問學習QT是不是要精通C++才行?我必須告訴你並不是,因為我本人就是個C++白痴(約等於什麼也不會的那種),按照我的學習思路,C++的語法是到用到的時候才會去查手冊的,並不會在學習QT時提前去學。當然,這樣做的前提是,你必須擁有其他至少一門OOP程式語言的使用經驗,否則還是先簡單學習下C++再來看這個系列的文章吧。

QT簡介

QT是一個C++的開發庫,裡面包括多執行緒、網路、GUI等等的模組。很多人把QT當成是一個GUI庫其實是片面了,因為QT更像一個大雜燴,GUI只是其功能的一部分。

QT學習資料

除了本系列文章之外你還可以參考官方的學習手冊。其實我是很推薦你看看官方手冊的,因為我所寫的是我按照我的理解方式總結出來的,也許你自己翻一遍官方手冊會有不一樣的感悟呢。英文不好沒關係,我也是藉著谷歌翻譯看的=_=

QT官方入門手冊(推薦初學者閱讀)

https://wiki.qt.io/Qt_for_Beginners

QT官方完全開發手冊(推薦做專案的時候隨用隨查)

https://doc.qt.io/qt-5/reference-overview.html

QT的應用案例

列舉四個比較有名的專案

  • 國產Deepin作業系統的DDE桌面環境
  • KDE桌面環境
  • WPS辦公軟體
  • HBuilderX前端開發者工具

如何從YouTube上扒等效柱狀投影的3D全景影片

導語

這段時間接了個專案,是給WordPress編寫全景影片播放外掛,客戶的影片來源是YouTube。

但是我發現了一個很有意思的問題:

做過全景影片的同學應該一眼就看出來這種不是常見的影片格式。

事實上,這個是谷歌最新研發的 “等角度立方體貼圖 (EAC) ”,這種格式目前只有YouTube可以正常解析。

當然,YouTube也提供了常見的“等效柱狀投影”的全景影片,下面看獲取方法。

YouTube上等效柱狀投影全景影片的獲取方法

核心思想就是改變瀏覽器的User Agent,讓YouTube識別不出你所用的瀏覽器,這樣他就不能確定你的瀏覽器是否支援解析“等角度立方體貼圖”格式的影片,所以會返回最普通的“等效柱狀投影”的影片。這樣即便是直接展示影片的原始樣式而不做全景播放處理也依然可以勉強觀看。

效果如下:

修改User Agent可以使用各個瀏覽器的外掛實現,比如FireFox可以使用User-Agent Switcher外掛,將UserAgent修改為curl或者為空都可以。之後就可以使用常用的扒影片工具從這個網頁上提取連結下載了。

記錄一個Laravel orm訪問器不起作用的問題

前言

今天碼磚的時候發現laravel orm的訪問器在使用者前臺死活都用不了,但是管理員後臺卻顯示正常,一時間感到很迷亂……

問題原因

經過仔細觀察,原來後臺是先查詢然後再透過手工呼叫物件屬性的方式顯示,前臺則是查詢後直接將結果返回給使用者。

翻了下官方手冊,orm的訪問器並不是在資料庫查詢的時候起的作用,而是在呼叫相應的orm物件屬性的時候才會生效。

所以,想用訪問器的話就不要把查詢到的orm物件直接返回給客戶了,中間加一層處理就好了。

附加

根據訪問器的工作原理可知,假如資料庫查詢時限制了輸出的欄位,那麼在訪問器中被限制輸出的欄位是沒辦法用$this->field或$this->attributes[‘field’]來索引的。

JB家的IDE在Linux下無法儲存FTP/資料庫的密碼的解決方案

前言

事情的緣由是這樣的——某天我開開心心的開啟computer想要碼一會磚,然後萬惡的Phpstorm一直在詢問我sftp的登入使用者名稱和密碼。即便我輸入的時候選中了記住密碼,在程式重啟之後這貨又會再次詢問。之後發現Datagrip也犯了一樣的毛病,一直詢問我資料庫登入資訊……

這裡必須要吐槽一下JB家IDE的腦殘設計——明知道是缺少依賴軟體包,為啥就不能直接在前端拋一個錯誤呢?搞得我還得一點一點的翻程式日誌,我太難了=_=…

錯誤原因

錯誤原因很簡單,就是前面說的:缺少依賴包。具體缺少的是一個叫做gnome-keyring的包,這個包是用來儲存各種密碼的。

解決方案

安裝之。以下列出常見的三個系列發行版下的安裝方式,其他發行版的同學使用各自的包管理器直接安裝就行,這個包通常都是整合在官方源裡的。

Ubuntu/Debian

sudo apt install gnome-keyring

Fedora/CentOS

sudo yum install gnome-keyring

Arch/Manjaro

sudo pacman -S gnome-keyring

整理下我的VIM配置檔案

以下是我個人使用的VIM配置檔案內容,記錄在這裡,方便日後查閱。

"開啟語法高亮
syntax on
set t_Co=256
"開啟檔案型別檢測功能
filetype on
"不同檔案型別採用不同縮排
filetype indent on
"允許使用外掛
filetype plugin on
filetype plugin indent on
"關閉vi模式
set nocp
"與windows共享剪貼簿
set clipboard+=unnamed
"取消VI相容,VI鍵盤模式不易用
set nocompatible
"顯示行號, 或set number
set nu
"歷史命令儲存行數
set history=100
"當檔案被外部改變時自動讀取
set autoread
"取消自動備份及產生swp檔案
set nobackup
set nowb
set noswapfile
"允許使用滑鼠點選定位
set mouse=a
"允許區域選擇
set selection=exclusive
set selectmode=mouse,key
"高亮游標所在行
set cursorline
"取消游標閃爍
set novisualbell
"總是顯示狀態行
set laststatus=2
"狀態列顯示當前執行的命令
set showcmd
"標尺功能,顯示當前游標所在行列號
set ruler
"貼上時保持格式
set paste
"高亮顯示匹配的括號
set showmatch
"在搜尋的時候忽略大小寫
set ignorecase
"高亮被搜尋的句子
set hlsearch
"在搜尋時,輸入的詞句的逐字元高亮(類似firefox的搜尋)
set incsearch
"繼承前一行的縮排方式,特別適用於多行註釋
set autoindent
"為C程式提供自動縮排
set smartindent
"使用C樣式的縮排
set cindent
"製表符為4
set tabstop=4
set expandtab
"%retab!
"統一縮排為4
set softtabstop=4
set shiftwidth=4
"允許使用退格鍵,或set backspace=2
set backspace=eol,start,indent
set whichwrap+=<,>,h,l
"取消換行
set nowrap
"啟動的時候不顯示那個援助索馬利亞兒童的提示
set shortmess=atI
"在被分割的視窗間顯示空白,便於閱讀
set fillchars=vert:\ ,stl:\ ,stlnc:\
"游標移動到buffer的頂部和底部時保持3行距離, 或set so=3
set scrolloff=3
"設定預設解碼
set fenc=utf-8
set fencs=utf-8,usc-bom,euc-jp,gb18030,gbk,gb2312,cp936
"設定字型
set guifont=Courier_New:h11:cANSI
set guifontwide=新宋體:h11:cGB2312
"設定編碼
set enc=utf-8
set fileencodings=ucs-bom,utf-8,chinese
set langmenu=zh_CN.UTF-8
language message zh_CN.UTF-8
source $VIMRUNTIME/delmenu.vim
source $VIMRUNTIME/menu.vim
"自動補全
filetype plugin indent on
set completeopt=longest,menu
"自動補全命令時候使用選單式匹配列表
set wildmenu
autocmd FileType ruby,eruby set omnifunc=rubycomplete#Complete
autocmd FileType python set omnifunc=pythoncomplete#Complete
autocmd FileType javascript set omnifunc=javascriptcomplete#CompleteJS
autocmd FileType html set omnifunc=htmlcomplete#CompleteTags
autocmd FileType css set omnifunc=csscomplete#CompleteCSS
autocmd FileType xml set omnifunc=xmlcomplete#CompleteTags
autocmd FileType java set omnifunc=javacomplete#Complet

35行C++程式碼實現時間戳轉時間演算法

程式邏輯概述

根據平年閏年計算規則,利用遞迴先求出年份。之後再根據固定的大小月規則和平年閏年下2月天數的不同求出月數,之後就剩下最簡單的天數、小時、分鐘和秒了。

這個程式我承認為了儘可能縮小行數,有的地方寫的不是很規範~正可謂是為了減少行數而不擇手段~

程式程式碼

注:去掉空行的話的確只有35行的,我並不是標題黨!

#include <iostream>

int time_stamp = 28800, year = 1969, month = 0, day = 0, hour = -1, minute = -1, tmp;

auto isLeapYear = [] { return (year % 4 == 0 && year % 100 != 0) || (year % 100 == 0 && year % 400 == 0) || (year % 3200 == 0 && year % 172800 == 0); };

int getYear(int *ptr_time_stamp) {
	++year;
	if (*ptr_time_stamp >= (isLeapYear() ? 31622400: 31536000))
		getYear(&(*ptr_time_stamp -= (isLeapYear() ? 31622400 : 31536000)));

	return year;
}

int getMonth(int *ptr_time_stamp) {
	++month;
	if (month == 2 && (*ptr_time_stamp >= (isLeapYear() ? 2505600 : 2419200)))
		getMonth(&(*ptr_time_stamp -= (isLeapYear() ? 2505600 : 2419200)));
	else if ((month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) && *ptr_time_stamp >= 2592000)
		getMonth(&(*ptr_time_stamp -= 2678400));
	else if ((month == 4 || month == 6 || month == 9 || month == 11) && *ptr_time_stamp >= 2678400)
		getMonth(&(*ptr_time_stamp -= 2592000));

	return month;
}

int getTime(int *ptr_time_stamp, int *type, int second) {
	++*type;
	if (*ptr_time_stamp >= second) 
		getTime(&(*ptr_time_stamp -= second), type, second);

	return *type;
}

int main(void) {
	std::cin >> tmp;
	time_stamp += tmp;

	std::cout << getYear(&time_stamp) << "年";
	std::cout << getMonth(&time_stamp) << "月";
	std::cout << getTime(&time_stamp, &day, 86400) << "日 ";
	std::cout << getTime(&time_stamp, &hour, 3600) << "時";
	std::cout << getTime(&time_stamp, &minute, 60) << "分" << time_stamp << "秒";

	return 0;
}