Go八股(Ⅳ)***slice,string,defer***

news2024/11/23 16:30:09

***slice,string,defer***

1.slice和arry的区别

arry:

Go语言中arry即为数据的一种集合,需要在声明时指定容量和初值,且一旦声明就长度固定,访问时按照索引访问。通过内置函数len可以获取数组中的元素个数。

初始化

数组在初始化时必须指定大小和初值,不过Go语言为我们提供了其他灵活的方式。

例如:

func main() {
    var arr [5]int //声明了一个大小为5的数组,初始化值为{0,0,0,0,0}
    arr := [5]int{1}//声明并初始化一个大小为5的数组,初始值为{1,0,0,0,0}
    arr := [...]int{1, 2, 3}//通过“...”自动获取数组长度,初始化后值为{1,2,3}
    arr := [...]int{4:1}//指定序列号为4的元素数值为1,初始值为{0,0,0,0.1}
}

数组作参数传入

Go语言数组作为参数传入时,必须指定参数数组的大小,且传入的大小必须与指定的大小一致,数组为按值传递的,函数内对数组的值的改变不影响初始数组

例如:

package main

import "fmt"

func PrintArry(arr [5]int) {
	arr[0] = 5
	fmt.Println(arr)
}

func main() {
	var arr [5]int = [5]int{1, 2, 3, 4, 5}
	PrintArry(arr)
	fmt.Println(arr)
}

运行结果确实这样的:

Slice

切片是Go语言中极为重要的一种数据类型,可以理解为动态长度的数组(虽然实际上Slice结构内包含了一个数组),访问时可以按照数组的方式访问,也可以通过切片操作访问。Slice有三个属性:指针、长度和容量。指针即Slice名,指向的为数组中第一个可以由Slice访问的元素;长度指当前slice中的元素个数,不能超过slice的容量;容量为slice能包含的最大元素数量,但实际上当容量不足时,会自动扩充为原来的两倍。通过内置函数lencap可以获取slice的长度和容量

初始化

Slice在初始化时需要初始化指针,长度和容量,容量未指定时将自动初始化为长度的大小。可以通过获取数组的引用,获取数组/Slice的切片构建或是make函数初始化数组。

例如

s:=[]int{1,2,3}//通过数组的引用初始化,值为{1,2,3},长度和容量为3

arr:=[5]int{1,2,3,4,5}
s:=arr[0:3] //通过数组的切片初始化,值为{1,2,3},长度和容量为5

s:=make([]int,4)//通过make初始化,值为{0,0,0,0},长度和容量wei4

s:=make([]int,3,5)//通过make初始化值为{0,0,0},长度为3,容量为5

气质特别要注意的时通过切片方式初始化。若时通过对Slice的切片进行初始化,实际上初始化之后的结构如图所示:

此时x的值为[2,3,5,7,11],y的值为[3,5,7],且两个slice的指针指向的是同一个数组,也即x中的元素的值的改变将会导致y中的值也一起改变

这样的初始化方式可能会导致内存被过度占用,如只需要使用一个极大的数组中的几个元素,但是由于需要指向整个数组,所以整个数组在GC时都无法被释放,一直占用内存空间。故使用切片操作进行初始化时,最好使用append函数将切片出来的数据复制到一个新的slice中,从而避免内存占用陷阱。

Slice作为函数参数

Go语言中Slice作为函数参数传递时为按引用传递的,函数内对Slice内元素的修改将导致函数外的值也发生改变,不过由于传入函数的时一个指针的副本,所以对该指针的修改不会导致原来的指针的变化(例如append不会改变原来slice的值)。

例如

func PrintSlice(s []int) {
	s = append(s, 4)
	s[0] = -1
	fmt.Println(s)
}

func main() {
	s := []int{1, 2, 3, 4, 5}
	s1 := s[0:3]
	fmt.Println("s:", s)
	fmt.Println("s1:", s1)
	PrintSlice(s1)
	fmt.Println("s:", s)
	fmt.Println("s1:", s1)
}

总的来说

  • 数组长度不能改变,初始化后长度就是固定的;切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
  • 结构不同,数组是一串固定数据,切片描述的是截取数组的一部分数据,从概念上说是一个结构体。
  • 初始化方式不同,如上。另外在声明时的时候:声明数组时,方括号内写明了数组的长度或使用...自动计算长度,而声明slice时,方括号内没有任何字符。
  • unsafe.sizeof的取值不同,unsafe.sizeof(slice)返回的大小是切片的描述符,不管slice里的元素有多少,返回的数据都是24字节。unsafe.sizeof(arr)的值是在随着arr的元素的个数的增加而增加,是数组所存储的数据内存的大小。
  • 函数调用时的传递方式不同,数组按值传递,slice按引用传递。

Slice的扩容机制

1.18版本之前

Go1.18之前切片的扩容是以容量1024为临界点,当旧容量 < 1024个元素,扩容变成2倍;当旧容量 > 1024个元素,那么会进入一个循环,每次增加25%直到大于期望容量。

1.18版本之后

当新切片需要的容量cap大于两倍扩容的容量,则直接按照新切片需要的容量扩容;
当原 slice 容量 < threshold 的时候,新 slice 容量变成原来的 2 倍;
当原 slice 容量 > threshold,进入一个循环,每次容量增加(旧容量+3*threshold)/4。

有什么好处呢,首先是双倍容量扩容的最大阈值从1024降为了256,只要超过了256,就开始进行缓慢的增长。其次是增长比例的调整,之前超过了阈值之后,基本为恒定的1.25倍增长,而现在超过了阈值之后,增长比例是会动态调整的,随着切片容量的变大,增长比例逐渐向着1.25进行靠近

内存对齐

以下是内存对齐得源码;

switch {
      // 当数组元素的类型大小为1时,不需要乘除计算就能够得到所需要的值  
      case et.size == 1:
        lenmem = uintptr(old.len)
        newlenmem = uintptr(cap)
        //前面两个语句只是对老长度和预期cap的类型转换,关键是下一个语句决定了newcap的长度
        // 内存对齐
        capmem = roundupsize(uintptr(newcap))
        // 判断是否溢出
        overflow = uintptr(newcap) > maxAlloc
        newcap = int(capmem)
      // 当类型大小是8个字节时  
      case et.size == sys.PtrSize:
        lenmem = uintptr(old.len) * sys.PtrSize
        newlenmem = uintptr(cap) * sys.PtrSize
        capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
        overflow = uintptr(newcap) > maxAlloc/sys.PtrSize
        newcap = int(capmem / sys.PtrSize)
      // 当类型大小是2的幂次方时  
      case isPowerOfTwo(et.size):
        var shift uintptr
        if sys.PtrSize == 8 {
            // Mask shift for better code generation.
            shift = uintptr(sys.Ctz64(uint64(et.size))) & 63
        } else {
            shift = uintptr(sys.Ctz32(uint32(et.size))) & 31
        }
        lenmem = uintptr(old.len) << shift
        newlenmem = uintptr(cap) << shift
        capmem = roundupsize(uintptr(newcap) << shift)
        overflow = uintptr(newcap) > (maxAlloc >> shift)
        newcap = int(capmem >> shift)
      // 当大小不是上面任何一种时  
      default:
        lenmem = uintptr(old.len) * et.size
        newlenmem = uintptr(cap) * et.size
        capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))
        capmem = roundupsize(capmem)
        newcap = int(capmem / et.size)
    }

之所以进行内存对齐,是因为更加合理得分配内存,如果分配得太多就会出现内存得浪费,如果分配得太少就会出现性能过低情况。

_MaxSmallSize: 其值为32768,即32kb大小。在Go中,当对象大小超过32kb时,内存分配策略和小于等于32kB时是有区别的。(对于内存大于32KB的称为大对象,会单独处理,对于内存小于等于32KB的对象,会在跨度类数组中找到合适的数组大小,其实这一步也就进行了内存对齐操作,找到了最小的对齐内存,所以往往newcap大小会比之前的稍有不同,一般都是向上取了一些值)
smallSizeMax: 其值为1024字节。
smallSizeDiv: 其值为8字节。
largeSizeDiv: 其值为128字节。
_PageSize: 8192字节,即8kb大小。Go按页来管理内存,而每一页的大小就为8kb。
class_to_size:Go中的内存分配会按照不同跨度(也可理解为内存大小,有点类似于段),其中跨度是指,go每一页的大小是8kb,对datablock划分成不同大小的内存块,
除了最小的8b,其余的大小都是8*2n,即8,16,32,48,…32768,具体规则间隔为8,16,32,64,128…,对应class_to_size的数组(1.18之后好像多了一个24元素)
将内存分割成不同内存块链表。当需要分配内存时,按照对象大小去匹配最合适的跨度找到空闲的内存块儿。Go中总共分为67个跨度,class_to_size是一个长度为68的数组,分别记录0和这67个跨度的值。
size_to_class8: 这是一个长度为129的数组,代表的内存大小区间为0~1024字节。以索引i为例,此位置的对象大小m为i
smallSizeDiv,size_to_class8[i]的值为class_to_size数组中跨度最接近m的下标。
size_to_class128:这是一个长度为249的数组,代表的内存大小区间为1024~32768字节。以索引i为例,此位置的对象大小m为smallSizeMax
i*largeSizeDiv, size_to_class128[i]的值为class_to_size数组中跨度最接近m的下标。
divRoundUp: 此函数返回a/b向上舍入最接近的整数。
alignUp: alignUp(size, _PageSize) = _PageSize * divRoundUp(size,
_PageSize)。

 上面得一大块内容,简而言之就是Go语言未来更好得分配内存,将每次扩容得量划分为67个区间

例如:

s3 := []int{1, 2}
s3 = append(s3, 3, 4, 5)
fmt.Println(cap(s3))

根据前文知,所需容量为5,又因所需容量大于2倍当前容量,故新容量也为5。

又因为int类型大小为8(等于64位平台上的指针大小),所以实际需要的内存大小为5 * 8 = 40字节。而67个跨度中最接近40字节的跨度为48字节,所以实际分配的内存容量为48字节。

最终计算真实的容量为48 / 8 = 6,和实际运行输出一致。

零切片,空切片,nil切片的区别

零切片

简单来说就是切片中的值都为0,切片已经分配空间,并且值也不为空

// 创建零切片
slice4 := make([]int,2,5)
fmt.Println(slice4,*(*reflect.SliceHeader)(unsafe.Pointer(&slice4))) // 输出:[0 0] {824634474496 2 5}

空切片

空切片就是已经初始化过空间的切片,但是切片中并没有内容

通常用make或者字面量进行初始化

s1 := []int{} // s1 是一个空切片,通过字面量创建
s2 := make([]int, 0) // s2 也是一个空切片,通过 make 创建

nil切片

通常使用var 来定义,既没有分配空间,更不用说切片的长度

var slice []int
fmt.Println(slice,*(*reflect.SliceHeader)(unsafe.Pointer(&slice))) // 输出:[] {0 0 0}

 string类型

string标准概念

在go的标准包中定义如下:

// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
type string string

  • string是8bit字节的集合,通常是但并不一定非得是UTF-8编码的文本。
  • string可以为空(长度为0),但不会是nil。
  • string对象不可以修改。
type stringStruct struct {
	str unsafe.Pointer		//字符串首地址,指向底层字节数组的指针
	len int					//字符串长度
}

对于字符串Hello,实际底层结构如下:

3.string类型的操作

3.1  声明

var str string
str = "Hello"

具体的字符串构建过程,是先根据字符串构建stringStruct,再转换成string:

func gostringnocopy(str *byte) string {//根据字符串地址构建string
	ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)} //先构造stringStruct
	s := *(*string)(unsafe.Pointer(&ss))//再将stringStruct转换成string
	return s
}

3.2.1  []byte转string

[]byte切片转换成string很简单(语法上):

func GetStringBySlice(s []byte) string {
    return string(s)
}

下面是转化时的内存图:

转换过程如下几步:

  1. 根据切片的长度申请内存空间,假设内存地址为p,切片长度为len(s)
  2. 构建string(sting.str =p; string.len=len)
  3. 拷贝数据(切片中数据拷贝到新申请的内存空间)

 3.2.2 string类型转[]byte

 下面是转化的代码,语法上很简单

func GetSliceByString(str string) []byte {
    return []byte(str)
}

同样string类型转化成[]byte类型也需要一次内存的拷贝。

1.申请切片内存空间

2.将string拷贝到切片

3.3 字符串的拼接

在Go语言中,字符串是不可变得,拼接字符串事实上是创建了一个新的字符串,如果代码中存在大量的字符串拼接,对性能会产生影响。

下面是go语言中关于拼接字符串的源码:

func concatstrings(buf *tmpBuf, a []string) string {
	idx := 0
	l := 0 //拼接后的字符串总长度
	count := 0
	for i, x := range a {
		n := len(x)
		if n == 0 {
			continue
		}
		if l+n < l {
			throw("string concatenation too long")
		}
		l += n
		count++
		idx = i
	}
	if count == 0 {
		return ""
	}

	// If there is just one string and either it is not on the stack
	// or our result does not escape the calling frame (buf != nil),
	// then we can return that string directly.
	if count == 1 && (buf != nil || !stringDataOnStack(a[idx])) {
		return a[idx]
	}
	s, b := rawstringtmp(buf, l)//生成指定大小的字符串,返回一个string和切片,二者共享内存
	for _, x := range a {
		copy(b, x)
		b = b[len(x):]//string无法修改,只能通过切片修改
	}
	return s
}

// 生成一个新的string,返回的string和切片共享相同的空间
func rawstring(size int) (s string, b []byte) {
	p := mallocgc(uintptr(size), nil, false)

	stringStructOf(&s).str = p
	stringStructOf(&s).len = size

	*(*slice)(unsafe.Pointer(&b)) = slice{p, size, size}

	return
}

3.3.1常见的拼接方式

使用“+”

s1+s2+s3

使用fmt.Sprintf

fmt.Sprintf("%s%s",s1,s2)

使用strings.Builder

func BuilderConcat(n int, str string) string {
	var builder strings.Builder
	for i := 0; i < n; i++ {
		builder.WriteString(str)
	}
	return builder.String()
}

使用bytes.Buffer

func bufferConcat(n int, s string) string {
	buf := new(bytes.Buffer)
	for i := 0; i < n; i++ {
		buf.WriteString(s)
	}
	return buf.String()
}

 使用[]byte

func byteConcat(n int, str string) string {
	buf := make([]byte, 0)
	for i := 0; i < n; i++ {
		buf = append(buf, str...)
	}
	return string(buf)
}

3.4 字符串的截取

1.截取普通英语字符串

str := "HelloWorld"
content := str[1 : len(str)-1] 

2.截取带中文的字符串

一个中文字符确定不止一个字节,需要先将其转为[]rune,再截取后,再转为string

strRune := []rune(str)
fmt.Println("string(strRune[:4]) = ",string(strRune[:4]))

4.为什么字符串不允许修改(只读属性)

在go实现中,string不包含内存空间,只有一个内存的地址,这样做的好处是string变得非常轻量,可以很方便的进行传递而不用担心内存拷贝。

string通常指向字符串字面量,而字符串字面量存储存储位置是只读段,而不是堆或栈上,所以string不可修改。

修改字符串时,可以将字符串转换为 []byte 进行修改。

var str string = "hello"
strBytes := []byte(str)
strBytes[0] = 'H'
str = string(strBytes)
fmt.Println(str)

defer

defer

一个函数中多个defer的执行顺序

defer 的作用就是把defer关键字之后的函数执行压入一个栈中延迟执行,多个defer的执行顺序是后进先出LIFO,也就是先执行最后一个defer,最后执行第一个defer

func main() {
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
}

return返回值的运行机制

1.返回值赋值

2.RET指令

而defer执行在赋值之后,RET之前。

defer,return,返回值三者执行的顺序是:return最先执行,先将结果写入返回值中;接着defer开始执行一些收尾工作;最后函数携带返回值退出

 不带命名返回值

如果函数的返回值是无名的(不带命名的返回值),则go语言会在执行return的时候执行一个类似创建一个临时变量作为保存return值得动作。

func main() {
	fmt.Println("return i:", test())
}

func test() int {
	i := 0
	defer func() {
		i++
		fmt.Println("defer1 ---i:", i)
	}()
	defer func() {
		i++
		fmt.Println("defer2 ---i:", i)
	}()
	return i
}

运行结果如下图所示:

 

如图所示,函数执行时先返回值然后再执行defer之后得函数。

上面得例子实际上进行了三步操作:

(1)赋值,因为返回值没有命名,所以return默认指定了一个返回值(假设为s),首先将i赋值为s,i初始值是0,所以s也是0

(2)后续的defer操作因为是针对i进行的,所以不会影响s,此后s不会更新,所以s还是0

(3)返回值,return s,也就是return 0

var i int

s:=i

return s

带命名的返回值

有名返回值的函数,由于返回值在函数定义的时候已经将该变量进行定义,在执行return的时候会先执行返回值保存操作,而后续的defer函数会改变这个给返回值(虽然defer实在return之后执行的),由于使用函数定义的变量,所以执行defer操作后会对该变量的修改会影响的return 的值

func main() {
	fmt.Println("return i:", test())
}

func test() (i int) {

	defer func() {
		i++
		fmt.Println("defer1 ---i:", i)
	}()
	defer func() {
		i++
		fmt.Println("defer2 ---i:", i)
	}()
	return i
}

运行结果如下;

这种情况其实就相当于一直在操作一个内存地址中的数。

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

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

相关文章

STM32H503开发(2)----STM32CubeProgrammer烧录

STM32H503开发----2.STM32CubeProgrammer烧录 概述硬件准备视频教学样品申请源码下载参考程序自举模式BOOT0设置UART烧录USB烧录 概述 STM32CubeProgrammer (STM32CubeProg) 是一款用于编程STM32产品的全功能多操作系统软件工具。 它提供了一个易用高效的环境&#xff0c;通过…

ubuntu下aarch64-linux-gnu(交叉编译) gdb/gdbserver(二)

ubuntu下aarch64-linux-gnu(交叉编译) gdb/gdbserver&#xff08;二&#xff09; 本教程作为gdb/gdbserver编译安装教程的一个补充&#xff0c;教会大家如何使用gdb/gdbserver进行远程调试。 如上图所示&#xff0c;我们需要将编译后的gdbserver上传至目标设备&#xff0c;其上…

架构零散知识点

1 数据库 1.1 数据库范式 有一个学生表&#xff0c;主键是学号&#xff0c;含有学生号、学生名、班级、班级名&#xff0c;违反了数据库第几范式&#xff1f; --非主属性不依赖于主键&#xff0c;不满足第二范式 有一个订单表&#xff0c;包含以下字段&#xff1a;订单ID&…

【时间之外】IT人求职和创业应知【31】

目录 新闻一&#xff1a;2024年“秦创原沣东杯”陕西省科技工作者创新创业大赛颁奖仪式暨沣东新城机器人产业发展大会盛大启幕 新闻二&#xff1a;声网CEO赵斌&#xff1a;RTE将成为生成式AI时代AI Infra的关键部分 新闻三&#xff1a;“5G工业互联网”融合应用试点城市名单…

移动开发(七):.NET MAUI使用RESTAPI实现查询天气笔记

目录 一、接口准备 二、实体部分 三、页面部分 四、后台代码逻辑 五、总结 在移动开发过程中,第三方对接是非常常见的。今天给大家分享.NET MAUI如何使用REST API实现输入城市名称查询天气的示例,希望对大家学习.NET MAUI可以提供一些帮助! 一、接口准备 首先我们需要…

Javascript中如何实现函数缓存?函数缓存有哪些应用场景?

#一、是什么 函数缓存&#xff0c;就是将函数运算过的结果进行缓存 本质上就是用空间&#xff08;缓存存储&#xff09;换时间&#xff08;计算过程&#xff09; 常用于缓存数据计算结果和缓存对象 解释 const add (a,b) > ab; const calc memoize(add); // 函数缓存…

【网络安全 | 并发问题】Nginx重试机制与幂等性问题分析

未经许可,不得转载。 文章目录 业务背景Nginx的错误重试机制proxy_next_upstream指令配置重试500状态码非幂等请求的重试问题幂等性和非幂等性请求non_idempotent选项的使用解决方案业务背景 在现代互联网应用中,高可用性(HA)是确保系统稳定性的关键要求之一。为了应对服务…

利用游戏引擎的优势

大家好&#xff0c;我是小蜗牛。 在当今快速发展的游戏产业中&#xff0c;选择合适的游戏引擎对开发者来说至关重要。Cocos Creator作为一款功能强大且灵活的游戏引擎&#xff0c;为开发者提供了丰富的工具和资源&#xff0c;使他们能够高效地开发出优秀的游戏。本文将探讨如何…

财务源码 财务软件 SaaS 云财务

&#x1f50d; 专业财务源码&#xff0c;助您快速开展财务管理&#xff01;&#x1f4c8; &#x1f3af; 我们提供一系列高质量、可定制、易于使用的财务源码&#xff0c;帮助您快速搭建强大的财务管理系统。无论是小型企业、中型企业&#xff0c;还是个人用户&#xff0c;我们…

数据流图,学习笔记

目录 一、数据流图的基本元素 外部实体&#xff08;External Entity&#xff09; 加工&#xff08;Process&#xff09; 数据存储&#xff08;Data Store&#xff09; 数据流&#xff08;Data Flow&#xff09; 二、数据流图的层次结构 顶层数据流图 中层数据流图 底层…

docker镜像文件导出导入

1. 导出容器&#xff08;包含内部服务&#xff09;为镜像文件&#xff08;docker commit方法&#xff09; 原理&#xff1a;docker commit命令允许你将一个容器的当前状态保存为一个新的镜像。这个新镜像将包含容器内所有的文件系统更改&#xff0c;包括安装的软件、配置文件等…

Android中桌面小部件framework层使用到的设计模式

在Android中&#xff0c;桌面小部件&#xff08;App Widget&#xff09;的Framework层采用了多种设计模式&#xff0c;以实现模块化、可维护性和高效的交互。 以下是Android桌面小部件Framework层中常用的设计模式及其具体应用&#xff1a; 1. 观察者模式&#xff08;Observe…

7.《双指针篇》---⑦三数之和(中等偏难)

题目传送门 方法一&#xff1a;双指针 1.新建一个顺序表用来返回结果。并排序数组。 2.for循环 i 从第一个数组元素遍历到倒数第三个数。 3.如果遍历过程中有值大于0的则break&#xff1b; 4.定义左右指针,以及target。int left i 1, right n - 1; int target -nums[i];…

DBeaver工具连接Hive

DBeaver工具连接Hive 首先解压安装包dbeaver-ce-latest-x86_64-setup.zip,并安装dbeaver-ce-latest-x86_64-setup.exe; 安装Kerberos客户端4.1-amd64.msi; 查看集群节点/etc/hosts文件内容,并追加到C:\Windows\System32\drivers\etc\hosts; 下载集群用户keytab文件,并解压…

从零开始 blender插件开发

blender 插件开发 文章目录 blender 插件开发环境配置1. 偏好设置中开启相关功能2. 命令行打开运行脚本 API学习专有名词1. bpy.data 从当前打开的blend file中&#xff0c;加载数据。2. bpy.context 可用于获取活动对象、场景、工具设置以及许多其他属性。3. bpy.ops 用户通常…

深入理解JVM

文章目录 1. JVM内存区域划分2. JVM中类加载过程类加载&#xff08;1&#xff09;类加载的基本流程&#xff08;2&#xff09;双亲委派模型 《深入理解java虚拟机》 在这本书前&#xff0c;面试官对于JVM也不是很了解。 这本书主要还是写个一下开发 JVM 的人。 1. JVM内存区域…

c# 开发web服务 webserver

024-11-10<<<<<<<<<<<<<<<<<<<<<<<<<< 开始插件前Cyber_CallWeb acajax_dac_database_viewer 2024-11-10<<<<<<<<<<<<<<<<<<<<…

WPS 默认模板修改

重装系统把word自定义样式搞没了&#xff0c;安装office时间太长&#xff0c;转战wps 解决方案 打开wps 点击【新建】word空白文档 设置修改你自己的样式 点击文件–另存为–Microsoft Word 带宏的模板文件&#xff08;*.dotm&#xff09; 另存路径为如下&#xff1a; 查…

使用vite构建一个react网站,并部署到Netlify上

这篇教程中&#xff0c;我会教你如何用vite快速构建一个react网站&#xff0c;并把网站免费部署到Netlify上&#xff0c;让别人可以经由网址访问你的react网站。 1. 使用vite构建基础框架 npm create vitelatestcd vite-project npm install npm run dev2. 网站内容设计 3. 构…

GPT-5 一年后发布?对此你有何期待?

GPT-5 一年后发布?对此你有何期待? 在最新技术的洪流中,GPT-5即将登场。你是否在思考,它将为我们的生活和工作带来哪些变革?接下来的探索,或许可以启发你对未来的想象。让我们一起深入这场关于未来AI语言模型的讨论。 一、技术上的提升 1.1 更强的语言理解能力 想象一…