天天看点

结合CGo对切片slice简单实现

在对自定义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 < ) || (nCap < nLen))
    {
        return NULL;
    }

    Slice *pSlice = (Slice*)malloc(sizeof(Slice));
    pSlice->ptr = (int*)malloc(nCap * sizeof(int));
    memset(pSlice->ptr, , 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, , 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 < ) || (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 < *ilen(slice){
            zcap = *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 = -
    slice.nCap = -
}

func printSlice(slice *Slice){
    fmt.Printf("\nslice : ")
    for i := ; 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(,)
    for i := ; i < ilen(pSlice); i++{
        set(pSlice, i, i+)
    }
    printSlice(pSlice)

    pSlice = iappend(pSlice, ,,)
    printSlice(pSlice)

    pSlice = iappend(pSlice, ,,,,,,)
    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 = 
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,我建议可以使用模板实现任意数据类型。

如有错误,望留言指正,不胜感激。