EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
前言:众所周知对于循环这种运算,matlab的运算速度是不快的 想起来个冷笑话,黑客帝国里的主角呢奥之所以能快过子弹,因为Matrix是matlab写成的hhhh 因此将循环部分用C语言写,然后编译成matlab可以调用的mex文件,无疑可以加快循环运算速度,从而加快整个代码速度 之前因为合并数据需要不会数据库,强行在matlab里利用类似excel的vlookup功能,无奈太慢,只能想办法最后知道了这种方法,最近偶然在桥哥的知识星球里又提到了,也顺便复习和记录一下。 ps:今天在闲鱼上买了小车车,高兴! 1.环境配置我的是matlab2019a,win10-64bit,c语言编译器我选的是TDM-GCC(gcc/g++),安装起来很简单,推荐matlab2015及以后的选择这种,详细安装过程可以见文末链接1中的For Matlab 2015部分
1 T* r8 U) h4 s2.如何编写可以被编译成matlab可执行文件的c语言代码我是在matlab中文论坛(地址见reference第2条)里找到如何编写的,不过他写的有点乱,我来消化后写一下自己的理解 mex接口函数跟一般的C语言文件主要是两点区别, 第一是必须引入头文件mex.h #include "mex.h"第二是多了一个叫mexFunction的函数 void mexFunction(int nlhs,mxArray *plhs[], int nrhs,const mxArray *prhs[])举个栗子:
; `3 `* g9 G4 H6 k- ?" O: y( }( w
1 d8 X1 |7 S; b, ?#include "mex.h" // 使用MEX文件必须包含的头文件
+ S p2 T! j. k+ d6 T6 Y
/ a T, }" b9 _# X: R- / E& u! ?- Z3 f$ C/ {
]; I7 {' H, s, X& m
// 执行具体工作的C函数3 K0 g" m u3 O- ~( }0 a2 c
$ ?1 A; S( S0 l; W% ]( Q. `+ K
. w( S, m0 y" T! A& |$ A3 N( u! v: M3 W8 @, a
double add(double x, double y) q4 p7 R$ [' S% I4 R
$ t* v8 O$ B7 B4 d4 ?
7 s; \- V' D$ n& i. ^3 F+ J3 q* b3 ~7 G* m& B5 `
{( b- C4 W% b0 }8 ~( k+ V% q& F
% R/ t; g Z# F H, C- / H, |& ], [* d9 {
* G" N: t. b, r9 N return x + y;
$ J! R9 p" H4 E( F3 z$ r
" c/ D7 k6 h/ i8 Z/ e! U. x/ y
; F8 a1 u* O4 G3 }, n" O/ _
/ n7 h9 u+ G5 A6 {4 P' i' d- l/ @. F1 q}; R2 B# ~+ B$ v( ?* |# g
% x+ k1 N: K8 R3 J- ! b( J) w: z- ^9 S( q0 P& Q
3 O, e2 f- V9 ?3 W1 j
// MEX文件接口函数
2 m7 ], z, s0 u& E$ R0 [
7 ~7 j( w* Z, e - 4 I1 Q; ]& B; z0 Y V
4 U, a3 a' p' ~$ G
void mexFunction(int nlhs,mxArray *plhs[], int nrhs,const mxArray *prhs[])( e' N, e' h7 u- U
% m9 n$ \! N* F5 m0 F2 g9 ]+ H8 }/ k
* h Q, c) P4 n5 K% x% L- i& Y* ]6 U3 c& k6 A! R$ ^' n
{
1 e9 S8 k; x4 U
- D" S. Y0 V- `& F; G7 Z+ j/ R: e- 0 ]' j% R# d6 X$ p
& g' h. z9 l$ b9 g# T0 m+ h
double *z;, p, ^+ n& w5 D% ~8 J
# i3 g$ \7 P* @! u - 4 f+ D7 C0 ~# u0 q' `$ E
9 b0 U9 ]; ~8 v; z, r' N8 d
double x, y;+ \+ s9 }, R S8 K
$ M6 Q" P& ^+ Y6 J* d) m4 A; f
- , Z! b9 w2 D$ C. ]3 J( b. m A. P
/ C9 Y) A# l2 B: `5 \1 ~5 a M; @/ V6 }. Q plhs[0] = mxCreateDoubleMatrix(1, 1, mxREAL);
. q$ H. j5 p( c' w* A m5 t' x+ u2 M7 V4 R1 b
- 7 Y" V' T4 A# q5 j& `
0 R6 _0 f) c$ v5 i, d+ n4 m z = mxGetPr(plhs[0]);* L: U0 K$ e- Q
# n( g% Z( ` U* t+ j - ' |+ |3 e8 ]8 }' B2 \* I8 y
( I% a2 ?* B' \ v2 L: T x = *(mxGetPr(prhs[0]));* S' H5 L4 W/ U) Q9 h y( o
) n- q$ _( d. u; ^
) p) V; N9 m( W& R) a/ K
2 o2 T% l( q l+ O5 \- j7 ^ y = *(mxGetPr(prhs[1]));
) M# j: X" l3 ~: u% {) T3 ^" Q' U8 G* X: q
- ( u& d$ t7 {0 s3 J9 ~- D
7 d' o& d, K( Z. j
*z = add(x, y);" p( q: o4 c" b& p4 b
* e7 A% ~. e, ^9 i
# L% `/ K& \/ K/ ~0 z( }" W! {( [
0 x1 I9 P% g. }; c}7 \: V+ v# j% X- \, n' w
: ^" W6 [( |( b0 }$ h
% }/ M, T: |- W% I. D
也可以写成: - - |0 w+ ^/ H+ Z
' W3 L" M" C" x* U1 ~1 z" O- _+ L#include "mex.h" // 使用MEX文件必须包含的头文件
( h# O5 N7 {) m9 E* X* |. I6 y6 q8 ]
- 0 w* \+ ^8 v( [
+ q+ a4 X% b, n
// MEX文件接口函数
7 H* R2 N4 P6 x3 [% e5 w/ e7 B5 H2 I3 j1 g% d, K/ C# W; h
* \9 y+ e1 L2 o+ N. y/ ^$ s* ~; p4 V3 q
void mexFunction(int nlhs,mxArray *plhs[], int nrhs,const mxArray *prhs[])8 M2 b: S& {% P8 S8 H! {! O5 P& I
8 ?, g9 z T4 N- % ~ ]$ i; \3 l9 g8 U& t! ^/ M
c/ i0 h5 n# ~! Q
{4 N# H% ]8 [% w! ]$ a
1 ^6 T- ?- n# X; N `1 N1 p! W
; V* e- N7 e+ X6 d
; z3 X6 K: u$ u7 ]5 d9 M7 f" j( z double *z;
1 n; T0 `3 n% p$ w, p( J5 r) n- m9 H+ i, a7 v8 i/ h$ C
3 R: w! c+ f. f/ P4 N. E3 e0 O7 ?7 `' o% {2 J
double x, y;
9 n2 b' O" w) ^7 b6 a: L' @1 A* o) w. J" V
* I7 Z- L n. D" J9 V8 T- K9 S' i6 i
& x6 j; U- N, i8 E plhs[0] = mxCreateDoubleMatrix(1, 1, mxREAL);$ M$ l1 @. O; R0 N- a+ m
( W+ N6 Y. C. b* Z2 S
! J- F2 H# e* _1 [
% T* x1 H$ l- Q: M7 O) ^% E z = mxGetPr(plhs[0]);
& W7 {+ n, V# p! {& q9 K3 W7 z9 p2 q8 {" R+ D3 V
: }0 R) Y1 R0 D( a0 O: o2 k+ |5 Y
" \9 a8 s$ m; C" r( {, l x = *(mxGetPr(prhs[0]));
' e* f9 o" U6 |$ w l) K
) e5 a- p# e) y% O' T: l9 ]9 X
+ i: T/ t( r* ]" L
* P# N& I9 ~; k y = *(mxGetPr(prhs[1]));
. H/ i& {& K. h0 D' r" Q
* l2 [6 `! Y# L# |+ W' j- * S0 M% @! Z5 }
; D2 T# U5 G0 U# {9 R, X& o+ @ *z=x+y;$ e- _+ t& g# P- Z
w# s3 G9 J( S6 K
. P1 d- A6 P( d( H
* Z* E6 M) [# ]$ s# {: r}
" L# X' f+ }; g g( P7 a" w
- p7 b: r+ R2 N: ?7 f/ q
9 j& x5 k: Q$ O
也就是说执行具体功能可以通过编写一个独立的c语言函数在接口函数里调用,也可以直接写在mexFunction接口函数里 我个人推荐将具体功能独立出来,这样一来: c语言函数负责执行具体功能 mexFunction接口函数负责数据的输入输出(将matlab数据输入c语言环境,运算后再输出回matlab)形式组织 再调用c语言函数
分工明确。 于是关键问题就是如何编写mexFunction接口函数了 void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])下面解释一下mexFunction各个参数的含义(这里看到是void,即无返回值,因为传值是通过指针数组plhs[]传出的) 参数 意义 英文全称 类型 X( e( \; Q" @4 B4 |# h4 {4 n
nlhs左边输出参数的数目number of left-hand side整型(int)
1 O& ]; v1 ]0 xplhs指向输出参数的指针pointer of left-hand side指针数组* e, Z; m5 y n) ^% b( s
nrhs右边输入参数的数目number of right-hand side整型(int)& n6 n& \' E J; B3 |' ~: F
prhs指向输入参数的指针pointer of right-hand side指针数组
4 U% B! R; n! ^7 }1 K$ T" Y第一个参数nlhs是输出参数的数目,第二个参数plhs是指向输出参数的指针 第三个参数nrhs是输入参数的数目,第四个参数prhs是指向输入参数的指针 其中plhs和prhs类型都是指向mxArray类型数据的指针,prhs这里还加了个const,这里的const跟之前c语言中是一样的,代表不改变,因为prhs是指向输入参数的指针,mxArray这个数据类型是在头文件mex.h中定义的,in fact ,在matlab中大多数数据都是以这种类型存在。 还是拿之前的栗子来解释:
% W- r9 K- E* p) X( V2 B1 l: }/ y0 [0 z0 A: F7 H
#include "mex.h" // 使用MEX文件必须包含的头文件
2 B! v. h* q$ q( k9 I
! A0 Z% Z+ B C* {# _- ) k5 W, _* M4 n' P; X
1 J8 [" V3 A4 J& ~1 y- @// MEX文件接口函数8 ^) X% B, a5 q: p- w. ~
8 R- m% P) O! @/ b- u+ S
- 0 |! I, [! T) F( D" \5 _, i
+ |1 J) |) {! Z: ovoid mexFunction(int nlhs,mxArray *plhs[], int nrhs,const mxArray *prhs[])
8 B# f' l) ?7 M6 C4 P
- s1 Y4 k( Q# x2 ?3 t - 9 r$ g4 J8 H0 x8 a/ {. s
! x0 s+ l& v U T$ B
{- j( I% v# F5 F" j) T
& C8 `1 B4 s1 Q$ _+ \, M - + R. z* ?1 k: e( G3 S3 P5 C, ^
8 b" l8 g l+ T, }" _$ Q( k8 H. s$ z" ] double *z;
0 g. _$ h+ i& M
# c; Q0 H9 s& z' C, I( k - * r- Y! X; j0 D* r
" P( s1 \6 ?, E, O double x, y;
7 A& G$ l C- r2 v$ [% F) C3 Q8 g( m) d A" R
% a/ q* P; e Q, b/ U- g
$ c" ?' A- R; G6 O5 D plhs[0] = mxCreateDoubleMatrix(1, 1, mxREAL);1 Y+ [1 e! j2 c- x! @
2 y' w6 x* M: V2 y! K5 G# N" ]
& e8 B. B. g1 f+ S K! I
" _( S4 h: W, ^ z = mxGetPr(plhs[0]);
k- s3 r6 d8 @2 O1 n2 U; N: q# M/ |
! _5 Y, D$ Y/ L
& p1 E# k- m$ P6 R x = *(mxGetPr(prhs[0]));
8 a5 K7 A. g: y# W0 w& ~! }5 l6 ~
- $ n, y- ^5 W+ }: E' \5 }9 ~" ]
0 }* w6 ]2 z# N5 a. ]1 t y = *(mxGetPr(prhs[1]));, M# ]) Z( r/ T" a" U% r# X
- b( z- C3 `2 q
/ C8 A. E0 z6 G4 Z! Q- c, g6 E8 c" q6 n# T1 Z' O
*z=x+y;+ a/ _: k; j+ X" q& d
* |6 y2 O' V9 R, }7 Y/ N# F4 q
" N8 N8 |) ]# l2 n6 z7 @0 ?3 K& ]* a+ V9 V, m Z4 w' t! x' `- q
}
, x. O, \9 f9 a2 ~1 ^0 P0 _( O+ O$ W2 a7 e3 O( F
% z0 c# o! ?7 y
在这个栗子中,输入参数是x,y,输入参数是z(虽然没有返回) plhs和prhs其实都是指针数组 prhs[0]代表指向第一个输入数据的指针,mxGetPr代表获得这个指针,后面再加个*就是c语言里面再正常不过的按指针取内容了 所以就这样x,y通过下面这两句传入到了mexFunction中 - 3 z0 k4 k6 Y0 ^! ^6 T$ D
2 n0 e( ?9 N- w6 Q( b$ F2 [9 d) sx = *(mxGetPr(prhs[0]));
2 z- i) }8 Q p5 ]! _- j; Z/ p5 K
/ B) Z; i3 q8 `0 a& b( q1 d2 a( ]7 o1 [! S4 h
y = *(mxGetPr(prhs[1]));$ p3 D* g- z" }# E9 Y
% Y3 I( O+ l6 b2 K/ i5 p% ~( V7 M" q( n. K0 W* ^# O" d
mxGetPr函数的功能是从指向mxArray类型数据的指针prhs[0]、prhs[1]中获得了指向double类型的指针 另一个函数是mxGetScalar,Scalar即标量,功能是把通过prhs传递进来的mxArray类型数据的指针所指向的数据(标量)赋给c程序里的变量。前面的mxGetPr是传递矢量的,否则矩阵或者向量就没有办法传进来 这里由于传入的每个参数是单个值,于是也可以用mxGetScalar改写:
7 S) A y: ~6 s1 c2 V( k1 ?6 Z- d) @$ U& J/ j8 Y) q5 E$ U
#include "mex.h" // 使用MEX文件必须包含的头文件/ p, c ?' ~' {" I$ k4 a$ g
9 a6 _! E3 B! `' P
# g1 |: c- ]7 T: }! G' l' Z1 ?2 }! v/ Q, Z
// MEX文件接口函数
7 j4 Y8 {% u* [9 z. t) g$ p5 z& p9 s
- 0 L! w, q. C' |: }: O: N3 H
! \$ ^# k/ \4 X" D1 s: T0 kvoid mexFunction(int nlhs,mxArray *plhs[], int nrhs,const mxArray *prhs[])5 ^9 a7 |: [' ~ P2 V% P
4 Q' f1 D) J+ X# {4 T. z! s - 1 ~4 d" u1 y; r; D5 k
8 m+ G+ Z7 @; J0 h- D/ n{9 \0 [* ?$ H( K" D" y8 S
4 c+ B* R2 S. F. O
- 5 j2 |8 P- @9 r6 N7 n+ P
8 E+ p5 L8 K; ?' h double *z;% m' ?" I9 d" c
, N) H& m+ s- K; U/ R6 N6 J# I
; L k& [4 M; d6 p4 i2 g6 T' H8 }7 S7 U; j+ i
double x, y;9 X. a' \! f0 o; k
$ L) o9 K2 |/ I6 v& [ Z
- 0 ?+ |1 a3 j4 I+ P% @8 G
# @4 d: v |0 ^3 s: I6 ?, J
plhs[0] = mxCreateDoubleMatrix(1, 1, mxREAL);1 t7 r7 g" ~5 i1 G
. [# g" G' N. b8 V/ v [
: q4 \) q9 X, w4 K4 j1 g: s: z- b9 M+ r2 k( l# {( h4 s
z = mxGetPr(plhs[0]);0 ?/ Y+ t4 ~6 ^" `# h( L0 X
4 z3 O5 v5 ^* l8 u, X2 N+ K/ E$ j
- ) i8 l* w7 Y4 V8 ~3 o% V4 ]& Z
$ a5 x+ b/ A* E |5 ^) D
x = mxGetScalar(prhs[0]));
Z1 B$ M/ v+ p& j. W6 j
9 z5 S' X) X1 L' A0 |
# ~% N s- e/ h2 c. F# v# x( _, |$ e4 |6 ^; W! p, w
y = mxGetScalar(prhs[1]));6 |9 }; _2 H5 c8 u; Q
5 H- g; x* V h3 S5 A8 R& ?- |/ l
7 c0 V0 v0 m P* @( i( p5 _
$ l; I" c2 P {2 D( A *z=x+y;
9 F+ r0 \. r h# y' N- f5 [) W0 ^: N" S$ h
- 7 L5 k P, ~! i7 X3 |: a$ B' q# K$ j
- w0 }) r$ {/ \9 K
}$ g: {. u% E" B. p! T; g
* A7 D0 s& o7 I4 z" W2 v1 a2 m
L! h6 o3 h& _$ o; {9 B2 ]9 t
(注意:输出参数因为需要通过指针传出,因此这里z必须用mxGetPr这个函数来从mxArray指针获取double指针) 但是这样还是有个问题:如果输入的不是单个的数据,是向量或者矩阵,即使我们通过mxGetPr获得了矩阵或者向量的指针,但是我们假如不知道矩阵的shape,还是不好取值或者计算,所以这里又有两个函数mxGetM和mxGetN可以通过传入参数指针获得传入参数(矩阵)的行和列数。 需要注意的是: 在matlab中矩阵的第一行和第一列的序号是从1开始的,而在c语言中是从0开始的,而且在c语言中的一维数组存储矩阵的数据时,是一列列从上到下,从左到右来存储的,也即 matlab中一个m*n的矩阵M(i,j)对应于c语言中一维数组N中的N[j*m+i]
举个栗子:
5 ~3 Y! A3 [0 e) f. }
4 G% _0 N8 ? j4 ^#include "mex.h"- t5 F) a5 z. ~& W3 n( Z
% \. X$ q& G' a: K- d1 X% v( I- J" ]
* G0 h5 {9 O v6 B+ [& X# v/ d* J4 E void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]){# r. |+ ~2 y h/ h/ x0 s
" E8 w, ? K$ _4 i
+ i4 {. t; h4 w% n6 {6 W, G+ W* ~- M1 A: y
! G& n6 a7 z) W8 R K
3 T1 v$ d- v) C. S/ M% O b
a/ k5 o X0 }" ~, x* ?
+ X7 }: v5 K5 g( I: q" h% X7 M' `double *data;( K0 U0 n0 }3 F, U3 D
3 p6 ^. [, \1 v$ z0 @4 @
- + ~6 y3 S5 h7 B$ z* M1 A
5 j! W, J0 }# ]' J* Qint M,N;8 O4 }+ j- t2 M0 S* h
* J4 D/ t& |& _+ A6 w3 J
0 A; n+ L& t( x, v1 t3 ?
; G, S( O0 U( \int i,j;% m+ j" P4 f7 w( ^4 u
- ~- V7 }# Z; w3 p$ I" j
/ k+ v9 e0 P9 { o9 l
+ I) k/ V! A* v, B! g9 Q0 B+ L/ Odata=mxGetPr(prhs[0]); //获得指向矩阵的指针
' p: ]" L' V, x# {2 A8 |0 T! t( K: G; Z3 P% E( ~
& J4 @: e0 K9 g2 Q; h# w
, U; U( u- V V- c) }2 Q3 Z' G' MM=mxGetM(prhs[0]); //获得矩阵的行数
- k7 s' r+ J6 M' l9 H5 R: K; {8 O4 P1 c" e- I: Q2 F& L; m$ ?$ O
( B8 ~8 a% m3 T2 m& {0 o) @2 }, P2 E# }
N=mxGetN(prhs[0]); //获得矩阵的列数# `- t& O0 y+ W( ~+ m4 D
- J0 w6 x1 I; {8 s) S
7 M* s% ^, [; q: Z6 Z; A
G, z4 R4 `- @3 J3 Qfor(i=0;i<M;i++)
* \1 S3 a0 j' _
8 ]' x7 |. H9 e; L' d4 f
: r$ ?' k5 D# h5 L* d
& T# f: u. P% N5 U C( {{
0 A1 _* v9 m# L2 d
# {8 G1 m* M' L6 l; G% F$ f$ j; ^
5 w, k- |4 X& A s3 q) l @+ i8 o, e3 o) c: {/ P" l/ L
for(j=0;j<N;j++): o6 a5 q; I( y* x' E; [* }5 U
6 t. P1 B7 X* W2 _* w
- " x7 o/ c' Q1 r- L+ Y
- b! J, l" l. b. ^* t; @7 n9 u mexPrintf("%4.3f ",data[j*M+i]);
$ G" p, L' T! b- ~1 I9 `" U% |3 q
- 1 C t& g* X7 w' f& c) J
4 s- u( o' A4 {# ~( L3 c
mexPrintf("\n");
. `$ d( D; g% `4 O/ p- ?7 N3 r* S7 a: A3 Z0 i' f% Y
* g% u& r2 e& Q- j; E8 P2 N* e& ?# {# I- a- ^8 \6 A
}$ \9 t3 a5 \9 t6 M9 P# ?3 H
/ X3 l9 B; D- a( ^
% X" f* ]. _6 I+ X
0 K Q0 @) j5 v. ^ }
$ J! z$ T0 U7 a4 n9 b! m% {9 Y! n/ U
# g. R4 |7 w: V9 C; B/ u3 d0 f; N% z2 `& `( a3 g5 P0 o+ _, a
假如是一个形如[1,2,3;4,5,6]的矩阵,则执行上述后会先后打印出1,4,2,5,3,6 以上讲的都是输入参数,由于输入数据在函数调用前就已经在matlab里申请过内存,由于mex函数与matlab共用一个地址空间,因此通过prhs传递指针即可传入输入参数。但输出参数却需要在mex函数里申请内存,才能将指针放在plhs中传递出去。由于返回指针类似必须是mxArray,所以matlab专门提供了一个函数:mxCreateDoubleMatrix来实现内存申请,函数原型为: mxArray * mxCreateDoubleMatrix(int m,int n,mxComplexity ComplexFlag)m,n分别是待申请矩阵的行数和列数,需要注意的是为矩阵申请内存后得到的是mxArray类型的指针,就可以放在plhs[]中传递出去了。但是对这个矩阵的处理(包括赋值)却需要在mex函数或者c语言函数中完成,这就需要通过前面的mxGetPr或者mxGetScalar。使用mxGetPr获得指向这个矩阵的double类型指针后,就可以对这个矩阵进行各种操作和运算了。 还是拿这个栗子讲,这里由于输出是一个数,于是m,n都是1,通过mxGetPr进一步获得指向double数据类型指针z
5 x3 C3 e* B1 @1 L0 I+ S
0 H5 c% U9 b2 c" ]! V; h8 P( N( P @6 J
+ d+ Z1 E3 f1 {. X2 K
`' c1 d, x, P2 w
4 u F# u/ j" ` double *z;
1 a% g% a4 {. w8 y, I- V% f, H# v9 b5 T; v% \/ {
- - s$ @# b2 Y: k' p
8 ^# a# `7 w# n; M5 _1 x# S# l1 t" D
plhs[0] = mxCreateDoubleMatrix(1, 1, mxREAL);
- K: z/ A& f( J( u( j: n
5 ~6 ^5 i0 [) H1 P - 6 | t$ O+ W9 a
; T# X2 S& t9 q) {% c* a8 e+ O z = mxGetPr(plhs[0]);
) O' R3 \3 j# _1 k9 b% H& x; P6 f6 o0 `
- 0 @3 i) [- y, `
: b5 `9 O0 E8 g* u7 J6 b' Q' I
x = *(mxGetPr(prhs[0]));" D3 B: t6 z3 O4 t( B
7 ^$ ]5 H! b- ?: b3 G$ M1 e6 g - 6 ^. f; |$ G6 r) W! b1 b4 m
( w0 F6 U: v% O0 A
y = *(mxGetPr(prhs[1]));; Q) ]& v8 Y$ V/ A) _' L& q, u
2 }" e8 n$ Q8 u; c9 Z7 g9 G; d
4 l# s& q6 Q3 X+ }- W7 b: Z. y
6 U$ X9 v/ i( e2 R* ]: i9 M *z=x+y;) ?" _& h- o7 K
0 B. W; ]' {9 \4 {6 S3 z) e. @! @+ v k
当然,matlab里使用到的并不只是double类型这一种矩阵,还有字符串类型,结构类型矩阵等,并也提供了对应的处理函数。
9 |8 K- ]8 O1 k% `* y% n" m |