Go语言中的复合类型详细介绍

golang复合类型包括:结构体、数组、切片、Maps。

1、数组

数组

golang中的数组与C语言中的数组差异很大,倒更类似Pascal中的数组。 (Slice,下个话题,有些像C语言中的数组)

代码如下:

var ar [3]int

声明ar为一个拥有三个整型数的数组,所有元素初始化为0。

大小是类型的一个组成部分。

内置的函数len可以用于获取数组大小:

代码如下:

len(ar) = 3

数组是值类型

golang中的数组是值,而非C语言中的隐式指针。你可以获得数组的地址,并生成一个指向数组的指针(例如,将其高效地传递给函数):

代码如下:

func f(a [3]int) { fmt.Println(a) }  
func fp(a *[3]int) { fmt.Println(a) }  
 
func main() {  
    var ar [3] int 
    f(ar) // 传递一个ar的拷贝  
    fp(&ar) // 传递一个指向ar的指针  
}

输出结果:

代码如下:

[0 0 0]

&[0 0 0]

数组字面值

所有的符合类型都有相同的值创建语法。以数组为例,其语法如下:

3个整数的数组:

代码如下:

[3]int{1, 2, 3}

10个整数的数组,前三个元素不是0:

代码如下:

[10]int{ 1, 2, 3}

不想数?使用…代表长度:

代码如下:

[...]int{1, 2, 3}

不想初始化所有值?使用key:value对:

代码如下:

[10]int{2:1, 3:1, 5:1, 7:1}

指向数组字面值的指针

你可以获取数组字面值的地址,这样可以得到一个指向新建数组实例的指针:

代码如下:

func fp(a *[3]int) { fmt.Println(a) }  
func main() {  
    for i := 0; i < 3; i++ {  
        fp(&[3]int{i, i*i, i*i*i})  
    }  
}

输出结果:

代码如下:

&[0 0 0]
&[1 1 1]
&[2 4 8]

2、切片(Slice)

切片

切片是对数组中某一段的引用。

切片比普通数组应用得更多也更广泛。

切片使用的代价很低。

一个切片类型很像一个没有大小的数组类型:

代码如下:

var a []int

内置的len(a)可以返回切片中元素的个数。

通过对数组或切片进行"切片",我们可以创建一个新切片:

代码如下:

a = ar[7:9]

a(上面例子中的a)的有效下标值是0和1;len(a) == 2。

切片速记

当对数组进行切片时,第一个下标值默认是0:

ar[:n]等价于a[0:n]。

第二个下标值默认为len(array/slice):

ar[n:]等价于ar[n:len(ar)]。

因此由数组创建切片时:

ar[:]等价于ar[0:len(ar)]。

切片引用数组

概念上:

代码如下:

type Slice struct {
base *elemType // 指向0th元素的指针
len int // 切片中元素的数量
cap int // 切片可以容纳元素的数量
}

数组:

代码如下:

ar: 7 1 5 4 3 8 7 2 11 5 3

切片:

代码如下:

a = ar[7:9] :base = &ar[7](指向ar中的2) len = 2 cap = 4

创建切片

切片字面值看起来像没有指定大小的数组字面值:

代码如下:

var slice = []int{1,2,3,4,5}

上面代码创建了一个长度为5的数组并创建一个切片用于引用这个数组。

我们可以使用内置的make函数分配一个切片(底层实际是个数组):

代码如下:

var s100 = make([]int, 100) // slice: 100 ints

为何用make而不是用new?因为我们需要创建切片,而不仅仅是为了分配内存。注意make([]int, 10)返回[]int,而new([]int)返回*[]int。

使用make创建切片、map以及channel。

切片容量

切片是对底层数组的一个引用。因此存在一些在数组里但却没在切片引用的范围内的元素。

内置的函数cap(capacity)用于报告切片可能增长到多长。

代码如下:

var ar = [10]int{0,1,2,3,4,5,6,7,8,9}
var a = ar[5:7] // 引用子数组{5,6}

len(a) = 2,cap(a) = 5,现在我们可以重新切片:

代码如下:

a = a[0:4] // 引用子数组 {5,6,7,8}

len(a)现在是4,而cap(a)依旧是5。

调整切片大小

切片可被当作可增长的数组用。使用make分配一个切片,并指定其长度和容量。当要增长时,我们可以做重新切片:

代码如下:

var sl = make([]int, 0, 100) // 长度 0, 容量 100  
func appendToSlice(i int, sl []int) []int {  
    if len(sl) == cap(sl) { error(…) }  
    n := len(sl)  
    sl = sl[0:n+1] // 长度增加1  
    sl[n] = i  
    return sl  
}

因此,sl的长度总是元素的个数,但其容量可根据需要增加。

这种手法代价很小,并且是Go语言中的惯用法。

切片使用的代价很小

你可以根据需要自由地分配和调整切片大小。它们的传递仅需要很小的代价;不必分配。

记住它们是引用,因此下层的存储可以被修改。

例如,I/O使用切片,而不是计数:

代码如下:

func Read(fd int, b []byte) int 
var buffer [100]byte  
    for i := 0; i < 100; i++ {  
    // 每次向Buffer中填充一个字节  
    Read(fd, buffer[i:i+1]) // no allocation here  
}

拆分一个Buffer:

代码如下:

header, data := buf[:n], buf[n:]

字符串也可以被切片,而且效率相似。

3、Maps

maps

Map是另外一种引用类型。它们是这样声明的:

代码如下:

var m map[string]float64

这里声明了一个map,索引key的类型为string,值类型为float64。这类似于C++中的类型*map<string, float64>。

对于给定map m,len(m)返回key的数量。

map的创建

和创建一个切片一样,一个map变量是一个空引用;在可以使用它之前,应先要向里面放入一些内容。

三种方式:

1) 字面值:逗号分隔的key:value对列表

代码如下:

m = map[string]float64{"1":1, "pi":3.1415}

2) 创建

代码如下:

m = make(map[string]float64) // make not new

3) 赋值

代码如下:

var m1 map[string]float64
m1 = m // m1和m现在引用相同的map

map索引

(接下来的几个例子全都使用:

代码如下:

m = map[string]float64{"1":1, "pi":3.1415})

访问一个元素;如果该元素不存在,则得到对应map value类型的零值:

代码如下:

one := m["1"]
zero := m["not present"] // zero被置为0.0.

设置一个元素的值(两次设置将更新为最新值)

代码如下:

m["2"] = 2
m["2"] = 3 // 思维混乱

测试存在性

要测试一个map中是否存在某个key,我们可以使用一个多项赋值的"comma, om"形式:

代码如下:

m = map[string]float64{"1":1, "pi":3.1415}

var value float64
var present bool

value, present = m[x]

或者按惯例:

代码如下:

value, ok := m[x] // "comma ok" 形式

如果map中存在x这个key,布尔变量会被设置为true;value会被赋值为map中key对应的值。相反,布尔变量会被设置为false,value被设置为相应值类型的零值。

删除

使用多元赋值可以删除map中的一个值:

代码如下:

m = map[string]float64{"1":1.0, "pi":3.1415}

var keep bool
var value float64
var x string = f()

m[x] = v, keep

如果keep的值为true,则将v赋值到map中;如果keep为false,则删除map中的key x。因此删除一个key:

代码如下:

m[x] = 0, false // 从map中删除x

译注:Go 1中上述的删除方式已被取消,取而代之的是delete(m, x)。

for和range

对于数组、切片和map(以及我们在第三部分将要看到的更多类型),for循环提供了一种特殊的语法用于迭代访问其中的元素。

代码如下:

m := map[string]float64{"1":1.0, "pi":3.1415}

for key, value := range m {
fmt.Printf("key %s, value %g\n", key, value)
}

只用一个变量,我们可以获得key:

代码如下:

for key = range m {
fmt.Printf("key %s\n", key)
}

变量可以用:=赋值或声明。

对于数组和切片来说,通过这种方式我们可以获得元素的下标以及元素值。

将range用于字符串

将for range用于字符串时,实际迭代的元素是Unicode码点(code point),而不是字节(对字节,可使用[]byte或使用标准的for语句)。我们假设字符串包

含使用UTF-8编码的字符。

下面循环:

代码如下:

s := "[\u00ff\u754c]"
for i, c := range s {
fmt.Printf("%d:%q ", i, c) // %q for 'quoted'
}

输出:0:'[' 1:'ÿ' 3:'界' 6:']'

如果遇到了错误的UTF-8码点,这个字符将被设置为U+FFFD,下标向后移动一个字节。

4、Structs

structs

对于Go中的struct,你应该感觉十分熟悉:简单的数据字段声明。

代码如下:

var p struct {
x, y float64
}

更常用的是:

代码如下:

type Point struct {
x, y float64
}
var p Point

struct允许程序员定义内存布局。

struct是值类型

struct是值类型,new(StructType)返回一个指向零值的指针(分配的内存都被置0)。

代码如下:

type Point struct {
x, y float64
}
var p Point
p.x = 7
p.y = 23.4
var pp *Point = new(Point)
*pp = p
pp.x = Pi // (*pp).x的语法糖

对于结构体指针,没有->符号可用。Go提供了间接的方式。

创建结构体

结构体是值类型,因此你可只通过声明就可以创建一个全0的结构体变量。

你也可以使用new创建一个结构体。

代码如下:

var p Point // 零值
pp := new(Point) // 惯用法

结构体字面值语法也不出所料:

代码如下:

p = Point{7.2, 8.4}
p = Point{y:8.4, x:7.2}
pp = &Point{7.2, 8.4} // 惯用法
pp = &Point{} //也是惯用法,== new(Point)

和数组一样,得到了结构体字面值的地址,就得到了新建结构体的地址。

这些例子都是构造器。

导出类型和字段

只有当结构体的字段(和方法,即将讲解)名字的首字母大写时,它才能被包外可见。

私有类型和字段:

代码如下:

type point struct { x, y float64 }

导出类型和字段:

代码如下:

type Point struct { X, Y float64 }

导出类型和私有类型混合字段:

代码如下:

type Point struct {
X, Y float64 // exported
name string // not exported
}

你甚至可以创建一个带有导出字段的私有类型。(练习:何时能派上用场呢?)

匿名字段

在一个结构体内,你可以声明不带名字的字段,比如另外一个结构体类型。这些字段被称为匿名字段。它们看起来就像里层的结构体简单插入或“嵌入”到

外层结构体似的。

这个简单的机制为从其他类型继承已有的实现提供了一种方法。

下面是一个例子。

一个匿名结构体字段:

代码如下:

type A struct {
ax, ay int
}

type B struct {
A
bx, by float64
}

B看起来像有四个字段ax、ay、bx和by。B可看成{ax, ay int; bx, by float64}。

然后B的字面值必须提供细节:

代码如下:

b := B{A{1, 2}, 3.0, 4.0}
fmt.Println(b.ax, b.ay, b.bx, b.by)

输出1 2 3 4

匿名字段以类型作为名字

匿名字段不仅仅是简单插入这些字段这么简单,其含义更为丰富:B还拥有字段A。匿名字段看起来就像名字为其类型名的字段。

代码如下:

b := B{A{ 1, 2}, 3.0, 4.0}
fmt.Println(b.A)

输出:{1 2}。如果A来自于另外一个包,这个字段依旧被称为A。

代码如下:

import "pkg"
type C struct { pkg.A }

c := C {pkg.A{1, 2}}
fmt.Println(c.A) // 不是 c.pkg.A

任意类型的匿名字段

任何具名类型或指向具名类型的指针都可以用作匿名字段。它们可以出现在结构体中的任意位置。

代码如下:

type C struct {
x float64
int
string
}
c := C{3.5, 7, "hello"}
fmt.Println(c.x, c.int, c.string)

输出:3.5 7 hello

冲突和遮蔽

如果有两个字段具有相同的名字(可能是一个继承类型的名字),代码将遵循下面规则:

1) 外层的名字遮蔽内层的名字。这提供了一个重写字段/方法的方式。
2) 如果在同一层次上出现了相同的名字,如果名字被使用,那么将是一个错误。(如果没有使用,不会出现错误)

二义性是没有规则能解决的,必须被修正。

冲突的例子

代码如下:

type A struct { a int }
type B struct { a, b int }
type C struct { A; B }
var c C

使用c.a将会出现错误。它到底是c.A.a还是c.B.a呢?

代码如下:

type D struct { B; b float64 }
var d D

使用d.b没有问题:它是float64类型变量,不是d.B.b。要获得内层的b,可用d.B.b。

(0)

相关推荐

  • Go语言转换所有字符串为大写或者小写的方法

    本文实例讲述了Go语言转换所有字符串为大写或者小写的方法.分享给大家供大家参考.具体如下: Go语言的string模块包含了ToLower和ToUpper函数,用于将字符串转换成小写和大写 复制代码 代码如下: package main import (   "fmt"   "strings" ) func main() {   fmt.Println(strings.ToUpper("hello world")) } 希望本文所述对大家的Go语

  • golang实现unicode转换为字符串string的方法

    本文实例讲述了golang实现unicode转换为字符串string的方法.分享给大家供大家参考,具体如下: 复制代码 代码如下: package main import (     "bytes"     "encoding/binary"     "encoding/hex"     "fmt"     "strings" ) func main() {     str := `\u5bb6\u65cf

  • Go语言中转换JSON数据简单例子

    Go语言转换JSON数据真是非常的简单. 以EasyUI的Demo为例,将/demo/datagrid/datagrid_data1.json 拷贝到$GOPATH/src目录: JSON.go: 复制代码 代码如下: package main import (         "encoding/json"         "fmt"         "io/ioutil" ) type product struct {         Pro

  • GO语言类型转换和类型断言实例分析

    本文实例讲述了GO语言类型转换和类型断言的用法.分享给大家供大家参考.具体分析如下: 由于Go语言不允许隐式类型转换.而类型转换和类型断言的本质,就是把一个类型转换到另一个类型. 一.类型转换 (1).语法:<结果类型> := <目标类型> ( <表达式> ) (2).类型转换是用来在不同但相互兼容的类型之间的相互转换的方式,所以,当类型不兼容的时候,是无法转换的.如下: 复制代码 代码如下: func test4() {     var var1 int = 7   

  • Go语言struct类型详解

    struct Go语言中,也和C或者其他语言一样,我们可以声明新的类型,作为其它类型的属性或字段的容器.例如,我们可以创建一个自定义类型person代表一个人的实体.这个实体拥有属性:姓名和年龄.这样的类型我们称之struct.如下代码所示: 复制代码 代码如下: type person struct {     name string     age int } 看到了吗?声明一个struct如此简单,上面的类型包含有两个字段. 1.一个string类型的字段name,用来保存用户名称这个属性

  • GO语言基本类型分析

    本文实例分析了GO语言基本类型.分享给大家供大家参考.具体如下: 一.整型 go语言有13种整形,其中有2种只是名字不同,实质是一样的,所以,实质上go语言有11种整形.如下: (1)int :依赖不同平台下的实现,可以是int32或int64 (2)int8 :  (-128->127) (3)int16: (-32768->32767) (4)int32: (-2 147 483 648->2 147 483 647) (5)int64 :(-9 223 372 036 854 77

  • Go语言基本的语法和内置数据类型初探

    Go令牌 Go程序包括各种令牌和令牌可以是一个关键字,一个标识符,常量,字符串文字或符号.例如,下面的Go语句由六个令牌: 复制代码 代码如下: fmt.Println("Hello, World!") 个体令牌是: 复制代码 代码如下: fmt . Println ( "Hello, World!" ) 行分离器 在Go程序,行的分隔符关键是一个语句终止.也就是说,每一个单独语句不需要特殊的分隔线; 在C编译器转到内部的地方; 作为语句终止符,表示一个逻辑实体的结

  • GO语言基本数据类型总结

    本文实例总结了GO语言基本数据类型.分享给大家供大家参考.具体如下: 1.注释(与C++一样) 行注释://块注释:/* ...*/ 2.标识符 可以这么说,除了数字开头的不允许,符号开头的不允许,关键字不允许,其他的Unicode字符组合都可以."_33"也可以是标识符."我们"也可以是标识符.标识符也区分大小写. (1).以大写字母开头的标识符是公开的.(这个很有意思) (2).其他任何标识符都是私有的. (3).空标识符"_"是一个占位符,

  • Go语言中的复合类型详细介绍

    golang复合类型包括:结构体.数组.切片.Maps. 1.数组 数组 golang中的数组与C语言中的数组差异很大,倒更类似Pascal中的数组. (Slice,下个话题,有些像C语言中的数组) 复制代码 代码如下: var ar [3]int 声明ar为一个拥有三个整型数的数组,所有元素初始化为0. 大小是类型的一个组成部分. 内置的函数len可以用于获取数组大小: 复制代码 代码如下: len(ar) = 3 数组是值类型 golang中的数组是值,而非C语言中的隐式指针.你可以获得数组

  • C语言中各种操作符的详细介绍(纯干货!)

    目录 算术操作符 移位操作符 位操作符 赋值操作符 单目操作符 (类型)   强制类型转换 条件操作符 总结 算术操作符 主要是 ( +   -   *   /    %) 五种算数操作符. 1.除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数. 2. 对于 / 操作符如果两个操作数都为整数,执行整数除法.而只要有浮点数执行的就是浮点数除 法. 3..% 操作符的两个操作数必须为整数.返回的是整除之后的余数. 当除数为小数时,int类型只会截取整数部分. 移位操作符 << 左移操作

  • C语言中基础小问题详细介绍

    1.printf格式输出函数 如果格式控制说明项数多于输出表列个数,则会输出错误数据:如果输出表列个数多于格式控制说明数,则多出数不被输出.%md,m指的是输出字段的宽度.如果输出字段位数小于m,则左端以空格补齐,若大于m,则按照实际位数输出.%-md,基本同上,只不过不同之处在于,空格在右端补齐printf参数可以是常量,变量或表达式,VC++ 6.0中采用从右向左顺序求值,从左向右输出如 复制代码 代码如下: int x = 5; printf("%4d%4d%4d", x, ++

  • java中的枚举类型详细介绍

    枚举中有values方法用于按照枚举定义的顺序生成一个数组,可以用来历遍.我们自定义的枚举类都是继承自java.lang.Enum,拥有一下实例中的功能: 复制代码 代码如下: //: enumerated/EnumClass.java // Capabilities of the Enum class import static net.mindview.util.Print.*; enum Shrubbery { GROUND, CRAWLING, HANGING } public clas

  • JavaScript中的值类型详细介绍

    计算机程序的实质很大程度上可以说是机器对各种信息(值)的操作与读写.在JavaScript中,存在多种类型的值,这些值分成两大类:Primitive(基本类型)和Object(对象). Primitive JavaScript中Primitive有5种类型: 1.Number.所有的数字,无论是整数还是小数,均为Number类型. 2.String.字符串类型. 3.Boolean.布尔类型,true或者false. 4.null.此类型只有null一个值. 5.undefined.此类型只有u

  • C语言 strcpy和memcpy区别详细介绍

    C语言 strcpy和memcpy区别详细介绍 PS:初学算法,开始刷leetcode,Rotate array的预备知识(写的代码Time Limit Exceed难过)于是百度高效算法,本篇作为预备知识. 1.strcpy和strncpy函数 这个不陌生,大一学C语言讲过,其一般形式为strcpy(字符数组1,字符串2)作用是将字符串2复制到字符数组1中去. EX: char str1[10]='',str2[]={"China"}; strcpy(str1,str2); strn

  • C语言初阶之数组详细介绍

    目录 插入排序讲解 二维数组 二维数组的初始化 二维数组的访问 n维数组 字符数组 字符数组和字符串 字符数组的输入输出 字符串函数的简单使用 综合使用字符串函数 总结 插入排序讲解 #include<stdio.h> int main() { int arr[8] = { 1,2,3,4,6,7,10 }; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); int n = 0; scanf("%d", &n); f

  • Maven中pom.xml配置文件详细介绍

    setting.xml主要用于配置maven的运行环境等一系列通用的属性,是全局级别的配置文件:而pom.xml主要描述了项目的maven坐标,依赖关系,开发者需要遵循的规则,缺陷管理系统,组织和licenses,以及其他所有的项目相关因素,是项目级别的配置文件. 基础配置 一个典型的pom.xml文件配置如下: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org

  • Android中View绘制流程详细介绍

    创建Window Window即窗口,这个概念在AndroidFramework中的实现为android.view.Window这个抽象类,这个抽象类是对Android系统中的窗口的抽象.在介绍这个类之前,我们先来看看究竟什么是窗口呢? 实际上,窗口是一个宏观的思想,它是屏幕上用于绘制各种UI元素及响应用户输入事件的一个矩形区域.通常具备以下两个特点: 独立绘制,不与其它界面相互影响: 不会触发其它界面的输入事件: 在Android系统中,窗口是独占一个Surface实例的显示区域,每个窗口的S

  • c++中STL库队列详细介绍

    1.queue单向队列(先进先出,只能从尾端加元素,从头删元素)         使用方式:在前面加上文件名'#include<queue>',再进行声明'queue<int>m;''其中'<>'里面是数组的类型,'m'是数组的名字.         操作: 1.q.push()//入队 2.q.pop()//让队首出队 3.q.front()//获得队首元素 4.q.back()//获得队尾元素         5.q.empty() 队列是否为空 6.q.size(

随机推荐