【Go基础】面向对象和反射机制

news2024/11/15 11:16:18

文章目录

  • 一、面向对象
    • 1. 面向对象的概念
    • 2. 构造函数
    • 3. 继承与重写
    • 4. 泛型
  • 二、反射
    • 1. 反射介绍
    • 2. 反射的基础数据类型
    • 3. 反射API
      • 3.1 reflect.Type
        • ①如何得到Type
        • ②指针Type转为非指针Type
        • ③获取struct成员变量的信息
        • ④获取struct成员方法的信息
        • ⑤获取函数的信息
        • ⑥判断类型是否实现了某接口
      • 3.2 reflect.Value
        • ①如果获得Value
        • ②Value转为Type
        • ③指针Value和非指针Value互相转换
        • ④得到Value对应的原始数据
        • ⑤空Value的判断
        • ⑥通过Value修改原始数据的值
        • ⑦通过Value修改Slice
        • ⑧修改map
        • ⑨调用函数
        • ⑩调用成员方法
      • 3.3 创建对象
        • ①创建struct
        • ②创建slice
        • ③创建map
    • 4. 自行实现json序列化

一、面向对象

1. 面向对象的概念

洗衣服过程剖析:

  • 给洗衣机里加脏衣服和洗衣粉
  • 启动洗衣机
  • 洗衣机自动注水,然后滚动
  • 脏衣服从黑颜色变成白颜色
  • 洗衣机自动停止

用面向过程的思想实现代码

// 准备洗衣服
// 输入参数:
// 	powder 洗衣机里放多少洗衣粉
// 	closes 洗衣机里放多少衣服
// 	clean 衣服是否是干净的
// 返回值:
// 	洗衣机是否开启
// 	准备洗多少衣服
func prepare(powder int, closes int, clean bool) (bool, int) {
	if powder <= 0 || closes <= 0 || clean == true {
		return false, 0
	}
	return true, closes
}

// 开始洗衣服
// 输入参数:
// 	washer_state 洗衣机是否开启
// 	closes 准备洗多少衣服
// 返回值:
// 	衣服是否是干净的
// 	洗了多少衣服
// 	洗衣机是否开启
func wash(washer_state bool, closes int) (bool, int, bool) {
	if washer_state == false {
		return false, 0, false
	} else {
		fmt.Println("注水")
		fmt.Println("滚动")
		fmt.Println("关机")
		return true, closes, false
	}
}

// 检查最终状态
// 输入参数:
// 	clean 衣服是否是干净的
// 	closes 洗了多少衣服
// 	washer_state 洗衣机是否开启
func check(clean bool, closes int, washer_state bool) {
	if clean && closes > 0 {
		fmt.Printf("洗干净了%d件衣服\n", closes)
		if washer_state {
			fmt.Println("你忘关洗衣机了")
		}
	} else {
		fmt.Println("洗衣失败")
	}
}

// 整个洗衣服的过程
func WashProcedure(powder, closes int) {
	washer_state := false
	clean := false

	washer_state, closes = prepare(powder, closes, clean)
	clean, closes, washer_state = wash(washer_state, closes)
	check(clean, closes, washer_state)
}

面向过程编程整个过程分为若干步,每一步对应一个函数,函数之间要传递大量的参数
面向对象编程把大量参数封装到一个结构体里面,给结构体赋予方法,方法里面去修改结构体的成员变量,go语言面向对象的好处:打包参数、继承、面向接口编程

// 洗衣机
type Washer struct {
	State  bool
	Powder int
}

// 衣服
type Closes struct {
	Clean bool
}

func (washer *Washer) prepare(closes []*Closes) error {
	if washer.State == true || washer.Powder <= 0 || len(closes) <= 0 {
		return errors.New("请确保在关机的状态下加入适量衣物和洗衣粉")
	}
	return nil
}

func (washer *Washer) wash(closes []*Closes) error {
	if err := washer.prepare(closes); err != nil {
		return err
	}

	fmt.Println("开机")
	washer.State = true

	// 检查是否有脏衣服
	clean := true
	for _, ele := range closes {
		if ele.Clean == false {
			clean = false
			break
		}
	}
	if clean {
		washer.State = false
		return errors.New("所有衣服都是干净的,不需要洗")
	}

	// 开始洗衣服
	fmt.Println("注水")
	fmt.Println("滚动")
	fmt.Println("关机")
	washer.State = false
	for _, ele := range closes {
		ele.Clean = true
	}
	return nil
}

func (washer *Washer) check(err error, closes []*Closes) {
	if err != nil {
		fmt.Printf("洗衣失败:%v\n", err)
	} else {
		fmt.Printf("洗干净了%d件衣服\n", len(closes))
		if washer.State == true {
			fmt.Println("你忘关洗衣机了")
		}
	}
}

2. 构造函数

定义User结构体

type User struct {
    Name string // ""表示未知
    Age int // -1表示未知
    Sex byte // 1男,2女,3未知
}
  • u := User{}构造一个空的User,各字段都取相应数据类型的默认值
  • up := new(User)构造一个空的User,并返回其指针

自定义构造函数

func NewDefaultUser() *User {
    return &User{
        Name: "",
        Age: -1,
        Sex: 3,
    }
}
func NewUser(name string, age int, sex byte) *User {
    return &User{
        Name: name,
        Age: age,
        Sex: sex,
    }
}

单例模式,确保在并发的情况下,整个进程里只会创建struct的一个实例

var (
    sUser *User
    uOnce sync.Once
)
func GetUserInstance() *User {
	// 确保即使在并发的情况下,下面的3行代码在整个go进程里只会被执行一次
    uOnce.Do(func() {
    if sUser == nil {
        sUser = NewDefaultUser()
        }
    })
    return sUser
}

// 调用GetUserInstance()得到的是同一个User实例
su1 := GetUserInstance()
su2 := GetUserInstance()
// 修改su1会影响su2

3. 继承与重写

通过嵌入匿名结构体,变相实现“继承”的功能,因为访问匿名成员时可以跳过成员名直接访问它的内部成员

type Plane struct {
	color string
}
type Bird struct {
	Plane 
}
bird := Bird {}
bird.Plane.color
bird.color

重写

func (plane Plane) fly() int {
	return 500
}

//重写父类(Plane)的fly方法
func (bird Bird) fly() int {
	return bird.Plane.fly()+100 // 调用父类的方法
}

正规来讲,Go语言并不支持继承,它只是支持组合

type Plane struct {}
type Car struct{}
// Bird组合了Plane和Car的功能
type Bird struct {
	Plane 
	Car
}

4. 泛型

在有泛型之前,同样的功能需要为不同的参数类型单独实现一个函数

func add4int(a, b int) int {
	return a + b
}
func add4float32(a, b float32) float32 {
	return a + b
}
func add4string(a, b string) string {
	return a + b
}

使用泛型

type Addable interface{
type int, int8, int16, int32, int64,
	uint, uint8, uint16, uint32, uint64, uintptr,
	float32, float64, complex64, complex128,string
}
func add[T Addable](a,b T)T{
	return a+b
}

在go1.17中泛型默认没有开启,如果想用运行时命令行需要加-gcflags=-G=3,或者设置环境变量export GOFLAGS=“-gcflags=-G=3”,泛型正式版将在go 1.18中发布,但是Go语言之父Rob Pike建议不在Go 1.18的标准库中使用泛型

二、反射

1. 反射介绍

反射就是在运行期间(不是编译期间)探知对象的类型信息和内存结构、更新变量、调用它们的方法

反射的使用场景:

  • 函数的参数类型是interface{},需要在运行时对原始类型进行判断,针对不同的类型采取不同的处理方式。比如json.Marshal(v interface{})
  • 在运行时根据某些条件动态决定调用哪个函数,比如根据配置文件执行相应的算子函数

Go标准库里的json序列化就使用了反射

type User struct {
    Name string
    Age int
    Sex byte `json:"gender"`
}
user := User{
    Name: "钱钟书",
    Age: 57,
    Sex: 1,
}
json.Marshal(user) // 返回 {"Name":"钱钟书","Age":57,"gender":1}

反射的弊端:

  • 代码难以阅读,难以维护
  • 编译期间不能发现类型错误,覆盖测试难度很大,有些bug需要到线上运行很长时间才能发现,可能会造成严重用后果
  • 反射性能很差,通常比正常代码慢一到两个数量级。在对性能要求很高,或大量反复调用的代码块里建议不要使用反射

2. 反射的基础数据类型

在这里插入图片描述

reflect.Type用于获取类型相关的信息

type Type interface {
	Method(int) Method // 第i个方法
	MethodByName(string) (Method, bool) // 根据名称获取方法
	NumMethod() int // 方法的个数
	Name() string // 获取结构体名称
	PkgPath() string // 包路径
	Size() uintptr // 占用内存的大小
	String() string // 获取字符串表述
	Kind() Kind //数据类型
	Implements(u Type) bool //判断是否实现了某接口
	AssignableTo(u Type) bool // 能否赋给另外一种类型
	ConvertibleTo(u Type) bool // 能否转换为另外一种类型
	Elem() Type // 解析指针
	Field(i int) StructField // 第i个成员
	FieldByIndex(index []int) StructField // 根据index路径获取嵌套成员
	FieldByName(name string) (StructField, bool) // 根据名称获取成员
	FieldByNameFunc(match func(string) bool) (StructField, bool) // 根据匹配函数匹配需要的字段
	Len() int // 容器的长度
	NumIn() int // 输出参数的个数
	NumOut() int // 返回参数的个数
}

通过reflect.Value获取、修改原始数据类型里的值

type Value struct {
	// 代表的数据类型
	typ *rtype
	// 指向原始数据的指针
	ptr unsafe.Pointer
}

3. 反射API

3.1 reflect.Type

①如何得到Type

通过TypeOf()得到Type类型

typeI := reflect.TypeOf(1)
typeS := reflect.TypeOf("hello")
fmt.Println(typeI) // int
fmt.Println(typeS) // string

typeUser := reflect.TypeOf(&common.User{})
fmt.Println(typeUser) // *common.User
fmt.Println(typeUser.Kind()) // ptr
fmt.Println(typeUser.Elem().Kind()) // struct

②指针Type转为非指针Type

typeUser := reflect.TypeOf(&common.User{})
typeUser2 := reflect.TypeOf(common.User{})
assert.IsEqual(typeUser.Elem(), typeUser2)

③获取struct成员变量的信息

typeUser := reflect.TypeOf(common.User{}) // 需要用struct的Type,不能用指针的Type
fieldNum := typeUser.NumField() // 成员变量的个数
for i := 0; i < fieldNum; i++ {
	field := typeUser.Field(i)
	fmt.Printf("%d %s offset %d anonymous %t type %s exported %t json tag %s\n", i,
		field.Name,            // 变量名称
		field.Offset,          // 相对于结构体首地址的内存偏移量,string类型会占据16个字节
		field.Anonymous,       // 是否为匿名成员
		field.Type,            // 数据类型,reflect.Type类型
		field.IsExported(),    // 包外是否可见(即是否以大写字母开头)
		field.Tag.Get("json")) // 获取成员变量后面``里面定义的tag
}
fmt.Println()

// 可以通过FieldByName获取Field
if nameField, ok := typeUser.FieldByName("Name"); ok {
	fmt.Printf("Name is exported %t\n", nameField.IsExported())
}
// 也可以根据FieldByIndex获取Field
thirdField := typeUser.FieldByIndex([]int{2}) // 参数是个slice,因为有struct嵌套的情况
fmt.Printf("third field name %s\n", thirdField.Name)

④获取struct成员方法的信息

typeUser := reflect.TypeOf(common.User{})
methodNum := typeUser.NumMethod() // 成员方法的个数,接收者为指针的方法【不】包含在内
for i := 0; i < methodNum; i++ {
	method := typeUser.Method(i)
	fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported())
}
fmt.Println()

typeUser2 := reflect.TypeOf(&common.User{})
methodNum = typeUser2.NumMethod() // 成员方法的个数,接收者为指针或值的方法【都】包含在内,也就是说值实现的方法指针也实现了(反之不成立)
for i := 0; i < methodNum; i++ {
	method := typeUser2.Method(i)
	fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported())
}

⑤获取函数的信息

func Add(a, b int) int {
	return a + b
}

typeFunc := reflect.TypeOf(Add) // 获取函数类型
fmt.Printf("is function type %t\n", typeFunc.Kind() == reflect.Func)
argInNum := typeFunc.NumIn() // 输入参数的个数
argOutNum := typeFunc.NumOut() // 输出参数的个数
for i := 0; i < argInNum; i++ {
	argTyp := typeFunc.In(i)
	fmt.Printf("第%d个输入参数的类型%s\n", i, argTyp)
}
for i := 0; i < argOutNum; i++ {
	argTyp := typeFunc.Out(i)
	fmt.Printf("第%d个输出参数的类型%s\n", i, argTyp)
}

⑥判断类型是否实现了某接口

// 通过reflect.TypeOf((*<interface>)(nil)).Elem()获得接口类型,因为People是个接口不能创建实例,所以把nil强制转为*common.People类型
typeOfPeople := reflect.TypeOf((*common.People)(nil)).Elem()
fmt.Printf("typeOfPeople kind is interface %t\n", typeOfPeople.Kind() == reflect.Interface)
t1 := reflect.TypeOf(common.User{})
t2 := reflect.TypeOf(&common.User{})
// 如果值类型实现了接口,则指针类型也实现了接口,反之不成立
fmt.Printf("t1 implements People interface %t\n", t1.Implements(typeOfPeople))

3.2 reflect.Value

①如果获得Value

通过ValueOf()得到Value

iValue := reflect.ValueOf(1)
sValue := reflect.ValueOf("hello")
userPtrValue := reflect.ValueOf(&common.User{
	Id:     7,
	Name:   "Ricky",
	Weight: 60,
	Height: 1.80,
})
fmt.Println(iValue)       // 1
fmt.Println(sValue)       // hello
fmt.Println(userPtrValue) // &{7 Ricky  60 1.80}

②Value转为Type

iType := iValue.Type()
sType := sValue.Type()
userType := userPtrValue.Type()
// 在Type和相应Value上调用Kind()结果一样的
fmt.Println(iType.Kind() == reflect.Int, iValue.Kind() == reflect.Int, iType.Kind() == iValue.Kind())  
fmt.Println(sType.Kind() == reflect.String, sValue.Kind() == reflect.String, sType.Kind() == sValue.Kind()) 
fmt.Println(userType.Kind() == reflect.Ptr, userPtrValue.Kind() == reflect.Ptr, userType.Kind() == userPtrValue.Kind())

③指针Value和非指针Value互相转换

userValue := userPtrValue.Elem()                    // Elem() 指针Value转为非指针Value
fmt.Println(userValue.Kind(), userPtrValue.Kind())  // struct ptr
userPtrValue3 := userValue.Addr()                   // Addr() 非指针Value转为指针Value
fmt.Println(userValue.Kind(), userPtrValue3.Kind()) // struct ptr

④得到Value对应的原始数据

通过Interface()函数把Value转为interface{},再从interface{}强制类型转换,转为原始数据类型;或者在Value上直接调用Int()、String()等一步到位

fmt.Printf("origin value iValue is %d %d\n", iValue.Interface().(int), iValue.Int())
fmt.Printf("origin value sValue is %s %s\n", sValue.Interface().(string), sValue.String())
user := userValue.Interface().(common.User)
fmt.Printf("id=%d name=%s weight=%.2f height=%.2f\n", user.Id, user.Name, user.Weight, user.Height)
user2 := userPtrValue.Interface().(*common.User)
fmt.Printf("id=%d name=%s weight=%.2f height=%.2f\n", user2.Id, user2.Name, user2.Weight, user2.Height)

⑤空Value的判断

var i interface{} // 接口没有指向具体的值
v := reflect.ValueOf(i)
fmt.Printf("v持有值 %t, type of v is Invalid %t\n", v.IsValid(), v.Kind() == reflect.Invalid)

var user *common.User = nil
v = reflect.ValueOf(user) // Value指向一个nil
if v.IsValid() {
	fmt.Printf("v持有的值是nil %t\n", v.IsNil()) // 调用IsNil()前先确保IsValid(),否则会panic
}

var u common.User // 只声明,里面的值都是0值
v = reflect.ValueOf(u)
if v.IsValid() {
	fmt.Printf("v持有的值是对应类型的0值 %t\n", v.IsZero()) // 调用IsZero()前先确保IsValid(),否则会panic
}

⑥通过Value修改原始数据的值

var i int = 10
var s string = "hello"
user := common.User{
	Id:     7,
	Name:   "Ricky",
	Weight: 60,
	Height: 1.80,
}

valueI := reflect.ValueOf(&i) // 由于go语言所有函数传的都是值,所以要想修改原来的值就需要传指针
valueS := reflect.ValueOf(&s)
valueUser := reflect.ValueOf(&user)
valueI.Elem().SetInt(8) // 由于valueI对应的原始对象是指针,通过Elem()返回指针指向的对象
valueS.Elem().SetString("golang")
valueUser.Elem().FieldByName("Weight").SetFloat(68.0) // FieldByName()通过Name返回类的成员变量

注意,要想修改原始数据的值,给ValueOf传的必须是指针,而指针Value不能调用Set和FieldByName方法,所以得先通过Elem()转为非指针Value
未导出成员的值不能通过反射进行修改

addrValue := valueUser.Elem().FieldByName("addr")
if addrValue.CanSet() {
	addrValue.SetString("北京")
} else {
	fmt.Println("addr是未导出成员,不可Set") // 以小写字母开头的成员相当于是私有成员
}

⑦通过Value修改Slice

users := make([]*common.User, 1, 5) //len=1,cap=5
users[0] = &common.User{
	Id:     7,
	Name:   "Ricky",
	Weight: 60,
	Height: 1.80,
}

sliceValue := reflect.ValueOf(&users) // 准备通过Value修改users,所以传users的地址
// 取得slice的长度
if sliceValue.Elem().Len() > 0 {
	sliceValue.Elem().Index(0).Elem().FieldByName("Name").SetString("令狐一刀")
	fmt.Printf("1st user name change to %s\n", users[0].Name)
}

甚至可以修改slice的cap,新的cap必须位于原始的len到cap之间,即只能把cap改小

sliceValue.Elem().SetCap(3)

通过把len改大,可以实现向slice中追加元素的功能

sliceValue.Elem().SetLen(2)
// 调用reflect.Value的Set()函数修改其底层指向的原始数据
sliceValue.Elem().Index(1).Set(reflect.ValueOf(&common.User{
	Id:     8,
	Name:   "李达",
	Weight: 80,
	Height: 180,
}))
fmt.Printf("2nd user name %s\n", users[1].Name)

⑧修改map

Value.SetMapIndex()函数:往map里添加一个key-value对
Value.MapIndex()函数: 根据Key取出对应的map

u1 := &common.User{
	Id:     7,
	Name:   "Ricky",
	Weight: 60,
	Height: 1.80,
}
u2 := &common.User{
	Id:     8,
	Name:   "Ricky",
	Weight: 60,
	Height: 1.80,
}
userMap := make(map[int]*common.User, 5)
userMap[u1.Id] = u1

mapValue := reflect.ValueOf(&userMap) // 准备通过Value修改userMap,所以传userMap的地址
mapValue.Elem().SetMapIndex(reflect.ValueOf(u2.Id), reflect.ValueOf(u2)) // SetMapIndex 往map里添加一个key-value对
mapValue.Elem().MapIndex(reflect.ValueOf(u1.Id)).Elem().FieldByName("Name").SetString("令狐一刀") // MapIndex 根据Key取出对应的map
for k, user := range userMap {
	fmt.Printf("key %d name %s\n", k, user.Name)
}

⑨调用函数

valueFunc := reflect.ValueOf(Add) // 函数也是一种数据类型
typeFunc := reflect.TypeOf(Add)
argNum := typeFunc.NumIn() // 函数输入参数的个数
args := make([]reflect.Value, argNum) // 准备函数的输入参数
for i := 0; i < argNum; i++ {
	if typeFunc.In(i).Kind() == reflect.Int {
		args[i] = reflect.ValueOf(3) // 给每一个参数都赋3
	}
}
sumValue := valueFunc.Call(args) // 返回[]reflect.Value,因为go语言的函数返回可能是一个列表
if typeFunc.Out(0).Kind() == reflect.Int {
	sum := sumValue[0].Interface().(int) // 从Value转为原始数据类型
	fmt.Printf("sum=%d\n", sum)
}

⑩调用成员方法

common.User{
	Id:     7,
	Name:   "Ricky",
	Weight: 60,
	Height: 1.80,
}
valueUser := reflect.ValueOf(&user) // 必须传指针,因为BMI()在定义的时候它是指针的方法
bmiMethod := valueUser.MethodByName("BMI") // MethodByName()通过Name返回类的成员变量
resultValue := bmiMethod.Call([]reflect.Value{}) // 无参数时传一个空的切片
result := resultValue[0].Interface().(float32)
fmt.Printf("bmi=%.2f\n", result)

// Think()在定义的时候用的不是指针,valueUser可以用指针也可以不用指针
thinkMethod := valueUser.MethodByName("Think")
thinkMethod.Call([]reflect.Value{})

valueUser2 := reflect.ValueOf(user)
thinkMethod = valueUser2.MethodByName("Think")
thinkMethod.Call([]reflect.Value{})

3.3 创建对象

①创建struct

user :=t := reflect.TypeOf(common.User{})
value := reflect.New(t) // 根据reflect.Type创建一个对象,得到该对象的指针,再根据指针提到reflect.Value
value.Elem().FieldByName("Id").SetInt(10)
user := value.Interface().(*common.User) // 把反射类型转成go原始数据类型Call([]reflect.Value{})

②创建slice

var slice []common.User
sliceType := reflect.TypeOf(slice)
sliceValue := reflect.MakeSlice(sliceType, 1, 3)
sliceValue.Index(0).Set(reflect.ValueOf(common.User{
	Id:     8,
	Name:   "张三",
	Weight: 80,
	Height: 180,
}))
users := sliceValue.Interface().([]common.User)
fmt.Printf("1st user name %s\n", users[0].Name)

③创建map

var userMap map[int]*common.User
mapType := reflect.TypeOf(userMap)
// mapValue:=reflect.MakeMap(mapType)
mapValue := reflect.MakeMapWithSize(mapType, 10)

user := &common.User{
	Id:     7,
	Name:   "Ricky",
	Weight: 60,
	Height: 1.80,
}
key := reflect.ValueOf(user.Id)
mapValue.SetMapIndex(key, reflect.ValueOf(user)) // SetMapIndex 往map里添加一个key-value对
mapValue.MapIndex(key).Elem().FieldByName("Name").SetString("令狐一刀") // MapIndex 根据Key取出对应的map
userMap = mapValue.Interface().(map[int]*common.User)
fmt.Printf("user name %s %s\n", userMap[7].Name, user.Name)

reflect包里除了MakeSlice()和MakeMap(),还有MakeChan()和MakeFunc()

4. 自行实现json序列化

所谓序列化即把struct实例转为string,比如定义了User和Book两个struct

type User struct {
    Name string
    Age int
    Sex byte `json:"gender"`
}
type Book struct {
    ISBN string `json:"isbn"`
    Author User `json:"author"`
    Keywords []string `json:"kws"`
}

Book的实例序列化后为

{
	"isbn": "4243547567",
	"author": {
		"Name": "钱钟书",
		"Age": 57,
		"gender": 1
	},
	"kws": ["爱情", "民国", "留学"]
}

序列化实现思路:

  • 从内向外、从简单到复杂地考虑序列化问题
    • 如果要序列化一个int、float、string,很简单
    • 如果要序列化一个slice,则在第1步的基础上用[]括起来
    • 如果要序列化一个struct,FieldName直接打印出来,FieldValue的序列化可以参考第1、2步
    • 如果struct内部还嵌套了struct,则递归调用第3步
  • 通过反射解析struct,得到json key和struct FieldName的对应关系
  • 如果FieldValue是基本的值类型,则通过反射给FieldValue赋值很简单
  • 如果FieldValue是slice类型,则需要通过反射先创建一个slice,再给slice里的每个元素赋值
  • 如果FieldValue是是内嵌struct,则递归调用反序列化函数,给FieldValue赋值
  • 如果FieldValue是是内嵌struct指针,则需要创建内嵌struct对应的实例(申请内存空间),再递归调用反序列化函数,给FieldValue赋值

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

『 MySQL篇 』:MySQL表的CURD操作

&#x1f4e2; MySQL 系列专栏持续更新中 … MySQL专栏 ​ 目录 目录一、SQL语句- SQL通用语法- 注释- SQL语句分类二、 基础表操作- 创建表- 查看库中的表- 查看表结构- 删除表- 重命名表三、MySQL 中的增删查改操作- 增加&#xff08;insert语句&#xff09;- 查询(select语…

CSS 使用 @font-face 引入外部字体

CSS 使用 font-face 引入外部字体下载所需字体到本地把下载字体文件放入font文件夹里定义字体引用字体结果&#x1f62c;没有退路时&#xff0c;潜能就发挥出来了 CSS 中使用开源字体 得意黑 得意黑的字体是真的好看 ✨推荐使用 下载所需字体到本地 这里介绍一款不错的中文字…

Shiro:核心组件、配置类、多Realm场景、自定义拦截器、实战场景

目录Shiro 的核心组件Shiro 认证流程Shiro 授权流程单 RealmShiro 登陆认证 SimpleAuthenticationInfo 对象多 RealmShiroConfigShiro过滤器配置 ShiroFilterFactoryBeanShiro自定义过滤器Shiro 过滤器执行链路梳理代码自取层级结构Login.javaBearerTokenRealm.javaShiroRealm.…

桶排序详细说明及实现-python

前言&#xff1a; 说到桶排序&#xff0c;那必定要有桶&#xff0c;那么桶的作用是什么呢&#xff1f;桶的作用就是将序列分为若干份放到桶中&#xff0c;每个桶中能装入的数量范围是一定的&#xff0c;只有最后一个桶可以设置装入很多。这是因为当分的桶一定时&#xff0c;前面…

SpringMVC-基础入门

文章目录SpringMVC1&#xff0c;SpringMVC概述2&#xff0c;SpringMVC入门案例2.1 需求分析2.2 案例制作步骤1:创建Maven项目步骤2:补全目录结构步骤3:导入jar包步骤4:创建配置类步骤5:创建Controller类步骤6:使用配置类替换web.xml步骤7:配置Tomcat环境步骤8:启动运行项目步骤…

【软考】系统集成项目管理工程师(十五)项目采购管理

一、项目采购管理概述二、项目采购管理子过程1. 编制采购管理计划2. 实施采购3. 控制采购4. 结束采购三、招投标1. 招标人的权利和义务2. 招标代理机构的权利和义务3. 招标方式和招投标程序4. 相关的法律责任一、项目采购管理概述 采购意味着从外界来源获得商品或服务,采购一…

Vue3组件初始化流程分析

本文主要来分析 vue3 组件的初始化(基于runtime-core(核心运行时)包)&#xff0c;将从createApp、mount 等常用 API 入手来分析组件的挂载、普通元素的挂载流程。 createApp 1、创建一个应用实例。使用方式如下: import { createApp } from vue import App from ./App.vueco…

进制转换(二进制、八进制、十进制、十六进制)超详细版

今天来总结一下各种进制转换问题&#xff0c;详细齐全易于理解&#xff0c;希望对你有帮助哦&#xff01; 各种进制之间的相互转换 先从我们最熟悉的十进制入手吧&#xff0c;其他进制与十进制的转换方法都是一样的。 整型有4种进制形式&#xff1a; 1.十进制&#xff1a; …

[ChatGPT]

最近hatGPT火爆全宇宙&#xff0c;几乎所有圈内人都在谈论这个美国人工智能公司OpenAI发布免费机器人对话模型ChatGPT&#xff08;GPT-3.5系列&#xff09;&#xff0c;模型中首次采用RLHF&#xff08;从人类反馈中强化学习&#xff09;方式。模型目前处于测试阶段&#xff0c;…

Windows 服务器刷题(2)(带答案)

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.刷题 前言 本章将会讲解Windows服务器刷题&#xff08;2&#xff09; 一…

新年礼物已收到!2022 Apache IoTDB Commits 数量排名 3/351!

社区喜报&#xff01;据 The Apache Software Foundation 官方 Projects Statistics&#xff08;项目信息统计网站&#xff09;的实时数据显示&#xff0c;Apache IoTDB 在过去 12 个月&#xff08;即 2022 年度&#xff09;共发表 6829 Commits&#xff0c;排名 2022 年度 Apa…

C++ Primer笔记——默认移动操作、移动迭代器、左右值引用成员函数、标准库仿函数、function包装器

目录 一.P476 合成的移动操作 二.P480 移动迭代器 三.P483 右值和左值引用成员函数 四.P510 标准库定义的仿函数 五.P512 标准库function类型&#xff08;包装器&#xff09; 一.P476 合成的移动操作 什么时候会有默认的移动构造和移动赋值函数&#xff0c;需满足以下几点…

Java设计模式-中介者模式Mediator

介绍 中介者模式&#xff08;Mediator Pattern&#xff09;&#xff0c;用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用&#xff0c;从而使其耦合松散&#xff0c;而且可以独立地改变它们之间的交互。中介者模式属于行为型模式&#xff0c;使代…

linux搭建webapp实战

首先介绍下linux&#xff0c;linux因其开源&#xff0c;定制化高&#xff0c;安全等原因&#xff0c;成为了目前web应用部署首选的操作系统&#xff0c;linux操作系统有很多版本&#xff0c;常见的有centos&#xff0c;debian&#xff0c;RHLE&#xff0c;redhat&#xff0c;乌…

【Linux】gcc/g++编译器、make/Makefile自动化构建工具

作者&#xff1a;小卢 专栏&#xff1a;《Linux》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 目录 1.gcc/c的概念&#xff1a; 2.程序编译过程详解&#xff1a; 2.1程序编译过程&#xff1a; 2.…

微电网(风、光、储能、需求响应)【Simulink 仿真实现】

目录 1 展现 2 典型几个介绍 2.1 采用PR的三相逆变器电压控制 2.2 太阳能直流微电网系统 2.3 主电网故障时的交流微电网性能 2.4 混合光伏、双馈发电和电池能源的微电网集成 3 写在后面 4 完整资源 1 展现 随便打开一个&#xff0c;就以第一个&#xff08;采用PID的三…

【GD32F427开发板试用】macOS/Linux系统开发环境搭建(开发、编译、烧录、调试)

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;HonestQiao 我日常的工作环境使用的是macOS系统或者Linux系统&#xff0c;万不得已才使用Windows。 GD32官方提供了Keil MDK-ARM和IAR两款工具…

如何使用Dx命令将jar打包成Dex,需要使用D8命令

使用dx打包的完整命令&#xff0c;将D:\ssh1.0.jar打包成MyClass.dex&#xff0c;MyClass.dex将会输出在当前CMD命令窗口的目录下。dx --dex --no-strict --min-sdk-version26 --outputMyClass.dex D:\ssh1.0.jar运行结果&#xff1a;不要使用dx命令&#xff0c;打出来的dex可以…

NestJS 项目实战 技术选型 (文末附视频)

前言 通过上一章的学习&#xff0c;我们了解了网关系统&#xff0c;并且针对要做的功能做了项目架构设计与需求拆解。 那在一个项目正式开发之前&#xff0c;我们还需要做一个技术调研&#xff0c;从开发框架、使用的工具、数据库等等进行一系列的预研&#xff0c;避免在业务…

嘿~ 基于分布式架构的技术交流社区(WhiteHoleV0.7)即将竣工!

文章目录前言项目介绍WhiteHole期望立项作者功能/模块简介用户模块问答模块社区模块博文模块Next前言 拖更&#xff0c;拖延了这么久&#xff0c;耗时超过3个月的项目&#xff0c;WhiteHoleV0.7 版本即将迎来最后的收尾工作。当然考虑到服务成本&#xff0c;和开发进度&#x…