一、第一个程序
基本程序结构
package main // 包 import "fmt" // 引入依赖代码
// 功能实现
func main() { fmt.Println("hello world!") }
注意点:
1.应用程序入口
- 必须是main包:package main
- 必须是main方法:func main()
- 文件名不一定是main.go
2.退出返回值
与其他主要编程语言的差异
- Go中main函数不支持任何返回值
- 通过os.Exit来返回状态
3.获取命令行参数
main函数不支持传入参数
func main()
在程序中直接通过os.Args获取命令行参数
编写测试文件
源码文件以_test结尾:xxx_test.go
测试方法名以Test开头:func TestXXX(t *testing.T){...}
package try_test import "testing" func TestFirstTry(t *testing.T) { t.Log("My first try!") }
结果
=== RUN TestFirstTry --- PASS: TestFirstTry (0.00s) first_test.go:7: My first try! PASS
Fibonacci数列
package try import ( "fmt" "testing" ) func TestFibList(t *testing.T) { var a int = 1 var b int = 1 //var{ // 可以省略int,类型推断 // a int = 1 // b int = 1 //} // 类型推断,初试化 //a := 1 //b := 1 fmt.Print(a, " ") for i:=0;i<5;i++{ fmt.Print(" ", b) tmp:=a a=b b=tmp+a } fmt.Println() }
二、变量、常量、数据类型和运算符
变量赋值
与其他主要编程语言的差异
赋值可以进行自动推断
在一个赋值语句中可以对多个变量进行同时赋值
func TestExchage(t *testing.T){ a := 1 b := 1 //tmp := a //a = b //b = tmp a, b= b, a t.Log(a, b) }
常量定义
与其他主要编程语言的差异
快速设置连续值
const ( Monday = iota + 1 Tuesday Wednesday Thrusday Friday Saturday Sunday ) const ( Open = 1 << iota Close Pending )
例子
package _const import "testing" const ( Monday = iota + 1 Tuesday Wednesday ) const( Readable = 1<<iota Writable Executable ) func TestConstantTry(t *testing.T) { t.Log(Monday, Tuesday, Wednesday) } // 1,2,3 func TestConstantTry1(t *testing.T) { a := 7 // 0111 t.Log(a&Readable==Readable, a&Writable==Writable, a&Executable==Executable) } // true,true,true
数据类型
基本数据类型
类型转化
与其他主要编程语言的差异
1.Go语言不允许隐式类型转换
2.别名和原有类型也不能进行隐式类型转换
package type_test import "testing" type MyInt int64 func TestImplict(t *testing.T) { var a int = 1 var b int64 //b = a // 不支持隐式类型转换 b = int64(a) t.Log(a, b) var c MyInt // c = b // 别名的隐式类型转换也不支持 c = MyInt(b) t.Log(a, b, c) }
类型的预定义值
指针类型
与其他主要编程语言的差异
1.不支持指针运算
func TestPoint(t *testing.T) { a := 1 aPtr := &a t.Log(a, aPtr) t.Logf("%T %T", a, aPtr) //aPtr = aPtr + 1 // 出错,不支持指针运算 }
结果
=== RUN TestPoint
--- PASS: TestPoint (0.00s)
type_test.go:22: 1 0xc04205e290
type_test.go:23: int *int
PASS
2.string是值类型,其默认初试化值为空字符串,而不是nil
func TestString(t *testing.T) { var s string t.Log("*"+s+"*") t.Log(len(s)) } 结果 === RUN TestString --- PASS: TestString (0.00s) type_test.go:29: ** type_test.go:30: 0 PASS
运算符
算术运算符
比较运算符
用==比较数组
- 相同维数且含有相同个数元素的数组才可以比较
- 每个元素都相同的才相等
逻辑运算符
位运算符
与其他主要编程语言的差异
三、编写结构化程序
for循环
和其他主要编程语言的差异
示例
实例
func TestWhileLoop(t *testing.T) { n := 0 for n < 5{ t.Log(n) n++ } } 结果 === RUN TestWhileLoop --- PASS: TestWhileLoop (0.00s) type_test.go:36: 0 type_test.go:36: 1 type_test.go:36: 2 type_test.go:36: 3 type_test.go:36: 4 PASS
if条件
与其他编程语言的差异
- condition表达式结果必须为布尔值
- 支持变量赋值
if var declaration; condition { // code to be executed if condition is true }
func TestIfMultiSec(t *testing.T) { if a := 1 == 1;a { t.Log("1==1") } } // a是true,所以执行括号里内容 结果 === RUN TestIfMultiSec --- PASS: TestIfMultiSec (0.00s) type_test.go:43: 1==1 PASS
switch条件
与其他主要编程语言的差异
- 条件表达式不限制为常量或整数
- 单个case中,可以出现多个结果选项,使用逗号分隔
- 与C等规则相反,Go语言不需要用break来明确退出一个case
- 可以不设定switch之后的条件表达式,在此情况下,整个switch结构与多个if...else...的逻辑作用等同
可以有多个选项
func TestSwitchMultiCase(t *testing.T) { for i := 0; i < 5; i++{ switch i { case 0, 2: t.Log("Even") case 1,3: t.Log("Odd") default: t.Log("It is not 0-3") } } }
相当于if...else...使用
func TestSwitchCaseCondition(t *testing.T) { for i := 0; i < 5; i++ { switch { case i%2 == 0: t.Log("Even") case i%2 == 1: t.Log("Odd") default: t.Log("unknow") } } }
四、数组和切片
数组的声明
数组元素遍历
与其他主要编程语言的差异
数组截取
切片内部结构
切片声明
切片共享存储结构
数组vs切片
- 容量是否可伸缩
- 是否可以进行比较
五、Map
Map声明
Map元素访问
与其他主要编程语言的差异
Map遍历
Map与工厂模式
- Map的value可以是一个方法
- 与Go的Dock type接口方式一起,可以方便实现单一方法对象的工厂模式
实现Set
六、字符串
与其他主要编程语言的差异
Unicode UTF8
编码与存储
常用字符串函数
package main import ( "fmt" "strings" ) func main() { var s string = "hello go" //判断字符串s是否包含子串 r := strings.Contains(s, "go") fmt.Println(r) //true var s1 []string = []string{"1", "2", "3", "4"} //将一系列字符串连接为一个字符串,之间用sep来分隔 s2 := strings.Join(s1, ",") fmt.Println(s2) //1,2,3,4 //子串sep在字符串s中第一次出现的位置,不存在则返回-1 n1 := strings.Index(s, "go") fmt.Println(n1) //6 //返回count个s串联的字符串 s3 := strings.Repeat(s, 2) fmt.Println(s3) //hello gohello go //返回将s中前n个不重叠old子串都替换为new的新字符串,如果n<0会替换所有old子串 s4 := strings.Replace(s, "o", "e", -1) fmt.Println(s4) //helle ge //字符串切割,如果后一个参数为空,则按字符切割,返回字符串切片 s5 := strings.Split(s, " ") fmt.Println(s5) //[hello go] //切除字符串两端的某类字符串 s6 := strings.Trim(s, "o") fmt.Println(s6) //hello g //去除字符串的空格符,并且按照空格分割返回slice s7 := " hello go " s8 := strings.Fields(s7) fmt.Println(s8) //[hello go] }
七、文件读写
文件读写
创建文件 Ceate :文件不存在创建,文件存在,将文件内容清空
参数:name,打开文件的路径,可以是绝对路径和相对路径
func main() { file, err := os.Create("./hello.txt") if err != nil { fmt.Println("Create error:", err) return } defer file.Close() fmt.Println("succes!") }
打开文件 Open :以只读方式打开文件,文件不存在也会打开失败
参数:name,打开文件的路径,可以是绝对路径和相对路径
package main import ( "fmt"; "os" ) func main() { file, err := os.Open("./hello.txt") if err != nil { fmt.Println("Open error:", err) return } defer file.Close() _, err = file.WriteString("hello") if err != nil { fmt.Println("Write error:", err) return } fmt.Println("succes!") }
结果
Write error: write ./hello.txt: Access is denied.
打开文件 OpenFile:以只读、只写、读写方式打开文件
参数1:name,打开文件的路径,可以是绝对路径和相对路径
参数2:打开文件权限,O_RDONLY、O_WRONLY、O_RDWR
参数3:一般传6
const (
O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件
O_RDWR int = syscall.O_RDWR // 读写模式打开文件
O_APPEND int = syscall.O_APPEND // 写操作时将数据附加到文件尾部
O_CREATE int = syscall.O_CREAT // 如果不存在将创建一个新文件
O_EXCL int = syscall.O_EXCL // 和O_CREATE配合使用,文件必须不存在
O_SYNC int = syscall.O_SYNC // 打开文件用于同步I/O
O_TRUNC int = syscall.O_TRUNC // 如果可能,打开时清空文件
)
package main import ( "fmt"; "os" ) func main() { file, err := os.OpenFile("./hello.txt", os.O_RDWR, 6) if err != nil { fmt.Println("OpenFile error:", err) return } defer file.Close() _, err = file.WriteString("hello") if err != nil { fmt.Println("Write error:", err) return } fmt.Println("succes!") }
结果
succes!
写文件
按字符串写:WriteString(string str)
_, err = file.WriteString("hello")
回车换行 Windows为 Linux为
按位置写:
Seek():修改文件的读写指针位置
func (f *File) Seek(offset int64, whence int) (ret int64, err error) { if err := f.checkValid("seek"); err != nil { return 0, err } r, e := f.seek(offset, whence) if e == nil && f.dirinfo != nil && r != 0 { e = syscall.EISDIR } if e != nil { return 0, f.wrapErr("seek", e) } return r, nil }
参数1: 偏移量。正是向文件尾偏,负是向文件头偏
参数2:偏移起始位置
io.SeekStart:文件起始的位置 0
io.SeekCurrent:文件当前位置 1
io.SeekEnd:文件结尾位置 2
返回值:表示从文件起始位置到当前文件读写指针位置的偏移量
按字节写:
writeAt():在文件指定偏移位置,写入[]byte,通常搭配Seek()
参数1:带写入的数据
参数2:偏移量
返回值:实际写出的字节数
package main import ( "fmt" "io" "os" ) func main() { file, err := os.OpenFile("./hello.txt", os.O_RDWR, 6) if err != nil { fmt.Println("OpenFile error:", err) return } defer file.Close() _, err = file.WriteString("hello") if err != nil { fmt.Println("Write error:", err) return } off, _ := file.Seek(0, io.SeekStart) fmt.Println(off) // 0 off, _ = file.Seek(1, io.SeekCurrent) fmt.Println(off) // 1 off, _ = file.Seek(0, io.SeekEnd) fmt.Println(off) // 5 off, _ = file.Seek(-1, io.SeekEnd) fmt.Println(off) // 4 off, _ = file.Seek(-2, io.SeekEnd) fmt.Println(off) // 3 n, _ := file.WriteAt([]byte("111"), off) fmt.Println(n) //3 fmt.Println("succes!") }
文件 hello ---> hel111,偏移量3的地方开始写:
读文件
// Reader implements buffering for an io.Reader object. type Reader struct { buf []byte rd io.Reader // reader provided by the client r, w int // buf read and write positions err error lastByte int // last byte read for UnreadByte; -1 means invalid lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid }
func NewReaderSize(rd io.Reader, size int) *Reader { // Is it already a Reader? b, ok := rd.(*Reader) if ok && len(b.buf) >= size { return b } if size < minReadBufferSize { size = minReadBufferSize } r := new(Reader) r.reset(make([]byte, size), rd) return r } // NewReader returns a new Reader whose buffer has the default size. func NewReader(rd io.Reader) *Reader { return NewReaderSize(rd, defaultBufSize) }
NewReader 返回的是结构体指针变量
// ReadBytes reads until the first occurrence of delim in the input, // returning a slice containing the data up to and including the delimiter. // If ReadBytes encounters an error before finding a delimiter, // it returns the data read before the error and the error itself (often io.EOF). // ReadBytes returns err != nil if and only if the returned data does not end in // delim. // For simple uses, a Scanner may be more convenient. func (b *Reader) ReadBytes(delim byte) ([]byte, error) { // Use ReadSlice to look for array, // accumulating full buffers. var frag []byte var full [][]byte var err error n := 0 for { var e error frag, e = b.ReadSlice(delim) if e == nil { // got final fragment break } if e != ErrBufferFull { // unexpected error err = e break } // Make a copy of the buffer. buf := make([]byte, len(frag)) copy(buf, frag) full = append(full, buf) n += len(buf) } n += len(frag) // Allocate new buffer to hold the full pieces and the fragment. buf := make([]byte, n) n = 0 // Copy full pieces and fragment in. for i := range full { n += copy(buf[n:], full[i]) } copy(buf[n:], frag) return buf, err }
按行读:
package main import ( "bufio" "fmt" "io" "os" ) func main() { file, err := os.OpenFile("./hello.txt", os.O_RDWR, 6) if err != nil { fmt.Println("OpenFile error:", err) return } defer file.Close() fmt.Println("succes!") // 创建一个带缓冲区的reader reader := bufio.NewReader(file) for{ buf, err := reader.ReadBytes(' ') // 读一行 if err != nil && err == io.EOF{ fmt.Println("文件读取完毕") return }else if err != nil { fmt.Println("ReadBytes error:", err) } fmt.Print(string(buf)) } }
按字节读、写文件
最常用
read()
write()
大文件拷贝
package main import ( "fmt" "io" "os" ) func main() { file_r, err := os.Open("E:/文档/xmind/C#.png") if err != nil { fmt.Println("Open err: ", err) return } defer file_r.Close() // 创建写文件 file_w, err := os.Create("./C#.png") if err != nil { fmt.Println("Create err: ", err) return } defer file_w.Close() // 从读文件中读取数据,放到缓冲区中 buf := make([]byte, 4096) // 循环,从读文件中读取数据,拷贝都写文件中 for { n, err := file_r.Read(buf) if err != nil && err == io.EOF { fmt.Printf("已经读完 n=%d ", n) return } file_w.Write(buf[: n]) // 读多少,写多少 } }
结果
已经读完 n=0
因此,我们也可以通过n=0来判断文件读取完毕
目录操作
打开目录,和打开目录是一样的
目录中的内容叫目录项
打开目录 OpenFile:以只读、只写、读写方式打开目录
参数1:name,打开文件的路径,可以是绝对路径和相对路径
参数2:打开文件权限,O_RDONLY、O_WRONLY、O_RDWR
参数3:os.ModeDir
返回值:返回一个可以读写目录的文件指针
读目录
func (f *File) Readdir(n int) ([]FileInfo, error) { if f == nil { return nil, ErrInvalid } return f.readdir(n) }
参数:n,表示读取目录的成员个数。通常传-1,表示读取目录所有文件对象。
返回值:FileInfo类型的切片。其内部保存了文件名。error中保存错误信息。
// A FileInfo describes a file and is returned by Stat and Lstat. type FileInfo interface { Name() string // base name of the file Size() int64 // length in bytes for regular files; system-dependent for others Mode() FileMode // file mode bits ModTime() time.Time // modification time IsDir() bool // abbreviation for Mode().IsDir() Sys() interface{} // underlying data source (can return nil) }
得到FileInfo类型切片后,可以range遍历切片元素,使用.Name()获取文件名。使用.Size()获取文件大小,使用.IsDir()判断文件是目录还是非目录文件
如,我们可以提示用户提供一个目录位置,打开该目录,查看目录下的所有成员,并判别它们是文件还是目录。
package main import ( "fmt" "os" ) func main() { // 获取用户输入的目录路径 fmt.Println("请输入待查询的目录:") var path string path = "./" // 打开目录 f, err := os.OpenFile(path, os.O_RDONLY, os.ModeDir) if err != nil { fmt.Println("Open dir error: ", err) } defer f.Close() // 读取目录项 info, err := f.Readdir(-1) for _, fileInfo := range info{ if fileInfo.IsDir(){ fmt.Println(fileInfo.Name(), " 是一个目录") } else { fmt.Println(fileInfo.Name(), " 是一个文件c") } } }