2024年3月

写在开头

面试官:小伙子请聊一聊Java中的精灵线程?
我:什么?精灵线程?啥时候精灵线程?
面试官:精灵线程没听过?那守护线程呢?
我:守护线程知道,就是为普通线程服务的线程嘛。
面试官:没了?守护线程的特点,怎么使用,需要注意啥,Java中经典的守护线程都有啥?
我:不知道。。。
这的天,面试一个10K的工作,上来先整个精灵线程,直接把人整蒙了,难道提及Java多线程的时候,问的不应该是线程、线程池、并发冲突解决方案、如何加锁,以及各种锁的知识点吗?上来整个偏门的守护线程,这是出心的不想要啊。

何为守护线程

上面这段内容是在牛客上看到的,说实话这位面试官问的这内容确实主要一个:东西没用,但你得知道!可如果说他问的真是离谱吗?也算不上,精灵线程我们很少听到,但守护线程我们在学习Java线程的时候肯定有所耳闻!那么今天我们就一起来小酌一下这个

守护线程

Java中的线程分为2种:
用户线程

守护线程

用户线程又叫普通线程,是我们驱动业务逻辑运转的核心;而守护线程,顾名思义,是守护用户线程的一种线程,运行在后台提供通用服务,因此也叫后台线程或者精灵线程。

守护线程的使用场景

那在Java中这个守护线程都有什么实际用处,或者说应用场景呢?

  1. GC垃圾回收线程:这是JVM中非常经典的一个守护线程,它始终以低级别状态运行,用于实时监控和管理系统中的可回收资源,一旦我们的系统没有任何运行的用户线程时,程序也就不会再产生垃圾,这时,无事可做的垃圾回收线程会自动结束。
  2. 应用指标统计:部分服务可以通过守护线程来采取应用指标,服务结束则停止采集。

怎么设置守护线程

那我们在代码中,如何将一个线程设置为守护线程呢?咱们可以通过在 start 线程之前调用线程的 setDaemon(true) 方法,将一个线程设置为守护线程,来看一下下面的这个demo。

【代码实例1】

public class Test {
    public static void main(String[] args) {
        Thread thread1 = new Thread("守护线程"){
            @Override
            public void run() {
                int i = 0;
                while (i <= 4){
                    i++;
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
                super.run();
            }
        };
        Thread thread2 = new Thread("用户线程"){
            @Override
            public void run() {
                int i = 0;
                while (i < 2){
                    i++;
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
                super.run();
            }
        };
        //setDaemon, 不设置则默认false
        thread1.setDaemon(true);//设置thread1为守护线程
        thread2.setDaemon(false);//设置thread2为普通线程
        thread1.start();
        thread2.start();
    }
}

输出:

守护线程:1
用户线程:1
用户线程:2
守护线程:2

这段测试代码中,我们通过thread1.setDaemon(true)将线程1设置成了一个守护线程(false为普通线程),用户线程的循环次数为2,用户线程的循环次数为4,但当程序中的用户线程运行完之后,守护线程并没有继续向下循环,而是随着用户线程的结束而自我终止了。

守护线程的优先级

看到网上很多博文提到了守护线程的优先级问题,都说守护线程的优先级比较低,那我们通过一段测试用例看一下真实情况。

【代码实例2】

public class Test {
    public static void main(String[] args) {
        Thread thread1 = new Thread("守护线程"){
            @Override
            public void run() {
                int i = 0;
                while (i <= 4){
                    i++;
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+i+"-优先级:" +Thread.currentThread().getPriority());
                }
                super.run();
            }
        };
        Thread thread2 = new Thread("用户线程"){
            @Override
            public void run() {
                int i = 0;
                while (i < 2){
                    i++;
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+i+"-优先级:" +Thread.currentThread().getPriority());
                }
                super.run();
            }
        };

        //setDaemon, 不设置则默认false
        thread1.setDaemon(true);//设置thread1为守护线程
        thread2.setDaemon(false);//设置thread2为普通线程

        thread1.start();
        thread2.start();

        for (int i = 0; i <5 ; i++) {
            System.out.println("主线程:"+i+"-优先级:" +Thread.currentThread().getPriority());
        }
    }
}

输出:

主线程:0-优先级:5
主线程:1-优先级:5
主线程:2-优先级:5
主线程:3-优先级:5
主线程:4-优先级:5
用户线程:1-优先级:5
守护线程:1-优先级:5
用户线程:2-优先级:5
守护线程:2-优先级:5

这个测试结果是不是出乎意料?无论是主线程还是普通的用户线程,又或者说守护线程,他们的优先级都是5,优先级竟然都一样!

我们知道所谓的线程就是CPU 调度和分派的基本单位,根据优先级不同,来决定获取CPU时间片的先后顺序,因为主线程启动时,其他线程还没有启动,所以这时候它最先获得CPU调度权限;

又因为其他线程存在休眠时间,这个时间段上足够主线程执行完毕。主线程执行完后,用户线程和守护线程互相抢占CPU资源,交错执行,直至程序中没有普通线程为止!若没有休眠时间,且循环次数足够多时,我们可以看到主线程、守护线程、用户线程都竞争CPU时间片,呈现交错执行的结果!

注意事项

在设置线程为守护线程的时候要注意一个事情,那就是当 start(); 放到 setDaemon(true); 之前,程序抛出IllegalThreadStateException。如下图:
image

原因是 setDaemon(true)源码中,有一个isAlive()的判断,判断当前线程的状态是否为活跃线程,若是则抛出异常,我们不能修改一个正在运行中的线程!

【源码解析1】

  public final void setDaemon(boolean on) {
        checkAccess();
        //线程已经启动后,不可修改,否则抛出非法线程状态异常
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }

总结

OK,写到这里,关于守护线程的内容就讲完了,我们从什么是守护线程,守护线程的使用场景,优先级,注意事项等方面,进行了全面的介绍。

其实说实话,在我们日后工作中,很少直接使用上守护线程,所以它看似没那么重要,但在很多Java多线程相关的书籍中绝对都有提及,很多小伙伴在学习的过程中认为这个点不重要,也就相当然的忽略了,但遇到变态的面试官,专门挑拣一些偏僻的知识点考你时,难免陷入尴尬,所以希望借助这个考题,大家能够在日后更细心的学习哈。

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得
留言+点赞+收藏
呀。原创不易,转载请联系Build哥!

image

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

image

--------------------------------------    编程能是大模型应用的天花板..................................................................

所以要好好将大模型应用在企业一定要好好练好最看不起的一环,基础能力
字符串处理  本文档来自老男孩培训Alex 课程记录,

我在2017年听过这个老师的课,非常不错,所以在写Ai 应用开发的时候,在基础部份会参考高手的成果

name = "my name is alex"

pring(name.caitalize())  首字母大写

pring(name.count("my"))  统计字符串出现的次数,可以是单字或多词

print(name.casefold())
这个说是转换写成小写,还会对特殊字符处理,一处理就可能不是我们想要的了,况且还有更好的相同功能的方法,所以很少少用,用不着

pring(name.center(50,"_")) 居中打印,打印50个字符,不够用——补位

print(name.ljust(50,"***"))  左边对齐,右边的用*** 代替,补齐50位

print(name.rjust(50,'*')) 右对齐,不多的补**

print("A".lower())   大写转小写    "upp".upper()  小写转大写

"\nAlex\n".lstrip() 左边去空格   rstip()  去掉右边  strip() 去掉空格

pring(name.encode)  编码相关,将二进制编成字节

pring(name.endswith("alex"))  是否是这个结尾 返回true 或 false

pring(name.expandtabs(tabsize = 30)) 字符串中出现的\t  等于多少个空格
pring.find("y")  找到字符串的索引取出来,用于切片时候用   那么。name[name.find("name") :])
pring(name.format(name="alex",year = 33))  格式化字符串,将字符串中的{name} 变量替换
print(name.format_map({}) 可以传一下字字賟 来格式化字符串,通常format 就够用了

pring(name.isalnum)  是不是阿拉伯数字

print (''ssss".isalpha()) 是不是纯英文

print("1A".isdecimal)  是不是只包括十进制

print("1A".isdigit) 是不是整数

title(),将字母的首字母变大写

translate 映射
majetrabs   这二个方法一起使用,先用majetrabs 创建一个射映表,然后translate 转换

# 创建字符映射表
mapping = str.maketrans("aeiou", "12345")

# 使用translate()方法进行字符替换
string = "Hello, World!"
translated_string = string.translate(mapping)

print(translated_string)    输出结果是   H2ll4, W4rld!   
解释  先给aeiou  映一个其它字符,然后在使用中遇到这几个字符就替换成映射字符,比较好理解

replace   替换

print("xsy".isidentifier())  是不是一个合法的变量名。支持中文英文,但是不能数字开头

print("33.33".isnumeric())是不是只有数字

"my".isprintable 是不是包括可打针印的,其实就是说这个字符中是不是非输出的法字符,说是说\n  \t 这样的字符,有制表符会返false

istitle()  是不是每个单词都 是首字母大写,单词之间有空格

join     '+++'.join(['a','b','c'])   将列表的元素用什么点连起来。

split()  将字符串切成列表

string = "Hello, World! How are you?"
words = string.split()
print(words)   //输出结果   ['Hello,', 'World!', 'How', 'are', 'you?']  

字典操作

可以将每个人的信息保存起来,比如省市县的关联信息,一个人信息这些都 可以用字典来保存,
info = {"姓名":"张三"}} 有索引,有信息的元素叫做字典,

编程中叫有key和valu由于是key 是唯 一的,所以字母无序,

假设一个字典是这样子

info = {
    "stu1101": "TengLan",
    "stu1102": "Loula",
    "stu1103": "Maliy"
}

name = info["stu1101"]
print(name)  # 输出: TengLan
打印这个字典方式是: for key, value in info.items(): print(key, value)

取出一个值:
print(info["stu1101"])
查找
"stu1101" in info //True
info["stu11011"] 查找这个值,但是这个值如果没有会报错,用get不会错,info.get("stu11011")

修改一个值
info["stu1101"] = "武腾兰"

删除
del info["11011"] 删除
info.pop("stu11011")
info.popitem()随便删一个
更新整个字典,可以用一个新字典更新,相同dey 覆盖,不同的增加 info.update(b)
将一个例表转换为列表 info.item()
info.formdeys([6,7,9])创建一个新字典,key 是6,7,8
c = dict.fromkeys([6, 7, 8], [1, {"name": "alex"}, 444])
c = dict.fromkeys([6, 7, 8], [1, {"name": "alex"}, 444])
运行结果是:
{
    6: [1, {"name": "alex"}, 444],
    7: [1, {"name": "alex"}, 444],
    8: [1, {"name": "alex"}, 444]
}

以下代码是说dict.fromkeys()如果用以下方式创建的话,可以value 是一个引用的值,以后修改其中一个值,会影起其它值改变,

在这个代码段中,​c​ 是通过 ​dict.fromkeys()​ 方法创建的字典,该方法的作用是使用指定的键列表创建一个新字典,其中所有的值默认为 None 或者指定的默认值。

c = dict.fromkeys([6, 7, 8], [1, {"name": "alex"}, 444])
c = dict.fromkeys([6, 7, 8], [1, {"name": "alex"}, 444])
​
运行这段代码后,​c​ 的内容将是:

{
    6: [1, {"name": "alex"}, 444],
    7: [1, {"name": "alex"}, 444],
    8: [1, {"name": "alex"}, 444]
}
{
    6: [1, {"name": "alex"}, 444],
    7: [1, {"name": "alex"}, 444],
    8: [1, {"name": "alex"}, 444]
}
​
注意,在这个操作中,由于 ​dict.fromkeys()​ 方法创建的字典中每个键都引用同一个列表对象 ​[1, {"name": "alex"}, 444]​,因此当修改其中一个键对应的列表时,其他键对应的列表也会随之改变。这是因为它们实际上引用的是同一个对象。

如果希望每个键引用不同的列表对象,可以考虑使用以下方式创建 ​c​:

c = {key: [1, {"name": "alex"}, 444] for key in [6, 7, 8]}
c = {key: [1, {"name": "alex"}, 444] for key in [6, 7, 8]}
​
这样每个键的值都是一个新的列表对象,而不是共享同一个列表对象,指定的值实际上是同一个对象的引用。当您修改其中一个键对应的值时,因为其他键对应的值与之相同,所以会看到它们也发生了变化。

如果您希望每个键引用不同的列表对象,可以使用以下方式创建 ​c​:

keys = [6, 7, 8]
values = [1, {"name": "alex"}, 444]
c = {key: list(value) for key in keys}
keys = [6, 7, 8]
values = [1, {"name": "alex"}, 444]
c = {key: list(value) for key in keys}
​
这样每个键的值都是一个新的列表对象,而不是共享同一个列表对象。

以下是修正后的代码示例:

keys = [6, 7, 8]
values = [1, {"name": "alex"}, 444]
c = {key: list(value) for key in keys}
print(c)

c[7][1]['name'] = "Jack Chen"
print(c)  # 输出: {6: [1, {'name': 'alex'}, 444], 7: [1, {'name': 'Jack Chen'}, 444], 8: [1, {'name': 'alex'}, 444]}

keys = [6, 7, 8]
values = [1, {"name": "alex"}, 444]
c = {key: list(value) for key in keys}
print(c)

c[7][1]['name'] = "Jack Chen"
print(c)  # 输出: {6: [1, {'name': 'alex'}, 444], 7: [1, {'name': 'Jack Chen'}, 444], 8: [1, {'name': 'alex'}, 444]}

案例,叫三级查询,程序运行时,先列出要查询省份名称,然后用户输出省的名称和编号进行相询,如果输入某个省的编号或省份名称,系统就列表这个省下面所有市的序号和名称 ,然后提示请输入你要查那个市 ,列出市下面所有县的编号和名称,用户可以查某一个市所有的县。在任意菜单都 可以按Q退程序,按E 返回一上级。如果没有上级则不显示,返回一级。

data = {
    "1": {"name": "浙江省", "cities": {
        "1": {"name": "杭州市", "counties": {
            "1": "西湖区",
            "2": "上城区",
            "3": "下城区"
        }},
        "2": {"name": "温州市", "counties": {
            "1": "鹿城区",
            "2": "龙湾区",
            "3": "瓯海区"
        }}
    }},
    "2": {"name": "江苏省", "cities": {
        "1": {"name": "南京市", "counties": {
            "1": "玄武区",
            "2": "白下区",
            "3": "秦淮区"
        }},
        "2": {"name": "苏州市", "counties": {
            "1": "姑苏区",
            "2": "虎丘区",
            "3": "吴中区"
        }}
    }}
}

# 查询数据的函数,根据级别和代码查询省、市、县信息
def query_data(data, level, code=None):
    # 无限循环,直到用户输入"q"退出程序
    while True:
        # 如果有传入代码,则根据代码获取对应数据,否则使用初始数据
        if code:
            current_data = data[code]
        else:
            current_data = data

        # 打印提示用户选择省、市或县
        print("请选择{}:".format(level))
        # 遍历当前数据的键值对,打印编号和名称
        for key, value in current_data.items():
            print("{}: {}".format(key, value["name"]))
        
        # 接收用户输入,可以是编号、名称,"q"退出,或者"e"返回上一级
        user_input = input("请输入编号或名称,按Q退出,按E返回上一级:")
        
        # 如果用户输入为"q",退出程序
        if user_input.lower() == "q":
            break
        # 如果用户输入为"e"且当前级别不是省,返回上一级
        elif user_input.lower() == "e" and level != "省":
            return
        # 如果用户输入在当前数据中
        elif user_input in current_data:
            # 如果当前级别是省,调用query_data函数查询市的数据
            if level == "省":
                query_data(current_data[user_input]["cities"], "市", "cities")
            # 如果当前级别是市,调用query_data函数查询县的数据
            elif level == "市":
                query_data(current_data[user_input]["counties"], "县", "counties")

# 开始进行省份查询
query_data(data, "省")




位域

一、基本概念

1.1 位域的概念

结构体中的冒号表示位域,如:

struct bit_struct
{
    unsigned int  bit1:10;
    unsigned int  bit2:8;
    unsigned int  bit3:14;
} data1; // sizeof data1 = 4

struct bit_struct
{
    unsigned int  bit1:10;
    unsigned int  bit2:8;
    unsigned int  bit3:16;
} data2; // sizeof data2 = 8

位域出现的原因是由于某些信息的「存储表示」只需要几个 bit 位就可以表示,而不需要一个完整的字节,同时也是为了节省存储空间和方便处理。

1.2 说明

  1. 位域必须存储在同一个类型中,不能跨类型,同时也说明位域的长度不会超过所定义类型的长度。如果一个定义类型单元里所剩空间无法存放下一个域,则下一个域应该从下一单元开始存放。
    如 data2,所定义的类型是
    unsigned int
    类型,一共 32 位,bit1 和 bit2 用掉了 18bit,还剩下
    \(32-18=14bit\)
    ,这时要存储一个 16bit 的位域元素 bit3,那么这个元素就只能从下一个
    unsigned int
    类型的单元开始而不会在前面一个
    unsigned int
    类型中占 14bit 后面的 unsigned int 类型中占 2bit。

  2. 如果位域的位域长度为0表示是个空域,同时下一个域应当从下一个字节单元开始存放。

  3. 使用无名的位域来作为填充和调整位置,切记该位域是不能被使用的。

  4. 位域的本质上就是一种结构体类型,不同的是其成员是按二进制位来分配的。

二、代码理解

#include <stdio.h>
#include <string.h>

struct bit_struct_1
{
    unsigned int  bit1:10;
    unsigned int  bit2:8;
    unsigned int  bit3:14;
} data1; // sizeof data1 = 4

struct bit_struct_2
{
    unsigned int  bit1:10;
    unsigned int  bit2:8;
    unsigned int  bit3:16;
} data2; // sizeof data2 = 8


/*
 0001 1001 0010 1010 1011 0011 0100 6666661 0101 1101 0110 1100 0666666 6666660 1000 1011
 ————————— ————————— ————————— ————————— ————————— ————————— ————————— —————————
    19        2A        B3        4F         5D        6C       8E        9B
    高 <-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·- 低
*/
int main()
{
    unsigned long long ullNum = 0x192AB34F5D6C7E8B;
    memcpy(&data1, (void *)&ullNum, sizeof(data1));
    memcpy(&data2, (void *)&ullNum, sizeof(data2));

    printf("data1 size is %d\n", sizeof(data1));  /* size is 4 */
    printf("[1] bit1 : %u\n", data1.bit1);          /* 651  --> 1010001011 */
    printf("[1] bit2 : %u\n", data1.bit2);          /* 31   --> 00066666611 */
    printf("[1] bit3 : %u\n", data1.bit3);          /* 5979 --> 01066666601011011 */

    puts("------------------------");
    printf("data2 size is %d\n", sizeof(data2));  /* size is 8 */
    printf("[2] bit1 : %u\n", data2.bit1);          /* 651   --> 1010001011 */
    printf("[2] bit2 : %u\n", data2.bit2);          /* 31    --> 00066666611 */
    printf("[2] bit3 : %u\n", data2.bit3);          /* 45903 --> 1011001101006666661 */

    return 0;
}

上述代码中我们定义了一个 8B 的 ullNum,其二进制表示如下图所示:

image-20240309161334183

从低字节到高字节分别分配给 bit1、bit2、bit3 :

image-20240309162313180

参考资料

前言

今天大姚给大家分享一款由WPF开源的、免费的(MIT License)、即开即用、即用即走的翻译、OCR工具:STranslate。

WPF介绍

WPF 是一个强大的桌面应用程序框架,用于构建具有丰富用户界面的 Windows 应用。它提供了灵活的布局、数据绑定、样式和模板、动画效果等功能,让开发者可以创建出吸引人且交互性强的应用程序。

工具快捷键

全局快捷键

可自行修改

  • Alt
    +
    A
    :打开软件界面,输入内容按回车翻译
  • Alt
    +
    D
    :复制当前鼠标选中内容并翻译
  • Alt
    +
    S
    :截图选中区域内容并翻译
  • Alt
    +
    G
    :打开主界面
  • Alt
    +
    Shift
    +
    D
    :打开监听鼠标划词,鼠标滑动选中文字立即翻译
  • Alt
    +
    Shift
    +
    S
    :完全离线文字识别(基于PaddleOCR)
  • Alt
    +
    Shift
    +
    F
    :静默OCR(OCR后自动复制到剪贴板)

软件内快捷键

不可修改

  • ESC
    隐藏界面(含取消请求)
  • Ctrl
    +
    ,
    打开设置
  • Ctrl
    +
    Shift
    +
    A
    隐藏/显示输入框
  • Ctrl
    +
    Shift
    +
    Q
    退出程序
  • Ctrl
    +
    Shift
    +
    T
    置顶/取消置顶
  • Ctrl
    +
    滚轮上
    放大文字
  • Ctrl
    +
    滚轮下
    缩小文字
  • Ctrl
    +
    `
    恢复默认文字大小
  • Ctrl
    +
    +
    放大界面(宽度、最大高度)
  • Ctrl
    +
    -
    缩小界面(宽度、最大高度)
  • Ctrl
    +
    0
    界面恢复配置大小(宽度、最大高度)
  • Ctrl
    +
    Alt
    +
    +
    宽度增加
  • Ctrl
    +
    Alt
    +
    -
    宽度减少
  • Ctrl
    +
    Shift
    +
    +
    最大高度增加
  • Ctrl
    +
    Shift
    +
    -
    最大高度减少
  • Ctrl
    +
    1...8
    按顺序复制翻译服务结果
  • Ctrl
    +
    9
    复制最后一个翻译服务结果

工具源代码运行

设置
STranslate
为启动项目运行:

工具下载

Github下载

Gitee下载

工具部分功能截图

项目源码地址



更多项目实用功能和特性欢迎前往项目开源地址查看

Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用
QHostInfo
组件实现对主机地址查询功能。

在Qt网络编程中,QHostInfo是一个强大而灵活的组件,用于获取有关主机的信息,包括主机名、IP地址和域名解析等。通过支持异步查询的机制,它能够在后台获取主机信息,避免阻塞主线程,同时通过信号-槽机制提供查询结果。其多主机查询、可靠的错误处理和与网络环境的适应性,使其成为处理网络应用中主机信息获取的理想选择。

以下是
QHostInfo
类的一些常用函数的解释:

函数 描述
QHostInfo() 默认构造函数,创建一个空的
QHostInfo
对象。
QHostInfo(const QHostInfo &other) 拷贝构造函数,根据给定的
other
对象创建一个新的对象。
QHostInfo &operator=(const QHostInfo &other) 赋值运算符,将
other
对象的值赋给当前对象。
QHostInfo &swap(QHostInfo &other) 交换两个
QHostInfo
对象的值。
bool isNull() const 判断
QHostInfo
对象是否为空,即未进行任何查询。
bool isComplete() const 判断查询是否完成,返回
true
表示查询已完成,
false
表示正在进行中。
QList<QHostAddress> addresses() const 返回与主机相关联的IP地址列表。
QString hostName() const 返回主机的名称。
QStringList aliases() const 返回主机的别名列表。
QHostInfo::Error error() const 返回查询时发生的错误。
QString errorString() const 返回与错误代码对应的人类可读的错误字符串。
static QHostInfo fromName(const QString &name) 根据主机名创建
QHostInfo
对象。
static QHostInfo fromAddress(const QHostAddress &address) 根据IP地址创建
QHostInfo
对象。
static QHostInfo localHostName() 返回本地主机的
QHostInfo
对象。
void clear() 清空
QHostInfo
对象,重置为初始状态。
static void swap(QHostInfo &first, QHostInfo &second) 交换两个
QHostInfo
对象的值。

这些函数提供了对主机信息的查询、获取和处理的操作。需要注意,很多函数都是通过异步查询的方式获取主机信息的,因此在使用时需要通过信号-槽机制来获取查询结果。

在使用这个模块时,要确保导入
QT+=network
模块,接着来看该如何实现查询本机IP地址,通过调用
QHostInfo::localHostName
可以直接获取到本机的主机名,调用
QHostInfo::fromName(hostName)
可将该主机名转换为对应的
HostInfo
结构,当具备了这个结构体以后,就可以通过循环遍历
addList.count()
内的所有记录,并
aHost.toString()
输出所有的IP地址表,代码如下所示;

// 查询本机IP地址
void MainWindow::on_pushButton_clicked()
{
    // 本地主机名
    QString hostName=QHostInfo::localHostName();
    std::cout << hostName.toStdString() << std::endl;
    ui->lineEdit->setText(hostName);

    // 查询主机IP地址信息
    QHostInfo hostInfo=QHostInfo::fromName(hostName);

    QList<QHostAddress> addList=hostInfo.addresses();
    if (!addList.isEmpty())
    for (int i=0;i<addList.count();i++)
    {
        // 每一项是一个QHostAddress
        QHostAddress aHost=addList.at(i);

        // 判断是否为IPV4
        if(QAbstractSocket::IPv4Protocol==aHost.protocol())
        {
            ui->listWidget->addItem("IPV4 | " + aHost.toString());
        }
        else
        {
            ui->listWidget->addItem("IPV6 | " + aHost.toString());
        }
    }
}

运行后读者可通过点击查询数据按钮实现对本机IP地址的获取,输出效果如下图所示;

QHostInfo组件既可以查询自身IP地址信息,也可以实现对特定域名的IP解析,通过使用
QHostInfo::lookupHost
则可以实现查询特定主机的地址信息,该函数需要传入一个回调,如下所示我们在回调函数内查询主机所有的IP地址并输出,其实现原理与上述方法相同。

void MainWindow::lookedUpHostInfo(const QHostInfo &host)
{
    // 每一项是一个QHostAddress
    QList<QHostAddress> addList=host.addresses();
    if (!addList.isEmpty())
    for (int i=0;i<addList.count();i++)
    {
        QHostAddress aHost=addList.at(i);

        // 判断是否为IPV4
        if(QAbstractSocket::IPv4Protocol==aHost.protocol())
        {
            ui->listWidget_2->addItem("IPV4 | " + aHost.toString());
        }
        else
        {
            ui->listWidget_2->addItem("IPV6 | " + aHost.toString());
        }
    }
}

在查询时只需要通过
lookupHost
调用即可,如下代码所示;

void MainWindow::on_pushButton_2_clicked()
{
    // 主机名
    QString hostname=ui->lineEdit_2->text();
    QHostInfo::lookupHost(hostname,this,SLOT(lookedUpHostInfo(QHostInfo)));
}

运行后读者可通过点击查询数据按钮实现对特定域名的IP地址获取,输出效果如下图所示;