Golang 笔记 2 函数、结构体、接口、指针
一、函数
Go中函数是一等(first-class)类型。我们可以把函数当作值来传递和使用。Go中的函数可以返回多个结果。
函数类型字面量由关键字func、由圆括号包裹声明列表、空格以及可以由圆括号包裹的结果声明列表组成。其中参数声明列表中的单个参数声明之间是由英文逗号分隔的。每个参数声明由参数名称、空格和参数类型组成。参数声明列表中的参数名称是可以被统一省略的。结果声明列表的编写方式与此相同。结果声明列表中的结果也是可以被省略的,并且在只有一个无名称的结果声明时还可以省略括号。例:
func (input1 string, input2 string)
string
这一类型字面量表示了一个接受两个字符串类型的参数且会返回一个字符串类型的结果的函数。如果我们在它的左边加入type关键字和一个标识符作为名称的话就变成了一个函数类型声明:
type MyFunc func(input1 string, input2 string)
string
函数值(或简称函数)的写法与此不完全相同。编写函数的时候需要先写关键字func和函数名称,后跟参数声明列表和结果声明列表,最后是花括号包裹的语句列表。例如:
func myFunc(part1 string, part2 string)
(result string) {
result = part1 + part2
return
}
如果结果声明是带名称的,那么它就相当于一个已被声明但未被显示赋值的变量。我们可以为它赋值且在return语句中省略掉需要返回的结果值。该函数还有一种更常规的写法:
func myFunc(part1 string, part2 string)
string {
return part1 + part2
}
函数myFunc是函数类型MyFunc的一个实现。实际上,只要一个函数的参数声明列表和结果声明列表中的数据类型顺序和名称与某一个函数类型完全一致,前者就是后者的一个实现。
我们可以声明一个函数类型的变量,如:
var splice func(string, string) string // 等价于var splice MyFunc
然后把函数myFunc赋给它:
splice = myFunc
如此一来,我们就可以在这个变量之上实施调用动作了:
splice("1", "2")
实际上,这是一个调用表达式。上面的代码可以简化为:
var splice = func(part1 string, part2 string)
string {
return part1 + part2
}
在这个示例中,我们直接使用了一个匿名函数来初始化splice变量。顾名思义,匿名函数就是不带名称的函数值。匿名函数直接由函数类型字面量和由花括号包裹的语句列表组成。注意,这里的函数类型字面量中的参数名称是不能被省略的。
还可以进一步简化--省去splice变量。例:
var result = func(part1 string, part2 string)
string {
return part1 + part2
}("1", "2");
函数类型的零值是nil。
二、结构体和方法
Go语言的结构体类型Struct比函数类型更灵活。它可以封装属性和操作。前者即是结构体类型中的字段,而后者则是结构体类型所拥有的方法。
结构体类型的字面量由关键字type、类型名称、关键字struct以及花括号包裹的若干字段声明组成,其中,每个字段声明独占一行并由字段名称(可选)和字段类型组成。例:
type Person struct {
Name string
Gender string
Age uint8
}
结构体类型Person中有三个字段,分别是Name、Gender和Age。我们可以用字面量创建出一个该类型的值,像这样:
Person{Name:"Robert",Gender:"Male",Age:33}
如果这里键值对的顺序与其类型中的字段声明完全相同的话,可以统一省略所有字段的名称,就像这样:
Person{"Robert", "Male", 33}
我们在编写某个结构体类型的值字面量时可以只对它的部分字段赋值,甚至不对它的任何字段赋值。这时未被显式赋值的字段的值则为其类型的零值。
与代表函数值的字面量相似。我们在写一个结构体值的字面量时不需要先拟好其类型。这样的结构体字面量被称为匿名结构体。与匿名函数类似,我们在编写匿名结构体时需要先写明其类型特征(包含字段声明),再写出它的值初始化部分。我们依照结构体类型Person创建一个匿名结构体:
p := struct {
Name string
Gender string
Age uint8
}{"Robert", "Male", 33}
匿名结构体最大的用处是在内部临时创建一个结构以封装数据,而不必正式为其声明相关规则。
结构体类型可以拥有若干方法(匿名结构体是不可能拥有方法的)。所谓方法,其实就是一种特殊的函数。它可以依附于某个自定义类型。方法的特殊在于它的声明包含了一个接收者声明。这里的接收者指代它所依附的那个类型。以结构体类型Person为例,下面是依附于它的一个名为Grow的方法的声明:
func (person *Person) Grow() {
person.Age++
}
如上所示,在关键字func和名称Grow之间的那个圆括号及其包括的内容就是接收者声明。其中的内容由两部分组成。第一部分是代表它所依附的那个类型的值的标识符。第二部分是它依附的那个类型的名称。后者表明了依附关系,而前者则使得在该方法中的代码可以使用到该类型的值(也称为当前值)。代表当前值的那个标识符可被称为接收者标识符,或简称为接收者。例:
p := Person{"Robert", "Male", 33}
p.Grow()
我们可以直接在Person类型的变量p之上应用调用表达式来调用它的方法Grow。注意,此时方法Grow的接收者标识符person指代的正是变量p的值。这也是“当前值”这个词的由来。在Grow方法中,我们通过使用选择表达式选择了当前值的字段Age。并使其自增。因此,在语句p.Grow()被执行之后,p所代表的那个人就又年长了一岁(p的Age字段的值已变成34)。
在Grow方法的接收者声明中的那个类型是*Person,而不是Person。实际上,前者是后者的指针类型。这也使得person指代的是p的指针,而不是它本身。 结构体类型属于值类型。它的零值并不是nil。而是其中字段的值为相应类型的零值的值。结构体类型Person的灵值若用字面量来表示的话是Person{}
三、接口
在Go语言中,一个接口类型总是代表着某一种类型(即所有实现它的类型)的行为。一个接口类型的声明通常会包含关键字type、类型名称、关键字interface以及由花括号包裹的若干方法声明。例:
type Animal interface {
Grow()
Move(string) string
}
接口类型中的方法声明是普通的方法声明的简化形式。它们只包括方法名称、参数声明列表和结果声明列表。其中的参数的名称和结果的名称都可以被省略。未省略应该是这样的:
Move(new string) (old string)
如果一个数据类型所拥有的方法集合中包含了某一个接口类型中的所有方法声明的实现,那么就可以说这个数据类型实现了那个接口类型。所谓实现了一个接口中的方法是指,具有与该方法相同的声明并且添加了实现部分(由花括号包裹的若干条语句)。相同的方法声明意味着完全一致的名称、参数类型列表和结果类型列表。其中,参数类型列表即为参数声明列表中除去参数名称的部分。一致的参数类型列表意味着其长度以及顺序的完全相同。对于结果类型列表也是如此。 我们无需在一个数据类型中声明它实现了哪个接口。只要满足了"方法集合为超集"的条件,就建立了“实现”关系。 现在我们已经认为*Person类型实现了Animal接口。但是Go语言编译器是否也这么认为呢?这显然需要一种显式的判定方法。在Go语言中,这种判定可以用类型断言来实现。不过,在这里,我们不能在一个非接口类型的值上应用类型断言来判定它是否属于某一个接口类型。我们必须先把前者转换成空接口类型的值。这就涉及到了Go的类型转换。
Go语言的类型转换规则定义了是否能够以及怎样把一个类型的值转换另一个类型的值。另一方面,所谓空接口类型即是不包含任何方法声明的接口类型,用interface{}表示,常简称为空接口。Go中的包含预定义的任何数据类型都可以被看作是空接口的实现。我们可直接使用类型转换表达式把一个*Person类型转换成空接口类型的值,例:
p := Person{"Robert", "Male", 33, "Beijing"}
v := interface{}(&p)
在类型字面量后跟由圆括号包裹的值(或能够代表它的变量、常量或表达式)就构成了一个类型转换表达式,意为将后者转换为前者类型的值。在这里,我们把表达式,意为将后者转换为前者类型的值。在这里,我们把表达式&p的求值结果转换成了一个空接口类型的值,并由变量v代表。表达式&p(&是取地址符)的求值结果是一个*Person类型的值,及p的指针。
在这之后,我们就可以在v上应用类型断言了,即:
h,ok := v.(Animal)
类型断言表达式v.(Animal)的求值结果可以有两个。第一个结果是被转换后的那个目标类型(这里是Animal)的值,而第二个结果则是转换操作成功与否的标志。显然,ok代表了一个bool类型的值。它也是这里判定实现关系的重要依据。
四、指针
指针操作涉及到两个操作符--&和*。这两个操作符均有多个用途。但是当它们作为地址操作符出现时,&的作用是取址,*的作用是取值。 当*出现在一个类型之前(如*Person和*[3]string)时就不能被看作是操作符了,而应该被视为一个符号。如此组合而成的标识符所表达的含义是作为第二部分的那个类型的指针类型。我们也可以把其中的第二部分所代表的类型称为基底类型。 现在后头看结构体类型Person。它及其两个方法的完整声明如下:
type Person strcut {
Name string
Gender string
Age uint8
Address string
}
func (person *Person) Grow() {
person.Age++
}
func (person *Person) Move(newAddres string) string {
old := person.Address
person.Address = newAddress
return old
}
注意,Person的两个方法Grow和Move的接收者类型都是*Person,而不是Person。只要一个方法的接收者类型是其所属类型的指针类型而不是该类型本身,那么就可以称该方法为一个指针方法。上面的Grow方法和Move方法都是Person类型的指针方法。
相对的,如果一个方法的接收者类型就是其所属的类型本身。那么我们就可以把它叫做值方法。
func (person Person) Grow() {
person.Age++
}
方法的接收者标识符所代表的是该方法当前所属的那个值的一个副本,而不是该值本身。 之所以表达式person.Age成立,是因为如果Go语言发现person是指针并且指向的那个值有Age字段,那么就会把该表达式视为(*person).Age。
拥有指针方法Grow和Move的指针类型*Person是接口类型Animal的实现类型,但是它的基底类型Person却不是。这样的表象隐藏了另一条规则:一个指针类型拥有以它以及以它的基底类型为接收者类型的所有方法,而它的基底类型却至拥有以它本身为接收者类型的方法。
我们在基底类型的值上仍然可以调用它的指针方法。例如,若我们有一个Person类型的变量bp,则调用表达式bp.Grow()是合法的。这是因为,如果Go语言发现我们调用的Grow方法是bp的指针方法,那么它会把调用表达式是为(&bp).Grow()。
Golang 笔记 2 函数、结构体、接口、指针的更多相关文章
- 换个语言学一下 Golang (9)——结构体和接口
基本上到这里的时候,就是上了一个台阶了.Go的精华特点即将展开. 结构体定义 上面我们说过Go的指针和C的不同,结构体也是一样的.Go是一门删繁就简的语言,一切令人困惑的特性都必须去掉. 简单来讲,G ...
- 【学习笔记】【C语言】指向结构体的指针
1.指向结构体的指针的定义 struct Student *p; 2.利用指针访问结构体的成员 1> (*p).成员名称 2> p->成员名称 3.代码 #include < ...
- Go语言学习笔记十: 结构体
Go语言学习笔记十: 结构体 Go语言的结构体语法和C语言类似.而结构体这个概念就类似高级语言Java中的类. 结构体定义 结构体有两个关键字type和struct,中间夹着一个结构体名称.大括号里面 ...
- 深入了解Windows句柄到底是什么(句柄是逻辑指针,或者是指向结构体的指针,图文并茂,非常清楚)good
总是有新入门的Windows程序员问我Windows的句柄到底是什么,我说你把它看做一种类似指针的标识就行了,但是显然这一答案不能让他们满意,然后我说去问问度娘吧,他们说不行网上的说法太多还难以理解. ...
- c语言中较常见的由内存分配引起的错误_内存越界_内存未初始化_内存太小_结构体隐含指针
1.指针没有指向一块合法的内存 定义了指针变量,但是没有为指针分配内存,即指针没有指向一块合法的内浅显的例子就不举了,这里举几个比较隐蔽的例子. 1.1结构体成员指针未初始化 struct stude ...
- C#将结构体和指针互转的方法
. 功能及位置 将数据从托管对象封送到非托管内存块,属于.NET Framework 类库 命名空间:System.Runtime.InteropServices 程序集:mscorlib(在 msc ...
- C语言结构体和指针
指针也可以指向一个结构体,定义的形式一般为: struct 结构体名 *变量名; 下面是一个定义结构体指针的实例: struct stu{ char *name; //姓名 int num; //学号 ...
- 37深入理解C指针之---结构体与指针
一.结构体与指针 1.结构体的高级初始化.结构体的销毁.结构体池的应用 2.特征: 1).为了避免含有指针成员的结构体指针的初始化复杂操作,将所有初始化动作使用函数封装: 2).封装函数主要实现内存的 ...
- matlab学习笔记12_3串联结构体,按属性创建含有元胞数组的结构体,filenames,isfield,isstruct,orderfields
一起来学matlab-matlab学习笔记12 12_3 结构体 串联结构体,按属性创建含有元胞数组的结构体,filenames,isfield,isstruct,orderfields 觉得有用的话 ...
- Golang通过反射获取结构体的标签
Golang通过反射获取结构体的标签 例子: package main import ( "fmt" "reflect" ) type resume struc ...
随机推荐
- 利用Ajax和JSON实现关于查找省市名称的二级联动功能
功能实现的思路:我们经常碰见网上购物时候填写收件地址会用到这个查找省市县的三级联动查找功能,我们可以利用Ajax和JSON技术模拟这个功能,说白了同样是使用Ajax的局部数据更新功能这个特性.因为省市 ...
- C/JS_实现冒泡排序
冒泡排序算法的运作如下:(从后往前) 比较相邻的元素.如果第一个比第二个大,就交换他们两个. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.在这一点,最后的元素应该会是最大的数. 针对所 ...
- 微信小程序中的单位
vw:viewpoint width,视窗宽度,1vw等于视窗宽度的1%. vh:viewpoint height,视窗高度,1vh等于视窗高度的1%. rpx:rpx单位是微信小程序中css的尺寸单 ...
- JAVA自学笔记13
JAVA自学笔记13 1.StringBuffer类 1)线程安全的可变字符序列 线程安全(即同步) 2)StringBuffer与String的区别:一个可变一个不可变 3)构造方法: ①publi ...
- poj3253 Fence Repair(贪心+哈夫曼 经典)
https://vjudge.net/problem/POJ-3253 很经典的题,运用哈夫曼思想,想想很有道理!! 具体实现还是有点绕人,最后被long long卡了一下,看数据大小的时候单纯相乘了 ...
- Linux curl命令使用代理、以及代理种类介绍(转)
Linux curl命令使用代理.以及代理种类介绍 本文转自http://aiezu.com/article/linux_curl_proxy_http_socks.html,感谢作者 有时出于个 ...
- Unity3D性能优化最佳实践(四)资源审查
Asset auditing - 资源审查 许多项目发生效能问题的真正原因只是由于人员操作不当或是试东试西,而不小心改到导入设定影响到导入的资源.(例如最近的gitlab惨案) 对于较大规模的项目,最 ...
- 通过chrome console 快速获取网页连接
通过chrome console 快速获取网页连接 var ip = document.getElementsByClassName("jDesc"); var str = &qu ...
- Unitek的USB3.0 TF卡读卡器
淘宝买了个Unitek的usb3.0读卡器, 用来换掉之前用了很久sks的sub2读卡器, 收到之后在Ubuntu下先测了一下, 发现识别出来的是usb2.1 lsusb -D /dev/bus/us ...
- <转>SQL Server CROSS APPLY and OUTER APPLY
Problem SQL Server 2005 introduced the APPLY operator, which is like a join clause and it allows joi ...