作者: tushushu
项目地址: https://github.com/tushushu/EssentialCython
参考书籍: 《Essential C++ 中文版》
编程环境: MacOS + Jupyter Notebook + Python 3.6
%load_ext Cython
3.1 指针的算术运算
3.1.1 array作为形参的特性
当我们写下如下函数
int min(int array[24]);
min()似乎仅能接受某个拥有24个元素的array,并且以传值的方式传入。事实上这两个假设都是错的:array并不会以传值的方式被复制一份,而且我们可以传递任意大小的array给min()。当数组被传给函数,或是由函数返回,仅有第一个元素的地址会被传递。下面我们来写代码验证这一点:
%%cython
print("声明一个min函数,形参为长度为24的数组")
cdef int cmin(int array[24]):
print("array的大小为", sizeof(array))
cdef:
int n = 8
int i
int res
if n == 0:
return 0
res = array[0]
for i in range(n):
if array[i] < res:
res = array[i]
# 我们把传入的array的首个元素改为666
array[0] = 666
return res
cdef int array[6]
array[:] = [8, 2, 1, 3, 6, 7]
print("定义长度为6的数组", array)
print()
res = cmin(array)
print("array的大小本应该是6 * 4 = 24,证明传入函数的是首元素的指针而不是array!")
print()
print("运算结果为", res)
print("函数正确运行,证明我们可以传递任意大小的array给min()!")
print()
print("打印刚才定义的长度为6的数组", array)
print("数组的值被更改,证明array并不会以传值的方式被复制一份!")
声明一个min函数,形参为长度为24的数组
定义长度为6的数组 [8, 2, 1, 3, 6, 7]
array的大小为 8
array的大小本应该是6 * 4 = 24,证明传入函数的是首元素的指针而不是array!
运算结果为 1
函数正确运行,证明我们可以传递任意大小的array给min()!
打印刚才定义的长度为6的数组 [666, 2, 1, 3, 6, 7]
数组的值被更改,证明array并不会以传值的方式被复制一份!
3.1.2 指针与数组
现代编译器对于数组的访问都会自动优化为其对应的指针加偏移量的形式。虽然array是以第一个元素的指针传入cmin()中,但仍然可以通过下标的方式访问元素。因为所谓的下标操作就是将array的起始地址加上索引值,产生出某个元素的地址,然后该地址被dereference再返回元素值。我们现在写代码验证一下:
注意:cython通过[0]或者from cython.operator cimport dereference as deref
的方式来解引用,而不能像C/C++一样直接用星号来操作。
%%cython
from cython.operator cimport dereference as deref
from libc.stdint cimport uintptr_t
cdef uintptr_t address
print("创建地址变量address")
print()
cdef int array[8]
array[:] = [1, 2, 3, 4, 5, 6, 7, 8]
print("创建长度为8的数组array", array)
address = <uintptr_t>array
print("打印array的地址为", address)
print()
cdef int *ptr = &array[0]
print("创建指针ptr指向array的首个元素")
address = <uintptr_t>ptr
print("打印ptr的地址为", address)
print()
print("两者的地址一样!")
print()
address = <uintptr_t>(ptr + 1)
print("打印ptr + 1的地址为", address)
print("指针算术运算,会把指针所指类型的大小考虑进去。")
print()
print("通过下标的方式访问指针指向的下一个元素为:", deref(ptr))
print("通过指针加法的方式访问指针指向的下一个元素为:", deref(ptr + 1))
print("通过array指针加法的方式访问下一个元素为:", (array + 1)[0])
创建地址变量address
创建长度为8的数组array [1, 2, 3, 4, 5, 6, 7, 8]
打印array的地址为 4400244288
创建指针ptr指向array的首个元素
打印ptr的地址为 4400244288
两者的地址一样!
打印ptr + 1的地址为 4400244292
指针算术运算,会把指针所指类型的大小考虑进去。
通过下标的方式访问指针指向的下一个元素为: 1
通过指针加法的方式访问指针指向的下一个元素为: 2
通过array指针加法的方式访问下一个元素为: 2
3.1.3 实现一个泛型算法
假设我们需要完成以下工作。给定一个储存任意类型元素的vector或array,以及一个元素。如果此元素存在于vector或array内,我们必须返回一个指针指向该值;反之则返回0。 编写如下C++代码,实现find函数。
template <typename T>
T *find(const T *first, const T *last, const T &target)
{
if (!first || !last)
{
return 0;
}
for (; first != last; ++first)
{
if (*first == target)
{
return first;
}
}
return 0;
}
然后像以前一样,编写.pyx和.pxd文件导入这个函数可以被Python/Cython调用,可能是Cython对C++的支持问题,使用C++测试的时候没问题,但Cython代码会报错。 更改一下实现方式,让函数返回数组或者vector的下标,而不是一个指针。
template <typename T>
int find(const T *first, int size, const T target)
{
if (!first)
{
return -1;
}
for (int i = 0; i < size; ++i)
{
if (first[i] == target)
{
return i;
}
}
return -1;
}
下面写代码测试一下:
%%cython --cplus --compile-args=-stdlib=libc++ --link-args=-stdlib=libc++
from chapter_3 cimport find
from libcpp.string cimport string
from libcpp.vector cimport vector
cdef int size
# 测试0: int array, 不可找到target
size = 8
cdef int itarget = 10
cdef int iarray[8]
iarray[:] = [1, 2, 3, 4, 5, 6, 7, 8]
print("target的下标为:", find(iarray, size, itarget))
# 测试1: int array, 可找到target
itarget = 4
print("target的下标为:", find(iarray, size, itarget))
# 测试2: float array, 可找到target
size = 4
cdef float ftarget = 2.0
cdef float farray[4]
farray[:] = [1.0, 2.0, 3.0, 4.0]
print("target的下标为:", find(farray, size, ftarget))
# 测试3: double array, 可找到target
size = 5
cdef double dtarget = 3.0
cdef double darray[5]
darray[:] = [1.0, 2.0, 3.0, 4.0, 5.0]
print("target的下标为:", find(darray, size, dtarget))
# 测试4: string array, 可找到target
size = 6
cdef string starget = b"hi"
cdef string sarray[6]
sarray[:] = [b"abc", b"d", b"ef", b"g", b"hi", b"jkl"]
print("target的下标为:", find(sarray, size, starget))
# 测试5: int vector, 可找到target
cdef vector[int] vec = [1, 2]
itarget = 2
size = vec.size()
print("target的下标为:", find(&vec[0], size, itarget))
# 测试6: int vector, 空vector
vec = []
itarget = 2
size = vec.size()
print("target的下标为:", find(&vec[0], size, itarget))
target的下标为: -1
target的下标为: 3
target的下标为: 1
target的下标为: 2
target的下标为: 4
target的下标为: 1
target的下标为: -1