Golang与C++在内存分配机制上的不同
C++如果使用new操作申请的内存是分配在堆上的要自己利用delete进行回收,如果是声明的局部变量会在栈上分配内存,并且在函数退出后由系统自动回收。但是GOlang在这方面与传统语言发生了非常大的区别,go语言编译器会做逃逸分析(escape analysis),分析局部变量的作用域是否逃出函数的作用域,要是没有,那么就放在栈上;要是变量的作用域超出了函数的作用域,那么就自动放在堆上。所以不用担心会不会memory leak,因为go语言有强大的垃圾回收机制。这样可以释放程序员的内存使用限制,让程序员关注程序逻辑本身。go语言也允许利用new来分配内存,但是new分配的内存也不是一定就放在堆上,而是根据其是否超出了函数作用域来判断是否放在堆上还是栈上。这点和C/C++很不一样。
下面用C++与GO分别实现链表来对比一下内存分配的差异
C++代码
// listTest.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdlib.h>
#include <string.h>
class Student
{
public:
int Age;
float Score;
char Name[10];
Student* next = NULL;
};
void list_transvers(Student* head)
{
while (head != NULL)
{
printf("name %s age %d score %f address %p\n", head->Name, head->Age, head->Score,head);
head = head->next;
}
}
Student* insertHead(Student* head)
{
for (int i = 0; i < 10; i++)
{
声明Student对象在栈分配内存
//Student stu;
//sprintf(stu.Name,"stu%d", i);
//stu.Age = rand()%100;
//stu.Score = (float)(rand() % 100);
//stu.next = head;
//head = &stu;
//声明Student*指针在堆分配内存
Student* pStu = new Student();
sprintf(pStu->Name, "stu%d", i);
pStu->Age = rand() % 100;
pStu->Score = (float)(rand() % 100);
pStu->next = head;
head = pStu;
}
printf("insertHead函数内部遍历\n");
list_transvers(head);
return head;
}
void releaseList(Student* head)
{
printf("释放链表节点内存\n");
while (head != NULL)
{
Student* next = head->next;
printf("%s节点内存释放\n",head->Name);
delete head;
head = next;
}
}
int main()
{
Student* head = new Student();
strcpy(head->Name,"jin");
head->Age = 18;
head->Score = 100;
head = insertHead(head);
printf("main函数内部遍历\n");
list_transvers(head);
releaseList(head);
getchar();
return 0;
}
上面这段代码中的“insertHead”函数中分别利用“在栈上分配内存”和“在堆上分配内存”实现了链表节点的头部插入功能,其实“在栈上分配内存”的实现方式明显是错误的,这是在学C++时要搞清楚的一个基本问题,由于链表节点是在堆上分配的内存所以链表使用完以后要释放节点内存。按照堆上分配内存的正确方法执行上述代码结果如下
结果正常
下面是GO代码
package main
import "fmt"
import "math/rand"
type Student struct{
Name string
Age int
Score float32
Next *Student
}
func transvers(head *Student){
for head!=nil{
fmt.Println(head)
head = head.Next
}
}
func insertHead(head *Student)(*Student){
for i:=0;i<10;i++{
//声明Student*指针
/*var stu *Student
stu = new(Student)
(*stu).Name = fmt.Sprintf("stu%d",i)
(*stu).Age= rand.Intn(100)
(*stu).Score= rand.Float32()*100
(*stu).Next = head
head = stu*/
声明Student对象
var stu Student
stu.Name = fmt.Sprintf("stu%d",i)
stu.Age= rand.Intn(100)
stu.Score= rand.Float32()*100
stu.Next = head
head = &stu
}
fmt.Println("insertHead函数内部遍历")
transvers(head)
return head
}
func main(){
var head *Student = new(Student)
(*head).Name = "hua"
(*head).Age = 18
(*head).Score = 100
head = insertHead(head)
fmt.Println("main函数内部遍历")
transvers(head)
}
go语言代码中的“insertHead”也采用了“指针对象新建节点”和“普通变量新建节点”的方式实现的,与C++不同的是这两种方法都可以正确的执行链表操作,而且链表节点的内存不需要释放,执行结果如下
可以看出GO语言即便是非指针的局部变量仍然可以在函数结束以后被访问,go语言编译器会做逃逸分析(escape analysis),这大大的方便了程序员的编程工作