Go 语言保姆级教程(上)

哈喽,大家好,我是瓜哥,致力于分享互联网各领域干货。

这篇文章可以说又是一本书了,排版,码字耗费了很长的时间,10W+字 Go 语言入门保姆级教程2021年版,觉得有价值记得一键四连支持。

目录

  • 什么是Go语言
  • Go语言优势
  • Go语言发展史
  • Go作者
  • Go语言现状
  • Go语言应用场景
  • 如何学习Go语言
  • 源文件对比
  • 代码管理对比
  • 关键字对比
  • 数据类型对比
  • 常量变量对比
  • 注释对比
  • 运算符对比
  • 流程控制语句对比
  • 函数和方法对比
  • 编程思想对比
  • 其它新增特性
  • Go语言SDK安装和配置
  • 安装Go语言开发工具
  • Goland安装
  • Go语言程序组成
  • Go语言程序主函数定义格式
  • Go语言HelloWorld
  • Go语言HelloWorld和C语言HelloWorld异同
  • Go语言注释
  • Go语言编码风格
  • 关键字
  • C语言关键字和Go语言关键字对比
  • 标识符
  • Go语言数据类型
  • Go语言变量
  • Go语言变量定义注意点
  • 局部变量和全局变量
  • 数据类型转换
  • 数值类型和字符串类型之间转换
  • Go语言常量
  • 输入函数
  • go命令行操作指令
  • 通过os包获取命令行参数
  • 通过flag包获取命令行参数
  • os包和flag包获取命令行参数对比
  • 算数运算符
  • 关系算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
  • 其它运算符
  • 运算符优先级
  • Go语言流程控制基本概念
  • 选择结构if
  • 选择结构switch
  • 循环结构for
  • 四大跳转
  • 函数
  • 和C语言函数差异
  • 值传递和引用传递
  • 匿名函数
  • 闭包
  • 延迟调用
  • init函数
  • 数组
  • 一维数组
  • 二维数组
  • 切片
  • map(字典、映射)
  • 结构体
  • 普通指针
  • 指向数组指针
  • 指向切片的指针
  • 指向字典指针
  • 指向结构体指针
  • 指针作为函数参数和返回值
  • 方法
  • 接口
  • 面向对象基本概念
  • 面向对象思想
  • 面向对象和面向过程区别
  • 面向对象的特点
  • 类与对象的关系
  • 如何设计一个类
  • 如何分析一个类
  • 如何定义一个类
  • 如何通过类创建一个对象
  • 不同包中变量、函数、方法、类型公私有问题
  • 面向对象三大特性
  • 异常处理
  • 打印异常信息
  • 中断程序
  • 恢复程序
  • 字符串相关方法
  • 正则表达式
  • 时间和日期函数
  • Go语言中调用C语言函数
  • C语言中调用Go语言函数(很少使用)
  • 文件的打开和关闭
  • 文件读取
  • 文件创建和写入
  • 判断文件是否存在
  • 练习
  • 并发编程基本概念
  • 什么是串行?
  • 什么是并行?
  • 什么是并发?
  • 什么是程序?
  • 什么是进程?
  • 什么是线程?
  • 什么是协程?
  • Go并发
  • 多线程同步问题
  • 管道(Channel)
  • select选择结构
  • 定时器补充

什么是Go语言

  • Go语言(Golang)是Google公司2009年推出的一门”高级编程言语”, 目的是为了解决:
    • “现有主流编程语言”明显落后于硬件发展速度的问题
    • 不能合理利用多核CPU的优势提升软件系统性能的问题
    • 软件复杂度越来越高, ***维护成本也越来越高***的问题
    • 企业开发中不得不在***快速开发和性能之间艰难抉择***的问题

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

  • Go语言专门针对多核CPU进行了优化, 能够充分使用硬件多核CPU的优势, 使得通过Go语言编写的软件系统性能能够得到很大提升
  • Go语言编写的程序,既可以媲美C或C++代码的运行速度, 也可以媲美Ruby或Python开发的效率
  • 所以Go语言很好的解决了”现有主流编程语言”存在的问题, 被誉”现代化的编程语言”

Go语言优势

  • 简单易学
    • Go语言的作者都有C的基因,Go自然而然也有了C的基因,但是Go的语法比C还简单, 并且几乎支持大多数你在其他语言见过的特性:封装、继承、多态、反射等
  • 丰富的标准库
    • Go目前已经内置了大量的库,特别是网络库非常强大
    • 前面说了作者是C的作者,所以Go里面也可以直接包含c代码,利用现有的丰富的C库
  • 跨平台编译和部署
    • Go代码可直接编译成机器码,不依赖其他库,部署就是扔一个文件上去就完事了. 并且Go代码还可以做到跨平台编译(例如: window系统编译linux的应用)
  • 内置强大的工具
    • Go语言里面内置了很多工具链,最好的应该是gofmt工具,自动化格式化代码,能够让团队review变得如此的简单,代码格式一模一样,想不一样都很困难
  • 性能优势: Go 极其地快。其性能与 C 或 C++相似。在我们的使用中,Go 一般比 Python 要快 30 倍左右
    • 语言层面支持并发,这个就是Go最大的特色,天生的支持并发,可以充分的利用多核,很容易的使用并发
    • 内置runtime,支持垃圾回收
  • … …

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


Go语言发展史

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

Go作者

  • Go语言是UNIX作者、C语言作者、谷歌V8引擎作者携手打造的, 由谷歌公司2009年推出的一门高级编程言语。图片

跟着谷歌走吃喝啥都有


Go语言现状

  • 多次获得TIOBE年度最佳语言图片

  • 2018年Go语言一度超过Java, 进入编程语言排行榜前三名.图片

  • 从公司角度:

    • 许多大厂都已经拥抱 Go 语言,包括阿里巴巴、京东、今日头条、小米、滴滴、七牛云、360等明星公司, 也包括知乎、轻松筹、快手、探探、美图、猎豹移动等等。同时,创业公司也很喜欢 Go 语言,主要因为其入门快、程序库多、运行迅速,很适合快速构建互联网软件产品。
  • 从业务维度:

    • Go 程序可以在装有 Windows、Linux、FreeBSD 等操作系统的服务器上运行,并用于提供基础软件支撑、API 服务、Web 服务、网页服务等等。
    • 在云计算、微服务、大数据、区块链、物联网等领域,Go 语言早已蓬勃发展. 除了语法简单, 性能优越以外, K8S底层架构在云计算的领导地位(K8S就是Go开发的), 也让这些各大公司不得不拥抱Go语言。
    • 区块链的崛起更进一步带动了Go工程师的需求,市面上大部分区块链明星项目都是用Go开发的, 足以说明Go在分布式系统中的地位,这也就是为什么今年开始,大批金融公司开始招聘Go工程师的重要原因。
  • 从薪资角度来看

    • 应届生普遍在4~8K,  1年左右普遍在10K左右, 2年~3年普遍在20K左右图片

Go语言应用场景

  • 网络编程,这一块目前应用最广,包括Web应用、API应用、下载应用、内存数据库等
  • 云平台开发,目前国外很多云平台在采用Go开
  • 服务器编程, 以前你如果使用Java或者C++做的那些事情,都可以用Go来做
  • 分布式系统,数据库代理器等
  • 它可以做从底层到前端的任何工作

如何学习Go语言

  • Go语言被称之为现代化的C语言, 所以无论是从语法特性, 还是作者本身, Go语言都与C语言有着莫大的关系, 所以学习本套课程之前如果你有C语言的基础, 那么将会事半功倍
  • 对于初学者而言, 学习编程的捷径只有一条, 那就是多动手

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


源文件对比

  • C语言源文件
文件扩展名 源类型
.h 头文件,存放代码声明
.c C语言源文件,存放代码实现
  • Go语言源文件
文件扩展名 源类型
.go Go语言源文件,存放代码实现

代码管理对比

  • C语言中通过文件来管理代码
    • 想使用某一个函数时,只需要include导入对应的.h文件即可
  • Go语言中通过包来管理代码
    • Go语言没有.h文件的概念, 在Go中想使用某一个函数时, 只需要import导入对应的包即可
  • C语言中函数、变量公私有管理
    • 通过extern和static实现是否公开函数和变量
  • Go语言中函数、变量公私有管理
    • 通过函数名称首字母大小写实现是否公开函数
    • 通过变量名称首字母大小写实现是否公开变量

关键字对比

  • C语言中一共有32个关键字
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
  • Go语言中一共有25个关键字
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

数据类型对比

  • C语言数据类型图片
  • Go语言数据类型图片

  • C语言各数据类型占用内存空间
类型 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
  • Go语言各数据类型占用内存空间
类型 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类型的整型
  • 和C语言一样,Go语言也提供了Sizeof计算变量的内存空间
    • 1.导入import “unsafe”包
    • 2.通过unsafe.Sizeof()计算变量内存空间

  • Go语言基本数据类型内部实现
    • 越老版本的代码越纯粹,越适合新手学习
    • 随着代码的更新迭代会逐步变得非常复杂, 所以此处建议下载1.4版本
    • golang官方网站下载go1.4版本源代码
    • 解压后打开路径: 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的实现代码,做到知其然知其所以然


常量变量对比

  • C语言定义常量和变量格式
数据类型 变量名称 = 值;
const 数据类型 常量名称 = 值;
  • Go语言定义常量和变量格式
    • 除了以下标准格式外,Go语言还提供了好几种简单的语法糖
var 变量名称 数据类型 = 值;
const 变量名称 数据类型 = 值;

注释对比

  • 和C语言一样,Go语言也支持单行注释和多行注释, 并且所有注释的特性都和C语言一样
    • 单行注释 // 被注释内容
    • 多行注释 /* 被注释内容*/
  • 在Go语言中,官方更加推荐使用单行注释,而非多行注释(详情可以直接查看Go官方源码)

运算符对比

  • 算数运算符和C语言几乎一样
    • 错误写法: a = i++;  return i++;
    • 错误写法: ++i; –i;
    • Go语言中++、–运算符不支持前置
    • Go语言中++、–是语句,不是表达式,所以必须独占一行
运算符 描述 实例
+ 相加 A + B
相减 A – B
* 相乘 A * B
/ 相除 B / A
% 求余 B % A
++ 自增 A++
自减 A–

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

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

  • 位运算符和C语言几乎一样
    • 新增一个&^运算符
运算符 描述 实例
& 参与运算的两数各对应的二进位相与, 对应位只要都是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语言几乎一样
    • 新增一个&^=运算符
运算符 描述 实例
= 将右边赋值给左边 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

流程控制语句对比

  • C语言流程控制中的if、switch、for在Go语言都可以使用
  • C语言中的四大跳转语句return、break、continue、goto在Go语言都可以使用
  • Go语言除了实现C语言中if、switch、for、return、break、continue、goto的基本功能以外,还对if、switch、for、break、continue进行了增强
    • 例如: if 条件表达式前面可以添加初始化表达式
    • 例如: break、continue可以指定标签
    • 例如: switch语句可以当做if/elseif来使用
    • … …
  • 值得注意的是Go语言中没有while循环和dowhile循环, 因为它们能做的Go语言中的for循环都可以做

函数和方法对比

  • C语言定义函数格式
返回值类型 函数名称(形参列表) {
        函数体相关语句;
        return 返回值;
}
  • Go语言定义函数格式
func  函数名称(形参列表)(返回值列表) {
        函数体相关语句;
        return 返回值;
}
  • C语言中没有方法的概念, 但是Go语言中有方法
    • 对于初学者而言,可以简单的把方法理解为一种特殊的函数
func  (接收者 接受者类型)函数名称(形参列表)(返回值列表) {
        函数体相关语句;
        return 返回值;
}

编程思想对比

  • C语言是一门面向过程的编程语言
    • 1.上街买菜
    • 2.摘菜
    • 3.洗菜
    • 4.切菜
    • 5.开火炒菜
    • 6.淘米煮饭
    • 7.吃饭
    • 面向过程: 按部就班, 亲力亲为,关注的是我应该怎么做?
    • 做饭例子: 面向过程做饭
  • Go语言是门面向对象的编程语言
    • 面向对象:化繁为简, 能不自己干自己就不干,关注的是我应该让谁来做?
    • 做饭例子: 面向对象做饭
    • 1.找个会做饭女朋友 or 男朋友
    • 2.老婆我饿了 or 老公我饿了
    • 3.躺着…等她/他把饭做好
    • 4.吃饭
  • 不要把面向过程和面向对象想象得那么神奇, 它们只是思考问题的方式不同而已

其它新增特性

  • 接口
  • 并发
  • 反射
  • 异常处理
  • ….

Go语言SDK安装和配置

  • 什么是SDK

    • 软件开发工具包(外语首字母缩写:SDK、外语全称:**SoftwareDevelopmentKit)**一般都是一些软件工程师为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件时的开发工具的集合
    • 如果不安装SDK, 你可以编写Go语言代码, 但是你不能编译执行编写好的Go语言代码
  • 如何安装?

  • 1.下载SDK安装包。地址: https://golang.google.cn/dl/

    • 由于新版本一般不太稳定, 所以我们选择下载上一个版本图片
  • 2.运行图形化安装包图片图片图片图片图片图片

  • 3.检测配置环境变量图片图片图片

    • 用于告诉操作系统,我们把Go语言SDK安装到哪了图片
    • 3.1.添加GOROOT环境变量
  • 3.2.配置GOPATH环境变量
    • 用于告诉操作系统,将来我们要在哪里编写Go语言程序图片图片
  • 3.3.配置GoBin环境变量
    • 用于告诉操作系统,去哪查找Go语言提供的一些应用程序图片图片
  • 最终结果图片
  • 4.检查是否安装配置成功
    • 4.1打开CMD图片
    • 4.2输入go version图片
    • 4.3输入go env图片

安装Go语言开发工具

  • 记事本(开发效率极低)
  • Vim(初学者入门门槛高)
  • VSCode(不喜欢)
  • Sublime Test(不喜欢)
  • GoLand(喜欢,当收费)
  • LiteIDE(开源免费, 跨平台运行,轻量级)
  • 生男生女都一样, 最关键是你中意哪个就用哪个

Goland安装

  • 下载安装包: 点我下载Goland
  • 提取码:lm7v
  • 运行安装文件图片
  • 疯狂下一步图片图片图片图片图片
  • 激活程序: 自行淘宝JetBrains 激活(仅供学生党参考, 在职人员请支持正版)图片
  • 看不习惯英文的可以自行百度Goland汉化包
  • 打开项目文件夹图片图片图片图片
  • 测试开发工具是否安装正确图片图片图片图片图片

  • 其它问题:
    • 提示没有安装JVM图片
    • 下载Java SDK 点我下载图片
    • 安装即可

Go语言程序组成

  • 和C语言程序一样,Go语言程序也是由众多函数组成的
  • 和C语言程序一样,程序运行时系统会***自动调用***名称叫做***main的函数***
  • 和C语言程序一样,如果一个程序***没有主函数***,则这个程序***不具备运行能力***
  • 和C语言程序一样,一个Go语言程序***有且只能有一个主函数***

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

  • C语言main函数格式
int main(int argc, const char * argv[]) {
    return 0;
}
  • Go语言main函数格式
    • func 告诉系统这是一个函数
    • main主函数固定名称
    • 函数左括号必须和函数名在同一行
    • main函数必须在main包中
// 告诉系统当前编写的代码属于哪个包
package main
// 定义了一个名称叫做main的函数
func main() {
}

Go语言HelloWorld

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

Go语言HelloWorld和C语言HelloWorld异同

  • 1.文件类型不同
    • C语言代码保存在.c为后缀的文件中
    • Go语言代码保存在.go为后缀的文件中
  • 2.代码管理方式不同
    • 我们会把不同类型的代码放到不同的.go文件中,然后通过package给该文件指定一个包名
    • 需要使用时直接通过import导入对应的包名即可图片
    • C语言会把不同类型的代码放到不同的.c文件中, 然后再编写对应的.h文件
    • 需要使用时直接通过#include导入对应文件的.h文件即可图片
    • C语言程序用***文件***的方式管理代码
    • Go语言程序用的形式管理代码
  • 3.main函数书写文件不同
    • C语言中main函数可以写在任意文件中, 只要保证一个程序只有一个main函数即可
    • Go语言中main函数只能写在包名为main的文件夹中, 同样需要保存一个程序只有一个main函数
  • 4.函数编写的格式不同
    • 注意:C语言函数的左括号可以和函数名称在同一行, 也可以不在同一行
    • C语言中函数的格式为
返回值类型 函数名称(形参列表) {
        函数体相关语句;
        return 返回值;
}
  • Go语言函数定义格式 注意:Go语言函数的左括号必须和函数名称在同一行,否则会报错
func  函数名称(形参列表)(返回值列表) {
        函数体相关语句;
        return 返回值;
}
  • 5.函数调用的格式不同
    • C语言通过#include导入.h文件后,直接通过函数名称调用函数
    • Go语言通过import导入对应的包后,需要通过包名.函数名称的方式调用
#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)
}
  • 6.语句的结束方式不同
    • C语言中每条语句都必须以分号结尾
    • Go语言中每条语句后面不用添加分号(编译器会自动添加)
#include <stdio.h>
#include "calculate.h"
int main()
{
    int res = sum(2, 3); // 不写分号会报错
    printf("res = %d!\n", res); // 不写分号会报错
    return 0; // 不写分号会报错
}
package main
import (
 "fmt"
 "lesson_1/calculate"
)
func main() {
 res := calculate.Sum(2, 3) // 不用写分号
 fmt.Println("res1 = ", res) // 不用写分号
}

Go语言注释

  • 和C语言一样,Go语言也支持单行注释和多行注释, 并且所有注释的特性都和C语言一样
    • 单行注释 // 被注释内容
    • 多行注释 /* 被注释内容*/
  • 在Go语言中,官方更加推荐使用单行注释,而非多行注释(详情可以直接查看Go官方源码)

Go语言编码风格

  • 1.go程序编写在.go为后缀的文件中
  • 2.包名一般使用文件所在文件夹的名称
  • 2.包名应该简洁、清晰且全小写
  • 3.main函数只能编写在main包中
  • 4.每一条语句后面可以不用编写分号(推荐)
  • 5.如果没有编写分号,一行只能编写一条语句
  • 6.函数的左括号必须和函数名在同一行
  • 7.导入包但没有使用包编译会报错
  • 8.定义局部变量但没有使用变量编译也会报错
  • 9.定义函数但没有使用函数不会报错
  • 10.给方法、变量添加说明,尽量使用单行注释

关键字

  • Go语言中的关键字和C语言中的关键字的含义样, 是指被Go语言赋予特殊含义的单词
  • Go语言中关键字的特征和C语言也一样
    • 全部都是小写
    • 在开发工具中会显示特殊颜色
  • Go语言中关键字的注意点和C语言也一样
    • 因为关键字在C语言中有特殊的含义, 所以不能用作变量名、函数名等

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

  • C语言中一共有32个关键字
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
  • Go语言中一共有25个关键字
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

  • Go语言中除了关键字以外,还有30多个预定义标识符
内建常量
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语言中的标识符的含义样, 是指程序员在程序中自己起的名字(变量名称、函数名称等)

  • 和C语言一样Go语言标识符也有一套命名规则, Go语言标识符的命名规则几乎和C语言一模一样

    • 只能由字母(a~z、 A~Z)、数字、下划线组成
    • 不能包含除下划线以外的其它特殊字符串
    • 不能以数字开头
    • 不能是Go语言中的关键字
    • 标识符严格区分大小写, test和Test是两个不同的标识符
  • 和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语言一样,标识符除了有命名规则以外,还有标识符命名规范
    • 驼峰命名: sendMessage / sayHello
    • _命名: send_message / say_hello
    • 规则必须遵守, 规范不一定要遵守, 但是建议遵守
    • Go语言的命名规范和C语言一样, 都是采用驼峰命名, 避免采用_命名

Go语言数据类型

  • Go语言本质是用C语言编写的一套高级开发语言, 所以Go语言中的数据类型大部分都是由C语言演变而来的

  • C语言数据类型图片

  • Go语言数据类型图片


  • C语言各数据类型占用内存空间
类型 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
  • Go语言各数据类型占用内存空间
类型 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类型的整型
  • 和C语言一样,Go语言也提供了Sizeof计算变量的内存空间
    • 1.导入import “unsafe”包
    • 2.通过unsafe.Sizeof()计算变量内存空间
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语言基本数据类型内部实现
    • 越老版本的代码越纯粹,越适合新手学习
    • 随着代码的更新迭代会逐步变得非常复杂, 所以此处建议下载1.4版本
    • golang官方网站下载go1.4版本源代码
    • 解压后打开路径: 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


Go语言变量

  • Go语言中变量的概念和C语言中也一样, 所以我们直接来看下如何定义和使用变量即可
  • C语言中定义变量的格式
数据类型 变量名称;
数据类型 变量名称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;
}
  • Go语言中定义变量有三种格式
// 标准格式
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)
}
  • 和C语言一样,除了可以定义单个变量以外,还支持一次性定义多个变量

    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)
    }
    
    • 方式二, 变量组
    • 方式一, 连续定义

Go语言变量定义注意点

  • 简短模式的含义是定义的同时初始化
package main
import "fmt"
func main() {
 num := 10
 num := 20 // 编译报错, 重复定义
 fmt.Println("num = ", num)
}
  • 一定不要把:=当做赋值运算符来使用
package main
import "fmt"
var num = 10 // 定义一个全局变量
func main() {
 num := 20 // 定义一个局部变量
 fmt.Println("num = ", num)
        test()
}
func test() {
 fmt.Println("num = ", num) // 还是输出10
}

  • :=只能用于定义局部变量,不能用于定义全局变量
package main
import "fmt"
num := 10 // 编译报错
func main() {
 fmt.Println("num = ", num)
}
  • 使用:=定义变量时,不能指定var关键字和数据类型
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)
}

  • C语言中全局变量没有赋值,那么默认初始值为0, 局部变量没有赋值,那么默认初始值是随机值
  • Go语言中无论是全局变量还是局部变量,只要定义了一个变量都有默认的0值
    • int/int8/int16/int32/int64/uint/uint8/uint16/uint32/uint64/byte/rune/uintptr的默认值是0
    • float32/float64的默认值是0.0
    • bool的默认值是false
    • string的默认值是””
    • pointer/function/interface/slice/channel/map/error的默认值是nil
    • 其它复合类型array/struct默认值是内部数据类型的默认值
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}
}

数据类型转换

  • C语言中数据可以隐式转换或显示转换, 但是Go语言中数据只能显示转换
  • C语言隐式转换
#include <stdio.h>
int main(){
  // 隐式转换:自动将实型10.6转换为整型后保存
   int a = 10.6;
 // 自动类型提升: 运算时会自动将小类型转换为大类型后运算
  double b = 1.0 / 2; // 等价于1.0 / 2.0
}
  • C语言显示转换(强制转换)
#include <stdio.h>
int main(){
  // 显示转换:强制将实型10.6转换为整型后保存
  int a = (int)10.5;
}
  • Go语言数值类型之间转换

    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)
    }
    
    • 格式: 数据类型(需要转换的数据)
    • 注意点: 和C语言一样数据可以从大类型转换为小类型, 也可以从小类型转换为大类型. 但是大类型转换为小类型可能会丢失精度

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

  • Go语言中不能通过 数据类型(变量)的格式将数值类型转换为字符串, 也不能通过 数据类型(变量)的格式将字符串转换为数值类型
 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)
  }
  • 字符串类型转换为数值类型时,如果不能转换除了返回error以外,还会返回对应类型的默认值
 package main
 import "fmt"
 func main() {
  var str1 string = "abc"
  num1, _ := strconv.ParseInt(str1, 10, 32)
  fmt.Println(num1) // 0

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

  num3, _ := strconv.ParseBool(str1)
  fmt.Println(num3) // false
 }
  • 看完上面的代码有没有种想打人的感觉? 如果有那么请继续往下看
  • 字符串类型和整型快速转换
   package main
   import "fmt"
   func main() {
    var num1 int32 = 110
    // 快速将整型转换为字符串类型
    // 注意:Itoa方法只能接受int类型
    var str1 string = strconv.Itoa(int(num1))
    fmt.Println(str1)

    var str2 string = "666"
    // 快速将字符串类型转换为整型
    // 注意: Atoi方法返回两个值, 一个值是int,一个值是error
    // 如果字符串能被转换为int,那么error为nil, 否则不为nil
    num2, err := strconv.Atoi(str2)
    if err != nil{
     fmt.Println(err)
    }
    fmt.Println(num2)
   }
  • 数值类型转字符串类型其它方式

    package main
    import "fmt"
    func main() {
     var num1 int32 = 110
     // Sprintf函数和Printf函数很像, 只不过不是输出而将格式化的字符串返回给我们
     var str1 string = fmt.Sprintf("%d", num1)
     fmt.Println(str1)
    
     var num2 float32 = 3.14
     var str2 string = fmt.Sprintf("%f", num2)
     fmt.Println(str2)
    
     var num3 bool = true
     var str3 string = fmt.Sprintf("%t", num3)
     fmt.Println(str3)
    }
    

Go语言常量

  • 和C语言一样Go语言中的常量也分为整型常量实型常量字符常量字符串常量自定义常量

  • 自定义常量

      #include <stdio.h>
      int main(int argc, const char * argv[])
      {
          const float PI = 998;
          PI = 110; // 报错
          printf("PI = %d\n", PI );
          return 0;
      }
    
    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)
    }
    
    • 除此之外Go语言还支持一次性定义多个常量
    • Go语言自定义常量: const 常量名称 数据类型 = 值or  const 常量名称 = 值
    • C语言自定义常量: const 数据类型 常量名称 = 值;

  • Go语言自定义常量注意点

    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)
    }
    
    • 在常量组中, 如果上一行常量有初始值,但是下一行没有初始值, 那么下一行的值就是上一行的值
    • 定义的局部变量或者导入的包没有被使用, 那么编译器会报错,无法编译运行
    • 但是定义的常量没有被使用,编译器不会报错, 可以编译运行

  • 枚举常量
    • C语言中枚举类型的本质就是整型常量
    • Go语言中没有C语言中明确意义上的enum定义, 但是可以借助iota标识符来实现枚举类型
  • C语言枚举格式:
 enum 枚举名 {
    枚举元素1,
    枚举元素2,
    … …
 };
  • C语言枚举中,如果没有指定初始值,那么从0开始递增

    #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;
    }
    
    • C语言枚举中, 如果指定了初始值,那么从指定的数开始递增

  • Go语言实现枚举格式
const(
  枚举元素1 = iota
  枚举元素2 = iota
  ... ...
)
  • 利用iota标识符标识符实现从0开始递增的枚举

    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
    }
    
  • iota注意点:

    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
    }
    
    • iota自增默认数据类型为int类型, 也可以显示指定类型
    • iota也支持常量组+多重赋值, 在同一行的iota值相同
    • 在同一个常量组中,如果iota被中断, 那么必须显示恢复
    • 在同一个常量组中,iota从0开始递增, 每一行递增1
    • 在同一个常量组中,只要上一行出现了iota,那么后续行就会自动递增
  • Go语言fmt包实现了类似C语言printf和scanf的格式化I/O, 格式化动作源自C语言但更简单 ##输出函数

  • func Printf(format string, a …interface{}) (n int, err error)

    package main
    import "fmt"
    func main() {
      name := "微信搜索:代码情缘"
      age := 33
      fmt.Printf("name = %s, age = %d\n", name, age) // name = lnj, age = 33
    }
    
    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
    }
    
    • Go语言Printf函数其它特性,如宽度、标志、精度、长度、转移符号等,和C语言一样.
    • Go语言中输出某一个值,很少使用%d%f等, 一般都使用%v即可
    • 输出复合类型时会自动生成对应格式后再输出
    • 除此之外,Go语言还增加了%v控制符,用于打印所有类型数据
    • 除此之外,Go语言还增加了%T控制符, 用于输出值的类型
    • 值得注意的是,输出十进制只能通过%d,不能像C语言一样通过%i
    • 除了和C语言一样,可以通过%o、%x输出八进制和十六进制外,还可以直接通过%b输出二进制
    • 和C语言用法几乎一模一样, 只不过新增了一些格式化符号

  • func Println(a …interface{}) (n int, err error)

    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}
    }
    
    • 输出之后在结尾处添加换行
    • 传入多个参数时, 会自动在相邻参数之间添加空格
    • 传入符合类型数据时, 会自动生成对应格式后再输出
    • 采用默认格式将其参数格式化并写入标准输出,

  • func Print(a …interface{}) (n int, err error)

    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}
    }
    
    • 输出之后不会在结尾处添加换行
    • 传入多个参数时, 只有两个相邻的参数都不是字符串,才会在相邻参数之间添加空格
    • 传入符合类型数据时, 会自动生成对应格式后再输出
    • 和Println几乎一样

  • 以下三个函数和Printf/Println/Print函数一样, 只不过上面三个函数是输出到标准输出, 而下面三个函数可以通过w指定输出到什么地方
  • func Fprintf(w io.Writer, format string, a …interface{}) (n int, err error)
  • func Fprintln(w io.Writer, a …interface{}) (n int, err error)
  • func Fprint(w io.Writer, a …interface{}) (n int, err error)
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)
}

  • 以下三个函数和Printf/Println/Print函数一样, 只不过上面三个函数是输出到标准输出, 而下面三个函数不会输出,而是将字符串返回给我们
  • func Sprintf(format string, a …interface{}) string
  • func Sprint(a …interface{}) string
  • func Sprintln(a …interface{}) string
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)
}

输入函数

  • func Scanf(format string, a …interface{}) (n int, err error)

    package main
    import "fmt"
    func main() {
      var num1 int
      var num2 int
      fmt.Scanf("%d%d", &num1, &num2)
      fmt.Println(num1, num2)
    }
    
    • 和C语言用法几乎一模一样, 但是只能输入一行数据
  • func Scan(a …interface{}) (n int, err error)

    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)
    }
    
    • 和C语言scanf函数几乎一样, 只不过不用指定格式化字符串
  • func Scanln(a …interface{}) (n int, err error)

    package main
    import "fmt"
    func main() {
      var num1 int
      var num2 int
      fmt.Scanln(&num1, &num2)
      fmt.Println(num1, num2)
    }
    
    • 和C语言用法几乎一模一样, 只不过不用指定格式化字符串, 并且只能输入一行数据

  • 以下三个函数和Scan/Scanln/Scanf函数一样, 只不过上面三个函数是从标准输入读取数据, 而下面三个函数可以通过r指定从哪读取数据
  • func Fscanf(r io.Reader, format string, a …interface{}) (n int, err error)
  • func Fscanln(r io.Reader, a …interface{}) (n int, err error)
  • func Fscan(r io.Reader, a …interface{}) (n int, err error)
  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)
  }
  • 以下三个函数和Scan/Scanln/Scanf函数一样, 只不过上面三个函数是从标准输入读取数据, 而下面三个函数是从字符串中读取数据
  • func Sscan(str string, a …interface{}) (n int, err error)
  • func Sscanf(str string, format string, a …interface{}) (n int, err error)
  • func Sscanln(str string, a …interface{}) (n int, err error)
  package main
  import "fmt"
  func main() {
 str := "lnj 33"
 var name string
 var age int
 //fmt.Sscanf(str, "%s %d",&name, &age)
 //fmt.Sscanln(str,&name, &age)
 fmt.Sscan(str,&name, &age)
 fmt.Println("name =",name, "age =",age)
  }

go命令行操作指令

  • 标准go语言项目文件目录格式
    • main文件夹是专门用于存储package main包相关源码文件的
    • 其它文件夹是专门用于存储除package main包以外源码文件的
    • 项目文件夹就是GOPATH指向的文件夹
    • src文件夹是专门用于存放源码文件的
    • bin文件夹是专门用于存储编译之后的可执行程序的
    • pag文件夹是专门用于存储编译之后的.a文件的

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


  • go version 查看当前安装的go版本
  • go env 查看当前go的环境变量
  • go fmt 格式化代码
    • 会将指定文件中凌乱的代码按照go语言规范格式化
  • go run 命令文件 编译并运行go程序
    • package main包中包含main函数的文件, 我们称之为命令文件
    • 其它包中的文件, 我们称之为源码文件
  • go build 编译检查
    • 对于非命令文件只会执行编译检查, 不会产生任何文件
    • 对于命令文件除了编译检查外,还会在当前目录下生成一个可执行文件
    • 对应只想编译某个文件, 可以在命令后面指定文件名称go build 文件名称
  • go install 安装程序
    • 对于非命令文件会执行编译检查, 并生成.a结尾的包, 放到 $GOPATH/pkg目录中
    • 对于命令文件会执行编译检查, 并生成可执行程序, 放到$GOPATH/bin目录中

通过os包获取命令行参数

  • C语言中的命令行参数
    • argc: argv中保存数据的个数
    • argv: 默认情况下系统只会传入一个值, 这个值就是main函数执行文件的路径
    • 我们可以通过配置开发工具,或者命令行运行时以空格+参数形式传递其它参数
    • 注意点: 无论外界传入的是什么类型, 我们拿到的都是字符串类型
#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;
}
图片
  • Go语言中的命令行参数
    • Go语言中main函数没有形参, 所以不能直接通过main函数获取命令行参数
    • 想要获取命令行参数必须导入os包, 通过os包的Args获取
    • 注意点: 无论外界传入的是什么类型, 我们拿到的都是字符串类型
package main

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

通过flag包获取命令行参数

  • Go语言中除了可以通过os包获取命令行参数以外,还可以通过flag包获取命令行参数
package main
import (
 "flag"
 "fmt"
)
func main() {
 /*
    flag.Xxxx(name, value, usage)
 第一个参数: 命令行参数名称
 第二个参数: 命令行参数对应的默认值
 第三个参数: 命令行参数对应的说明
 */
 // 1.设置命令行参数
 name := flag.String("name", "lnj", "请输入人的姓名")
 age := flag.Int("age", 33, "请输入人的年龄")
 // 2.将命令行参数解析到注册的参数
 flag.Parse()
 // 3.使用命令行参数
 // 注意flag对应方法返回的都是指针类型, 所以使用时必须通过指针访问
 fmt.Println("name = ", *name)
 fmt.Println("age = ", *age)
}
图片
  • flag获取命令行参数第二种写法
package main
import (
 "flag"
 "fmt"
)
func main() {
 /*
 flag.Xxxx(*type, name, value, usage)
 第一个参数:保存命令行参数变量地址
 第二个参数: 命令行参数名称
 第三个参数: 命令行参数对应的默认值
 第四个参数: 命令行参数对应的说明
 */
 // 1.定义变量,保存命令行参数的值
 var name string
 var age int
 // 2.设置命令行参数
 flag.StringVar(&name, "name", "lnj", "请输入人的姓名")
 flag.IntVar(&age, "age", 33,"请输入人的姓名")
 // 3.注册解析命令行参数
 flag.Parse()
 // 4.使用命令行参数
 fmt.Println("name = ", name)
 fmt.Println("age = ", age)
}

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

  • 通过os包获取命令行参数
    • 如果用户没有传递参数报错
    • 需要严格按照代码中的顺序传递参数, 否则造成数据混乱
    • 不能指定参数的名称
    • 获取到的数据都是字符串类型
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)
}
图片

算数运算符

  • 算数运算符和C语言几乎一样
运算符 描述 实例
+ 相加 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)

 // ... ... 其它以此类推
}
  • Go语言中++、–运算符不支持前置
    • 错误写法: ++i; –i;
  • Go语言中++、–是语句,不是表达式,所以必须独占一行
    • 错误写法: a = i++;  return i++;
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)
}
  • Go语言中字符串支持利用+号进行拼接
package main
import "fmt"
func main() {
 str := "abc" + "def"
 //fmt.Println(str)
}

关系算符

  • 关系算符和C语言一样
运算符 描述 实例
== 检查两个值是否相等,如果相等返回 True 否则返回 False。 A == B
!= 检查两个值是否不相等,如果不相等返回 True 否则返回 False。 A != B
> 检查左边值是否大于右边值,如果是返回 True 否则返回 False。 A > B
< 检查左边值是否小于右边值,如果是返回 True 否则返回 False。 A < B
>= 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。 A >= B
<= 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。 A <= B
  • 注意点:
    • 和C语言不通的是, Go语言中关系运算符只能返回true和false

逻辑运算符

  • 逻辑运算符和C语言一样
运算符 描述 实例
&& 如果两边的操作数都是 True,则条件 True,否则为 False。 A && B
\|\| 如果两边的操作数有一个 True,则条件 True,否则为 False。 A || B
! 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 !A
  • 注意点:
    • 和C语言不通的是, Go语言中关系运算符只能返回true和false
    • 逻辑非只能用于true和false

位运算符

  • 位运算符和C语言几乎一样
运算符 描述 实例
& 参与运算的两数各对应的二进位相与, 对应位只要都是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语言几乎一样
    • 新增一个&^=运算符
运算符 描述 实例
= 将右边赋值给左边 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)) // 编译报错
}

运算符优先级

  • 和C语言一样, 只需记住()优先级最高即可

Go语言流程控制基本概念

  • Go语言流程控制和C语言一样, 也有三大流程控制结构
    • 顺序结构(默认结构)
    • 选择结构(if / switch)
    • 循环结构(for)

选择结构if

  • 和C语言不同的的是
    • 条件表达式的值必须是布尔类型(Go语言中没有非零即真的概念)
    • 条件表达式前面可以添加初始化语句
    • 不需要编写圆括号
    • 左大括号必须和条件语句在同一行
  • 第一种格式:
    • 条件表达式结果为true,那么执行if后面{}中代码
if 初始化语句; 条件表达式{
    语句块;
}
package main
import "fmt"
func main() {
 // 如果后续需要用到age变量, 可以将变量放到if外面
 age := 18
 if age >= 18{
  fmt.Println("成年人")
 }
}
package main
import "fmt"
func main() {
 // 如果后续不需要用到age变量, 可以将变量放到条件表达式前面
 if age := 18; age >= 18{
  fmt.Println("成年人")
 }
}

  • 第二种格式:
    • 条件表达式结果为true,那么执行if后面{}中代码
    • 否则执行else后面{}中代码
if 初始化语句; 条件表达式{
    语句块;
}else{
    语句块;
}
package main
import "fmt"
func main() {
 if age := 18;age >= 18 {
  fmt.Println("成年人")
 }else{
  fmt.Println("未成年人")
 }
}
  • 第三种格式:
    • if后面条件表达式结果为true,那么执行if后面{}中代码
    • 否则判断else if条件表达式是否为true,为true执行else if后面{}中代码
    • 否则依次判断后续else if条件表达式是否为true,哪个为true就执行哪个else if后面{}中代码
    • 都不满足则执行else后面{}中代码
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("未成年人")
 }
}
  • 值得一提的是Go语言中没有C语言中的三目运算符, 所以C语言中三目能干的在Go语言中都只能通过if else的形式来完成

选择结构switch

  • 和C语言不同的的是
    • 和if一样,表达式前面可以添加初始化语句
    • 和if一样,不需要编写圆括号
    • 和if一样,左大括号必须和表达式在同一行
    • case表达式的值不一定要是常量, 甚至可以不用传递
    • 一个case后面可以有多个表达式, 满足其中一个就算匹配
    • case后面不需要添加break
    • 可以在case语句块最后添加fallthrough,实现case穿透
    • case后面定义变量不需要添加{}明确范围
  • 格式
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...")
 }
}
  • 注意点:
    • case后面不用编写break, 不会出现case穿透问题
    • 如果想让case穿透,必须在case语句块最后添加fallthrough关键
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...")
 }
}
  • case后面不仅仅可以放常量,还可以放变量和表达式
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的数")
 }
}
  • case后面定义变量不用添加{}明确作用于范围
package main
import "fmt"
func main() {
    switch num := 1;num {
   case 1:
  value := 10 // 不会报错
  fmt.Println(value)
   default:
  fmt.Println("Other...")
 }
}
  • 其它特性和C语言一样

循环结构for

  • 和C语言不同的的是
    • 和if一样,条件表达式的值必须是布尔类型
    • 和if一样,不需要编写圆括号
    • 和if一样,左大括号必须和表达式在同一行
  • 格式:
for 初始化表达式;循环条件表达式;循环后的操作表达式 {
    循环体语句;
}
package main
import "fmt"
func main() {
 for i:=0; i<10; i++{
  fmt.Println(i)
 }
}
  • Go语言中没有while/dowhile循环, 所以可以通过如下格式实现C语言中while循环用法
package main
import "fmt"
func main() {
 i:=0
 for i<10 {
  fmt.Println(i)
  i++
 }
}
  • 最简单死循环
package main
import "fmt"
func main() {
 for{
  fmt.Println("根本停不下来")
 }
}
  • 除了实现基本的循环结构以外,Go语言还实现了一种高级for循环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)
 }
}

四大跳转

  • 和C语言一样,Go语言中也有四大跳转语句, 分别是return、break、continue、goto
  • break语句
    • Go语言中的break语句可以用于,立即跳出switch、for和select
    • 但不同的是Go语言中的break语句可以指定标签
package main
import "fmt"
func main() {
 for i:=0; i<10; i++{
  if(i == 5){
   break // 跳出所在循环
  }
  fmt.Println(i)
 }
}
  • 利用break跳转到指定标签
    • 标签必须在使用之前定义
    • 标签后面只能跟switch和循环语句, 不能插入其它语句
    • 跳转到标签之后switch和循环不会再次被执行
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")
}
  • continue语句
    • Go语言中的continue语句可以用于,立即进入下一次循环
    • 但不同的是Go语言中的continue语句可以指定标签
package main
import "fmt"
func main() {
 for i:=0; i<5; i++{
  if (i == 2) {
   continue
  }
  fmt.Println(i)
 }
}
  • 利用continue 跳转到指定标签
    • 标签必须在使用之前定义
    • 标签后面只能跟循环语句, 不能插入其它语句
    • 对于单层循环和直接编写continue一样
    • 对于多层循环,相当于跳到最外层循环继续判断条件执行
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")
}
  • goto语句
    • Go语言中的goto和C语言中用法一样, 用于在同一个函数中瞎跳
package main
import "fmt"
func main() {
 num := 1
outer:
 if(num <= 10){
  fmt.Println(num)
  num++
  goto outer // 死循环
 }
 fmt.Println("come here")
}
package main
import "fmt"
func main() {
 num := 1
 if(num <= 10){
  fmt.Println(num)
  num++
  goto outer // 死循环
 }
outer:
 fmt.Println("come here")
}
  • Go语言中的return语句和C语言一模一样,都是用于结束函数,将结果返回给调用者

函数

  • Go语言和C语言一样也有函数的概念, Go语言中函数除了定义格式和不用声明以外,其它方面几乎和C语言一模一样
  • 格式:
func 函数名称(形参列表)(返回值列表){
    函数体;
}
  • 无参数无返回值函数
func say()  {
 fmt.Println("Hello World!!!")
}
  • 有参数无返回值函数
func say(name string)  {
 fmt.Println("Hello ", name)
}
  • 无参数有返回值函数
func sum() int { // 只有一个返回值时,返回值列表的()可以省略
 return 1 + 1
}
  • 有参数有返回值函数
func sum(a int, b int) int {
 return a + b
}

和C语言函数差异

  • 和C语言不同的是,Go语言中可以给函数的返回值指定名称
// 给返回值指定了一个名称叫做res, return时会自动将函数体内部res作为返回值
// 其实本质就是提前定义了一个局部变量res, 在函数体中使用的res就是这个局部变量,返回的也是这个局部变量
func sum() (res int) { 
 res = 1 + 1
 return
}
  • 和C语言不同的是,Go语言中的函数允许有多个返回值函数
func calculate(a int, b int) (sum int, sub int) {
 sum = a + b
 sub = a - b
 return
}
  • 相邻同类型形参OR返回值类型可以合并, 可以将数据类型写到最后一个同类型形参OR返回值后面
// a, b都是int类型, 所以只需要在b后面添加int即可
func calculate(a, b int) (sum, sub int) {
 sum = a + b
 sub = a - b
 return
}
  • 和C语言不同的是Go语言中的函数不需要先声明在使用
package main
import "fmt"
func main() {
 say();
}
func say()  { // 在后面定义也可以在前面使用
 fmt.Println("Hello World!!!")
}

值传递和引用传递

  • Go语言中值类型有: 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"
}
  • Go语言中引用类型有: 指针、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"
}

匿名函数

  • 匿名函数也是函数的一种, 它的格式和普通函数一模一样,只不过没有名字而已

    • 普通函数的函数名称是固定的, 匿名函数的函数名称是系统随机的
  • 匿名函数可以定义在函数外(全局匿名函数),也可以定义在函数内(局部匿名函数), Go语言中的普通函数不能嵌套定义, 但是可以通过匿名函数来实现函数的嵌套定义

    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
      }
    }
    
    • 只要闭包还在使用外界的变量, 那么外界的变量就会一直存在
    • 闭包中使用的变量和外界的变量是同一个变量, 所以可以闭包中可以修改外界变量
    • 也就是说只要匿名函数中用到了外界的变量, 那么这个匿名函数就是一个闭包

延迟调用

  • Go语言中没有提供其它面向对象语言的析构函数, 但是Go语言提供了defer语句用于实现其它面向对象语言析构函数的功能
  • defer语句常用于释放资源解除锁定以及错误处理
    • 例如C语言中我们申请了一块内存空间,那么不使用时我们就必须释放这块存储空间
    • 例如C语言中我们打开了一个文件,那么我们不使用时就要关闭这个文件
    • 例如C语言中我们打开了一个数据库, 那么我们不使用时就要关闭这个数据库
    • 这一类的操作在Go语言中都可以通过defer语句来完成
  • 无论你在什么地方注册defer语句,它都会在所属函数执行完毕之后才会执行, 并且如果注册了多个defer语句,那么它们会按照后进先出的原则执行
    • 正是因为defer语句的这种特性, 所以在Go语言中关闭资源不用像C语言那样用完了再关闭, 我们完全可以打开的同时就关闭, 因为无论如何defer语句都会在所属函数执行完毕之后才会执行
  package main
  import "fmt"
  func main() {
 defer fmt.Println("我是第一个被注册的") // 3
 fmt.Println("main函数中调用的Println") // 1
 defer fmt.Println("我是第二个被注册的") // 2
  }

init函数

  • golang里面有两个保留的函数:
    • init函数(能够应用于所有的package)
    • main函数(只能应用于package main)
    • 这两个函数在定义时不能有任何的参数和返回值
  • go程序会自动调用init()和main(),所以你不能在任何地方调用这两个函数
  • package main必须包含一个main函数, 但是每个package中的init函数都是可选的
  • 一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数
  • 单个包中代码执行顺序如下
    • 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函数的作用

    • init函数用于处理当前文件的初始化操作, 在使用某个文件时的一些准备工作应该放到这里

数组

  • 和C语言一样,Go语言中也有数组的概念, Go语言中的数组也是用于保存一组相同类型的数据
  • 和C语言一样,Go语言中的数组也分为一维数组多维数组

一维数组

  • 格式: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]
    }
    
    • …会根据初始化元素个数自动确定数组长度
    • 和C语言一样,Go语言中如果定义数组的同时初始化,那么元素个数可以省略,但是必须使用...来替代
    • 和C语言中的数组不同,Go语言中数组除了可以定义的同时初始化以外,还可以先定义再一次性初始化
    • 和C语言一样,Go语言中的数组也提供了好几种初始化方式
    • 和C语言中数组不同, Go语言中数组定义之后就有默认的初始值
    • 默认初始值就是保存数据类型的默认值(零值)
  • Go语言中数组的访问和使用方式和C语言一样都是通过数组名称[索引]的方式

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])
}
  • 遍历数组
    • Go语言中提供了两种遍历数组的方式, 一种是通过传统for循环遍历, 一种是通过for…range循环遍历
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]
     }
    
    • Go语言中的数组是值类型, 赋值和传参时会复制整个数组
    • 如果元素类型支持==、!=操作时,那么数组也支持此操作
    • Go语言中数组长度也是数据类型的一部分

二维数组

  • 用法和C语言数组一样, 只是创建的格式不同
  • 格式: [行数][列数]类型
  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]]
   }

最后,码字不易,点赞,在看,关注,转发、一键四连支持。

发表评论

邮箱地址不会被公开。 必填项已用*标注