6 v% e% `2 S% q' t; S5 r+ Henum 8 u3 Y! Z A! n: S: M{! }! m* Q, V! @, A8 d' P( ?% A9 l
Red = 0,' y- z d; S6 n4 G% G8 p$ X3 G% L- H- G
Green = 1,' _4 p) A( H; o9 X# I; [
Blue = 20 F" T5 J& g. l# H, y% ^0 ? @
}; % s5 H8 Q0 X- B8 e; h% o0 Punsigned char rgb_image[576][720][3]; * c5 s; D& L9 _% [9 tint row = 40;0 V8 f m$ J( ]7 i
int col = 50;- a P( a2 @( `( ^8 ]( p2 G
unsigned char r = rgb_image[row][col][Red];# f6 M2 `1 }$ g4 O: h# k
unsigned char g = rgb_image[row][col][Green]; 2 I; S/ m* }9 Q; X* e1 Kunsigned char b = rgb_image[row][col][Blue]; 7 a1 ?. W: A) Z- N+ U9 ?, B数组指针以及与指针数组的区别 , L, S; A% Y& |- q% g+ |7 M- G数组指针,是一个指针, 指向的对象是数组。 数组指针的赋值,要求数组的长度匹配,否则会报错。当指向1维数组时, 需用用*取得数组对象的引用,再用下标来访问数组元素。 , v4 R# i+ o0 I指针数组,是一个数组, 数组保存的元素的类型是指针。6 Z4 S" o1 [; H" `" y- g) Q7 f
数组指针的定义1 n" H9 }" m$ S- [7 n
数组指针定义先定义一个数组。: l8 T, V- b, y
int a[10];6 W& Y/ @4 ]: I J% y/ q5 l- V% u
然后对数组里的名称用括号括起来后再在变量名称前面加个*号 ! U! ^* R" e; q7 W! B9 G5 j$ Wint (*a)[10]; # U% P7 B: z/ r后面你会发现函数指针定义类似。# v g2 s7 u$ d! ^% c
// 各类定义对比 . X s9 e# c$ c0 f1 o8 g; r. Qint a, *b, **c, d[10], e[10][20], *f[10], (*g)[10], *(*h)[10];9 W P; Z6 B' G' i
3 M7 G* z" a2 n
int *f[10]; // 指针数组, f是包含10个元素的数组, 数组里每一个元素的类型都是int* 8 D! T! r% R- \$ t/ n( Fint *(f2[10]); // 指针数组。另外一种定义方式。 / _0 X! Y' {, N9 q* H B2 eint(*f3[10]); // 指针数组。另外一种定义方式。 " h3 c9 ~" M3 B# f' B% Aint(f4)[10]; // int数组2 ~4 ]8 o' f+ |: z% V
int(*g)[10]; // 数组指针, g是一个指针, 这个指针可以指向类型是int,元素个数是10的数组 7 w, ?- D- B2 e5 V( W( g1 |0 bint* (*h)[10]; // 数组指针, h是一个指针, 这个指针可以指向类型是int*,元素个数是10的指针数组3 o4 K7 o$ P9 [1 ^7 D. d! c
; l+ y3 C* e; g# X3 H' g
int d[10]; : r; F0 y- ?" l+ o( fg = &d; 3 k! ?9 L; V4 ?+ O0 G ; {! F2 `& ^- S" _ y" k$ rint* e[10];; t6 [2 N, N. z ?: c
h = &e; $ _. k! L% O# G& r数组指针的使用 # y$ O5 V2 i+ k2 ^& `数组指针一般先通过*号取得指针指向的数组对象, 然后再用下标操作访问元素。/ k& k, `3 E4 X
; n/ g/ I0 ?7 a* B6 H3 D6 z* e# G
int a[10]; : g6 G1 ?/ w; K7 E; r: x/ Iint(*p_ary)[10] = &a; // p_ary是一个指针, 指向"int (*)[10]"类型的数组 9 B; h1 I- j& e: o$ L, V- Lfor (int i = 0; i < 10; i++) {" Y: J5 t5 [; y7 e9 T9 H" a
// p_ary是一个指向数组的指针, 需要先通过间接寻址运算符*(indirection operator)取得数组对象 1 Q* \) x4 l; C // 再通过下标操作访问元素。# X" ?: _+ s' ]9 X) A- ?$ q
(*p_ary) = i; # k$ p2 u3 w( v6 ~) u4 H1 S( F} 8 [0 H. u2 T8 y. q8 t8 `" U; A: ?1 @. P7 o
int b[10]; K5 ^, ?7 Q+ N% T6 r+ iint c[20]; * q7 \& l# z) C# u2 ~( O9 ^& Qp_ary = &b; // 合法 ! y3 n. R2 A2 S1 L# Q1 z op_ary = &c; // 报错! 不能将 "int (*)[20]" 类型的值分配到 "int (*)[10]" 类型的实体 , I- I% J. u2 `: F0 ^数组指针指向多维数组的子数组 ) N& i9 W; d hint a[10]; 8 z: t+ d( k! F8 `' mint b[4][10];& z3 W$ B0 |/ e, |/ v3 `
int(*p_ary)[10] = &a; % t& X! T/ I: N- vfor (int i = 0; i < 10; i++) { * _& M! _& A; V- Y (*p_ary) = 1; ; T0 [' S+ }6 B4 i9 U+ W0 W} . a* l4 e) M" k. h3 b # ~0 B6 j' z! Lp_ary = &b[2]; // 多维数组,可以看作数组的数组, ; A1 \$ R& I+ z6 p// b[2][0] ~ b[2][9]的值都被改成2了, |! h/ r- l9 P& v
for (int i = 0; i < 10; i++) { ' T* n% G/ K D9 u8 {/ ` (*p_ary) = 2;2 h% K7 z0 ~3 o
} $ t. A6 d. ~+ Z0 P. o9 i" Z& J0 e多维数组指针: m/ l; G# ~ |5 r. `8 ]' B
多维数组指针,是一种指针,指向的对象是个多维数组,支持多个下标操作。 B$ E3 I& }3 t/ W1 c# k0 x j* i$ G) w0 S- c& W+ g
int a[2][5][10]; & J) `+ K2 J; S: }% L# Tint(*p_ary1)[10] = &a[1][2]; // 1维数组指针 |% s% `% V) T8 _
int(*p_ary2)[5][10] = &a[1]; // 2维数组指针, @# j4 z0 E3 S/ ]8 W, L2 I
for (int row = 0; row < 5; row++) { - _3 q- F& Q0 z( A5 ? for (int col = 0; col < 10; col++) {4 N" ?8 R7 r" l# x. i5 c
(*p_ary2)[row][col] = row * col; ( _( | t2 n4 {' q* Y$ c; { }! z6 ^ S. ?1 t7 W P0 y
}. W0 U7 y- _- e6 o, W7 G
数组指针和指针数组对比实例9 T. \- X( x: q9 l
数组指针还是记住两步法即可 ~ U% n' @6 H7 m) X- A
6 b( J1 X- p5 S9 U- Z" f6 n0 C( \7 w
定义一个数组 9 N6 [7 n, X4 x1 b3 A O: y) Z括号包围1中定义的名称,再在名称前加个*号。 ' ~* T& L0 o& uint a[10];. i; W; n/ w$ Q
int(*ary_pointer1)[10] = &a; // 数组指针7 j+ A! m8 J3 H7 p
int* pointer_ary1[10]; // 指针数组。元素类型是int*( |& _& @; r5 l9 z2 d
int *(ponter_ary2[10]); // 指针数组。另外一种定义方式。 1 ~: O! O# r# i6 N9 Rint (*ponter_ary3[10]); // 指针数组。另外一种定义方式。. B3 Y" L) d. L- s' o2 X
int c, *d, (*ary_pointer2)[10], *pointer_ary3[10]; // 排列定义比较。 - |. Z3 r) b1 l7 ]& E" F8 {% X3 \0 ?, W) r& @/ D" b9 N
// 指针数组可以把每个元素指向数组对应位置的地址。& ~+ b2 n% O, J1 J& f' w
// 这样遍历指针数组, 可以达到遍历数组元素的效果,但是注意每个元素都是指针, " T) q2 Z% I P. u% z0 ^// 需要访问原数组的值的话, 需要对指针用*间接寻址运算符。5 @# @, c0 F3 e5 G5 S+ J
int* pointer_ary[10]; # n0 @- ^! s% x- P( {, F' Q1 e6 c$ Ofor (int i = 0; i < 10; i++) {6 k9 X' Z" q& Z5 l( j$ |4 D
pointer_ary = &a;6 A" B7 @& W1 n0 h- ?* _2 ^
} ! g, t+ Y, `+ h# T// 类似遍历原数组效果。 9 W. Y& M# \: Z1 z% u- f7 e& {for (int i = 0; i < 10; i++) { 2 A2 n; L ?/ B% a. d# |4 K *pointer_ary = i; // 修改原数组。; b9 G% ~! M* ]# N! s6 N
} 6 f3 J( V8 l0 e* o: Q# M: i# o' K! e* `/ f* {- y1 P: E% L
函数指针 & |/ I- \: D$ T! c& `1 ^& d' _取得函数地址2 |8 y. U/ G5 {3 i9 D5 I" K% H
函数的名称作为参数被传递时,会隐式转换成函数指针, 和在函数名称前加取地址符&等价。建议带上更加统一和清晰。 ; g/ U" O }" L2 C9 z3 O! N4 [0 O$ f' A3 P4 w2 j4 p5 T
void f(int); * z: U0 C$ f: I" w8 vint main()/ _0 G$ r# A c/ b4 K
{ 1 v. G' s9 i: W, L# |& K6 Y void (*p1)(int) = &f; 2 k( q7 Z- \2 U: n void (*p2)(int) = f; // same as &f) j/ U. t9 G/ }5 Q# ~. t; a: T) M
return 0;/ S5 W a) d5 Z3 u1 M
} # P5 A+ b2 [, F7 d0 z! Z. ]翻译成汇编代码, p1和p2的赋值是一样的。 8 _1 p! l. e2 r# O3 e 0 B: x _( z. e1 L 1 l2 }; n) _7 _" q) U9 R1 @' s; C. U: ^& p6 E+ j% o7 r
函数指针的声明 % c7 q2 t9 W2 \ S! k/ o单个函数指针变量定义步骤 2 R) A+ e# m. i" O& e( b定义一个函数。void fun1(int a, int b); int fun2(double a); 6 ^+ K' ?3 g; j' m, D% q用括号把函数名称包围起来,然后在名称前面加*号。void (*fun1)(int a, int b); int (*fun2)(double a); % p; D8 U$ D3 I l: ~如果要定义函数指针数组,在定义单个函数指针的基础上,在名称后面加上[数组长度]void (*fun1[2])(int a, int b); int (*fun2[10])(double a); 0 Z. T( `8 y7 j3 ?" ytypedef定义函数指针5 a9 t0 I+ Q1 V5 B9 {& i8 M
可读性高比单个定义要高,特别是声明多个同类型的函数指针,或者函数指针数组。! \. Q; w* t6 D' f7 w) f) I
! v$ e9 _/ w4 B, X5 ]! ?6 _3 |
typedef定义函数指针的语法. f4 N: P3 }# g
typedef有两种做法, 一种就是定义一种函数对象,另外一种就是定义函数指针。用法稍稍不同,效果是一样。其中函数对象不支持赋值, 但是支持引用。! }8 q! q( O* w% }
9 `! H' D b7 B4 g3 H- O5 x# F1 \9 \
typedef int FuncObject(int a, int b); // FuncObject类型是函数对象 ' d. [1 n, Q# `1 D2 k; ptypedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针 ) w; J& }8 y. k6 w9 @8 E) AFuncObject* f1 = &Add; / b, R1 ?" \5 f7 `4 hFuncPointer f2;- W- V, P3 s6 E0 q$ _
f2 = f1; // f1, f2类型一样, 都是形式为int(int, int)的函数的指针。, E" I: t5 z, v h7 z3 z* T* x
FuncObject f3 = Add; // 报错! 函数对象不支持拷贝 * l+ {4 V) C/ I0 @; ?5 lFuncObject f4 = &Add; // 报错!&Add是函数指针,与函数对象类型不匹配 ( M) ~7 _4 N; [$ \, w6 sFuncObject& f5 = Add; // 正确. n' {3 l' ~0 K! T1 {: ?$ |
int ret = f5(2, 3); // 正确 & P9 y9 W. Y- X2 uFuncObject& f6 = &Add; // 报错!&Add是函数指针,与函数对象引用类型不匹配4 ~2 [2 Q6 j4 C1 H0 S7 R
如何记住typedef定义函数指针的步骤 8 s0 A1 W' L) I" v. S x- d像定义一个函数指针那样, 指定一个名称。int (*CalFun)(int a, int b); & h' w5 l+ }9 Z. ?7 v在这个函数指针变量声明前面加上typedef。typedef int (*CalFun)(int a, int b);) r3 E4 Q; H8 G' x9 A: P3 Q1 z; S
完整例子! B: D2 ` W) `9 D
typedef int(*CalFun)(int a, int b); 1 o4 M D8 `# p5 i. P7 X1 p 7 @7 k7 P) v# ]/ r( a* W( Uint Add(int a, int b) $ v. z) r" T" p1 S3 G+ W{, y; g/ j# f) M- d8 q0 g
return (a + b);+ L0 `+ J' k1 |" `5 d, U
} 7 z5 O9 O3 z5 Q, ?/ q/ v) r( ? k5 j5 w0 g
int Sub(int a, int b) - s& N: E( ~( _( y% c6 k8 x2 j; W{ / p, q7 I$ H& F6 h. M; T/ s& N return (a - b); 8 V6 M5 w4 k+ `% S}( F' v# @! z$ }( ?) L6 j: M
4 N( V6 `& }# r% Z8 yint main(int argc, char** argv)" H6 l% E; g8 y, t6 M+ s
{$ l/ v5 A1 Q5 |( y% I8 r: |
CalFun f1 = Add; & R+ m& |9 B" B! L3 h CalFun f2 = Sub;2 j; l) ]# X' N6 g, t3 i1 x1 j
int a = f1(2, 3); ( o" p4 D, h: Q2 \ int b = f2(10, 5);4 J+ R6 A4 m9 V0 |: q+ a
' J4 r3 D/ E: o( f4 [4 V // typedef定义的函数指针数组。 3 r- _ S; C$ S7 d' y CalFun f_ary[2];1 v8 X6 l1 d- z$ |8 S
f_ary[0] = Add; : J8 e6 A( x, c1 n2 N: z f_ary[1] = Sub;) r6 w- P- L, k1 [" Y5 t
* L1 ^7 Q$ `; o/ C // 单个定义的函数指针数组。 ! _& j1 x9 W$ F' w int(*f_ary2[2])(int a, int b);$ h0 D9 g: z, ?% o- o! f
f_ary2[0] = Add;; x% F. O+ q; q% _+ m
f_ary2[1] = Sub;* ?' t$ m; ]" u: F
# t/ I! i5 |/ u$ a) lusing别名定义函数指针) O; n7 _! g8 l7 @
c++11以后的类型别名定义--using也可以用于定义函数指针, typedef的好处它都有,个人感觉比typedef更直观。using类型别名同样分函数对象和函数指针两种方式。 * {. Y% j( G% ]7 O0 l; n 4 l3 t) X4 j( Y2 s8 b7 Atypedef int FuncObject(int a, int b); // FuncObject类型是函数对象 9 c* G: ~! c& l z! F6 `using FuncObject = int(int a, int b); " o- c! r- Z2 k2 c2 n1 w" ltypedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针+ [5 B3 F" S% G3 s, [6 a. Y
using FuncPointer = int(*)(int a, int b);+ n7 j; P2 n* P3 L
函数指针的调用 G! S5 Y) l1 m% h* K; b7 }函数指针和函数对象都可以直接后加括号调用1 u0 E8 p% O1 P- Q6 C
int f();$ o' R/ i9 w# Q; n6 ?- B, o: H
int (*p)() = f; // pointer p is pointing to f 0 r: _. D) A7 o @7 U- hint (&r)() = *p; // the lvalue that identifies f is bound to a reference 6 U# A7 B# f8 b6 u* a+ T% J: {r(); // function f invoked through lvalue reference / n; V0 d0 w2 ?. h3 G/ u(*p)(); // function f invoked through the function lvalue $ b" ^6 ~( u. Mp(); // function f invoked directly through the pointer4 J q N# i7 \/ {4 F$ @
如果函数有重载, 函数指针会指向匹配的那个版本。 1 U) O+ u4 ~$ K8 k+ ~3 S( Ptemplate<typename T> * B7 [7 n% _+ o+ u9 c) {( JT f(T n) { return n; }, {# Y8 a# g A% L; G
$ G! t+ s; t* V6 q
double f(double n) { return n; }& E4 j) p: {4 m2 ? {% q4 a) M
; V, b) W. i6 G! w) V
int main() ' Q& k z* h) X: z{ 1 m# H4 w3 y& J0 t int (*p)(int) = f; // instantiates and selects f<int> ' ?$ B' _$ ] r} 8 M+ S M$ p1 [8 ]1 Q. q7 _成员函数指针 5 ~* s- P2 C: h静态成员函数,除了增加了访问控制以外,跟普通的函数指针没什么区别,所以普通函数指针可以直接指向类的静态成员函数。但非静态的成员函数与普通函数指针不太一样,声明时需要指定函数归属的类名,并且调用需要指定对象实例。 m( s. J, G2 q1 p% _+ _3 b
2 g: @* _$ i% |" y+ g7 x0 L: C成员函数指针定义。1 L, B" C0 p' A1 z
像定义类成员函数实现那样写, 并任意指定名称,这里作func。void ClassName::func(int); 6 g1 W ?0 B8 |8 S9 s9 F括号把类名、范围解析运算符::、名称包围起来。void (ClassName::func)(int); 9 Y8 }% [! I9 g在名称的前面加个*号void (ClassName::*func)(int);* l$ ^! t1 {8 n- e) ]) V8 q
成员函数也支持typedef和using的定义方式。typedef void(C::* MemberFunc)(int); using MemberFunc = void(C::*)(int); 5 s+ ~7 c* R6 C4 k6 X成员函数指针如何调用。 4 N$ U. ?& m3 ?" Z/ ?) W5 g假设成员函数指针名字为func3 ^/ g: [5 ?3 O1 p
$ W& t5 y" N; A' y% S6 K
void (ClassName::*func)(int); & Y, B4 e) y2 O" U A+ v! u对象式调用。 , C; T* M! q9 D) \% ?ClassName c; // 被调用的对象! _, z5 ?" x; d4 r7 j
成员函数指针名字当作正常函数那样写。 : V; q5 {- ]: W7 ] c7 X$ I9 Jc.func(3);. G7 H% |! L3 J2 P3 L
成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。 + g6 r5 }# ?) \' D" i7 s& Mc.*func(3);7 n' c- D# |2 d% ~
最后用括号把调用对象、成员访问运算符.、间接寻址运算符*、和成员函数指针的名称包围起来。 0 m; N* ~6 I' Q8 U0 o% q(c.*func)(3);% R2 T6 D0 s5 ?/ \
为何要加上括号? 根据c++的优先级标准,取成员运算符. > 函数调用() > 间接引用符*。 *号优先级比函数调用要低, 成员函数指针还没取得对象就被调用了,自然报错。 另外6 [2 H+ i5 _) ]+ g I+ u4 k/ Z' E3 F6 E
(c.(*func))(3);. ]- j+ z& J! k6 C |: C
这样的写法也不行。 .*和->*是整体作为一个运算符的,中间不能用括号隔开。' v* \8 j& K1 u/ \5 m9 C
指针式调用# T* e z- w4 r i' O5 `: Y0 w5 \5 T
ClassName* p; // 被调用的对象的指针 ) u1 ^# T! O o% h( O成员函数指针名字当作正常函数那样写。 " _/ `1 M! y/ H6 A+ pp->func(3); 4 v. a+ ^* z' X, E6 x成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。 7 ~ s& p. O W' B& K- U7 c( `8 Np->*func(3); ) O$ {4 y. @ y8 p* }! T最后用括号把调用对象、成员访问运算符->、间接寻址运算符*、和成员函数指针的名称包围起来。 |, r4 z6 E( N7 @8 D) d(p->*func)(3); 0 S# F# F- T! Q B函数指针使用完整例子 ; h9 s, x. r2 ~; b0 ~struct Cal ( `+ n1 y Q- R2 p9 B{ 0 X% [) U" C; p& ~1 I9 O" B" E0 T1 Q$ Z9 E
int add(int a, int b); 9 Q$ B* r7 }+ g$ w9 _8 W
int sub(int a, int b);/ |! \& `2 _; I6 ~' S
};6 w) J9 B% a3 ]! ^& b
* B i: p2 ^1 ~7 L" w/ d tint main() " ?& C S7 w. P6 Z z{ # W. `* z" x4 v/ N Z2 p int (Cal::*fun)(int, int) = &Cal::add; " H4 P( o# Q; R2 i fun = &Cal::sub;% T! v/ d; }8 N$ V. l- {0 Y
+ w) r6 Y2 X5 e! b4 }1 l Cal* p_cal = new Cal();1 `( B+ o% v) F; X2 y& L7 \! N
int r1 = (p_cal->*fun)(2, 3); ! m1 p9 T, {5 ?+ x delete p_cal;, e5 L3 D) p3 b3 q" ^( y
( a# b) j0 u5 Q- P; t+ c5 H
Cal local_cal;8 ~: t- r4 Y: t1 B. s* g1 u
int r2 = (local_cal.*fun)(8, 6);+ d' B, ?' S3 J/ R5 n' L; o
}. y: @6 ~- E2 A" T$ s! ~/ \; z7 Q2 _
% { N A% Q& c _
成员变量指针 ) U( ~5 X1 W; h, D: l成员变量指针比成员函数指针还要简单些,没有函数调用, 无需考虑函数调用和间接引用符*的优先级问题。 ! Z* }" M7 p) d1 x0 H ' e" T# k& l5 x& h |; c成员变量指针的定义, i: d5 u3 o8 V7 [3 u
假如以下结构体C。, N5 ~+ R, h- J4 a$ p- B! @
9 y7 m4 Q2 s% r- s" |
struct C 6 p( ?5 i6 M D$ ?% d{ ; n q$ T; ^1 F0 }+ W3 U7 c) U int m; " h' g9 c0 b5 m9 t6 ?};3 j% ^5 a! F6 k( N
单个成员变量指针定义 ! I% }6 R) O' U+ w. @
假设名称为p, 类似静态成员变量定义那样声明 , m3 F( f) p& L- q( ^' x7 d" I7 l! fint C::p; " S/ P# \) l/ E( n- S在名称前面加上指针标识号* F. c; s* B+ D' G1 h" l( b
int C::*p;3 r; [9 V" E J- y
typedef或using方式定义 & B$ P3 j5 R$ vtypedef int C::*MemberPointer; , ]# u+ |2 D1 K+ n+ E; p4 S4 p) r% |using MemberPointer = int C::*;5 d! r: y4 T) x: D
成员变量指针的使用。' L. S: R5 }5 g( ^
类似成员函数指针那样,直接使用指向成员的指针运算符:.* 和->*即可。 ! w5 e/ D: [4 J成员变量指针, 能让我们实现一些遍历成员的动态功能。 例如把一个类/结构体的多个同类型的成员变量放进一个容器里,然后遍历访问这些成员变量。 A6 I- _9 D$ }& _2 [# Q
5 I% Y6 W+ T/ N 完整例子 $ f) }& F% y/ ~" I2 t+ C( _struct C { int m; }; 4 L+ B5 D* j" J8 A \9 w. wint main() / j5 n) Q- \% C9 j8 m3 [) ?9 P{5 H" Z- Y0 J5 H. b/ ?$ V
int C::* p = &C::m; // pointer to data member m of class C 0 s2 L" {1 V2 N C c = {7}; a1 x9 W" M0 H( Z, N# R& q6 u
std::cout << c.*p << '\n'; // prints 7 ( W. X" N0 S4 d- K/ z- s C* cp = &c;% [+ F% ?: [; f& l) } g
cp->m = 10;0 T( |7 u0 \+ k: P
std::cout << cp->*p << '\n'; // prints 10 6 a( o! v' D7 Y* V8 d R: t3 S$ P9 z
———————————————— 3 a* o d- R; c+ {/ {' l版权声明:本文为CSDN博主「南风fahaxiki」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 ! s8 b% ^3 }/ R, i# @3 l' ^原文链接:https://blog.csdn.net/m0_64407685/article/details/126788115 # T* Z' j# I0 n0 y1 E# ], } [ {3 D, I+ k) N