首页 > 编程语言 >1小时快速了解Go语言(写给打算转职golang的程序员)

1小时快速了解Go语言(写给打算转职golang的程序员)

时间:2024-09-14 11:21:02浏览次数:10  
标签:err int 转职 var golang go func Go main

本文章也有对应的视频讲解:1小时快速了解Go语言

开发环境搭建

Go语言下载地址:https://go.dev/dl,Windows、Mac、Linux都支持,Windows和Mac下载后直接双击安装即可,Linux下载后解压到任意目录都可以,Linux需要手动设置环境变量GOROOT/GOPATH/PATH。
IDE使用Vscode即可,下载地址:https://code.visualstudio.com/Download,在vscode里安装一个扩展,名叫“Go”,出自Go Team at Google。
写第一段Go代码:

package main

import "fmt"

func main() {
	var a float64
	fmt.Printf("the value of a is %f\n", a)
}

保存上述go代码,文件名必须以.go结尾,运行go代码:

go run xxx.go

main()函数是程序的唯一入口,且main()函数必须位于package main中,一个目录下只能存在一个main()函数。
import里指明需要引用的其他package。
Printf()是格式化输出函数,其位于fmt这个package下,%f是浮点数的占位符。
在Go语言里左大括号{统一置于行尾,不能另起一行书写。

基础数据类型

变量声明:

var a int   // 此时a=0

任何时候,Go语言总是变量名在前,类型在后。其他常用的基础数据类型还有int32、int64、float32、float64、string、bool。
变量初始化,即第一次给变量赋值:

var a int = 1
var a = 1   // 根据值推断类型
a := 1      // 省略var,用:代替

修改变量的值:

a = 2

强制类型转换:

var a float64
var b int64
var c int
c = int(b)          // int64不会自动转为int
a = float64(c)      // int不会自动转为float64

指针:

a := 3
var b *int = &a     // &是取址符号,*int表示指向int的指针类型
fmt.Printf("a的地址是%p\n", b)      // 格式化输出地址使用%p
var c int = *b + 7      // 指针前面加个*,把指针类型转为原始的数据类型
fmt.Printf("a+7=%d\n", c)

函数

func swap(a int, b int) (c int, d int){
    c, d = b, a
    return
}

func关键字表示后面要定义一个函数。
函数可以有0个或多个参数,这里指a和b。
函数可以有0个或多个返回值,这里指c和d。

package main

import (
	"fmt"
	"time"
)

func work() {
	begin := time.Now() //当前时间
	defer func() {      //defer后面跟匿名函数
		diff := time.Now().Sub(begin) //计算时间差
		fmt.Printf("用时%d毫秒\n", diff.Milliseconds())
	}()     //小括号里用于传递参数值,匿名函数参数为空,所以这里没有传值
	time.Sleep(2 * time.Second) //休眠2秒钟
}

defer后面跟的代码或匿名函数,在主函数(这里指work())临退出之前才执行。

结构体

定义结构体:

type User struct{
    name string
    age int
}

创建结构体实例:

u1 := User{name: "大乔乔", age: 18}
u2 := User{age: 18}     // name为空字符串
u3 := User{}            // name为空字符串,age为0

读写结构体的成员变量:

u3.age = 28
var a int = u1.age + u2.age

结构体可以拥有0个或多个成员方法:

func (u User) GetAge() int {
    return u.age
}
func (u *User) SetAge(a int) {
    u.age = a
}

成员方法其实等价于普通函数:

// 函数参数传递的是User的拷贝
func GetAge(u User) int {
    return u.age
}
// 函数参数传递的是User的指针
func SetAge(u *User, a int) {
    u.age = a
}

调用结构体的成员方法:

u3.SetAge(u2.GetAge())

Go语言里不存在静态成员,所以成员变量和成员方法只能通过结构体的实例来访问。
Go语言不存在继承,但是可以把一个结构体作为另一个结构体的成员变量的类型。

type Leader struct{
    TeamSize int
    human User
}
var l Leader    // 声明结构体变量后,其成员变量均为空值
l.human = User{name: "大乔乔", age: 18}
l.human.SetAge(l.human.GetAge()+10)

接口

接口是一组方法的集合。

type Animal interface{
    GetAge() int
    SetAge(int)
}

凡是具备GetAge和SetAge这两个方法的结构体都实现了Animal接口,比如上文的User结构体,即可以认为User是一种Animal。

var a Animal
a = u1

也就是说GetAge和SetAge是成为Animal的准入条件,那如果一个接口里没有任何方法(空接口),就可以认为任意类型都实现了这个接口。Go语言自带了一个空接口类型--any,任何数据类型都属于一种any。

type any interface{}
var b any
b = u1
b = false 
b = 9
b = "golang"

结构体实现接口,不需要在定义结构体时显式使用implements等关键字。
函数参数、结构体的成员变量都可以是接口类型。

func getAge(a Animal) int{
    return a.GetAge() + 10
}

type Arm struct{
    Member Animal
    country string
}

func (arm Arm) GetAge () int {
    return arm.Member.GetAge() + 10
}

引用数据类型

切片

切片的定义

type slice struct { 
    array unsafe.Pointer    // 底层数组的指针
    len int 
    cap int 
}

由于切片持有一个数组的指针,我们称切片“引用”了一个数组,所以切片是一种引用数据类型。

arr := make([]int, 3, 5)       // 切片指向一个int数组,长度为3,容量为5
arr[0], arr[1], arr[2] = 2, 9, 7    // 给切片的3个元素赋值
brr := arr      // 切片的赋值拷贝

注意brr:=arr,go语言里的所有等号赋值都会发生拷贝,由于切片是一个包含3个成员变量的结构体,所以切片的拷贝实际上拷贝的是那3个成员变量,也就是说并没有拷贝底层数组,而只是拷贝了底层数组的指针。
brr[0]=3把底层数组的首元素修改成了3,此时arr[0]也变成了3。
切片的长度可以增加:

arr = append(arr, 5)    // arr的长度为4,brr的长度还是3,即brr[3]会发生数组越界,而arr[3]没问题

新元素5追加到7后面,即放到预分配内存里。注意append函数并不会修改切片的长度,只是它会返回一个新切片,新切片的长度增加了。
通过多次append如果预分配内存填满了,则go会自动申请一块更大的底层数组,把老数组的元素逐一拷贝到新数组里去。

arr = append(arr, 5)    // arr的长度为5,brr的长度还是3。注意此时预分配内存已填满
arr = append(arr, 5)    // arr的长度为6,容量为10。arr指向新的更大的数组,brr还指向老数组

切片的遍历有两种方式:

var sum int   // 此时sum的值是0
for i:=0;i<len(arr);i++{
    sum += arr[i]
}

sum = 0
for i,ele := range arr{
    _ = i   // 变量i声明了就必须使用,这里把它赋给_,_相当于占位符,_无法被读取
    sum += ele
}

fmt.Println(sum)

map

map也是一种引用数据类型,它引用的是一个哈希表HashTable。
map的声明和赋值:

var m map[int]string        //声明map变量,key是int类型,value是string类型
m := map[int]string{3:"abc", 7:"mmj"}   //声明并初始化
m[5]="hsi"      //往map里添加一对key-value,也可能是更新key对应的value
delete(m, 3)    //从map里删除key及其对于的value

map的遍历:

for key, value := range m{
    fmt.Printf("key=%d, value=%s\n", key, value)
}

channel

channel也是一种引用数据类型,它引用的是一个环形队列,队列意味着先进先出。
声明并初始化channel变量:

ch := make(chan int, 10)   //队列里存在int类型的元素,队列容量为10

读写channel:

ch <- 1     //向队列尾部添加元素1。如果队列已满,则此操作会阻塞
a := <-ch   //从队列首部取走一个元素,并赋给变量a。如果队列已空,则此操作会阻塞

遍历channel:

close(ch)
for ele := range ch{
    fmt.Println(ele)
}

注意遍历channel之前一定要先close它,表示不允许再向队列里写入元素,否则遍历channel的for循环可能一直无法退出甚至发生死锁。

并发

Go语言的并发性能非常优异,Go语言使用协程而非线程并发执行任务,协程比线程占用的内存空间更小,协程的创建、销毁、切换成本也更低。
go语言启动一个协程非常方便,应该是所有语言里面最方便的:

// 没有返回值的函数
func work(arg string){
    // do something
}

go work("abc")  //启动一个协程,异步地去执行work函数

// 或者直接在go关键字后面跟匿名函数
go func(arg string){
    // do something
}("abc")

其它语言通过类似于join的函数等待线程结束,go语言里等待一个协程运行结束会比较麻烦。

package main

import (
	"fmt"
	"time"
)

func main() {
	var result int
	ch := make(chan bool)
	go func() { //启动子协程
		time.Sleep(2 * time.Second)
		result = 5 //go语言不能获得子协程的返回值,只能通过一个公共变量来传递结果
		ch <- true
	}()
	<-ch //channel为空,读操作阻塞。子协程结束后写channel,此时读channel才解除阻塞,从而实现了main协程等子协程结束
	fmt.Println(result)
}

文件读写

// 读取一个文件的全部内容,写入另外一个文件
func CopyFile() {
	fin, err := os.Open("a.go") //打开文件,准备读它
	if err != nil {
		log.Fatalf("打开文件a.go失败: %v\n", err)
	}
	defer fin.Close() //用完之后,记得关闭文件

	fout, err := os.OpenFile("b.go", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) //打开文件,准备读它
	if err != nil {
		log.Fatalf("打开文件b.go失败: %v\n", err)
	}
	defer fout.Close() //用完之后,记得关闭文件

	buffer := make([]byte, 1024) //创建长度为容量均为1024的byte切片,作为数据中转站
	for {                        //相当于while true,go语言里没的while关键字
		n, err := fin.Read(buffer) //从文件中读取1024个字节,放入buffer,n是实际读出的字节数,仅最后一次Read会发生n<1024的情况
		if err != nil {
			if err == io.EOF { //EOF(End Of File)表示来到文件末尾
				if n > 0 {
					fout.Write(buffer[0:n]) //[0:n]表示截取切片的前n个元素
				}
			} else {
				fmt.Printf("读文件异常: %v\n", err)
			}
			break
		} else {
			fout.Write(buffer[0:n]) //把中转站里的内容写到一个文件里去
		}
	}
}

依赖管理

包package是go语言里面组织代码的最小单元,注意不是go文件,通常一个目录下的所有go文件的package必须一致,除非另一个package名以_test结尾。一个包下的所有go文件直接合并到一个go文件里(仅删除第一行的package xxx)不会有任何语法错误,这也意味着一个包内不能定义同名的全局变量、结构体、接口、函数(init()函数除外)。
若干个包组成一个模块module,module文件在go.mod文件里指定。go.mod里同时指定了go的版本号,以及本module依赖的所有其他module。
以大写开头的全局变量(即函数外的变量)、函数、结构体、结构体的成员变量、结构体的成员方法、接口可以在其他包中使用,称为“可导出”,类似于其他语言里public的概念。
/--project_root_path
  |
  /--dir1
  |  |
  |  /--a.go
  |
  /--dir2
  |  |
  |  /--b.go
  |
  /--go.mod

go.mod

module dqq/1h

go 1.22.0

通过go mod init dqq/1h命令可以自动生成上述文件。
a.go

package util 

// 可导出函数
func GetAge() int {
	return 1
}

b.go

package main 

import "dqq/1h/dir1"   // {module}/{path}

func main(){
	util.GetAge()    // 调用函数时需要在前面加个包名。如果是访问本包内的函数、变量不加包名
}

如果import的内容不是go语言标准库里的包(即不在$GOROOT/src目录下),且module名称也不是当前module,则会去$GOPATH/pkg/mod目录下去搜寻相应的module。通过go get $module_name命令会去互联网上下载对应module的源代码,放到本地的$GOPATH/pkg/mod目录下,并添加到go.mod文件的require条目里。
一个包下可以有多个init()函数,当import这个包时会去执行该包下的所有init()函数,执行main()函数之前会执行执行package main里的所有init()函数。

数据库编程

go语言官方只写了读写数据库的接口,并没有写任何具体的实现。所以针对各种数据库的读写只能依赖第三方库,而很多第三方库并没有按照go官方的接口来实现。
github.com/go-sql-driver/mysql按照go官方接口的约定,实现了针对mysql的读写。
先下载这个库:

go get github.com/go-sql-driver/mysql

package main

import (
	"database/sql"
	"fmt"
	"os"
	"time"

	_ "github.com/go-sql-driver/mysql"     // 注册接口实现
)

func CheckError(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "fatal error: %s\n", err.Error()) //stdout是行缓冲的,他的输出会放在一个buffer里面,只有到换行的时候,才会输出到屏幕。而stderr是无缓冲的,会直接输出
		os.Exit(1)
	}
}

func main() {
	//DSN(data source name)格式:[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
	db, err := sql.Open("mysql", "tester:123456@tcp(localhost:3306)/test?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai")
	CheckError(err)
	defer db.Close()
	rows, err := db.Query("select id,name,city,score,enrollment from student where enrollment>=20220703 limit 5") //查询得分大于2的记录
	CheckError(err)
	defer rows.Close()
	for rows.Next() { //没有数据或发生error时返回false
		var id int
		var score float32
		var name, city string
		var enrollment time.Time
		err = rows.Scan(&id, &name, &city, &score, &enrollment) //通过scan把db里的数据赋给go变量
		CheckError(err)
		fmt.Printf("id=%d, score=%.2f, name=%s, city=%s, enrollment=%s \n", id, score, name, city, enrollment.Format("2006-01-02 15:04:05"))
	}
}

http编程

package main

import (
	"net/http"
)

func home(w http.ResponseWriter, request *http.Request) {
	// 从request里可以取得http请求体和请求头
	w.Header().Set("key", "value") //先设置响应头
	w.Write([]byte("Welcome"))     //后设置响应体

}

func main() {
	http.HandleFunc("/", home)                                         //路由,请求要目录时去执行HelloHandler
	if err := http.ListenAndServe("127.0.0.1:5678", nil); err != nil { //ListenAndServe如果不发生error会一直阻塞。为每一个请求单独创建一个协程去处理
		panic(err) //打印错误信息和调用堆栈,以状态2结束进程
	}
}

打开浏览器,在地址栏输入http://localhost:5678/,查看响应体和响应头。

grpc编程

首先需要编写proto文件。

echo.proto

syntax="proto3";

option go_package = "./idl;idl";  //分号前是go文件的输出目录,分号后是go文件的package名

message Content {
    string data = 1;
}

service EchoService {
    rpc Echo(Content) returns (Content);
}

通过以下命令将.proto文件转为go文件。

protoc --go_out=. --go-grpc_out=. --proto_path=. echo.proto

服务端代码:

package main

import (
	"context"
	"dqq/1h/idl"
	"net"

	"google.golang.org/grpc"
)

type MyEcho struct {
	idl.UnimplementedEchoServiceServer //EchoServiceServer接口要求所有的实现必须内嵌一个UnimplementedEchoServiceServer,用于将来的扩展
}

func (s MyEcho) Echo(ctx context.Context, request *idl.Content) (*idl.Content, error) {
	if request == nil {
		return nil, nil
	}
	return &idl.Content{Data: request.Data}, nil //返回结构体指针,加个&符号。error是个接口,可以用nil赋值
}

func main() {
	// 监听本地的5678端口
	lis, err := net.Listen("tcp", "127.0.0.1:5678")
	if err != nil {
		panic(err)
	}
	//创建服务
	server := grpc.NewServer()
	// 注册服务的具体实现
	idl.RegisterEchoServiceServer(server, &MyEcho{})
	// 启动服务
	err = server.Serve(lis)
	if err != nil {
		panic(err)
	}
}

客户端代码:

package main

import (
	"context"
	"dqq/1h/idl"
	"fmt"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
	//连接到服务端
	conn, err := grpc.NewClient(
		"127.0.0.1:5678",
		grpc.WithTransportCredentials(insecure.NewCredentials()), //身份认证Credential即使为空,也必须设置
	)
	if err != nil {
		fmt.Printf("dial failed: %s", err)
		return
	}
	//创建client
	client := idl.NewEchoServiceClient(conn)
	request := &idl.Content{Data: "大乔乔"}                    //构造请求
	resp, err := client.Echo(context.Background(), request) //发起grpc调用
	if err != nil {
		fmt.Printf("rpc failed: %v\n", err)
	} else {
		fmt.Println(resp.Data) //打印调用结果
	}
}

进阶内容

enjoy your golang travels! 如需快速、高效、深入的学习Go语言,欢迎试听我录制的golang视频课程《golang从入门到通天》(电脑端按Ctrl++放大页面观看)

标签:err,int,转职,var,golang,go,func,Go,main
From: https://blog.csdn.net/OrisunZhang/article/details/142252904

相关文章

  • Golang中命名参数的高级使用技巧与实例分析
    Golang是一门以简洁、高效著称的编程语言,因其强大的并发处理能力和灵活的语法设计,被广泛应用于各种开发场景。在Golang中,参数传递是日常编程中不可或缺的部分,而命名参数的使用则能够提升代码的可读性和维护性。本文将探讨Golang中命名参数的高级使用技巧,并通过实例进行分析,帮助开......
  • GO语言初步详细介绍以及环境变量的配置----保姆级教程
    一:概述Go语言(也称为Golang)是一种由Google公司设计和开发的静态类型、编译型编程语言。自2009年正式对外发布以来,Go语言以其简洁、高效和强大的并发处理能力迅速赢得了开发者的青睐,并在多个领域得到广泛应用。二:具体说明<1>Go语言的详细介绍1.1Go语言的特点简洁、易读和易写:Go语言......
  • Django的IT人才招聘网站管理系统的设计与实现-附源码03763
    摘   要随着信息技术行业的迅速发展,企业对于高素质的IT人才的需求日益增长。为了满足企业招聘需求和提供更好的求职体验,开发一个高效、可靠的招聘网站管理系统变得尤为重要。论文将首先介绍Django框架的特点和优势,包括其灵活性、可扩展性和安全性等方面。然后,我们将详细......
  • 单例模式 Go
    单例模式单例模式的基本概念单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。它是一种常用的模式,可以在多种编程语言中实现。以下是单例模式的一些关键特点和理论概念:关键特性单一实例:确保只创建一个实例。全局访问:提供一个全局访问点......
  • 解决Go程序可执行文件在alpine容器中无法运行
    Go可执行程序在alpine容器中无法运行的问题解决今天遇到一个问题,我把我的go应用编译好之后,在Dockerfile里指定它到容器中启动,但是启动不起来,我通过测试,发现了这个现象:我的程序是在容器里的,但是我要运行时,它缺提示notfound原因notfound不是说找不到这个程序,而是找不到需要的......
  • 如何学习go语言
    学习Go语言是一个系统而逐步深入的过程,以下是一些关键步骤和建议,帮助你高效地学习Go语言:书在python33点(0M一、了解Go语言的基础知识学习语法和基本概念:阅读官方的Go语言文档,包括语言规范、标准库和示例代码,了解Go语言的基本特性和用法。Go语言拥有简洁的语法结构和高效......
  • 创建一个Django项目
    步骤1:创建Django项目和应用创建Django项目:bashdjango-adminstartprojectmyprojectcdmyproject创建Django应用:bashpythonmanage.pystartappmyapp安装并配置DjangoRestFramework(如果尚未安装):bashpipinstalldjangorestframework将myapp和rest_f......
  • 基于django+vue大学生学科竞赛管理系统【开题报告+程序+论文】-计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着高等教育的不断发展,学科竞赛已成为培养大学生创新精神、实践能力和团队协作精神的重要途径。然而,传统的手工管理方式在处理日益增长的......
  • 基于django+vue大学生学科竞赛管理系统【开题报告+程序+论文】-计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着高等教育的快速发展与教育理念的持续创新,学科竞赛已成为培养大学生创新能力、实践能力和团队协作精神的重要途径。近年来,各类学科竞赛......
  • 基于django+vue大学生心理咨询服务网站【开题报告+程序+论文】-计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景在当今快节奏、高压力的社会环境中,大学生群体作为社会未来的栋梁,其心理健康问题日益凸显。学业压力、人际关系、职业规划等多重因素交织,使......