《Kotlin核心编程》笔记:反射、注解和加锁

Kotlin 和 Java 反射

在这里插入图片描述
在这里插入图片描述

  • 1)Kotlin 的 KClass 和 Java 的 Class 可以看作同一个含义的类型,并且可以通过.java.kotlin方法在KClassClass之间互相转化。
  • 2)Kotlin 的 KCallable 和 Java 的 AccessiableObject 都可以理解为可调用元素。Java 中构造方法为一个独立的类型,而 Kotlin 则统一作为 KFunction 处理。
  • 3)Kotlin 的 KProperty 和 Java 的 Field 不太相同。Kotlin 的KProperty通常指相应的GetterSetter (只有可变属性Setter)整体作为一个KProperty(通常情况 Kotlin 并不存在字段的概念),而 Java 的 Field 通常仅仅指字段本身。

在某些情况下(通常是碰到一些Kotlin独有的特性时)Kotlin编译器会在生产的字节码中存储额外信息,这些信息目前是通过 kotlin.Metadata 解实现的。Kotlin 编译器会用 Metadata 标注这些类。

Kotlin 的 KClass

KClass的特别属性或者函数(在Kotlin中独有,Java没有与之对应的特性):

属性或函数名称含义
isCompanion是否伴生对象
isData是否数据类
isSealed是否密封类
objectInstanceobject实例(如果是object
companionObjectInstance伴生对象实例
declaredMemberExtensionFunctions扩展函数(声明的)
declaredMemberExtensionProperties扩展属性(声明的)
memberExtensionFunctions本类及超类扩展函数
memberExtensionProperties本类及超类扩展属性
starProjectedType泛型通配类型

Kotlin 的 KCallable

Kotlin 把 Class 中的属性(Property)、函数(Funciton)甚至构造函数都看作 KCallable,因为它们是可调用的,它们都是Class的成员。那我们如何获取一个Class的成员呢?

KClass给我们提供了一个members方法,它的返回值就是一个Collection<KCallable<*>>

KCallable提供的 API:

API 描述含义
isAbstract: BooleanKCallable 是否为抽象的
isFinal: BooleanKCallable 是否为 final
isOpen: BooleanKCallable 是否为 open
name: StringKCallable 的名称
parameters: List<KParameter>调用此 KCallable 需要的参数
returnType: KTypeKCallable 的返回类型
typeParameters: List<KTypeParameter>KCallable 的类型参数
visibility: KVisibility?KCallable 的可见性
call(vararg args: Any?): R给定参数调用此 KCallable

KMutablePropertyKProperty的一个子类,那我们如何识别一个属性是KMutableProperty还是KProperty呢?参考如下代码:

fun KMutablePropertyShow() {
    val p = Person("张三", 8, "HangZhou")
    val props = p::class.memberProperties
    for (prop in props) {
        when (prop) {
            is KMutableProperty<*> -> prop.setter.call(p, "Hefei")
            else -> prop.call(p)
        }
    }
    println(p.address)
}

获取参数信息

Kotlin 把参数分为3个类别,分别是函数的参数(KParameter)、函数的返回值(KType)及类型参数(KTypeParameter)。

KParameter

使用KCallabel.parameters即可获取一个List<KParameter>,它代表的是函数(包括扩展函数)的参数。

API 描述含义
index: Int返回该参数在参数列表里面的索引
isOptional: Boolean该参数是否为 Optional
isVararg: Boolean该参数是否为 vararg
kind: Kind该参数的 Kind
name: String?该参数的名称
type: KType该参数的类型
fun KParameterShow() {
    // val p = Person("张三", 8, "HangZhou")
    for (c in Person::class.members) {
        print("${c.name} -> ")
        for (p in c.parameters) {
            print("${p.type}" + " -- ")
        }
        println()
    }
}

运行结果:

address -> Person 
name -> Person
detailAddress -> Person,kotlin.String 
isChild -> Person
equals -> kotlin.Any,kotlin.Any? 
hashCode -> kotlin.Any
toString -> kotlin.Any

通过上面的运行结果我们发现,对于属性和无参数的函数,它们都有一个隐藏的参数为类的实例,而对于声明参数的函数,类的实例作为第 1 个参数,而声明的参数作为后续的参数。对于那些从Any继承过来的参数,Kotlin 默认它们的第 1 个参数为Any

KType

每一个KCallabe都可以使用returnType来获取返回值类型,它的结果类型是一个KType,代表着Kotlin中的类型。

API 描述含义
arguments: List<KTypeProjection>该类型的类型参数
classifier: KClassifier?得到结果为 List(忽略类型参数)的类型
isMarkedNullable: Boolean该类型是否标记为可空类型

classifier API其实就是获取该参数在类层面对应的类型, 如 Int -> class kotlin.IntList<String> -> class kotlin.collections.List

KTypeParameter

KClassKCallable中我们可以通过typeParameters来获取classcallable的类型参数,它返回的结果集是List<KTypeParameter>,不存在类型参数时就返回一个空的List

fun <A> get(a: A) : A { 
	return a
}

然后我们可以使用下面的代码来获取get方法和List<String>的类型参数:

fun KTypeParameterShow() {
    for (c in Person::class.members) {
        if (c.name.equals("get")) {
            println(c.typeParameters)
        }
    }
    
    val list = listOf<String>("How")
    println(list::class.typeParameters)
}

运行结果:

[A]
[E]

Kotlin 的注解

前面我们提及过注解 kotlin.Metadata,这是实现 Kotlin 大部分独特特性反射的关键,Kotlin 将这些信息直接以注解形式存储在字节码文件中,以便运行时反射可以获取这些数据。

由于 Kotlin 兼容 Java,所以所有 Java 可以添加注解的地方,Kotlin 也都可以。并且 Kotlin 也简化了注解创建语法,创建注解就像创建 class 一样简单,只需额外在 class 前增加 annotation 关键字即可。

annotation class FooAnnotation(val bar: String) 

上面的代码就直接创建了FooAnnotation注解,和创建其他 Kotlin 的类一样,正如前文所说,只要在前面加上annotation,这个类就变成了注解,和等价的 Java 代码相比较,确实简化了很多。

同时和 Java 一样,注解的参数只能是常量,并且仅支持下列类型:

  • Java 对应的基本类型;
  • 字符串;
  • Class 对象(KClass或者Java的Class);
  • 其他注解;
  • 上述类型数组。注意基本类型数组需要指定为对应的XXXArray,例如IntArray,⽽不是Array<Int>

元注解

类似@Target这样标注在注解上的注解我们称之为元注解。我们知道 Java 中的java.lang.annotation 包中定义了下列 5 个元注解:

  • @Documented文档(通常是API文档)中必须出现该注解。
  • @Inherited如果超类标注了该类型,那么其子类型也将自动标注该注解而无须指定。
  • @Repeatable这个注解在同一位置可以出现多次。
  • @Retention表示注解用途,有3种取值。
    • Source。仅在源代码中存在,编译后class文件中不包含该注解信息。
    • CLASSclass文件中存在该注解,但不能被反射读取。
    • RUNTIME。注解信息同样保存在class文件中并且可以在运行时通过反射获取。
  • @Target表明注解可应用于何处。

和 Java 一样在 Kotlin 中也有对应的元注解类。Kotlin 中的元注解类定义在 kotlin.annotation 包下,主要有:

KotlinJava含义
@Retention@Retention注解的保留期
@Target@Target注解可用于哪些目标对象
@MustBeDocumented@Documented注解将被文档工具提取到API文档中
@Repeatable@Repeatable注解可以多次应用于相同的声明或类型

注意到,相比 Java 中5种元注解少了 @Inherited,Kotlin 目前不支持 Inherited,理论上实现继承没有很大难度,但当前版本还不支持。

通过上面对比我们发现,Kotlin 和 Java 注解整体上是保持一致的,熟悉 Java 注解的读者应该很容易将这部分知识迁移到 Kotlin。

@Target

@Target顾名思义就是目标对象,也就是我们定义的注解能够应用于哪些目标对象,可以同时指定多个作用的目标对象。

@Target的原型:

@Target(AnnotationTarget.ANNOTATION_CLASS)
@MustBeDocumented
public annotation class Target(vararg val allowedTargets : AnnotationTarget) 

@Target的原型中我们可以看出,它接受一个vararg可变数量的参数,所以可以同时指定多个作用的目标对象,并且参数类型限定为 AnnotationTarget

@Retention

@Retention 我们可以理解为保留期,和 Java 一样 Kotlin 有三种时期:源代码时期(SOURCE)、编译时期(BINARY)、运行时期(RUNTIME)。

@Retention的原型:

@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Retention(val value : AnnotationRetention = AnnotationRetention.RUNTIME) 

Retention 接收一个AnnotationRetention类型的参数,该参数有个默认值,默认是保留在运行时期AnnotationRetention 是一个枚举类,其定义如下:

public enum class AnnotationRetention {
	// Annotation isn't stored in binary output 
	SOURCE,
	// Annotation is stored in binary output, but invisible for reflection 
	BINARY,
	// Annotation is stored in binary output and visible for reflection (default retention) 
	RUNTIME
}

基本上对应了 Java 的三种类型,只不过 Kotlin 中默认值是 RUNTIME

AnnotationTarget

前面提到在@Target元注解中可以同时指定一个或多个目标对象,那么到底有哪些目标对象呢?接下来让我们看一下:

Kotlin (Annotation Target)Java (Target)说明
CLASSTYPE作用于类
ANNOTATION_CLASSANNOTATION_TYPE作用于注解本身(即元注解)
TYPE_PARAMETERTYPE_PARAMETER作用于类型参数
PROPERTYN/A作用于属性
FIELDFIELD作用于字段(属性通常包含字段 Getter 以及 Setter
LOCAL_VARIABLEFIELD作用于局部变量
VALUE_PARAMETERN/A作用于 val 参数
CONSTRUCTORCONSTRUCTOR作用于构造函数
FUNCTIONMETHOD作用于函数(Java只有Method
PROPERTY_GETTERN/A作用于 Getter
PROPERTY_SETTERN/A作用于 Setter
TYPETYPE_USE作用于类型
EXPRESSIONN/A作用于表达式
FILEPACKAGE作用于文件开头/包声明(两者有细微区别)
TYPEALIASN/A作用于类型别名

Kotlin支持几乎所有Java支持的标注的位置,并且增加了一些kotlin独有的位置。

一个简单Kotlin注解使用的例子:

annotation class Cache(val namespace: String, val expires: Int)
annotation class CacheKey(val keyName: String, val buckets: IntArray)

@Cache(namespace = "hero", expires = 3600)
data class Hero(
    @CacheKey(keyName = "heroName", buckets = intArrayOf(1,2,3))
    val name: String,
    val attack: Int,
    val defense: Int,
    val initHp: Int
)

Kotlin的代码常常会表达多重含义。例如,上述例子中的name除了生成了一个不可变的字段之外,实际上还包含了Getter,同时又是其构造函数的一个参数。

这就带来一个问题,@CacheKey注解究竟是作用于何处?

精确控制注解的位置

为了解决这个问题,Kotlin 引入精确的注解控制语法,假如我们有注解 annotation class CacheKey

用法含义
@file:CacheKeyCacheKey 注解作用于文件
@property:CacheKeyCacheKey 注解作用于属性
@field:CacheKeyCacheKey 注解作用于字段
@get:CacheKeyCacheKey 注解作用于 Getter
@set:CacheKeyCacheKey 注解作用于 Setter
@receiver:CacheKeyCacheKey 注解作用于扩展函数或属性
@param:CacheKeyCacheKey 注解作用于构造函数参数
@setparam:CacheKeyCacheKey 注解作用 Setter 的参数
@delegate:CacheKeyCacheKey 注解作用于存储代理实例的字段

例如:

@Cache(namespace = "hero", expires = 3600)
data class Hero(
    @property:CacheKey(keyName = "heroName", buckets = [1, 2])
    val name: String,

    @field:CacheKey(keyName = "atk", buckets = [1, 2, 3])
    val attack: Int,
        @get:CacheKey(keyName = "def", buckets = [1, 2, 3])

    val defense: Int,
    val initHp: Int
)

上述CacheKey分别作用在属性、字段和Getter上。

反射获取注解信息

这有一个前提就是这个注解的Retentaion标注为Runtime或者没有显示指定(注默认为Runtime)。

annotation class Cache(val namespace: String, val expires: Int)
annotation class CacheKey(val keyName: String, val buckets: IntArray)

@Cache(namespace = "hero", expires = 3600)
data class Hero(
    @CacheKey(keyName = "heroName", buckets = [1, 2, 3])
    val name: String,
    val attack: Int,
    val defense: Int,
    val initHp: Int
)

fun main() {
    val cacheAnnotation = Hero::class.annotations.find{ it is Cache } as Cache?
    println("namespace ${cacheAnnotation?.namespace}")
    println("expires ${cacheAnnotation?.expires}")
}

通过反射获取注解信息是在运行时发生的,和Java一样存在一定的性能开销,当然这种开销大部分时候可以忽略不计。此外前面提到的注解标注位置也会影响注解信息的获取。例如@file:CacheKey这样标注的注解,则无法通调用KProperty.annotions获取到该注解信息。

注解的使用场景

  • 提供信息给编译器:编译器可以利用注解来处理一些,比如一些警告信息,错误等
  • 编译阶段时处理:利用注解信息来生成一些代码,在 Kotlin 生成代码非常常见,一些内置的注解为了与 Java API 的互操作性,往往借助注解在编译阶段生成一些额外的代码
  • 运行时处理:某些注解可以在程序运行时,通过反射机制获取注解信息来处理一些程序逻辑

下面是一个通过注解来标注Http请求方法的代码示例:

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class HttpMethod(val method : Method) 

interface Api {
    val name: String
    val version: String
        get() = "1.0"
}

@HttpMethod(Method.POST)
class ApiGetArticles : Api {
    override val name: String
        get() = "/api.articles"
}

fun fire(api: Api) {
    val annotations = api.javaClass.annotations
    val method = annotations.find { it is HttpMethod } as? HttpMethod
    println("通过注解得知该接口需需要通过:${method?.method}方式请求")
}

我们知道著名的网络请求库 Retrofit 就是通过这种方式来标注接口请求的方法、路径、参数等信息的。

加锁

虽然 Kotlin 是基于 Java 改良过来的语言,但是它没有 synchronized 关键字,取而代之,它使用了@Synchronized注解和synchronized()函数来实现等同的效果。比如:

class Shop {
    val goods = hashMapOf<Long,Int>()
    init {
        goods.put(1,10)
        goods.put(2,15)
    }
    @Synchronized
    fun buyGoods(id: Long) {
        val stock = goods.getValue(id)
        goods.put(id, stock - 1)
    }
    fun buyGoods2(id: Long) {
        synchronized(this) {
            val stock = goods.getValue(id)
            goods.put(id, stock - 1)
        }
    }
}

注意这里的synchronized(this)是 kotlin 中的方法,而非 java 中的 synchronized 关键字。

Kotlin 除了支持 Java 中 synchronized 这种并发原语外,也同样支持其他一些并发工具,比如 volatile 关键字,java.util.concurrent.*下面的并发工具。当然,Kotlin 也做了一些改造,比如 volatile 关键字在 Kotlin 中也变成了注解:

@Volatile private var running = false

除了可以用 synchronized 这种方式来对代码进行同步加锁以外,在 Java 中还可以用 Lock 的方式来对代码进行加锁。所以我们试着改造一下上面的 buyGoods 方法:

var lock: Lock = ReentrantLock()

fun buyGoods(id: Long) {
    lock.lock()
    try {
        val stock = goods.getValue(id)
        goods.put(id, stock - 1)
    } catch (ex: Exception) {
        println("[Exception] is ${ex}")
    } finally {
        lock.unlock()
    }
}

但是这种写法似乎有如下不好之处:

  • 若是在同一个类内有多个同步方法,将会竞争同一把锁;
  • 在加锁之后,编码人员很容易忘记解锁操作;
  • 重复的模板代码。

那么,我们现在试着对它进行改进,提高这个方式的抽象程度:

fun <T> withLock (lock: Lock, action: () -> T): T {
    lock.lock()
    try{
        return action()
    } catch (ex: Exception) {
        println("[Exception] is ${ex}")
    } finally {
        lock.unlock()
    }
}

withLock方法支持传入一个lock对象和一个Lamada表达式,所以我们现在可以不用关心对buyGoods进行加锁了,只需要在调用的时候传入一个lock对象即可。

fun buyGoods(id: Long) {
    val stock = goods.getValue(id)
    goods.put(id, stock - 1)
}

var lock: Lock = ReentrantLock()
withLock(lock) {
	buyGoods(1)
}

Kotlin 类库中也默认添加了该方式的支持:

var lock: Lock = ReentrantLock()
lock.withLock { 
	buyGoods(1)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/250750.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入理解JVM设计的精髓与独特之处

这是Java代码的执行过程 从软件工程的视角去深入拆解&#xff0c;无疑极具吸引力&#xff1a;首个阶段仅依赖于源高级语言的细微之处&#xff0c;而第二阶段则仅仅专注于目标机器语言的特质。 不可否认&#xff0c;在这两个编译阶段之间的衔接&#xff08;具体指明中间处理步…

Bifrost 中间件 X-Requested-With 系统身份认证绕过漏洞复现

0x01 产品简介 Bifrost是一款面向生产环境的 MySQL,MariaDB,kafka 同步到Redis,MongoDB,ClickHouse等服务的异构中间件 0x02 漏洞概述 Bifrost 中间件 X-Requested-With 存在身份认证绕过漏洞,未经身份认证的攻击者可未授权创建管理员权限账号,可通过删除请求头实现身…

HNU-计算机网络-实验4-网络层与链路层协议分析(PacketTracer)

计算机网络 课程基础实验四网络层与链路层协议分析&#xff08;PacketTracer&#xff09; 计科210X 甘晴void 202108010XXX 文章目录 计算机网络 课程基础实验四<br>网络层与链路层协议分析&#xff08;PacketTracer&#xff09;一、实验目的二、实验内容4.1 路由器交换…

Linux-常用实用操作

一、常用操作总结 1、各类小技巧&#xff08;快捷键&#xff09; ① ctrl c 强制停止 Linux某些程序的运行&#xff0c;如果想要强制停止它&#xff0c;可以使用快捷键ctrl c 命令输入错误&#xff0c;也可以通过快捷键ctrl c&#xff0c;退出当前输入&#xff0c;重新输…

HarmonyOS(十二)——全面认识HarmonyOS三种渲染控制

渲染控制概述 ArkUI通过自定义组件的build()函数和builder装饰器中的声明式UI描述语句构建相应的UI。在声明式描述语句中开发者除了使用系统组件外&#xff0c;还可以使用渲染控制语句来辅助UI的构建&#xff0c;这些渲染控制语句包括控制组件是否显示的条件渲染语句&#xff…

打工人副业变现秘籍,某多/某手变现底层引擎-Stable Diffusion涂鸦功能与局部重绘

在 StableDiffusion图生图的面板里,除了图生图(img2img)选卡外,还有局部重绘(Inpaint),涂鸦(Sketch),涂鸦重绘(Inpaint Sketch),上传重绘蒙版(Inpaint Uplaod)、批量处理(Batch)等功能。下面我就讲解一下这些功能的作用和使用。 涂鸦 Sketch 中文意思为素描,速写,草…

文本聚类——文本相似度(聚类算法基本概念)

一、文本相似度 1. 度量指标&#xff1a; 两个文本对象之间的相似度两个文本集合之间的相似度文本对象与集合之间的相似度 2. 样本间的相似度 基于距离的度量&#xff1a; 欧氏距离 曼哈顿距离 切比雪夫距离 闵可夫斯基距离 马氏距离 杰卡德距离 基于夹角余弦的度量 公式…

sectigo续费难吗

Sectigo是一家成立时间较长的CA认证机构&#xff0c;自成立以来&#xff0c;一直致力于提供安全、可靠的数字证书。这些证书被广泛应用于各种互联网应用中&#xff0c;如电子商务网站、企业内网、在线银行等等。事实上&#xff0c;购买了Sectigo的SSL证书的客户想要续费并不难&…

PyQt6 QDial旋钮控件

锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计46条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话版…

蓝牙协议栈学习笔记

蓝牙协议栈学习笔记 蓝牙简介 蓝牙工作在全球通用的 2.4GHz ISM&#xff08;即工业、科学、医学&#xff09;频段&#xff0c;使用 IEEE802.11 协议 蓝牙 4.0 是迄今为止第一个蓝牙综合协议规范&#xff0c;将三种规格集成在一起。其中最重要的变化就是 BLE&#xff08;Blue…

深入理解Dubbo-7.服务消费调用源码分析

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理&#x1f525;如果感觉博主的文章还不错的话&#xff…

Linux权限(上)

目录 shell命令以及运行原理 Linux权限 Linux中的用户类别 文件类型 文件的访问权限 在讲权限之前&#xff0c;我们得先了解一下命令的执行原理。 shell命令以及运行原理 我们每次在打开Xshell执行相关命令时&#xff0c;通常会看到这样一段代码&#xff1a; [yjdhecs…

手工酸奶加盟店赚钱吗?一年有多少利润

手工酸奶以其新鲜、健康、美味的特点&#xff0c;受到了越来越多消费者的喜爱。 那开一家手工酸奶加盟店能赚钱吗&#xff1f;一年又能有多少利润呢&#xff1f; 作为经营酸奶店5年的创业者&#xff0c;我给大家分享下最真实的情况。&#xff08;可以点赞收藏&#xff0c;方便…

巨杉数据库入选“2023信创独角兽企业100强”

近日&#xff0c;《互联网周刊》、eNet研究院、德本咨询联合发布了“2023信创独角兽企业100强”榜单&#xff0c;巨杉数据库凭借卓越的技术实力和出色的研发能力荣登榜单&#xff0c;本次上榜既是对巨杉数据库长期深耕信创领域的高度认可&#xff0c;也是对其在分布式文档型数据…

通过“待办事项列表项目”快速学习Pyqt5的一些特性

Pyqt5相关文章: 快速掌握Pyqt5的三种主窗口 快速掌握Pyqt5的2种弹簧 快速掌握Pyqt5的5种布局 快速弄懂Pyqt5的5种项目视图&#xff08;Item View&#xff09; 快速弄懂Pyqt5的4种项目部件&#xff08;Item Widget&#xff09; 快速掌握Pyqt5的6种按钮 快速掌握Pyqt5的10种容器&…

W25N01GV 芯片应用

项目中处于成本考虑&#xff0c;要把Nor Flash换成低成本的Nand Flash。 这里总结下芯片应用。 总体概述&#xff1a; 1&#xff09;W25N01&#xff08;NandFlash&#xff09;和W25Q&#xff08;Nor Flash&#xff09;的操作大不一样。 NandFlash擦除以块&#xff08;128KB&…

LeetCode-反转链表问题

1.反转链表 题目描述&#xff1a; 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 思路&#xff1a; 反转链表也就是链表倒置&#xff0c;我一直以来的办法就是先建立一个头节点&#xff0c;之后再遍历链表来进行头插。 代码&#xff1…

HBuilderX 配置 夜神模拟器 详细图文教程

在电脑端查看App的效果&#xff0c;不用真机调试&#xff0c;下载一个模拟器就可以了 --- Nox Player&#xff0c;夜神模拟器&#xff0c;是一款 Android 模拟器。他的使用非常安全&#xff0c;最重要的是完全免费。 一. 安装模拟器 官网地址&#xff1a; (yeshen.com) 二.配…

也许你不需要人工智能

已经不记得我是什么时候开始使用谷歌搜索引擎的&#xff0c; 在刚开始的时候&#xff0c;我看到了一本书&#xff0c;里面有各种各样的搜索技巧。在考虑到如果我不会搜索引擎这种关键技能&#xff0c;那么我将在这个信息时代落后&#xff0c;我读了那本书。 从那本书中我学到了…

运行软件时提示msvcp140.dll丢失的5个解决方法

电脑打开软件提示找不到msvcp140.dll丢失&#xff0c;这是许多用户在使用电脑过程中会遇到的问题。本文将为您介绍五个详细的解决方法&#xff0c;以及msvcp140.dll丢失的原因、作用和是什么。 一、msvcp140.dll丢失原因 系统损坏&#xff1a;操作系统在使用过程中&#xff0…