【Golang】Go语言中的反射原理解析与应用实战

devtools/2024/10/18 21:40:13/

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Python全栈,Golang开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:Go语言开发零基础到高阶实战
景天的主页:景天科技苑

在这里插入图片描述

文章目录

  • Go语言中的反射
    • 一、反射的基本概念
    • 二、静态类型与动态类型
    • 三、为什么要用反射
    • 四、为什么不建议使用反射
    • 五、反射的关键函数
    • 六、反射的使用场景
      • 1. 动态数据处理
      • 2. 获取类型信息
      • 3. 获取值信息
      • 4. 反射修改变量的值
      • 5. 反射调用方法
      • 6. 反射调用函数
    • 七、反射的注意事项
    • 八、总结

Go语言中的反射

反射(Reflection)是计算机科学中的一个重要概念,它允许程序在运行时检查变量和值,获取它们的类型信息,并且能够修改它们。
Go语言通过内置的reflect包提供了反射功能,使得开发者可以编写灵活的代码,处理各种不同类型的值,而不必在编译时就知道这些值的具体类型。
本文将结合实际案例,详细介绍Go语言中反射的基本概念、关键函数以及使用场景。

一、反射的基本概念

在Go语言中,反射允许程序在运行时动态获取变量的各种信息,比如变量的类型、值等。
如果变量是结构体类型,还可以获取到结构体本身的各种信息,比如结构体的字段、方法。通过反射,还可以修改变量的值、调用方法。使用反射需要引入reflect包。

Go语言中的每一个变量都包含两部分信息:类型(type)和值(value)。reflect包让我们能够在运行时获取这些信息。
reflect.TypeOf()函数用于获取任何值的类型,返回一个reflect.Type类型的值。
reflect.ValueOf()函数用于获取任何值的运行时表示,返回一个reflect.Value类型的值。

二、静态类型与动态类型

Go语言是静态类型的语言,但是也可以通过反射机制实现一些动态语言的功能
反射过程中,编译的时候就知道变量类型的就是静态类型、如果在运行时候才知道类型的就是动态类型

静态类型:变量在声明时候给他赋予类型的

var name string // string是静态类型的
var age int  // int 是静态类型的

动态类型:在运行的时候可能发生变化,主要考虑赋值问题

var A interface{}   // interface{} 静态类型 A = 10              // interface{} 静态类型   此时的A是动态类型 int
A = "jingtian"   // interface{} 静态类型   此时的A是动态类型 string

像js,pyhton都是动态类型语言,定义变量的时候,不用指明类型,给它什么类型,运行时就是什么类型
在这里插入图片描述
在这里插入图片描述

三、为什么要用反射

1、我们需要编写一个函数,但是不知道函数传递给我的参数时什么?没约定好,传入的类型太多,这些类型不能统一表示,反射

2、我们在某些使用,需要根据条件来判断具体使用哪个函数处理问题,根据用户的输入来决定,这时候就需要对函数的参数进行反射,在运行期间来动态处理。

3、开发一些脚手架,框架的时候,自动实现一些底层的判断。比如 interface{} = any,由于这种动态类型是不确定的,我们可能在底层代码进行判断,从而选择使用什么来处理。

4、反射主要就是在不确定数据类型的和值的时候使用。如果我们不知道这个对象的信息,我们可以通过这个对象拿到代码中的一切。

四、为什么不建议使用反射

1、和反射相关的代码,不方便阅读,开发中,代码可读性(指标)很重要

2、Go语言是静态类型的语言,编译器可以找出开发时候的错误,如果代码中有大量反射代码,随时可能存在安全问题,panic,项目就终止

3、反射的性能很低,相对于正常的开发,至少慢2-3个数量级。项目关键位置低耗时,一定是不能使用反射的。更多时候使用约定。

五、反射的关键函数

reflectType__82">1. reflect.Type 类型

reflect.Type是一个接口,通过reflect.TypeOf函数对接收的任意数据类型进行反射,可以获取该类型的各种信息。reflect.Type接口包含以下方法:

  • Kind():返回该接口的具体分类(Kind)。
  • Name():返回该类型在自身包内的类型名,如果是未命名类型会返回空字符串。
  • PkgPath():返回类型的包路径,即明确指定包的import路径。对于内建类型(如string、error)或未命名类型(如*T、struct{}、[]int),会返回空字符串。
  • String():返回类型的字符串表示。
  • Size():返回要保存一个该类型的值需要多少字节。
  • Align():返回当从内存中申请一个该类型值时,会对齐的字节数。
  • FieldAlign():返回当该类型作为结构体的字段时,会对齐的字节数。
  • Implements(u Type):如果该类型实现了u代表的接口,返回真。
  • AssignableTo(u Type):如果该类型的值可以直接赋值给u代表的类型,返回真。
  • ConvertibleTo(u Type):如果该类型的值可以转换为u代表的类型,返回真。
  • Bits():返回该类型的位字数。如果该类型的Kind不是Int、Uint、Float或Complex,会panic。
  • Len():返回array类型的长度,如非数组类型将panic。
  • Elem():返回该类型的元素类型,如果该类型的Kind不是Array、Chan、Map、Ptr或Slice,会panic。
  • Key():返回map类型的键的类型,如非映射类型将panic。
  • ChanDir():返回一个channel类型的方向,如非通道类型将会panic。
  • NumField():返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic。
  • Field(i int):返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic。
  • FieldByIndex(index []int):返回索引序列指定的嵌套字段的类型,等价于用索引中每个值链式调用本方法,如非结构体将会panic。
  • FieldByName(name string):返回该类型名为name的字段(会查找匿名字段及其子字段),布尔值说明是否找到,如非结构体将panic。
  • FieldByNameFunc(match func(string) bool):返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic。
  • IsVariadic():如果函数类型的最后一个输入参数是"…"形式的参数,返回真。
  • NumIn():返回func类型的参数个数,如果不是函数,将会panic。

reflectValue__109">2. reflect.Value 类型

reflect.Value是一个结构体,为Go值提供了反射接口。reflect.Value包含以下方法:

  • IsValid():判断Value是否包含有效的值。
  • IsNil():判断Value是否为nil。
  • Kind():返回Value的Kind。
  • Type():返回Value的类型。
  • Convert(t Type):将Value转换为Type类型的值。
  • Elem():如果Value是一个指针、数组、切片、映射、通道或接口,返回它指向或持有的元素。
  • Bool()Int()Uint()Float()Complex():分别返回Value的布尔、整数、无符号整数、浮点数和复数表示。
  • OverflowInt(x int64)OverflowUint(x uint64)OverflowFloat(x float64)OverflowComplex(x complex128):分别判断将Value转换为整数、无符号整数、浮点数和复数时是否会溢出。
  • Bytes():返回Value的字节切片表示。
  • String():返回Value的字符串表示。
  • Pointer():返回Value的指针表示。
  • InterfaceData():返回Value的接口数据,用于类型断言。
  • Slice(i, j int):返回Value的切片,从索引i到索引j(不包括j)。
  • Slice3(i, j, k int):返回Value的三维切片,从索引i到索引j到索引k(不包括k)。
  • Cap():返回Value的容量。
  • Len():返回Value的长度。
  • Index(i int):返回Value的第i个元素。
  • MapIndex(key Value):返回Value中键为key的元素。
  • MapKeys():返回Value的所有键。
  • NumField():返回Value的字段数。
  • Field(i int):返回Value的第i个字段。
  • FieldByIndex(index []int):返回索引序列指定的嵌套字段。
  • FieldByName(name string):返回Value中名为name的字段。
  • FieldByNameFunc(match func(string) bool):返回Value中第一个字段名满足函数match的字段。
  • Recv():从通道接收值,返回接收到的值和是否成功接收。
  • TryRecv():尝试从通道接收值,不阻塞,返回接收到的值和是否成功接收。

六、反射的使用场景

1. 动态数据处理

反射的一个主要场景是处理动态数据结构,例如解析JSON或处理数据库查询结果。在这些场景中,数据的结构在编译时可能是未知的,因此需要使用反射来动态处理。

示例1:反射基本数据类型

package mainimport ("fmt""reflect"
)// 反射
/*
Type : reflect.TypeOf(a) , 获取变量的类型
Value :reflect.ValueOf(a) , 获取变量的值
*/func main() {// 正常编程定义变量var a int = 3// func TypeOf(i any) Type 反射得到该变量的数据类型fmt.Println("type", reflect.TypeOf(a))// func ValueOf(i any) Value  反射得到该变量的值fmt.Println("value", reflect.ValueOf(a))// 根据反射的值,来获取对象对应的类型和数值// 如果我们不知道这个对象的信息,我们可以通过这个对象拿到代码中的一切。// 获取a的类型v := reflect.ValueOf(a) // string int User// Kind : 获取这个值的种类, 在反射中,所有数据类型判断都是使用种类。//根据kind来判断其类型if v.Kind() == reflect.Float64 {fmt.Println(v.Float())}if v.Kind() == reflect.Int {fmt.Println(v.Int())}fmt.Println(v.Kind() == reflect.Float64)//fmt.Println(v.Type())}

在这里插入图片描述

如果取值时,类型不对,会报panic异常
在这里插入图片描述

示例2:解析JSON

package mainimport ("encoding/json""fmt""reflect"
)type User struct {ID   int    `json:"id"`Name string `json:"name"`Age  int    `json:"age"`
}func main() {jsonStr := `{"id":1, "name":"jigntian", "age":18}`var user User// func Unmarshal(data []byte, v any) error//先解析,看是否报错,要是报错,给的就不是json字符串err := json.Unmarshal([]byte(jsonStr), &user)if err != nil {fmt.Println("Error:", err)return}// 使用反射获取User结构体的字段信息val := reflect.ValueOf(user)typ := reflect.TypeOf(user)for i := 0; i < val.NumField(); i++ {fieldVal := val.Field(i)fieldType := typ.Field(i)fmt.Printf("Field Name: %s, Field Value: %v, Field Type: %s\n", fieldType.Name, fieldVal, fieldType.Type)}
}

在这里插入图片描述

2. 获取类型信息

通过reflect.TypeOf函数,我们可以获取变量的类型信息。reflect.Type类型提供了多种方法来获取类型的详细信息,如类型名称、类型种类、字段信息等。

以下是一个示例,演示了如何获取类型信息:

package mainimport ("fmt""reflect"
)type Person struct {Name stringAge  int
}func main() {var p Person = Person{Name: "Alice", Age: 30}// 获取类型信息t := reflect.TypeOf(p)// 打印类型名称fmt.Println("Type name:", t.Name())// 打印类型种类fmt.Println("Type kind:", t.Kind())// 打印结构体字段信息for i := 0; i < t.NumField(); i++ {field := t.Field(i)fmt.Printf("Field name: %s, Field type: %s\n", field.Name, field.Type)}
}

在这里插入图片描述

在这个示例中,我们定义了一个Person结构体,并创建了一个Person类型的变量p。通过reflect.TypeOf函数,我们获取了变量p的类型信息。然后,我们打印了类型名称、类型种类以及结构体字段信息。

获取结构体信息

package mainimport ("fmt""reflect"
)type User5 struct {Name stringAge  intSex  string
}func (user User5) Say(msg string) {fmt.Println("User 说:", msg)
}// PrintInfo  打印结构体信息
func (user User5) PrintInfo() {fmt.Printf("姓名:%s,年龄:%d,性别:%s\n", user.Name, user.Age, user.Sex)
}func main() {user := User5{"jingtian", 18, "男"}reflectGetInfo(user)
}// 通过反射,获取变量的信息
func reflectGetInfo(v interface{}) {// 1、获取参数的类型Type , 可能是用户自己定义的,但是Kind一定是内部类型structgetType := reflect.TypeOf(v)fmt.Println(getType.Name()) // 类型信息 User5fmt.Println(getType.Kind()) // 找到上级的种类Kind  struct// 2、获取值getValue := reflect.ValueOf(v)//查看类型 Type得到的是我们自定义的类型main.User5   Kind得到的是go内置的类型 structfmt.Println("获取到的value--type值类型", getValue.Type()) // main.User5fmt.Println("获取到的value--kind值类型", getValue.Kind()) // structfmt.Println("获取到value", getValue)// 获取字段,通过Type扒出字段// Type.NumField() 获取这个类型中有几个字段  3// field(index) 得到字段的值for i := 0; i < getType.NumField(); i++ {field := getType.Field(i) // 类型//通过valueof.Field().Interface拿到值value := getValue.Field(i).Interface() // value// 打印fmt.Printf("字段名:%s,字段类型:%s,字段值:%v\n", field.Name, field.Type, value)}// 获取这个结构体的方法 , NumMethod 可以获取方法的数量for i := 0; i < getType.NumMethod(); i++ {method := getType.Method(i)fmt.Printf("方法的名字:%s\t,方法类型:%s", method.Name, method.Type)//执行方法//method.Name()}}/*
由上面反射,就可以推导出结构体字段以及其方法
type User struct{Name stringAge intSex string
}
func (main.User) PrintInfo(){}
func (main.User) Say(string)
*/

在这里插入图片描述

通过反射可以 实现拿到一个对象,还原它的本身结构信息

3. 获取值信息

通过reflect.ValueOf函数,我们可以获取变量的值信息。reflect.Value类型提供了多种方法来获取和设置值,如获取布尔值、整数值、浮点数值、字符串值等。此外,reflect.Value类型还提供了方法来操作结构体字段、数组元素、映射键值对等。

以下是一个示例,演示了如何获取值信息并操作结构体字段:

package mainimport ("fmt""reflect"
)type Person3 struct {Name stringAge  int
}func main() {var p Person3 = Person3{Name: "jingtian", Age: 18}// 获取值信息v := reflect.ValueOf(p)// 打印值信息fmt.Println("Value of Name:", v.FieldByName("Name").String())fmt.Println("Value of Age:", v.FieldByName("Age").Int())// 修改结构体字段值(注意:这里只是演示,实际上无法直接修改,因为v是不可变的)// 要修改值,需要使用reflect.ValueOf的Elem方法配合指针变量// 下面的代码会报错:panic: reflect: call of reflect.Value.SetInt on zero Value// v.FieldByName("Age").SetInt(35)// 正确的修改方式pv := reflect.ValueOf(&p).Elem() // 获取指针指向的元素的值pv.FieldByName("Age").SetInt(35) // 修改字段值// 打印修改后的值fmt.Println("Modified Age:", p.Age)
}

在这里插入图片描述

在这个示例中,我们定义了一个Person3结构体,并创建了一个Person3类型的变量p。通过reflect.ValueOf函数,我们获取了变量p的值信息。
然后,我们打印了结构体字段的值。注意,由于reflect.ValueOf返回的值是不可变的,所以我们不能直接修改字段值。为了修改字段值,我们需要使用reflect.ValueOf的Elem方法配合指针变量来获取可变的值。

4. 反射修改变量的值

通过反射修改值,需要操作对象的指针,拿到地址,然后拿到指针对象
通过Canset来判断值是否可以被修改

package mainimport ("fmt""reflect"
)// 反射设置变量的值
func main() {var num float64 = 3.14update(&num)  //注意,这里传入指针地址fmt.Println(num)}func update(v any) {// 通过反射修改值,需要操作对象的指针,拿到地址,然后拿到指针对象pointer := reflect.ValueOf(v)newValue := pointer.Elem()fmt.Println("类型:", newValue.Type())fmt.Println("判断该类型是否可以修改:", newValue.CanSet())// 通过反射对象给变量赋值//newValue.SetFloat(999.43434)// 也可以对类型进行判断,根据不同类型修改成不同的值if newValue.Kind() == reflect.Float64 {// 通过反射对象给变量赋值newValue.SetFloat(2.21)}if newValue.Kind() == reflect.Int {// 通过反射对象给变量赋值newValue.SetInt(2)}
}

在这里插入图片描述

修改结构体变量:通过属性名,来实现修改

package mainimport ("fmt""reflect"
)type Person4 struct {Name stringAge  int
}// 反射设置变量的值
func main() {person := Person4{"jingtian", 18}//注意,这里传入指针update2(&person)fmt.Println(person)}func update2(v any) {// 通过反射修改值,需要操作对象的指针,拿到地址,然后拿到指针对象pointer := reflect.ValueOf(v)newValue := pointer.Elem()fmt.Println("类型:", newValue.Type())fmt.Println("判断该类型是否可以修改:", newValue.CanSet())//修改结构体数据// 需要找到对象的结构体字段名newValue.FieldByName("Name").SetString("王安石")//如果不知道字段的类型,可以判断下fmt.Println("看下字段的类型", newValue.FieldByName("Name").Kind())newValue.FieldByName("Age").SetInt(99)}

在这里插入图片描述

5. 反射调用方法

反射还可以用于在运行时动态调用对象的方法。这在需要根据字符串名称调用方法的场景下非常有用,例如实现一个简单的命令行接口或基于插件的架构。
通过方法名,找到这个方法, 然后调用 Call() 方法来执行 包括无参方法和有参方法

package mainimport ("fmt""reflect"
)type User6 struct {Name stringAge  intSex  string
}func (user User6) Say2(msg string) {fmt.Println(user.Name, "说:", msg)
}// PrintInfo2  打印结构体信息
func (user User6) PrintInfo2() {fmt.Printf("姓名:%s,年龄:%d,性别:%s\n", user.Name, user.Age, user.Sex)
}func main() {user := User6{"景天", 18, "男"}// 通过方法名,找到这个方法, 然后调用 Call() 方法来执行// 反射调用方法value := reflect.ValueOf(user)fmt.Printf("kind:%s,  type:%s\n", value.Kind(), value.Type())//根据方法名找到方法,通过call来调用方法,call里面跟参数,。无参使用nil// func (v Value) Call(in []Value) []Value  Call的参数是个reflect.Value类型的切片value.MethodByName("PrintInfo2").Call(nil) // 无参方法调用// 有参方法调用。先创建个reflect.Value类型的切片,长度为参数的个数args := make([]reflect.Value, 1)//给参数赋值// func ValueOf(i any) Valueargs[0] = reflect.ValueOf("这反射来调用的")//找到方法名,调用传参value.MethodByName("Say2").Call(args) // 有参方法调用
}

在这里插入图片描述

6. 反射调用函数

reflect.ValueOf 通过函数名来进行反射 得到函数名 reflect.ValueOf(函数名)
然后通过函数名.Call() 调用函数

package mainimport ("fmt""reflect"
)// 反射调用函数  func
func main() {// 通过函数名来进行反射  reflect.ValueOf(函数名)// Kind funcvalue1 := reflect.ValueOf(fun1)fmt.Println("打印拿到的数据类型", value1.Kind(), value1.Type())//调用无参函数value1.Call(nil)//调用有参函数value2 := reflect.ValueOf(fun2)fmt.Println("打印有参函数", value2.Kind(), value2.Type())//创建参数args1 := make([]reflect.Value, 2)args1[0] = reflect.ValueOf(1)args1[1] = reflect.ValueOf("hahahhaha")value2.Call(args1)//调用有参数,有返回值函数vuale3 := reflect.ValueOf(fun3)fmt.Println(vuale3.Kind(), vuale3.Type())args2 := make([]reflect.Value, 2)args2[0] = reflect.ValueOf(2)args2[1] = reflect.ValueOf("hahahhaha")//接收返回值 返回的是切片// func (v Value) Call(in []Value) []ValueresultValue := vuale3.Call(args2)fmt.Println("返回值:", resultValue[0])
}// 无参函数
func fun1() {fmt.Println("fun1:无参")
}// 有参函数
func fun2(i int, s string) {fmt.Println("fun2:有参 i=", i, " s=", s)
}// 有返回值函数
func fun3(i int, s string) string {fmt.Println("fun3:有参有返回值 i=", i, " s=", s)return s
}

在这里插入图片描述

七、反射的注意事项

  1. 性能开销反射相对于直接操作类型有更高的性能开销,因为它需要在运行时进行类型检查和值转换。因此,在性能敏感的场景中应谨慎使用反射

  2. 安全性反射允许程序在运行时访问和修改几乎任何值,这可能导致意外的副作用或安全问题。因此,在使用反射时应确保只访问和修改预期的值,并避免潜在的类型冲突或数据损坏。

  3. 代码可读性:使用反射会使代码变得更加复杂和难以阅读。因此,在编写代码时应权衡反射带来的灵活性和代码可读性的重要性。

  4. 编译时检查:尽管反射提供了动态类型检查和值操作的能力,但它无法替代编译时类型检查。因此,在编写使用反射的代码时,应确保在编译时尽可能多地检查类型错误和逻辑错误。

八、总结

Go语言的反射功能提供了一种强大的机制来在运行时动态检查和操作值。通过反射,我们可以编写更加灵活和通用的代码来处理各种不同类型的值。然而,反射也带来了性能开销、安全性和代码可读性等挑战。因此,在使用反射时应谨慎权衡其优缺点,并根据具体场景做出合适的选择。

通过本文的介绍和示例代码,我们了解了Go语言中反射的基本概念、关键函数以及使用场景。希望这些内容能帮助你更好地理解和使用Go语言的反射功能。


http://www.ppmy.cn/devtools/126826.html

相关文章

Flutter结合鸿蒙next 中数据类型转换的高级用法:dynamic 类型与其他类型的转换解析

目录 写在前面 1. 什么是 dynamic 类型&#xff1f; 示例 2. dynamic 与其他类型的转换 2.1 强制类型转换 示例 2.2 使用 is 操作符 示例 2.3 从 List 转换 示例 3. dynamic 类型的最佳实践 3.1 避免过度使用 dynamic 3.2 使用 Null Safety 示例 3.3 异常处理 示…

【Oracle 数据库技术分享】BLOB 与 CLOB 的选择与应用

【Oracle 数据库技术分享】BLOB 与 CLOB 的选择与应用 文章目录 前言一、为什么选择 BLOB 或 CLOB&#xff1f;二、BLOB 与 CLOB 的区别1.数据类型2.性能3.功能4.兼容性5.存储限制6.查询和处理 三、选择 BLOB 或 CLOB 的建议1.根据数据类型选择2.考虑性能需求3.兼容性和集成4.综…

MFC工控项目实例二十四模拟量校正值输入

承接专栏《MFC工控项目实例二十三模拟量输入设置界面》 对模拟量输入的零点校正值及满量程对应的电压值进行输入。 1、在SenSet.h文件中添加代码 #include "BtnST.h" #include "ShadeButtonST.h"/ // SenSet dialogclass SenSet : public CDialog { // Co…

Java初学者的学习顺序

学习 Java 语言的全面流程可以分为多个阶段&#xff0c;从基础概念、编程语法、面向对象设计&#xff0c;到高级应用和项目实践&#xff0c;逐步深入。下面是一个详细的 Java 学习路径&#xff0c;适合初学者和中级开发者。 一、初级阶段&#xff1a;Java 基础语法 1. 安装和…

APQP在制造行业的应用:搭上数字化项目管理平台很nice

APQP&#xff08;Advanced Product Quality Planning&#xff0c;即产品质量先期策划&#xff09;最早由汽车行业引入&#xff0c;并因其在质量管理方面的显著效果而逐渐被其他制造业领域所采纳。 APQP提供了一种从产品设计的最初阶段到生产过程的全面质量管理框架&#xff0c;…

MySQL表的基本查询上

1&#xff0c;创建表 前面基础的文章已经讲了很多啦&#xff0c;直接上操作&#xff1a; 非常简单&#xff01;下一个&#xff01; 2&#xff0c;插入数据 1&#xff0c;全列插入 前面也说很多了&#xff0c;直接上操作&#xff1a; 以上插入和全列插入类似&#xff0c;全列…

php的echo和print输出语句⑥

在 PHP 中有两个基本的输出方式&#xff1a; echo 和 print。 echo 和 print 区别: echo : 可以输出一个或多个字符串 print : 只允许输出一个字符串。 提示&#xff1a;echo 输出的速度比 print 快&#xff0c; echo 没有返回值&#xff0c;print有返回值1。 <?php …

构建高效作业管理平台:Spring Boot师生协作评审系统

1系统概述 1.1 研究背景 如今互联网高速发展&#xff0c;网络遍布全球&#xff0c;通过互联网发布的消息能快而方便的传播到世界每个角落&#xff0c;并且互联网上能传播的信息也很广&#xff0c;比如文字、图片、声音、视频等。从而&#xff0c;这种种好处使得互联网成了信息传…