C++ QT零基础教学(二)

一. 引子

        在上一篇文章里面稍微讲解了一点C++的基础,但是后面想了想要是还是继续单纯写C++的内容的话要写到几百年年以后了,都不知道什么时候到QT了,所以这次就直接开始从QT开始描写了,当然肯定不会是很有难度,尽量还是会用通俗易懂的语言,因为毕竟我也是才开始接触QT的新手

二. 新建工程

        这里我们先举一个具体的例子,首先新建一个工程,点击NEW

        选择Qt Widgets Application

        设置一个工程的名字,和工程的存储位置

        这里选择qmake

        这里选择dialog.h,然后下一步

        这里直接下一步

        这里直接选择MinGW

        直接完成

        在这里生成了如下工程,工程创建成功后

三. QMessageBox

        在Qt中,QMessageBox是一个用于显示对话框的类,它可以显示信息、警告、错误提示等,并提供按钮让用户进行交互

        首先我们先点开上面二队dialog.ui,

        点击一个Push Button放到这个页面上

        然后右下角黄色框向下滑找到一个属性,text,PushButton,或者也可以直接更改按钮的名字

        右键这个按钮,然后有一个选项转到槽,会出现如下一个界面

        选择第一个clicked,然后点击ok,然后会在dialog.cpp的文件中生成一个函数,

        然后我来解释一下这里发生了什么

        在QT中,我们使用QT Designer设计界面,并为按钮等控件添加槽函数,这样当用户点击按钮时,就会自动调用我们编写的槽函数

        QT自动生成了on_pushButton_Clicked()函数,QT在dialog.h中声明了这个槽函数,那么什么是槽函数,可以理解为QT里自动执行的函数,会在某些特定的时间发生时被QT触发
        例如信号,类似于有人敲门(就是事件发生),槽函数就是听到敲门后,去开门(响应事件),在QT里,如果按钮被点击(信号发生),请自动执行某个函数(槽)

        在转到槽的时候QT主要进行了以下操作,QT在我们的类中(这里是Dialog)加上了槽函数,例如,在dialog.h中自动添加了槽函数的声明,就像是在QT里登记了一个开门的人,但是这里还没有写开门后要干什么

private slots:
    void on_pushButton_clicked();

        然后在dialog.h中,自动创建一个空额槽函数,这里只是生成了这个函数,并不会自己调用它,必须等按钮被点击,QT才会自动执行这个函数

void Dialog::on_pushButton_clicked()
{
    
}

        QT在后台连接了“按钮被点击”和”槽函数“,你在QT Designer里点击转到槽后,QT偷偷在后台连接了

1. QMessageBox information

        现在在函数中写下如下代码,

void Dialog::on_pushButton_clicked()
{
    QMessageBox::information(this,"information","是否退出系统",QMessageBox::Yes|QMessageBox::No,QMessageBox::Yes);
}

        先执行一下这段代码看看效果,点击这个按钮后,就会跳出来这个界面

        QMessageBox,主要用于提示用户信息,警告用户风险,显示错误信息,获取用户确认,而
QMessageBox::information()就是信息提示

·        这个函数的第一个参数this,this指向当前Dialog窗口的实例,可以这么简单理解,如果没有这个this,把函数的第一个参数换成NULL,那么之后这个提示用户信息窗口,就无法和Dialog产生关联,就无法通过窗口关闭最外面的界面

        而如果加入了this参数就可以和外面的界面产生关联,能够关闭写入此界面的逻辑

        那么后面的参数就很简单了,第一个information就是左上角的信息,然后是否退出系统就是中间给用户的提示信息,之后有一个|这个符号,表示下方有两个选择,一个是Yes、一个no,当然也可以有其他参数,只需要在后面再加入|这个然后写入想要的内容就可以了,以及最后一个参数就是默认没有选择时候的按钮,可以看到上面的图片有一个Yes处有一个高亮亮,所以如果把后面改成No的话,自然就是no的高亮

        那下一步增加一点逻辑

void Dialog::on_pushButton_clicked()
{
    QMessageBox::information(this,"information","是否退出系统",QMessageBox::Yes|QMessageBox::No,QMessageBox::Yes);
    close();
}

        写入这段代码后,点击提示框后就可以点击yes和no关闭整个界面,可以发现是yes和no,但是如果我们要实现Yes是关闭,但是no就是不关闭怎么办,那就加入逻辑,因为这里的代码意思就是,点击这个按钮,弹出选项,但是不论选项什么

2. QMessageBox question

        所以为了符合正常的逻辑,我们把代码再修改一下,这下实际的代码逻辑就非常的清晰了,能够点击yes就是关闭界面,不想关闭界面就直接点击no就可以了,但是其中代码的变动幅度也有点大,我们做一个解释

        前面的那段代码,弹出QMessageBox::information() 信息框,但不是真正的询问。QMessageBox::Yes | QMessageBox::No 在 QMessageBox::information() 里不起作用,因为 information() 不会返回用户的选择,它只是显示一个对话框,让我们点击关闭按钮。
无论点什么,信息框都会直接关闭,程序执行 close(); 关闭窗口
        而QMessageBox::question() 询问框:这个框有两个按钮:“是(Yes)”和“否(No)”
QMessageBox::question() 会返回我们的选择。ret == QMessageBox::Yes,执行 close(); 关闭窗口。ret == QMessageBox::No,close(); 不会执行,窗口保持打开状态

        int ret这个步骤就是为了创建一个变量来存储QMessageBox::question()的返回值,存储这个返回值后之后使用if判断,所以就实现了我们最后的效果

void Dialog::on_pushButton_clicked()
{
    int ret = QMessageBox::question(this,"information","是否退出系统",QMessageBox::Yes|QMessageBox::No,QMessageBox::Yes);
    if(ret == QMessageBox ::Yes)
    {
        close();
    }
}

        那么接下来我们如法炮制,去控件里面添加第二个按钮

        然后还是右键转到槽,自动帮我们生成按钮按下后的函数

3. QMessageBox warning 

        这是一个警告框,用来提示用户危险操作,就可以使用warning。上面的写法和下面的写入都可以 

void Dialog::on_pushButton_2_clicked()
{
    //QMessageBox::warning(this,"critical","此操作可能会对系统产生不可逆的影响",QMessageBox::Yes|QMessageBox::No,QMessageBox::Yes);
    QMessageBox::warning(this,"warning","这个操作可能导致数据丢失");
}

        效果如下

        如果注释掉第二行保留第一行则是,可以根据实际的开发中,决定warning的用处

4. QMessageBox critical

        还是按照老方法,设置第三个按钮,

void Dialog::on_pushButton_3_clicked()
{
    QMessageBox::critical(this,"错误","无法打开文件");
}

        平时遇到的系统发生错误就用到的是这个功能

5. QMessageBox about

        一般是用来显示关于软件的信息,例如还是创建第四个按钮,然后在函数中写入代码 


void Dialog::on_pushButton_4_clicked()
{
    QMessageBox::about(NULL,"about","默认提示框");
}

        显示的参数如下,这里写入Null,只是提示一下前面的参数是可以更改的,并不是一直都需要写入this,同理后面的参数也都是可以改变的,包括加入Yes或者no等带有逻辑的参数

6. 汇总 

        QMessageBox的主要功能如上,这里写一段代码用于加深理解

        使用第一种写法,这种写法我们自己手动创建了一个实例,QMessageBox messageBox(...) 先创建一个消息框对象 messageBox。messageBox.exec() 显式地执行这个对话框,并返回我们的选择。
        exec()是QDialog继承的方法,它会阻塞当前函数,直到我们做出选择,exec()的返回值就是我们点击的按钮(QMessageBox::Yes 或 QMessageBox::No);

 QMessageBox messageBox(QMessageBox::NoIcon,"登录","用户和密码验证是否成功?",QMessageBox::Yes|QMessageBox::No,NULL);

    messageBox.setText("验证完成,请确认是否继续");
   int result = messageBox.exec();

   switch(result)
    {
        case QMessageBox::Yes:
            QMessageBox::about(NULL,"提示","你好,你已经点击yes按钮");
            break;
        case QMessageBox::No:
            QMessageBox::about(NULL,"提示","你好,你已经点击no按钮");
            break;
        default:
            break;
    }

        第二种写法,就是前面我们讲解的question的用法,这里将它具体与switch用发结合在了一起

    int result = QMessageBox::question(this,"登录","用户和密码验证是否成功",QMessageBox::Yes|QMessageBox::No,QMessageBox::Yes);

    switch (result) {

        case QMessageBox::Yes:
            QMessageBox::about(this,"提示","你好,你已经点击yes按钮");
        break;
    case QMessageBox::No:
            QMessageBox::about(this,"提示","你好,你已经点击no按钮");
        break;
    default:
        break;

        主要是这两种用法有什么区别,对于QMessageBox::question这种静态函数,就比较适用于我们现在的场景,因为只需要简单的使用yes和no按钮,所以在实际开发中也有个缺点没有扩展性

       相较于自己创建实例的方法,缺点就是较为复杂,但是可以自定义QMessage的属性(比如设置按钮文本、窗口大小等),需要修改UI样式,就是加上额外的控件或者图标,或者需要队QMessage进行更复杂的控制

四. 面向对象编程

        面向对象编程是C++这门语言的核心概念,上面我们也提到过实例或者类等概念,但对于刚开始接触的人确实不好理解,那么,今天就提到的类(class)和实例(instance,或称作Object)展开,进行一定的描述

1. 类(class)—模板或蓝图

        类可以比作一个模板或者蓝图,定义了一类对象的属性和行为,我们举一个实际的例子。例如我们要设计一个Car(汽车)类,就应该有
        属性(成员变量):颜色、品牌    方法(成员函数):启动、加速、刹车,想象我们要造一辆机车,首先就需要一张蓝图,上面规定了颜色、品牌、加速方法等。类就是规则,但并不是实际存在

// 汽车的设计蓝图(类)
class Car {
public:
    // 属性(数据)
    string color;
    string brand;

    // 方法(行为)
void accelerate() {
    std::cout << color << "" << brand << "加速!当前速度:" << ++speed << "km/h" << std::endl;
}

private:
    int speed = 0; // 私有属性,外部无法直接修改
};

2. 实例(Instance 对象Object)——具体的事物

        实例是一个类的一个具体对象,每个对象都基于类的定义创建,并拥有自己的数据。例如上面的Car只是一个模板,但是宝马或者奔驰就是这个Car类的实例
        根据蓝图创建出来的具体汽车就是实例。每辆汽车有独立的数据

int main() {
    // 造两辆真实的汽车(实例)
    Car myCar;      // 我的红色宝马
    myCar.color = "红色";
    myCar.brand = "宝马";

    Car yourCar;    // 你的黑色奥迪
    yourCar.color = "黑色";
    yourCar.brand = "奥迪";

    myCar.accelerate();  // 我的车加速到1km/h
    yourCar.accelerate(); // 你的车也加速到1km/h(两辆车独立)
}

        在这里benz和bmw都是car的实例,它们各自有自己的brand、color、speed,但是都是可以调用start和accelerate方法,目前的效果就如下

3. 构造函数(Constructor)—出厂设置

        目前举例来说就是让汽出厂时自动设置颜色和品牌,避免忘记初始化。构建函数再对象创建时候自动调用,举个例子
        现在我在里面加入了构造函数,在实例当中也可以像上面一样直接初始化,但是这里我们还是引入了构建函数
        因为这样能够确保对象始终有效,直接初始化可能遗漏关键属性,导致对象处于“半完成”状态,加入构造函数会强制在创建时候提供必要参数

#include <iostream>
// 汽车的设计蓝图(类)
class Car {
public:
    // 属性(数据)
    std::string color;
    std::string brand;

    Car(std::string c, std::string b)
    {
        color = c;
        brand = b;
        std::cout << "一辆" << color << brand << "已出厂!" << std::endl;
    }

    // 方法(行为)
    void accelerate() {
        std::cout << color << "" << brand << "加速!当前速度:" << ++speed << "km/h" << std::endl;
    }

private:
    int speed = 0; // 私有属性,外部无法直接修改
};

int main() {
    // 造两辆真实的汽车(实例)
    Car myCar("红色","宝马");      // 我的红色宝马
    
    Car yourCar("黑色","奥迪");    // 你的黑色奥迪

    myCar.accelerate();  // 我的车加速到1km/h
    yourCar.accelerate(); // 你的车也加速到1km/h(两辆车独立)
}

        其次直接初始化需将成员设为public,破坏封装。构造函数可通过private变量隐藏实现的细节,例如
        设置到了后面还可以直接执行计算,验证参数,连接数据库等

class Car {
private:
    int speed; // 外部无法直接修改
public:
    Car(std::string c, std::string b, int s) : speed(s) { ... }
};

// 外部只能通过构造函数初始化speed,避免随意篡改
Car myCar("红色", "宝马", 0); 

        另外一点就是构造函数的名字必须和类名相同,这是语法规定,这样编译器直到它是用来初始化对象的特殊函数,构建函数也没有返回值
        Car是类名(设计蓝图),myCar是变量(实际的汽车),Car(“红色”,"宝马"),我们写Car myCar()编译器自动调用Car(std::string,std::string)构造函数

        构造函数旨在new对象时,调用你写 Car myCar("红色", "宝马");,编译器自动调用 Car(std::string, std::string) 构造函数。类 Car 只是模板,不会被创建实例,而 myCar 这个变量才是真正的对象。

        如果使用普通函数就会重名。例如

class Car {
public:
    void Car() {  //  这是普通函数,不是构造函数(没有返回值的构造函数才行)
        std::cout << "我是一个普通函数,不是构造函数!" << std::endl;
    }
};

        构造函数也是唯一可以和类同名的函数,对于上面我们的实例代码都还可以换种写法,避免color = c这种重复赋值

Car(std::string c, std::string b) : color(c), brand(b) {
    std::cout << "一辆" << color << brand << "已出厂!" << std::endl;
}

4. 继承(Inheritance)--升级车型

        例如电动车继承汽车的基础功能(颜色、加速),同时增加新功能(充电)

class ElectricCar : public Car {
public:
    // 调用父类构造函数初始化基础属性
    ElectricCar(std::string c, std::string b) : Car(c, b) {}

    // 新增功能
    void charge() {
        std::cout << "正在充电..." << std::endl;
    }
};



int main() {

    ElectricCar tesla("蓝色", "特斯拉");
    tesla.accelerate(); // 直接使用父类方法
    tesla.charge();    // 自己新增的方法
}

        可以通过以上这个例子简单的理解继承的机制,ElectricCar 继承 了 Car ,扩展了它的功能,public Car 表示 ElectricCar 继承了 Car 的所有 public 和 protected 成员(但 private 的不继承)。ElectricCar 相当于 “电动车是汽车的一种”(继承就是一种“is-a”关系)

        然后Car(c, b) 这里显式调用 Car 的构造函数,初始化 color 和 brand,这样 ElectricCar 继承 Car 的属性,不需要再手动赋值 color = c; 了
        ElectricCar 没有自己定义 accelerate(),所以它直接使用 Car 的 accelerate() 方法。这是 ElectricCar 独有 的功能,Car 里没有 charge() 方法。 电动车有充电功能,但普通汽车没有,所以 charge() 只在 ElectricCar 里定义。

        继承的作用就是让代码更简洁,ElectricCar 直接复用 Car 的功能,不用重复写 accelerate()、color、brand 等属性。最后的运行结果如下

        那么这个章节博主的分享就到这里

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值