【1】Qt Start


一、Qt 概述

1.1 什么是 Qt

Qt 是一個跨平台的 C++ 圖形用戶界面應用程序框架。它為應用程序開發者提供建立藝術級圖形界面所需的所有功能。它是完全面向對象的,很容易擴展,並且允許真正的組件編程

1.2 支持的平台

  • Windows – XP、Vista、Win7、Win8、Win2008、Win10

  • Uinux/X11 – Linux、Sun Solaris、HP-UX、Compaq Tru64 UNIX、IBM AIX、SGI IRIX、FreeBSD、BSD/OS、和其他很多 X11 平台

  • Macintosh – Mac OS X

  • Embedded – 有幀緩沖支持的嵌入式 Linux 平台,Windows CE

Windows 建議下載 MinGW 版本,安裝時建議組件全部選中。

1.3 Qt 的優點

  • 跨平台,幾乎支持所有的平台

  • 接口簡單,容易上手,學習 QT 框架對學習其他框架有參考意義。

  • 一定程度上簡化了內存回收機制

  • 開發效率高,能夠快速的構建應用程序。

  • 有很好的社區氛圍,市場份額在緩慢上升。

  • 可以進行嵌入式開發。

1.4 成功案例

  • Linux 桌面環境 KDE

  • WPS Office 辦公軟件

  • Skype 網絡電話

  • Google Earth 谷歌地圖

  • VLC 多媒體播放器

  • VirtualBox 虛擬機軟件

二、創建 Qt 項目

2.1 使用向導創建

打開 Qt Creator 界面選擇 New Project 或者選擇菜單欄 【文件】-【新建文件或項目】菜單項

彈出 New Project 對話框,選擇 Qt Widgets Application。選擇【Choose】按鈕,彈出如下對話框

設置項目名稱和路徑,按照向導進行下一步

選擇編譯套件

向導會默認添加一個繼承自 CMainWindow 的類,可以在此修改類的名字和基類。默認的基類有 QMainWindow、QWidget 以及 QDialog 三個,可以選擇 QWidget(類似於空窗口),這里先創建一個不帶 UI 的界面,繼續下一步

系統會默認添加 main.cpp、mywidget.cpp、 mywidget.h 和一個 .pro 項目文件,點擊完成,即可創建出一個 Qt 桌面程序。

創建后,結果如圖

2.2 .pro 文件

在使用 Qt 向導生成的應用程序 .pro 文件格式如下:

QT       += core gui  # 包含的模塊
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets # 大於 Qt4 版本 才包含 widget 模塊
TARGET = QtFirst  # 應用程序名  生成的.exe程序名稱
TEMPLATE = app    # 模板類型    應用程序模板 Application  
SOURCES += main.cpp\   # 源文件
        mywidget.cpp
HEADERS  += mywidget.h   # 頭文件

.pro就是工程文件(project),它是 qmake 自動生成的用於生產 makefile 的配置文件.pro文件的寫法如下:

  • 注釋:從“#”開始,到這一行結束。
  • 模板變量告訴 qmake 為這個應用程序生成哪種 makefile。下面是可供使用的選擇:TEMPLATE = app
    • app - 建立一個應用程序的 makefile。這是默認值,所以如果模板沒有被指定,這個將被使用。
    • lib - 建立一個庫的 makefile。
    • vcapp - 建立一個應用程序的 Visual Studio 項目文件。
    • vclib - 建立一個庫的 Visual Studio 項目文件。
    • subdirs -這是一個特殊的模板,它可以創建一個能夠進入特定目錄並且為一個項目文件生成 makefile 並且為它調用 make 的 makefile。
  • 指定生成的應用程序名TARGET = QtFirst
  • 工程中包含的頭文件HEADERS += mywidget.hHEADERS += include/painter.h(可以有路徑)
  • 工程中包含的 .ui 設計文件FORMS += forms/painter.ui
  • 工程中包含的源文件SOURCES += main.cpp
  • 工程中包含的資源文件RESOURCES += qrc/painter.qrc
  • greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
    • 如果 QT_MAJOR_VERSION 大於 4(也就是當前使用的 Qt5 及更高版本)需要增加 widgets 模塊。如果項目僅需支持 Qt5,也可以直接添加 “QT += widgets” 一句。不過為了保持代碼兼容,最好還是按照 QtCreator 生成的語句編寫。
  • 配置信息
    • CONFIG 用來告訴 qmake 關於應用程序的配置信息。
    • CONFIG += c++11 //使用 c++11 的特性
  • 在這里使用 “+=” ,是因為添加[配置選項]到任何一個已經存在中。這樣做比使用 “=” 那樣替換已經指定的所有選項更安全。

2.3 分析其他文件

【mywidget.h】

#ifndef MYWIDGET_H
#define MYWIDGET_H

#include <QWidget> //包含頭文件 QWidget 窗口類

class myWidget : public QWidget
{
    Q_OBJECT // Q_OBJECT宏,允許類中使用信號和槽的機制

public:
    myWidget(QWidget *parent = 0); //構造函數
    ~myWidget(); //析構函數
};

#endif // MYWIDGET_H

【mywidget.cpp】

#include "mywidget.h"

MyWidget::MyWidget(QWidget *parent)
    : QWidget(parent)
{
}

MyWidget::~MyWidget()
{
}

【main.cpp】

#include "mywidget.h"
#include <QApplication>// 包含一個應用程序類的頭文件

//main程序入口  argc命令行變量的數量  argv命令行變量的數組
int main(int argc, char *argv[])
{
    //a應用程序對象,在Qt中,應用程序對象 有且僅有一個
    QApplication a(argc, argv);
	
    //窗口對象  myWidget父類  -> QWidget
    myWidget w;
	
    //窗口對象 默認不會顯示,必須要調用show方法顯示窗口
    w.show();

    //讓應用程序對象進入消息循環
    //代碼阻塞到這行,只有當點擊窗口右上角的叉,才會關閉窗口,中止運行
    return a.exec();
}

【注意】

  • Qt 系統提供的標准類名聲明頭文件沒有 .h 后綴
  • Qt 一個類對應一個頭文件,類名就是頭文件名
  • QApplication 應用程序類
    • 管理圖形用戶界面應用程序的控制流和主要設置。
    • 是 Qt 的整個后台管理的命脈。它包含主事件循環,在其中來自窗口系統其它資源的所有事件處理和調度。它也處理應用程序的初始化和結束,並且提供對話管理
    • 對於任何一個使用 Qt 的圖形用戶界面應用程序,都正好存在一個 QApplication 對象,而不論這個應用程序在同一時間內是不是有 0、1、2 或更多個窗口。
  • a.exec()
    • 程序進入消息循環,等待對用戶輸入進行響應。這里 main() 把控制權轉交給 Qt,Qt 完成事件處理工作,當應用程序退出的時候 exec() 的值就會返回。在 exec() 中,Qt 接受並處理用戶和系統的事件並且把它們傳遞給適當的窗口部件。

2.4 命名規范 & 快捷鍵

【命名規范】

  • 類名 首字母大寫,單詞和單詞之間首字母大寫
  • 函數名 變量名稱 首字母小寫,單詞和單詞之間首字母大寫

【快捷鍵】

  • 注釋 ctrl + /
  • 運行 ctrl + r
  • 編譯 ctrl + b
  • 字體縮放 ctrl + 鼠標滾輪
  • 查找 ctrl + f
  • 整行移動 ctrl + shift + ↑ 或者↓
  • 幫助文檔 F1兩次 F1,大屏顯示幫助文檔。按 Esc 退出幫助文檔
  • 自動對齊 ctrl + i;
  • 同名之間的 .h.cpp 切換 F4
  • 幫助文檔 第一種方式 F1 第二種 左側按鈕 第三種 C:\Qt\Qt5.6.0\5.6\mingw49_32\bin

三、第一個 Qt 小程序

3.1 在窗口中新建按鈕

  • 引入按鈕頭文件 #include <QPushButton>

  • 創建按鈕方式一:QPushButton *btn = new QPushButton;

    • 顯示按鈕:btn->show();。show 以頂層方式(即新建一個窗口)彈出窗口控件
    • 為了在主窗口中顯示按鈕,將其父窗口設置為主窗口:btn->setParent(this);
    • 為按鈕設置文本:btn->setText("第一個按鈕");

    • 為按鈕設置尺寸:btn->resize(150,50);

    • 默認是窗口大小以控件大小顯示,可以設置窗口大小:resize(600, 400);
  • 創建按鈕方式二:QPushButton *btn2 = new QPushButton("第二個按鈕",this);

  • 如果一個程序中同時創建上述兩個按鈕,則只會顯示【第二個按鈕】,因為第二個按鈕將第一個按鈕覆蓋了

    • 解決:移動按鈕 btn2->move(100,100);
  • 將窗口設置為固定大小,用戶不可修改窗口尺寸:setFixedSize(600, 400);

  • 修改窗口左上角的窗口名字:setWindowTitle("第一個窗口");

#include "mywidget.h"
#include <QPushButton>

MyWidget::MyWidget(QWidget *parent)
    : QWidget(parent)
{
    //創建一個按鈕
    QPushButton *btn  = new QPushButton;
    //btn->show();  //新建一個窗口,顯示按鈕
    btn->setParent(this);
    btn->setText("第一個按鈕");
    btn->resize(150,50);

    QPushButton *btn2  = new QPushButton("第二個按鈕",this);
    btn2->move(100,100);
    resize(600, 400);
    setFixedSize(600, 400);
    setWindowTitle("第一個窗口");
}

MyWidget::~MyWidget()
{
}

3.2 對象模型/對象樹

QObject 是以對象樹的形式組織起來的。

  • 當你創建一個 QObject 對象時,會看到 QObject 的構造函數接收一個 QObject 指針作為參數,這個參數就是 parent,也就是父對象指針。這相當於,在創建 QObject 對象時,可以提供一個其父對象,創建的這個 QObject 對象會自動添加到其父對象的 children() 列表。

  • 父對象析構的時候,這個列表children()中的所有對象也會被析構。(注意,這里的父對象並不是繼承意義上的父類!)這種機制在 GUI 程序設計中相當有用。例如,一個按鈕有一個 QShortcut(快捷鍵)對象作為其子對象。當刪除按鈕的時候,這個快捷鍵理應被刪除。這是合理的。

QWidget 是能夠在屏幕上顯示的一切組件的父類

  • QWidget 繼承自 QObject,因此也繼承了這種對象樹關系。一個孩子自動地成為父組件的一個子組件。因此,它會顯示在父組件的坐標系統中,被父組件的邊界剪裁。例如,當用戶關閉一個對話框的時候,應用程序將其刪除,那么,希望屬於這個對話框的按鈕、圖標等應該一起被刪除。事實就是如此,因為這些都是對話框的子組件。

  • 當然,也可以自己刪除子對象,它們會自動從其父對象列表中刪除。比如,當刪除了一個工具欄時,其所在的主窗口會自動將該工具欄從其子對象列表中刪除,並且自動調整屏幕顯示。

Qt 引入對象樹的概念,在一定程度上解決了內存問題

  • 當一個 QObject 對象在上創建的時候,Qt 會同時為其創建一個對象樹。不過,對象樹中對象的順序是沒有定義的。這意味着,銷毀這些對象的順序也是未定義的。

    • 當創建的對象在堆區時候,如果指定的父親是 QObject 或其派生下來的類,可以不用管理釋放的操作,Qt 自動將對象會放入到對象樹中。
  • 任何對象樹中的 QObject 對象 delete 的時候,如果這個對象有 parent,則自動將其從 parent 的children() 列表中刪除;如果有孩子,則自動 delete 每一個孩子。Qt 保證沒有 QObject會被 delete 兩次,這是由析構順序決定的。


如果 QObject 在上創建,Qt 保持同樣的行為。正常情況下,這也不會發生什么問題。代碼如下:

{
    QWidget window;
    QPushButton quit("Quit", &window);
}

作為父組件的 window 和作為子組件的 quit 都是 QObject 的子類(事實上,它們都是 QWidget 的子類,而 QWidget 是 QObject 的子類)。這段代碼是正確的,quit 的析構函數不會被調用兩次,因為標准 C++要求,局部對象的析構順序應該按照其創建順序的相反過程。因此,這段代碼在超出作用域時,會先調用 quit 的析構函數,將其從父對象 window 的子對象列表中刪除,然后才會再調用 window 的析構函數

但是,如果使用下面的代碼:

{
    QPushButton quit("Quit");
    QWidget window;
    quit.setParent(&window);
}

情況又有所不同,析構順序就有了問題。在上面的代碼中,作為父對象的 window 會首先被析構因為它是最后一個創建的對象。在析構過程中,它會調用子對象列表中每一個對象的析構函數,也就是說,quit 此時就被析構了。然后,代碼繼續執行,在 window 析構之后,quit 也會被析構,因為 quit 也是一個局部變量,在超出作用域的時候當然也需要析構。但是,這時候已經是第二次調用 quit 的析構函數,C++ 不允許調用兩次析構函數,因此,程序崩潰

結論:由此看到,Qt 的對象樹機制雖然在一定程度上解決了內存問題,但是也引入了一些值得注意的事情。這些細節在今后的開發過程中很可能時不時跳出來煩擾一下,所以,最好從開始就養成良好習慣,在 Qt 中,盡量在構造的時候就指定 parent 對象,並且大膽在堆上創建。

3.3 Qt 窗口坐標系

坐標體系:以左上角為原點(0,0),X向右增加,Y向下增加。

對於嵌套窗口,其坐標是相對於父窗口來說的。

四、信號和槽機制

  信號槽是 Qt 框架引以為豪的機制之一。所謂信號槽,實際就是觀察者模式當某個事件發生之后,比如,按鈕檢測到自己被點擊了一下,它就會發出一個信號(signal)。這種發出是沒有目的的,類似廣播。如果有對象對這個信號感興趣,它就會使用連接(connect)函數,意思是,將想要處理的信號和自己的一個函數(稱為槽(slot))綁定來處理這個信號。也就是說,當信號發出時,被連接的槽函數會自動被回調。這就類似觀察者模式:當發生了感興趣的事件,某一個操作就會被自動觸發。

4.1 系統自帶的信號和槽

下面完成一個小功能,上面已經學習了按鈕的創建,但是還沒有體現出按鈕的功能,按鈕最大的功能也就是點擊后觸發一些事情,比如點擊按鈕,就把當前的窗口給關閉掉,那么在 Qt 中,這樣的功能如何實現呢?

QPushButton * quitBtn = new QPushButton("關閉窗口",this);
connect(quitBtn, &QPushButton::clicked, this, &MyWidget::close); // 信號槽的使用方式
// MyWidget也可以使用父對象:&QWidget::close
// 是 clicked,不是click

connect() 函數最常用的一般形式:connect(sender, signal, receiver, slot);

  • sender:發出信號的對象

  • signal:發送對象發出的信號(函數地址)

  • receiver:接收信號的對象

  • slot:接收對象在接收到信號之后所需要調用的函數(槽函數)(函數的地址)

信號槽的優點:松散耦合。信號發送端與接收端本身沒有關聯,通過 connect 函數連接,將兩端連接起來。


系統自帶的信號和槽通常查找方法:通過幫助文檔,如上面的按鈕的點擊信號,在幫助文檔中輸入 QPushButton,首先可以在 Contents 中尋找關鍵字 signals(信號),但是發現並沒有找到,這時候應該想到也許這個信號的被父類繼承下來的,因此去他的父類 QAbstractButton 中就可以找到該關鍵字,點擊 signals 索引到系統自帶的信號有如下幾個:

這里的 clicked 就是要找到的信號,槽函數的尋找方式和信號一樣,只不過他的關鍵字是slot。

4.2 自定義信號和槽

使用 connect() 可以連接系統提供的信號和槽。但是,Qt 的信號槽機制並不僅僅是使用系統提供的那部分,還會允許自己設計自己的信號和槽。

🔶 Qt 的信號槽代碼

首先定義一個學生類和老師類:

* 老師類中聲明信號 餓了 hungry
signals:
       void hungury();
* 學生類中聲明槽   請客 treat
public slots:
       void treat();

在窗口中聲明一個公共方法下課,這個方法的調用會觸發老師餓了這個信號,而響應槽函數學生請客
void MyWidget::ClassIsOver()
{
    //發送信號
    emit teacher->hungury();
}

學生響應了槽函數,並且打印信息
//自定義槽函數 實現
void Student::treat()
{
       qDebug() << "該吃飯了!";
}

在窗口中連接信號槽
    teacher = new Teacher(this);
    student = new Student(this);

    connect(teacher,&Teacher::hungury,student,&Student::treat);

在窗口中調用下課函數,測試打印出 “該吃飯了”
    ClassIsOver();

🔶 自定義信號槽需要注意的事項

  • 發送者和接收者都需要是 QObject 的子類(當然,槽函數是全局函數、Lambda 表達式等無需接收者的時候除外);

  • 信號和槽函數返回值是 void,可以有參數 ,可以重載。

  • 信號只需要聲明,不需要實現,寫到關鍵字 signals 下。

  • 槽函數需要聲明也需要實現,早期 Qt 版本必須要寫到 public slots,高級版本可以寫到 public 或者 全局下。

  • 槽函數是普通的成員函數,作為成員函數,會受到 public、private、protected 的影響;

  • 使用 emit 在恰當的位置觸發信號(發送信號);

  • 使用 connect() 函數連接信號和槽。

  • 任何成員函數、static 函數、全局函數和 Lambda 表達式都可以作為槽函數。

  • 信號槽要求信號和槽的參數一致,所謂一致,是參數類型一致

  • 如果信號和槽的參數不一致,允許的情況是,槽函數的參數可以比信號的少,即便如此,槽函數存在的那些參數的順序也必須和信號的前面幾個一致起來。這是因為,你可以在槽函數中選擇忽略信號傳來的數據(也就是槽函數的參數比信號的少)

🔶 程序中同時定義帶參數與不帶參數的 hungry 函數,需要利用函數指針,明確指向函數的地址:

void hungury();  
void hungury(QString name);  自定義信號
void treat();  
void treat(QString name );  自定義槽

// 🍓 但是由於有兩個重名的自定義信號和自定義的槽,直接連接會報錯,所以需要利用【函數指針】來指向函數地址,然后再做連接
void (Teacher:: * teacherSingal)(QString) = &Teacher::hungury;
void (Student:: * studentSlot)(QString) = &Student::treat;

connect(teacher,teacherSingal,student,studentSlot);

// 無參信號和槽連接
void(Teacher:: *teacherSignal2)(void) = &Teacher::hungry;
void(Student:: *studentSlot2)(void)  = &Student::treat;
connect(teacher,teacherSignal2,student,studentSlot2);

🔶 程序中直接打印 QString 類型的數據時,輸出數據會帶有引號:

qDebug() << "請老師吃飯,老師要吃:"<<footName;

// 輸出(宮保雞丁有引號)
// 請老師吃飯,老師要吃: "宮保雞丁"

解決:將 QString 轉成 char*

  • .ToUtf8() 轉為 QByteArray

  • .Data() 轉為 Char*

qDebug() << "請老師吃飯,老師要吃:"<<footName.toUtf8().data();

// 輸出
// 請老師吃飯,老師要吃: 宮保雞丁

🔶 信號連接信號:

信號除了連接槽函數,還可以連接信號!直接去觸發[老師餓了]信號,而不通過調用函數 ClassIsOver()

connect(btn,&QPushButton::clicked, teacher, teacherSignal2);

🔶 斷開信號:

參數與想要斷開的 connect 函數完全一樣!

disconnect(btn,&QPushButton::clicked, teacher, teacherSignal2)

4.3 信號槽的拓展

一個信號可以和多個槽相連

  • 如果是這種情況,這些槽會一個接一個的被調用,但是它們的調用順序是不確定的。

多個信號可以連接到一個槽

  • 只要任意一個信號發出,這個槽就會被調用。

一個信號可以連接到另外的一個信號

  • 當第一個信號發出時,第二個信號被發出。除此之外,這種信號-信號的形式和信號-槽的形式沒有什么區別。

**信號和槽的參數 **

  • 必須類型一一對應
  • 信號的參數個數可以多余槽函數的參數個數,但是信號前面幾個參數與多余槽函數必須一一相對應(信號多出的參數要放到后面)

槽可以被取消鏈接

  • 這種情況並不經常出現,因為當一個對象 delete 之后,Qt 自動取消所有連接到這個對象上面的槽。

信號槽可以斷開

  • 利用 disconnect 關鍵字是可以斷開信號槽的

使用 Lambda 表達式

  • 在使用 Qt 5 的時候,能夠支持 Qt 5 的編譯器都是支持 Lambda 表達式的。在連接信號和槽的時候,槽函數可以使用Lambda表達式的方式進行處理。

4.4 Qt4 版本的信號槽寫法

connect(teacher,SIGNAL(hungry(QString)),student,SLOT(treat(QString))); 

這里使用了 SIGNAL 和 SLOT 這兩個宏,將兩個函數名轉換成了字符串。注意到 connect() 函數的 signal 和 slot 都是接受字符串,一旦出現連接不成功的情況,Qt4 是沒有編譯錯誤的(因為一切都是字符串,編譯期是不檢查字符串是否匹配),而是在運行時給出錯誤。這無疑會增加程序的不穩定性。Qt5 在語法上完全兼容 Qt4,而反之是不可以的。

  • 優點 參數直觀
  • 缺點 編譯器不會檢測參數類型

4.5 Lambda 表達式

C++11 中的 Lambda 表達式用於定義並創建匿名的函數對象,以簡化編程工作。

Lambda 表達式的基本構成:

[capture](parameters) mutable ->return-type
{
      statement
}
即:[函數對象參數](操作符重載函數參數)mutable ->返回值{函數體}

① 函數對象參數;

[],標識一個 Lambda 的開始,這部分必須存在,不能省略。函數對象參數是傳遞給編譯器自動生成的函數對象類的構造函數的。函數對象參數只能使用那些到定義 Lambda 為止時 Lambda 所在作用范圍內可見的局部變量(包括 Lambda 所在類的 this)。函數對象參數有以下形式:

  • 。沒有使用任何函數對象參數。
  • =。函數體內可以使用 Lambda 所在作用范圍內所有可見的局部變量(包括 Lambda 所在類的 this),並且是值傳遞方式(相當於編譯器自動按值傳遞了所有局部變量)。
  • &。函數體內可以使用 Lambda 所在作用范圍內所有可見的局部變量(包括 Lambda 所在類的 this),並且是引用傳遞方式(相當於編譯器自動按引用傳遞了所有局部變量)。
  • this。函數體內可以使用 Lambda 所在類中的成員變量
  • a。將 a 按進行傳遞。按值進行傳遞時,函數體內不能修改傳遞進來的 a 的拷貝,因為默認情況下函數是 const 的要修改傳遞進來的 a 的拷貝,可以添加 mutable 修飾符
  • &a。將 a 按引用進行傳遞。
  • a, &b。將 a 按進行傳遞,b 按引用進行傳遞。
  • =,&a, &b。除 a 和 b 按引用進行傳遞外,其他參數都按進行傳遞。
  • &, a, b。除 a 和 b 按進行傳遞外,其他參數都按引用進行傳遞。

② 操作符重載函數參數;

標識重載的()操作符的參數,沒有參數時,這部分可以省略。參數可以通過按值(如:(a,b))和按引用(如:(&a,&b))兩種方式進行傳遞。

③ 可修改標示符;

mutable 聲明,這部分可以省略。按值傳遞函數對象參數時,加上 mutable 修飾符后,可以修改按值傳遞進來的拷貝(注意是能修改拷貝,而不是值本身)。

    QPushButton * myBtn = new QPushButton (this);
    QPushButton * myBtn2 = new QPushButton (this);
    myBtn2->move(100,100);
    int m = 10;

    connect(myBtn,&QPushButton::clicked,this,[m] ()mutable { m = 100 + 10; qDebug() << m; }); // 第二步:按該按鈕,輸出 110

    connect(myBtn2,&QPushButton::clicked,this,[=] ()  { qDebug() << m; }); // 第三步:按該按鈕,輸出 10 (由此可見,外部變量的 m 沒有被改變)

    qDebug() << m;  //第一個輸出:運行時先執行此語句,輸出 10

④ 函數返回值;

->返回值類型,標識函數返回值的類型,當返回值為void,或者函數體中只有一處return的地方(此時編譯器可以自動推斷出返回值類型)時,這部分可以省略。

⑤ 是函數體
{},標識函數的實現,這部分不能省略,但函數體可以為空

Lambda表達式
[]標識符 匿名函數

  • = 值傳遞
  • & 引用傳遞

() 參數
{} 實現體
返回值 ->int {}

4.6 信號和槽總結


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM