第四章 基本结构和基本数据类型
4.1 文件名、关键字和标识符
- 以“.go”为后缀。
- 由小写字母组成,如果以多个部分组成则使用下划线“_”隔开。
- 以下标识符无效:
1、lab(以数字开头)
2、case(go语言关键字)
3、a+b(包含运算符)
4.2 go语言的结构和基本要素
4.2.1 包的概念
每个go文件只属于一个包。
一个包可以由多个以“.go”为后缀的源文件组成。
必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main
4.2.2 包导入
使用import关键字
import "fmt"
多个包的导入 :
import "fmt" import "os"
或
import "fmt"; import "os"
或
import (
"fmt"
"os"
)
或
import ("fmt"; "os")
推荐使用
import (
"fmt"
"os"
)
包的文件路径
- 包如果不以“./”或“/”开头,则go会在全局文件中查找,例如:fmt
- 以“./”开头则会在相对目录中查找
- 以“/”开头的会在绝对目录中查找
包的规范和规则
- 除了“_”符号,包中所有代码对象标识符必须唯一
- 标识符(包括常量、类型、函数、结构字段等等)以大写开头 ,就可以被外部包的代码所使用。(类似java中的public)
- 标识符以小写开头,则对外包不可见。(类似java的private)
- 对外可见的标识符(大写开头),要以包名去访问。
- 导入一个包如果没有使用,则会引发错误:imported and not used:
别名的使用
import fm "fmt" // fm将作为fmt 的别名
4.2.3 函数
如果你学过java或者php,你可能搞不太清楚函数和方法的区别,但你如果学过python,你会发现go中函数和方法的区别很像,但又不一样。接下来先学习函数吧!
函数最简单格式:
func functionName(){}
规范格式:被外部调用的函数遵循Pascal命名法,即首字母大写。否则遵循驼峰命名法,即第一个字母小写,其余单词首字母大写。
func functionName(parameter_list)(return_value_ist){
.........
}
例如:
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { // 这里是处理逻辑代码 // 返回多个值 return value1, value2 }
入口函数
main包下面的main函数,作为程序入口,该函数没有参数,也没有返回值。
4.2.4 注释
单行注释://
多行注释:/**/
几乎所有全局作用域的类型、常量、变量、函数和被导出的对象都应该有一个合理的注释。
关于注释的规范感兴趣的道友可以自行查取相关知识:godoc工具的使用,在第三章。
4.2.5 类型
类型可以是基本类型,如:int、float、bool、string;结构化的(复合的),如:struct、array、slice、 map、channel;只描述类型的行为的,如:interface。
4.2.6 程序的一般结构
- 首先要做的就是命名包
- 接着就导包,不用等到包就不需要导入
- 接下来就是申明函数、常量、变量等等。
需要注意的是一个程序只有一个main包并且只有一个main函数作为入口,一个文件中可以包含多个init函数,并且会依次经行初始化工作。
4.2.7 类型转换
go语言为了避免像C、C++、java等等语言那样,由于隐式转换可能带来数据的不确定性,因此所有的转换都必须显示说明。
类似转换可以看作是函数,当然我们也可以自定义自己的类型转换。
格式:
valueOfTypeB = typeB(valueOfTypeA)
示例:
a := 5.0
b := int(a)
4.2.8 命名规范
干净、可读的代码和简洁性是 Go 追求的主要目标。通过 gofmt 来强制实现统一的代码风格。Go 语言中对象的命 名也应该是简洁且有意义的。像 Java 和 Python 中那样使用混合着大小写和下划线的冗长的名称会严重降低代码 的可读性。名称不需要指出自己所属的包,因为在调用的时候会使用包名作为限定符。返回某个对象的函数或方法的 名称一般都是使用名词,没有 Get... 之类的字符,如果是用于修改某个对象,则使用 SetName 。有必须要的话 可以使用大小写混合的方式,如 MixedCaps 或 mixedCaps,而不是使用下划线来分割多个名称。
4.3 常量
常量就是运行过程中值和地址不变的量。站在权限角度来看,这个常量是只读类型。
格式:
const identifier [type] = value
go为了简化操作定义常量的方式主要有两种:
显式类型定义:
const b string = "abc"
隐式类型定义:
const b = "abc"
注意:常量是在编译是就确定的量。
正确的:
const b = 1/2;
错误的:
const a = functionName() // 不能用函数的返回值作为常量值,因为返回值的地址是不确定的。
常量几种申明方式:
单个声明
const a = 1
多个申明:值得注意的是,类型必须一致
1、一般申明
const ( b = 1 a = 2 )
const ( a = 1 b c = 3 d = iota )
示例
package main import "fmt" const ( a = 1 b c = 3 d = iota e f ) func main() { fmt.Println("a=", a) fmt.Println("b=", b) fmt.Println("c=", c) fmt.Println("d=", d) fmt.Println("e=", e) fmt.Println("f=", f) }
结果:
a= 1 b= 1 c= 3 d= 3 e= 4 f= 5
4.4 变量
申明格式:var identifier type
申明用类型多个变量
var a, b *int;
多个变量声明
var a int
var b bool
var c string
简化var
var (
a int
b bool
c string
)
d := 1 // := 函数局部变量中使用
a, b, c := 1, "a", false
4.4.1 值类型和引用类型
基本类型(int、float、string、bbool)属于值类型,使用这些类型的时候会直接指向内存中的值。
引用类型(指针),指向的是一块内存地址。
本质上来说,值类型和引用类型都是直接指向内存中的值,不同在于值类型指向的是值,而引用类型指向的是值的引用。
比如:a := "string", 使用&a会得到a的地址,这个地址的值就称为a的引用,而应用类型的变量就是保存这些地址的值。
下面用一个例子说明一下
package main import "fmt" func main() { a := "string" b := &a fmt.Println("a的值等于:", a) fmt.Println("a的地址等于:", &a) fmt.Println("b的值等于:", b) fmt.Println("b指向的值等于:", *b) fmt.Println("b的地址等于:", &b) fmt.Println("分割线----------") // 同步改变变量的值 a = "new String" fmt.Println("a的值等于:", a) fmt.Println("a的地址等于:", &a) fmt.Println("b的值等于:", b) fmt.Println("b指向的值等于:", *b) fmt.Println("b的地址等于:", &b) fmt.Println("分割线----------") // 引用类型可以修改引用的地址值 c := "ttt" b = &c fmt.Println("c的值等于:", c) fmt.Println("c的地址等于:", &c) fmt.Println("b的值等于:", b) fmt.Println("b指向的值等于:", *b) fmt.Println("b的地址等于:", &b) }
结果:
a的值等于: string a的地址等于: 0xc00003a1f0 b的值等于: 0xc00003a1f0 b指向的值等于: string b的地址等于: 0xc000006028 分割线---------- a的值等于: new String a的地址等于: 0xc00003a1f0 b的值等于: 0xc00003a1f0 b指向的值等于: new String b的地址等于: 0xc000006028 分割线---------- c的值等于: ttt c的地址等于: 0xc00003a240 b的值等于: 0xc00003a240 b指向的值等于: ttt b的地址等于: 0xc000006028
可能使用过java的人看到a重新赋值后地址依然没有感到疑惑,解释一下,在java中string类型是引用类型(类似例子中的b)。
在这个例子中我们同样看到,使用“&”能够获得该变量的地址,即为引用,使用“*”能从引用中获取到指向的值。
4.4.2 init函数
变量除了可以在全局声明中初始化,也可以在 init 函数中初始化。这是一类非常特殊的函数,它不能够被人为调 用,而是在每个包完成初始化后自动执行,并且执行优先级比 main 函数高。
每一个源文件都可以包含一个或多个 init 函数。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。
示例:
package main var PI float64 func main() { } func init(){ PI = 122333.5666 } func init() { print("PI", PI, "\n") } func init() { PI = 11111.11111 } func init() { print("PI=", PI, "\n") }
4.5 基本类型和运算
bool类型
bool类型的取值只有两个:true和false;可以通过运算符比较获得。
整型(整数)
- int8(-128 -> 127)
- int16(-32768 -> 32767)
- int32(-2,147,483,648 -> 2,147,483,647)
- int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
无符号整数:
- uint8(0 -> 255)
- uint16(0 -> 65,535)
- uint32(0 -> 4,294,967,295)
- uint64(0 -> 18,446,744,073,709,551,615
int和uint在32位操作系统上4个字节,64位系统8个字节
float类型(小数)
- float32(+- 1e-45 -> +- 3.4 * 1e38)
- float64(+- 5 1e-324 -> 107 1e308)
复数
字符类型
var ch byte = 'A'
- 判断是否为字母: unicode.IsLetter(ch)
- 判断是否为数字: unicode.IsDigit(ch)
- 判断是否为空白符号: unicode.IsSpace(ch)
字符串
单引号和双引号的区别:单引号只能写一个字符或者是ASCII码,双引号是字符串,这个定义跟C语言一样,和c/c++不一样的是Go字符串根据长度限定,不是特殊字符“\0”。\0一般作为C语言字符数组的结束符。
说起字符串还得要说一下转义字符,这些常用于字符串中,主要转义字符如下:
- \n :换行符
- \r :回车符
- \t :tab 键
- \u 或 \U :Unicode 字符
- \\ :反斜杠自身
两个字符串连接使用"+"号
运算符优先级
有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由 上至下代表优先级由高到低:
优先级 | 运算符 |
7 | ^(按位取反)、!(非) |
6 | *(乘)、/(除)、%(求余)、<<(位运算左移)、>>(位运算右移)、&(按位与) |
5 | +(加)、-(减)、|(按位或)、^(按位异或) |
4 | ==(相等)、!=(不等)、>(大于)、>= (大于等于)、<(小于)、<=(小于等于) |
3 | <- (管道传值) |
2 | &&(与) |
1 | ||(或) |
大家可以自行查询关于运算符
类型别名
type TZ int
package main type TZ int func main() { var a, b TZ = 3, 4 c := a + b print(c) }
4.6 strings包
判断前缀:strings.HasPerfix(s, prefix string) bool
判断是否以“s”开头
package main import ( "fmt" "strings" ) func main() { s := "string" fmt.Println(strings.HasPrefix(s, "s")) // true fmt.Println(strings.HasPrefix(s, "t")) // false }
判断后缀:strings.HasSuffix(s, suffix string) bool
package main import ( "fmt" "strings" ) func main() { s := "string" fmt.Println(strings.HasSuffix(s, "g")) // true fmt.Println(strings.HasSuffix(s, "t")) // false }
包含子字符串: strings.Contains(s, substr string) bool
package main import ( "fmt" "strings" ) func main() { s := "string" fmt.Println(strings.Contains(s, "ri")) // true fmt.Println(strings.Contains(s, "2e")) // false }
判断子字符串的索引位置,找不到返回-1:strings.index(s, substr string) int
package main import ( "fmt" "strings" ) func main() { s := "my name is hardy,last name is kay" fmt.Println(strings.Index(s, "hardy")) // 11 fmt.Println(strings.Index(s, "is")) // 8 fmt.Println(strings.Index(s, "hello")) // -1 }
字符串替换: strings.Replace(str, old, new string, n int) string
package main import ( "fmt" "strings" ) func main() { s := "my name is hardy,last name is kay" fmt.Println(strings.Replace(s, "hardy", "hello", 2)) // my name is hello,last name is kay fmt.Println(strings.Replace(s, "is", "hello", 2)) // my name hello hardy,last name hello kay }
统计子字符串出现的次数: strings.Count(s, substr string) int
package main import ( "fmt" "strings" ) func main() { s := "my name is hardy,last name is kay" fmt.Println(strings.Count(s, "hardy")) // 1 fmt.Println(strings.Count(s, "hello")) // 0 fmt.Println(strings.Count(s, "is")) // 2 fmt.Println(strings.Count(s, "s")) // 3 }
重复字符串: strings.Repeat(s, 10) string
package main import ( "fmt" "strings" ) func main() { s := "hardy" fmt.Println(strings.Repeat(s, 10)) // hardyhardyhardyhardyhardyhardyhardyhardyhardyhardy }
全部转为大写:ToUpper(s string) string
package main import ( "fmt" "strings" ) func main() { s := "my name is hardy,last name is kay" fmt.Println(strings.ToUpper(s)) // MY NAME IS HARDY,LAST NAME IS KAY }
全部转为小写:ToLower(s string) string
package main import ( "fmt" "strings" ) func main() { s := "MY NAME IS HARDY,LAST NAME IS KAY" fmt.Println(strings.ToLower(s)) // my name is hardy,last name is kay }
剔除开头或结尾的字符:Trim(s string, cutset string) string
类似的操作函数还有:TrimSpace 来剔除字符串开头和结尾的空白符号,剔除开头或者结尾的字符串,则可以使用 TrimLeft 或者 TrimRight 来实现。
package main import ( "fmt" "strings" ) func main() { s := "my name is hardy, my last name is kay my" fmt.Println(strings.Trim(s, "my")) // name is hardy, my last name is kay }
分割字符串
空白字符分割:Fields(s string) []string
package main import ( "fmt" "strings" ) func main() { s := "my name is hardy last name is kay my" r := strings.Fields(s) for k,v := range r{ fmt.Println("k=", k, ", v=", v) } }
结果:
k= 0 , v= my k= 1 , v= name k= 2 , v= is k= 3 , v= hardy k= 4 , v= last k= 5 , v= name k= 6 , v= is k= 7 , v= kay k= 8 , v= my
指定字符串分割:Split(s, sep string) []string
package main import ( "fmt" "strings" ) func main() { s := "my name is hardy last name is kay my" r := strings.Split(s, "i") for k,v := range r{ fmt.Println("k=", k, ", v=", v) } }
结果:
k= 0 , v= my name k= 1 , v= s hardy last name k= 2 , v= s kay my
拼接slice到字符串
package main import ( "fmt" "strings" ) func main() { s := "my name is hardy last name is kay my" r := strings.Fields(s) fmt.Println(strings.Join(r, ",")) //my,name,is,hardy,last,name,is,kay,my }