Golang 学习之路(二)数据类型

字符串

和 Java 中的字符串类 String 一样,Go 中的字符串也是一个不可改变的字节序列

内置的 len 函数表示字符串的长度,即包含的字节数目,索引操作 s[i] 表示第i个字符,i的取值范围为0<=i<len(s)

s := "this is a string"
fmt.Println(len(s))		//16
fmt.Println(s[16])		//panic: runtime error: index out of range
fmt.Println(s[0:4])		//this
fmt.Println(s[5:7])		//is
fmt.Println(s[:7])		//this is
fmt.Println(s[5:])		//is a string
fmt.Println(s[:])		//this is a string

因为字符串是不可改变的,所以对于字符串内容的修改是不被允许的

s[0] = "T" 			//无法编译通过

标准库中的 bytes、strings、strconv、unicode四个包对字符串的处理

strings 中有对字符串的截取,查找,替换等多种方法

//查找一个路径中的文件的基础文件名,不带后缀
func BaseName(s string) string {

	slash := strings.LastIndex(s, "/")
	s = s[slash+1:]

    if dot := strings.LastIndex(s,".");dot >= 0 {
		s = s[:dot]
	}
	return s
}

var s string = "D:/a/b/c/d.go"
fmt.Println(part1.BaseName(s))		//d
//将一个整型的数据用逗号分隔,如123456789 --> 123,456,789
func SplitInt(n string) string{
	len := len(n)
	if len <= 3 {
		return n
	}
	return SplitInt(n[:len-3]) + "," + n[len-3:]
}

var s = 123456789
fmt.Println(part1.SplitInt(strconv.Itoa(s)))

下面是stringsstrconv的结构图,方法名大写字母开头的方法即为可以被外部引用的方法,分布在不同的 go 文件里,可以根据语义去查找对应的API

strings 包结构
|-- strings
|   |-- builder.go
|   |-- builder_test.go
|   |-- compare.go
|   |-- compare_test.go
|   |-- example_test.go
|   |-- export_test.go
|   |-- reader.go
|   |-- reader_test.go
|   |-- replace.go
|   |-- replace_test.go
|   |-- search.go
|   |-- search_test.go
|   |-- strings.go
|   |-- strings.s
|   `-- strings_test.go

strconv 包结构
|-- strconv
|   |-- atob.go
|   |-- atob_test.go
|   |-- atof.go
|   |-- atof_test.go
|   |-- atoi.go
|   |-- atoi_test.go
|   |-- decimal.go
|   |-- decimal_test.go
|   |-- doc.go
|   |-- example_test.go
|   |-- export_test.go
|   |-- extfloat.go
|   |-- fp_test.go
|   |-- ftoa.go
|   |-- ftoa_test.go
|   |-- internal_test.go
|   |-- isprint.go
|   |-- itoa.go
|   |-- itoa_test.go
|   |-- makeisprint.go
|   |-- quote.go
|   |-- quote_test.go
|   |-- strconv_test.go
|   `-- testdata
|       `-- testfp.txt

字符串和字符slice之间的切换

s := "abc"
b := []byte(s)
s2 := string(b)

bytes 包还提供了Buffer类型用于字节slice的缓存,可以随着byte,[]byte,string 等类型的写入而动态变化

//把整型数组准换为字符串数组
func IntsToString(ints []int) string {
	var buf bytes.Buffer
	buf.WriteByte('[')

	for i, v := range ints {
		if v > 0 {
			buf.Write([]byte(strconv.Itoa(v)))
			if i < len(ints)-1 {
				buf.WriteString(",")
			}
		}
	}
	buf.WriteByte(']')
	return buf.String()
}

fmt.Println(part2.IntsToString([]int{1,2,3}))		//[1,2,3]

通过byte.Buffer来实现上面的用逗号分隔字符串的非递归实现

//将一个整型的数据用逗号分隔,如123456789 --> 123,456,789
func SplitIntByBuffer(n string) string {
	l := len(n)
	if l <= 3 {
		return n
	}

	var buf bytes.Buffer
	a := l % 3
	buf.WriteString(n[:a])
	for i := a; i < l; i += 3 {
		if i != 0{
			buf.WriteString(",")
		}
		buf.WriteString(n[i : i+3])
	}
	return buf.String()
}

var s = 1234567
fmt.Println(part2.SplitIntByBuffer(strconv.Itoa(s)))	//1,234,567

常量

常量表达式的值在编译期完成,而不是运行期

常量用关键字 const 来标记,可以声明一组常量

const (
    a = 1
    b
    c = 2
    d
)

fmt.Println(a,b,c,d)		//1 1 2 2

如果是批量声明的常量,除了第一个常量外其他的常量右边的表达式都可以省略,如果省略了则表示后面的常量使用前面常量的初始化写法,并且对应的类型也是一样的,上面的ba的值和类型是一样的,同理dc也是一样的

iota 常量生成器

在第一个声明常量所在行,iota会被初始化为 0

type Count int

const (
    FIRST Count = 1 + iota
    SECOND
    THIRD
    FOURTH
    FIFTH
)

fmt.Println(FIRST,SECOND,THIRD,FOURTH,FIFTH) 		//1 2 3 4 5

Slice

slice代表变长的序列,序列中每个元素都有相同的类型。slice的语法和数组很像,只是没有固定长度

slice由指针,长度,容量三部分组成,通过len函数可以查看长度,通过cap函数可以查看容量的大小,对于定长的数组来说,容量与长度是等价的,对于slice来说,长度是指底层数组的长度,但是容量则是值切片后重新切片时可以达到的最大长度;

// The len built-in function returns the length of v, according to its type:
//	Array: the number of elements in v.
//	Pointer to array: the number of elements in *v (even if v is nil).
//	Slice, or map: the number of elements in v; if v is nil, len(v) is zero.
//	String: the number of bytes in v.
//	Channel: the number of elements queued (unread) in the channel buffer;
//	if v is nil, len(v) is zero.
// For some arguments, such as a string literal or a simple array expression, the
// result can be a constant. See the Go language specification's "Length and
// capacity" section for details.
func len(v Type) int

// The cap built-in function returns the capacity of v, according to its type:
//	Array: the number of elements in v (same as len(v)).
//	Pointer to array: the number of elements in *v (same as len(v)).
//	Slice: the maximum length the slice can reach when resliced;
//	if v is nil, cap(v) is zero.
//	Channel: the channel buffer capacity, in units of elements;
//	if v is nil, cap(v) is zero.
// For some arguments, such as a simple array expression, the result can be a
// constant. See the Go language specification's "Length and capacity" section for
// details.
func cap(v Type) int

按照源码中对于cap的说明,结合代码来理解

month := [...]string{1:"Jan",2:"Feb",3:"Mar",4:"Apr",5:"May",6:"Jun",
		7:"Jul",8:"Aug",9:"Sep",10:"Oct",11:"Nov",12:"Dec"}
for i, v := range month {
    fmt.Println(strconv.Itoa(i) + "-----" + v)
}

fmt.Println(cap(month),len(month)) 		//13 13
Q2 := month[4:7]
fmt.Println(cap(Q2),len(Q2))			//9 3

在没有slice操作之前,month的长度和容量是相等的,都为 13,其中索引为 0 的元素零值为空字符串;在切片之后生成了Q2Q2的长度为 7 - 4 = 3,容量则是从 4 开始一直到month的长度,即 13 - 4 = 9;

对于s[i:j]len(s)=j-i,cap(s)=len(s)-i

因为slice值包含指向第一个slice元素的指针,因此向函数传递slice将允许在函数内部修改底层数组的元素,也就是说,复制一个slice只是对底层的数组创建了一个新的slice别名

func reverse(n []int) {
	for i, j := 0, len(n)-1; i < j; i, j = i+1, j-1 {
		n[i], n[j] = n[j], n[i]
	}
}

slice := []int{1,2,3,4,5}
fmt.Println(slice)		//[1 2 3 4 5]
reverse(slice)
fmt.Println(slice)		//[5 4 3 2 1]

与数组相比,在传参的时候,数组是直接的值传递,而slice是可以理解为引用指针的传递

func modifyArr(n [5]int)  {
	new := n
	new[0] = 100
}

func modifySlice(n []int)  {
	new := n
	new[0] = 100
}

arr := [5]int{1,2,3,4,5}
slice := []int{1,2,3,4,5}
modifyArr(arr)
fmt.Println(arr)		//[1 2 3 4 5]

modifySlice(slice)
fmt.Println(slice)		//[100 2 3 4 5]

append函数的使用

append函数可以在一个已有的slice中添加一个或者多个元素,甚至一个新的slice

但是我们无法明确新的slice和之前的slice是否引用相同的底层数组,也就是说无法确认在append操作后再去操作之前的slice会不会对新的slice产生影响

slice := []int{1,2,3,4,5}
fmt.Printf("slice cap is %d\n",cap(slice))								//slice cap is 5

slice1 := append(slice,9)
fmt.Printf("slice1 cap is %d\t%p\t%p\n",cap(slice1),slice1,slice)		//slice1 cap is 10	0xc0000520a0	0xc000078030

slice2 := append(slice,10)
fmt.Printf("slice2 cap is %d\t%p\t%p\n",cap(slice2),slice2,slice)		//slice2 cap is 10	0xc0000520f0	0xc000078030

slice3 := append(slice2,10)
fmt.Printf("slice3 cap is %d\t%p\t%p\n",cap(slice3),slice3,slice2)		//slice3 cap is 10	0xc0000520f0	0xc0000520f0

slice4 := append(slice2,6,7,7,5,0)
fmt.Printf("slice4 cap is %d\t%p\t%p\n",cap(slice4),slice4,slice2)		//slice4 cap is 20	0xc0000b2000	0xc0000520f0

可以看出在初始容量 5 的时候,添加一个元素超出初始容量之后便做了扩容处理,slice1容量增大一倍到了 10,并且分配了新的内存空间,也就是说不再引用之前slice的底层数组,在添加没有超出容量的时候,每一次对slice的添加都会分配一个新的内存空间(slice1slice2),但是对已经append操作过再继续append元素并且没有超出容量的情况下,是不会重新分配内存空间的(slice3slice2),超出容量之后,则会重新分配内存空间(slice4slice2

Map

在 Go 语言中,一个map就是一个哈希表的引用,map类型可以写作map[K]V,其中KV分别对应keyvaluekeyvalue都必须各自有着相同的数据类型,但是keyvalue的类型可以不一样,并且key必须是支持==比较运算符的数据类型,所以map可以通过测试key是否相等来判断元素是否存在

map的创建方式

1、内置的make函数、
m := make(map[string]int)
2、通过 map 的字面值语法
age := map[string]int{
    "a":20,
    "b":19,
}

map中的元素被禁止取址操作,因为随着元素数量的增加会重新分配更大的内存空间,类似slice的扩容,从而导致之前的地址失效

map的迭代顺序也是不确定的,可以手动显示的对key值进行排序,使用sort包的Strings函数处理

func SortMap(m map[string]string) {

	var keys []string
	for k := range m {
		keys = append(keys, k)
	}

	sort.Strings(keys)

	for _, k := range keys {
		fmt.Println("key:" + k + "-------->map:" + m[k])
	}
}

//测试上面的函数
func TestSortMap(t *testing.T) {
	m := map[string]string{
			"d":"13",
			"b":"312",
			"w":"12",
			"f":"12",
	}
    //一般用布尔变量ok来接收map中是否存在key,适用与马上用来判断的场景
	if key, ok := m["d"]; ok {
		fmt.Println(key)
	}
	SortMap(m)
}
//按照key升序排列,输出打印如下:
//13
//key:b-------->map:312
//key:d-------->map:13
//key:f-------->map:12
//key:w-------->map:12

为了便于测试,使用测试文件的测试方法来运行测试程序,避免每次必须main函数作为入口运行的麻烦。将文件的命名加上_test,文件命名格式变成"json_test.go"的格式,然后再文件内创建Test函数并加上t *testing.T就好了,类似于 Java 的单元测试

JSON

在 Go 语言中,对JSON数据类型也有着良好的支持,可用于编码map类型(key类型是字符串)和结构体。

type Book struct {
	IndexNum  int `json:"num"`
	Name      string
	Price     string
	Hardcover bool `json:"favorite,omitempty"`
	Language  []string
}

这种结构特别适合JSON格式,并且在两者之间转换特别容易。将类似Book结构体的slice转换为JSON的过程叫做编组(marshaling),通过调用json.Marshal函数完成,如果需要整齐的缩进格式,可以调用json.MarshalIndent函数完成。

有编码成JSON的方法,自然就有逆操作解码,可以通过json.Unmarshal函数来完成JSON数据的解码,转换成一个结构体slice

func TestJson(t *testing.T) {
	var books = []Book{
		{IndexNum: 1, Name: "Thinking in Java", Price: "78", Hardcover: false,
			Language: []string{"English", "Chinese", "Japanese"}},
		{IndexNum: 2, Name: "The Go Programing Language", Price: "68", Hardcover: true,
			Language: []string{"English", "Chinese", "Japanese"}},
		{IndexNum: 3, Name: "Effective Java", Price: "59", Hardcover: true,
			Language: []string{"English", "Chinese"}},
	}
	//data, err := json.Marshal(books)
	data, err := json.MarshalIndent(books, "", "	")
	if err != nil {
		log.Fatalf("JSON marshaling failed: %s", err)
	}
	fmt.Printf("%s\n", data)
	
	var bookD []Book
	if err := json.Unmarshal(data, &bookD); err != nil {
		log.Fatalf("JSON unmarshaling failed: %v", err)
	}
	fmt.Println(bookD)
}

上面测试方法运行后的输出结果为:

[
	{
		"num": 1,
		"Name": "Thinking in Java",
		"Price": "78",
		"Language": [
			"English",
			"Chinese",
			"Japanese"
		]
	},
	{
		"num": 2,
		"Name": "The Go Programing Language",
		"Price": "68",
		"favorite": true,
		"Language": [
			"English",
			"Chinese",
			"Japanese"
		]
	},
	{
		"num": 3,
		"Name": "Effective Java",
		"Price": "59",
		"favorite": true,
		"Language": [
			"English",
			"Chinese"
		]
	}
]
[{1 Thinking in Java 78 false [English Chinese Japanese]} {2 The Go Programing Language 68 true [English Chinese Japanese]} {3 Effective Java 59 true [English Chinese]}]

结构体中json:"num"是结构体成员Tag , 这些tag是在编译阶段关联到该成员的元信息字符串,通常有key:"value"键值对格式组成,在Book结构体中,IndexNum的输出值已经由num代替了,而在HardCover后面的tag中,还多了一个omitempty选项,表示结构体成员为空或者为零值的时候不生成JSON对象,在这里bool类型false为零值,所以在num:1的那本书中并没有显示favorite,也就是Hardcover属性

文本模版

虽然对于简单的输出,使用Printf函数就足够,但是需要输出复杂格式的时候就显得比较难处理了。

这时通过文本模版就可以很灵活的自定义显示输出的内容。一个模版是一个字符串或者一个文件,里面包含了一个或者多个由双花括号包含的{{action}}对象,模版语言包含通过选择结构体的成员、调用函数或方法,表达式控制流if-else语句和range循环语句等多种特性

通过一个调用GitHub API的例子来理解自定义显示文本以及对返回JSON格式数据的接收处理

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"testing"
	"text/template"
	"time"
)

const IssueUrl = "https://api.github.com/search/issues"

type IssueSearchResult struct {
	TotalCount int `json:"total_count"`
	Items      []*Item
}

type Item struct {
	Id        int
	Title     string
	Number    int
	HTMLURL   string `json:"html_url"`
	State     string
	CreatedAt time.Time `json:"created_at"`
	Score     float64
	User      *User
	Body      string
}

type User struct {
	Login   string
	HTMLURL string `json:"html_url"`
}

func SearchIssue(key []string) (*IssueSearchResult, error) {

	query := url.QueryEscape(strings.Join(key, " "))

	res, err := http.Get(IssueUrl + "?q=" + query)
	if err != nil {
		return nil, err
	}

	if res.StatusCode != http.StatusOK {
		res.Body.Close()
		log.Fatalf("search is failed:%v", err)
		return nil, fmt.Errorf("search is failed : %s", res.Status)
	}

	var result IssueSearchResult

	//读取输入流数据,并且将其存储在&result指向的值中
	if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
		res.Body.Close()
		return nil, err
	}

	res.Body.Close()
	return &result, err
}

//自定义的计算相差天数的函数
func daysAgo(t time.Time) int {
	return int (time.Since(t).Hours() / 24)
}

func TestTextTemplate(t *testing.T)  {
	const templateText =
		`the total issue is : {{.TotalCount}}
		{{range .Items}}
		------------
		Numbers:	{{.Number}}
		User:		{{.User.Login}}
		Title:		{{.Title | printf "%.64s"}}
		Age:		{{.CreatedAt | daysAgo}} days ago
		{{end}}`

	var report = template.Must(template.New("issueList").Funcs(template.
		FuncMap{"daysAgo":daysAgo}).Parse(templateText))

	key := []string{"go", "java"}

	result, err := SearchIssue(key)
	//为了便于输出打印显示,只选取前3条items的数据
	result.Items = result.Items[:3]
	if err != nil {
		log.Fatalf("the error is %v", err)
	}

	if err := report.Execute(os.Stdout,result);err != nil {
		log.Fatalf("the error is %v", err)
	}
}

上面的输出结果:

the total issue is : 521881
		
		------------
		Numbers:	76
		User:		pantianying
		Title:		go []string to java []string
		Age:		6 days ago
		
		------------
		Numbers:	187
		User:		alourie
		Title:		Sync java and go sidecar clients
		Age:		3 days ago
		
		------------
		Numbers:	628
		User:		wangqj
		Title:		add java call go doc
		Age:		26 days ago
		
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值