1、指针、指针的概念、指针变量的定义、取地址运算符 &、无类型指针、
指针占用内存的说明、野指针 与 空指针、空指针理解的扩展、指针的兼容性(即指针类型之间一定要匹配)、
不同的数据类型在内存中占用的地址、指向常量的指针 和 指针常量、指针与数组的关系、指针运算、
通过指针使用数组元素、不同类型的指针的区别以及与数组的关系、小案例:int类型与ip地址的对应关系
使用指针给二维数组排序、
2、指针数组、二级指针(指向指针的指针)、三级指针及其以上指针、函数的参数为指针变量时(指针变量作为函数的参数)、
函数的参数为数组名时(即数组名作为函数的参数)、函数的返回值为指针时(即指针作为函数的返回值)、
3、字符指针 与 字符串、通过指针访问字符串数组、通过指针使得字符串逆置、函数的参数为char *(即char *作为函数的参数)、
自定义函数实现求字符串长度和字符串拷贝、例外:如果函数的参数是一个字符串时,那么并不需要再传递一个参数说明这个字符串有多长、
4、指针数组作为main函数的形参、举个小例子:用到main函数的参数,实现计算两个数的和、
课后作业写一个程序,需要用到main函数的参数、
C语言里面的test()和test(void)是不一样的。什么也不写的话,C语言就比较含糊了,容易出错,结果不可知。
c语言几个松散的地方(不足的地方,不严禁的地方,它容易出错的地方)。
写一个函数求字符串的长度。课后思考,用递归函数实现求字符串长度。
指针是c语言里面最抽象的、最重要的、最常用的。
指针变量也是一个变量,
指针存放的内容是一个地址,该地址指向一块内存空间,
指针是一种数据类型(指针类型)。
计算机内存的最小单位是什么?BYTE(字节)
对于内存,每个BYTE(字节)都有一个唯一不同的编号,这个编号就是内存地址。
操作系统就给内存的每一个字节编了一个号,所以说:一个编号对应的是一个BYTE(字节)的空间大小。
地址的编号:在32位系统下是一个4个字节的无符号整数;在64位系统下是一个8个字节的无符号整数。
(因为地址不可能是负的,又因为无符号数可以表达一个更大的地址,有符号数表示的最大地址会变小)
& 可以取得一个变量在内存当中的地址。(取地址取的是内存地址)
定义一个指针变量,但不指定它指向具体哪种数据类型。可以通过强制转化将 void * 转化为其他类型指针,
也可以用 (void *) 将其他类型强制转化为void类型指针。
1 linux下示例代码如下: 5 int *p; //定义了一个可以指向int类型地址的指针变量,指针变量的名字叫p。*不是指针变量名字的一部分。 13 //而且注意:每一次执行该代码后,输出的编号都会发生变化! 15 *p = 10; //通过指针变量间接的访问a的值,*p代表指针指向变量的值,p代表指向变量的地址。
在同一个系统下,不管指针指向什么样类型的变量,地址的大小(或叫编号的大小)总是一样的。
1 linux下示例代码如下: 11 //地址的编号:在32位系统下是一个4个字节的无符号整数;在64位系统下是一个8个字节的无符号整数。 12 //指针变量的名字叫p1、p2、p3。指针变量的大小是多大呢?因为指针变量对应的是某某的首地址的编号, 13 //即指针变量对应的是编号,而编号就是内存地址。即编号在64位系统下是一个8个字节的无符号整数。 14 //所以指针变量的大小就是编号的大小,而编号在64位系统下用8个字节的无符号整数表示。 15 //举例子说明下:同一个酒店,房间的编号的长度都是一样的。
1 linux下示例代码如下: 15 //*p2是什么?不管是*p1还是*p2都代表变量a的值,但p1和p2确实是两个不同的指针变量。
野指针:没有指向任何有效地址的指针变量,所以在代码中避免出现野指针,
如果一个指针不能确定指向任何一个变量地址,那么就将这个指针变成空指针。
1 linux下示例代码如下: 8 *p = 100; //不能这样写,没有初始化过值的指针,这种指针叫野指针。 9 return 0; //因为地址编号所占用的内存不是你程序要调用的内存。对于操作系统而言,不是你的内存你就不能改! 10 //如果你非要改的话,操作系统就会发现你在做非法操作,会直接把你清理出去了。即程序出错。
1 linux下示例代码如下: 8 p = NULL; //如果一个指针变量没有明确的指向一块内存,那么就把这个指针变量指向NULL。 9 //这个指针就是空指针,空指针是合法的。 11 //NULL在c语言里面就是一个宏常量,值是0。那么我们为什么不直接写0呢? 12 //NULL代表的是空指针,而不是一个整数零,这样看的会舒服些。(这只是粗浅易懂的解释)
NULL就是系统定义特殊的0,把你初始化的指针指向它,可以防止“野指针”的恶果。
NULL是个好东西,给一出生的指针一个安分的家。
用C语言编程不能不说指针,说道指针又不能不提NULL,那么NULL究竟是个什么东西呢? C语言中又定义,定义如下:
所以我觉得,如果一个指针被赋予NULL,应该就相当于这个指针执行了0x0000这个逻辑地址,
但是C语言中0x0000这个逻辑地址用户是不能使用的,
(有些人说是因为0x0000没有映射到物理地址,也有人说是因为0x0000映射到的地址是操作系统用于判断野指针的,我也不太懂,总之就是用户不能使用啦)
所以当你试图取一个指向了NULL的指针的内容(或者叫值)时,就会提示段错误,听着有点绕,看程序:
*node的意思是:取指针变量node的值,也就是逻辑地址0x0000,而这个地址是不能被访问的(即不能被取出来的),
c语言语法上没有问题,所以编译器编译没有问题,但是编译器编译后运行会出现段错误。
1 linux下示例代码如下:
指针之间赋值比普通数据类型赋值检查更为严格,例如:不可以把一个 double * 赋值给int。
我们不要把指针想象的特别神秘!其实指针变量也是一个变量。
它里面放的就是一个地址的编号,地址的编号就是一个8个字节的无符号的整数(64位系统下)。
区别是:这个整数不能直接赋值,而是来自于对另外一个变量的取地址操作而得到!
1 linux下示例代码如下:
1 linux下示例代码如下:
每一次编译后执行,输出的地址会发生变化,但是相邻地址间的间隔不变。
其余的类型就不一一举例啦!
int *const p; //定义一个指针常量,一旦指向某一变量的地址后,不可再指向其他变量的地址。(注意:指针常量也叫常量指针)
const int *p; //p是一个变量,但指向一个常量。(即p可以指向任何地址,但是只能通过*p来读这块地址的内容,不能通过*p来写这块地址的内容)
int *const p; //p是一个常量,但指向一个变量或者常量。(即如果一旦p指向了任何一个有效的地址后,就不可再指向其他变量的地址,但可以通过*p来读写这块地址的内容)
1 linux下示例代码如下: 8 int *p = &a; //此时的p指向了一个int类型的地址,可以通过*p的方式来修改这个内存a的值。 20 a = 10; //但是呢,不可以通过*p来改a的值,可以通过a去修改a的值。 23 //c语言的一个小漏洞 25 //b = 0; //定义了一个常量,那么这个常量权限是只读了。 27 //通过指针的方法:即可以通过指向一个变量地址的指针去指向它,然后通过*p1去间接的修改b的值。 28 //注意编译的时候会出现警告!我们忽略这个警告强行改!这时把b的值改了!!! 32 //这就是在c语言中用常量的时候不用const了! 33 //因为c语言中的const是有问题的,因为可以通过指针变量间接的修改const定义的常量的值,所以在c语言中用#define定义常量的时候更多。 35 //为什么#define不能改呢?实质上#define就是一个文本替换,直接把它替换成一个整数了,整数又不是一个变量。 36 //但是在C++中就没有这个漏洞了。为什么呢?因为c++里面的const是个真的const,而c语言中的const只是在语法的角度不让你去赋值,实际上是假的。 37 //这是c语言本身存在的弱项。 45 //p2 = &b;//直接编译错误//p2是一个指针常量,p2只能指向固定的一个变量的地址,但可以用*p2读写这个变量的值。
1 linux下示例代码如下: 9 p = a; //当指针变量指向一个数组的时候,c语言规定指针变量名可以当做数组名使用。
指针变量可以进行计算,如果是 int * 类型每加一,变化4个整数;
如果是 char * 类型每加一,变化1个整数。其他类型以此类推。
linux下示例代码如下:
linux下示例代码如下:
linux下示例代码如下:
linux下示例代码如下:
小结:c语言中所有的数据类型都可以理解为一个char的数组。
实际上的ip地址是一个无符号的整数构成的。1个int,4个字节。
1、把整数转换为ip地址
linux下示例代码如下:
linux下示例代码如下:
linux下示例代码如下:
linux下示例代码如下:
指针是一个变量,既然是变量就也存在内存地址,所以可以定义一个指向指针的指针。
linux下示例代码如下:
linux下示例代码如下:
linux下示例代码如下图所示:
能用一级指针解决的问题不要用二级指针,能用二级指针解决的不用三级指针,指针级数过多会导致程序很复杂。
工作中大量使用的是一级指针,二级指针也很常用,三级指针就很罕见了,四级指针几乎没有。但笔试会考你哦!可以画图解决!
函数的参数为指针变量(指针变量作为函数的参数)
实际上指针更多的时候用在函数的参数上。
函数的参数可以使是指针类型。它的作用是将一个变量的地址编号传送给另一个函数。
函数参数是指针变量的画图说明如下:
函数的参数为数组名时(即数组名作为函数的参数)
当一个数组名作为函数的形参的时候,c语言将数组名解释为指针变量,其实是一个指针变量名。
如果数组名作为函数的参数,那么这个就不是数组名了,而是一个指针变量名。
当把一个数组名作为函数的参数时,修改形参的值的时候,同时也影响实参的数组成员的值。
如果把一个数组名作为函数的参数,那么在函数内部就不知道这个数组的元素个数了,需要再增加一个参数来标明这个数组的大小。
如果将一个数组作为函数的形参进行传递,那么数组的内容可以在被调用函数的内部进行修改,
有时候不希望这样的事情发生,所以要对形参采用const进行修饰。
1 int *test() //函数的返回值类型是指针类型(具体的讲解在下一节:内存管理)
这三个函数分别实现内存设置、内存复制、内存移动功能。
linux下示例代码如下:
使用memcpy时,首先一定要确保内存没有重叠区域。
linux下示例代码如下:
内存拷贝说明画图如下:
内存重叠区域说明如下图所示:
linux下示例代码如下:
linux下示例代码如下:
linux下示例代码如下:
如果将一个数组作为函数的形参进行传递,那么数组的内容可以在被调用函数的内部进行修改,
有时候不希望这样的事情发生,所以要对形参采用const进行修饰。代码如下:
linux下示例代码如下:
例外:如果函数的参数是一个字符串时,那么并不需要再传递一个参数说明这个字符串有多长。
linux下示例代码如下:
先来看一个指针数组作为函数的参数(此时把指针数组解释为二级指针)
linux下示例代码如下:
args是命令行参数的字符串数组,argc代表命令行参数的数量,程序名字本身就算一个参数。
main函数是由系统调用的,所以main函数的参数功能是:得到命令行的参数。
linux下示例代码如下:
程序名 整数1 运算符 整数2,程序运行的结果是计算结果。
a 5 + 6 注意:中间的加号是字符串。
linux下示例代码如下:
Go 是一种开源的程序设计语言,它意在使得人们能够方便地构建简单,可靠,高效的软件。
1 计算机硬件技术更新频繁,性能提高很快,目前主流编程语言发展落后,不能合理利用多核CPU优势来提高系统性能
2 软件设计复杂度高,维护成本大
3 C/C++编译速度过慢,需要解决提高速度
1 继承C很多理念,包括表达式语法,控制结构,基础数据类型,调用参数传值,指针等
2 引入包的概念,用于组织程序结构,go语言中一个文件都要属于一个包,而不能单独存在
3 垃圾回收机制,内存自动回收,不需要开发人员管理,C需要
A 从语言层面支持病啊,实现简单
B goroutine轻量级线程,可实现大并发处理,高效利用多核特性
C 基于CPS并发模型实现5 吸收了管道通信机制,形成Go语言特有的管道channel,通过管道,实现了不同goroute之间的互相通信
7 新的创新,切片,延时执行等特性
1 区块链开放,其核心是去中心化,公开透明
2 go服务器端/游戏开发软件工程师,其核心是支撑主站后台流量(排序,推荐,搜索等),提供负载均衡,cache缓存,容错,按条件分流等特点,运行统计指标
3 golang分布式/云计算工程师 ,其核心是CDN调度系统,分发系统
安装环境可见菜鸟教程,此处介绍也较为详细
集成开发环境推荐使用Jetbrains 公司的Goland,其公司共有多种开发环境和工具,其次可使用微软公司的visual studio code,其是一个高定制化的轻量编译器,能够根据自己需要制定Go语言的开发流程
Goland 是Jetbrains公司在IntelliJ平台上开发的Go语言整合工具开发继承环境,提供Go语言的编辑,编译,调试,工程管理,重构等各种功能,其地址为:
安装过程本篇不在赘述。主要针对安装完成设置进行相关说明
此处可进行编辑和修改其目录
GOPATH 是Go语言编译时参考的工作路径,其默认是空,可选择对应的目录进行设置,默认会读取系统的GOPATH ,可增加多个
VS Code 使用JSON 格式的配置文件进行所有功能和特性的配置,VS Code 可以通过扩展程序为编译器实现语言高亮,参数提示,编译,调试,文档生成等各种功能。
选择"文件----首选项----设置",在用户-----扩展----Go中进行修改和编辑相关所需的参数
package main //定义该文件所属的包,每个文件都必须属于一个包,一般这个包时该文件所在的目录名称
1 如果先编译成可执行文件,则可将可执行文件拷贝到没有go开发环境的机器上,仍可运行,但其必须和源文件使用同一种系统
2 如果直接使用go run,则如果需要在另一台设备上运行,则需要该设备安装go环境
3 在编译时,编译器会将程序运行以来的库文件包含在可执行文件中,所以,可执行文件变得很大
1 有了xxx.go 源文件,通过编译器将其生成可识别的二进制码文件
3 若程序没提示错误,则会生成可执行文件
4 程序错误,则会报告对应的行数
其名称和源码文件相同,只是后缀发生了变化
1 go 源文件必须是以".go"为扩展名
2 go 应用程序的执行入口时main()函数
3 go 语言严格区分大小写
4 go 方法是由一条条语句构成,每个语句不需要分号
5 go 编译器时一行行编译,一行只能写一个语句,若要写多个,则通过; 分号隔开
6 go语言定义变量或者import的包若没用到,则编译代码不能通过
7 go语言中大括号是成对出现的,缺一不可
package main //定义该文件所属的包,每个文件都必须属于一个包,一般这个包时该文件所在的目录名称
// 这是一个程序执行入口
这是一个多行注释,可用于说明较多的程序问题
2 注释块中不能被嵌套
3 官方推荐使用单行注释
1 使用一次tab 操作,实现缩进,默认整体向右移动,使用shift+tab 整体向左移动
2 或者使用gofmt来进行格式化
3 运算符两边习惯性各加上一个空格
4 一般的,大括号必须位于上一行的结尾而不是下一行的开始
变量相当于内存中的一个数据存储空间的表示,可以把变量值看做是一个房间的门牌号,通过这个门牌号可找到这个房间,及通过变量名能找到内存中访问的变量值
变量的功能是存储用户数据的,不同的逻辑有不同的对象类型,也就是不同变量类型,经过半个多世纪的发展,编程语言已经形成一整套固定的类型,这些类型在编程语言中基本是相通的,常见变量的数据类型有:整型,浮点型,布尔型,结构体等。
Go 语言作为C语言家族代表,在C语言的定义方法和类型上进行了优化和调整,更加灵活易学。
Go语言的每一个变量都拥有自己的类型,必须经过声明才能开始使用
var 变量名 变量类型
// 变量声明,此处声明的变量若不使用,则编译和运行是不能通过的
// 变量声明,此处声明的变量若不使用,则编译和运行是不能通过的
// 变量声明,此处声明的变量若不使用,则编译和运行是不能通过的
2 赋值变量和使用变量
Go 语言在声明变量时,自动对变量对应的内存区域进行初始化操作,每个变量会初始化成其类型的默认值,如
整形和浮点型变量的默认值为0
字符串变量的默认值为空字符串
布尔便来给你默认为bool
切片,函数,指针变量的默认类型为nil
在C 语言中,变量在声明时,并不对变量对应的内存区域进行清理操作,此时,变量值可能是完全不可预期的结果。
// 变量声明,此处声明的变量若不使用,则编译和运行是不能通过的
var 变量名 类型= 表达式
// 变量声明,此处声明的变量若不使用,则编译和运行是不能通过的
// 变量声明,此处声明的变量若不使用,则编译和运行是不能通过的 a = 10 // 定义整形数据a并进行初始化
// 变量声明,此处声明的变量若不使用,则编译和运行是不能通过的
5 交换赋值 (起初的内存资源非常紧缺,计算机大牛通过一些算法来避免使用中间变量)
在使用多重赋值时,若不需要左值中接受变量,可使用匿名变量,匿名变量的表现是一个"_"下划线,使用匿名变量时,只需要在变量声明的地方使用下划线替换即可
1 变量表示内存中的一个存储区域,该区域内的数值可以在同一种类型范围内不断变化
2 该区域有自己的名称(变量名)和类型(数据类型)
A 指定变量类型,声明后,不赋值,使用默认值
B 根据值自行判断变量类型
C 使用:= 进行声明,左侧的变量不应该是已经声明的变量,否则会导致编译错误
4 变量在其作用域内不能重名
5 变量= 变量名+数值类型
6 go 变量若没给初始值,则编译器会使用默认值
Go 语言中有丰富的数据类型,除了基本的整形,浮点型,布尔型,字符串外,还有切片,结构体,函数,map,通道(channel)等,Go语言的基本类型和其他语言大体相同,切片类型有着指针的便利性,但比指针安全,很多高级语言都配有切片进行安全和高效率的内存操作。
复杂的数据类型,如结构体,函数,map和切片将会在后面介绍,此处不再介绍。
3 golang程序中整形变量在使用时,遵循保小不保大原则,及:在保证程序正确运行时,应尽量使用占用空间小的数据类型
4 bit:计算机中的最小存储单元,byte 计算机中最基本的存储单元
1 关于浮点数字在机器中存放形式是浮点数=符号位+指数位+尾数位
2 尾部部分可能丢失,造成精度丢失
golang中没有专门的字符类型,若需要存储单个字符(字母),一般使用byte来保存
字符串是一串固定长度的字符链接起来的字符序列,golang的字符是由单个字节链接起来的,也就是传统的字符串是由字符组成,而golang是由字节组成
1 字符类型常量是用单引号''括起来的单个字符,""双引号括起来的叫字符串
2 golang中允许使用转义字符"\"来将后面的字符变为特殊字符
3 go语言使用utf-8编码,基本不存在乱码问题,字符是四字节,字符串是16字节大小
4 在go语言中,字符本质是一个整数,直接输出时,是该字符对应的utf-8编码对应的码值
5 可以直接给某个变量赋值一个数字,然后格式化输出使用%c,即可输出对应的Unicode字符
6 字符类型是可以进行运算的,相当于一个整数
2 若保存的字符对应码值大于255,则可使用int保存
3 若使用字符串输出,则使用%c
1 字符型存储到计算机中,需要将字符对应的码值找出来其顺序如下
存储:字符-----对应码值----二进制---存储
读取:二进制----码值---字符---读取2 字符和码值的对应关系是通过字符编码表来决定的
3 go语言的编码都为utf8,不会产生乱码情况
字符串就是一串固定长度的字符链接起来的字符序列,go语言的字符串就是由单个字符链接
Go 语言的字符串常见转义符包含回车、换行、单双引号、指标符等
换行符(直接跳到下一行的同列位置) |
1 golang语言的字符串的字节使用utf-8编码表示Unicode文本,这样golang统一使用utf-8编码,不会产生乱码
2 字符串,不能修改,字符串是不可变数据类型,及不能通过修改某个字符来修改,整体是可以进行赋值的
3 字符串的两种表示形式
A 双引号,会识别转义字符
B 反引号,以字符串原生形式输出,包括换行符和特殊字符,可以防止输出源码等效果 **
布尔型数据在Go语言中以bool 类型进行声明,布尔型数据只有true和false两个值,布尔类型占用一个字节,适用于逻辑运算,一般适用于流程控制。
不可以是0或者非0的整数替代false或true,这点和C语言不同
golang和java/C 不同,go在不同类型的变量之间赋值需要显式转换,也就是golang中的数据类型不能自动转换
表达式T(v),将v转换为类型T
v:就是需要转换的变量
Go中,数据类型的转换是可以从范围小的--->表示范围大的,也可以从范围大的-->范围小的,被转换的是变量存储的数值,变量本身的数据类型并没有发生变化
在转换中,若将int64转换成int8,编译器本身不会报错,只是转换结果按溢出处理**
在程序开发中,我们经常需要将基本数据类型转换成string类型,或者将string类型转换成基本数据类型
返回字符串表示的整数值,接受正负号。 base指定进制(2到36),如果base为0,则会从字符串前置判断,"0x"是16进制,"0"是8进制,否则是10进制;将string转换为整数时,需要确保string类型能够转换成有效数据,如"hello",则不能转换为整形
ASCII 字符串遍历直接使用下表
字符串索引比较常用的有以下几种方式
Go语言的字符串无法直接修改每一个字符元素,只能重新构造新的字符串并赋值给原来的字符串变量实现
GO语言中的字符串是不可修改的
修改字符串时,可将字符串转换成[]byte进行修改
[]byte个string可以通过强制类型转换互转
在%v的基础上,对结构体字段名和值进行了展开 输出符合Go语言格式的值 输出Go语言语法格式的类型的值 整形以十六进制方式显示 整形以十六进制,字母大写方式显示
指针概念在Go语言中被拆分为两个核心概念
1 类型指针:允许对这个指针类型的数据进行修改,传递数据使用指针,而无需值拷贝,类型指针不能进行偏移和运算
2 切片:由指向起始元素的原始指针,元素数量和容量组成
Go语言的指针类型变量拥有指针的高效访问,但又不会发生指针偏移,从而避免非法修改关键性数据问题,同时,垃圾回收机制也比较容易对不会发生偏移的指针进行检索和回收。
切片比原始指针具备更强大的特性,更为安全,切片发生越界时,运行时会报出宕机,并打印出堆栈,而指针只会崩溃。
ptr := &l1 //指针类型,此处指针类型和源数据类型相似,为*int ,其值为地址每个变量在运行时都会拥有一个地址,这个地址代表变量在内存中的位置,Go语言中使用"&"操作符放在变量前面来对变量进行取值操作
其中v代表被取地址的变量,被取地址的v使用ptr变量进行接收,ptr的类型就是"*T",称作T的指针类型,"*"表示指针
其每次运行的值是不同的,在32位平台上,将是32位地址,在64位平台上将是64位地址
ptr := &l //指针类型,此处指针类型和源数据类型相似,为*int ,其值为地址变量,指针和地址的关系:每个变量都拥有地址,指针的值就是变量的地址。
取地址操作符"&" 和 取值操作符"*" 是一对互补操作符,"&"取出地址,"*" 根据地址取出地址指向的值。
变量,指针地址,指针变量,取地址,取值的关系如下
对变量进行取地址(&)操作,可获取这个变量的指针变量
指针变量的值是指针地址
对指针变量进行取值(*)操作,可以获得这个指针变量指向的原变量值,可进行赋值修改。
栈(stack)是一种拥有特殊规则的线性表数据结构
栈只允许放线性表的一段放入数据,之后在这一端取出数据,按照先进后出LIFO的顺序,如向箱子中放东西,放的越早,越是最后被拿出来的。
往栈中放入元素的过程称为入栈,入栈会增加栈的元素数量,最后放入的元素总是位于栈的顶部,最先放入的元素总是位于栈的底部。
从栈中取出元素时,只能从栈顶取出,取出元素后,栈的数量会变少。最先放入的元素总是最后被取出,最后放入的元素总是最先被取出,不允许从栈底取出数据,也不允许对栈成员进行任何查看和修改操作。
栈可用于内存分配,栈的分配和回收速度极快
Go 默认情况下会将c和d 分配在栈上,这两个变量在Test()函数退出时就不再使用,函数结束时,保存在c和d的栈内存再出站释放内存,整个分配内存的过程通过栈的分配和回收都会非常迅速。
堆在内存分配中类似于一个往房间里摆放各种家具,家具的尺寸有大有小,分配内存时,需要找一块足够大的空间进行存储,在多次分配内存后,其会导致在向其中分配空间,虽然有足够空间,但各空间分配不均,导致无法形成连续空间来存储数据,此时便需要对这些空间进行调整优化。
堆分配内存和栈分配内存相比,堆适合不可预知大小的内存分配,但为此付出的代价是分配速度较慢,且容易形成内存碎片。
堆和栈各有优缺点,在C/C++语言中,需要开发者自己学习如何进行内存分配,选用不同的内存分配方式来适应不同的算法需求,如函数局部变量尽量使用栈,全局变量,结构体成员使用堆分配等,程序员需要在不同项目中学习,记忆并实践和使用
Go语言将这个过程整合到编译器中,命名为"变量逃逸分析",这个技术由编码分析代码的特征和代码生命周期,决定应该如何对堆还是栈进行内存分配,即使程序员使用Go语言完成了整个工程后也不会感受到这个过程
其中-goflags参数是编译参数,其中-m 表示内存分配分析,-l 表示避免程序内联,也就是避免对程序进行优化
第二行表示变量a逃逸到堆。
第三行表示Test(0) 调用逃逸到堆,由于Test()函数会返回一个整形值,这个值被fmt.Println使用后还是会在其声明后继续在main()函数中存在。上面例子中变量c是整形,其值通过Test()的返回值"逃出"了Test()函数,c变量值被复制并作为Test()函数的返回,及时c变量在Test()函数中分配的内存被释放,也不会影响main()中使用Test()返回的值,c变量使用栈分配不会影响结果。
第一行出现了新提示,将c移动到堆中,这话表示,Go编译器已经确认如果将c变量分配在栈上是无法保证程序最终结果的,如果坚持这样做,则可能是引入一个局部变量的地址,Go最终选择将c的Test结构分配到堆上,然后由垃圾回收机制进行回收c的内存。
在使用Go语言进行编译时,Go语言的设计者不希望开发者将精力放在内存应该分配在栈还是堆上的问题,编译器会自动帮助开发者完成这个选择。
其编译其觉得应该分配在堆和栈上的原则是:
golang对各种变量,方法等命名时使用的字符序列称为标识符,凡是可以起名字的地方都叫标识符
1 有26个大小写英文字母、数字0-9和下划线"_"组成
4 标识符不能包含空格
5 下划线本身在Go中是一种特殊的标识符,称为空标识符,其可以代表任何其他的标识符,但是其对应的值会被忽略,所以单独出现时仅表示为占位符,不能作为标识符使用
6 不能以系统关键字作为标识符
1 包名:保持package的名字和上级目录保持一致,尽量采用有意义的包名,简短,有意义,不要和标准库冲突
2 变量名,函数名,常量名均采用驼峰命名法
3 如果变量名,函数名,常量名首字母大写,则可被其他包访问,如果首字母小写,则只能在本包中使用(注: 可理解成,首字母大写是共有,首字母小写是私有)
在Go中为了简化代码编译过程中对代码的解析,定义的保留关键字有25个,如下
除了保留关键字外,go还提供了36个预定义标识符,其包含基础数据类型和内嵌函数
值类型:变量直接存储值,内存通常在栈中分配
应用类型:变量存储一个地址,这个地址对应的空间才是真正存储值的地方,内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收
相对于变量,常量是恒定不变的值,如圆周率
可以在编译时,对常量表达式求值计算,并在运行期使用该计算结果,计算结果无法被修改
常量的声明和变量非常类似,只是把var换成了const
A C1 = iota //开始生成枚举值,默认为0,将A的类型标识为C1,这样标识后,const下方的常量默认可以是默认类型, // 默认使用前面的类型作为常量类型,此行的iota进行常量值自动生成,iota起始为0,一般建议从0开始 // 一个const声明被的每一行常量声明,将会自动套用前面的iota格式,并自动增加,Go 语言中现阶段没有枚举,可以使用常量配合iota模拟枚举
类型别名是Go 1.9 版本添加的新功能,主要用于代码升级,迁移中类型兼容性的问题,在C/C++语言中,代码重构升级可以使用宏快速定义新的一段代码,Go语言中没有选择加宏,而是将解决代码重构中最麻烦的类型名变更问题。
//Go 1.9 版本之前的内建类型定义的代码如下
类型别名与类型定义表面上只是一个等号的差异,但实际区别比较大
IntAlias 类型只会存在于代码中,在编译完成时,不会有IntAlias 类型。
//包中,因此不能为不在同一个包中的类型定义方法能够随意为各种类型起名字,是否意味着可以在自己包里为这些类型任意添加方法,如下