Home

Awesome

  1. 介绍GoGuide 致力于打造最易懂的 Go语言教程,让天下没有难学的 Go 语言
  2. PDF版本GoGuide PDF1.0 版本下载 提取码:dmqx
  3. 转载须知 :以下所有文章与视频教程皆为我的原创,转载请联系我们,如发现恶意抄袭/搬运,会动用法律武器维护自己的权益,让我们一起维护一个良好的技术创作环境!
  4. Star/Fork 支持:开源不易,如果开源项目帮助你打开 Go 语言的学习大门,希望你能 Star 支持我们,你的支持就是我们持续更新的动力。
  5. 视频教程:待开源 ......
<p align="center"> <a href="https://mp.weixin.qq.com/s/PpeblHk6Ml4w8iNrrw8UAA"><img src="https://img.shields.io/badge/%E5%9C%A8%E7%BA%BF%E9%98%85%E8%AF%BB-read-brightgreen" alt="在线阅读"></a> <a href="#公众号"><img src="https://img.shields.io/badge/%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7-%E4%BB%A3%E7%A0%81%E6%83%85%E7%BC%98-orange" alt="公众号"></a> <a href="#公众号"><img src="https://img.shields.io/badge/PDF-Go%E8%AF%AD%E8%A8%80%E4%BF%9D%E5%A7%86%E7%BA%A7%E6%95%99%E7%A8%8B-orange" alt="PDF"></a> <a href="#联系我"><img src="https://img.shields.io/badge/%E8%81%94%E7%B3%BB%E6%88%91%E4%BB%AC-weChat-orange" alt="联系我"></a> </p>

免责声明

GoGuide 致力于打造最易懂的 Go 学习之旅,设计该项目的过程中可能存在勘误,请认真斟酌识别,学习路线仅供参考,记住,适合自己的才是最好的。

给我一个 Star! ⭐

如果您喜欢或者打算使用这个项目来学习或者来开始你的 Go 语言学习之路,给我一个 Star,谢谢!

GoGuide 学习路线图

C 语言

C 语言知识点详解已经开源,请移步 CNote 开源项目:

CNote 开源项目地址

Go 语言

Go语言基础

什么是Go语言

科普小知识: 1.静态语言: 1.1一般都需要通过编译器(compiler)将源代码翻译成机器码,之后才能执行。程序被编译之后无论是程序中的数据类型还是程序的结构都不可以被改变 1.2静态语言的性能和安全性都非常好, 例如C和C++、Go, 但是C和C++的缺点是开发速度慢, 维护成本高 2.动态语言 2.1一般不需要通过编译器将源代码翻译成机器码,在运行程序的时候才逐行翻译。程序在运行的过程中可以动态修改程序中的数据类型和程序的结构 2.2动态语言开发速度快,维护成本低,例如Ruby和Python, 但是Ruby和Python的性能和安全性又略低


Go语言优势

Go语言的吉祥物是地鼠 地鼠的特点是速度快、成群结队、头脑简单 而Go语言的特点正好也是编程速度快、并发性好、简单易学


Go语言发展史

2007年,谷歌工程师Rob Pike, Ken Thompson和Robert Griesemer开始设计一门全新的语言,这是Go语言的最初原型。
2009年11月10日,Go语言以开放源代码的方式向全球发布。
2011年3月16日,Go语言的第一个稳定(stable)版本r56发布。
2012年3月28日,Go语言的第一个正式版本Go1发布。
2013年4月04日,Go语言的第一个Go 1.1beta1测试版发布。
2013年4月08日,Go语言的第二个Go 1.1beta2测试版发布。
2013年5月02日,Go语言Go 1.1RC1版发布。
2013年5月07日,Go语言Go 1.1RC2版发布。
2013年5月09日,Go语言Go 1.1RC3版发布。 
2013年5月13日,Go语言Go 1.1正式版发布。
2013年9月20日,Go语言Go 1.2RC1版发布。
2013年12月1日,Go语言Go 1.2正式版发布。
2014年6月18日,Go语言Go 1.3版发布。
2014年12月10日,Go语言Go 1.4版发布。
2015年8月19日,Go语言Go 1.5版发布,本次更新中移除了”最后残余的C代码”。
2016年2月17日,Go语言Go 1.6版发布。
2016年8月15日,Go语言Go 1.7版发布。
2017年2月17日,Go语言Go 1.8版发布。
2017年8月24日,Go语言Go 1.9版发布。
2018年2月16日,Go语言Go 1.10版发布。

Go作者

跟着谷歌走吃喝啥都有


Go语言现状


Go语言应用场景


如何学习Go语言

竹子用了4年的时间, 仅仅长了3cm, 从第五年开始, 以每天30cm的速度疯狂地生长, 仅仅用了六周的时间就长到了15米。 其实,在前面的四年, 竹子将根在土壤里延伸了数百平米。 做人做事亦是如此, 不要担心你此时此刻的付出得不到回报, 因为这些付出都是为了扎根。


源文件对比

文件扩展名源类型
.h头文件,存放代码声明
.cC语言源文件,存放代码实现
文件扩展名源类型
.goGo语言源文件,存放代码实现

代码管理对比


关键字对比

12345678
ifelseswitchcasedefaultbreakreturngoto
dowhileforcontinuetypedefstructenumunion
charshortintlongfloatdoublevoidsizeof
signedunsignedconstautoregisterstaticexternvolatile
12345678
ifelseswitchcasedefaultbreakreturngoto
fallthroughforcontinuetypestructvarconstmap
funcinterfacerangeimportpackagedefergoselect
chan

数据类型对比


类型32位编译器64位编译器
char11
int44
float44
double88
short22
long48
long long88
void*48
类型32位编译器64位编译器本质
int8/uint811signed char/unsigned char
int16/uint1622signed short/unsigned short
int32/uint3244signed int/unsigned int
int64/uint6488signed long long int/unsigned long long int
byte11uint8/unsigned char
rune44int32/signed int
int48根据机器位数决定长度
uintptr48根据机器位数决定长度 uint32/uint64
float3244float
float6488double
true11char类型的整型
false11char类型的整型

// 第8行到35行
typedef	signed char		int8;
typedef	unsigned char		uint8;
typedef	signed short		int16;
typedef	unsigned short		uint16;
typedef	signed int		int32;
typedef	unsigned int		uint32;
typedef	signed long long int	int64;
typedef	unsigned long long int	uint64;
typedef	float			float32;
typedef	double			float64;

#ifdef _64BIT
typedef	uint64		uintptr;
typedef	int64		intptr;
typedef	int64		intgo; // Go's int
typedef	uint64		uintgo; // Go's uint
#else
typedef	uint32		uintptr;
typedef	int32		intptr;
typedef	int32		intgo; // Go's int
typedef	uint32		uintgo; // Go's uint
#endif

#ifdef _64BITREG
typedef	uint64		uintreg;
#else
typedef	uint32		uintreg;
#endif

// 第153行到157行
enum
{
	true	= 1,
	false	= 0,
};

install B 时刻: Go本质就是用C语言编写的一门高级编程语言 所以江哥前面教你C语言就是为了今天能让你看懂Go的实现代码,做到知其然知其所以然


常量变量对比

数据类型 变量名称 = 值;
const 数据类型 常量名称 = 值;
var 变量名称 数据类型 = 值;
const 变量名称 数据类型 = 值;

注释对比


运算符对比

运算符描述实例
+相加A + B
-相减A - B
*相乘A * B
/相除B / A
%求余B % A
++自增A++
--自减A--

运算符描述实例
==检查两个值是否相等,如果相等返回 True 否则返回 False。A == B
!=检查两个值是否不相等,如果不相等返回 True 否则返回 False。A != B
>检查左边值是否大于右边值,如果是返回 True 否则返回 False。A > B
<检查左边值是否小于右边值,如果是返回 True 否则返回 False。A < B
>=检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。A >= B
<=检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。A <= B

运算符描述实例
&&如果两边的操作数都是 True,则条件 True,否则为 False。A && B
||如果两边的操作数有一个 True,则条件 True,否则为 False。A || B
!如果条件为 True,则逻辑 NOT 条件 False,否则为 True。!A

运算符描述实例
&参与运算的两数各对应的二进位相与, 对应位只要都是1结果就为1A & B
|参与运算的两数各对应的二进位相或,对应位只要其中一个是1结果就为1A | B
^参与运算的两数各对应的二进位相异或,对应位只要不同结果就是1A ^ B
<<左移运算符,左移n位就是乘以2的n次方A << 2
>>右移运算符,右移n位就是除以2的n次方B >> 2
&^逻辑清零运算符, B对应位是1,A对应位清零,B对应位是0, A对应位保留原样A &^ B
int main(){
	/*
	  0110      a
	&^1011      b 如果b位位1,那么结果为0, 否则结果为a位对应的值
	----------
	  0100
	*/
	a1 := 6
	b1 := 11
	res1 := a1 &^ b1
	fmt.Println("res1 = ", res1) // 4

	/*
	  1011      a
	&^1101      b 如果b位位1,那么结果为0, 否则结果为a位对应的值
	----------
	  0010
	*/
	a2 := 11
	b2 := 13
	res2 := a2 &^ b2
	fmt.Println("res2 = ", res2) // 2
}

运算符描述实例
=将右边赋值给左边C = A + B 将 A + B 表达式结果赋值给 C
+=相加后再赋值C += A 等于 C = C + A
-=相减后再赋值C -= A 等于 C = C - A
*=相乘后再赋值C *= A 等于 C = C * A
/=相除后再赋值C /= A 等于 C = C / A
%=求余后再赋值C %= A 等于 C = C % A
<<=左移赋值C <<= 2 等于 C = C << 2
>>=右移赋值C >>= 2 等于 C = C >> 2
&=位逻辑与赋值C &= 2 等于 C = C & 2
^=位逻辑或赋值C ^= 2 等于 C = C ^ 2
|=位逻辑异或赋值C |= 2 等于 C = C | 2
&^=位逻辑清零赋值C &^= 2 等于 C = C &^ 2

流程控制语句对比


函数和方法对比

返回值类型 函数名称(形参列表) {
        函数体相关语句;
        return 返回值;
}
func  函数名称(形参列表)(返回值列表) {
        函数体相关语句;
        return 返回值;
}
func  (接收者 接受者类型)函数名称(形参列表)(返回值列表) {
        函数体相关语句;
        return 返回值;
}

编程思想对比


其它新增特性

Go语言SDK安装和配置


安装Go语言开发工具


Goland安装


Go语言程序组成


Go语言程序主函数定义格式

int main(int argc, const char * argv[]) {
    return 0;
}
// 告诉系统当前编写的代码属于哪个包
package main
// 定义了一个名称叫做main的函数
func main() {
}

Go语言HelloWorld

package main // 告诉系统当前代码属于main这个包
import "fmt" // 导入打印函数对应的fmt包
func main() {
        // 通过包名.函数名称的方式, 利用fmt包中的打印函数输出语句
	fmt.Println("Hello World!!!")
}

Go语言HelloWorld和C语言HelloWorld异同

返回值类型 函数名称(形参列表) {
        函数体相关语句;
        return 返回值;
}
func  函数名称(形参列表)(返回值列表) {
        函数体相关语句;
        return 返回值;
}
#include <stdio.h>
#include "calculate.h"
int main()
{
    int res = sum(2, 3); // 直接利用函数名称调用函数
    printf("res = %d!\n", res);
    return 0;
}
package main
import (
	"fmt"
	"lesson_1/calculate"
)
func main() {
	res := calculate.Sum(2, 3) // 使用包名.函数名称调用函数
	fmt.Println("res1 = ", res)
}
#include <stdio.h>
#include "calculate.h"
int main()
{
    int res = sum(2, 3); // 不写分号会报错
    printf("res = %d!\n", res); // 不写分号会报错
    return 0; // 不写分号会报错
}
package main
import (
	"fmt"
	"lesson_1/calculate"
)
func main() {
	res := calculate.Sum(2, 3) // 不用写分号
	fmt.Println("res1 = ", res) // 不用写分号
}

Go语言注释


Go语言编码风格

关键字


C语言关键字和Go语言关键字对比

12345678
ifelseswitchcasedefaultbreakreturngoto
dowhileforcontinuetypedefstructenumunion
charshortintlongfloatdoublevoidsizeof
signedunsignedconstautoregisterstaticexternvolatile
12345678
ifelseswitchcasedefaultbreakreturngoto
fallthroughforcontinuetypestructvarconstmap
funcinterfacerangeimportpackagedefergoselect
chan

内建常量
truefalseiotanil
內建类型
intint8int16int32
int64uintuint8uint16
uint32uint64uintptrfloat32
float64complex64complex128bool
byterunestringerror
內建函数
makelencapnew
appendcopydeletereal
imagpanicrecovercomplex

标识符



Go语言数据类型


类型32位编译器64位编译器
char11
int44
float44
double88
short22
long48
long long88
void*48
类型32位编译器64位编译器本质
int8/uint811signed char/unsigned char
int16/uint1622signed short/unsigned short
int32/uint3244signed int/unsigned int
int64/uint6488signed long long int/unsigned long long int
byte11uint8/unsigned char
rune44int32/signed int
int48根据机器位数决定长度
uintptr48根据机器位数决定长度 uint32/uint64
float3244float
float6488double
true11char类型的整型
false11char类型的整型
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	fmt.Println("int size = ", unsafe.Sizeof(int(0)))
	fmt.Println("int8 size = ", unsafe.Sizeof(int8(0)))
	fmt.Println("int16 size = ", unsafe.Sizeof(int16(0)))
	fmt.Println("int32 size = ", unsafe.Sizeof(int32(0)))
	fmt.Println("int64 size = ", unsafe.Sizeof(int64(0)))
	fmt.Println("uint size = ", unsafe.Sizeof(uint(0)))
	fmt.Println("uint8 size = ", unsafe.Sizeof(uint8(0)))
	fmt.Println("uint16 size = ", unsafe.Sizeof(uint16(0)))
	fmt.Println("uint32 size = ", unsafe.Sizeof(uint32(0)))
	fmt.Println("uint64 size = ", unsafe.Sizeof(uint64(0)))
	fmt.Println("uintptr size = ", unsafe.Sizeof(uintptr(0)))
	fmt.Println("byte size = ", unsafe.Sizeof(byte(0)))
	fmt.Println("rune size = ", unsafe.Sizeof(rune(0)))
	fmt.Println("float32 size = ", unsafe.Sizeof(float32(0)))
	fmt.Println("float64 size = ", unsafe.Sizeof(float64(0)))
	fmt.Println("true size = ", unsafe.Sizeof(true))
	fmt.Println("false size = ", unsafe.Sizeof(false))

}

// 第8行到35行
typedef	signed char		int8;
typedef	unsigned char		uint8;
typedef	signed short		int16;
typedef	unsigned short		uint16;
typedef	signed int		int32;
typedef	unsigned int		uint32;
typedef	signed long long int	int64;
typedef	unsigned long long int	uint64;
typedef	float			float32;
typedef	double			float64;

#ifdef _64BIT
typedef	uint64		uintptr;
typedef	int64		intptr;
typedef	int64		intgo; // Go's int
typedef	uint64		uintgo; // Go's uint
#else
typedef	uint32		uintptr;
typedef	int32		intptr;
typedef	int32		intgo; // Go's int
typedef	uint32		uintgo; // Go's uint
#endif

#ifdef _64BITREG
typedef	uint64		uintreg;
#else
typedef	uint32		uintreg;
#endif

// 第153行到157行
enum
{
	true	= 1,
	false	= 0,
};

install B 时刻: Go本质就是用C语言编写的一门高级编程语言 所以江哥前面教你C语言就是为了今天能让你看懂Go的实现代码,做到知其然知其所以然 注意点: 企业开发中一般使用int, 因为int会根据你当前的操作系统自动转换为int32和int64


Go语言变量

数据类型 变量名称;
数据类型 变量名称1, 变量名称2;
#include <stdio.h>

int main(int argc, const char * argv[])
{
    int num1; // 先定义
    num1 = 10; // 后初始化
    printf("num1 = %d\n", num1);

    int num2 = 20; // 定义的同时初始化
    printf("num2 = %d\n", num2);

    // 注意: 同时定义多个变量,不支持定义时初始化, 只能先定义后初始化
    int num3, num4; //同时定义多个变量
    num3 = 30;
    num4 = 40;
    printf("num3 = %d\n", num3);
    printf("num4 = %d\n", num4);

    return 0;
}
// 标准格式
var 变量名称 数据类型 = 值;
// 自动推到类型格式
var 变量名称 = 值;
// 简短格式(golang官方推荐格式)
变量名称 := 值;
package main
import "fmt"
func main() {
	var num1 int // 先定义
	num1 = 10 // 后赋值
	fmt.Println("num1 = ", num1)

	var num2 int = 20 // 定义的同时赋值
	fmt.Println("num2 = ", num2)

	var num3  = 30 // 定义的同时赋值, 并省略数据类型
	fmt.Println("num3 = ", num3)
    
	num4  := 40 // 定义的同时赋值, 并省略关键字和数据类型
	/*
	num4  := 40 等价于
	var num4 int
	num4 = 40
	*/
	fmt.Println("num4 = ", num4)
}

Go语言变量定义注意点

package main
import "fmt"
func main() {
	num := 10
	num := 20 // 编译报错, 重复定义
	fmt.Println("num = ", num)
}
package main
import "fmt"
var num = 10 // 定义一个全局变量
func main() {
	num := 20 // 定义一个局部变量
	fmt.Println("num = ", num)
        test()
}
func test() {
	fmt.Println("num = ", num) // 还是输出10
}

package main
import "fmt"
num := 10 // 编译报错
func main() {
	fmt.Println("num = ", num)
}
package main
import "fmt"
func main() {
	//var num int := 10 // 编译报错
	//var num := 10 // 编译报错
	num int := 10 // 编译报错
	fmt.Println("num = ", num)
	fmt.Println("num = ", num)
}
package main
import "fmt"
func main() {
	var(
		num := 10 // 编译报错
	)
	fmt.Println("num = ", num)
}
package main
import "fmt"
func main() {
	//num1, num2 := 666, 888 // 正确
	num1, num2 := 666 // 报错
	fmt.Printf("%d, %d\n", num1, num2)
}
package main
import "fmt"
func main() {
	// 定义一个变量num1
	num1 := 10
	// 同时定义两个变量num1和num2, 由于num2从来没有定义过,
	// 所以对于num1来说:=退化为赋值运算符, 而对于num2来说:=仍然是定义+赋值
	num1, num2 := 20, 30
	fmt.Println("num1 = ", num1)
	fmt.Println("num2 = ", num2)
}
package main
import "fmt"
func main() {
	num1 := 10
	num2 := 20
	// 报错, 因为num1,和num2都已经被定义过
	// 至少要有任意一个变量没有被定义过,才会退化赋值
	num1, num2 := 30, 40
	fmt.Println("num1 = ", num1)
	fmt.Println("num2 = ", num2)
}

局部变量和全局变量

package main
import "fmt"
//var num1 int
//var num1 int // 报错, 重复定义
var num3 int
func main() {
	//var num2
	//var num2 // 报错, 重复定义
	
	var num3 int // 不报错, 因为作用域不同
	fmt.Println("num3 = ", num3)
}

package main
import "fmt"
func main() {
	var intV int // 整型变量
	var floatV float32 // 实型变量
	var boolV bool // 布尔型变量
	var stringV string // 字符串变量
	var pointerV *int // 指针变量
	var funcV func(int, int)int // function变量
	var interfaceV interface{} // 接口变量
	var sliceV []int // 切片变量
	var channelV chan int // channel变量
	var mapV map[string]string // map变量
	var errorV error // error变量

	fmt.Println("int = ", intV) // 0
	fmt.Println("float = ", floatV) // 0
	fmt.Println("bool = ", boolV) // false
	fmt.Println("string = ", stringV) // ""
	fmt.Println("pointer = ", pointerV) // nil
	fmt.Println("func = ", funcV) // nil
	fmt.Println("interface = ", interfaceV) // nil
	fmt.Println("slice = ", sliceV) // []
	fmt.Println("slice = ", sliceV == nil) // true
	fmt.Println("channel = ", channelV) // nil
	fmt.Println("map = ", mapV) // map[]
	fmt.Println("map = ", mapV == nil) // true
	fmt.Println("error = ", errorV) // nil

	var arraryV [3]int // 数组变量
	type Person struct{
		name string
		age int
	}
	var structV Person // 结构体变量
	fmt.Println("arrary = ", arraryV) // [0, 0, 0]
	fmt.Println("struct = ", structV) // {"" 0}
}

数据类型转换

#include <stdio.h>
int main(){
  // 隐式转换:自动将实型10.6转换为整型后保存
   int a = 10.6;
 // 自动类型提升: 运算时会自动将小类型转换为大类型后运算
  double b = 1.0 / 2; // 等价于1.0 / 2.0
}
#include <stdio.h>
int main(){
  // 显示转换:强制将实型10.6转换为整型后保存
  int a = (int)10.5;
}

数值类型和字符串类型之间转换

	package main
	import "fmt"
	func main() {
		var num1 int32 = 65
		// 可以将整型强制转换, 但是会按照ASCII码表来转换
		// 但是不推荐这样使用
		var str1 string = string(num1)
		fmt.Println(str1)

		var num2 float32 = 3.14
		// 不能将其它基本类型强制转换为字符串类型
		var str2 string = string(num2)
		fmt.Println(str2)

		var str3 string = "97"
		// 不能强制转换, cannot convert str2 (type string) to type int
		var num3  int = int(str3)
		fmt.Println(num3)
	}
  package main
  import "fmt"
  func main() {
  	var num1 int32 = 10
  	// 第一个参数: 需要被转换的整型,必须是int64类型
  	// 第二个参数: 转换为几进制,  必须在2到36之间
  	// 将32位十进制整型变量10转换为字符串,并继续保留10进制格式
  	str1 := strconv.FormatInt(int64(num1), 10)
  	fmt.Println(str1) // 10
  	// 将32位十进制整型变量10转换为字符串,并转换为2进制格式
  	str2 := strconv.FormatInt(int64(num1), 2)
  	fmt.Println(str2) // 1010

  	var num5 float64 = 3.1234567890123456789
  	// 第一个参数: 需要转换的实型, 必须是float64类型
  	// 第二个参数: 转换为什么格式,f小数格式, e指数格式
  	// 第三个参数: 转换之后保留多少位小数, 传入-1按照指定类型有效位保留
  	// 第四个参数: 被转换数据的实际位数,float32就传32, float64就传64
  	// 将float64位实型,按照小数格式并保留默认有效位转换为字符串
  	str3 := strconv.FormatFloat(num5, 'f', -1, 64)
  	fmt.Println(str3) // 3.1234567
  	str4 := strconv.FormatFloat(num5, 'f', -1, 64)
  	fmt.Println(str4) // 3.1234567890123457
  	// 将float64位实型,按照小数格式并保留2位有效位转换为字符串
  	str5 := strconv.FormatFloat(num5, 'f', 2, 64)
  	fmt.Println(str5) // 3.12
  	// 将float64位实型,按照指数格式并保留2位有效位转换为字符串
  	str6 := strconv.FormatFloat(num5, 'e', 2, 64)
  	fmt.Println(str6) // 3.12

  	var num6 bool = true
  	str7 := strconv.FormatBool(num6)
  	fmt.Println(str7) // true
  }
  package main
  import "fmt"
  func main() {
  	var str1 string = "125"
  	// 第一个参数: 需要转换的数据
  	// 第二个参数: 转换为几进制
  	// 第三个参数: 转换为多少位整型
  	// 注意点: ParseInt函数会返回两个值, 一个是转换后的结果, 一个是错误
  	// 如果被转换的数据转换之后没有超出指定的范围或者不能被转换时,
  	// 那么错误为nil, 否则错误不为nil
  	// 将字符串"125"转换为10进制的int8
  	num1, err := strconv.ParseInt(str1, 10, 8)
  	if err != nil {
  		fmt.Println(err)
  	}
  	fmt.Println(num1)

  	var str2 string = "150"
  	// 将字符串"150"转换为10进制的int8
  	// 由于int8的取值范围是-128~127, 所以转换之后超出了指定的范围, error不为nil
  	num2, err := strconv.ParseInt(str2, 10, 8)
  	if err != nil {
  		fmt.Println(err)
  	}
  	fmt.Println(num2)

  	var str3 string = "3.1234567890123456789"
  	// 第一个参数: 需要转换的数据
  	// 第二个参数: 转换为多少位小数, 32 or 64
  	// ParseFloat同样有两个返回值, 如果能够正常转换则错误为nil, 否则不为nil
  	num3, err := strconv.ParseFloat(str3, 32)
  	if err != nil {
  		// 例如: 把字符串"3.14abc"转换为小数就会报错, 因为"3.14abc"不是一个小数
  		fmt.Println(err)
  	}
  	fmt.Println(num3)

  	var str4 string = "true"
  	// 第一个参数: 需要转换的数据
  	// ParseBool同样有两个返回值, 如果能够正常转换则错误为nil, 否则不为nil
  	num4, _ := strconv.ParseBool(str4)
  	fmt.Println(num4)
  }
	package main
	import "fmt"
	func main() {
		var str1 string = "abc"
		num1, _ := strconv.ParseInt(str1, 10, 32)
		fmt.Println(num1) // 0

		num2, _ := strconv.ParseFloat(str1, 32)
		fmt.Println(num2) // 0

		num3, _ := strconv.ParseBool(str1)
		fmt.Println(num3) // false
	}
   package main
   import "fmt"
   func main() {
   	var num1 int32 = 110
   	// 快速将整型转换为字符串类型
   	// 注意:Itoa方法只能接受int类型
   	var str1 string = strconv.Itoa(int(num1))
   	fmt.Println(str1)

   	var str2 string = "666"
   	// 快速将字符串类型转换为整型
   	// 注意: Atoi方法返回两个值, 一个值是int,一个值是error
   	// 如果字符串能被转换为int,那么error为nil, 否则不为nil
   	num2, err := strconv.Atoi(str2)
   	if err != nil{
   		fmt.Println(err)
   	}
   	fmt.Println(num2)
   }
  package main
  import "fmt"
  func main() {
  	var num1 int32 = 110
  	// Sprintf函数和Printf函数很像, 只不过不是输出而将格式化的字符串返回给我们
  	var str1 string = fmt.Sprintf("%d", num1)
  	fmt.Println(str1)

  	var num2 float32 = 3.14
  	var str2 string = fmt.Sprintf("%f", num2)
  	fmt.Println(str2)

  	var num3 bool = true
  	var str3 string = fmt.Sprintf("%t", num3)
  	fmt.Println(str3)
  }

Go语言常量



 enum 枚举名 {
    枚举元素1,
    枚举元素2,
    … …
 };
#include <stdio.h>
int main(int argc, const char * argv[])
{
    enum Gender{
        male,
        female,
        yao,
    };
//    enum Gender g = male;
//    printf("%d\n", g); // 0
//    enum Gender g = female;
//    printf("%d\n", g); // 1
    enum Gender g = yao;
    printf("%d\n", g); // 2
    return 0;
}
#include <stdio.h>
int main(int argc, const char * argv[])
{
    enum Gender{
        male = 5,
        female,
        yao,
    };
//    enum Gender g = male;
//    printf("%d\n", g); // 5
//    enum Gender g = female;
//    printf("%d\n", g); // 6
    enum Gender g = yao;
    printf("%d\n", g); // 7
    return 0;
}

const(
  枚举元素1 = iota
  枚举元素2 = iota
  ... ...
)



package main
import (
	"fmt"
	"net/http"
	"os"
)
func main() {
	// os.Stdout 写入到标准输出
	name := "lnj"
	age := 33
	// 第一个参数: 指定输出到什么地方
	// 第二个参数: 指定格式控制字符串
	// 第三个参数: 指定要输出的数据
	fmt.Fprintf(os.Stdout, "name = %s, age = %d\n", name, age)

	// http.ResponseWriter 写入到网络响应
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		fmt.Fprintf(writer, "name = %s, age = %d\n", name, age)
	})
	http.ListenAndServe(":8888", nil)
}

package main
import (
	"fmt"
	"net/http"
	"os"
)
func main() {
	name := "lnj"
	age := 33
	// 按照指定的格式生成字符串
	str := fmt.Sprintf("name = %s, age = %d\n", name, age)
	// 输出生成的字符串
	fmt.Println(str)
}

输入函数


  package main
  import "fmt"
  func main() {
	  var num1 int
	  var num2 int
	  // 第一个参数: 指定从哪读取数据
	  // 第二个参数: 指定格式控制字符串
	  // 第三个参数: 指定要输出的数据
	  fmt.Fscanf(os.Stdin, "%d%d", &num1, &num2)
	  fmt.Println(num1, num2)


	  s := strings.NewReader("lnj 33")
	  var name string
	  var age int
	  // 从指定字符串中扫描出想要的数据
	  // 注意:
	  fmt.Fscanf(s, "%s%d", &name, &age)
	  fmt.Println("name =",name, "age =",age)
  }
  package main
  import "fmt"
  func main() {
	str := "lnj 33"
	var name string
	var age int
	//fmt.Sscanf(str, "%s %d",&name, &age)
	//fmt.Sscanln(str,&name, &age)
	fmt.Sscan(str,&name, &age)
	fmt.Println("name =",name, "age =",age)
  }

go命令行操作指令

|---项目文件夹 -----------|--------src文件夹 -----------------------------|--------main文件夹 -----------------------------|--------其它文件夹 -----------|--------bin文件夹 -----------|--------pkg文件夹



通过os包获取命令行参数

#include <stdio.h>
int main(int argc, const char * argv[])
{
    for(int i = 0; i < argc; i++){
        printf("argv[%d] = %s\n", i, argv[i]);
    }
    return 0;
}

package main

import (
	"fmt"
	"os" // 用于获取命令行参数的包
)
func main() {
	// 1.获取传入参数个数
	num := len(os.Args)
	// 2.打印所有获取到的参数
	for i := 0; i < num; i++ {
		fmt.Println(os.Args[i])
	}
}


通过flag包获取命令行参数

package main
import (
	"flag"
	"fmt"
)
func main() {
	/*
    flag.Xxxx(name, value, usage)
	第一个参数: 命令行参数名称
	第二个参数: 命令行参数对应的默认值
	第三个参数: 命令行参数对应的说明
	*/
	// 1.设置命令行参数
	name := flag.String("name", "lnj", "请输入人的姓名")
	age := flag.Int("age", 33, "请输入人的年龄")
	// 2.将命令行参数解析到注册的参数
	flag.Parse()
	// 3.使用命令行参数
	// 注意flag对应方法返回的都是指针类型, 所以使用时必须通过指针访问
	fmt.Println("name = ", *name)
	fmt.Println("age = ", *age)
}

package main
import (
	"flag"
	"fmt"
)
func main() {
	/*
	flag.Xxxx(*type, name, value, usage)
	第一个参数:保存命令行参数变量地址
	第二个参数: 命令行参数名称
	第三个参数: 命令行参数对应的默认值
	第四个参数: 命令行参数对应的说明
	*/
	// 1.定义变量,保存命令行参数的值
	var name string
	var age int
	// 2.设置命令行参数
	flag.StringVar(&name, "name", "lnj", "请输入人的姓名")
	flag.IntVar(&age, "age", 33,"请输入人的姓名")
	// 3.注册解析命令行参数
	flag.Parse()
	// 4.使用命令行参数
	fmt.Println("name = ", name)
	fmt.Println("age = ", age)
}

os包和flag包获取命令行参数对比

package main
import (
	"os"
	"fmt"
)
int main(){
	name := os.Args[1]
	age := os.Args[2]
	fmt.Println("name = ", name)
	fmt.Println("age = ", age)
}
package main
import (
	"flag"
	"fmt"
)
int main(){
	name := flag.String("name", "lnj", "请输入人的姓名")
	age := flag.Int("age", 33, "请输入人的年龄")
	// 2.注册解析命令行参数
	flag.Parse()
	// 3.使用命令行参数
	// 注意flag对应方法返回的都是指针类型, 所以使用时必须通过指针访问
	fmt.Println("name = ", *name)
	fmt.Println("age = ", *age)
}

算数运算符

运算符描述实例
+相加A + B
-相减A - B
*相乘A * B
/相除B / A
%求余B % A
++自增A++
--自减A--
package main
import "fmt"
int main(){
	var num1 int32 = 10
	//var num2 int64 = num1 // 类型不同不能进行赋值运算
	var num2 int64 = int64(num1) // 类型不同不能进行赋值运算
	fmt.Println(num2)

	var num3 int32 = 10
	var num4 int64 = 20
	//var res int64 = num3 + num4 // 类型不同不能进行算数运算
	var res1 int64 = int64(num3) + num4 // 类型不同不能进行算数运算
	fmt.Println(res1)

	var num5 int32 = 10
	var num6 int64 = 20
	//var res2 bool = (num5 == num6) // 类型不同不能进行关系运算
	var res2 bool = (num5 == int32(num6)) // 类型不同不能进行关系运算
	fmt.Println(res2)

	// ... ... 其它以此类推
}
package main
import "fmt"
func main() {
	num1 := 0
	num1++
	fmt.Println(num1)
	//++num1 // 编译报错, Go语言中++只能后置,不能前置
	//fmt.Println(num1)
	//var num2 int = num1++ // 编译报错, num1++是语句不是表达式, 所以必须独占一行
	//fmt.Println(num2)
}
package main
import "fmt"
func main() {
	str := "abc" + "def"
	//fmt.Println(str)
}

关系算符

运算符描述实例
==检查两个值是否相等,如果相等返回 True 否则返回 False。A == B
!=检查两个值是否不相等,如果不相等返回 True 否则返回 False。A != B
>检查左边值是否大于右边值,如果是返回 True 否则返回 False。A > B
<检查左边值是否小于右边值,如果是返回 True 否则返回 False。A < B
>=检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。A >= B
<=检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。A <= B

逻辑运算符

运算符描述实例
&&如果两边的操作数都是 True,则条件 True,否则为 False。A && B
||如果两边的操作数有一个 True,则条件 True,否则为 False。A || B
!如果条件为 True,则逻辑 NOT 条件 False,否则为 True。!A

位运算符

运算符描述实例
&参与运算的两数各对应的二进位相与, 对应位只要都是1结果就为1A & B
|参与运算的两数各对应的二进位相或,对应位只要其中一个是1结果就为1A | B
^参与运算的两数各对应的二进位相异或,对应位只要不同结果就是1A ^ B
<<左移运算符,左移n位就是乘以2的n次方A << 2
>>右移运算符,右移n位就是除以2的n次方B >> 2
&^逻辑清零运算符, B对应位是1,A对应位清零,B对应位是0, A对应位保留原样A &^ B
int main(){
	/*
	  0110      a
	&^1011      b 如果b位位1,那么结果为0, 否则结果为a位对应的值
	----------
	  0100
	*/
	a1 := 6
	b1 := 11
	res1 := a1 &^ b1
	fmt.Println("res1 = ", res1) // 4

	/*
	  1011      a
	&^1101      b 如果b位位1,那么结果为0, 否则结果为a位对应的值
	----------
	  0010
	*/
	a2 := 11
	b2 := 13
	res2 := a2 &^ b2
	fmt.Println("res2 = ", res2) // 2
}

赋值运算符

运算符描述实例
=将右边赋值给左边C = A + B 将 A + B 表达式结果赋值给 C
+=相加后再赋值C += A 等于 C = C + A
-=相减后再赋值C -= A 等于 C = C - A
*=相乘后再赋值C *= A 等于 C = C * A
/=相除后再赋值C /= A 等于 C = C / A
%=求余后再赋值C %= A 等于 C = C % A
<<=左移赋值C <<= 2 等于 C = C << 2
>>=右移赋值C >>= 2 等于 C = C >> 2
&=位逻辑与赋值C &= 2 等于 C = C & 2
^=位逻辑或赋值C ^= 2 等于 C = C ^ 2
|=位逻辑异或赋值C |= 2 等于 C = C | 2
&^=位逻辑清零赋值C &^= 2 等于 C = C &^ 2

其它运算符

运算符描述实例
&返回变量存储地址&a; 将给出变量的实际地址
*访问指针指向内存*p; 访问指针p指向内存
package main
import "fmt"
int main(){
	var num int = 666
	var p *int = &num
	fmt.Println(num)
	fmt.Println(*p)
	num = 777
	fmt.Println(num)
	*p = 999
	fmt.Println(num)
}
#include <stdio.h>
int main()
{
    int ages[3] = {19, 23, 22};
    int *arrayP = &ages[0];
    printf("ages[0] = %i\n", *(arrayP + 0)); // *(arrayP + 0) == *arrayP
    printf("ages[1] = %i\n", *(arrayP + 1));
    printf("ages[2] = %i\n", *(arrayP + 2));
    return 0;
}
package main
import "fmt"
int main(){
	var ages [3]int = [3]int{19, 23, 22}
	var p *int = &ages[0]
	//fmt.Println(&ages[0])
	//fmt.Println(*p) // 19
	fmt.Println(*(p + 0)) // 编译报错
}

运算符优先级

Go语言流程控制基本概念


选择结构if

if 初始化语句; 条件表达式{
    语句块;
}
package main
import "fmt"
func main() {
	// 如果后续需要用到age变量, 可以将变量放到if外面
	age := 18
	if age >= 18{
		fmt.Println("成年人")
	}
}
package main
import "fmt"
func main() {
	// 如果后续不需要用到age变量, 可以将变量放到条件表达式前面
	if age := 18; age >= 18{
		fmt.Println("成年人")
	}
}

if 初始化语句; 条件表达式{
    语句块;
}else{
    语句块;
}
package main
import "fmt"
func main() {
	if age := 18;age >= 18 {
		fmt.Println("成年人")
	}else{
		fmt.Println("未成年人")
	}
}
if 初始化语句; 条件表达式{
    语句块;
}else if 条件表达式{
    语句块;
}
... ...
else{
    语句块;
}
package main
import "fmt"
func main() {
	if age := 18;age > 55{
		fmt.Println("老年人")
	}else if age >= 40{
		fmt.Println("中年人")
	}else if age >= 18{
		fmt.Println("成年人")
	}else{
		fmt.Println("未成年人")
	}
}

选择结构switch

switch 初始化语句; 表达式{
  case 表达式1, 表达式2:
        语句块;
  case 表达式1, 表达式2:
        语句块;
  default:
        语句块;
}
package main
import "fmt"
func main() {
	switch num := 3;num {
		case 1:
			fmt.Println("星期一")
		case 2:
			fmt.Println("星期二")
		case 3:
			fmt.Println("星期三")
		case 4:
			fmt.Println("星期四")
		case 5:
			fmt.Println("星期五")
		case 6:
			fmt.Println("星期六")
		case 7:
			fmt.Println("星期日")
		default:
			fmt.Println("Other...")
	}
}
package main
import "fmt"
func main() {
	switch num := 3;num {
	case 1,2,3,4,5:
		fmt.Println("工作日")
	case 6,7:
		fmt.Println("非工作日")
	default:
		fmt.Println("Other...")
	}
}
package main
import "fmt"
func main() {
	switch num := 3;num {
	case 1:
		fallthrough
	case 2:
		fallthrough
	case 3:
		fallthrough
	case 4:
		fallthrough
	case 5:
		fmt.Println("工作日")
	case 6:
		fallthrough
	case 7:
		fmt.Println("非工作日")
	default:
		fmt.Println("Other...")
	}
}
package main
import "fmt"
func main() {
	value := 2
	switch num:=1; num {
	case value: // 变量
		fmt.Println("num等于value")
	default:
		fmt.Println("num不等于value")
	}
}
package main
import "fmt"
func main() {
	value := 2
	switch num:=1; num {
	case getValue(): // 函数
		fmt.Println("num等于value")
	default:
		fmt.Println("num不等于value")
	}
}
func getValue() int {
	return 1
}
package main
import "fmt"
func main() {
	switch num:=18; {
		case num >=0 && num <=10: // 表达式
			fmt.Println("num是一个0~10之间的数")
		case num >10 && num <=20:
			fmt.Println("num是一个11~20之间的数")
		default:
			fmt.Println("num是一个大于20的数")
	}
}
package main
import "fmt"
func main() {
    switch num := 1;num {
	  case 1:
		value := 10 // 不会报错
		fmt.Println(value)
	  default:
		fmt.Println("Other...")
	}
}

循环结构for

for 初始化表达式;循环条件表达式;循环后的操作表达式 {
    循环体语句;
}
package main
import "fmt"
func main() {
	for i:=0; i<10; i++{
		fmt.Println(i)
	}
}
package main
import "fmt"
func main() {
	i:=0
	for i<10 {
		fmt.Println(i)
		i++
	}
}
package main
import "fmt"
func main() {
	for{
		fmt.Println("根本停不下来")
	}
}
for 索引, 值 := range 被遍历数据{
}
package main
import "fmt"
func main() {
	// 1.定义一个数组
	arr := [3]int{1, 3, 5}
	// 2.快速遍历数组
	// i用于保存当前遍历到数组的索引
	// v用于保存当前遍历到数组的值
	for i, v := range arr{
		fmt.Println(i, v)
	}
}

四大跳转

package main
import "fmt"
func main() {
	for i:=0; i<10; i++{
		if(i == 5){
			break // 跳出所在循环
		}
		fmt.Println(i)
	}
}
package main
import "fmt"
func main() {
outer:
	switch num:=2; num {
	case 1:
		fmt.Println(1)
	case 2:
		fmt.Println(2)
		break outer
	default:
		fmt.Println("other")
	}
	fmt.Println("come here")
}
package main
import "fmt"
func main() {
outer:
	for i:=0; i<5; i++{
		for j:=0; j<10; j++ {
			if (j == 5) {
				break outer// 跳出到指定标签
			}
			fmt.Println(j)
		}
	}
	fmt.Println("come here")
}
package main
import "fmt"
func main() {
	for i:=0; i<5; i++{
		if (i == 2) {
			continue
		}
		fmt.Println(i)
	}
}
package main
import "fmt"
func main() {
outer:
	for i:=0; i<5; i++{
		fmt.Println("i =", i) // 0 1 2 3 4
		for j:=0; j<10; j++ {
			if (j == 5) {
				continue outer// 跳出到指定标签
			}
		}
	}
	fmt.Println("come here")
}
package main
import "fmt"
func main() {
	num := 1
outer:
	if(num <= 10){
		fmt.Println(num)
		num++
		goto outer // 死循环
	}
	fmt.Println("come here")
}
package main
import "fmt"
func main() {
	num := 1
	if(num <= 10){
		fmt.Println(num)
		num++
		goto outer // 死循环
	}
outer:
	fmt.Println("come here")
}

Go 语言入门

函数

func 函数名称(形参列表)(返回值列表){
    函数体;
}
func say()  {
	fmt.Println("Hello World!!!")
}
func say(name string)  {
	fmt.Println("Hello ", name)
}
func sum() int { // 只有一个返回值时,返回值列表的()可以省略
	return 1 + 1
}
func sum(a int, b int) int {
	return a + b
}

和C语言函数差异

// 给返回值指定了一个名称叫做res, return时会自动将函数体内部res作为返回值
// 其实本质就是提前定义了一个局部变量res, 在函数体中使用的res就是这个局部变量,返回的也是这个局部变量
func sum() (res int) { 
	res = 1 + 1
	return
}
func calculate(a int, b int) (sum int, sub int) {
	sum = a + b
	sub = a - b
	return
}
// a, b都是int类型, 所以只需要在b后面添加int即可
func calculate(a, b int) (sum, sub int) {
	sum = a + b
	sub = a - b
	return
}
package main
import "fmt"
func main() {
	say();
}
func say()  { // 在后面定义也可以在前面使用
	fmt.Println("Hello World!!!")
}

值传递和引用传递

package main
import "fmt"
func main() {
	num := 10
	change(num)
	fmt.Println(num) // 10
}
func change(num int)  {
	num = 998
}
package main
import "fmt"
func main() {
	arr := [3]int{1, 3, 5}
	change(arr)
	fmt.Println(arr) // 1, 3, 5
}
func change(arr [3]int)  {
	arr[1] = 8
}
package main
import "fmt"
type Person struct {
	name string
	age int
}
func main() {
	p := Person{"lnj", 33}
	change(p)
	fmt.Println(p.name) // lnj
}
func change(p Person)  {
	p.name = "zs"
}
package main
import "fmt"
func main() {
	num := 10
	change(&num)
	fmt.Println(num) // 998
}
func change(num *int)  {
	*num = 998
}
package main
import "fmt"
func main() {
	arr := []int{1, 3, 5}
	change(arr)
	fmt.Println(arr) // 1, 8, 5
}
func change(arr []int)  {
	arr[1] = 8
}
package main
import "fmt"
func main() {
	mp := map[string]string{"name":"lnj", "age":"33"}
	change(mp)
	fmt.Println(mp["name"]) // zs
}
func change(mp map[string]string)  {
	mp["name"] = "zs"
}

匿名函数

package main
import "fmt"
func main() {
	func(s string){
		fmt.Println(s)
	}("hello lnj")
}
package main
import "fmt"
func main() {
	a := func(s string) {
		fmt.Println(s)
	}
	a("hello lnj")
}
package main
import "fmt"
func main() {
	test(func(s string) {
		fmt.Println(s)
	})
}
func test(f func(s string))  {
	f("hello lnj")
}
package main
import "fmt"
func main() {
	res := test()
	res(10, 20)
}
func test() func(int, int) {
	return func(a int, b int) {
		fmt.Println(a + b)
	}
}
package main
import "fmt"
func main() {
	// 项目经理的一天
	work(func() {
		fmt.Println("组织部门开会")
		fmt.Println("给部门员工分配今天工作任务")
		fmt.Println("检查部门员工昨天提交的代码")
		fmt.Println("... ...")
	})
	// 程序员的一天
	work(func() {
		fmt.Println("参加部门会议")
		fmt.Println("修改测试提交的BUG")
		fmt.Println("完成老大今天安排的任务")
		fmt.Println("... ...")
	})
}
// 假设我们需要编写一个函数,用于描述一个人每天上班都需要干嘛
// 职场中的人每天上班前,上班后要做的事几乎都是相同的, 但是每天上班过程中要做的事确实不确定的
// 所以此时我们可以使用匿名函数来解决, 让上班的人自己觉得自己每天上班需要干什么
func work(custom func())  {
	// 上班前
	fmt.Println("起床")
	fmt.Println("刷牙")
	fmt.Println("洗脸")
	fmt.Println("出门")
	fmt.Println("上班打卡")
	fmt.Println("开电脑")

	// 上班中
	custom()

	// 上班后
	fmt.Println("关电脑")
	fmt.Println("下班打卡")
	fmt.Println("出门")
	fmt.Println("到家")
	fmt.Println("吃饭")
	fmt.Println("睡觉")

}
func work(custom func())  {
	// 这种写法的好处是代码层次清晰,并且如果有一些变量
	// 只需要在上班前或上班后使用,还可以将这些变量隔离,不对外界造成污染
	// 上班前
	func(){
		fmt.Println("起床")
		fmt.Println("刷牙")
		fmt.Println("洗脸")
		fmt.Println("出门")
		fmt.Println("上班打卡")
		fmt.Println("开电脑")
	}()

	// 上班中
	custom()

	// 上班后
	func(){
		fmt.Println("关电脑")
		fmt.Println("下班打卡")
		fmt.Println("出门")
		fmt.Println("到家")
		fmt.Println("吃饭")
		fmt.Println("睡觉")
	}()

}
func work(custom func())  {
	// 前提条件是这个函数只在work函数中使用, 两者有较强的关联性, 否则建议定义为普通函数
	pre := func(){
		fmt.Println("起床")
		fmt.Println("刷牙")
		fmt.Println("洗脸")
		fmt.Println("出门")
		fmt.Println("上班打卡")
		fmt.Println("开电脑")
	}
	latter := func(){
		fmt.Println("关电脑")
		fmt.Println("下班打卡")
		fmt.Println("出门")
		fmt.Println("到家")
		fmt.Println("吃饭")
		fmt.Println("睡觉")
	}
	
	// 上班前
	pre()
	// 上班中
	custom()
	// 上班后
	latter()
}

闭包


延迟调用

  package main
  import "fmt"
  func main() {
	defer fmt.Println("我是第一个被注册的") // 3
	fmt.Println("main函数中调用的Println") // 1
	defer fmt.Println("我是第二个被注册的") // 2
  }

init函数

package main
import  "fmt"
const constValue  = 998 // 1
var gloalVarValue int = abc() // 2
func init() { // 3
	fmt.Println("执行main包中main.go中init函数")
}
func main() { // 4
	fmt.Println("执行main包中main.go中main函数")
}
func abc() int {
	fmt.Println("执行main包中全局变量初始化")
	return 998
}

数组


一维数组

package main
import "fmt"
func main() {
	arr := [...]int{1, 3, 5}
	// 使用数组, 往数组中存放数据
	arr[1] = 666
	// 访问数组, 从数组中获取数据
	fmt.Println(arr[0])
	fmt.Println(arr[1])
	fmt.Println(arr[2])
}
package main
import "fmt"
func main() {
	arr := [...]int{1, 3, 5}
	// 传统for循环遍历
	for i:=0; i<len(arr); i++{
		fmt.Println(i, arr[i])
	}
	// for...range循环遍历
	for i, v := range arr{
		fmt.Println(i, v)
	}
}

二维数组

  package main
  import "fmt"
  func main() {
	// 创建一个两行三列数组
	arr := [2][3]int{
		{1, 2, 3},
		{4, 5, 6}, //注意: 数组换行需要以逗号结尾
	}
	fmt.Println(arr)// [[1 2 3] [4 5 6]]
   }
package main
  import "fmt"
  func main() {
	// 创建一个两行三列数组
	arr := [...][3]int{
		{1, 2, 3},
		{4, 5, 6},
	}
	fmt.Println(arr)// [[1 2 3] [4 5 6]]
   }

切片


  package main
  import "fmt"
  func main() {
	var arr = [5]int{1, 3, 5, 7, 9}
	// 同时指定开始位置和结束位置
	var sce1 = arr[0:2]
	fmt.Println(sce1) // [1 3]

	// 只指定结束位置
	var sce3 = arr[:2]
	fmt.Println(sce3) // [1 3]

	// 只指定开始位置
	var sce2 = arr[0:]
	fmt.Println(sce2) // [1 3 5 7 9]

	// 都不指定
	var sce4 = arr[:]
	fmt.Println(sce4) // [1 3 5 7 9]
  }
 package main
 import "fmt"
 func main() {
	// 第一个参数: 指定切片数据类型
	// 第二个参数: 指定切片的长度
	// 第三个参数: 指定切片的容量
	var sce = make([]int, 3, 5)
	fmt.Println(sce) // [0 0 0]
	fmt.Println(len(sce)) // 3
	fmt.Println(cap(sce)) // 5
	/*
	内部实现原理
	var arr = [5]int{0, 0, 0}
	var sce = arr[0:3]
	*/
 }
 package main
 import "fmt"
 func main() {
	var sce = []int{1, 3, 5}
	fmt.Println(sce) // [1 3 5]
	fmt.Println(len(sce)) // 3
	fmt.Println(cap(sce)) // 3
 }


map(字典、映射)

package main
import "fmt"
func main() {
	var dic map[int]int = map[int]int{0:1, 1:3, 2:5}
	fmt.Println(dic) // map[0:1 1:3 2:5]

	// 获取map中某个key对应的值
	fmt.Println(dic[0]) // 1
	
	// 修改map中某个key对应的值
	dic[1] = 666
	fmt.Println(dic) // map[0:1 1:666 2:5]
}
package main
import "fmt"
func main() {
	var dict map[string]string = map[string]string{"name":"lnj", "age":"33", "gender":"male"}
	fmt.Println(dict)// map[name:lnj age:33 gender:male]
}

package main
import "fmt"
func main() {
	dict  := map[string]string{"name":"lnj", "age":"33", "gender":"male"}
	fmt.Println(dict)// map[name:lnj age:33 gender:male]
}
package main
import "fmt"
func main() {
	var dict = make(map[string]string, 3)
	dict["name"] = "lnj"
	dict["age"] = "33"
	dict["gender"] = "male"
	fmt.Println(dict)// map[age:33 gender:male name:lnj]
}
package main
import "fmt"
func main() {
	var dict = make(map[string]string)
	dict["name"] = "lnj"
	dict["age"] = "33"
	dict["gender"] = "male"
	fmt.Println(dict)// map[age:33 gender:male name:lnj]
}
package main
import "fmt"
func main() {
	// map声明后不能直接使用, 只有通过make或语法糖创建之后才会开辟空间,才能使用
	var dict map[string]string
	dict["name"] = "lnj" // 编译报错
	dict["age"] = "33"
	dict["gender"] = "male"
	fmt.Println(dict)
}

package main
import "fmt"
func main() {
	var dict = make(map[string]string)
	fmt.Println("增加前:", dict) // map[]
	dict["name"] = "lnj"
	fmt.Println("增加后:", dict) // map[name:lnj]
}
package main
import "fmt"
func main() {
	var dict = map[string]string{"name":"lnj", "age":"33", "gender":"male"}
	fmt.Println("修改前:", dict) // map[name:lnj age:33 gender:male]
	dict["name"] = "zs"
	fmt.Println("修改后:", dict) // map[age:33 gender:male name:zs]
}
package main
import "fmt"
func main() {
	var dict = map[string]string{"name":"lnj", "age":"33", "gender":"male"}
	fmt.Println("删除前:", dict) // map[name:lnj age:33 gender:male]
	// 第一个参数: 被操作的字典
	// 第二个参数: 需要删除元素对应的key
	delete(dict, "name")
	fmt.Println("删除后:", dict) // map[age:33 gender:male]
}
package main
import "fmt"
func main() {
	var dict = map[string]string{"name":"lnj", "age":"33", "gender":"male"}
	//value, ok := dict["age"]
	//if(ok){
	//	fmt.Println("有age这个key,值为", value)
	//}else{
	//	fmt.Println("没有age这个key,值为", value)
	//}
	if value, ok := dict["age"]; ok{
		fmt.Println("有age这个key,值为", value)
	}
}
​```go
- ***map遍历***
  + 注意: map和数组以及切片不同,map中存储的数据是无序的, 所以多次打印输出的顺序可能不同
​```go
	var dict = map[string]string{"name":"lnj", "age":"33", "gender":"male"}
	for key, value := range dict{
		fmt.Println(key, value)
	}

结构体

type 类型名称 struct{
  属性名称 属性类型
  属性名称 属性类型
  ... ...
}
type Studentstruct {
	name string
	age int
}

  package main
  import "fmt"
  type Student struct {
	  name string
	  age int
  }
  func main() {
	var stu= Student{"lnj", 33}
	// 获取结构体中某个属性对应的值
	fmt.Println(stu.name)

	// 修改结构体中某个属性对应的值
	stu.name = "zs"
	fmt.Println(stu)
  }
  package main
  import "fmt"
  type Student struct {
	  name string
	  age int
  }
  func main() {
	var stu Student // 相当于 var stu = Student{}
	fmt.Println(stu) // { 0}
	stu.name = "lnj" // 不会报错
	stu.age = 33
	fmt.Println(stu) // {lnj 33}
  }


  package main
  import "fmt"
  func main() {
	type Person1 struct {
		name string
		age int
	}
	type Person2 struct {
		name string
		age int
	}
	type Person3 struct {
		age int
		name string
	}
	type Person4 struct {
		nm string
		age int
	}
	type Person5 struct {
		name string
		age string
	}
	type Person6 struct {
		age int
		name string
		gender string
	}

	var p1 Person1 = Person1{"lnj", 33}
	var p2 Person2
	// 类型名称不一样不能直接赋值(Person1、Person2)
	//p2 = p1

	// 虽然类型名称不一样, 但是两个类型中的`属性名称`、`属性类型`、`属性个数`、`排列顺序`都一样,所以可以强制转换
	p2 = Person2(p1)
	fmt.Println(p2)
	// 两个结构体类型中的`属性名称`、`属性类型`、`属性个数`都一样,但是`排列顺序`不一样,所以不能强制转换
	//var p3 Person3
	//p3 = Person3(p1)
	//fmt.Println(p3)

	// 两个结构体类型中的`属性类型`、`属性个数`、`排列顺序`都一样,但是`属性名称`不一样,所以不能强制转换
	//var p4 Person4
	//p4 = Person4(p1)
	//fmt.Println(p4)

	// 两个结构体类型中的`属性名称`、`属性个数`、`排列顺序`都一样,但是`属性类型`不一样,所以不能强制转换
	//var p5 Person5
	//p5 = Person5(p1)
	//fmt.Println(p5)

	// 两个结构体类型中的`属性名称`、`属性类型`、`排列顺序`都一样,但是`属性个数`不一样,所以不能强制转换
	//var p6 Person6
	//p6 = Person6(p1)
	//fmt.Println(p6)
  }

普通指针

package main

import (
	"fmt"
	"unsafe"
)

func main() {

	var p1 *int;
	var p2 *float64;
	var p3 *bool;
	fmt.Println(unsafe.Sizeof(p1)) // 8
	fmt.Println(unsafe.Sizeof(p2)) // 8
	fmt.Println(unsafe.Sizeof(p3)) // 8
}
package main

import (
	"fmt"
)

func main() {

	// 1.定义一个普通变量
	var num int = 666
	// 2.定义一个指针变量
	var p *int = &num
	fmt.Printf("%p\n", &num) // 0xc042064080
	fmt.Printf("%p\n", p) // 0xc042064080
	fmt.Printf("%T\n", p) // *int
	// 3.通过指针变量操作指向的存储空间
	*p = 888
	// 4.指针变量操作的就是指向变量的存储空间
	fmt.Println(num) // 888
	fmt.Println(*p) // 888
}

指向数组指针

#include <stdio.h>

int main(){
     int arr[3] = {1, 3, 5};
     printf("%p\n", arr); // 0060FEA4
     printf("%p\n", &arr); // 0060FEA4
     printf("%p\n", &arr[0]); // 0060FEA4
}
package main
import "fmt"

func main() {
	var arr [3]int = [3]int{1, 3, 5}
	fmt.Printf("%p\n", arr) // 乱七八糟东西
	fmt.Printf("%p\n", &arr) // 0xc0420620a0
	fmt.Printf("%p\n", &arr[0]) // 0xc0420620a0
}
#include <stdio.h>

int main(){
     int arr[3] = {1, 3, 5};
     int *p1 = arr;
     p1[1] = 6;
     printf("%d\n", arr[1]);

     int *p2 = &arr;
     p2[1] = 7;
     printf("%d\n", arr[1]);

     int *p3 = &arr[0];
     p3[1] = 8;
     printf("%d\n", arr[1]);
}
package main

import "fmt"

func main() {
	// 1.错误, 在Go语言中必须类型一模一样才能赋值
	// arr类型是[3]int, p1的类型是*[3]int
	var p1 *[3]int
	fmt.Printf("%T\n", arr)
	fmt.Printf("%T\n", p1)
	p1 = arr // 报错
	p1[1] = 6
	fmt.Println(arr[1])

	// 2.正确, &arr的类型是*[3]int, p2的类型也是*[3]int
	var p2 *[3]int
	fmt.Printf("%T\n", &arr)
	fmt.Printf("%T\n", p2)
	p2 = &arr
	p2[1] = 6
	fmt.Println(arr[1])

	// 3.错误, &arr[0]的类型是*int, p3的类型也是*[3]int
	var p3 *[3]int
	fmt.Printf("%T\n", &arr[0])
	fmt.Printf("%T\n", p3)
	p3 = &arr[0] // 报错
	p3[1] = 6
	fmt.Println(arr[1])
}
package main

import "fmt"

func main() {


	var arr [3]int = [3]int{1, 3, 5}
	var p *[3]int
	p = &arr
	fmt.Printf("%p\n", &arr) // 0xc0420620a0
	fmt.Printf("%p\n", p) // 0xc0420620a0
	fmt.Println(&arr) // &[1 3 5]
	fmt.Println(p) // &[1 3 5]
	// 指针指向数组之后操作数组的几种方式
	// 1.直接通过数组名操作
	arr[1] = 6
	fmt.Println(arr[1])
	// 2.通过指针间接操作
	(*p)[1] = 7
	fmt.Println((*p)[1])
	fmt.Println(arr[1])
	// 3.通过指针间接操作
	p[1] = 8
	fmt.Println(p[1])
	fmt.Println(arr[1])

	// 注意点: Go语言中的指针, 不支持+1 -1和++ --操作
	*(p + 1) = 9 // 报错
	fmt.Println(*p++) // 报错
	fmt.Println(arr[1])
}

指向切片的指针

package main

import "fmt"

func main() {
	// 1.定义一个切片
	var sce[]int = []int{1, 3, 5}
	// 2.打印切片的地址
	// 切片变量中保存的地址, 也就是指向的那个数组的地址 sce = 0xc0420620a0
	fmt.Printf("sce = %p\n",sce )
	fmt.Println(sce) // [1 3 5]
	// 切片变量自己的地址, &sce = 0xc04205e3e0
	fmt.Printf("&sce = %p\n",&sce )
	fmt.Println(&sce) // &[1 3 5]
	// 3.定义一个指向切片的指针
	var p *[]int
	// 因为必须类型一致才能赋值, 所以将切片变量自己的地址给了指针
	p = &sce
	// 4.打印指针保存的地址
	// 直接打印p打印出来的是保存的切片变量的地址 p = 0xc04205e3e0
	fmt.Printf("p = %p\n", p)
	fmt.Println(p) // &[1 3 5]
	// 打印*p打印出来的是切片变量保存的地址, 也就是数组的地址 *p = 0xc0420620a0
	fmt.Printf("*p = %p\n", *p)
	fmt.Println(*p) // [1 3 5]
	
	// 5.修改切片的值
	// 通过*p找到切片变量指向的存储空间(数组), 然后修改数组中保存的数据
	(*p)[1] = 666
	fmt.Println(sce[1])
}

指向字典指针

package main
import "fmt"
func main() {

	var dict map[string]string = map[string]string{"name":"lnj", "age":"33"}
	var p *map[string]string = &dict
	(*p)["name"] = "zs"
	fmt.Println(dict)
}

指向结构体指针

指针作为函数参数和返回值

方法

func (接收者 数据类型)方法名称(形参列表)(返回值列表){
  方法体
}
package main
import "fmt"
// 1.定义一个结构体
type Person struct {
	name string
	age int
}
// 2.定义一个函数, 并将这个函数和Person结构体绑定在一起
func (p Person)say()  {
	fmt.Println("my name is", p.name, "my age is", p.age)
}
func main() {
	// 3.创建一个结构体变量
	per := Person{"lnj", 33}
	// 4.利用结构体变量调用和结构体绑定的方法
	// 调用时会自动将调用者(per)传递给方法的接收者(p)
	// 所以可以在方法内部通过p方法结构体变量的属性
	per.say()
}


接口

type 接口名称 interface{
  函数声明
}
package main
import "fmt"
// 1.定义一个接口
type usber interface {
	start()
	stop()
}
type Computer struct {
	name string
	model string
}
// 2.实现接口中的所有方法
func (cm Computer)start() {
	fmt.Println("启动电脑")
}
func (cm Computer)stop() {
	fmt.Println("关闭电脑")
}

type Phone struct {
	name string
	model string
}
// 2.实现接口中的所有方法
func (p Phone)start()  {
	fmt.Println("启动手机")
}
func (p Phone)stop()  {
	fmt.Println("关闭手机")
}

// 3.使用接口定义的方法
func working(u usber)  {
	u.start()
	u.stop()
}
func main() {
	cm := Computer{"戴尔", "F1234"}
	working(cm) // 启动电脑 关闭电脑

	p := Phone{"华为", "M10"}
	working(p)  // 启动手机 关闭手机
}

Go语言进阶

面向对象基本概念

面向对象思想


面向对象和面向过程区别






面向对象的特点


类与对象的关系


如何设计一个类

事物名称(类名):人(Person)
属性:身高(height)、年龄(age)
行为(功能):跑(run)、打架(fight)

如何分析一个类

飞机
炮弹
装甲车
老王
热狗
草泥马

如何定义一个类

type Person struct {
    name string // 人的属性
    age int // 人的属性
}
// 人的行为
func (p Person)Say()  {
    fmt.Println("my name is", p.name, "my age is", p.age)
}

如何通过类创建一个对象

// 3.创建一个结构体变量
    p1 := Person{"lnj", 33}
    per.say()

    p2 := Person{"zs", 18}
    per.Say()

不同包中变量、函数、方法、类型公私有问题

package demo

import "fmt"

var num1 int = 123 // 当前包可用
var Num1 int = 123 // 其它包也可用

type person struct { // 当前包可用
	name string // 当前包可用
	age int // 当前包可用
}

type Student struct { // 其它包也可用
	Name string // 其它包也可用
	Age int // 其它包也可用
}

func test1()  { // 当前包可用
	fmt.Println("test1")
}
func Test2()  { // 其它包也可用
	fmt.Println("Test2")
}

func (p person)say()  { // 当前包可用
	fmt.Println(p.name, p.age)
}
func (s Student)Say()  { // 其它包也可用
	fmt.Println(s.Name, s.Age)
}

面向对象三大特性

package model
import "fmt"
type Person struct { // 其它包也可用
	name string // 当前包可用
	age int // 当前包可用
}
func (p *person)SetAge(age int)  {
	// 安全校验
	if age < 0 {
		fmt.Println("年龄不能为负数")
	}
	p.age = age
}
package main
import (
	"fmt"
	"main/model"
)
func main()  {
	// 报错, 因为name和age不是公开的
	//p := model.Person{"lnj", 18}

	// 方式一
	//p := model.Person{}
	//p.SetAge(18)
	//fmt.Println(p)

	// 方式二
	//p := new(model.Person)
	//p.SetAge(18)
	//fmt.Println(p)
}

package main

import "fmt"

type Person struct {
	name string
	age int
}
type Student struct {
	Person // 学生继承了人的特性
	score int
}
type Teacher struct {
	Person // 老师继承了人的特性
	Title string
}
func main()  {
	s := Student{Person{"lnj", 18}, 99}
	//fmt.Println(s.Person.name)
	fmt.Println(s.name) // 两种方式都能访问
	//fmt.Println(s.Person.age)
	fmt.Println(s.age) // 两种方式都能访问
	fmt.Println(s.score)
}
package main

import "fmt"

type Person struct {
	name string // 属性重名
	age int
}
type Student struct {
	Person
	name string // 属性重名
	score int
}
func main()  {
	s := Student{Person{"zs", 18}, "ls", 99}

	fmt.Println(s.Person.name) // zs
	fmt.Println(s.name) // ls
	//fmt.Println(s.Person.age)
	fmt.Println(s.age) // 两种方式都能访问
	fmt.Println(s.score)
}
package main

import "fmt"

type Object struct {
	life int
}
type Person struct {
	Object
	name string
	age int
}
type Student struct {
	Person
	score int
}
func main()  {
	s := Student{Person{Object{77}, "zs", 33}, 99}
	//fmt.Println(s.Person.Object.life)
	//fmt.Println(s.Person.life)
	fmt.Println(s.life) // 三种方式都可以
	//fmt.Println(s.Person.name)
	fmt.Println(s.name) // 两种方式都能访问
	//fmt.Println(s.Person.age)
	fmt.Println(s.age) // 两种方式都能访问
	fmt.Println(s.score)
}
package main

import "fmt"

type Object struct {
	life int
}
type Person struct {
	name string
	age int
}
type Student struct {
	Object
	Person
	score int
}
func main()  {
	s := Student{Object{77}, Person{"zs", 33}, 99}
	//fmt.Println(s.Person.life)
	fmt.Println(s.life) // 两种方式都可以
	//fmt.Println(s.Person.name)
	fmt.Println(s.name) // 两种方式都能访问
	//fmt.Println(s.Person.age)
	fmt.Println(s.age) // 两种方式都能访问
	fmt.Println(s.score)
package main

import "fmt"
type Person struct {
	name string
	age int
}
// 父类方法
func (p Person)say()  {
	fmt.Println("name is ", p.name, "age is ", p.age)
}

type Student struct {
	Person
	score float32
} 

func main() {
	stu := Student{Person{"zs", 18}, 59.9}
	stu.say()
}
package main

import "fmt"
type Person struct {
	name string
	age int
}
// 父类方法
func (p Person)say()  {
	fmt.Println("name is ", p.name, "age is ", p.age)
}

type Student struct {
	Person
	score float32
}
// 子类方法
func (s Student)say()  {
	fmt.Println("name is ", s.name, "age is ", s.age, "score is ", s.score)
}
func main() {
	stu := Student{Person{"zs", 18}, 59.9}
	// 和属性一样, 访问时采用就近原则
	stu.say() 
	// 和属性一样, 方法同名时可以通过指定父类名称的方式, 访问父类方法
	stu.Person.say()
}

猫: 猫-->动物
狗: 狗-->动物
男人 : 男人 -->人 -->高级动物
女人 : 女人 -->人 -->高级动物
package main

import "fmt"
// 1.定义接口
type Animal interface { 
	Eat()
}
type Dog struct {
	name string
	age int
}
// 2.实现接口方法
func (d Dog)Eat()  {
	fmt.Println(d.name, "正在吃东西")
}

type Cat struct {
	name string
	age int
}
// 2.实现接口方法
func (c Cat)Eat()  {
	fmt.Println(c.name, "正在吃东西")
}
// 3.对象特有方法
func (c Cat)Special()  {
	fmt.Println(c.name, "特有方法")
}

func main()  {
	// 1.利用接口类型保存实现了所有接口方法的对象
	var a Animal
	a = Dog{"旺财", 18}
	// 2.利用接口类型调用对象中实现的方法
	a.Eat()
	a = Cat{"喵喵", 18}
	a.Eat()
	// 3.利用接口类型调用对象特有的方法
	//a.Special() // 接口类型只能调用接口中声明的方法, 不能调用对象特有方法
	if cat, ok := a.(Cat); ok{
		cat.Special() // 只有对象本身才能调用对象的特有方法
	}
}

异常处理


打印异常信息

package main
import "fmt"
func main() {
	// 1.创建错误信息
	var err error = fmt.Errorf("这里是错误信息")
	// 2.打印错误信息
	fmt.Println(err) // 这里是错误信息
}
package main
import "fmt"
func main() {
	// 1.创建错误信息
	var err error = errors.New("这里是错误信息")
	// 2.打印错误信息
	fmt.Println(err) // 这里是错误信息
}
package builtin
// 定义了一个名称叫做error的接口
// 接口中声明了一个叫做Error() 的方法
type error interface {
	Error() string
}
// 指定包名为errors
package errors 
// 定义了一个名称叫做errorString的结构体, 里面有一个字符串类型属性s
type errorString struct {
	s string
}
// 实现了error接口中的Error方法
// 内部直接将结构体中保存的字符串返回
func (e *errorString) Error() string {
	return e.s
}
// 定义了一个New函数, 用于创建异常信息
// 注意: New函数的返回值是一个接口类型
func New(text string) error {
        // 返回一个创建好的errorString结构体地址
	return &errorString{text}
}
func Errorf(format string, a ...interface{}) error {
	return errors.New(Sprintf(format, a...))
}
package main
import "fmt"
func div(a, b int) (res int, err error) {
	if(b == 0){
		// 一旦传入的除数为0, 就会返回error信息
		err = errors.New("除数不能为0")
	}else{
		res = a / b
	}
	return
}
func main() {
	//res, err := div(10, 5)
	res, err := div(10, 0)
	if(err != nil){
		fmt.Println(err) // 除数不能为0
	}else{
		fmt.Println(res) // 2
	}
}

中断程序

package main
import "fmt"
func div(a, b int) (res int) {
	if(b == 0){
		//一旦传入的除数为0, 程序就会终止
		panic("除数不能为0")
	}else{
		res = a / b
	}
	return
}
func main() {
	res := div(10, 0)
	fmt.Println(res)
}
package main
import "fmt"
func main() {
	// 例如:数组角标越界, 就会自动触发panic
	var arr = [3]int{1, 3, 5}
	arr[5] = 666 // 报错
	fmt.Println(arr)

	// 例如:除数为0, 就会自动触发panic
	var res = 10 / 0
	fmt.Println(res)
}

恢复程序

package main
import "fmt"
func div(a, b int) (res int) {
	// 定义一个延迟调用的函数, 用于捕获panic异常
	// 注意: 一定要在panic之前定义
	defer func() {
		if err := recover(); err != nil{
			res = -1
			fmt.Println(err) // 除数不能为0
		}
	}()
	if(b == 0){
		//err = errors.New("除数不能为0")
		panic("除数不能为0")
	}else{
		res = a / b
	}
	return
}

func setValue(arr []int, index int ,value int)  {
	arr[index] = value
}
func main() {
	res := div(10, 0)
	fmt.Println(res) // -1
}
package main
import "fmt"
func div(a, b int) (res int) {
	if(b == 0){
		//err = errors.New("除数不能为0")
		panic("除数不能为0")
	}else{
		res = a / b
	}
	return
}
func main() {
	// panic异常会沿着调用堆栈向外传递, 所以也可以在外层捕获
	defer func() {
		if err := recover(); err != nil{
			fmt.Println(err) // 除数不能为0
		}
	}()
	div(10, 0)
}
package main
import "fmt"
func test1()  {
	// 多个异常,只有第一个会被捕获
	defer func() {
		if err := recover(); err != nil{
			fmt.Println(err) // 异常A
		}
	}()
	panic("异常A") // 相当于return, 后面代码不会继续执行
	panic("异常B")
}
func main() {
	test1(10, 0)
}
package main
import "fmt"
func test2()  {
	// 如果有异常写在defer中, 并且其它异常写在defer后面, 那么只有defer中的异常会被捕获
	defer func() {
		if err := recover(); err != nil{
			fmt.Println(err) // 异常A
		}
	}()
	
	defer func() { 
		panic("异常B")
	}()
	panic("异常A")
}
func main() {
	test1(10, 0)
}

字符串相关方法

package main
import "fmt"
func main() {
	str1 := "lnj"
	fmt.Println(len(str1)) // 3
	str2 := "公号:代码情缘"
	fmt.Println(len(str2)) // 12
}
package main
import "fmt"
func main() {
	str := "公号:代码情缘"
	// 注意byte占1个字节, 只能保存字符不能保存汉字,因为一个汉字占用3个字节
	arr1 := []byte(str) // 12
	fmt.Println(len(arr1))
	for _, v := range arr1{
		fmt.Printf("%c", v) // lnj李南江
	}

	// Go语言中rune类型就是专门用于保存汉字的
	arr2 := []rune(str)
	fmt.Println(len(arr2)) // 6
	for _, v := range arr2{
		fmt.Printf("%c", v) // lnj李南江
	}
}
package main
import (
	"strings"
	"fmt"
)
func main() {
	// 查找`字符`在字符串中第一次出现的位置, 找不到返回-1
	res := strings.IndexByte("hello 李南江", 'l')
	fmt.Println(res) // 2

	// 查找`汉字`OR`字符`在字符串中第一次出现的位置, 找不到返回-1
	res = strings.IndexRune("hello 李南江", '李')
	fmt.Println(res) // 6
	res = strings.IndexRune("hello 李南江", 'l')
	fmt.Println(res) // 2

	// 查找`汉字`OR`字符`中任意一个在字符串中第一次出现的位置, 找不到返回-1
	res = strings.IndexAny("hello 李南江", "wml")
	fmt.Println(res) // 2
	// 会把wmhl拆开逐个查找, w、m、h、l只要任意一个被找到, 立刻停止查找
	res = strings.IndexAny("hello 李南江", "wmhl")
	fmt.Println(res) // 0
	// 查找`子串`在字符串第一次出现的位置, 找不到返回-1
	res = strings.Index("hello 李南江", "llo")
	fmt.Println(res) // 2
	// 会把lle当做一个整体去查找, 而不是拆开
	res = strings.Index("hello 李南江", "lle")
	fmt.Println(res) // -1
	// 可以查找字符也可以查找汉字
	res = strings.Index("hello 李南江", "李")
	fmt.Println(res) // 6

	// 会将字符串先转换为[]rune, 然后遍历rune切片逐个取出传给自定义函数
	// 只要函数返回true,代表符合我们的需求, 既立即停止查找
	res = strings.IndexFunc("hello 李南江", custom)
	fmt.Println(res) // 6

	// 倒序查找`子串`在字符串第一次出现的位置, 找不到返回-1
	res := strings.LastIndex("hello 李南江", "l")
	fmt.Println(res) // 3
}
func custom(r rune) bool {
	fmt.Printf("被调用了, 当前传入的是%c\n", r)
	if r == 'o' {
		return true
	}
	return false
}
package main
import (
	"strings"
	"fmt"
)
func main() {
	// 查找`子串`在字符串中是否存在, 存在返回true, 不存在返回false
	// 底层实现就是调用strings.Index函数
	res := strings.Contains( "hello 代码情缘", "llo")
	fmt.Println(res) // true

	// 查找`汉字`OR`字符`在字符串中是否存在, 存在返回true, 不存在返回false
	// 底层实现就是调用strings.IndexRune函数
	res = strings.ContainsRune( "hello 代码情缘", 'l')
	fmt.Println(res) // true
	res = strings.ContainsRune( "hello 代码情缘", '李')
	fmt.Println(res) // true

	// 查找`汉字`OR`字符`中任意一个在字符串中是否存在, 存在返回true, 不存在返回false
	// 底层实现就是调用strings.IndexAny函数
	res = strings.ContainsAny( "hello 代码情缘", "wmhl")
	fmt.Println(res) // true

	// 判断字符串是否已某个字符串开头
	res = strings.HasPrefix("lnj-book.avi", "lnj")
	fmt.Println(res) // true

	// 判断字符串是否已某个字符串结尾
	res = strings.HasSuffix("lnj-book.avi", ".avi")
	fmt.Println(res) // true
}
package main
import (
	"strings"
	"fmt"
)
func main() {
	// 比较两个字符串大小, 会逐个字符地进行比较ASCII值
	// 第一个参数 >  第二个参数 返回 1
	// 第一个参数 <  第二个参数 返回 -1
	// 第一个参数 == 第二个参数 返回 0
	res := strings.Compare("bcd", "abc")
	fmt.Println(res) // 1
	res = strings.Compare("bcd", "bdc")
	fmt.Println(res) // -1
	res = strings.Compare("bcd", "bcd")
	fmt.Println(res) // 0

	// 判断两个字符串是否相等, 可以判断字符和中文
	// 判断时会忽略大小写进行判断
	res2 := strings.EqualFold("abc", "def")
	fmt.Println(res2) // false
	res2 = strings.EqualFold("abc", "abc")
	fmt.Println(res2) // true
	res2 = strings.EqualFold("abc", "ABC")
	fmt.Println(res2) // true
	res2 = strings.EqualFold("代码情缘", "代码情缘")
	fmt.Println(res2) // true
}
package main
import (
	"strings"
	"fmt"
)
func main() {
	// 将字符串转换为小写
	res := strings.ToLower("ABC")
	fmt.Println(res) // abc
	
	// 将字符串转换为大写
	res = strings.ToUpper("abc")
	fmt.Println(res) // ABC

	// 将字符串转换为标题格式, 大部分`字符`标题格式就是大写
	res = strings.ToTitle("hello world")
	fmt.Println(res) // HELLO WORLD
	res = strings.ToTitle("HELLO WORLD")
	fmt.Println(res) // HELLO WORLD

	// 将单词首字母变为大写, 其它字符不变
	// 单词之间用空格OR特殊字符隔开
	res = strings.Title("hello world")
	fmt.Println(res) // Hello World
}
package main
import (
	"strings"
	"fmt"
)
func main() {
	// 按照指定字符串切割原字符串
	// 用,切割字符串
	arr1 := strings.Split("a,b,c", ",")
	fmt.Println(arr1) // [a b c]
	arr2 := strings.Split("ambmc", "m")
	fmt.Println(arr2) // [a b c]

	// 按照指定字符串切割原字符串, 并且指定切割为几份
	// 如果最后一个参数为0, 那么会范围一个空数组
	arr3 := strings.SplitN("a,b,c", ",", 2)
	fmt.Println(arr3) // [a b,c]
	arr4 := strings.SplitN("a,b,c", ",", 0)
	fmt.Println(arr4) // []

	// 按照指定字符串切割原字符串, 切割时包含指定字符串
	arr5 := strings.SplitAfter("a,b,c", ",")
	fmt.Println(arr5) // [a, b, c]

	// 按照指定字符串切割原字符串, 切割时包含指定字符串, 并且指定切割为几份
	arr6 := strings.SplitAfterN("a,b,c", ",", 2)
	fmt.Println(arr6) // [a, b,c]

	// 按照空格切割字符串, 多个空格会合并为一个空格处理
	arr7 := strings.Fields("a  b c    d")
	fmt.Println(arr7) // [a b c d]

	// 将字符串转换成切片传递给函数之后由函数决定如何切割
	// 类似于IndexFunc
	arr8 := strings.FieldsFunc("a,b,c", custom)
	fmt.Println(arr8) // [a b c]

	// 将字符串切片按照指定连接符号转换为字符串
	sce := []string{"aa", "bb", "cc"}
	str1 := strings.Join(sce, "-")
	fmt.Println(str1) // aa-bb-cc


	// 返回count个s串联的指定字符串
	str2 := strings.Repeat("abc", 2)
	fmt.Println(str2) // abcabc

	// 第一个参数: 需要替换的字符串
	// 第二个参数: 旧字符串
	// 第三个参数: 新字符串
	// 第四个参数: 用新字符串 替换 多少个旧字符串
	// 注意点: 传入-1代表只要有旧字符串就替换
	// 注意点: 替换之后会生成新字符串, 原字符串不会受到影响
	str3 := "abcdefabcdefabc"
	str4 := strings.Replace(str3, "abc", "mmm", -1)
	fmt.Println(str3) // abcdefabcdefabc
	fmt.Println(str4) // mmmdefmmmdefmmm
}
func custom(r rune) bool {
	fmt.Printf("被调用了, 当前传入的是%c\n", r)
	if r == ',' {
		return true
	}
	return false
}
package main
import (
	"strings"
	"fmt"
)
func main() {
	// 去除字符串两端指定字符
	str1 := strings.Trim("!!!abc!!!def!!!", "!")
	fmt.Println(str1) // abc!!!def
	// 去除字符串左端指定字符
	str2 := strings.TrimLeft("!!!abc!!!def!!!", "!")
	fmt.Println(str2) // abc!!!def!!!
	// 去除字符串右端指定字符
	str3 := strings.TrimRight("!!!abc!!!def!!!", "!")
	fmt.Println(str3) // !!!abc!!!def
	// // 去除字符串两端空格
	str4 := strings.TrimSpace("   abc!!!def ")
	fmt.Println(str4) // abc!!!def

	// 按照方法定义规则,去除字符串两端符合规则内容
	str5 := strings.TrimFunc("!!!abc!!!def!!!", custom)
	fmt.Println(str5) // abc!!!def
	// 按照方法定义规则,去除字符串左端符合规则内容
	str6 := strings.TrimLeftFunc("!!!abc!!!def!!!", custom)
	fmt.Println(str6) // abc!!!def!!!
	//  按照方法定义规则,去除字符串右端符合规则内容
	str7 := strings.TrimRightFunc("!!!abc!!!def!!!", custom)
	fmt.Println(str7) // !!!abc!!!def

	// 取出字符串开头的指定字符串
	str8 := strings.TrimPrefix("lnj-book.avi", "lnj-")
	fmt.Println(str8) // book.avi

	// 取出字符串结尾的指定字符串
	str9 := strings.TrimSuffix("lnj-book.avi", ".avi")
	fmt.Println(str9) // lnj-book
}

正则表达式

package main
import (
	"strings"
	"fmt"
)
func main() {
	// 创建一个正则表达式匹配规则对象
	// reg := regexp.MustCompile(规则字符串)
	// 利用正则表达式匹配规则对象匹配指定字符串
	// 会将所有匹配到的数据放到一个字符串切片中返回
	// 如果没有匹配到数据会返回nil
	// res := reg.FindAllString(需要匹配的字符串, 匹配多少个)

	str := "Hello 李南江 1232"
	reg := regexp.MustCompile("2")
	res := reg.FindAllString(str, -1)
	fmt.Println(res) // [2 2]
	res = reg.FindAllString(str, 1)
	fmt.Println(res) // [2]
}
package main
import (
	"strings"
	"fmt"
)
func main() {
	res2 := findPhoneNumber("13554499311")
	fmt.Println(res2) // true

	res2 = findPhoneNumber("03554499311")
	fmt.Println(res2) // false

	res2 = findPhoneNumber("1355449931")
	fmt.Println(res2) // false
}
func findPhoneNumber(str string) bool {
	// 创建一个正则表达式匹配规则对象
	reg := regexp.MustCompile("^1[1-9]{10}")
	// 利用正则表达式匹配规则对象匹配指定字符串
	res := reg.FindAllString(str, -1)
	if(res == nil){
		return  false
	}
	return  true
}
package main
import (
	"strings"
	"fmt"
)
func main() {
	res2 = findEmail("123@qq.com")
	fmt.Println(res2) // true

	res2 = findEmail("ab?de@qq.com")
	fmt.Println(res2) // false

	res2 = findEmail("123@qqcom")
	fmt.Println(res2) // false
}
func findEmail(str string) bool {
	reg := regexp.MustCompile("^[a-zA-Z0-9_]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]+")
	res := reg.FindAllString(str, -1)
	if(res == nil){
		return  false
	}
	return  true
}

时间和日期函数

package main

import (
	"fmt"
	"time"
)
func main()  {
	var t time.Time = time.Now()
	// 2018-09-27 17:25:11.653198 +0800 CST m=+0.009759201
	fmt.Println(t)
}
package main

import (
	"fmt"
	"time"
)
func main()  {
	var t time.Time = time.Now()
	fmt.Println(t.Year())
	fmt.Println(t.Month())
	fmt.Println(t.Day())
	fmt.Println(t.Hour())
	fmt.Println(t.Minute())
	fmt.Println(t.Second())
}
package main

import (
	"fmt"
	"time"
)
func main()  {
	var t time.Time = time.Now()
	fmt.Printf("当前的时间是: %d-%d-%d %d:%d:%d\n", t.Year(), 
		t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())

	var dateStr = fmt.Sprintf("%d-%d-%d %d:%d:%d", t.Year(), 
		t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())
fmt.Println("当前的时间是:", dateStr)
}
package main

import (
	"fmt"
	"time"
)
func main()  {
	var t time.Time = time.Now()
	// 2006/01/02 15:04:05这个字符串是Go语言规定的, 各个数字都是固定的
	// 字符串中的各个数字可以只有组合, 这样就能按照需求返回格式化好的时间
	str1 := t.Format("2006/01/02 15:04:05")
	fmt.Println(str1)
	str2 := t.Format("2006/01/02")
	fmt.Println(str2)
	str3 := t.Format("15:04:05")
	fmt.Println(str3)
}
const (
	Nanosecond  Duration = 1    // 纳秒
	Microsecond          = 1000 * Nanosecond // 微秒
	Millisecond          = 1000 * Microsecond // 毫秒
	Second               = 1000 * Millisecond // 秒
	Minute               = 60 * Second // 分钟
	Hour                 = 60 * Minute // 小时
)
package main

import (
	"fmt"
	"time"
)
func main()  {
	for{
		// 1秒钟打印一次
		time.Sleep(time.Second * 1)
		// 0.1秒打印一次
		//time.Sleep(time.Second * 0.1)
		time.Sleep(time.Millisecond * 100)
		fmt.Println("Hello LNJ")
	}
}
package main

import (
	"fmt"
	"time"
)

func main()  {
	t := time.Now()
	// 获取1970年1月1日距离当前的时间(秒)
	fmt.Println(t.Unix())
	// 获取1970年1月1日距离当前的时间(纳秒)
	fmt.Println(t.UnixNano())
}
package main

import (
	"fmt"
	"math/rand"
	"time"
)
func main()  {
	// 创建随机数种子
	rand.Seed(time.Now().UnixNano())
	// 生成一个随机数
	fmt.Println(rand.Intn(10))
}

Go语言中调用C语言函数

package main
//#include <stdio.h>
//void say(){
// printf("Hello World\n");
//}
import "C"

func main()  {
	C.say()
}
package main
/*
#include <stdio.h>
void say(){
	printf("Hello World\n");
}
*/
import "C"

func main()  {
	C.say()
}

C语言中调用Go语言函数(很少使用)

package main

import "C"
import "fmt"
// 导出Go函数声明, 给C使用
//export GoFunction
func GoFunction() {
	fmt.Println("GoFunction!!!")
}
package main
/*
#include <stdio.h>
// 声明Go中的函数
extern void GoFunction();

void CFunction() {
        printf("CFunction!\n");
        GoFunction();
}
*/
import "C"

func main()  {
	C.CFunction()
}

C.char,
C.schar (signed char),
C.uchar (unsigned char),
C.short,
C.ushort (unsigned short),
C.int, C.uint (unsigned int),
C.long,
C.ulong (unsigned long),
C.longlong (long long),
C.ulonglong (unsigned long long),
C.float,
C.double
package main
/*
#include <stdio.h>
int num = 123;
float value = 3.14;
char ch = 'N';
*/
import "C"
import "fmt"

func main()  {
	var num1 C.int = C.num
	fmt.Println(num1)
	var num2 int
	//num2 = num1 // 报错
	num2 = int(num1)
	fmt.Println(num2)


	var value1 C.float = C.value
	fmt.Println(value1)
	var value2 float32 = float32(C.value)
	fmt.Println(value2)


	var ch1 C.char = C.ch
	fmt.Println(ch1)
	var ch2 byte = byte(C.ch)
	fmt.Println(ch2)
}

package main
/*
#include <stdio.h>
char *str = "www.it666.com";
void say(char *name){
	printf("my name is %s", name);
}
*/
import "C"
import (
	"fmt"
	"unsafe"
)

func main()  {
	// 1.C语言字符串转换Go语言字符串
	str1 := C.str
	str2 := C.GoString(str1)
	fmt.Println(str2)

	// 2.Go语言字符串转换C语言字符串
	str := "lnj"
	cs := C.CString(str)
	C.say(cs)
	// 注意: 转换后所得到的C字符串cs并不能由Go的gc所管理,我们必须手动释放cs所占用的内存
	C.free(unsafe.Pointer(cs))
}

package main
/*
#include <stdio.h>
int num = 123;
void *ptr = &num;
*/
import "C"
import (
	"fmt"
	"unsafe"
)

func main()  {
	// 这是一个C语言变量
	var num C.int = C.num
	// 这是一个C语言指针
	var p1 *C.int = &num
	fmt.Println(*p1)

	//var p2 *C.void = C.ptr // 报错
	// 利用unsafe.Pointer接收viod *
	var p2 unsafe.Pointer = C.ptr
	// 将unsafe.Pointer转换为具体类型
	var p3 *C.int = (*C.int)(p2)
	fmt.Println(*p3)
}

package main
/*
#include <stdio.h>
enum Gender {
   GenderMale,
   GenderFemale,
   GenderYao
};
*/
import "C"
import "fmt"

func main()  {
	var sex C.enum_Gender = C.GenderMale
	fmt.Println(sex)
	sex = C.GenderFemale
	fmt.Println(sex)
	sex = C.GenderYao
	fmt.Println(sex)
}

package main
/*
#include <stdio.h>
struct Point {
    float x;
    float y;
};
*/
import "C"
import (
	"fmt"
)

func main()  {
	// 1.利用C的结构体类型创建结构体
	var cp C.struct_Point = C.struct_Point{6.6, 8.8}
	fmt.Println(cp)
	fmt.Printf("%T\n", cp)

	// 2.将C语言结构体转换为Go语言结构体
	type GoPoint struct {
		x float32
		y float32
	}
	var gp GoPoint
	gp.x = float32(cp.x)
	gp.y = float32(cp.y)
	fmt.Println(gp)
}

package main
/*
#include <stdio.h>
int cArray[5] = {1, 2, 3, 4, 5};
*/
import "C"
import "fmt"

func main()  {
	var cArr [5]C.int = C.cArray
	fmt.Println(cArr)
	fmt.Printf("%T\n", cArr)
}

package main
/*
#include <stdio.h>
char lowerCase(char ch){
    // 1.判断当前是否是小写字母
    if(ch >= 'a' && ch <= 'z'){
        return ch;
    }
    // 注意点: 不能直接编写else, 因为执行到else不一定是一个大写字母
    else if(ch >= 'A' && ch <= 'Z'){
        return ch + ('a' - 'A');
    }
    return ' ';
}
char getCh(){
    // 1.接收用户输入的数据
    char ch;
    scanf("%c", &ch);
    setbuf(stdin, NULL);
    // 2.大小写转换
    ch = lowerCase(ch);
    // 3.返回转换好的字符
    return ch;
}
 */
import "C"
import "fmt"

func main()  {
	for{
		fmt.Println("请输入w a s d其中一个字符, 控制小人走出迷宫")
		var ch byte = byte(C.getCh())
		fmt.Printf("%c", ch)
	}
}

文件的打开和关闭

type file struct {
	pfd     poll.FD
	name    string
	dirinfo *dirInfo 
}
type File struct {
	*file // os specific
}
package main
import (
	"fmt"
	"os"
)

func main() {
	// 1.打开一个文件
	// 注意: 文件不存在不会创建, 会报错
	// 注意: 通过Open打开只能读取, 不能写入
	fp, err := os.Open("d:/lnj.txt")
	if err != nil{
		fmt.Println(err)
	}else{
		fmt.Println(fp)
	}

	// 2.关闭一个文件
	defer func() {
		err = fp.Close()
		if err != nil {
			fmt.Println(err)
		}
	}()
}

文件读取

package main

import (
	"fmt"
	"io"
	"os"
)

func main() {
	// 1.打开一个文件
	// 注意: 文件不存在不会创建, 会报错
	// 注意: 通过Open打开只能读取, 不能写入
	fp, err := os.Open("d:/lnj.txt")
	if err != nil{
		fmt.Println(err)
	}else{
		fmt.Println(fp)
	}

	// 2.关闭一个文件
	defer func() {
		err = fp.Close()
		if err != nil {
			fmt.Println(err)
		}
	}()

	// 3.读取指定指定字节个数据
	// 注意点: \n也会被读取进来
	//buf := make([]byte, 50)
	//count, err := fp.Read(buf)
	//if err != nil {
	//	fmt.Println(err)
	//}else{
	//	fmt.Println(count)
	//	fmt.Println(string(buf))
	//}

	// 4.读取文件中所有内容, 直到文件末尾为止
	buf := make([]byte, 10)
	for{
		count, err := fp.Read(buf)
		// 注意: 这行代码要放到判断EOF之前, 否则会出现少读一行情况
		fmt.Print(string(buf[:count]))
		if err == io.EOF {
			break
		}
	}
}
package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() {
	// 1.打开一个文件
	// 注意: 文件不存在不会创建, 会报错
	// 注意: 通过Open打开只能读取, 不能写入
	fp, err := os.Open("d:/lnj.txt")
	if err != nil{
		fmt.Println(err)
	}else{
		fmt.Println(fp)
	}

	// 2.关闭一个文件
	defer func() {
		err = fp.Close()
		if err != nil {
			fmt.Println(err)
		}
	}()

	// 3.读取一行数据
	// 创建读取缓冲区, 默认大小4096
	//r :=bufio.NewReader(fp)
	//buf, err := r.ReadBytes('\n')
	//buf, err := r.ReadString('\n')
	//if err != nil{
	//	fmt.Println(err)
	//}else{
	//	fmt.Println(string(buf))
	//}

	// 4.读取文件中所有内容, 直到文件末尾为止
	r :=bufio.NewReader(fp)
	for{
		//buf, err := r.ReadBytes('\n')
		buf, err := r.ReadString('\n')
		fmt.Print(string(buf))
		if err == io.EOF{
			break
		}
	}
}
package main

import (
	"fmt"
	"io/ioutil"
)

func main() {

	filePath := "d:/lnj.txt"
	buf, err := ioutil.ReadFile(filePath)
	if err !=nil {
		fmt.Println(err)
	}else{
		fmt.Println(string(buf))
	}
}

文件创建和写入

package main

import (
	"fmt"
	"os"
)

func main() {

	// 1.创建一个文件
	fp, err := os.Create("d:/lnj.txt")
	if err != nil{
		fmt.Println(err)
	}
	// 2.关闭打开的文件
	defer func() {
		err := fp.Close()
		if err != nil {
			fmt.Println(err)
		}
	}()
	// 2.往文件中写入数据
	// 注意: Windows换行是\r\n
	bytes := []byte{'l','n','j','\r','\n'}
	fp.Write(bytes)
	
	fp.WriteString("www.it666.com\r\n")
	fp.WriteString("www.itzb.com\r\n")
	// 注意: Go语言采用UTF-8编码, 一个中文占用3个字节
	fp.WriteString("李南江")
}
package main

import (
	"fmt"
	"os"
)

func main() {

	// 注意点: 第三个参数在Windows没有效果
	// -rw-rw-rw- (666)   所有用户都有文件读、写权限。
	//-rwxrwxrwx (777)  所有用户都有读、写、执行权限。
	// 1.打开文件
	//fp, err := os.OpenFile("d:/lnj.txt", os.O_CREATE|os.O_RDWR, 0666)
	fp, err := os.OpenFile("d:/lnj.txt", os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		fmt.Println(err)
	}
	// 2.关闭打开的文件
	defer func() {
		err := fp.Close()
		if err != nil {
			fmt.Println(err)
		}
	}()

	// 注意点:
	// 如果O_RDWR模式打开, 被打开文件已经有内容, 会从最前面开始覆盖
	// 如果O_APPEND模式打开, 被打开文件已经有内容, 会从在最后追加
	// 3.往文件中写入数据
	bytes := []byte{'l','n','j','\r','\n'}
	fp.Write(bytes)
	fp.WriteString("www.it666.com\r\n")
}
package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {

	// 1.打开文件
	fp, err := os.OpenFile("d:/lnj.txt", os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		fmt.Println(err)
	}
	// 2.关闭打开的文件
	defer func() {
		err := fp.Close()
		if err != nil {
			fmt.Println(err)
		}
	}()

	// 3.创建缓冲区
	w := bufio.NewWriter(fp)

	// 4.写入数据到缓冲区
	bytes := []byte{'l','n','j','\r','\n'}
	w.Write(bytes)
	w.WriteString("www.it666.com\r\n")

	// 5.将缓冲区中的数据刷新到文件
	w.Flush()
}
package main

import (
	"fmt"
	"io/ioutil"
)

func main() {

	// 1.写入数据到指定文件
	data := []byte{'l','n','j','\r','\n'}
	err := ioutil.WriteFile("d:/abc.txt", data, 0666)
	if err != nil {
		fmt.Println(err)
	}else{
		fmt.Println("写入成功")
	}
}

判断文件是否存在

type FileInfo interface {
    Name() string       // 文件的名字(不含扩展名)
    Size() int64        // 普通文件返回值表示其大小;其他文件的返回值含义各系统不同
    Mode() FileMode     // 文件的模式位
    ModTime() time.Time // 文件的修改时间
    IsDir() bool        // 等价于Mode().IsDir()
    Sys() interface{}   // 底层数据来源(可以返回nil)
}
package main

import (
	"fmt"
	"os"
)

func main() {

	info, err := os.Stat("d:/lnj.txt")
	if err == nil {
		fmt.Println("文件存在")
		fmt.Println(info.Name())
	}else if os.IsNotExist(err) {
		fmt.Println("文件不存在")
	}else{
		fmt.Println("不确定")
	}
}

练习

package main

import (
	"fmt"
	"io/ioutil"
)

func main() {

	// 1.读取一个文件
	buf, err := ioutil.ReadFile("d:/lnj.txt")
	if err != nil {
		fmt.Println(err)
		return
	}
	// 2.写入读取的数据到另一个文件
	err =ioutil.WriteFile("d:/abc.txt", buf, 0666)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("拷贝完成")

}
package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() {

	// 1.定义拷贝文件的路径
	scrPath := "D:/a.png"
	destPath := "E:/b.png"
	// 2.打开被拷贝文件
	fr, err := os.Open(scrPath)
	if err != nil {
		fmt.Println(err)
		return
	}
	// 3.关闭打开文件
	defer func() {
		err := fr.Close()
		if err != nil{
			fmt.Println(err)
		}
	}()
	// 4.创建读取缓冲区
	r := bufio.NewReader(fr)

	// 1.创建写入文件
	fw, err := os.Create(destPath)
	if err != nil {
		fmt.Println(err)
		return
	}
	// 2.关闭打开文件
	defer func() {
		err := fw.Close()
		if err != nil{
			fmt.Println(err)
		}
	}()
	// 3.创建写入缓冲区
	w := bufio.NewWriter(fw)
	// 4.利用系统copy函数完成拷贝
	count, err := io.Copy(w, r)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(count)
	fmt.Println("拷贝完成")
}

并发编程基本概念


什么是串行?

什么是并行?

什么是并发?


什么是程序?

什么是进程?

什么是线程?

什么是协程?


Go并发

package main

import (
	"fmt"
	"time"
)

func sing()  {
	for i:=0; i< 10; i++{
		fmt.Println("我在唱歌")
		time.Sleep(time.Millisecond)
	}
}
func dance() {
	for i:=0; i< 10; i++{
		fmt.Println("我在跳舞---")
		time.Sleep(time.Millisecond)
	}
}

func main() {
	// 串行: 必须先唱完歌才能跳舞
	//sing()
	//dance()

	// 并行: 可以边唱歌, 边跳舞
	// 注意点: 主线程不能死, 否则程序就退出了
	go sing() // 开启一个协程
	go dance() // 开启一个协程
	for{
		;
	}
}

多线程同步问题

func printer(str string) { // 让先来的人拿到锁, 把当前函数锁住, 其它人都无法执行 // 上厕所关门 lock.Lock() for _, v := range str{ fmt.Printf("%c", v) time.Sleep(time.Millisecond * 500) } // 先来的人执行完毕之后, 把锁释放掉, 让其它人可以继续使用当前函数 // 上厕所开门 lock.Unlock() } func person1() { printer("hello") } func person2() { printer("world") } func main() { go person1() go person2() for{ ; } }

---
## 生产者消费者问题
- 所谓的生产者消费者模型就是
+ 某个模块(函数)负责生产数据, 这些数据由另一个模块来负责处理
+ 一般生产者消费者模型包含三个部分"生产者"、"缓冲区"、"消费者"
![](https://img-blog.csdnimg.cn/img_convert/c6b9dc46029e893cc11f3a85b7f223f0.png)
- 为什么生产者消费者模型要含三个部分?  直接生产和消费不行么?
- 一个案例说明一切
+ 生产者好比现实生活中的某个人
+ 缓冲区好比现实生活中的邮箱
+ 消费者好比现实生活中的邮递员
- 如果只有生产者和消费者, 那么相当于只有写信的人和邮递员, 那么如果将来过去的邮递员离职了, 你想邮寄信件必须想办法结识新的邮递员(消费者发生变化, 会直接影响生产者, 耦合性太强)
- 如果在生产者和消费者之间添加一个缓冲区, 那么就好比有了邮箱, 以后邮寄信件不是找邮递员, 只需把信件投递到邮箱中即可, 写信的人不需要关心邮递员是谁(解耦)
- 如果只有生产者和消费者, 那么每个人邮寄信件都需要直接找邮递员(1对1关系), 如果有10个人要邮寄信件, 那么邮递员只能依次找到每个人, 然后才能取件(效率低下)
- 如果在生产者和消费者之间添加一个缓冲区, 那么所有的人只需要将信件投递到邮箱即可, 邮递员不用关心有多少人要邮寄信件, 也不用依次取件, 只需要找到邮箱从邮箱中统一取件即可(效率提高)
- 如果只有生产者和消费者, 那么如果邮寄信件太多邮递员无法一次拿走, 这个时候非常难办
- 如果在生产者和消费者之间添加一个缓冲区, 那么如果信件太多可以先拿走一部分, 剩下的继续放到邮箱中下次再拿
-  ```... ...```
---
## 生产者和消费者资源竞争问题
- 例如生产比较慢, 而消费比较快, 就会导致消费者消费到错误数据
```go
package main

import (
  "fmt"
  "math/rand"
  "sync"
  "time"
)
// 创建一把互斥锁
var lock = sync.Mutex{}

// 定义缓冲区
var sce []int = make([]int, 10)

// 定义生产者
func producer(){
  // 加锁, 注意是lock就是我们的锁, 全局公用一把锁
  lock.Lock()
  rand.Seed(time.Now().UnixNano())
  for i:=0;i<10;i++{
  	num := rand.Intn(100)
  	sce[i] = num
  	fmt.Println("生产者生产了: ", num)
  	time.Sleep(time.Millisecond * 500)
  }
  // 解锁
  lock.Unlock()
}
// 定义消费者
func consumer()  {
  // 加锁, 注意和生产者中用的是同一把锁
  // 如果生产者中已加过了, 则阻塞直到解锁后再重新加锁
  lock.Lock()
  for i:=0;i<10;i++{
  	num := sce[i]
  	fmt.Println("---消费者消费了", num)
  }
  lock.Unlock()
}

func main() {
  go producer()
  go consumer()
  for{
  	;
  }
}

管道(Channel)


package main
import "fmt"
func main() {
	// 1.声明一个管道
	var mych chan int
	// 2.初始化一个管道
	mych = make(chan int, 3)
	// 3.查看管道的长度和容量
	fmt.Println("长度是", len(mych), "容量是", cap(mych))
	// 4.像管道中写入数据
	mych<- 666
	fmt.Println("长度是", len(mych), "容量是", cap(mych))
	// 5.取出管道中写入的数据
	num := <-mych
	fmt.Println("num = ", num)
	fmt.Println("长度是", len(mych), "容量是", cap(mych))
}

package main

import "fmt"

func main() {
	// 1.声明一个管道
	var mych chan int
	// 2.初始化一个管道
	mych = make(chan int, 3)

	// 注意点: 管道中只能存放声明的数据类型, 不能存放其它数据类型
	//mych<-3.14

	// 注意点: 管道中如果已经没有数据, 
	// 并且检测不到有其它协程再往管道中写入数据, 那么再取就会报错
	//num = <-mych
	//fmt.Println("num = ", num)

	// 注意点: 如果管道中数据已满, 再写入就会报错
	mych<- 666
	mych<- 777
	mych<- 888
	mych<- 999
}

package main

import "fmt"

func main() {
	// 1.创建一个管道
	mych := make(chan int, 3)
	// 2.往管道中存入数据
	mych<-666
	mych<-777
	mych<-888
	// 3.遍历管道
	// 第一次遍历i等于0, len = 3,
	// 第二次遍历i等于1, len = 2
	// 第三次遍历i等于2, len = 1
	//for i:=0; i<len(mych); i++{
	//	fmt.Println(<-mych) // 输出结果不正确
	//}

	// 3.写入完数据之后先关闭管道
	// 注意点: 管道关闭之后只能读不能写
	close(mych)
	//mych<- 999 // 报错

	// 4.遍历管道
	// 利用for range遍历, 必须先关闭管道, 否则会报错
	//for value := range mych{
	//	fmt.Println(value)
	//}

	// close主要用途:
	// 在企业开发中我们可能不确定管道有还没有有数据, 所以我们可能一直获取
	// 但是我们可以通过ok-idiom模式判断管道是否关闭, 如果关闭会返回false给ok
	for{
		if num, ok:= <-mych; ok{
			fmt.Println(num)
		}else{
			break;
		}
	}
	fmt.Println("数据读取完毕")
}

package main
import (
	"fmt"
	"time"
)
// 创建一个管道
var myCh = make(chan int, 5)
func demo()  {
	var myCh = make(chan int, 5)
	//myCh<-111
	//myCh<-222
	//myCh<-333
	//myCh<-444
	//myCh<-555
	//fmt.Println("我是第六次添加之前代码")
	//myCh<-666
	//fmt.Println("我是第六次添加之后代码")

	fmt.Println("我是第六次直接获取之前代码")
	<-myCh
	fmt.Println("我是第六次直接获取之后代码")
}
func test()  {
	//myCh<-111
	//myCh<-222
	//myCh<-333
	//myCh<-444
	//myCh<-555
	//fmt.Println("我是第六次添加之前代码")
	//myCh<-666
	//fmt.Println("我是第六次添加之后代码")

	//fmt.Println("我是第六次直接获取之前代码")
	//<-myCh
	//fmt.Println("我是第六次直接获取之后代码")
}
func example()  {
	time.Sleep(time.Second * 2)
	myCh<-666
}
func main() {
	// 1.同一个go程中操作管道
	// 写满了会报错
	//myCh<-111
	//myCh<-222
	//myCh<-333
	//myCh<-444
	//myCh<-555
	//myCh<-666

	// 没有了去取也会报错
	//<-myCh

	// 2.在协程中操作管道
	// 写满了不会报错, 但是会阻塞
	//go test()

	// 没有了去取也不会报错, 也会阻塞
	//go test()

	//go demo()
	//go demo()
	
	// 3.只要在协程中操作了管道, 就会发生阻塞现象
	go example()
	fmt.Println("myCh之前代码")
	<-myCh
	fmt.Println("myCh之后代码")

	//for{
	//	;
	//}
}

package main

import (
	"fmt"
	"math/rand"
	"time"
)
// 定义缓冲区
var myCh = make(chan int, 5)
var exitCh = make(chan bool, 1)

// 定义生产者
func producer(){
	rand.Seed(time.Now().UnixNano())
	for i:=0;i<10;i++{
		num := rand.Intn(100)
		fmt.Println("生产者生产了: ", num)
		// 往管道中写入数据
		myCh<-num
		//time.Sleep(time.Millisecond * 500)
	}
	// 生产完毕之后关闭管道
	close(myCh)
	fmt.Println("生产者停止生产")
}
// 定义消费者
func consumer()  {
	// 不断从管道中获取数据, 直到管道关闭位置
	for{
		if num, ok := <-myCh; !ok{
			break
		}else{
			fmt.Println("---消费者消费了", num)
		}
	}
	fmt.Println("消费者停止消费")
	exitCh<-true
}

func main() {
	go producer()
	go consumer()
	fmt.Println("exitCh之前代码")
	<-exitCh
	fmt.Println("exitCh之后代码") 
}

package main
import "fmt"
var myCh1 = make(chan int, 5)
var myCh2 = make(chan int, 0)
func main() {
	// 有缓冲管道
	// 只写入, 不读取不会报错
	//myCh1<-1
	//myCh1<-2
	//myCh1<-3
	//myCh1<-4
	//myCh1<-5
	//fmt.Println("len =",len(myCh1), "cap =", cap(myCh1))

	// 无缓冲管道
	// 只有两端同时准备好才不会报错
	go func() {
		fmt.Println(<-myCh2)
	}()
	// 只写入, 不读取会报错
	myCh2<-1
	//fmt.Println("len =",len(myCh2), "cap =", cap(myCh2))
	// 写入之后在同一个线程读取也会报错
	//fmt.Println(<-myCh2)
	// 在主程中先写入, 在子程中后读取也会报错
	//go func() {
	//	fmt.Println(<-myCh2)
	//}()
}

package main
import (
	"fmt"
	"math/rand"
	"time"
)
// 定义缓冲区
//var myCh = make(chan int, 0)
var myCh = make(chan int)
var exitCh = make(chan bool, 1)

// 定义生产者
func producer(){
	rand.Seed(time.Now().UnixNano())
	for i:=0;i<10;i++{
		num := rand.Intn(100)
		fmt.Println("生产者生产了: ", num)
		// 往管道中写入数据
		myCh<-num
		//time.Sleep(time.Millisecond * 500)
	}
	// 生产完毕之后关闭管道
	close(myCh)
	fmt.Println("生产者停止生产")
}
// 定义消费者
func consumer()  {
	// 不断从管道中获取数据, 直到管道关闭位置
	for{
		if num, ok := <-myCh; !ok{
			break
		}else{
			fmt.Println("---消费者消费了", num)
		}
	}
	fmt.Println("消费者停止消费")
	exitCh<-true
}

func main() {
	go producer()
	go consumer()
	fmt.Println("exitCh之前代码")
	<-exitCh
	fmt.Println("exitCh之后代码")
}

IO的延迟说明: 看到的输出结果和我们想象的不太一样, 是因为IO输出非常消耗性能, 输出之后还没来得及赋值可能就跑去执行别的协程了


package main

import "fmt"

func main() {
	// 1.定义一个双向管道
	var myCh chan int = make(chan int, 5)

	// 2.将双向管道转换单向管道
	var myCh2 chan<- int
	myCh2 = myCh
	fmt.Println(myCh2)
	var myCh3 <-chan int
	myCh3 = myCh
	fmt.Println(myCh3)

	// 3.双向管道,可读可写
	myCh<-1
	myCh<-2
	myCh<-3
	fmt.Println(<-myCh)
	
	// 3.只写管道,只能写, 不能读
	//	myCh2<-666
	//	fmt.Println(<-myCh2)

	// 4.指读管道, 只能读,不能写
	fmt.Println(<-myCh3)
	//myCh3<-666
	
	// 注意点: 管道之间赋值是地址传递, 以上三个管道底层指向相同容器
}
package main
import (
	"fmt"
	"math/rand"
	"time"
)
// 定义生产者
func producer(myCh chan<- int){
	rand.Seed(time.Now().UnixNano())
	for i:=0;i<10;i++{
		num := rand.Intn(100)
		fmt.Println("生产者生产了: ", num)
		// 往管道中写入数据
		myCh<-num
		//time.Sleep(time.Millisecond * 500)
	}
	// 生产完毕之后关闭管道
	close(myCh)
	fmt.Println("生产者停止生产")
}
// 定义消费者
func consumer(myCh <-chan int)  {
	// 不断从管道中获取数据, 直到管道关闭位置
	for{
		if num, ok := <-myCh; !ok{
			break
		}else{
			fmt.Println("---消费者消费了", num)
		}
	}
	fmt.Println("消费者停止消费")

}

func main() {
	// 定义缓冲区
	var myCh = make(chan int, 5)
	go producer(myCh)
	consumer(myCh)
}

select选择结构

	select {
	case IO操作1:
		IO操作1读取或写入成功就执行
	case IO操作2:
		IO操作2读取或写入成功就执行
	default:
		如果上面case都没有成功,则进入default处理流程
	}
package main

import (
	"fmt"
	"time"
)
func main() {
	// 创建管道
	var myCh = make(chan int)
	var exitCh = make(chan bool)

	// 生产数据
	go func() {
		for i:=0;i <10;i++{
			myCh<-i
			time.Sleep(time.Second)
		}
		//close(myCh)
		exitCh<-true
	}()

	// 读取数据
	for{
		fmt.Println("读取代码被执行了")
		select {
		case num:= <-myCh:
			fmt.Println("读到了", num)
		case <-exitCh:
			//break // 没用, 跳出的是select
			return
		}
		fmt.Println("-----------")
	}
}
package main
import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	// 1.创建管道
	myCh := make(chan int, 5)
	exitCh := make(chan bool)

	// 2.生成数据
	go func() {
		for i:=0; i<10; i++ {
			myCh<-i
			time.Sleep(time.Second * 3)
		}
	}()

	// 3.获取数据
	go func() {
		for{
			select {
			case num:= <-myCh:
				fmt.Println(num)
			case <-time.After(time.Second * 2):
				exitCh<-true
				runtime.Goexit()
			}
		}
	}()

	<-exitCh
	fmt.Println("程序结束")
}

定时器补充

type Timer struct {
	C <-chan Time // 对于我们来说, 这个属性是只读的管道
	r runtimeTimer
}
package main
import (
	"fmt"
	"time"
)
func main() {
	start := time.Now()
	fmt.Println("开始时间", start)
	timer := time.NewTimer(time.Second * 3)
	fmt.Println("读取之前代码被执行")
	end := <-timer.C // 系统写入数据之前会阻塞
	fmt.Println("读取之后代码被执行")
	fmt.Println("结束时间", end)
}
func After(d Duration) <-chan Time {
	return NewTimer(d).C
}
package main
import (
	"fmt"
	"time"
)
func main() {
	start := time.Now()
	fmt.Println("开始时间", start)
	timer := time.After(time.Second * 3)
	fmt.Println("读取之前代码被执行")
	end := <-timer // 系统写入数据之前会阻塞
	fmt.Println("读取之后代码被执行")
	fmt.Println("结束时间", end)
}

type Ticker struct {
    C <-chan Time // 周期性传递时间信息的通道
    // 内含隐藏或非导出字段
}
package main
import (
	"fmt"
	"time"
)
func main() {
	// 1.创建一个周期定时器
	ticker := time.NewTicker(time.Second)
	// 2.不断从重启定时器中获取时间
	for{
		t := <-ticker.C // 系统写入数据之前会阻塞
		fmt.Println(t)
	}
}

通用开发技能

Git版本控制

Git 版本控制

数据结构与算法

SQL 基础学习

前端基础知识

html + css 入门

JavaScript 进阶

Vue 基础学习

Go-Web 编程

Go 语言爬虫

Go 语言分布式开发

Go 语言设计模式

Go 语言学习资料

Go开源书籍推荐

编程常见问题汇总

  1. 如何学习编程?
  2. 如何选择一门编程语言?
  3. 如何做好离职交接工作?
  4. 学习编程的算法网站有哪些?
  5. 持续更新 ing

其他

捐赠支持

项目的发展离不开你的支持,如果 GoGuide 帮助到你打开编程的大门,请作者喝杯咖啡吧 ☕ 后续我们会继续完善更新!加油!

点击捐赠支持作者

联系我

学习路线

各种技术的学习路线

整理了一份各个技术的学习路线,需要的小伙伴加我微信:“leader_fengy ”备注“ 学习路线” 即可!

公众号

如果大家想要实时关注我们更新的文章以及分享的干货的话,可以关注我们的微信公众号“代码情缘”。

《C语言保姆级教程》:Go语言保姆级教程》PDF 版本在微信公众号后台回复 "书籍" 即可免费领取!

我的公众号