Golang字符串string详解_golang string-程序员宅基地

技术标签: golang  

1、 string的定义

Golang中的string的定义在reflect包下的value.go中,定义如下:

StringHeader 是字符串的运行时表示,其中包含了两个字段,分别是指向数据数组的指针和数组的长度。

// StringHeader is the runtime representation of a string.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type StringHeader struct {
    
	Data uintptr
	Len  int
}

 

2、string不可变

Golang中的字符串是不可变的,不能通过索引下标的方式修改字符串中的数据:

在这里插入图片描述

运行代码,可以看到编译器报错,string是不可变的

在这里插入图片描述
 
但是能不能进行一些骚操作来改变元素的值呢?

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
    

	a := "hello,world"
	b := a[6:]


	bptr := (*reflect.StringHeader) (unsafe.Pointer(&b))

	fmt.Println(a)
	fmt.Println(b)

	*(*byte)(unsafe.Pointer(bptr.Data)) = '.'

	fmt.Println(a)
	fmt.Println(b)
}

// 运行结果
hello,world
world
unexpected fault address 0x49d7e3
fatal error: fault
[signal 0xc0000005 code=0x1 addr=0x49d7e3 pc=0x4779fa]

goroutine 1 [running]:
runtime.throw(0x49c948, 0x5)
	C:/Program Files/Go/src/runtime/panic.go:1117 +0x79 fp=0xc0000dbe90 sp=0xc0000dbe60 pc=0x405fd9
runtime.sigpanic()
	C:/Program Files/Go/src/runtime/signal_windows.go:245 +0x2d6 fp=0xc0000dbee8 sp=0xc0000dbe90 pc=0x4189f6
main.main()
	F:/go_workspace/src/code/string_test/main.go:20 +0x13a fp=0xc0000dbf88 sp=0xc0000dbee8 pc=0x4779fa
runtime.main()
	C:/Program Files/Go/src/runtime/proc.go:225 +0x256 fp=0xc0000dbfe0 sp=0xc0000dbf88 pc=0x4087f6
runtime.goexit()
	C:/Program Files/Go/src/runtime/asm_amd64.s:1371 +0x1 fp=0xc0000dbfe8 sp=0xc0000dbfe0 pc=0x435da1

Process finished with the exit code 2

在上面的代码中,因为在go语言中不能进行指针的加减运算,因此取切片,让b的Data指针指向’,'所在的位置。然后把"hello,world"中的逗号改为点,但是发现还是不行,程序直接崩溃了。看来go语言中的指针得到了大大的限制,设计者并不想让程序员过度使用指针来写出一些不安全的代码。

 

3、使用string给另一个string赋值

Golang中的字符串的赋值并不是拷贝底层的字符串数组,而是数组指针和长度字段的拷贝。例如:当我们定义了一个字符串 a := “hello,world” 然后定义了 b := a 底层所做的操作只是创建了两个StringHeader的结构体,它们的Data字段都指向同一段数据,如下图:

在这里插入图片描述

我们可以利用代码来证实这一点:

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
    

	a := "hello,world"
	b := a

	fmt.Println(a)
	fmt.Println(b)

	aptr := (*reflect.StringHeader) (unsafe.Pointer(&a))
	bptr := (*reflect.StringHeader) (unsafe.Pointer(&b))

	fmt.Println("a ptr:", unsafe.Pointer(aptr.Data))
	fmt.Println("b ptr:", unsafe.Pointer(bptr.Data))
}

// 运行结果
hello, world
hello, world
a ptr: 0x6bdb76
b ptr: 0x6bdb76

在上面的代码中,将a和b转换为StringHeader类型的指针,然后分别打印出,a和b的Data指针的值,发现是相同的

那么如果对a做切片赋值给b呢?

func main() {
    

	a := "hello,world"
	b := a[6:]

	fmt.Println(a)
	fmt.Println(b)

	aptr := (*reflect.StringHeader) (unsafe.Pointer(&a))
	bptr := (*reflect.StringHeader) (unsafe.Pointer(&b))

	fmt.Println("a ptr:", unsafe.Pointer(aptr.Data))
	fmt.Println("b ptr:", unsafe.Pointer(bptr.Data))
}

// 运行结果
hello,world
world
a ptr: 0xd4d849
b ptr: 0xd4d84f

0xd4d849 - 0xd4d84f = 0x000006

显然,也没有分配新的数组并拷贝数据,而是将原字符数组的指针的偏移赋给了b的StringHeader的Data

4、string重新赋值

如果对一个已经赋值的字符串重新赋值,也不会修改原内存空间,而是申请了新的内存空间,对其赋值,并指向新的内存空间。如下图:

在这里插入图片描述

也可以使用代码来证实一下:

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
    

	a := "hello,world"

	aptr := (*reflect.StringHeader) (unsafe.Pointer(&a))

	fmt.Println("a ptr:", unsafe.Pointer(aptr.Data))
	fmt.Println("a len", aptr.Len)

	a = "hello,golang"
	newAPtr := (*reflect.StringHeader) (unsafe.Pointer(&a))
	fmt.Println("b ptr:", unsafe.Pointer(newAPtr.Data))
	fmt.Println("b len:", newAPtr.Len)
}

// 运行结果
a ptr: 0x3ed7f4
a len 11
b ptr: 0x3edb2c
b len: 12

 

5、string和[]byte的互相转换

我们通常可以使用下面的方式来进行string[]byte的互相转换:

func main() {
    
	// 字符串转[]byte
	str := "hello"
	bytes := []byte(str)
	
	// []byte转字符串
	s := string(bytes)
}

但是上面的两种方式都会涉及到深拷贝,因为string是不可变的,如果不进行深拷贝,那么如果用户修改从string转换后的[]byte数据,就会发生错误。
 
如果要减少深拷贝,采用浅拷贝的方式,可以采用unsafe包,但是在使用的时候要一定要确保不能修改[]byte中的数据。否则程序会崩溃。
下面的两个转换函数提供了浅拷贝的方式:

func StringToBytes(str string) []byte {
    
	return  *(*[]byte)(unsafe.Pointer(&struct{
    
		string
		int
	}{
    str, len(str)}))
}

func BytesToString(b []byte) string {
    
	return *(*string)(unsafe.Pointer(&b))
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Peerless__/article/details/121209732

智能推荐

highcharts柱状图+折线图_highcharts3d折线图-程序员宅基地

文章浏览阅读1.4k次。<div id="container" style="min-width: 280px; height: 233px; margin: 0 auto"></div>var Ri1 = parseFloat("33.3");var Ri2 = parseFloat("25.0");var Ri3 = parseFloat("44.4_highcharts3d折线图

ResultSet结果集映射给实体类集合_t t = clazz.getconstructor().newinstance(); 属性赋值-程序员宅基地

文章浏览阅读2.6k次。public static List<Object> handler(ResultSet rs, Class<?> clazz) { List<Object> list = new ArrayList<>(); Object obj = null; try { while (rs..._t t = clazz.getconstructor().newinstance(); 属性赋值

FX3/CX3 DMA Overview_adma discriptor-程序员宅基地

文章浏览阅读1k次。. DMA Socket - A DMA socket is a FX3 device construct that maps to one end of a data path into or out of the device . DMA Buffer - A DMA buffer is a memory buffer in the FX3 system memory that is assi_adma discriptor

命令行抓log-程序员宅基地

文章浏览阅读1.9k次。命令行抓log、Log.isLoggable的使用

Android WorkManager,看这一篇就够了-程序员宅基地

文章浏览阅读1.1w次,点赞4次,收藏11次。1.简介Android上有许多可延期的后台工作选项。此代码实验室涵盖WorkManager,这是一个可延迟的后台工作的兼容,灵活且简单的库。WorkManager是Android上推荐的任务调度框架,用于可延缓的工作,并且可以执行。什么是WorkManagerWorkManager是Android Jetpack的一部分,是用于后台工作的架构组件,需要兼顾机会和有保证的执行。机会..._android workmanager

Leetcode学习之路 —— Golang实现_leetcode 多线程golang-程序员宅基地

文章浏览阅读1.6k次。由于之前一直在用C++,所以leetcode的题也是用C++做的,现在对golang越来越感兴趣,所以就打算用golang来做。其实leetcode中有一些题很不适合用Golang来做,同样的做法,因为两种语言数据结构底层实现不同,与C++比起来golang会麻烦很多很多。但是在有些时候还有一些优势,不管怎么样,做题就是锻炼思维,明白怎么做,能实现出来就好了。https://github.co..._leetcode 多线程golang

随便推点

Swift- 嵌套类型_swift4 嵌套对象-程序员宅基地

文章浏览阅读359次。struct BlackjackCard { // nested Suit enumeration enum Suit: Character { case spades = "", hearts = "♡", diamonds = "♢", clubs = "" } //..._swift4 嵌套对象

JavaFX Pane_fxml pane-程序员宅基地

文章浏览阅读1.6k次。Pane面板类主要有如下几种:栈面板类StackPane、边界面板类BorderPane、流式面板类FlowPane、网格面板类GridePane、单行面板类HBox和单列面板类VBox等几种。ps:一个节点只能添加到一个面板中JavaFX CSSsPane.setStyle("-fx-border-color: yellow;-fx-background-color: green");bt1.setStyle("-fx-border-color: red");StackPaneStackP_fxml pane

数据结构实验六之图的邻接矩阵存储实现_实验邻接矩阵的实现-程序员宅基地

文章浏览阅读657次。源代码:#ifndef MGraph_H#define MGraph_Hconst int MaxSize=10;template&lt;class DataType&gt;class MGraph{ public: MGraph(DataType a[],int n,int e); ~MGraph(){} void DFSTraverse(int v);//深度优先遍历 void BFS..._实验邻接矩阵的实现

Java 保护Excel 工作簿和工作表_free spire.xls for java怎么引入依赖-程序员宅基地

文章浏览阅读647次。出于安全原因,你可能需要保护整个工作簿或工作表。 有时,你甚至可能还需要保护某个工作表,但却保留指定的单元格进行编辑。 本文将介绍如何使用Free Spire.XLS for Java来实现这些操作。将Spire.Xls.jar 添加为依赖项方法1:下载Free Spire.XLS for Java包并解压缩,然后从lib文件夹下,将Spire.Xls.jar包作为依赖项添加到你的Java应用..._free spire.xls for java怎么引入依赖

R语言对完全随机分组实验、拉丁方实验及正交实验进行方差分析(例题,过程+代码)_r语言拉丁方实验-程序员宅基地

文章浏览阅读5.9k次,点赞13次,收藏57次。第一题题目研究5种不同的配料(A、B、C、D、E)对某一化学过程反应时间的效应。每批新材料仅够进行5次试验。每次试验大约需要1.5小时,所以一天只能做5次试验。实验者决定用拉丁方来进行实验,使用日期和批次效应可以系统地控制。分析不同配料对反应时间的效应有无显著差异。解答读取数据并进行预处理mydata = read.csv("data.csv")head(mydata)time <- factor(time)batch <- factor(batch)ingredient_r语言拉丁方实验

Ignite分布式的内存数据库简单应用_java ignite - 分布式内存数据库-程序员宅基地

文章浏览阅读4.8k次,点赞5次,收藏14次。一:简介Ignite是什么? 一个以内存为中心的分布式数据库、缓存和处理平台,可以在PB级数据中,以内存级的速度进行事务性、 分析性以及流式负载的处理。 ignite是分布式内存网格的一种实现,其基于java平台,具有可持久化,分布式事务,分布式计算等特点,此外还支持丰富的键值存储以及SQL语法(基于h2引擎),可以看成是一个分布式内存数据库。 ..._java ignite - 分布式内存数据库

推荐文章

热门文章

相关标签