登录 免费注册 首页 | 行业黑名单 | 帮助
维库电子市场网
技术交流 | 电路欣赏 | 工控天地 | 数字广电 | 通信技术 | 电源技术 | 测控之家 | EMC技术 | ARM技术 | EDA技术 | PCB技术 | 嵌入式系统
驱动编程 | 集成电路 | 器件替换 | 模拟技术 | 新手园地 | 单 片 机 | DSP技术 | MCU技术 | IC 设计 | IC 产业 | CAN-bus/DeviceNe

求助:在GCCAVR中C++的类成员函数如何定义函数指针数组

作者:hotpower 栏目:单片机
求助:在GCCAVR中C++的类成员函数如何定义函数指针数组
在GCCAVR一般的函数指针数组可以定义为:

typedef void (* PFV)(void);
PFV KeyCommandTab[] PROGMEM = {
  Key00,
  Key01,
  Key02,
  Key03,
  Key04,
};


prog_void (*KeyCommandTab[])(void) = {
  Key00,
  Key01,
  Key02,
  Key03,
  Key04,
};

调用函数可以为:
KeyCommandTab[0]();//调用Key00();
//...............................
KeyCommandTab[4]();//调用Key04();

其中: Key00(),..Key04()为普通的C函数.

如果它们为C++的Key类的成员函数,又该如何定义呢???
下列定义为何走不通???
prog_void (*KeyCommandTab[])(void) = {
  Key.Key00,
  Key.Key01,
  Key.Key02,
  Key.Key03,
  Key.Key04,
};

直接调用都没问题,如Key.Key00();
但想用散转(函数指针)就...难道类成员函数根本就不能用函数指针???

谢谢用过类成员函数指针的网友!!!


2楼: >>参与讨论
hotpower
自己再顶一下
现在该下地农忙了...

谢谢用过的网友,暂且用一般函数吧.有结果当然还是想用成员函数了.

3楼: >>参与讨论
John_Lee
理论上...
C++ 的类(class)和 C 的结构(struct)是一样的,都是自定义类型。作为类型,在语法规则上其实和语言的内部类型是一样的,只不过内部类型不用声明而已。类型在声明(declare)时,是不能强加存储修饰的,而只能在定义(define)变量时,加上存储修饰和“section”修饰。实际上就是说,一个变量(内部类型或自定义类型)只能作为一个整体存储在相同的存储空间。

而你的意思好像是想:把“自定义类型”中的一部分数据放在另外的存储空间,也就是说,你想把一个变量分为两个存储空间!你自己说行吗?


* - 本贴最后修改时间:2005-6-27 15:23:25 修改者:John_Lee

4楼: >>参与讨论
宇宙飞船
请问是否是在class中的成员函数中定义数组?
我对标题有点混,hotpower 把标题的意思说清楚一些,咱们来想想。。。

请问是否是在class中的成员函数中定义数组?

数组是指:
         1  一般函数数组指针?
         2  类内的函数数指针?
           


5楼: >>参与讨论
hotpower
谢谢两位网友的回复
特别感谢John_Lee.

宇宙飞船:
    我一直在执行键盘命令时,若汇编就使用散转,C就用函数指针.

但在C++中,为了"面子"就定义了键盘处理类,其中有每个键的命令执行函数K00(),K01()...

由于每个键要求最少有三个事件需要处理,即
1.键释放事件处理
2.单击键事件处理
3.长压键事件处理

既然已经定义了键盘处理类,那么就自然想在类中"散转",
即函数指针数组的值(函数地址/函数名)想使用类成员函数名而不想使用全局函数名.

John_Lee说得很在理!!!谢谢...不过还是不甘心...

但此事让我联想起了我以前的"求助: GCCAVR如何不编译.bss段内的初始化代码?"----http://www.21icbbs.com/club/bbs/ShowAnnounce.asp?id=1531283


testcode曾来了个"一帖惊醒梦中人!!!"
testcode教我把整个类的不初始化,这次求助难度可能会更大...


期待外星人带给老农一个"一帖烧醒梦中人!!!"

如果本主题能得到,必然有点"火星撞地球"的味道!!!

http://www.21icbbs.com/club/bbs/ShowAnnounce.asp?id=1531283

6楼: >>参与讨论
hotpower
郁闷呀...又是求助无望...
 
7楼: >>参与讨论
testcode
可以使用指向类成员的指针
class Key
{
public:
        Key();
        void Key00(CHAR key);
        void Key01(CHAR key);
        void Key02(CHAR key);
        void Key03(CHAR key);
        void Key04(CHAR key);    
};

Key::Key()
{
}

void Key::Key00(CHAR key)
{
    PORTB = key;
}

void Key::Key01(CHAR key)
{
    PORTB = key;
}

void Key::Key02(CHAR key)
{
    PORTB = key;
}

void Key::Key03(CHAR key)
{
    PORTB = key;
}

void Key::Key04(CHAR key)
{
    PORTB = key;
}

typedef void (Key::*ACTION)(CHAR key);

ACTION KeyCommandTab[]= {
    &Key::Key00,
    &Key::Key01,
    &Key::Key02,
    &Key::Key03,    
    &Key::Key04,        
};

Key *myKey;

int main(void)
{    
    (myKey->*KeyCommandTab[0])(20);
    (myKey->*KeyCommandTab[1])(35);
    
    while(1);
}

////////////////////////////////////////////////////////////////
摘自: <C++ Primer>>
成员函数有一个非成员函数不具有的属性——它的类its class 指向成员函数的指针必须与向其赋值的函数类型匹配不是两个而是三个方面都要匹配: 1. 参数的类型和个数 2. 返回类型 3. 它所属的类类型
在成员函数指针和普通函数指针之间的不匹配是由于这两种指针在表示上的区别:
函数指针存储函数的地址可以被用来直接调用那个函数关于函数指针;
成员函数指针首先必须被绑定在一个对象或者一个指针上才能得到被调用对象的this 指针,然后才调用指针所指的成员函数.

8楼: >>参与讨论
农民讲习所
对,用::来访问类的成员,不是用.
 
9楼: >>参与讨论
宇宙飞船
高手真多!
 
10楼: >>参与讨论
John_Lee
两个方法
1、用 testcode 所说的指向成员函数的指针(pointer to member function)的方法。
例:
#include <avr/io.h>
#include <avr/pgmspace.h>

class foo_t;

typedef void (foo_t::*func_t) (uint8_t);

class foo_t
{
public:
    void func1 (uint8_t);
    void func2 (uint8_t);
    void func3 (uint8_t);
    void func4 (uint8_t);
    void func5 (uint8_t);
    static func_t func_array[5];            //    要说明为静态成员
    void func (uint8_t index, uint8_t arg);
};

func_t foo_t::func_array[5] PROGMEM = {        //    指针数组放在 progmem 中
    &foo_t::func1,
    &foo_t::func2,
    &foo_t::func3,
    &foo_t::func4,
    &foo_t::func5
};

void foo_t::func (uint8_t index, uint8_t arg)
{
    uint16_t addr = pgm_read_word (&func_array[index]);    //    取 funcx 地址
    func_t f;
    asm volatile ("" : "=r" (f) : "0" (addr));            //    强制转换,我试了很多其它转换语法,都不行,只有这样
    (this->*f) (arg);
}

但是,用此方法编译出的代码,我看不懂。代码如下:
.GLOBAL    _ZN5foo_t10func_arrayE
    .section    .progmem.data,"a",@progbits
    .type    _ZN5foo_t10func_arrayE, @object
    .size    _ZN5foo_t10func_arrayE, 20
_ZN5foo_t10func_arrayE:
    .word    pm(_ZN5foo_t5func1Eh)
    .word    0
    .word    pm(_ZN5foo_t5func2Eh)
    .word    0
    .word    pm(_ZN5foo_t5func3Eh)
    .word    0
    .word    pm(_ZN5foo_t5func4Eh)
    .word    0
    .word    pm(_ZN5foo_t5func5Eh)
    .word    0
    .text
.GLOBAL    _ZN5foo_t4funcEhh
    .type    _ZN5foo_t4funcEhh, @function
_ZN5foo_t4funcEhh:
/* prologue: frame size=0 */
    PUSH r28
    PUSH r29
/* prologue end (size=2) */
    movw r18,r24
    mov r30,r22
    clr r31
    lsl r30
    rol r31
    lsl r30
    rol r31
    subi r30,lo8(-(_ZN5foo_t10func_arrayE))
    sbci r31,hi8(-(_ZN5foo_t10func_arrayE))
/* #APP */
    lpm r24, Z+
    lpm r25, Z
    
/* #NOAPP */
    movw r26,r24
    movw r22,r26
    movw r24,r28
    sbrc r24,0
    rjmp .L2
    movw r30,r26
    rjmp .L3
.L2:
    movw r30,r24
    asr r31
    ror r30
    add r30,r18
    adc r31,r19
    ld __tmp_reg__,Z+
    ld r31,Z
    mov r30,__tmp_reg__
    add r30,r22
    adc r31,r23
    ld __tmp_reg__,Z+
    ld r31,Z
    mov r30,__tmp_reg__
.L3:
    asr r25
    ror r24
    mov r22,r20
    add r24,r18
    adc r25,r19
    icall
/* epilogue: frame size=0 */
    pop r29
    pop r28
    ret

2、用静态函数
例:
#include <avr/io.h>
#include <avr/pgmspace.h>

typedef void func_t (uint8_t);

class foo_t
{
public:
    static void func1 (uint8_t);            //    说明为静态
    static void func2 (uint8_t);
    static void func3 (uint8_t);
    static void func4 (uint8_t);
    static void func5 (uint8_t);
    static func_t *func_array[5];
    void func (uint8_t index, uint8_t arg);
};

func_t *foo_t::func_array[5] PROGMEM = {
    func1, func2, func3, func4, func5
};

void foo_t::func (uint8_t index, uint8_t arg)
{
    func_t *f = reinterpret_cast<func_t *> (pgm_read_word (&func_array[index]));
    f (arg);
}

这种方法编译出的代码很清晰,如下:
.GLOBAL    _ZN5foo_t10func_arrayE
    .section    .progmem.data,"a",@progbits
    .type    _ZN5foo_t10func_arrayE, @object
    .size    _ZN5foo_t10func_arrayE, 10
_ZN5foo_t10func_arrayE:
    .word    pm(_ZN5foo_t5func1Eh)
    .word    pm(_ZN5foo_t5func2Eh)
    .word    pm(_ZN5foo_t5func3Eh)
    .word    pm(_ZN5foo_t5func4Eh)
    .word    pm(_ZN5foo_t5func5Eh)
    .text
.GLOBAL    _ZN5foo_t4funcEhh
    .type    _ZN5foo_t4funcEhh, @function
_ZN5foo_t4funcEhh:
/* prologue: frame size=0 */
/* prologue end (size=0) */
    mov r30,r22
    clr r31
    add r30,r30
    adc r31,r31
    subi r30,lo8(-(_ZN5foo_t10func_arrayE))
    sbci r31,hi8(-(_ZN5foo_t10func_arrayE))
/* #APP */
    lpm r18, Z+
    lpm r19, Z
    
/* #NOAPP */
    mov r24,r20
    movw r30,r18
    icall
/* epilogue: frame size=0 */
    ret


11楼: >>参与讨论
hotpower
一"雨"浇醒梦中人
非常感谢John_Lee!!!!!!!!!!

特别特别感谢testcode!!!!!!!!!

一个"&"号断送了老农的"前程"...这个"雨"---"&"太妙了!!!

在MCU中应用C++确实有些"难为情",但它的风格实在令人难以忘怀...

我明白testcode肯定能够解决这个问题,虽然眼花但很"真情"..."相信您没错"...

两次的救助不知该如何答谢???

特别感谢上次的将整个类不初始化!!!
#define __noinit__ __attribute__ ((section (".noinit"))) //变量不初始化

__noinit__ RtcObj  Rtc;
__noinit__ LedObj  Led;
__noinit__ KeyObj  Key;
__noinit__ CTRLObj CTRL;


只能以此程序向帮助我的网友们报答我的一片真心...

class KeyObj;//键盘模块

class KeyObj {
private:
  unsigned CHAR KeyVal;//每20ms测试的键值(有1无0/有低无高)
  unsigned CHAR KeyCount;//键盘计数器
  unsigned int KeyTest[8];//2.5mS压键计数器
public:
  KeyObj(void);
  void KeyInit(void);
  unsigned CHAR GetKeyVal(void);//
  void KeyCommandExec(unsigned CHAR);
  void Exec(void);
  void Key00(void);
  void Key01(void);
  void Key02(void);
  void Key03(void);
  void Key04(void);

  void Key10(void);
  void Key11(void);
  void Key12(void);
  void Key13(void);
  void Key14(void);

  void Key20(void);
  void Key21(void);
  void Key22(void);
  void Key23(void);
  void Key24(void);
};//键盘模块

__noinit__ KeyObj  Key;


//取串行键盘键值
inline
unsigned CHAR KeyObj::GetKeyVal(void)
{
unsigned CHAR KeyVal = 0;
  PORTC &= ~(1 << KeyCP);//脉冲锁存74HC165并行数据开始
  _NOP();//延时
  PORTC |=  (1 << KeyCP);//脉冲锁存74HC165并行数据结束
  for(int i = 8; i > 0; i --)
  {
    KeyVal <<= 1;
    PORTC &= ~(1 << KeyCLK);//发送读键脉冲
    _NOP();//延时
    if (!(PINC & (1 << KeyData))) KeyVal ++;//数据'1',有键压下为‘1’
    PORTC |=  (1 << KeyCLK);//发送读键脉冲
  }
  return KeyVal;//无键压下为0
}

//键扫描及运行(每2.5mS运行一次,实现“零耗时消除键盘抖动”)
inline
void KeyObj::Exec(void)//此程序套在定时中断中
{
static const unsigned CHAR KeyTab[8] PROGMEM = {//只取当前键值表
  0b10000000,//键盘0
  0b01000000,//键盘1
  0b00100000,//键盘2
  0b00010000,//键盘3
  0b00001000,//键盘4
  0b00000100,//短接线0
  0b00000010,//短接线1
  0b00000001 //短接线2
};
  KeyCount &= 0x07;//只有8个键
  KeyVal = GetKeyVal();//取所有键位值
  if (KeyVal & pgm_read_byte(&KeyTab[KeyCount])) {//有键压下(每次只取1键以实现“零耗时消除键盘抖动”)
    if (KeyTest[KeyCount] < 1200) {//1200*2.5mS=3S//压键长短判别
      KeyTest[KeyCount] ++;//计数每20mS键盘压键的次数(选2较好些)
      if (KeyTest[KeyCount] == 2) {//键刚压下//短压键
        KeyCommandExec(1);//短压键事件处理(1)
      }
    }
    else {//长压键 2.5mS*1200=3S
      KeyTest[KeyCount] = 2;//再压3S继续执行下一次长压键
      KeyCommandExec(2);//长压键3S事件处理(2)
    }
  }
  else {//无键压下
    if (KeyTest[KeyCount] >= 2) {//键释放
      KeyCommandExec(0);//键释放事件处理(0)
    }
    else {//无键压下或干扰
    }
    KeyTest[KeyCount] = 0;//清除压键计数器
  }
  KeyCount ++;//为下次扫描做准备
  KeyCount &= 0x07;//只有8个键
}


typedef prog_void (KeyObj::* PFV)(void);
void KeyObj::KeyCommandExec(unsigned CHAR mode)
{
static const PFV KeyCommandTab[3][5] = {//键盘事件处理表
  {&KeyObj::Key00, &KeyObj::Key01, &KeyObj::Key02, &KeyObj::Key03, &KeyObj::Key04}, //键释放事件处理
  {&KeyObj::Key10, &KeyObj::Key11, &KeyObj::Key12, &KeyObj::Key13, &KeyObj::Key14}, //压键事件处理
  {&KeyObj::Key20, &KeyObj::Key21, &KeyObj::Key22, &KeyObj::Key23, &KeyObj::Key24}  //长压键事件处理
};
  KeyCount &= 0x07;//取键盘序号
  if (KeyCount <= 4) {//只有5个键,KeyCount>=5为3个短接线
    (::Key.*KeyCommandTab[(int)mode][(int)KeyCount])();//运行KeyX0()~KeyX4()
  }
}

/*------------------------
     键释放事件处理
------------------------*/
void KeyObj::Key00(void)
{
}

void KeyObj::Key01(void)
{
}

void KeyObj::Key02(void)
{
}

void KeyObj::Key03(void)
{
}

void KeyObj::Key04(void)
{
}

/*------------------------
     压键事件处理
------------------------*/
void KeyObj::Key10(void)
{
}

void KeyObj::Key11(void)
{
}

void KeyObj::Key12(void)
{
}

void KeyObj::Key13(void)
{
}

void KeyObj::Key14(void)
{
}

/*------------------------
     长压键事件处理
------------------------*/
void KeyObj::Key20(void)
{
}

void KeyObj::Key21(void)
{
}

void KeyObj::Key22(void)
{
}

void KeyObj::Key23(void)
{
}

void KeyObj::Key24(void)
{
}





* - 本贴最后修改时间:2005-6-28 23:12:27 修改者:hotpower

http://blog.21ic.org/more.asp?NAME=hotpower&id=1277

12楼: >>参与讨论
testcode
static const PFV KeyCommandTab[3][5] PROGMEM必须强制转换指针
static const PFV KeyCommandTab[3][5] PROGMEM必须强制转换指针。
/*******************************************************************/
typedef prog_void (KeyObj::* PFV)(void);
void KeyObj::KeyCommandExec(unsigned CHAR mode)
{
  static const PFV KeyCommandTab[3][5] PROGMEM = {//???????
  {&KeyObj::Key00, &KeyObj::Key01, &KeyObj::Key02, &KeyObj::Key03, &KeyObj::Key04}, //???????
  {&KeyObj::Key10, &KeyObj::Key11, &KeyObj::Key12, &KeyObj::Key13, &KeyObj::Key14}, //??????
  {&KeyObj::Key20, &KeyObj::Key21, &KeyObj::Key22, &KeyObj::Key23, &KeyObj::Key24}  //???????
};
  KeyCount &= 0x07;//?????
  if (KeyCount <= 4) {//??5??,KeyCount>=5?3????
  uint16_t  addr;
  PFV f;    
  addr = pgm_read_word(&KeyCommandTab[(int)mode][(int)KeyCount]);    //    ? funcx ??
  asm volatile ("" : "=r" (f) : "0" (addr));            //    ????,???????????,???,????
  (this->*f) ();//??KeyX0()~KeyX4()       
  }
}

//另以下语句编译不通。
(::Key.*KeyCommandTab[(int)mode][(int)KeyCount])();//运行KeyX0()~KeyX4()
如下改时可以通过编译:
(this->*KeyCommandTab[(int)mode][(int)KeyCount])();//??KeyX0()~KeyX4()

13楼: >>参与讨论
hotpower
两种写法都编译通过,但不能运行
1.    (this->*f) ();//运行KeyX0()~KeyX4()     
2.    (Key.*f) ();//运行KeyX0()~KeyX4()      


ICALL时的Z=0,故此转换成了"软启动"...看来此法不可靠...

再:
static const PFV KeyCommandTab[3][5] PROGMEM = {//键盘放事件处理表//使用1710字节,但运行不通!!!
//static const PFV KeyCommandTab[3][5] = {//键盘放事件处理表//使用1650+60字节(.text+.data),运行正确
  {&KeyObj::Key00, &KeyObj::Key01, &KeyObj::Key02, &KeyObj::Key03, &KeyObj::Key04}, //键释放事件处理
  {&KeyObj::Key10, &KeyObj::Key11, &KeyObj::Key12, &KeyObj::Key13, &KeyObj::Key14}, //压键事件处理
  {&KeyObj::Key20, &KeyObj::Key21, &KeyObj::Key22, &KeyObj::Key23, &KeyObj::Key24}  //长压键事件处理
};

好像PFV指针的长度为60字节(.data)/15=4字节/每个指针宽度

268:          asm volatile ("" : "=r" (f) : "0" (addr));
+00000093:   019C        MOVW    R18,R24          Copy register pair
+00000094:   01CA        MOVW    R24,R20          Copy register pair
+00000095:   01B9        MOVW    R22,R18          Copy register pair
269:          (this->*f) ();//运行KeyX0()~KeyX4()     
+00000096:   FD80        SBRC    R24,0            Skip if bit in register cleared
---- No Source ------------------------------------------------------------------------------------
+00000097:   C002        RJMP    PC+0x0003        Relative jump
+00000098:   01F9        MOVW    R30,R18          Copy register pair
+00000099:   C00D        RJMP    PC+0x000E        Relative jump
+0000009A:   01FC        MOVW    R30,R24          Copy register pair
+0000009B:   95F5        ASR     R31              Arithmetic shift right
+0000009C:   95E7        ROR     R30              Rotate right through carry
+0000009D:   0FEA        ADD     R30,R26          Add without carry
+0000009E:   1FFB        ADC     R31,R27          Add with carry
+0000009F:   9001        LD      R0,Z+            Load indirect and postincrement
+000000A0:   81F0        LDD     R31,Z+0          Load indirect with displacement
+000000A1:   2DE0        MOV     R30,R0           Copy register
+000000A2:   0FE6        ADD     R30,R22          Add without carry
+000000A3:   1FF7        ADC     R31,R23          Add with carry
+000000A4:   9001        LD      R0,Z+            Load indirect and postincrement
+000000A5:   81F0        LDD     R31,Z+0          Load indirect with displacement
+000000A6:   2DE0        MOV     R30,R0           Copy register
+000000A7:   9595        ASR     R25              Arithmetic shift right
+000000A8:   9587        ROR     R24              Rotate right through carry
+000000A9:   0F8A        ADD     R24,R26          Add without carry
+000000AA:   1F9B        ADC     R25,R27          Add with carry
+000000AB:   9509        ICALL                    Indirect call to (Z)
+000000AC:   9508        RET                      Subroutine return
@000000AD: _ZN6KeyObj5Key00Ev
---- main.c ---------------------------------------------------------------------------------------
504:        PORTB |= (1 << CtrlJ1);
+000000AD:   9AC0        SBI     0x18,0           Set bit in I/O register
---- No Source ------------------------------------------------------------------------------------
+000000AE:   9508        RET                      Subroutine return
@


268:          asm volatile ("" : "=r" (f) : "0" (addr));            //    ????,???????????,???,????
+00000092:   019C        MOVW    R18,R24  &nbs
14楼: >>参与讨论
testcode
另一种写法
typedef prog_void (KeyObj::* PFV)(void);
typedef void func_t (void);
    
void KeyObj::KeyCommandExec(unsigned CHAR mode)
{
    static const PFV KeyCommandTab[3][5] PROGMEM = {//???????
  {&KeyObj::Key00, &KeyObj::Key01, &KeyObj::Key02, &KeyObj::Key03, &KeyObj::Key04}, //???????
  {&KeyObj::Key10, &KeyObj::Key11, &KeyObj::Key12, &KeyObj::Key13, &KeyObj::Key14}, //??????
  {&KeyObj::Key20, &KeyObj::Key21, &KeyObj::Key22, &KeyObj::Key23, &KeyObj::Key24}  //???????
};
  KeyCount &= 0x07;//?????
  if (KeyCount <= 4)
  {
        func_t *f = reinterpret_cast<func_t *> (pgm_read_word (&KeyCommandTab[(int)mode][(int)KeyCount]));
        f();   
  }
}

AVR MEMORY Usage:
-----------------
DEVICE: ATMEGA32

Program:     392 bytes (1.2% Full)
(.text + .data + .bootloader)

Data:         18 bytes (0.9% Full)
(.data + .bss + .noinit)
 

* - 本贴最后修改时间:2005-6-29 6:25:25 修改者:testcode

15楼: >>参与讨论
kanprin
受益非浅啊!
 
16楼: >>参与讨论
c7140
顶一把,好文!
 
17楼: >>参与讨论
John_Lee
pointer to member function 确实有问题
问题 1、每个指针占用 4 bytes,从我在上面给出的结果的 _ZN5foo_t10func_arrayE 标号可以看出。但我不知道为什么?

问题 2、指针取出正确,但是紧接着的调用算法却怎么看都不对!按理就 2~3条机器指令就可以完成的调用,却编译出了26条指令!!这是 avr-gcc 的 bug 呢还是本人水平不够,看不懂作者的意图?

hotpower,你还是再用静态成员函数试试吧!肯定可以的!


18楼: >>参与讨论
hotpower
哈哈,我终于再次从黄河里跳出来了
胡搅蛮缠今天上午十点终于搞定了!!!

非常感谢John_Lee和testcode.

特别是testcode陪我到上午3点...

头晕了,看了半场足球...太累了没看完...但是心里有些底了...

终于想明白了---正规战不行就来个游击战!!!不讲什么道理,只要能从黄河里跳出来即可!!!

一个简单的问题被搞得如此复杂,从汇编角度分析,指针不就是个2字节地址吗???

只要有本事取出此地址,那么不就跳出了"黄河"???很管什么鲤鱼之类的"道理"???

typedef void (* PFV)(void);//函数指针
class KeyObj {
private:
public:
  unsigned CHAR KeyVal;//每20ms测试的键值(有1无0/有低无高)
  unsigned CHAR KeyCount;//键盘计数器
  unsigned int KeyTest[8];//2.5mS压键计数器
public:
  KeyObj(void);
  void KeyInit(void);
  unsigned CHAR GetKeyVal(void);//
  void KeyCommandExec(unsigned CHAR);
  void Exec(void);
  static PFV KeyCommandTab[3][5];
  static void Key00(void);
  static void Key01(void);
  static void Key02(void);
  static void Key03(void);
  static void Key04(void);

  static void Key10(void);
  static void Key11(void);
  static void Key12(void);
  static void Key13(void);
  static void Key14(void);

  static void Key20(void);
  static void Key21(void);
  static void Key22(void);
  static void Key23(void);
  static void Key24(void);
};//键盘模块

PFV KeyObj::KeyCommandTab[3][5] PROGMEM = {//键盘事件处理表
  {Key00, Key01, Key02, Key03, Key04}, //键释放事件处理
  {Key10, Key11, Key12, Key13, Key14}, //压键事件处理
  {Key20, Key21, Key22, Key23, Key24}  //长压键事件处理
};

void KeyObj::KeyCommandExec(unsigned CHAR mode)
{
PFV func;
  KeyCount &= 0x07;//取键盘序号
  if (KeyCount <= 4) {//只有5个键,KeyCount>=5为3个短接线
    func = (PFV)pgm_read_word (&KeyCommandTab[mode][KeyCount]);//取FLASH键盘放事件处理表
    func();//运行KeyX0()~KeyX4()
  }
}


现在没时间测试if,SWITCH和函数指针之间的长度和效率的对比.
有时间来个"三跳大逆转",给那些反对函数指针的人一个理论的对比...
也许他看得太"远"了...给他1000个事件让他处理处理...

* - 本贴最后修改时间:2005-6-29 14:32:07 修改者:hotpower

19楼: >>参与讨论
hotpower
谢谢testcode对20mS次数计数器的错误更正
if (KeyTest[KeyCount] < 1200) {//1200*2.5mS=3S//压键长短判别
应该为
if (KeyTest[KeyCount] < 150) {//150*20mS=3S//压键长短判别
因为每键间隔为2.5mS,但每次扫描自己时应该为8*2.5mS=20mS

这样,KeyTest[]的类型就变为unsigned CHAR,也让我省了8个RAM...

谢谢testcode!

这是我移植4051的,还没运行...



//键扫描及运行
inline
void KeyObj::Exec(void)
{
static const unsigned CHAR KeyTab[8] PROGMEM = {//只取当前键值表
  0b10000000,//键盘0
  0b01000000,//键盘1
  0b00100000,//键盘2
  0b00010000,//键盘3
  0b00001000,//键盘4
  0b00000100,//短接线0
  0b00000010,//短接线1
  0b00000001 //短接线2
};
  KeyCount &= 0x07;//只有8个键
  KeyVal = GetKeyVal();//取所有键位值
  if (KeyVal & pgm_read_byte(&KeyTab[KeyCount])) {//有键压下(每次只取1键以实现“零耗时消除键盘抖动”)
    if (KeyTest[KeyCount] < 150) {//150*20mS=3S//压键长短判别
      KeyTest[KeyCount] ++;//计数每20mS键盘压键的次数(选2较好些)
      if (KeyTest[KeyCount] == 2) {//键刚压下//短压键
        KeyCommandExec(1);//短压键事件处理(1)
      }
    }
    else {//长压键 20mS*150=3S
      KeyTest[KeyCount] = 2;//再压3S继续执行下一次长压键
      KeyCommandExec(2);//长压键3S事件处理(2)
    }
  }
  else {//无键压下
    if (KeyTest[KeyCount] >= 2) {//键释放
      KeyCommandExec(0);//键释放事件处理(0)
    }
    else {//无键压下或干扰
    }
    KeyTest[KeyCount] = 0;//清除压键计数器
  }
  KeyCount ++;//为下次扫描做准备
  KeyCount &= 0x07;//只有8个键
}

20楼: >>参与讨论
hotpower
求助后的总结报告
正确而简单地说应该使用静态类成员的指针而非指向类成员的指针

正如John_Lee所述的两个方法的分析,后者确实把简单问题复杂话了.
因为静态类成员是属于该类的全局对象和函数,它的指针是普通指针.引用该指针不需要类对象.
而类成员的指针必须总是通过特定的对象或指向该类类型的对象的指针来访问.


再者GCCAVR取FLASH数据必须通过pgm_read_word函数形成汇编代码LPM指令才能取出跳转地址.
而且将其转换为类成员的指针我和John_Lee一样都不能找到更好地方法,只能通过嵌入汇编语句完成.
这样也失去了C++的特色.

所以,静态类成员的指针应该是不错的方法.所以Key00()等函数必须声明为静态函数,这样才能在
KeyCommandTab[]函数指针数组中以函数名即函数的装载地址的身份出现,这样就实现了不同函数的散转.

至于函数指针数组是定义为静态类成员或在类成员函数中出现都无关紧要,但有一点是可以肯定的,那就是也必须
定义为静态数组,而且前者必须在类体外初始化,后者可以直接初始化. 特别注意后者虽可不定义为静态数组,但它
将会将数组装载在RAM中.例:

class KeyObj {
public:
  KeyObj(void);
  void Exec(void);//每2.5mS中断调用一次, KeyCount会自动+1
private:
  void KeyInit(void);
  unsigned CHAR GetKeyVal(void);
  void KeyCommandExec(unsigned CHAR);//测试时需声明为public
//................
  static void Key00(void);//必须声明为static!!!
//................
  static void Key24(void);//必须声明为static!!!
};//键盘模块


typedef void (* PFV)(void);//普通函数指针
void KeyObj::KeyCommandExec(unsigned CHAR mode)
{
static PFV KeyCommandTab[3][5] PROGMEM = {//键盘放事件处理表//必须声明为static!!!否则忽略PROGMEM
  {Key00, Key01, Key02, Key03, Key04}, //键释放事件处理
  {Key10, Key11, Key12, Key13, Key14}, //压键事件处理
  {Key20, Key21, Key22, Key23, Key24}  //长压键事件处理
};
PFV func;//定义普通函数指针
  KeyCount &= 0x07;//取键盘序号//使用1602字节(.text),运行正确
  if (KeyCount <= 4) {//只有5个键,KeyCount>=5为3个短接线.
    func = reinterpret_cast<PFV>(pgm_read_word(&KeyCommandTab[mode][KeyCount]));//取FLASH键盘放事件处理表
    func();//运行KeyX0()~KeyX4()
  }
}

最后感觉KeyCommandTab[]函数数组指针还是定义在类成员函数KeyCommandExec()为好,因为其他成员并不需要访问KeyCommandTab[].
当然这样做会在不了解KeyCommandTab[]要取Key00()等函数指针时会把对Key00()等定义为静态函数产生疑问.
如果其他成员并需要访问KeyCommandTab[],则改为:

PFV func;//定义普通函数指针
class KeyObj {
public:
  KeyObj(void);
  void Exec(void);//每2.5mS中断调用一次, KeyCount会自动+1
private:
  void KeyInit(void);
  unsigned CHAR GetKeyVal(void);
  void KeyCommandExec(unsigned CHAR);//测试时需声明为public
//................
  static const PFV KeyCommandTab[3][5];//必须声明为static!!!
  static void Key00(void);//必须声明为static!!!
//................
  static void Key24(void);//必须声明为static!!!
};//键盘模块

//初始化键盘放事件处理表,因为KeyCommandTab数组为静态成员,故必须在类体外进行初始化
const PFV KeyObj::KeyCommandTab[3][5] PROGMEM = {//键盘放事件处理表
  {Key00, Key01, Key02, Key03, Key04}, //键释放事件处理
  {Key10, Key11, Key12, Key13, Key14}, //压键事件处理
  {Key20, Key21, Key22, Key23, Key24}  //长压键事件处理
};

void KeyObj::KeyCommandExec(unsigned CHAR mode)
{
PFV func;//定义普通函数指针
  KeyCount &= 0x07;//取键盘序号//使用1602字节(.text),运行正确
  if (KeyCount <= 4) {//只有5个键,KeyCount>=5为3个短接线.
//由于KeyCommandExec()函数也是KeyObj的类成员函数,故可直接访问KeyCommandTab[]数组  
    func = reinterpret_cast<PFV>(pgm_read_word(&KeyCommandTab[mode][KeyCount]));//取FLASH键盘放事件处理表
    func();//运行KeyX0()~KeyX4()
  }
}

现在和求助帖对比发现也就"static"一词之差,但结果却相差万里.

此帖到此讨论结束,也作为我对求助的总结"报告".
我认为求助而不总结的都是些"白帖",总结后就会知道自己永远还是一只不会飞的大菜鸟.

我很菜但我肯吃苦,我虽笨但我更努力.
我灌水但我不害人,我无翅但我梦飞翔.

再次感谢John_Lee和testcode两位网友!




* - 本贴最后修改时间:2005-6-30 18:28:34 修改者:hotpower

21楼: >>参与讨论
BitFu
静态成员不能直接访问类的其他成员
所以,感觉这样用的局限太大了。

22楼: >>参与讨论
hotpower
BitFu的担心不无道理,但骑驴找驴的结果可想而知
讨论了许久却一直未涉及到静态成员访问类的其他成员的问题,确实有点遗憾.
本该收场,看来还需多聊几句...

正如BitFu的担心, 静态成员确实不能直接访问类的其他成员!!!

但静态成员的好处使我们简单地解决了函数指针的访问问题,但却带来了BitFu的担心.

在本讨论中,静态成员(函数)Key00()~Key24()一直为空函数,故一直是个迷.

现举个例子:
Key0和Key1键同时按下时关闭控制1

/*------------------------
     压键事件处理
------------------------*/
void KeyObj::Key10(void)//静态成员函数
{
  if (KeyTest[1] >= 2) {//Key1与Key0键(本键)同时压下时
    Ctrl.ColseCtrl1();//关闭控制1
  }
}

void KeyObj::Key11(void)//静态成员函数
{
  if (KeyTest[0] >= 2) {//Key0与Key1键(本键)同时压下时
    Ctrl.ColseCtrl1();//关闭控制1
  }
}

但是编译不会通过!!!因为静态成员Key10()和Key11()根本无法访问成员数组KeyTest[].

那么,用静态成员函数做散转对象还不如一般的C函数Key()!!!
费如此大牛劲得来的"成果"莫非"一钱不值"???

所谓"骑驴找驴",就是静态类成员"后天"总该知道类KeyObj的使用者Key.
虽限制了类的很多特点,但也维持了其他类成员不得直接访问Key00()的特性.

在MCU中,Key10()等静态成员肯定都是"一次性的",很少有人愿继承,所以就多书写点字吧...
如果嫌麻烦也可将需要访问的成员尽量声明为静态成员,因为静态成员可以直接访问静态成员.例

/*------------------------
     压键事件处理
------------------------*/
void KeyObj::Key10(void)//静态成员函数
{
  if (Key.KeyTest[1] >= 2) {//Key1与Key0键(本键)同时压下时
    Key.KeyInit();//访问类成员
    Key12();//访问静态类成员
    Ctrl.ColseCtrl1();//关闭控制1//访问其他类成员
  }
}

void KeyObj::Key11(void)//静态成员函数
{
  if (Key.KeyTest[0] >= 2) {//Key0与Key1键(本键)同时压下时
    Key13();//访问静态类成员
    Ctrl.ColseCtrl1();//关闭控制1//访问其他类成员
  }
}

让BitFu见笑了...非典嘛就是"只要达到目的,不管使用什么手段,哪怕---卑鄙无耻"

* - 本贴最后修改时间:2005-6-30 23:46:07 修改者:hotpower

23楼: >>参与讨论
testcode
GCC转换为类成员的指针的另一方法
GCC转换为类成员的指针问题:
今天找到另一种方法,使用memcpy_P而不用pgm_read_word,将其转换为类成员的指针,测试通过。

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <compat/ina90.h>

class KeyObj ;

typedef prog_void (KeyObj::* PFV)(void);

#define KeyCP     (1)
#define KeyCLK    (2)
#define KeyData   (3)

class KeyObj {
private:
  unsigned CHAR KeyVal;  
  unsigned int KeyTest[8];
public:
    unsigned CHAR KeyCount;
  KeyObj(void);
  void KeyInit(void);
  void SetKeyVal(unsigned CHAR keyval);
  void SetKeyCount(unsigned CHAR count);
  unsigned CHAR GetKeyVal(void);//
  void KeyCommandExec(unsigned CHAR);
  static const PFV KeyCommandTab[3][5];
  void Exec(void);
  void Key00(void);
...
  void Key24(void);
};

inline unsigned CHAR KeyObj::GetKeyVal(void)
{
unsigned CHAR KeyVal = 0;
  PORTC &= ~(1 << KeyCP);
  _NOP();//??
  PORTC |=  (1 << KeyCP);
  for(int i = 8; i > 0; i --)
  {
    KeyVal <<= 1;
    PORTC &= ~(1 << KeyCLK);
    _NOP();//??
    if (!(PINC & (1 << KeyData))) KeyVal ++;
    PORTC |=  (1 << KeyCLK);
  }
  return KeyVal;//?????0
}

inline void KeyObj::Exec(void)
{
static const unsigned CHAR KeyTab[8] PROGMEM = {
  0b10000000,
  0b01000000,
  0b00100000,
  0b00010000,
  0b00001000,
  0b00000100,
  0b00000010,
  0b00000001
};
  KeyCount &= 0x07;
  KeyVal = GetKeyVal();
  if (KeyVal & pgm_read_byte(&KeyTab[KeyCount])) {
    if (KeyTest[KeyCount] < 1200) {
      KeyTest[KeyCount] ++;
      if (KeyTest[KeyCount] == 2) {
        KeyCommandExec(1);
      }
    }
    else {
      KeyTest[KeyCount] = 2;
      KeyCommandExec(2);
    }
  }
  else {
    if (KeyTest[KeyCount] >= 2) {
      KeyCommandExec(0);
    }
    else {
    }
    KeyTest[KeyCount] = 0;
  }
  KeyCount ++;
  KeyCount &= 0x07;
}

void KeyObj::SetKeyVal(unsigned CHAR keyval)
{
    KeyVal = keyval;
}

void KeyObj::SetKeyCount(unsigned CHAR count)
{
    KeyCount = count;
}

const PFV KeyObj::KeyCommandTab[3][5] PROGMEM=
{
      {&KeyObj::Key00, &KeyObj::Key01, &KeyObj::Key02, &KeyObj::Key03, &KeyObj::Key04}, //???????
      {&KeyObj::Key10, &KeyObj::Key11, &KeyObj::Key12, &KeyObj::Key13, &KeyObj::Key14}, //??????
      {&KeyObj::Key20, &KeyObj::Key21, &KeyObj::Key22, &KeyObj::Key23, &KeyObj::Key24}  //???????
};
    
void KeyObj::KeyCommandExec(unsigned CHAR mode)
{
    
  KeyCount &= 0x07;
  if (KeyCount <= 4)
  {
    PFV f;
    memcpy_P(&f,  &(KeyObj::KeyCommandTab[(int)mode][(int)KeyCount]), sizeof(PFV));
    (this->*f)();
  }
}

void KeyObj::Key00(void)
{
}
...

void KeyObj::Key24(void)
{
}



24楼: >>参与讨论
zhousd
佩服楼主的钻研精神!
希望能够见到楼主更多的提神贴子!收藏!

25楼: >>参与讨论
John_Lee
静态成员函数访问非静态类数据成员的一般方法...
最常用和普通的方法是:可以在声明静态成员函数时增加一个类指针参数,用来访问非静态类数据成员,其实就相当于将 this 显示化了而已。在调用该静态函数时,调用者需要传递一个类指针参数。

例:
class foo_t
{
    ...
    static uint8_t static_data;                // 静态数据成员
    uint8_t ordinary_data;                     // 普通数据成员
    static void static_func (foo_t *, ...);    // 静态成员函数
    void ordinary_func (...);                  // 普通成员函数
    ...
};

void foo_t::ordinary_func (...)
{
    ...
    static_func (this, ...);          // 调用静态成员函数,将 this 指针传递给 static_func 以便访问 普通数据成员
    ...
}

void foo_t::static_func (foo_t *foo, ...)
{
    foo->ordinary_data = static_data;     // 直接访问 static_data,通过 foo 访问 ordinary_data
}


26楼: >>参与讨论
hotpower
再次感谢两位
特别是感谢testcode!!!

昨晚BitFu提出了静态成员访问其他非成员的问题后,我在下班的路上也考虑继续

对成员函数指针数组在GCCAVR转换上进行研究.

路上感觉必须重新考虑,因为难度在pgm_read_word()函数.
而(KeyObj::* )()从编译的结果好象为4个字节,那么pgm_read_word()读出的结果即地址肯定散转错误!!!

正在考虑中,testcode早就给出了答案!!!很是感激!!!

我实际最不喜欢*,最不喜欢->   不比较喜欢.

但:
    PFV f;
    memcpy_P(&f,  &(KeyObj::KeyCommandTab[(int)mode][(int)KeyCount]), sizeof(PFV));
    (this->*f)();
确实应该彻底地解决了问题.

John_Lee的方法也很不错,但函数都多带了个参数有点繁...

再次谢谢两位!!!!!!!!!!!

27楼: >>参与讨论
John_Lee
hotpower,仔细考虑一下!
1、从程序简洁性和代码效率上考虑,肯定不能用 memcpy!这是什么效率!
2、从本质上看,类的“普通成员函数”和“静态成员函数”的区别,就是“普通成员函数”有一个隐含的 this 指针参数,用来访问“普通数据成员”(“普通成员函数”的参数个数,要比写的多一个);而“静态成员函数”没有 this 指针,显然只能访问“静态数据成员”。
如果给“静态成员函数”增加一个类指针参数,就相当于显示地增加了一个 this 指针!虽然代码的简洁性上稍差,但效率和开销完全等同于“普通成员函数”,这是 C++ 程序设计中经常使用的方法。
3、“* -> .”,都有它们各自应有的作用,编程时该用哪个就要用哪个!不能凭喜好而偏颇(呵呵,我搞了nn年程序设计,还是头次听说谁喜欢或讨厌哪个算符)。


28楼: >>参与讨论
hotpower
瞎搞就是硬道理(何必讲什么大道理)
瞎搞就是硬道理(何必讲什么大道理)

对testcode的GCC转换为类成员的指针的方法进行了调试及反汇编列表,结果确实不太满意。
虽然运行正常但代码太长,导致我对三种散转的对比报告成为一堆废纸。
使我真的被“梁山好汉”招安入伙,成了第109名“水寇”终于“晚节不保”,心情难以平静。。。
放手一博,瞎搞就是硬道理!!!

先请看类成员的指针的方法的反汇编代码列表:

268:      void KeyObj::KeyCommandExec(unsigned CHAR mode)
269:      {
+00000078:   930F        PUSH    R16              PUSH register on stack
+00000079:   931F        PUSH    R17              PUSH register on stack
+0000007A:   93CF        PUSH    R28              PUSH register on stack
+0000007B:   93DF        PUSH    R29              PUSH register on stack
+0000007C:   B7CD        IN      R28,0x3D         In from I/O location
+0000007D:   B7DE        IN      R29,0x3E         In from I/O location
+0000007E:   9724        SBIW    R28,0x04         Subtract immediate from word
+0000007F:   B60F        IN      R0,0x3F          In from I/O location
+00000080:   94F8        CLI                      GLOBAL Interrupt Disable
+00000081:   BFDE        OUT     0x3E,R29         Out to I/O location
+00000082:   BE0F        OUT     0x3F,R0          Out to I/O location
+00000083:   BFCD        OUT     0x3D,R28         Out to I/O location
+00000084:   018C        MOVW    R16,R24          Copy register pair
277:        KeyCount &= 0x07;//取键盘序号//使用1602字节(.text),运行正确
+00000085:   01FC        MOVW    R30,R24          Copy register pair
+00000086:   8141        LDD     R20,Z+1          Load indirect with displacement
+00000087:   7047        ANDI    R20,0x07         Logical AND with immediate
+00000088:   8341        STD     Z+1,R20          Store indirect with displacement
278:        if (KeyCount <= 4) {//只有5个键,KeyCount>=5为3个短接线.
+00000089:   3045        CPI     R20,0x05         Compare with immediate
+0000008A:   F590        BRCC    PC+0x33          Branch if carry cleared
286:          memcpy_P(&func,  &(KeyObj::KeyCommandTab[mode][KeyCount]), sizeof(PFV));
+0000008B:   2F26        MOV     R18,R22          Copy register
+0000008C:   2733        CLR     R19              Clear Register
+0000008D:   01C9        MOVW    R24,R18          Copy register pair
+0000008E:   0F88        LSL     R24              Logical Shift Left
+0000008F:   1F99        ROL     R25              Rotate Left Through Carry
+00000090:   0F88        LSL     R24              Logical Shift Left
+00000091:   1F99        ROL     R25              Rotate Left Through Carry
+00000092:   0F82        ADD     R24,R18          Add without carry
+00000093:   1F93        ADC     R25,R19          Add with carry
+00000094:   0F84        ADD     R24,R20          Add without carry
+00000095:   1D91        ADC     R25,R1           Add with carry
+00000096:   0F88        LSL     R24              Logical Shift Left
+00000097:   1F99        ROL     R25              Rotate Left Through Carry
+00000098:   0F88        LSL     R24              Logical Shift Left
+00000099:   1F99        ROL  &nb
29楼: >>参与讨论
潜艇8421
牛!!
 
30楼: >>参与讨论
zhousd
精彩的贴子!收藏!
 
31楼: >>参与讨论
IceAge
关于成员函数指针的问题。
我公布我自己做的一个delegate class, 希望能有所帮助


#if !defined(_Class_Delegate_)
#define _Class_Delegate_

//#include <stdarg.h>

/*************************************************************************************************
    Warning: Unsafe Code !!!
*************************************************************************************************/

#define SetEventHandler(instance, function) _SetEventHandler((instance), reinterpret_cast<PTR_CALLBACK>(function))

class CDelegate;
typedef void (CDelegate::*PTR_CALLBACK)(void* sender, DWORD para);

class CDelegate
{
    PTR_CALLBACK        m_pCallBack;
    CDelegate*            m_pInstance;

public:
    CDelegate()
    {
        m_pCallBack = NULL;
        m_pInstance = NULL;
    }

    void _SetEventHandler(void* pInstance, PTR_CALLBACK pCallBack)
    {
        m_pInstance = (CDelegate*) pInstance;
        m_pCallBack = pCallBack;
    }

    void Invoke(void* sender, DWORD para)
    {
        if (m_pInstance != NULL && m_pCallBack != NULL)
            (m_pInstance->*m_pCallBack)(sender, para);
    }

    void Cancel()
    {
        m_pInstance = NULL;
    }

#if 0
    void _SetEventHandler(void* pInstance, ...)
    {
        m_pInstance = (CDelegate*) pInstance;
        va_list args;
        va_start(args, pInstance);
        m_pCallBack = va_arg(args, PTR_CALLBACK);
        va_end(args);
    }
#endif
};

#endif




32楼: >>参与讨论
hotpower
如此好的东东不知AVR受用否???
 

* - 本贴最后修改时间:2005-7-9 0:14:44 修改者:hotpower

33楼: >>参与讨论
IceAge
用法:
class ClassKeyBoard
{
  ...
     CDelegate    m_Delegate_Keypressed;
};

void ClassKeyBoard::KeyPressed()
{
    //call ClassB::KeyPressed
     m_Delegate_Keypressed.Invoke(this, theKey);
}

...

void ClassB::Init()
{
     keyBoard.m_Delegate_Keypressed.SetEventHandler(
        this, ClassB::KeyPressed);
}

void ClassB::KeyPressed(void* sender, DWORD theKey)
{
}

34楼: >>参与讨论
hotpower
我关心能否在GCCAVR上编译通过而且代码较少
 
35楼: >>参与讨论
IceAge
reinterpret_cast与不定参数都可以避过 compiler
对成员函数指针的语法检查。

呵呵,看了hotpower 的无私奉献,我也不能不表示点什么。

这个class 是个跨平台的通用类。理论上,invoke 仅产生少至两行汇编代码。




参与讨论
昵称:
讨论内容:
 
 
相关帖子
请问: 可否用74HC373来代替74LS373
ATmega162在16M下ISP不能下载
mega8的奇怪问题.与I2c.IO口有关????!
今天对MEGA16做了低温实验
求救:WinAVR编译C++程序时老是自作主张修改我的东东
免费注册为维库电子开发网会员,参与电子工程师社区讨论,点此进入


Copyright © 1998-2006 www.dzsc.com 浙ICP证030469号