友情提示:编辑器显示 12 万 多字,先点赞,关注,收藏,一键三连支持,再学习。
本文对比 C 语言进行学习 Go 语言,如果你有 C 语言基础,学习 Go 语言会容易很多。
这篇文章可以说是一本书了,排版,码字耗费了很长的时间,10W+字 Go 语言从入门保姆级教程2021年版,觉得有价值记得一键三连支持。
科普小知识:
1.静态语言:
1.1一般都需要通过编译器(compiler)将源代码翻译成机器码,之后才能执行。程序被编译之后无论是程序中的数据类型还是程序的结构都不可以被改变
1.2静态语言的性能和安全性都非常好, 例如C和C++、Go, 但是C和C++的缺点是开发速度慢, 维护成本高
2.动态语言
2.1一般不需要通过编译器将源代码翻译成机器码,在运行程序的时候才逐行翻译。程序在运行的过程中可以动态修改程序中的数据类型和程序的结构
2.2动态语言开发速度快,维护成本低,例如Ruby和Python, 但是Ruby和Python的性能和安全性又略低
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版发布。
跟着谷歌走吃喝啥都有
多次获得TIOBE年度最佳语言
2018年Go语言一度超过Java, 进入编程语言排行榜前三名.
从公司角度:
从业务维度:
K8S就是Go开发的
), 也让这些各大公司不得不拥抱Go语言。从薪资角度来看
4~8K
, 1年左右普遍在10K左右
, 2年~3年普遍在20K左右
竹子用了4年的时间, 仅仅长了3cm, 从第五年开始, 以每天30cm的速度疯狂地生长, 仅仅用了六周的时间就长到了15米。 其实,在前面的四年, 竹子将根在土壤里延伸了数百平米。 做人做事亦是如此, 不要担心你此时此刻的付出得不到回报, 因为这些付出都是为了扎根。
文件扩展名 | 源类型 |
---|---|
.h | 头文件,存放代码声明 |
.c | C语言源文件,存放代码实现 |
文件扩展名 | 源类型 |
---|---|
.go | Go语言源文件,存放代码实现 |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|
if | else | switch | case | default | break | return | goto |
do | while | for | continue | typedef | struct | enum | union |
char | short | int | long | float | double | void | sizeof |
signed | unsigned | const | auto | register | static | extern | volatile |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|
if | else | switch | case | default | break | return | goto |
fallthrough | for | continue | type | struct | var | const | map |
func | interface | range | import | package | defer | go | select |
chan |
类型 | 32位编译器 | 64位编译器 |
---|---|---|
char | 1 | 1 |
int | 4 | 4 |
float | 4 | 4 |
double | 8 | 8 |
short | 2 | 2 |
long | 4 | 8 |
long long | 8 | 8 |
void* | 4 | 8 |
类型 | 32位编译器 | 64位编译器 | 本质 |
---|---|---|---|
int8/uint8 | 1 | 1 | signed char/unsigned char |
int16/uint16 | 2 | 2 | signed short/unsigned short |
int32/uint32 | 4 | 4 | signed int/unsigned int |
int64/uint64 | 8 | 8 | signed long long int/unsigned long long int |
byte | 1 | 1 | uint8/unsigned char |
rune | 4 | 4 | int32/signed int |
int | 4 | 8 | 根据机器位数决定长度 |
uintptr | 4 | 8 | 根据机器位数决定长度 uint32/uint64 |
float32 | 4 | 4 | float |
float64 | 8 | 8 | double |
true | 1 | 1 | char类型的整型 |
false | 1 | 1 | char类型的整型 |
go\src\runtime\runtime.h
// 第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结果就为1 | A & B |
\| | 参与运算的两数各对应的二进位相或,对应位只要其中一个是1结果就为1 | A | B |
^ | 参与运算的两数各对应的二进位相异或,对应位只要不同结果就是1 | A ^ 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 返回值; }
什么是SDK
如何安装?
1.下载SDK安装包。地址: https://golang.google.cn/dl/
2.运行图形化安装包
3.检测配置环境变量
go version
go env
- 激活程序: 自行淘宝
JetBrains 激活
(仅供学生党参考, 在职人员请支持正版)
- 看不习惯英文的可以自行百度
Goland汉化包
int main(int argc, const char * argv[]) { return 0; }
// 告诉系统当前编写的代码属于哪个包 package main // 定义了一个名称叫做main的函数 func main() { }
package main // 告诉系统当前代码属于main这个包 import "fmt" // 导入打印函数对应的fmt包 func main() { // 通过包名.函数名称的方式, 利用fmt包中的打印函数输出语句 fmt.Println("Hello World!!!") }
返回值类型 函数名称(形参列表) { 函数体相关语句; 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) // 不用写分号 }
// 被注释内容
/* 被注释内容*/
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|
if | else | switch | case | default | break | return | goto |
do | while | for | continue | typedef | struct | enum | union |
char | short | int | long | float | double | void | sizeof |
signed | unsigned | const | auto | register | static | extern | volatile |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|
if | else | switch | case | default | break | return | goto |
fallthrough | for | continue | type | struct | var | const | map |
func | interface | range | import | package | defer | go | select |
chan |
预定义标识符
内建常量 | |||
---|---|---|---|
true | false | iota | nil |
內建类型 | |||
---|---|---|---|
int | int8 | int16 | int32 |
int64 | uint | uint8 | uint16 |
uint32 | uint64 | uintptr | float32 |
float64 | complex64 | complex128 | bool |
byte | rune | string | error |
內建函数 | |||
---|---|---|---|
make | len | cap | new |
append | copy | delete | real |
imag | panic | recover | complex |
命名规则
, Go语言标识符的命名规则几乎和C语言一模一样
空标识符
, 它对应的值会被忽略package main import "fmt" func main() { // 将常量10保存到名称叫做num的变量中 var num int = 10 fmt.Println("num = ", num) // 忽略常量20,不会分配存储空间,也不会保存常量20 //var _ int = 20 //fmt.Println("_ = ", _) // cannot use _ as value // Go语言中如果定义了变量没有使用, 那么编译会报错(sub declared and not used) // 所以如果我们只使用了sum,没有使用sub会报错 // 为了解决这个问题, 我们可以使用_忽略sub的值 //var sum, sub int = calculate(20, 10) var sum, _ int = calculate(20, 10) fmt.Println("sum = ", sum) } func calculate(a, b int)(int, int) { var sum int = a + b var sub int = a - b return sum, sub } ```go + Go语言默认的编码方式就是UTF-8, 所以Go语言支持中文, 所以可以用中文作为标识符(非常非常非常不推荐) ```go package main import "fmt" func main() { // 不会报错, 可以正常运行 var 年龄 int = 33 fmt.Println("年龄 = ", 年龄) // 33 // 不会报错, 可以正常运行 var 结果 int = 计算器(10, 20) fmt.Println("结果 = ", 结果) // 30 } func 计算器(第一个变量, 第二个变量 int)int { return 第一个变量 + 第二个变量 }
命名规则
以外,还有标识符命名规范
Go语言本质是用C语言编写的一套高级开发语言, 所以Go语言中的数据类型大部分都是由C语言演变而来的
C语言数据类型
Go语言数据类型
类型 | 32位编译器 | 64位编译器 |
---|---|---|
char | 1 | 1 |
int | 4 | 4 |
float | 4 | 4 |
double | 8 | 8 |
short | 2 | 2 |
long | 4 | 8 |
long long | 8 | 8 |
void* | 4 | 8 |
类型 | 32位编译器 | 64位编译器 | 本质 |
---|---|---|---|
int8/uint8 | 1 | 1 | signed char/unsigned char |
int16/uint16 | 2 | 2 | signed short/unsigned short |
int32/uint32 | 4 | 4 | signed int/unsigned int |
int64/uint64 | 8 | 8 | signed long long int/unsigned long long int |
byte | 1 | 1 | uint8/unsigned char |
rune | 4 | 4 | int32/signed int |
int | 4 | 8 | 根据机器位数决定长度 |
uintptr | 4 | 8 | 根据机器位数决定长度 uint32/uint64 |
float32 | 4 | 4 | float |
float64 | 8 | 8 | double |
true | 1 | 1 | char类型的整型 |
false | 1 | 1 | char类型的整型 |
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)) }
go\src\runtime\runtime.h
// 第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
数据类型 变量名称; 数据类型 变量名称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) }
package main import "fmt" func main() { var num1, num2 int // 先定义 num1 = 10 // 后赋值 num2 = 20 fmt.Println("num1 = ", num1) fmt.Println("num2 = ", num2) var num3, num4 int = 30, 40 // 定义的同时赋值 fmt.Println("num3 = ", num3) fmt.Println("num4 = ", num4) var num5, num6 = 50, 60 // 定义的同时赋值, 并省略数据类型 fmt.Println("num5 = ", num5) fmt.Println("num6 = ", num6) num7, num8 := 70, 80 // 定义的同时赋值, 并省略关键字和数据类型 fmt.Println("num7 = ", num7) fmt.Println("num8 = ", num8) }
package main import "fmt" func main() { var( // 先定义 num1 int num2 float32 ) num1 = 10 // 后赋值 num2 = 3.14 fmt.Println("num1 = ", num1) fmt.Println("num2 = ", num2) var( // 定义的同时赋值 num3 int = 30 num4 float32 = 6.66 ) fmt.Println("num3 = ", num3) fmt.Println("num4 = ", num4) var( // 定义的同时赋值, 并省略数据类型 num5 = 50 num6 = 7.77 ) fmt.Println("num5 = ", num5) fmt.Println("num6 = ", num6) var( // 一行定义多个 num7, num8 = 70, 80 num9, num10 = 9.99, 100 ) fmt.Println("num7 = ", num7) fmt.Println("num8 = ", num8) fmt.Println("num9 = ", num9) fmt.Println("num10 = ", num10) }
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) }
和C语言一样,按照变量的作用域,我们可以把变量划分为局部变量和全局变量
Go语言中局部变量的概念以及全局变量的概念和C语言一模一样
局部变量:
全局变量:
和C语言不同的是, C语言中可以定义相同名称的全局变量, 而Go语言中无论全局变量还是局部变量, 只要作用域相同都不能出现同名的变量
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 num0 int = 10 var num1 int8 = 20 var num2 int16 //num2 = num0 // 编译报错, 不同长度的int之间也需要显示转换 //num2 = num1 // 编译报错, 不同长度的int之间也需要显示转换 num2 = int16(num0) num2 = int16(num1) fmt.Println(num2) var num3 float32 = 3.14 var num4 float64 //num4 = num3 // 编译报错, 不同长度的float之间也需要显示转换 num4 = float64(num3) fmt.Println(num4) var num5 byte = 11 var num6 uint8 // 这里不是隐式转换, 不报错的原因是byte的本质就是uint8 num6 = num5 fmt.Println(num6) var num7 rune = 11 var num8 int32 num8 = num7 // 这里不是隐式转换, 不报错的原因是byte的本质就是int32 fmt.Println(num8) }
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) }
strconv..FormatXxx()
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 }
strconv.ParseXxx()
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) }
和C语言一样Go语言中的常量也分为整型常量
、实型常量
、字符常量
、字符串常量
、自定义常量
自定义常量
const 数据类型 常量名称 = 值;
#include <stdio.h> int main(int argc, const char * argv[]) { const float PI = 998; PI = 110; // 报错 printf("PI = %d\n", PI ); return 0; }
const 常量名称 数据类型 = 值
or const 常量名称 = 值
package main import "fmt" func main() { //const PI float32 = 3.14 //PI = 110 // 报错 //fmt.Println("PI = ", PI ) const PI = 3.14 PI = 110 // 报错 fmt.Println("PI = ", PI ) }
一次性定义多个常量
package main import "fmt" func main() { // 多重赋值方式 const num1, num2 int = 100, 200 fmt.Println("num1 = ", num1) fmt.Println("num2 = ", num2) // 常量组方式 const ( num3 = 100 num4 = 200 ) fmt.Println("num3 = ", num3) fmt.Println("num4 = ", num4) // 常量组+多重赋值 const ( num5, num6 = 100, 200 num7 = 300 ) fmt.Println("num5 = ", num5) fmt.Println("num6 = ", num6) fmt.Println("num7 = ", num7) }
package main import "fmt" func main() { // 可以编译运行 const PI float32 = 3.14 }
package main import "fmt" func main() { const ( num1 = 998 num2 // 和上一行的值一样 num3 = 666 num4 // 和上一行的值一样 num5 // 和上一行的值一样 ) fmt.Println("num1 = ", num1) // 998 fmt.Println("num2 = ", num2) // 998 fmt.Println("num3 = ", num3) // 666 fmt.Println("num4 = ", num4) // 666 fmt.Println("num5 = ", num5) // 666 const ( num1, num2 = 100, 200 num3, num4 // 和上一行的值一样, 注意变量个数必须也和上一行一样 ) fmt.Println("num1 = ", num1) fmt.Println("num2 = ", num2) fmt.Println("num3 = ", num3) fmt.Println("num4 = ", num4) }
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" func main() { const ( male = iota female = iota yao = iota ) fmt.Println("male = ", male) // 0 fmt.Println("male = ", female) // 1 fmt.Println("male = ", yao) // 2 }
每一行递增1
package main import "fmt" func main() { const ( male = iota // 这里出现了iota female // 这里会自动递增 yao ) fmt.Println("male = ", male) // 0 fmt.Println("male = ", female) // 1 fmt.Println("male = ", yao) // 2 }
package main import "fmt" func main() { const ( male = iota female = 666 // 这里被中断, 如果没有显示恢复, 那么下面没有赋值的常量都和上一行一样 yao ) fmt.Println("male = ", male) // 0 fmt.Println("male = ", female) // 666 fmt.Println("male = ", yao) // 666 }
package main import "fmt" func main() { const ( male = iota female = 666 // 这里被中断 yao = iota // 这里显示恢复, 会从当前常量组第一次出现iota的地方开始,每一行递增1, 当前是第3行,所以值就是2 ) fmt.Println("male = ", male) // 0 fmt.Println("male = ", female) // 666 fmt.Println("male = ", yao) // 2 }
package main import "fmt" func main() { const ( a, b = iota, iota c, d = iota, iota ) fmt.Println("a = ", a) // 0 fmt.Println("b = ", b) // 0 fmt.Println("c = ", c) // 1 fmt.Println("d = ", d) // 1 }
package main import "fmt" func main() { const ( male float32 = iota // 显示指定类型,后续自增都会按照指定类型自增 female yao ) fmt.Printf("%f\n", male) // 0.0 fmt.Printf("%f\n", female) // 1.0 fmt.Printf("%f\n", yao) // 2.0 fmt.Println("male = ", reflect.TypeOf(female)) // float32 }
package main import "fmt" func main() { name := "微信搜索:代码情缘" age := 33 fmt.Printf("name = %s, age = %d\n", name, age) // name = lnj, age = 33 }
还可以
直接通过%b输出二进制package main import "fmt" func main() { num := 15 fmt.Printf("十进制 = %d\n", num) fmt.Printf("八进制 = %o\n", num) fmt.Printf("十六进制 = %x\n", num) fmt.Printf("二进制 = %b\n", num) }
package main import "fmt" func main() { type Person struct { name string age int } num1 := 10 num2 := 3.14 per := Person{"lnj", 33} fmt.Printf("num1 = %T\n", num1) // int fmt.Printf("num2 = %T\n", num2) // float64 fmt.Printf("per = %T\n", per) // main.Person }
package main import "fmt" func main() { type Person struct { name string age int } num1 := 10 num2 := 3.14 per := Person{"lnj", 33} // 此时相当于把%v当做%d fmt.Printf("num1 = %v\n", num1) // 10 // 此时相当于把%v当做%f fmt.Printf("num2 = %v\n", num2) // 3.14 }
会
在结尾处添加换行package main import "fmt" func main() { num1 := 10 num2 := 3.14 fmt.Println(num1, num2) // 10 3.14 fmt.Println("num1 =", num1, "num2 =", num2) // num1 = 10 num2 = 3.14 type Person struct { name string age int } per := Person{"lnj", 33} fmt.Println(per) // {lnj 33} }
不会
在结尾处添加换行都不是
字符串,才会在相邻参数之间添加空格package main import "fmt" func main() { num1 := 10 num2 := 3.14 fmt.Print(num1, num2) // 10 3.14 fmt.Print("num1 =", num1, "num2 =", num2) // num1 =10 num2 =3.14 type Person struct { name string age int } per := Person{"lnj", 33} fmt.Print(per) // {lnj 33} }
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.Scanf("%d%d", &num1, &num2) fmt.Println(num1, num2) }
package main import "fmt" func main() { var num1 int var num2 int fmt.Scan(&num1, &num2) fmt.Println(num1, num2) var num3 float32 var num4 float32 fmt.Scan(&num3, &num4) fmt.Println(num3, num4) }
package main import "fmt" func main() { var num1 int var num2 int fmt.Scanln(&num1, &num2) fmt.Println(num1, num2) }
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) }
|—项目文件夹
-----------|--------src文件夹
-----------------------------|--------main文件夹
-----------------------------|--------其它文件夹
-----------|--------bin文件夹
-----------|--------pkg文件夹
go version
查看当前安装的go版本go env
查看当前go的环境变量go fmt
格式化代码
go run 命令文件
编译并运行go程序
go build
编译检查
go build 文件名称
go install
安装程序
空格+参数
形式传递其它参数字符串类型
#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]) } }
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) }
会
报错需要
严格按照代码中的顺序传递参数, 否则会
造成数据混乱不能
指定参数的名称字符串
类型package main import ( "os" "fmt" ) int main(){ name := os.Args[1] age := os.Args[2] fmt.Println("name = ", name) fmt.Println("age = ", age) }
通过flag包获取命令行参数
不会
报错不需要
严格按照代码中的顺序传递参数, 不会
造成数据混乱可以
指定参数的名称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结果就为1 | A & B |
\| | 参与运算的两数各对应的二进位相或,对应位只要其中一个是1结果就为1 | A | B |
^ | 参与运算的两数各对应的二进位相异或,对应位只要不同结果就是1 | A ^ 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)) // 编译报错 }
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 初始化语句; 表达式{ 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 初始化表达式;循环条件表达式;循环后的操作表达式 { 循环体语句; }
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循环
for...range循环
可以快速完成对字符串、数组、slice、map、channel遍历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") }
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 }
// 给返回值指定了一个名称叫做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!!!") }
值类型
有: int系列、float系列、bool、string、数组、结构体
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" }
引用类型
有: 指针、slice、map、channel
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" // 方式一 var a = func() { fmt.Println("hello world1") } // 方式二 var ( b = func() { fmt.Println("hello world2") } ) func main() { a() b() }
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() { num := 10 a := func() { num++ // 在闭包中用到了main函数中的num, 所以这个匿名函数就是一个闭包 fmt.Println(num) // 11 } a() }
package main import "fmt" func main() { num := 10 a := func() { num = 6 // 在闭包中用到了main函数中的num, 所以这个匿名函数就是一个闭包 fmt.Println(num) // 6 } fmt.Println("执行闭包前", num) // 10 a() fmt.Println("执行闭包后", num) // 6 }
package main import "fmt" func main() { res := addUpper() // 执行addUpper函数,得到一个闭包 fmt.Println(res()) // 2 fmt.Println(res()) // 3 fmt.Println(res()) // 4 fmt.Println(res()) // 5 } func addUpper() func() int { x := 1 return func() int { x++ // 匿名函数中用到了addUpper中的x,所以这是一个闭包 return x } }
释放资源
、解除锁定
以及错误处理
等
后进先出
的原则执行
package main import "fmt" func main() { defer fmt.Println("我是第一个被注册的") // 3 fmt.Println("main函数中调用的Println") // 1 defer fmt.Println("我是第二个被注册的") // 2 }
不能
在任何地方调用这两个函数main包-->常量-->全局变量-->init函数-->main函数-->Exit
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 }
多个包之间代码执行顺序如下
init函数的作用
相同类型
的数据一维数组
和多维数组
var arr [元素个数]数据类型
有默认的初始值
package main import "fmt" func main() { // 1.定义一个数组 var arr [3]int // 2.打印数组 fmt.Println(arr) //[0 0 0] // 1.定义一个数组 var arr [3]bool // 2.打印数组 fmt.Println(arr) //[false false false] }
package main import "fmt" func main() { // 1.定义的同时完全初始化 var arr1 [3]int = [3]int{1, 3, 5} // 2.打印数组 fmt.Println(arr1) // [1 3 5] // 1.定义的同时部分初始化 var arr4 [3]int = [3]int{8, 9} // 2.打印数组 fmt.Println(arr4) // [8 9 0] // 1.定义的同时指定元素初始化 var arr5 [3]int = [3]int{0:8, 2:9} // 2.打印数组 fmt.Println(arr5) // [8 0 9] // 1.先定义再逐个初始化 var arr3 [3]int arr3[0] = 1 arr3[1] = 2 arr3[2] = 3 // 2.打印数组 fmt.Println(arr3) // [1 2 3] }
可以先定义再一次性初始化
package main import "fmt" func main() { // 1.先定义再一次性初始化 var arr2 [3]int arr2 = [3]int{2, 4, 6} // 2.打印数组 fmt.Println(arr2) // [2 4 6] }
...
来替代
package main import "fmt" func main() { // 1.定义的同时完全初始化 var arr1 = [...]int{1, 3, 5} // 2.打印数组 fmt.Println(arr1) // [1 3 5] // 1.定义的同时指定元素初始化 var arr2 = [...]int{6:5} // 2.打印数组 fmt.Println(arr2) // [0 0 0 0 0 0 5] }
数组名称[索引]
的方式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() { var arr1 [3]int var arr2 [3]int //var arr3 [2]int fmt.Println(arr1 == arr2) // true //fmt.Println(arr1 == arr3) // 编译报错, 不是相同类型不能比较 }
package main import "fmt" func main() { var arr1 [3]int = [3]int{1, 3, 5} var arr2 [3]int = [3]int{1, 3, 5} var arr3 [3]int = [3]int{2, 4, 6} // 首先会判断`数据类型`是否相同,如果相同会依次取出数组中`对应索引的元素`进行比较, // 如果所有元素都相同返回true,否则返回false fmt.Println(arr1 == arr2) // true fmt.Println(arr1 == arr3) // false }
package main import "fmt" func main() { var arr1 [3]int = [3]int{1, 3, 5} var arr2 [3]int = arr1 arr2[0] = 666 fmt.Println(arr1) // [1 3 5] fmt.Println(arr2) // [666 3 5] }
[行数][列数]类型
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]] }
切片
type slice struct{ array unsafe.Pointer // 指向底层数组指针 len int // 切片长度(保存了多少个元素) cap int // 切片容量(可以保存多少个元素) }
array[startIndex:endIndex]
package main import "fmt" func main() { var arr = [5]int{1, 3, 5, 7, 9} // 从数组0下标开始取,一直取到2下标前面一个索引 var sce = arr[0:2] fmt.Println(sce) // [1 3] // 切片len = 结束位置 - 开始位置 fmt.Println(len(sce)) // 2 - 0 = 2 fmt.Println(cap(sce)) // 5 - 0 = 5 // 数组地址就是数组首元素的地址 fmt.Printf("%p\n", &arr) // 0xc04200a330 fmt.Printf("%p\n", &arr[0]) // 0xc04200a330 // 切片地址就是数组中指定的开始元素的地址 // arr[0:2]开始地址为0, 所以就是arr[0]的地址 fmt.Printf("%p\n", sce) // 0xc04200a330 }
package main import "fmt" func main() { var arr = [5]int{1, 3, 5, 7, 9} // 根据数组的索引片段创建切片 var sce = arr[2:4] fmt.Println(sce) // [5 7] fmt.Println(len(sce)) // 4 - 2 = 2 fmt.Println(cap(sce)) // 5 - 2 = 3 fmt.Printf("%p\n", &arr[2]) // 0xc042076070 fmt.Printf("%p\n", sce) // 0xc042076070 }
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] }
make(类型, 长度, 容量)
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 }
切片名称[索引]
方式操作切片package main import "fmt" func main() { var sce = []int{1, 3, 5} // 使用切片, 往切片中存放数据 sce[1] = 666 // 访问切片, 从切片中获取数据 fmt.Println(sce) // [1 666 5] }
切片名称[索引]
方式操作切片, 不能越界package main import "fmt" func main() { var sce = []int{1, 3, 5} // 编译报错, 越界 sce[3] = 666 }
末尾
添加一个元素, 并返回一个追加数据之后的切片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 fmt.Printf("追加数据前: %p\n", sce) // 0xc0420600a0 // 第一个参数: 需要把数据追加到哪个切片中 // 第二个参数: 需要追加的数据, 可以是一个或多个 sce = append(sce, 666) fmt.Println("追加数据后:", sce) // [1 3 5 666] fmt.Println("追加数据后:", len(sce)) // 4 fmt.Println("追加数据后:", cap(sce)) // 6 fmt.Printf("追加数据前: %p\n", sce) // 0xc042076b60 }
copy(目标切片, 源切片)
, 会将源切片中数据拷贝到目标切片中package main import "fmt" func main() { var sce1 = []int{1, 3, 5} var sce2 = make([]int, 5) fmt.Printf("赋值前:%p\n", sce1) // 0xc0420600a0 fmt.Printf("赋值前:%p\n", sce2) // 0xc042076060 // 将sce2的指向修改为sce1, 此时sce1和sce2底层指向同一个数组 sce2 = sce1 fmt.Printf("赋值后:%p\n", sce1) // 0xc0420600a0 fmt.Printf("赋值后:%p\n", sce2) // 0xc0420600a0 //copy(sce2, sce1) fmt.Println(sce1) // [1 3 5] fmt.Println(sce2) // [1 3 5] sce2[1] = 666 fmt.Println(sce1) // [1 666 5] fmt.Println(sce2) // [1 666 5] }
package main import "fmt" func main() { var sce1 = []int{1, 3, 5} var sce2 = make([]int, 5) fmt.Printf("赋值前:%p\n", sce1) // 0xc0420600a0 fmt.Printf("赋值前:%p\n", sce2) // 0xc042076060 // 将sce1中的数据拷贝到sce2中,, 此时sce1和sce2底层指向不同数组 copy(sce2, sce1) fmt.Printf("赋值后:%p\n", sce1) // 0xc0420600a0 fmt.Printf("赋值后:%p\n", sce2) // 0xc042076060 //copy(sce2, sce1) fmt.Println(sce1) // [1 3 5] fmt.Println(sce2) // [1 3 5 0 0] sce2[1] = 666 fmt.Println(sce1) // [1 3 5] fmt.Println(sce2) // [1 666 5 0 0] }
package main import "fmt" func main() { // 容量为3 var sce1 = []int{1, 3, 5} // 容量为5 var sce2 = make([]int, 5) fmt.Println("拷贝前:", sce2) // [0 0 0 0 0] // sce2容量足够, 会将sce1所有内容拷贝到sce2 copy(sce2, sce1) fmt.Println("拷贝后:", sce2) // [1 3 5 0 0] }
package main import "fmt" func main() { // 容量为3 var sce1 = []int{1, 3, 5} // 容量为2 var sce2 = make([]int, 2) fmt.Println("拷贝前:", sce2) // [0 0] // sce2容量不够, 会将sce1前2个元素拷贝到sce2中 copy(sce2, sce1) fmt.Println("拷贝后:", sce2) // [1 3] }
package main import "fmt" func main() { arr := [5]int{1, 3, 5, 7, 9} sce1 := arr[0:4] sce2 := sce1[0:3] fmt.Println(sce1) // [1 3 5 7] fmt.Println(sce2) // [1 3 5] // 由于底层指向同一数组, 所以修改sce2会影响sce1 sce2[1] = 666 fmt.Println(sce1) // [1 666 5 7] fmt.Println(sce2) // [1 666 5] }
package main import "fmt" func main() { var arr1 [3]int = [3]int{1, 3, 5} var arr2 [3]int = [3]int{1, 3, 5} var arr3 [3]int = [3]int{2, 4, 6} // 首先会判断`数据类型`是否相同,如果相同会依次取出数组中`对应索引的元素`进行比较, // 如果所有元素都相同返回true,否则返回false fmt.Println(arr1 == arr2) // true fmt.Println(arr1 == arr3) // false sce1 := []int{1, 3, 5} sce2 := []int{1, 3, 5} //fmt.Println(sce1 == sce2) // 编译报错 fmt.Println(sce1 != nil) // true fmt.Println(sce2 == nil) // false }
package main import "fmt" func main() { // 数组声明后就可以直接使用, 声明时就会开辟存储空间 var arr [3]int arr[0] = 2 arr[1] = 4 arr[2] = 6 fmt.Println(arr) // [2 4 6] // 切片声明后不能直接使用, 只有通过make或语法糖创建之后才会开辟空间,才能使用 var sce []int sce[0] = 2 // 编译报错 sce[1] = 4 sce[2] = 6 fmt.Println(sce) }
package main import "fmt" func main() { str := "abcdefg" // 通过字符串生成切片 sce1 := str[3:] fmt.Println(sce1) // defg sce2 := make([]byte, 10) // 第二个参数只能是slice或者是数组 // 将字符串拷贝到切片中 copy(sce2, str) fmt.Println(sce2) //[97 98 99 100 101 102 103 0 0 0] }
var dic map[key数据类型]value数据类型
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] }
make(类型, 容量)
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] }
make(类型)
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" func main() { type Student struct { name string age int } // 完全初始化 var stu1= Student{"lnj", 33} fmt.Println(stu1) // 部分初始化 // 部分初始化必须通过 属性名称: 方式指定要初始化的属性 var stu2 = Student{name:"lnj"} fmt.Println(stu2) }
package main import "fmt" func main() { // 注意: 这里不用写type和结构体类型名称 var stu2 = struct { name string age int }{ name: "lnj", age: 33, } fmt.Println(stu2) }
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" type Student struct { name string age int } func main() { type Demo struct { age int // 基本类型作为属性 arr [3]int // 数组类型作为属性 sce []int // 切片类型作为属性 mp map[string]string // 字典类型作为属性 stu Student // 结构体类型作为属性 } var d Demo = Demo{ 33, [3]int{1, 3, 5}, []int{2, 4, 6}, map[string]string{"class":"one"}, Student{ "lnj", 33, }, } fmt.Println(d) // {33 [1 3 5] [2 4 6] map[class:one] {lnj 33}} }
package main import "fmt" type Student struct { name string age int } func main() { type Demo struct { age int // 基本类型作为属性 arr [3]int // 数组类型作为属性 sce []int // 切片类型作为属性 mp map[string]string // 字典类型作为属性 stu Student // 结构体类型作为属性 } // 定义结构体变量 var d Demo // 可以直接使用基本类型属性 d.age = 33 // 可以直接使用数组类型属性 d.arr[0] = 666 // 不可以直接使用切片类型属性 //d.sce[0] = 888 // 编译报错 d.sce = make([]int, 5) // 先创建 d.sce[0] = 888 // 后使用 // 不可以直接使用字典类型属性 //d.mp["class"] = "one" // 编译报错 d.mp = make(map[string]string)// 先创建 d.mp["class"] = "one"// 后使用 // 可以直接使用结构体类型属性 d.stu.name = "lnj" fmt.Println(d) // {33 [666 0 0] [888 0 0 0 0] map[class:one] {lnj 0}} }
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) }
有命名的数据类型
都可以作为匿名属性(int、float、bool、string、struct等)package main import "fmt" func main() { type Person struct { int float32 bool string } // 不指定名称初始化 per1 := Person{3, 3.14, false, "lnj"} fmt.Println(per1) // 可以把数据类型作为名字显示初始化 per2 := Person{ int: 3, float32: 3.14, bool: true, string: "lnj", } fmt.Println(per2) // 可以把数据类型当做属性名称操作结构体 per2.int = 666 fmt.Println(per2.int) // 666 }
结构体类型作为匿名属性
package main import "fmt" func main() { type Person struct { name string age int } type Student struct { Person // 匿名属性 class string } stu := Student{ Person{"lnj", 33}, "学前一班", } fmt.Println(stu) // {{lnj 33} 学前一班} }
package main import "fmt" func main() { type Person struct { name string age int } type Student struct { Person // 匿名属性 class string } stu := Student{ Person{"lnj", 33}, "学前一班", } fmt.Println(stu) // {{lnj 33} 学前一班} // 方式一: 先找到匿名属性,再访问匿名属性中的属性 stu.Person.name = "zs" fmt.Println(stu) // {{zs 33} 学前一班} // 方式二: 直接访问匿名属性中的属性 // 系统会先查找当前结构体有没有名称叫做name的属性 // 如果没有会继续查找匿名属性中有没有名称叫做name的属性 stu.name = "ww" fmt.Println(stu) // {{ww 33} 学前一班} }
package main import "fmt" func main() { type Person struct { name string age int } type Class struct { name string time string } type Student struct { Person // 匿名属性 Class // 匿名属性 } stu := Student{ Person{"lnj", 33}, Class{"学前一班", "2020-12-12"}, } fmt.Println(stu) // {{lnj 33} {学前一班 2020-12-12}} // 编译报错, 系统搞不清楚要找哪个name //stu.name = "zs" stu.Person.name = "zs" stu.Class.name = "小学一年级" fmt.Println(stu) // {{zs 33} {小学一年级 2020-12-12}} }
package main import "fmt" func main() { type Person struct { name string } type Student struct { per Person age int } var stu Student = Student{Person{"lnj"}, 18} //fmt.Println(stu.name) // 报错 fmt.Println(stu.per.name) // 必须通过属性进一步查找 fmt.Println(stu.age) }
package main import "fmt" func main() { type Person struct { Person // 错误 name string } type Student struct { *Student // 正确, 链表 age int } }
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) }
package main import "fmt" type Student struct { name string age int } func main() { // 创建时利用取地址符号获取结构体变量地址 var p1 = &Student{"lnj", 33} fmt.Println(p1) // &{lnj 33} // 通过new内置函数传入数据类型创建 // 内部会创建一个空的结构体变量, 然后返回这个结构体变量的地址 var p2 = new(Student) fmt.Println(p2) // &{ 0} }
package main import "fmt" type Student struct { name string age int } func main() { var p = &Student{} // 方式一: 传统方式操作 // 修改结构体中某个属性对应的值 // 注意: 由于.运算符优先级比*高, 所以一定要加上() (*p).name = "lnj" // 获取结构体中某个属性对应的值 fmt.Println((*p).name) // lnj // 方式二: 通过Go语法糖操作 // Go语言作者为了程序员使用起来更加方便, 在操作指向结构体的指针时可以像操作接头体变量一样通过.来操作 // 编译时底层会自动转发为(*p).age方式 p.age = 33 fmt.Println(p.age) // 33 }
Go语言中的方法其实就是一个特殊函数, 只不过这个函数是和某种属性类型绑定在一起的而已
Go语言中的方法一般用于
将函数和结构体绑定在一起
, 让结构体除了能够保存数据外还能具备某些行为
将函数和数据类型绑定的格式
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() }
package main import "fmt" type Person struct { name string age int } // 定义一个方法 func (p Person)say() { fmt.Println("say方法") } // 定义一个函数 func say() { fmt.Println("say函数") } func main() { p := Person{"lnj", 33} fmt.Printf("%T\n", p.say) // func() fmt.Printf("%T\n", say) // func() // 定义一个保存没有形参没有返回值的函数类型变量 var fn func() // 利用函数类型变量保存一个方法 fn = p.say // 利用函数类型变量调用一个方法 fn() // 利用函数类型变量保存一个函数 fn = say // 利用函数类型变量调用一个函数 fn() }
package main import "fmt" type Person struct { name string age int } // 定义一个方法 func (p Person)say() { fmt.Println("my name is", p.name, "my age is", p.age) } // 定义一个函数 func test() { fmt.Println("test") } func main() { per := Person{"lnj", 33} per.say() // 方法只能通过绑定类型的变量调用 //say() // 编译报错, 不能直接调用 test() // 编译通过, 可以直接调用 }
package main import "fmt" type Person struct { name string age int } // 定义一个方法 func (p Person)say() { fmt.Println("my name is", p.name, "my age is", p.age) } // 定义一个函数 func test(p Person) { fmt.Println("my name is", p.name, "my age is", p.age) } func main() { per := Person{"lnj", 33} per.say() // my name is lnj my age is 33 test(per) // my name is lnj my age is 33 }
package main import "fmt" type Person struct { name string age int } // 接收者是一个变量 func (p Person)setName(name string) { p.name = name } // 接收者是一个指针 func (p *Person)setAge(age int) { p.age = age } func main() { per := Person{"lnj", 33} fmt.Println(per) // {lnj 33} // 值传递, 方法内部修改不会影响方法外部 per.setName("zs") fmt.Println(per) // {lnj 33} p := &per // 地址传递, 方法内部修改会影响方法外部 (*p).setAge(18) fmt.Println(per) // {lnj 18} }
package main import "fmt" type Person struct { name string age int } // 接收者是一个变量 func (p Person)setName(name string) { p.name = name } // 接收者是一个指针 func (p *Person)setAge(age int) { p.age = age } func main() { per := Person{"lnj", 33} // 方式一: 先拿到指针,然后再通过指针调用 p := &per (*p).setAge(18) fmt.Println(per) // {lnj 18} // 方式二: 直接利用变量调用, 底层会自动获取变量地址传递给接收者 per.setAge(66) fmt.Println(per) // {lnj 66} }
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) // 启动手机 关闭手机 }
type usber interface { func start(){ // 错误 fmt.Println("启动") } func stop() { // 错误 fmt.Println("停止") } }
type usber interface { name string // 错误 start() stop() }
该接口类型
接收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("关闭电脑") } // 2.只实现了接口中部分方法 type Phone struct { name string model string } func (p Phone)start() { fmt.Println("启动手机") } func main() { // 1.定义一个usber接口类型变量 var i usber // 2.用usber接口类型变量接收Computer类型结构体 i = Computer{"戴尔", "F1234"} // 实现了所有方法, 不会报错 // 3.用usber接口类型变量接收Phone类型结构体 //i = Phone{"华为", "M10"} // 只实现了部分方法, 会报错 fmt.Println(i) }
package main import "fmt" type A interface { fna() } type B interface { fnb() } type C interface { A // 嵌入A接口 B // 嵌入B接口 fnc() } type Person struct {} func (p Person)fna() { fmt.Println("实现A接口中的方法") } func (p Person)fnb() { fmt.Println("实现B接口中的方法") } func (p Person)fnc() { fmt.Println("实现C接口中的方法") } func main() { p := Person{} p.fna() // 实现A接口中的方法 p.fnb() // 实现B接口中的方法 p.fnc() // 实现C接口中的方法 }
type A interface { A // 报错, 不能自己搞自己 }
type A interface { fn() } type B interface { fn() } type C interface { A B // 报错, A接口和B接口都有名称叫做fn的方法 fnc() }
package main import "fmt" type aer interface { fna() } type ber interface { aer fnb() } // Person实现了超集接口所有方法 type Person struct {} func (p Person)fna() { fmt.Println("实现A接口中的方法") } func (p Person)fnb() { fmt.Println("实现B接口中的方法") } // Student实现了子集接口所有方法 type Student struct { } func (p Student)fna() { fmt.Println("实现A接口中的方法") } func main() { var i ber // 子集接口变量不能转换为超集接口变量 //i = Student{} fmt.Println(i) var j aer // 超集接口变量可以自动转换成子集接口变量, j = Person{} fmt.Println(j) }
package main import "fmt" func main() { // 1.定义一个空接口类型变量 var i interface{} // 2.用接口类型保存任意类型数据 i = 123 fmt.Println(i) // 123 i = 3.14 fmt.Println(i) // 3.14 i = "lnj" fmt.Println(i) // lnj i = [3]int{1, 3, 5} fmt.Println(i) // [1 3 5] i = []int{2, 4, 6} fmt.Println(i) // [2 4 6] i = map[string]string{"name": "lnj"} fmt.Println(i) // map[name:lnj] i = Computer{"戴尔", "F1234"} fmt.Println(i) // {戴尔 F1234} }
package main import "fmt" // 1.定义一个接口 type usber interface { start() stop() } // 2.自定义int类型 type integer int // 2.实现接口中的所有方法 func (i integer)start() { fmt.Println("int类型实现接口") } func (i integer)stop() { fmt.Println("int类型实现接口") } func main() { var i integer = 666 i.start() // int类型实现接口 i.stop() // int类型实现接口 }
package main import "fmt" type studier interface { read() } type Person struct { name string age int } func (p Person)read() { fmt.Println(p.name, "正在学习") } func main() { // 1.定义一个接口类型变量 var s studier // 2.用接口类型变量接收实现了接口的结构体 s = Person{"lnj", 33} s.name = "zs" // 报错, 由于s是接口类型, 所以不能访问属性 fmt.Println(s) }
package main import "fmt" type studier interface { read() } type Person struct { name string age int } func (p Person)read() { fmt.Println(p.name, "正在学习") } func main() { var s studier s = Person{"lnj", 33} s.name = "zs" // 报错, 由于s是接口类型, 所以不能访问属性 // 2.定义一个结构体类型变量 //var p Person // 不能用强制类型转换方式将接口类型转换为原始类型 //p = Person(s) // 报错 // 2.利用ok-idiom模式将接口类型还原为原始类型 // s.(Person)这种格式我们称之为: 类型断言 if p, ok := s.(Person); ok { p.name = "zs" fmt.Println(p) } // 2.通过 type switch将接口类型还原为原始类型 // 注意: type switch不支持fallthrought switch p := s.(type) { case Person: p.name = "zs" fmt.Println(p) // {zs 33} default: fmt.Println("不是Person类型") } }
package main import "fmt" type studier interface { read() } type Person struct { name string age int } func (p Person)read() { fmt.Println(p.name, "正在学习") } func main() { // 1.定义一个抽象接口类型 var i interface{} i = Person{"lnj", 33} // 不能调用read方法, 因为抽象接口中没有这个方法 //i.read() // 2.利用ok-idiom模式将抽象接口转换为具体接口 if s, ok := i.(studier); ok{ // 可以调用read方法,因为studier中声明了这个方法,并且结构体中实现了这个方法 s.read() // lnj 正在学习 } }
面向对象是相对面向过程而言
面向对象和面向过程都是一种思想
面向过程
回想下前面我们完成一个需求的步骤:
在上面每一个具体步骤中我们都是参与者, 并且需要面对具体的每一个步骤和过程, 这就是面向过程最直接的体现
面向过程
面向对象
面向过程
面向对象
面向对象的核心就是对象,那怎么创建对象?
属性
和行为
Go语言中的类相当于图纸,用来描述一类事物。也就是说要想创建对象必须先有类
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) // 这里是错误信息 }
builtin
包中定义了一个名称叫做error的接口. 源码如下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) }
100 *time.Millisecond
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)) }
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() }
- Go语言中没有包名是C的包, 但是这个导入会促使Go编译器利用cgo工具预处理文件
- 在预处理过程中,cgo会产生一个临时包, 这个包里包含了所有C函数和类型对应的Go语言声明
- 最终使得cgo工具可以通过一种特殊的方式来调用import "C"之前的C语言代码
cc1.exe: sorry, unimplemented: 64-bit mode not compiled in
//export Go函数名称
导出Go的函数名称extern 返回值类型 Go函数名称(形参列表);
声明Go中导出的函数名称//export Go函数名称
和extern 返回值类型 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() }
- 由于不在同一个文件, 所以需要通过go build或者go install同时编译多个文件
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) }
C.GoString(str)
C.CString(str)
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 = # */ 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("李南江") }
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 // 如果可能,打开时清空文件 )
const ( // 单字符是被String方法用于格式化的属性缩写。 ModeDir FileMode = 1 << (32 - 1 - iota) // d: 目录 ModeAppend // a: 只能写入,且只能写入到末尾 ModeExclusive // l: 用于执行 ModeTemporary // T: 临时文件(非备份文件) ModeSymlink // L: 符号链接(不是快捷方式文件) ModeDevice // D: 设备 ModeNamedPipe // p: 命名管道(FIFO) ModeSocket // S: Unix域socket ModeSetuid // u: 表示文件具有其创建者用户id权限 ModeSetgid // g: 表示文件具有其创建者组id的权限 ModeCharDevice // c: 字符设备,需已设置ModeDevice ModeSticky // t: 只有root/创建者能删除/移动文件 // 覆盖所有类型位(用于通过&获取类型位),对普通文件,所有这些位都不应被设置 ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice ModePerm FileMode = 0777 // 覆盖所有Unix权限位(用于通过&获取类型位) )
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在语言级别支持协程
(多数语言在语法层面并不直接支持协程), 叫做goroutine.
人们把Go语言称之为21世纪的C语言. 第一是因为Go语言设计简单, 第二是因为21世纪最重要的就是并行程序设计.而Go从语言层面就支持并发和并行
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{ ; } }
package main import ( "fmt" "runtime" ) func sing() { for i:=0; i< 10; i++{ fmt.Println("我在唱歌") // Gosched使当前go程放弃处理器,以让其它go程运行。 // 它不会挂起当前go程,因此当前go程未来会恢复执行 runtime.Gosched() } } func dance() { for i:=0; i< 10; i++{ fmt.Println("我在跳舞---") runtime.Gosched() } } func main() { go sing() go dance() for{ ; } }
package main import ( "fmt" "runtime" ) func main() { go func() { fmt.Println("123") // 退出当前协程 //runtime.Goexit() // 退出当前函数 //return test() fmt.Println("456") }() for{ ; } } func test() { fmt.Println("abc") // 只会结束当前函数, 协程中的其它代码会继续执行 //return // 会结束整个协程, Goexit之后整个协程中的其它代码不会执行 runtime.Goexit() fmt.Println("def") }
package main import ( "fmt" "runtime" ) func main() { num := runtime.NumCPU() fmt.Println(num) }
func main() { // 获取带来了CPU个数 num := runtime.NumCPU() // 设置同时使用CPU个数 runtime.GOMAXPROCS(num) }
package main import ( "fmt" "sync" "time" ) // 创建一把互斥锁 var lock sync.Mutex 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{ ; } }
... ...
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)
var 变量名chan 数据类型
mych := make(chan 数据类型, 容量)
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 { 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) } }
声明:文章首发 CSDN,未经授权请勿转载,转载请私信我。
码字不易,点赞,收藏,评论,一键三连支持,你的支持就是我更新文章最大的动力。
附:本文 PDF 版本下载链接 提取码:dmqx