内存逃逸、枚举、字符串

目录

1:内存逃逸

2:枚举iota

3:字符串

4:类型转换

 

一、内存逃逸

理解堆、栈

go的内存中应用了两种数据结构用于存放变量:

1:堆(heap):堆是用于存放进程执行中被动态分配的内存段。它的大小并不固定,可动态扩张或缩减。当进程调用 malloc 等函数分配内存时,

  新分配的内存就被动态加入到堆上(堆被扩张)。当利用 free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减);

2:栈(stack):栈又称堆栈, 用来存放程序暂时创建的局部变量,也就是我们函数的大括号{ }中定义的局部变量。

栈是先进后出,往栈中放元素的过程,称为入栈,取元素的过程称为出栈。

栈可用于内存分配,栈的分配和回收速度非常快

在程序的编译阶段,编译器会根据实际情况自动选择或者上分配局部变量的存储空间,不论使用 var 还是 new 关键字声明变量都不会影响编译器的选择。

var global *int
func f() {
    var x int
    x = 1
    global = &x
}
func g() {
    y := new(int)
    *y = 1
}

上述代码中,函数 f 里的变量 x 必须在堆上分配,因为它在函数退出后依然可以通过包一级的 global 变量找到,虽然它是在函数内部定义的。

用Go语言的术语说,这个局部变量 x 从函数 f 中逃逸了(原本在栈上面的,跑了堆上。就叫做内存逃逸)。

相反,当函数 g 返回时,变量 y 不再被使用,也就是说可以马上被回收的。因此,y 并没有从函数 g 中逃逸,

编译器可以选择在栈上分配 *y 的存储空间,也可以选择在堆上分配,然后由Go语言的 GC(垃圾回收机制)回收这个变量的内存空间。

&city escapes to heap    city逃逸到堆上面去了

所以我们知道是存在这个内存逃逸现象的,对程序员来说不用担心内存会不会泄露的问题。

二、枚举

这玩意还是比较有用的,比如我们一年12月:January 就表示 1 February表示2

package main

import "fmt"

const (
	January = iota+1 //iota自增 从0开始
	February
	March
	April
	May
	June
)

func main() {
	fmt.Println(January) //1
	fmt.Println(June) //6
}

 

三、字符串

3.1:字符串的定义

一个字符串的底层其实就是一个不可改变的字节序列。

var mystr string = "hello"

字符串中可以使用转义字符来实现换行、缩进等效果,常用的转义字符包括:

  1. \n:换行符

  2. \r:回车符

  3. \t:tab 键

  4. \u 或 \U:Unicode 字符

  5. \:反斜杠自身

如果使用``反引号,会被原样进行赋值和输出,跨行输出

func main() {
fmt.Println(`\t 码神之路Go大法好`) // \t 码神之路Go大法好
fmt.Println(`\t 码神之路
Go大法好`) //使用反引号 可以进行字符串换行
//反引号一般用在 需要将内容进行原样输出的时候 使用
}

字符串是字节的定长数组,byte 和 rune 都是字符类型,若多个字符放在一起,就组成了字符串

比如 hello ,对照 ascii 编码表,每个字母对应的编号是:104,101,108,108,111

import (
    "fmt"
)

func main() {
    var mystr01 string = "hello"
    var mystr02 [5]byte = [5]byte{104, 101, 108, 108, 111}
    fmt.Printf("myStr01: %s\n", mystr01)
    fmt.Printf("myStr02: %s", mystr02)
}

思考:hello,码神之路 占用几个字节

package main

import (
	"fmt"
)

func main() {
	//中文三字节,字母一个字节
	var myStr01 string = "hello,码神之路"
	fmt.Printf("mystr01: %d\n", len(myStr01))
}

3.2:字符串的应用

获取字符串的字节长度

func main() {
	//中文三字节,字母一个字节
	var myStr01 string = "hello,码神之路"
	fmt.Printf("mystr01: %d\n", len(myStr01)) //18
}

获取字符串的字符长度

func main() {
	//如何计算字符串的长度
	str3 := "hello"
	str4 := "你好"
	fmt.Println(len(str3))  // 1个字母占1个字节
	fmt.Println(len(str4))  // 1个中文占3个字节,go从底层支持utf8
	fmt.Println(utf8.RuneCountInString(str4)) // 2
}

获取字符串指定位置内容(只能针对纯 ASCII 码,中括号代码取的第几个字节)

  • 字符串 str 的第 1 个字节:str[0]

  • 第 i 个字节:str[i - 1]

  • 最后 1 个字节:str[len(str)-1]

字符串拼接

func main() {
	//如何计算字符串的长度
	str3 := "hello"
	str4 := "你好"
	fmt.Printf("%s\n",str3+str4) //第一种:使用+号 hello你好

	//第二种使用WriteString()
	var stringBuilder bytes.Buffer
	//节省内存分配,提高处理效率
	stringBuilder.WriteString(str3)
	stringBuilder.WriteString(str4)
	fmt.Println(stringBuilder.String()) //hello你好
}

思考:如果从字符串 hello 码神之路 中获取 该如何获取呢?

func main() {
	var myStr01 string = "hello,码神之路"
	a := []rune(myStr01)
	fmt.Println(a) //[104 101 108 108 111 44 30721 31070 20043 36335]
	fmt.Println(string(a)) //hello,码神之路
	fmt.Println(string([]rune(myStr01)[6])) //码
}

//要知道字符串的底层是byte的字节数组,我们可以将字符串先变成 rune类型的数组数组。然后再进行取值

字符串遍历

unicode字符集使用for range进行遍历,ascii字符集可以使用for range或者for循环遍历

func main() {
	var str1 string = "hello"
	var str2 string = "hello,码神之路"
	// 遍历
	for i :=0; i< len(str1); i++{
		fmt.Printf("ascii: %c %d\n", str1[i], str1[i])
	}
	for _, s := range  str1{
		fmt.Printf("unicode: %c %d\n ", s, s)
	}
	// 中文只能用 for range
	for _, s := range  str2{
		fmt.Printf("unicode: %c %d\n ", s, s)
	}
}

字符串的格式化

  • print : 结果写到标准输出

  • Sprint:结果会以字符串形式返回

func main() {
	str1 := "你好,"
	str2 := "码神之路"
	var stringBuilder bytes.Buffer
	stringBuilder.WriteString(str1)
	stringBuilder.WriteString(str2)
	// Sprint 以字符串形式返回
	result := fmt.Sprintf(stringBuilder.String())
	fmt.Println(result)
}
  • %c 单一字符
  • %T 动态类型
  • %v 本来值的输出
  • %+v 字段名+值打印
  • %d 十进制打印数字
  • %p 指针,十六进制
  • %f 浮点数
  • %b 二进制
  • %s string

字符串查找

如何获取字符串中的某一段字符?

  • strings.Index(): 正向搜索子字符串

  • strings.LastIndex():反向搜索子字符串

package main

import (
	"fmt"
	"strings"
)

func main() {
	// 查找
	tracer := "码神来了,码神bye bye"

	// 正向搜索字符串
	comma := strings.Index(tracer, ",")
	fmt.Println(",所在的位置:",comma)
	fmt.Println(tracer[comma+1:])  // 码神bye bye

	add := strings.Index(tracer, "+")
	fmt.Println("+所在的位置:",add)  // +所在的位置: -1

	pos := strings.Index(tracer[comma:], "码神")
	fmt.Println("码神,所在的位置", pos) // 码神,所在的位置 1

	fmt.Println(comma, pos, tracer[5+pos:])  // 12 1 码神bye bye
}

四、类型转换

在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于Go语言不存在隐式类型转换,因此所有的类型转换都必须显式的声明

//类型 B 的值 = 类型 B(类型 A 的值)
valueOfTypeB = type B(valueOfTypeA)

示例:

a := 5.0
b := int(a)

整数 与 字符串转换

// 字符串与其他类型的转换
// str 转 int
newStr1 := "1"
intValue, _ := strconv.Atoi(newStr1)
fmt.Printf("%T,%d\n", intValue, intValue)  // int,1

// int 转 str
intValue2 := 1
strValue := strconv.Itoa(intValue2)
fmt.Printf("%T, %s\n", strValue, strValue)

浮点数 与字符串

// str 转  float
string3 := "3.1415926"
f,_ := strconv.ParseFloat(string3, 32)
fmt.Printf("%T, %f\n", f, f)  // float64, 3.141593
//float 转 string
floatValue := 3.1415926
//4个参数,1:要转换的浮点数 2. 格式标记(b、e、E、f、g、G)
//3. 精度 4. 指定浮点类型(32:float32、64:float64)
// 格式标记:
// ‘b’ (-ddddp±ddd,二进制指数)
// ‘e’ (-d.dddde±dd,十进制指数)
// ‘E’ (-d.ddddE±dd,十进制指数)
// ‘f’ (-ddd.dddd,没有指数)
// ‘g’ (‘e’:大指数,‘f’:其它情况)
// ‘G’ (‘E’:大指数,‘f’:其它情况)
//
// 如果格式标记为 ‘e’,‘E’和’f’,则 prec 表示小数点后的数字位数
// 如果格式标记为 ‘g’,‘G’,则 prec 表示总的数字位数(整数部分+小数部分)
formatFloat := strconv.FormatFloat(floatValue, 'f', 2, 64)
fmt.Printf("%T,%s",formatFloat,formatFloat)

 

注意:使用内置库strings是对字符串的一系列方法

 

posted @ 2022-03-01 16:13  你是我的神奇  阅读(51)  评论(0编辑  收藏  举报