热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

《Go语言程序设计》读书笔记(一)基础类型和复合类型

前言最近在读《Go语言程序设计》这本书想通过看书巩固一下自己的基础知识,把已经积累的点通过看书学习再编织成一个网,这样看别人写的优秀代码时才能更好理解。当初工作中需要使用Go开发项











前言

最近在读《Go 语言程序设计》这本书想通过看书巩固一下自己的基础知识,把已经积累的点通过看书学习再编织成一个网,这样看别人写的优秀代码时才能更好理解。当初工作中需要使用 Go开发项目时看了网上不少教程,比如 uknown 翻译的《the way to go》看完基本上每次使用遇到不会的时候还会再去翻阅,这次把书中的重点还有一些平时容易忽视的Go语言中各种内部结构(类型、函数、方法)的一些行为整理成读书笔记。

因为《Go 语言程序设计》不是针对初学者的,所以我只摘选最重要的部分并适当补充和调换描述顺序力求用最少的篇幅描述清楚每个知识点。

《Go 语言程序设计》在线阅读地址:https://yar999.gitbooks.io/gopl-zh/content...

如果刚接触 Go建议先去读 《the-way-to-go》在线阅读地址:https://github.com/unknwon/the-way-to-go_Z...

(如Summer在评论里所说 LearnKu 里有《the-way-to-go》这本书的镜像,《Go 入门指南》阅读体验和加载速度都要更好一些 )


命名:


  • 函数名、变量名、常量名、类型名、包名等所有的命名,都遵循一个简单的命名规则:一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线。


  • 大写字母和小写字母是不同的:heapSort和Heapsort是两个不同的名字。


  • 关键字不可用于命名


  • break default func interface select
    case defer go map struct
    chan else goto package switch
    const fallthrough if range type
    continue for import return var

  • 推荐驼峰式命名


  • 名字的开头字母的大小写决定了名字在包外的可见性。如果一个名字是大写字母开头的,那么它可以被外部的包访问,包本身的名字一般总是用小写字母。



声明:


  • Go语言主要有四种类型的声明语句:var、const、type和func,分别对应变量、常量、类型和函数。

变量:


  • var声明语句可以创建一个特定类型的变量,然后给变量附加一个名字,并且设置变量的初始值。变量声明的一般语法如下:

    var 变量名字 类型 = 表达式

    其中“类型”或“= 表达式”两个部分可以省略其中的一个。如果省略的是类型信息,那么将 根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量。 数值类型变量对应的零值是0,布尔类型变量对应的零值是false,字符串类型对应的零值是空字符串,接口或引用类型(包括slice、map、chan和函数)变量对应的零值是nil。数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值。

    零值初始化机制可以确保每个声明的变量总是有一个良好定义的值,因此在Go语言中不存在未初始化的变量。这个特性可以简化很多代码,而且可以在没有增加额外工作的前提下确保边界条件下的合理行为。例如:

    var s string
    fmt.Println(s) // ""


字符串:


  • 文本字符串通常被解释为采用UTF8编码的Unicode码点(rune)序列。


  • 内置的len函数可以返回一个字符串中的字节数目(不是rune字符数目),索引操作s[i]返回第i个字节的字节值,i必须满足0 ≤ i

  • 字符串的值是不可变的:一个字符串包含的字节序列永远不会被改变,当然我们也可以给一个字符串变量分配一个新字符串值。可以像下面这样将一个字符串追加到另一个字符串:

    s := "left foot"
    t := s
    s += ", right foot"

    这并不会导致原始的字符串值被改变,但是变量s将因为+=语句持有一个新的字符串值,但是t依然是包含原先的字符串值。

    因为字符串是不可修改的,因此尝试修改字符串内部数据的操作也是被禁止的:

    s[0] = 'L' // compile error: cannot assign to s[0]

  • 每一个UTF8字符解码,不管是显式地调用utf8.DecodeRuneInString解码或是在range循环中隐式地解码,如果遇到一个错误的UTF8编码输入,将生成一个特别的Unicode字符’\uFFFD’,在印刷中这个符号通常是一个黑色六角或钻石形状,里面包含一个白色的问号””。当程序遇到这样的一个字符,通常是一个危险信号,说明输入并不是一个完美没有错误的UTF8字符串。


  • 字符串的各种转换:

    string接受到[]rune的类型转换,可以将一个UTF8编码的字符串解码为Unicode字符序列:

    // "program" in Japanese katakana
    s := "プログラム"
    fmt.Printf("% x\n", s) // "e3 83 97 e3 83 ad e3 82 b0 e3 83 a9 e3 83 a0"
    r := []rune(s)
    fmt.Printf("%x\n", r) // "[30d7 30ed 30b0 30e9 30e0]"

    (在第一个Printf中的% x参数用于在每个十六进制数字前插入一个空格。)

    如果是将一个[]rune类型的Unicode字符slice或数组转为string,则对它们进行UTF8编码:

    fmt.Println(string(r)) // "プログラム"

    将一个整数转型为字符串意思是生成以只包含对应Unicode码点字符的UTF8字符串:

    fmt.Println(string(65)) // "A", not "65"
    fmt.Println(string(0x4eac)) // "京"

    如果对应码点的字符是无效的,则用’\uFFFD’无效字符作为替换:

    fmt.Println(string(1234567)) // ""


复合数据类型:


  • 基本数据类型,它们可以用于构建程序中数据结构,是Go语言的世界的原子。以不同的方式组合基本类型可以构造出复合数据类型。我们主要讨论四种类型——数组、slice、map和结构体,数组和结构体都是有固定内存大小的数据结构。相比之下,slice和map则是动态的数据结构,它们将根据需要动态增长。

数组:


  • 数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。

    q := [3]int{1, 2, 3}
    q = [4]int{1, 2, 3, 4} // compile error: cannot assign [4]int to [3]int


Slice:


  • 长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。


  • x[m:n]切片操作对于字符串则生成一个新字符串,如果x是[]byte的话则生成一个新的[]byte。


  • slice并不是一个纯粹的引用类型,它实际上是一个类似下面结构体的聚合类型:

    type IntSlice struct {
    ptr *int
    len, cap int
    }


Map:


  • 在Go语言中,一个map就是一个哈希表的引用,map类型可以写为map[K]V,其中K和V分别对应key和value。map中所有的key都有相同的类型,所有的value也有着相同的类型,但是key和value之间可以是不同的数据类型。


  • map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作:

    _ = &ages["bob"] // compile error: cannot take address of map element

    禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效。


  • map上的大部分操作,包括查找、删除、len和range循环都可以安全工作在nil值的map上,它们的行为和一个空的map类似。但是向一个nil值的map存入元素将导致一个panic异常:

    ages["carol"] = 21 // panic: assignment to entry in nil map

    在向map存数据前必须先创建map。


  • 和slice一样,map之间也不能进行相等比较;唯一的例外是和nil进行比较。要判断两个map是否包含相同的key和value,我们必须通过一个循环实现。



结构体:


  • 下面两个语句声明了一个叫Employee的命名的结构体类型,并且声明了一个Employee类型的变量dilbert:

    type Employee struct {
    ID int
    Name string
    Address string
    DoB time.Time
    Position string
    Salary int
    ManagerID int
    }
    var dilbert Employee

    dilbert结构体变量的成员可以通过点操作符访问,比如dilbert.Name和dilbert.DoB。因为dilbert是一个变量,它所有的成员也同样是变量,我们可以直接对每个成员赋值:

    dilbert.Salary -= 5000 // demoted, for writing too few lines of code

    或者是对成员取地址,然后通过指针访问:

    position := &dilbert.Position
    *position = "Senior " + *position // promoted, for outsourcing to Elbonia

  • 如果结构体成员名字是以大写字母开头的,那么该成员就是导出的;这是Go语言导出规则决定的。一个结构体可能同时包含导出和未导出的成员。未导出的成员只能在包内部访问,在外部包不可访问。


  • 结构体类型的零值中每个成员其类型的是零值。通常会将零值作为最合理的默认值。例如,对于bytes.Buffer类型,结构体初始值就是一个随时可用的空缓存,还有sync.Mutex的零值也是有效的未锁定状态。有时候这种零值可用的特性是自然获得的,但是也有些类型需要一些额外的工作。


  • 因为结构体通常通过指针处理,可以用下面的写法来创建并初始化一个结构体变量,并返回结构体的地址:

    pp := &Point{1, 2}

  • Go语言有一个特性让我们只声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。下面的代码中,Circle和Wheel各自都有一个匿名成员。我们可以说Point类型被嵌入到了Circle结构体,同时Circle类型被嵌入到了Wheel结构体。

    type Circle struct {
    Point
    Radius int
    }
    type Wheel struct {
    Circle
    Spokes int
    }

    得益于匿名嵌入的特性,我们可以直接访问叶子属性而不需要给出完整的路径:

    var w Wheel
    w.X = 8 // equivalent to w.Circle.Point.X = 8
    w.Y = 8 // equivalent to w.Circle.Point.Y = 8
    w.Radius = 5 // equivalent to w.Circle.Radius = 5
    w.Spokes = 20

  • 外层的结构体不仅仅是获得了匿名成员类型的所有成员,而且也获得了该类型导出的全部的方法。这个机制可以用于将一个有简单行为的对象组合成有复杂行为的对象。





golang


推荐阅读
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 判断数组是否全为0_连续子数组的最大和的解题思路及代码方法一_动态规划
    本文介绍了判断数组是否全为0以及求解连续子数组的最大和的解题思路及代码方法一,即动态规划。通过动态规划的方法,可以找出连续子数组的最大和,具体思路是尽量选择正数的部分,遇到负数则不选择进去,遇到正数则保留并继续考察。本文给出了状态定义和状态转移方程,并提供了具体的代码实现。 ... [详细]
  • FeatureRequestIsyourfeaturerequestrelatedtoaproblem?Please ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
author-avatar
帅气小子勇哥
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有