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;
}