在对自定义slice实现的过程中,体会到以下两点:
1、Go封装得确实比较好,但是Go中的指针也因此受到了大大的削弱,在Go中指针平常也就只能用来做引用传递。
2、对于Cgo的资料太少了,为此买了两本书,也就一本有略微提及。我希望你能从代码中找到你对CGo存在的疑问和解决办法。
不多说,上代码
package main
/*
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
typedef struct stSlice{
int *ptr;
int nLen;
int nCap;
}Slice;
Slice* imake(int nLen, int nCap){
if ((nLen < 0) || (nCap < nLen))
{
return NULL;
}
Slice *pSlice = (Slice*)malloc(sizeof(Slice));
pSlice->ptr = (int*)malloc(nCap * sizeof(int));
memset(pSlice->ptr, 0, nCap * sizeof(int));
pSlice->nLen = nLen;
pSlice->nCap = nCap;
return pSlice;
}
void resize(Slice *pSlice, int nNewLen, int nNewCap)
{
int *pOld = pSlice->ptr;
int nSize = pSlice->nLen * sizeof(int);
pSlice->ptr = (int*)malloc(nNewCap * sizeof(int));
memset(pSlice->ptr, 0, nNewCap * sizeof(int));
pSlice->nLen = nNewLen;
pSlice->nCap = nNewCap;
memcpy(pSlice->ptr, pOld, nSize);
free(pOld);
pOld = NULL;
}
void Set(Slice *pSlice, int nIndex, int nVal){
pSlice->ptr[nIndex] = nVal;
}
int Get(Slice *pSlice, int index){
return pSlice->ptr[index];
}
void Free(Slice *pSlice){
if(pSlice->ptr != NULL){
free(pSlice->ptr);
pSlice->ptr = NULL;
}
}
int len(Slice *pSlice){
return pSlice->nLen;
}
int cap(Slice *pSlice){
return pSlice->nCap;
}
*/
import "C"
import (
"fmt"
)
type Slice struct{
nLen int
nCap int
pCSlice *C.Slice // C.Slice内部保存有(C语言)对动态数组操作的指针,方便内存的释放与操作内存
}
func imake(nlen int, ncap int) *Slice{
if(nlen < 0) || (ncap < nlen){
return nil
}
cs := C.imake(C.int(nlen), C.int(ncap))
slice := new(Slice)
slice.nLen = int(cs.nLen)
slice.nCap = int(cs.nCap)
slice.pCSlice = cs
return slice
}
func iappend(slice *Slice, nums ... int) *Slice{
nSliceLen := ilen(slice)
zlen := nSliceLen + len(nums)
if zlen < icap(slice){
slice.nLen = zlen
}else{
zcap := zlen
if zcap < 2*ilen(slice){
zcap = 2*ilen(slice)
}
C.resize(slice.pCSlice, C.int(zlen), C.int(zcap))// 重新分配数组大小
slice.nLen = int(slice.pCSlice.nLen)
slice.nCap = int(slice.pCSlice.nCap)
}
index := nSliceLen
for _,val := range nums{// 将添加的数据,写入到末尾
set(slice, index, val)
index++
}
return slice
}
func ilen(slice *Slice) int{
return slice.nLen
}
func icap(slice *Slice) int{
return slice.nCap
}
func set(slice *Slice, index int, val int){
if index >= ilen(slice){
panic("error:index out of range")
}
C.Set(slice.pCSlice, C.int(index), C.int(val))
}
func get(slice *Slice, index int) int{
if index >= ilen(slice){
panic("error:index out of range")
}
nVal := C.Get(slice.pCSlice, C.int(index))
return int(nVal)
}
func Free(slice *Slice){
C.Free(slice.pCSlice)
slice.nLen = -1
slice.nCap = -1
}
func printSlice(slice *Slice){
fmt.Printf("\nslice : ")
for i := 0; i < ilen(slice); i++{
fmt.Printf("%v ", get(slice, i))
}
fmt.Printf("\nlen = %v", ilen(slice))
fmt.Printf("\ncap = %v\n", icap(slice))
}
func main(){
pSlice := imake(5,8)
for i := 0; i < ilen(pSlice); i++{
set(pSlice, i, i+2)
}
printSlice(pSlice)
pSlice = iappend(pSlice, 10,20,30)
printSlice(pSlice)
pSlice = iappend(pSlice, 4,5,6,7,8,9,0)
printSlice(pSlice)
Free(pSlice)
}
一、结合源码对slice的认知
1、不难看出,CGo中使用C语言进行的是对内部数组指针的操作。和Go中的slice一样,slice只是维护了一个内部的数组指针,对数据的调用和添加都是通过该数组指针实现。
2、在iappend中可以发现,当追加的数据超过nCap值时,会调用resize()函数进行内存的重新分配,然后将原数组的数据拷贝到新的数组中,之后便释放原数组指针。至此,可以看出以下两点:
(1)追加数据的重新内存分配与数据拷贝会消耗时间,应尽量避免
(2)重新分配的数组指针与原来的指针是完全不同的两个值,这也是为什么我们将切片值作为函数参数传递时,如果函数中发生了数据追加,返回回来的切片,往往得不到我们想要的数据。
如果非要传递切片参数,使用切片的指针传递。例如 func F(p *[]int),与C中的指针的指针差不多
二、CGo中遇到了一些易错点:
1、只支持C语言,不能嵌入C++语言的语法与关键字
2、结构体不能直接定义后使用,例如:
2.1 错误用法
struct Slice{
int *ptr;
int nLen;
int nCap;
};
void imake(Slice* pSlice, int nLen, int nCap){
}
报错:error: unknown type name 'Slice'
2.2 正确用法(应该对结构体定义别名)
typedef struct stSlice{
int *ptr;
int nLen;
int nCap;
}Slice;
void imake(Slice* pSlice, int nLen, int nCap){
}
3、C.int转换为Go中的int.其他数据类型类似
var cInt C.int
var gInt int = 10
cInt = C.int(gInt)
gInt = int(cInt)
4、*C.int与*int的转换.其他数据类型类似
var pc *C.int
pg := new(int)
pc = (*C.int)(unsafe.Pointer(pg))
pg = (*int)(unsafe.Pointer(pc))
这里也有许多缺陷,仅仅对int型切片进行了实现。如果你要实现完整的slice,我建议可以使用模板实现任意数据类型。
如有错误,望留言指正,不胜感激。