Go学习记录(一)

简介:

本文为Go学习过程中记录的笔记,参考文档如下:
《Go入门指南》

一些介绍

1. Go的由来

——其中一个点是:“在 Go 语言出现之前,开发者们总是面临非常艰难的抉择,究竟是使用执行速度快但是编译速度并不理想的语言(如:C++),还是使用编译速度较快但执行效率不佳的语言(如:.NET、Java),或者说开发难度较低但执行速度一般的动态语言呢?显然,Go 语言在这 3 个条件之间做到了最佳的平衡:快速编译,高效执行,易于开发。”

2. Go的特点:

  • 目标是将静态语言的安全性和高效性与动态语言的易开发性结合,达到完美平衡。
  • Go种仍旧存在指针,但是并不允许进行指针运算;
  • **通过goroutine这种轻量级线程的概念实现对网络通信、并发和并行编程的极佳支持,**线程间的通信通过channel;
  • 构建速度(编译和链接到机器代码的速度)极快;——动态语言的优点
  • 内存问题也考虑到了,尽管Go语言像其它静态语言一样执行本地代码,但它依旧运行在某种意义上的虚拟机,以此实现高效快速的垃圾回收;同时,也因为其垃圾回收和自动分配内存的原因,Go语言并不适合用来开发实时性要求很高的软件;

3. 一些特性

  • Go语言并没有类和继承的概念,它是通过接口的概念来实现多态性的,类型之间也没有层级之说;

  • 函数是Go语言的基本构件;

  • 隐式类型转换是不被允许的,所有的东西都是显式的;

  • Go语言也有一些动态语言的特性(通过关键字var);

  • 是一门完全支持UTF-8的编程语言,源码文件格式都可以使用UTF-8编码;

4. 关于特性的缺失:

在这里插入图片描述

基本结构与基本数据类型:

1. 包的概念、导入与可见性:

  • 必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main;package main表示该源文件可以独立运行,每个Go应用程序都包含一个名为main的包;

  • 如果打算编译包名不是为main的源文件,如pack,编译后产生的对象文件将会是pack.a而不是可执行程序;另外,所有的包名都应该使用小写字母。

  • 如果想要构建一个程序,则包和包内的文件都必须以正确的顺序进行编译,包的依赖关系决定了其构建顺序,编译器会从后缀名为.o的对象文件中提取传递依赖类型的信息。如:A.go依赖B.go,而B.go依赖C.go,那么有以下的事件:

    • 编译C.go,B.go然后是A.go;
    • 为了编译A.go,编译器读取B.o;
    • 即,.o文件用来存储目标文件所需要的依赖,A.go编译前发现依赖于B.go,于是检索B.o并编译其依赖,然后再编译B.go最后编译A.go;
  • 包的导入:

    import "fmt"
    import "os"
    //等价于下面
    import (
    	"fmt"
        "os"
    )
    
    • 包名不以 “.” 或者 “/” 开头,则Go会在全局文件进行查找;
    • 包名以 “./” 开头,则Go会在相对目录中查找;
    • 包名以 “/” 开头,则会在系统的绝对路径中查找;
  • 可见性规则:

    当标识符以一个大写字母开头,如:Println,那么使用这种形式的标识符的对象就可以被外部包的代码所使用,这被称为导出,相当于访问权限为public;相反如果以小写字母开头,则对外部是不可见的,但是在整个包的内部是可见并且可用的(把包理解成类,小写标识符则是private变量)。

  • 如果导入了一个包却没有使用,在构建程序时则会引发错误。

2. 函数:

  • 最简单格式:

    func functionName([arguments argumentType]) returnType{
        
    }
    //注意,这里的"{"必须与函数声明放在同一行,这是强制规定
    

    可以在括号内写入0个或多个函数的参数,使用逗号分割,每个参数的名称后面必须紧跟着该参数的类型,如果有返回类型则写在括号的后面。

  • main函数是一个比较特殊的函数,它是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有init()函数则会先执行该函数)。main函数既没有参数,也没有返回类型。

  • Go语言虽然看起来不使用分号作为语句的结束,但实际上这一步是由编译器自己完成的,因此一条语句需要只写一行,而不能分行写;

  • 程序正常退出的代码为0。

3. 注释:

  • 单行注释://

  • 多行注释:/* 不可嵌套使用 */

4. 类型——参考Java的类

  • 基本类型,如:int,float,bool,string;
  • 结构化的(复合的),如:struct,array,slic,map,channel;
  • 只描述类型的行为的,如:interface;

使用var声明的变量的值会自动初始化为该类型的零值,类型定义了某个变量的值的集合与可对其进行操作的集合。

结构化的类型初始化时没有真正的值,它使用nil作为默认值,且Go中不存在类型继承。

函数也可以是一个确定的类型,就是以函数返回类型作为其类型。且函数的返回类型定义可以返回多个值,之间需要使用逗号分割,并使用小括号括起来:

func FunctionName(a typea,b typeb) (t1 typet1,t2 typet2)

使用type关键字可以定义自己的类型(自定义类?)。Go语言中的变量声明方式:

var varName varType = value

每个值都必须在经过编译后属于某个类型!!!

5. Go程序的一般结构——理论上:

  • 在完成包的 import 之后,开始对常量、变量和类型的定义或声明。
  • 如果存在 init 函数的话,则对该函数进行定义(这是一个特殊的函数,每个含有该函数的包都会首先执行这个函数)。
  • 如果当前包是 main 包,则定义 main 函数。
  • 然后定义其余的函数,首先是类型的方法,接着是按照 main 函数中先后调用的顺序来定义相关函数,如果有很多函数,则可以按照字母顺序来进行排序。

6. 类型转换:

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

valueOfTypeB = typeB(valueOfTypeA)
//示例:
a := 5.0
b := int(a)

7. 常量:常量使用关键字const定义,用于存储不会改变的数据;

存储在常量中的数据类型只可以是布尔型,数字型和字符串型;

常量的定义格式:

const identifier [type] = value
const Pi = 3.14159

在Go语言的常量定义中,可以省略类型说明符,因为编译器可以根据常量的值来推断其类型,未定义类型的常量会在必要时可根据上下文来获得相关类型;

常量的值必须是在编译时就能够确定的,可以在其赋值表达式中涉及计算过程,但是所有用于计算的值必须在编译期间就能获得。

const test = getNumber()
//引发构建错误,因为在编译期间自定义函数均属于未知
//无法用于常量的赋值,但内置函数可以使用

一些常量赋值方式:

const beef,two,c = "eat",2,"veg"
const Monday,Tuesday,Wednesday,Thursday,Friday,Saturday=1,2,3,4,5,6

//常量还可用作枚举
const (
	Unknown=0
    Female=1
    Male=2
)

//iota关键字,可以用作枚举值
const (
	a=iota
    b=iota
    c=iota
)
//第一个iota等于0,每当iota在新的一行被使用时
//它的值都会+1,所以代表a=0,b=1,c=2
//iota在每次遇到新的const关键字,就会被重置为0
//上面的代码也可以简写成以下形式
const (
	a=iota
    b
    c
)

8. 变量

  • 声明

    var identifier type
    

    Go和许多编程语言不同的地方是,它在声明变量时将变量的类型放在变量的名称之后,这样做是为了避免像C语言中那样含糊不清的声明方式:

    int* a,b;
    

    在上面的这条C语言声明语句中,只有a是指针而b不是,而在Go中则可以较好地解决这个问题:

    var a,b *int		//在这里,a和b都是指针
    

    同时,声明和赋值可以组合起来在一个语句中完成,因为Go编译器可以根据变量的值来自动推断其类型,有点像Python这类动态语言,只不过Python是在运行时进行推断,而Go是在编译时就已经完成了推断过程,因此,Go的变量声明与赋值可以如下:

    var a = 15
    var a int = 15
    //这两条语句的效果是一样的
    
    var a
    //像这种语法是不正确的,并无法让我们判断变量a是什么类型的变量
    

    变量的声明也可以使用简短声明语法 " := ",但是该用法只能初始化某个变量,一旦变量经过了var关键字或者简短声明语法进行初始化,那么再使用简短声明语法则会直接报错。var通常用于声明包级别的全局变量,在函数体内声明局部变量时建议使用简短声明语法。

  • 值类型与引用类型:

    像int,float,bool,string这些基本类型都属于值类型,使用这些类型的变量直接指向内存中的值,像数组和结构这些复合类型也是值类型,在使用 “=” 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝。值类型的变量的值存储在栈中。

    引用类型的变量即为指针,存储的是目标对象的值所在的内存地址,或者内存地址中第一个字所在的位置,当对于引用类型的变量使用赋值语句时,只有引用地址被复制。

  • 打印函数:

    fmt.Sprintf()
    fmt.Printf()
    //这两个函数的作用是相同的,需要使用占位符,即传统的C语言打印
    //比较特殊的一个占位符是"%v",它直接调用对象的toString占位(以Java的理解)
    
    fmt.Print()
    fmt.Println()
    //这两个函数则会自动使用格式化标识符"%v"对字符串进行格式化,即Java的打印方法
    
  • 并行赋值:

    Go语言支持并行赋值,如下列这种方式:

    a,b,c := 5,7,"abc"
    

    同时,如果想要交换两个变量的值,则可以简单地使用以下这种方式:

    a,b = b,a
    

    并行赋值也可以用来接收,多返回值函数的调用结果。

  • init函数:

    变量除了可以在全局声明中初始化,也可以在init函数中初始化。这是一类非常特殊的函数,它不能被人为地调用,而是在每个包完成初始化后自动执行,并且执行优先级比main函数高。

    每个源文件都只能包含一个init函数,初始化总是以单线程执行,并且按照包的依赖关系顺序执行。

9. 基本类型和运算符:

  • bool布尔类型:只能是true或false;两个类型相同的值可以使用相等 “==” 或者不等 “!=” 运算符来进行比较并获得一个布尔类型的值。

    Go对于值之间的比较又非常严格的限制,只有两个类型相同的值才可以进行比较。

    在格式化输出的时候,可以使用 “%t” 来表示要输出的值为布尔型。

  • 数字类型(int和float):Go语言支持整型和浮点型数字,并且原生支持复数。

    Go中也有基于架构(指操作系统的位数)的类型,例如:int、uint和uintptr。这种类型的长度都是根据运行程序所在的操作系统类型所决定的:

    • int和uint都是根据运行程序所在的操作系统类型所决定的,在32位操作系统上用4个字节,在64位操作系统上用8个字节;
    • uintptr的长度被设定为足够存放一个指针即可;

    与操作系统架构无关的类型都有固定的大小,并在类型的名称中就可以看出来:

    整数:(int型是计算最快的一种类型)
    - int8
    - int16
    - int32
    - int64
    
    无符号整数:
    - uint8
    - uint16
    - uint32
    - uint64
    
    浮点型:
    - float32(精确到小数点后7位)
    - float64(精确到小数点后15位,尽可能使用float64,因为math包中所有有关数学运算的函数都会要求接收这个类型)
    

    可以通过增加前缀0的方式表示8进制数(如:077),增加前缀0x来表示16进制数(如:0xFF),使用e来表示10的连乘(如:1e3=1000);

    高精度的数字类型向低精度的数字类型进行转换的时候容易出现异常,不建议,或者自己编写安全的精度转换工具。

  • 复数:

    Go拥有以下复数类型:
    - complex64(32位实数和虚数)
    - complex128(64位实数和虚数)
    

    复数使用re+imI表示,其中re代表实数部分,im代表虚数部分,I代表根号-1;

    var c1 complex64 = 5+10i
    fmt.Printf("The value is: %v",c1)
    //The value is: 5+10i
    

    函数 real© 和 imag© 可以分别获得复数中相应的实数和虚数部分,在使用格式化说明符时,可以使用%v来表示复数。

    cmath库中包含了一些操作复数的公共方法,如果对内存的要求不高,最好使用complex128作为计算类型。

  • 位运算:位运算只能用于整数类型的变量,且需要当它们拥有等长位模式时,%b是用来表示位的格式化标识符。

    • 按位异或:^,作为二元运算符时
    • 位清除:&^,将运算符左边数据相异的位保留,相同位清零
    • 按位取反:^,作为一元运算符时,包括符号位在内
  • 算术运算符:++和–的语义虽然和Java中一样,但是带有++和–的只能作为语句,不能作为表达式。因此,n=i++这种写法是无效的,其他的像f(i++)或者a[i]=b[i++]这种语法也是不允许的。

  • 随机数:rand包实现了伪随机数的生成

    import "math/rand"
    
    rand.Int()			//随机生成int64类型的数据,非负
    rand.Intn(a int)	//随机生成a范围内(小于a且非负)的数据
    rand.Float32()		//随机生成float32类型的数据(0.0-1.0,包括0.0但不包括1.0)
    
    rand.Seed(Value)	//提供伪随机数的生成种子,默认情况下使用当前时间
    
  • 类型别名:type关键字的一种用法

    type TZ int			//此后可以使用TZ声明int类型变量
    

    但是,类型别名得到的新类型并非和原类型完全相同,新类型不会拥有原类型所附带的方法。

  • 字符类型(byte):严格来说,字符只是整数的特殊用例

    var ch byte = 'A'
    

    格式化说明符%c用于表示字符,%U情况下则会输出字符的Unicode编码。包unicode包含了一些针对测试字符的非常有用的函数:

    unicode.IsLetter(ch)	//判断是否为字母
    unicode.isDigit(ch)		//判断是否为数字
    unicode.IsSpace(ch)		//判断是否为空白符号
    

10. 字符串:

字符串是UTF-8字符的一个序列,当字符为ASCII码时则占用1个字节,其他字符则根据需要占用2-4个字节。像Java是始终使用2个字节的,Go这样做的好处是不仅减少了内存和硬盘空间占用,同时也不用像其他语言一样需要对使用UTF-8字符集的文本进行编码和解码。字符串是一种值类型,且值不可变。

  • 解释字符串——常见字符串,如果遇到转义字符则会被相应地转义

    \n:	换行符
    \r:	回车符
    \t:	tab键
    \u:	Unicode字符
    \\:	反斜杠本身
    
  • 非解释字符串:该类字符串使用反引号括起来,不会对转义字符做处理,支持换行。

在字符串中可以通过索引的方式获得其中的某个字符,但值得注意的是,因为前面说了,字符串中的每个字符并不是等长的,因此可能会出现问题,只建议在纯ASCII字符串中使用。同时,获取字符串中某个字节的地址的行为是非法的。

可以通过len()函数获得字符串的长度。

字符串的拼接:

  • 可以通过使用 “+” 将字符串拼接在一起或者使用 “+=”;
  • 可以使用strings.Join()函数,参考StringBuilder与String的区别;
  • 也可以使用字节缓冲bytes.Buffer拼接;
package main

import (
	"fmt"
	"unicode/utf8"
)

func main() {
	// count number of characters:
	str1 := "asSASA ddd dsjkdsjs dk"
	fmt.Printf("The number of bytes in string str1 is %d\n", len(str1))
	fmt.Printf("The number of characters in string str1 is %d\n", utf8.RuneCountInString(str1))
	str2 := "asSASA ddd dsjkdsjsこん dk"
	fmt.Printf("The number of bytes in string str2 is %d\n", len(str2))
	fmt.Printf("The number of characters in string str2 is %d", utf8.RuneCountInString(str2))
}

//运行结果:
//str1的字节长度为22,字符数为22
//str2的字节长度为28,字符数为24

11. strings——字符串操作包

  • 前缀和后缀:

    strings.HasPrefix(s,prefix string) bool
    //判断字符串s是否以字符串prefix开头,返回bool型数据
    
    strings.HasSuffix(s,suffix string) bool
    //判断字符串s是否以字符串suffix结尾,返回bool型数据
    
  • 包含关系:

    strings.Contains(s,substr string) bool
    //判断字符串s是否包含字符串substr,返回bool型数据
    
  • 判断子字符串或字符在父字符串中出现的位置(索引):

    strings.Index(s,str string) int
    //返回字符串str在字符串s中的索引,-1表示不存在
    strings.LstIndex(s,str string) int
    //返回字符串str在字符串s中最后出现位置的索引,-1表示不存在
    //如果ch是非ASCII编码的字符,建议使用以下函数对字符进行定位
    strings.IndexRune(s string,r rune) int
    
  • 字符串替换:

    strings.Replace(str,old,new,n) string
    //将字符串str中的前n个字符串old替换为字符串new,并返回一个新的字符串
    //如果n=-1则替换所有字符串old为字符串new
    
  • 统计字符串出现次数:

    strings.Count(s,str string) int
    //计算字符串str在字符串s中出现的非重叠次数
    
  • 重复字符串:

    ```go
    strings.Repeat(s,count int) string
    //用于重复count次字符串s并返回一个新的字符串
    ```
    
  • 转换大小写:

    strings.ToLower(s) string
    strings.Toupper(s) string
    //只转换Unicode字符
    
  • 修剪字符串:

    strings.TrimSpace(s)
    //剔除字符串开头和结尾的空白符号
    strings.Trim(s,cut)
    //剔除字符串开头和结尾的cut字符串
    strings.TrimLeft()
    strings.TrimRight()
    
  • 分割字符串:

    strings.Fields(s)
    //默认识别空字符串作为分割符号,将其放到string数组中返回
    strings.Split(s,sep)
    //使用sep作为分割符号,将字符串s一一分割并将结果放到string数组中并返回
    
  • 拼接slice到字符串:

    strings.Join(sl []string,sep string) string
    //使用sep作为分割符号将sl中的字符拼接起来
    
  • 从字符串中读取内容:

    strings.NewReader(str)
    //用于生成一个Reader并读取字符串中的内容,然后返回指向该Reader的指针
    - Read()				//从[]byte中读取内容
    - ReadByte(),ReadRune()	//从字符串中读取下一个byte或者rune
    

12. strconv——字符串转换包

strconv.Itoa(i int) string
//返回数字i所表示的字符串类型的十进制数
strconv.FormatFloat(f float64,fmt byte,prec int,bitSize int) string
//将64位浮点型数字转换为字符串,fmt表示格式,prec表示精度
//bitSize则用32表示float32,用64表示float64
strconv.Atoi(s string)(i int,err error)
//将字符串转换为int型
strconv.ParseFloat(s string,bitSize int) (f float64,err error)
//将字符串转换为float64型

13. 时间和日期:time包

var t Time = time.Now()
//获取当前时间
t.Year()
//获取当前时间的年份
t.Month()
//获取当前时间的月份
t.Day()
//获取当前时间的日期
t.Minute()
//获取当前时间的分钟
t.Format("02 Jan 2006 15:04")
//设置t的格式化与目标字符串显式格式相同,Go的独特设置
//也可以使用time.RFC822和time.ANSIC这两种格式

14. 指针:

Go语言的取地址符是&,一个指针变量可以指向任何一个值的内存地址,在32位机器上占用4个字节,在64位机器上占用8个字节(这里指的是指针变量的大小),也可以通过在指针类型前面加上*号来获取指针指向的内容。

在使用*获取指针的内容冰箱要做出修改时,需要记住,所有的指针必须初始化后,即真正指向了一个变量之后,才可以对其中的内容进行修改。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值