• str…字符串操作函数
    char stpcpy(char *dest,const char *src)
    将字符串src复制到dest
    char strcat(char *dest,const char *src)
    将字符串src添加到dest末尾
    char strchr(const char *s,int c)
    检索并返回字符c在字符串s中第一次出现的位置
    int strcmp(const char *s1,const char *s2)
    比较字符串s1与s2的大小,并返回s1-s2
    char strcpy(char *dest,const char *src)
    将字符串src复制到dest
    size_t strcspn(const char *s1,const char *s2)
    扫描s1,返回在s1中有,在s2中也有的字符个数
    char strdup(const char *s)
    将字符串s复制到最近建立的单元
    int stricmp(const char *s1,const char *s2)
    比较字符串s1和s2,并返回s1-s2
    size_t strlen(const char *s)
    返回字符串s的长度
    char strlwr(char *s)
    将字符串s中的大写字母全部转换成小写字母,并返回转换后的字符串
    char strncat(char *dest,const char *src,size_t maxlen)
    将字符串src中最多maxlen个字符复制到字符串dest中
    int strncmp(const char *s1,const char *s2,size_t maxlen)
    比较字符串s1与s2中的前maxlen个字符
    char strncpy(char *dest,const char *src,size_t maxlen)
    复制src中的前maxlen个字符到dest中
    int strnicmp(const char *s1,const char *s2,size_t maxlen)
    比较字符串s1与s2中的前maxlen个字符
    char strnset(char *s,int ch,size_t n)
    将字符串s的前n个字符置于ch中
    char strpbrk(const char *s1,const char *s2)
    扫描字符串s1,并返回在s1和s2中均有的字符个数
    char strrchr(const char *s,int c)
    扫描最后出现一个给定字符c的一个字符串s
    char strrev(char *s)
    将字符串s中的字符全部颠倒顺序重新排列,并返回排列后的字符串
    char strset(char *s,int ch)
    将一个字符串s中的所有字符置于一个给定的字符ch
    size_t strspn(const char *s1,const char *s2)
    扫描字符串s1,并返回在s1和s2中均有的字符个数
    char strstr(const char *s1,const char *s2)
    扫描字符串s2,并返回第一次出现s1的位置
    char strtok(char *s1,const char *s2)
    检索字符串s1,该字符串s1是由字符串s2中定义的定界符所分隔
    char strupr(char *s)
    将字符串s中的小写字母全部转换成大写字母,并返回转换后的字符串

    存贮分配子程序,所在函数库为dos.h、alloc.h、malloc.h、stdlib.h、process.h
    int allocmem(unsigned size,unsigned *seg)利用DOS分配空闲的内存,
    size为分配内存大小,seg为分配后的内存指针
    int freemem(unsigned seg)释放先前由allocmem分配的内存,seg为指定的内存指针
    int setblock(int seg,int newsize)本函数用来修改所分配的内存长度,
    seg为已分配内存的内存指针,newsize为新的长度

    int brk(void *endds)
    本函数用来改变分配给调用程序的数据段的空间数量,新的空间结束地址为endds
    char *sbrk(int incr)
    本函数用来增加分配给调用程序的数据段的空间数量,增加incr个字节的空间

    unsigned long coreleft() 本函数返回未用的存储区的长度,以字节为单位
    void *calloc(unsigned nelem,unsigned elsize)分配nelem个长度为elsize的内存空间
    并返回所分配内存的指针
    void *malloc(unsigned size)分配size个字节的内存空间,并返回所分配内存的指针
    void free(void *ptr)释放先前所分配的内存,所要释放的内存的指针为ptr
    void *realloc(void *ptr,unsigned newsize)改变已分配内存的大小,ptr为已分配有内
    存区域的指针,newsize为新的长度,返回分配好的内存指针.

    long farcoreleft() 本函数返回远堆中未用的存储区的长度,以字节为单位
    void far *farcalloc(unsigned long units,unsigned long unitsz)
    从远堆分配units个长度为unitsz的内存空间,并返回所分配内存的指针
    void *farmalloc(unsigned long size)分配size个字节的内存空间,
    并返回分配的内存指针
    void farfree(void far *block)释放先前从远堆分配的内存空间,
    所要释放的远堆内存的指针为block
    void far *farrealloc(void far *block,unsigned long newsize)改变已分配的远堆内
    存的大小,block为已分配有内存区域的指针,newzie为新的长度,返回分配好
    的内存指针

    时间日期函数,函数库为time.h、dos.h
    在时间日期函数里,主要用到的结构有以下几个:
    总时间日期贮存结构tm
    ┌──────────────────────┐
    │struct tm │
    │{ │
    │ int tm_sec; /*秒,0-59*/ │
    │ int tm_min; /*分,0-59*/
  • =================================================================

    接口子程序,所在函数库为:dos.h、bios.h
    unsigned sleep(unsigned seconds)暂停seconds微秒(百分之一秒)
    int unlink(char *filename)删除文件filename
    unsigned FP_OFF(void far *farptr)本函数用来取远指针farptr的偏移量
    unsigned FP_SEG(void far *farptr)本函数用来没置远指针farptr的段值
    void far *MK_FP(unsigned seg,unsigned off)根据段seg和偏移量off构造一个far指针
    unsigned getpsp()取程序段前缀的段地址,并返回这个地址
    char *parsfnm(char *cmdline,struct fcb *fcbptr,int option)
    函数分析一个字符串,通常,对一个文件名来说,是由cmdline所指的一个命令行.
    文件名是放入一个FCB中作为一个驱动器,文件名和扩展名.FCB是由fcbptr所指
    定的.option参数是DOS分析系统调用时,AL文本的值.

    int absread(int drive,int nsects,int sectno,void *buffer)本函数功能为读特定的
    磁盘扇区,drive为驱动器号(0=A,1=B等),nsects为要读的扇区数,sectno为开始的逻
    辑扇区号,buffer为保存所读数据的保存空间
    int abswrite(int drive,int nsects,int sectno,void *buffer)本函数功能为写特定的
    磁盘扇区,drive为驱动器号(0=A,1=B等),nsects为要写的扇区数,sectno为开始的逻
    辑扇区号,buffer为保存所写数据的所在空间
    void getdfree(int drive,struct dfree *dfreep)本函数用来取磁盘的自由空间,
    drive为磁盘号(0=当前,1=A等).函数将磁盘特性的由dfreep指向的dfree结构中.
    dfree结构如下:
    ┌───────────────────┐
    │struct dfree │
    │{ │
    │ unsigned df_avail; /*有用簇个数*/ │
    │ unsigned df_total; /*总共簇个数*/ │
    │ unsigned df_bsec; /*每个扇区字节数*/│
    │ unsigned df_sclus; /*每个簇扇区数*/ │
    │} │
    └───────────────────┘
    char far *getdta() 取磁盘转换地址DTA
    void setdta(char far *dta)设置磁盘转换地址DTA
    void getfat(int drive,fatinfo *fatblkp)
    本函数返回指定驱动器drive(0=当前,1=A,2=B等)的文件分配表信息
    并存入结构fatblkp中,结构如下:
    ┌──────────────────┐
    │struct fatinfo │
    │{ │
    │ char fi_sclus; /*每个簇扇区数*/ │
    │ char fi_fatid; /*文件分配表字节数*/│
    │ int fi_nclus; /*簇的数目*/ │
    │ int fi_bysec; /*每个扇区字节数*/ │
    │} │
    └──────────────────┘
    void getfatd(struct fatinfo *fatblkp) 本函数返回当前驱动器的文件分配表信息,
    并存入结构fatblkp中,结构如下:
    ┌──────────────────┐
    │struct fatinfo │
    │{ │
    │ char fi_sclus; /*每个簇扇区数*/ │
    │ char fi_fatid; /*文件分配表字节数*/│
    │ int fi_nclus; /*簇的数目*/ │
    │ int fi_bysec; /*每个扇区字节数*/ │
    │} │
    └──────────────────┘

    int bdos(int dosfun,unsigned dosdx,unsigned dosal)本函数对MSDOS系统进行调用,
    dosdx为寄存器dx的值,dosal为寄存器al的值,dosfun为功能号
    int bdosptr(int dosfun,void *argument,unsiigned dosal)本函数对MSDOS系统进行调用,
    argument为寄存器dx的值,dosal为寄存器al的值,dosfun为功能号
    int int86(int intr_num,union REGS *inregs,union REGS *outregs)
    执行intr_num号中断,用户定义的寄存器值存于结构inregs中,
    执行完后将返回的寄存器值存于结构outregs中.
    int int86x(int intr_num,union REGS *inregs,union REGS *outregs,
    struct SREGS *segregs)执行intr_num号中断,用户定义的寄存器值存于
    结构inregs中和结构segregs中,执行完后将返回的寄存器值存于结构outregs中.
    int intdos(union REGS *inregs,union REGS *outregs)
    本函数执行DOS中断0x21来调用一个指定的DOS函数,用户定义的寄存器值
    存于结构inregs中,执行完后函数将返回的寄存器值存于结构outregs中
    int intdosx(union REGS *inregs,union REGS *outregs,struct SREGS *segregs)
    本函数执行DOS中断0x21来调用一个指定的DOS
  • =======================================================================

    输入输出子程序,函数库为io.h、conio.h、stat.h、dos.h、stdio.h、signal.h
    int kbhit() 本函数返回最近所敲的按键
    int fgetchar() 从控制台(键盘)读一个字符,显示在屏幕上
    int getch() 从控制台(键盘)读一个字符,不显示在屏幕上
    int putch() 向控制台(键盘)写一个字符
    int getchar() 从控制台(键盘)读一个字符,显示在屏幕上
    int putchar() 向控制台(键盘)写一个字符
    int getche() 从控制台(键盘)读一个字符,显示在屏幕上
    int ungetch(int c) 把字符c退回给控制台(键盘)
    char *cgets(char *string) 从控制台(键盘)读入字符串存于string中
    int scanf(char *format[,argument…])从控制台读入一个字符串,分别对各个参数进行
    赋值,使用BIOS进行输出
    int vscanf(char *format,Valist param)从控制台读入一个字符串,分别对各个参数进行
    赋值,使用BIOS进行输出,参数从Valist param中取得
    int cscanf(char *format[,argument…])从控制台读入一个字符串,分别对各个参数进行
    赋值,直接对控制台作操作,比如显示器在显示时字符时即为直接写频方式显示
    int sscanf(char *string,char *format[,argument,…])通过字符串string,分别对各个
    参数进行赋值
    int vsscanf(char *string,char *format,Vlist param)通过字符串string,分别对各个
    参数进行赋值,参数从Vlist param中取得
    int puts(char *string) 发关一个字符串string给控制台(显示器),
    使用BIOS进行输出
    void cputs(char *string) 发送一个字符串string给控制台(显示器),
    直接对控制台作操作,比如显示器即为直接写频方式显示
    int printf(char *format[,argument,…]) 发送格式化字符串输出给控制台(显示器)
    使用BIOS进行输出
    int vprintf(char *format,Valist param) 发送格式化字符串输出给控制台(显示器)
    使用BIOS进行输出,参数从Valist param中取得
    int cprintf(char *format[,argument,…]) 发送格式化字符串输出给控制台(显示器),
    直接对控制台作操作,比如显示器即为直接写频方式显示
    int vcprintf(char *format,Valist param)发送格式化字符串输出给控制台(显示器),
    直接对控制台作操作,比如显示器即为直接写频方式显示,
    参数从Valist param中取得
    int sprintf(char *string,char *format[,argument,…])
    将字符串string的内容重新写为格式化后的字符串
    int vsprintf(char *string,char *format,Valist param)
    将字符串string的内容重新写为格式化后的字符串,参数从Valist param中取得
    int rename(char *oldname,char *newname)将文件oldname的名称改为newname
    int ioctl(int handle,int cmd[,int *argdx,int argcx])
    本函数是用来控制输入/输出设备的,请见下表:
    ┌───┬────────────────────────────┐
    │cmd值 │功能 │
    ├───┼────────────────────────────┤
    │ 0 │取出设备信息 │
    │ 1 │设置设备信息 │
    │ 2 │把argcx字节读入由argdx所指的地址 │
    │ 3 │在argdx所指的地址写argcx字节 │
    │ 4 │除把handle当作设备号(0=当前,1=A,等)之外,均和cmd=2时一样 │
    │ 5 │除把handle当作设备号(0=当前,1=A,等)之外,均和cmd=3时一样 │
    │ 6 │取输入状态 │
    │ 7 │取输出状态 │
    │ 8 │测试可换性;只对于DOS 3.x │
    │ 11 │置分享冲突的重算计数;只对DOS 3.x │
    └───┴────────────────────────────┘
    int (*ssignal(int sig,int(*action)())()执行软件信号(没必要使用)
    int gsignal(int sig) 执行软件信号(没必要使用)

    int _open(char *pathname,int access)为读或写打开一个文件,
    按后按access来确定是读文件还是写文件,access值见下表
    ┌──────┬────────────────────┐
    │access值 │意义 │
    ├──────┼────────────────────┤
    │O_RDONLY │读文件 │
    │O_WRONLY │写文件
  • ==========================================================================

    进程函数,所在函数库为stdlib.h、process.h
    void abort() 此函数通过调用具有出口代码3的_exit写一个终止信息于stderr,
    并异常终止程序。无返回值
    int exec…装入和运行其它程序
    int execl( char *pathname,char *arg0,char *arg1,…,char *argn,NULL)
    int execle( char *pathname,char *arg0,char *arg1,…,
    char *argn,NULL,char *envp[])
    int execlp( char *pathname,char *arg0,char *arg1,…,NULL)
    int execlpe(char *pathname,char *arg0,char *arg1,…,NULL,char *envp[])
    int execv( char *pathname,char *argv[])
    int execve( char *pathname,char *argv[],char *envp[])
    int execvp( char *pathname,char *argv[])
    int execvpe(char *pathname,char *argv[],char *envp[])
    exec函数族装入并运行程序pathname,并将参数
    arg0(arg1,arg2,argv[],envp[])传递给子程序,出错返回-1
    在exec函数族中,后缀l、v、p、e添加到exec后,
    所指定的函数将具有某种操作能力
    有后缀 p时,函数可以利用DOS的PATH变量查找子程序文件。
    l时,函数中被传递的参数个数固定。
    v时,函数中被传递的参数个数不固定。
    e时,函数传递指定参数envp,允许改变子进程的环境,
    无后缀e时,子进程使用当前程序的环境。

    void _exit(int status)终止当前程序,但不清理现场
    void exit(int status) 终止当前程序,关闭所有文件,写缓冲区的输出(等待输出),
    并调用任何寄存器的"出口函数",无返回值

    int spawn…运行子程序
    int spawnl( int mode,char *pathname,char *arg0,char *arg1,…,
    char *argn,NULL)
    int spawnle( int mode,char *pathname,char *arg0,char *arg1,…,
    char *argn,NULL,char *envp[])
    int spawnlp( int mode,char *pathname,char *arg0,char *arg1,…,
    char *argn,NULL)
    int spawnlpe(int mode,char *pathname,char *arg0,char *arg1,…,
    char *argn,NULL,char *envp[])
    int spawnv( int mode,char *pathname,char *argv[])
    int spawnve( int mode,char *pathname,char *argv[],char *envp[])
    int spawnvp( int mode,char *pathname,char *argv[])
    int spawnvpe(int mode,char *pathname,char *argv[],char *envp[])
    spawn函数族在mode模式下运行子程序pathname,并将参数
    arg0(arg1,arg2,argv[],envp[])传递给子程序.出错返回-1
    mode为运行模式
    mode为 P_WAIT 表示在子程序运行完后返回本程序
    P_NOWAIT 表示在子程序运行时同时运行本程序(不可用)
    P_OVERLAY表示在本程序退出后运行子程序
    在spawn函数族中,后缀l、v、p、e添加到spawn后,
    所指定的函数将具有某种操作能力
    有后缀 p时, 函数利用DOS的PATH查找子程序文件
    l时, 函数传递的参数个数固定.
    v时, 函数传递的参数个数不固定.
    e时, 指定参数envp可以传递给子程序,允许改变子程序运行环境.
    当无后缀e时,子程序使用本程序的环境.

    int system(char *command) 将MSDOS命令command传递给DOS执行


    ================================================================

    转换子程序,函数库为math.h、stdlib.h、ctype.h、float.h
    char *ecvt(double value,int ndigit,int *decpt,int *sign)
    将浮点数value转换成字符串并返回该字符串
    char *fcvt(double value,int ndigit,int *decpt,int *sign)
    将浮点数value转换成字符串并返回该字符串
    char *gcvt(double value,int ndigit,char *buf)
    将数value转换成字符串并存于buf中,并返回buf的指针
    char *ultoa(unsigned long value,char *string,int radix)
    将无符号整型数value转换成字符串并返回该字符串,radix为转换时所用基数
    char *ltoa(long value,char *string,int radix)
    将长整型数value转换成字符串并返回该字符串,radix为转换时所用基数
    char *itoa(int value,char *string,int radix)
    将整数value转换成字符串存入string,radix为转换时所用基数
    double atof(char *nptr) 将字符串nptr转换成双精度数,并返回这个数,错误返回0
    int atoi(char *nptr) 将字符串nptr转换成整型数, 并返回这个数,错误返回0
    long atol(char *nptr) 将字符串nptr转换成长整型数,并返回这个数,错误返回0
    double strtod(char *str,char **endptr)将字符串str转换成双精度数,并返回这个数,
    long strtol(char *str,char **endptr,int base)将字符串str转换成长整
  • 分类函数,所在函数库为ctype.h
    int isalpha(int ch) 若ch是字母(’A’-’Z’,’a’-’z’)返回非0值,否则返回0
    int isalnum(int ch) 若ch是字母(’A’-’Z’,’a’-’z’)或数字(’0’-’9’)
    返回非0值,否则返回0
    int isascii(int ch) 若ch是字符(ASCII码中的0-127)返回非0值,否则返回0
    int iscntrl(int ch) 若ch是作废字符(0x7F)或普通控制字符(0x00-0x1F)
    返回非0值,否则返回0
    int isdigit(int ch) 若ch是数字(’0’-’9’)返回非0值,否则返回0
    int isgraph(int ch) 若ch是可打印字符(不含空格)(0x21-0x7E)返回非0值,否则返回0
    int islower(int ch) 若ch是小写字母(’a’-’z’)返回非0值,否则返回0
    int isprint(int ch) 若ch是可打印字符(含空格)(0x20-0x7E)返回非0值,否则返回0
    int ispunct(int ch) 若ch是标点字符(0x00-0x1F)返回非0值,否则返回0
    int isspace(int ch) 若ch是空格(’ ’),水平制表符(’\t’),回车符(’\r’),
    走纸换行(’\f’),垂直制表符(’\v’),换行符(’\n’)
    返回非0值,否则返回0
    int isupper(int ch) 若ch是大写字母(’A’-’Z’)返回非0值,否则返回0
    int isxdigit(int ch) 若ch是16进制数(’0’-’9’,’A’-’F’,’a’-’f’)返回非0值,
    否则返回0
    int tolower(int ch) 若ch是大写字母(’A’-’Z’)返回相应的小写字母(’a’-’z’)
    int toupper(int ch) 若ch是小写字母(’a’-’z’)返回相应的大写字母(’A’-’Z’)

    -----------------------------------------------------------------------------------------------------------------------------

    数学函数,所在函数库为math.h、stdlib.h、string.h、float.h
    int abs(int i) 返回整型参数i的绝对值
    double cabs(struct complex znum) 返回复数znum的绝对值
    double fabs(double x) 返回双精度参数x的绝对值
    long labs(long n) 返回长整型参数n的绝对值
    double exp(double x) 返回指数函数ex的值
    double frexp(double value,int *eptr) 返回value=x*2n中x的值,n存贮在eptr中
    double ldexp(double value,int exp); 返回value*2exp的值
    double log(double x) 返回logex的值
    double log10(double x) 返回log10x的值
    double pow(double x,double y) 返回xy的值
    double pow10(int p) 返回10p的值
    double sqrt(double x) 返回x的开方
    double acos(double x) 返回x的反余弦cos-1(x)值,x为弧度
    double asin(double x) 返回x的反正弦sin-1(x)值,x为弧度
    double atan(double x) 返回x的反正切tan-1(x)值,x为弧度
    double atan2(double y,double x) 返回y/x的反正切tan-1(x)值,y的x为弧度
    double cos(double x) 返回x的余弦cos(x)值,x为弧度
    double sin(double x) 返回x的正弦sin(x)值,x为弧度
    double tan(double x) 返回x的正切tan(x)值,x为弧度
    double cosh(double x) 返回x的双曲余弦cosh(x)值,x为弧度
    double sinh(double x) 返回x的双曲正弦sinh(x)值,x为弧度
    double tanh(double x) 返回x的双曲正切tanh(x)值,x为弧度
    double hypot(double x,double y) 返回直角三角形斜边的长度(z),
    x和y为直角边的长度,z2=x2+y2
    double ceil(double x) 返回不小于x的最小整数
    double floor(double x) 返回不大于x的最大整数
    void srand(unsigned seed) 初始化随机数发生器
    int rand() 产生一个随机数并返回这个数
    double poly(double x,int n,double c[])从参数产生一个多项式
    double modf(double value,double *iptr)将双精度数value分解成尾数和阶
    double fmod(double x,double y) 返回x/y的余数
    double frexp(double value,int *eptr) 将双精度数value分成尾数和阶
    double atof(char *nptr) 将字符串nptr转换成浮点数并返回这个浮点数
    double atoi(char *nptr) 将字符串nptr转换成整数并返回这个整数
    double atol(char *nptr) 将字符串nptr转换成长整数并返回这个整数
    char *ecvt(double value,int ndigit,int *decpt,int *sign)
    将浮点数value转换成字符串并返回该字符串
    char *fcvt(double value,int ndigit,int *decpt,int *sign)
    将浮点数value转换成字符串并返回该字符串
    char *gcvt(double value,int ndigit,char *buf)
    将数value转换成字符串并存于
  • 本文转载自VCROAD,作者南海昭信.

    一、背景
    FlashGet的透明效果大家羡慕吧.传统的Windows应用程序想实现半透明效果,一般来说需要处理自己的窗口的WM_Paint消息窗口,很麻烦.现在好了,SetLayeredWindowAttributes是windows的新api,win2000以上才支持,它能使使窗体拥有透明效果.我在Google搜了下,介绍SetLayeredWindowAttributes的文章大多是delphi的和vb的.好不容易找到一篇vc的,依法炮制后,vc的IDE却说我SetLayeredWindowAttributes没有定义!后来想想应该是我的sdk没有升级.于是我在vc安装目录搜索"SetLayeredWindowAttributes"的"*.h"文件,果然没有.怎么办?升级sdk吧.我去微软的网站一看,新的sdk就核心sdk就有二百多m呢(解压后更大),可怜我的硬盘没有一个分区大于200m的了!怎么办,这么好玩的api给看不给用:( 失望之余,我忽然想到了未公开api的使用的方法.这是个系统支持,自己sdk却没有的api,就把他当做windows未公开api试试!

    二、简单介绍一下SetLayeredWindowAttributes:(详见msdn)

    BOOL SetLayeredWindowAttributes(
    HWND hwnd, // handle to the layered window
    COLORREF crKey, // specifies the color key
    BYTE bAlpha, // value for the blend function
    DWORD dwFlags // action
    );

    Windows NT/2000/XP: Included in Windows 2000 and later.
    Windows 95/98/Me: Unsupported.
    Header: Declared in Winuser.h; include Windows.h.
    Library: Use User32.lib.

    一些常量:
    WS_EX_LAYERED = 0x80000;
    LWA_ALPHA = 0x2;
    LWA_COLORKEY=0x1
    其中dwFlags有LWA_ALPHA和LWA_COLORKEY
    LWA_ALPHA被设置的话,通过bAlpha决定透明度.
    LWA_COLORKEY被设置的话,则指定被透明掉的颜色为crKey,其他颜色则正常显示.
    注:要使使窗体拥有透明效果,首先要有WS_EX_LAYERED扩展属性(旧sdk也没有的).
     

    三、例子代码:
    在OnInitDialog()加入:
     

    //加入WS_EX_LAYERED扩展属性
    SetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE,
    GetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE)^0x80000);
    HINSTANCE hInst = LoadLibrary("User32.DLL");
    if(hInst)
    {
    typedef BOOL (WINAPI *MYFUNC)(HWND,COLORREF,BYTE,DWORD);
    MYFUNC fun = NULL;
    //取得SetLayeredWindowAttributes函数指针
    fun=(MYFUNC)GetProcAddress(hInst, "SetLayeredWindowAttributes");
    if(fun)fun(this->GetSafeHwnd(),0,128,2);
    FreeLibrary(hInst);
    }

    唉!如果装了最新sdk就不用那么麻烦了!
    怎么样,效果不错吧!稍加修改还可以作出淡出淡入的效果. 注意第三个参数(128)不要取得太小了,为0的话完全透明,你就找不到窗体了!
    小小心得,一吐为快.希望对初学者有所帮助.如有不妥,欢迎指正.
  • 本文转贴自CSDN文档中心.

    //选择排序法SelectionSort(int arr[],int n)
    template <typename T>
    void SelectionSort(T arr[],int n)
    {
    int smallIndex; //表中最小元素的下标
    int pass,j; //用来扫描子表的下标
    T temp; //用来交换表元素的临时变量

    //pass的范围是0~n-2
    for (pass=0;pass<n-1;pass++)
    {
    //从下标pass开始扫描子表
    smallIndex=pass;

    //j遍历整个子表arr[pass+1]到arr[n-1]
    for(j=pass+1;j<n;j++)

    //如果找到更小的元素,则将该位置赋值给smallIndex
    if(arr[j]<arr[smallIndex])
    smallIndex=j;

    //如果smallIndex和pass不在相同的位置
    //则将子表中的最小项与arr[pass]交换
    if(smallIndex!=pass)
    {
    temp=arr[pass];
    arr[pass]=arr[smallIndex];
    arr[smallIndex]=temp;
    }
    }
    }

    /************************************************************************
    双端选择排序算法:是上面选择排序算法的变种,可以定位每个子表中最小和最大元素
    并把它们分别放在子表的开头和结尾.
    ************************************************************************/
    //双端选择排序算法函数deSelSort()的实现
    template <typename T>
    void deSelSort(T arr[],int n)
    {
    int smallIndex,largeIndex; //表中最小及最大元素的下标
    int leftPass=0,rightPass=n-1,i,j; //用来从表左边及右边扫描子表的下标
    T temp; //用于交换元素的临时变量

    while (leftPass<=rightPass)
    {
    //从左边及右边开始扫描子表
    smallIndex=leftPass;
    largeIndex=rightPass;

    //j和i遍历整个子表arr[LeftPass]~arr[rightPass]
    for (i=leftPass+1;i<rightPass;i++)
    //如果找到更小的元素,则将该位置赋值给smallIndex
    if (arr[i]<arr[smallIndex])
    smallIndex=i;

    //如果smallIndex和leftPass不在相同的位置
    //则将子表中的最小项与arr[pass]交换
    if (smallIndex!=leftPass)
    {
    temp=arr[leftPass];
    arr[leftPass]=arr[smallIndex];
    arr[smallIndex]=temp;
    }


    for (j=rightPass-1;j>leftPass;j--)
    if(arr[j]>arr[largeIndex])
    largeIndex=j;

    if(largeIndex!=rightPass)
    {
    temp=arr[rightPass];
    arr[rightPass]=arr[largeIndex];
    arr[largeIndex]=temp;
    }

    //从两头收缩子表
    leftPass++;
    rightPass--;
    }
    }

    //自编冒泡法排序算法函数bubbleSort()的实现
    template <typename T>
    int bubbleSort(T arr[],int n)
    {
    bool exchanged=false; //是否发生交换
    int i,j; //用于遍历子表的下标
    T temp; //用于交换元素的临时变量

    //开始遍历过程,以下标j构成子表,共有n-1个子表
    for (j=n-1;j>=0;j--) //j从后往前收缩n-1~0,以构成子表0~n-1,0~n-2,0~n-3..0~1
    {
    exchanged=false;
    for (i=0;i<j;i++) //遍历子表范围0~j
    {

    if (arr[i]>arr[i+1])
    {
    temp=arr[i];
    arr[i]=arr[i+1];
    arr[i+1]=temp;
    exchanged=true;
    }
    }
    if (!exchanged) return n-j-1;//如果在一次遍历中没有发生交换,则表示已经
    //排序好,中断遍历过程
    }
    return n-1-j;
    }


    //冒泡法排序一般算法函数bubbleSortEx()的实现
    template <typename T>
    int bubbleSortEx(T arr[],int n)
    {
    int i,pass; //用于遍历子表的下标
    T temp; //用于交换元素的临时变量

    //开始遍历过程,以下标j构成子表,共有n-1个子表
    for (pass=0;pass<n;pass++) //pass从后往后扩大0~n-1,以构成子表0~n-1,0~n-2,0~n-3..0~1
    {
    for (i=0;i<n-pass;i++) //遍历子表范围0~n-pass
    {
    if (arr[i]>arr[i+1])
    {
    temp=arr[i];
    arr[i]=arr[i+1];
    arr[i+1]=temp;
    }
    }
    }
    return pass;
    }
  • // GetLocalPort.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"
    #include <windows.h>
    #include <stdio.h>
    #include <stdlib.h>

    int main(int argc, char* argv[])
    {
    system("netstat -a >localPort.txt");
    printf("Hello World!\n");
    return 0;
    }
  • 本文主要包括二个部分,第一部分重点介绍在VC中,怎么样采用sizeof来求结构的大小,以及容易出现的问题,并给出解决问题的方法,第二部分总结出VC中sizeof的主要用法。

    1、 sizeof应用在结构上的情况

    请看下面的结构:

    struct MyStruct

    {

    double dda1;

    char dda;

    int type

    };

    对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求:

    sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13

    但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。你知道为什么在VC中会得出这样一个结果吗?

    其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了“对齐”处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)。

    类型
    对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)

    Char
    偏移量必须为sizeof(char)即1的倍数

    int
    偏移量必须为sizeof(int)即4的倍数

    float
    偏移量必须为sizeof(float)即4的倍数

    double
    偏移量必须为sizeof(double)即8的倍数

    Short
    偏移量必须为sizeof(short)即2的倍数



    各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。同时VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。

    下面用前面的例子来说明VC到底怎么样来存放结构的。

    struct MyStruct

    {

    double dda1;

    char dda;

    int type

    };

    为上面的结构分配空间的时候,VC根据成员变量出现的顺序和对齐方式,先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用sizeof(char)=1个字节;接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是sizeof(int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的地址对于结构的起始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把type存放在偏移量为12的地方,该成员变量占用sizeof(int)=4个字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以没有空缺的字节需要填充。所以整个结构的大小为:sizeof(MyStruct)=8+1+3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。

    下面再举个例子,交换一下上面的MyStruct的成员变量的位置,使它变成下面的情况:

    struct MyStruct

    {

    char dda;

    double dda1;

    int type

    };

    这个结构占用的空间为多大呢?在VC6.0环境下,可以得到sizeof(MyStruc)为24。结合上面提到的分配空间的一些原则,分析下VC怎么样为上面的结构分配空间的。(简单说明)

    struct MyStruct

    {

    char dda;//偏移量为0,满足对齐方式,dda占用1个字节;

    double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8

    //的倍数,需要补足7个字节才能使偏移量变为8(满足对齐

    //方式),因此VC自动填充7个字节,dda1存放在偏移量为8

    //的地址上,它占用8个字节。

    int type;//下一个可用的地址的偏移量为16,是sizeof(int)=4的倍

    //数,满足int的对齐方式,所以不需要VC自动填充,type存

    //放在偏移量为16的地址上,它占用4个字节。

    };//所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构

    //的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof

    //(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为

    //sizeof(double)=8的倍数。



    所以该结构总的大小为:sizeof(MyStruc)为1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。



    VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。

    VC中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n
  • static 是c++中很常用的修饰符,它被用来控制变量的存储方式和可见性,下面我将从 static 修饰符的产生原因、作用谈起,全面分析static 修饰符的实质。

    static 的两大作用:

    一、控制存储方式:

      static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间。

      1、引出原因:函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,大家知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现?
    最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此函数控制)。

      2、 解决方案:因此c++ 中引入了static,用它来修饰变量,它能够指示编译器将此变量在程序的静态存储区分配空间保存,这样即实现了目的,又使得此变量的存取范围不变。

    二、控制可见性与连接类型 :

      static还有一个作用,它会把变量的可见范围限制在编译单元中,使它成为一个内部连接,这时,它的反义词为”extern”.

      static作用分析总结:static总是使得变量或对象的存储形式变成静态存储,连接方式变成内部连接,对于局部变量(已经是内部连接了),它仅改变其存储方式;对于全局变量(已经是静态存储了),它仅改变其连接类型。

    类中的static成员:

    一、出现原因及作用:

      1、需要在一个类的各个对象间交互,即需要一个数据对象为整个类而非某个对象服务。

      2、同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。

      类的static成员满足了上述的要求,因为它具有如下特征:有独立的存储区,属于整个类。

    二、注意:

      1、对于静态的数据成员,连接器会保证它拥有一个单一的外部定义。静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。

      2、类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态数据和静态成员函数。

    const 是c++中常用的类型修饰符,但我在工作中发现,许多人使用它仅仅是想当然尔,这样,有时也会用对,但在某些微妙的场合,可就没那么幸运了,究其实质原由,大多因为没有搞清本源。故在本篇中我将对const进行辨析。溯其本源,究其实质,希望能对大家理解const有所帮助,根据思维的承接关系,分为如下几个部分进行阐述。

    c++中为什么会引入const

      c++的提出者当初是基于什么样的目的引入(或者说保留)const关键字呢?,这是一个有趣又有益的话题,对理解const很有帮助。

    1. 大家知道,c++有一个类型严格的编译系统,这使得c++程序的错误在编译阶段即可发现许多,从而使得出错率大为减少,因此,也成为了c++与c相比,有着突出优点的一个方面。

    2. c中很常见的预处理指令 #define variablename variablevalue 可以很方便地进行值替代,这种值替代至少在三个方面优点突出:

      一是避免了意义模糊的数字出现,使得程序语义流畅清晰,如下例:
      #define user_num_max 107 这样就避免了直接使用107带来的困惑。

      二是可以很方便地进行参数的调整与修改,如上例,当人数由107变为201时,进改动此处即可,

      三是提高了程序的执行效率,由于使用了预编译器进行值替代,并不需要为这些常量分配存储空间,所以执行的效率较高。

      鉴于以上的优点,这种预定义指令的使用在程序中随处可见。

    3. 说到这里,大家可能会迷惑上述的1点、2点与const有什么关系呢?,好,请接着向下

    看来:

      预处理语句虽然有以上的许多优点,但它有个比较致命的缺点,即,预处理语句仅仅只是简单值替代,缺乏类型的检测机制。这样预处理语句就不能享受c++严格类型检查的好处,从而可能成为引发一系列错误的隐患。

    4.好了,第一阶段结论出来了:
    结论: const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。

    现在它的形式变成了:

    const datatype variablename = variablevalue ;
    为什么const能很好地取代预定义语句?
    const 到底有什么大神通,使它可以振臂一挥取代预定义语句呢?

    1. 首先,以const 修饰的常量值,具有不可变性,这是它能取代预定义语句的基础。

    2. 第二,很明显,它也同样可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。

    3. 第三,c++的编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高,同时,这也是它取代预定义语句的重要基础。这里,我要提一下,为什么说这一点是也是它能取代预定义语句的基础,这是因为,编译器不会去读存储的内容,如果编译器为const分配了存储空间,它就不能够成为一个编译期间的常量了。

    4. 最后,const定义也像一个
  • Solmyr和Zero的故事 —— 内存,最后一块
    Zero

    --------------------------------------------------------------------------------

    “Shit!又死机了。我已经在这平台上工作了一个月了。可死机的次数比我在这个月里被女孩甩的次数还多。为什么?还不是这该死的平台,为什么掌上系统的内存就和愿意和我说话的女孩一样少?”Solmyr抱怨道。

    “兄弟,怎么了。”Zero问。

    “Zero是我们这组的主程序员,他懂得很多,人长的也帅,很讨女孩子的欢心。甚至连美工组的Lili(长得比孙燕姿还好看)也暗恋他。”。Solmyr一边想,一边说,“老大,你看,又死机了。为什么我每次用动态内存超过10次,就crash了呢?”

    “我知道你原来是Java程序员,也许到现在,你还念念不忘那GC (垃圾收集机制,我说还不如叫上帝也哭泣-God Cry),可你要知道,你现在是在用C++编程,重要的是效率。”

    “效率,我……”忽然,Solmyr觉得嘴角边似乎有液体流过。那是Solmyr的口水。每次提到效率,Solmyr总要流口水,就像看到漂亮MM,Solmyr要喷鼻血一样。

    “C++中,关于动态内存的是new and delete。”

    “我知道,”Solmyr急于表现自己,想证明自己对C++并不是一无所知,“我正在读Scott Meyer的More Effective C++。在C++中,new operator是C++内建的行为。任何人(也许除了Bjarne Stroustrup)都无法改变。new operator先调用一个名为operator new的函数动态申请内存。标准模式就像这样:

    void* operator new(size_t size);

    然后在传回的void*指针上进行构造的行为。而delete operator则先析构对象,然后调用名为operator delete的函数,标准形式就像这样:

    void operator delete(void* pToDeAlloc)

    // GotW中说即使在指针参数后加上size_t size,仍然是标准形式。
    // size_t size的作用是检查所要卸除的内存是否是期望的大小,
    //如果在类层次中定义的话,只要基类是virtual destructor,那么size可以
    // 确保是正确大小。

    而placement new……。”

    “唔,说得不错,有进步。关于placement new/delete,先知Meyer[1]有详细的论述。原来的placement new仅仅是这样:

    void* operator new(size_t, void* pMem) { return pMem; }

    ‘ 随着时间过去,任何‘要求额外引数’的 operator new 版本,也都渐渐采用 placement new 这个术语。事实上这个术语已经被铭记於 C++ 标准规格中。因此,当 C++ 程序员谈到所谓的 placement new 函数,他们所谈的可能是上述那个‘需要额外一个 void* 参数,用以指出物件置于何处’的版本,但也可能是指那些‘所需参数比单一而必要之 size_t 引数更多’的任何 operator new 版本,包括上述函数,也包括其他‘参数更多’的 operator new 函数。’——引自[1]。

    现在我们来考虑一个问题,如果在operator new结束申请内存后,构造函数抛出了异常,那么已经申请的内存谁来回收,答案当然是编译器。因为整个new operator还未结束。所以你无法获得控制权。如果你对operator new/delete进行了重载,那么编译器会调用那个operator delete呢?由于不同的operator new可能通过不同的方法获得内存,而让不知道怎样分配的operator delete去释放内存显然是不负责任的。所以编译器假定哪个operator delete与operator new有相同的参数(除了size_t size和void* pDeAlloc),那么那个operator delete便知道operator new怎样获得内存。构造函数抛出异常后,也会调用与operator new相同参数的operator delete来释放内存。”

    “那么,我该怎么解决现在的问题呢?”

    “别那么急,已经下午五点了。该回家了,别让人以为程序员是夜游神。回家看看Effective C++第二部分和GotW9, GotW10。明天再说吧。”

    第二天一早,Solmyr啃着大饼走进了办公室,看到Guru Zero早已姿势优雅地坐在电脑前收发Email,不禁自惭形秽,连忙放下手中的大饼,跑去和Zero说:“老大,昨天晚上,我挑灯夜读,总结出两点:

    1. 如果你写了一个operator new,请对应写一个operator delete。
    2. 不要delete不是自己new来的内存。
    “嗯,不错,怎么我看上去,就象是Effective C++中的条款呢?你有没有自己想过关于placement delete的问题?”

    “placement delete有什么问题吗?”Solmyr一脸茫然的问道。

    “你有没有试过把一块用placement new申请得到的内存用placement delete卸除掉呢?不妨你现在试试看。”

    只见Solmyr跑到一台电脑前,两手如飞在键盘上敲击,可是我们能听到的只有他的唉声叹气和编译器的哇哇大叫。Solmyr实在是没办法,只能向Zero求教。Zero喝了一口咖啡,说道:“我们平时写的那些要求额外参数的operator delete只有在构造函数抛出异常时,才会被编译器自动调用,而我们是不可能手工调用到任何带有额外参数的delete的,为什么没有一个内建的‘placement delete’来与‘placement new’相匹配的原因在于没有办法保证它被正确使用,在C++类型系统中,无法推断一个指针从哪里获得它指向的内存,可能是指向heap,也可
  • Solmyr 的小品文系列之八:拷贝
    Elminster

    --------------------------------------------------------------------------------

    “zero 帮帮忙吧 ~~ ”

    “灿烂”的笑脸,充满诚意的眼神,再加上点头哈腰的姿势,这三者构成了一尊名为“有求于人”的塑像。

    在 QQ 上聊的正欢的 zero 抬起头,看着塑像的作者和材料 ——— pisces ,方圆五十米内唯一的女性程序员 ——— 问道:“什么事?”

    “我这里有一段 C++ 程序调不通。”

    “这类问题你应该去问 Solmyr。”

    “哎呀,别开玩笑了,我哪敢去问他呀!总说我笨!上次问他一个小问题,结果又被训的狗血喷头,哼!”,pisces 显得忿忿不平,“还是你来帮帮我吧,我知道你是部门里有数的高手,肯定搞的定的。帮帮忙吧 ~~”

    zero 明显的被打动了,于是,在 pisces 的努力下,zero 坐到了 pisces 的计算机前。

    “好吧,什么问题?”

    “是这样的啦,这里有一组 C 风格的 API ,负责管理设备上的字符通信链接。它们是好些年前设计的”,说着,pisces 调出了一些代码:

    // old C style API
    typedef int conn_handle;
    typedef struct
    {
    /* ... 打开链接所需的参数和属性 ... */
    }conn_attr;

    conn_handle open_conn(conn_attr* p_attr, char* buf, unsigned int buf_size);
    void close_conn(conn_handle h);

    char read_conn(conn_handle h);
    void write_conn(conn_handle h, char c);

    ...

    “枝节的东西不算,主干大概就是这样,一对函数负责打开和关闭,一对函数负责读写。创建链接时候的那个 buf 参数指向一个缓冲区,这个要你自己分配并把长度传进去,和链接一一对应,read_conn/write_conn 会用它做缓冲。我的任务就是写个类把这些 API 包装起来。”,说着 pisces 又调出了另外一段代码:

    // pisces’ connection class
    class connection
    {
    private:
    conn_attr m_attr;
    bool m_opened;
    int m_bufsize;
    char* m_buf;

    conn_handle m_h;
    ...

    public:
    connection(const conn_attr& attr, int bufsize)
    {
    m_attr = attr;
    m_opened = false;
    m_bufsize = bufsize;
    m_buf = new char[m_bufsize];
    }
    ~connection() { delete m_buf; }

    void open()
    {
    m_h = open_conn(&m_attr, m_buf, m_bufsize);
    m_opened = true;
    }
    void close()
    {
    close_conn(m_h);
    m_opened = false;
    }

    char read()
    {
    assert(m_opened);
    return read_conn(m_h);
    }
    void write(char c)
    {
    assert(m_opened);
    write_conn(m_h, c);
    }
    ...

    };

    “应该是很简单的,可是不知道怎么回事,用了 connection 类的程序总是时不时的崩溃,说是非法的内存操作。”,pisces 显得很苦恼。

    zero 一眼就看出了毛病 ——— 这使他小小的自鸣得意了一下 ——— 但是表面上不动声色,等到他看过 pisces 提供的“总是引发崩溃”的代码段之后,他才开口说到:

    “这是一个常见的错误 pisces”,zero 尽量使自己的口吻和语气听起来象一个权威,“关于 C++,有一条重要的指导原则:析构函数、拷贝构造函数和赋值运算符三者几乎总是一起出现。也就是说,如果你为一个类写了析构函数,那么往往你不得不再提供一个拷贝构造函数和一个赋值运算符,违反它往往意味着错误。你看这里:”

    说着,zero 在屏幕上标出了两行代码:

    void some_func()
    {
    conn_attr attr;
    ...
    connection c1(512, attr);
    connection tmp = c1;
    ...
    }

    “这里对象 tmp 是从 c1 拷贝构造而来的,而你没有定义拷贝构造函数,这使得编译器在这里自动进行按位拷贝,而这使得 tmp 和 c1 的所有成员都相等,包括 m_buf 成员。这样在函数返回时,c1 析构的时候 delete 了一遍 m_buf,在 tmp 析构的时候又 delete 了一遍 ……”

    “哦!我明白了!” pisces 打断了 zero ,“所以就出现一个非法内存操作,对吧?哎呀,这一条以前在学校里写 string 类的时候遇到过,我怎么会忘了呢?”

    “对,你只要写一个拷贝构造函数和一个赋值运算符处理一下 m_buf 指针就可以解决这个问题了。这你自己搞的定吧?”

    “我可以的,多谢了 zero !”

    zero 心满意足的回到了自己的座位上,开始继续和“你不懂我纤细的心”在 QQ 上探讨“爱情的意义”。可是好景不长,没过多久,本文开头所描述的景象再一次的出现了。

    “zero 帮帮忙吧 ~~ ”

    zero 在心中叹了口气,抬头问道:“又是什么问题,pisces?”

    “呃,还是那个类。我照你说的给 conn 添加了拷贝构造函数,非法内存操作确实少多了,可还是有,还有好像链接传输数据也有点问题 ———— 你还是过来帮我看看吧 ~~”

    zero 心不甘情不愿的再次来到了 pisces 的计算机前,翻出 pisces 写的拷贝构造函数检查起来:
  • Solmyr 的小品文系列之七:异常
    Elminster

    --------------------------------------------------------------------------------

    大雨。

    乌云象铅块一样低低的压了下来,豆大的雨滴打的玻璃窗啪啪作响,难得一见的异常天气正在竭力表现它令人讨厌的一面。不过这一切似乎并没有影响到 Solmyr,他仍然以他习惯的舒适姿势半躺在宽大的椅子里,手里还托着一杯热腾腾的果汁,在他背后,zero 在键盘上敲打着什么。

    “唉,Solmyr ,标准库中的 stack 怎么会是这个样子?设计糟透了。”zero 停止了工作,转过身来面对 Solmyr ,看起来有些困惑。

    “胡乱批评被纳入神圣标准的成员是会遭天遣的。”Solmyr 低着头,以一种算命先生似的语调答道。

    不知道上天是否打算加强 Solmyr 的说服力,恰在此时天空划过一道闪电,蓝白色的电光挣扎着努力向地面扑来,紧接着就是“喀喇”一声巨响 ——— 这个雷很近。

    一秒钟前还在想“这未免也太扯了”的 zero 表情一下子变得很古怪,良久才恢复正常。他标出了两行代码接着说到:“好、好吧,Solmyr,那请你解释一下为什么 stack 的界面是这个样子。”

    std::stack<int> si;
    ……
    int i = si.top();
    si.pop();

    “只要让 pop() 返回栈顶元素就可以把上面两行合成一行,而且更加直观,为什么要搞成现在这样?”

    目睹了 zero 表情变化的 Solmyr 强忍住放声大笑的冲动 ——— 老天知道他忍的有多辛苦 ——— 缓缓的把杯子放到桌上,转过身来开始讲解这个问题:

    “原因在于异常。”

    “异常?”

    “对,很多代码在没有异常的时候工作的挺好,但是一旦出现异常就变得不可收拾,就像一间茅草屋,平时看起来没什么问题,一遇到今天这种天气 …… ”,Solmyr 指了指窗外,“ …… 立刻就会垮掉。考虑一下如果 pop() 返回栈顶元素需要怎样实现,假设栈内部用数组实现,且不考虑栈是否为空的问题。”

    “很简单啊。”,zero 打开了编辑器,写下:

    template <typename T>
    T stack<T>::pop()
    {
    ... ...
    return data[top--]; // 假设数据存储于数组 data 中,top 代表栈顶位置
    }

    Solmyr 摇摇头:“这就是茅草屋。要知道 stack 是个模板类,它存放的元素 T 可能是用户定义的类。我来问你,如果类型 T 的拷贝构造函数抛出异常,会出现什么情况?”

    “嗯 …… 按值返回,返回值是个临时对象,该临时对象以 data[top] 拷贝构造 …… 嗯,这样一来函数返回时可能抛出异常,客户此时无法取得该元素。”

    “还有呢?”

    “还有?”

    “提示,你的 top 怎么了?”

    “ …… 哎呀!糟了!top 此时已经减一,栈顶元素就此丢失了!这样的话 …… 必须实现一个函数允许客户修改 ……”,zero 说不下去了。他想了一会,摇摇头承认失败:“不行,这里拷贝构造发生在函数返回之后,无论如何无法避免这种情况。只能在文档里写明:要求 T 的拷贝构造函数不抛出异常。” zero 停了一停,小心翼翼的问 Solmyr :“这个不算过分的要求吧?”

    Solmyr 的回答异常简短:“new”

    “哦对,new 在分配内存失败时会抛出 std::bad_alloc …… 算我没说。Solmyr ,我明白了,为了处理异常的情况,调整栈顶位置必须在所有数据拷贝完成之后,所以按值返回是不可接受的。”

    “正确。所以对于一个设计目标是最大限度可复用性的标准库成员而言,这是不可接受的。” Solmyr 顿了顿,继续说到:“而且异常带来的影响远不止此。我刚才说‘假设栈内部用数组实现’,但如果你充分考虑抛出异常的各种可能性,你就会发现用数组实现是糟糕的主意。”

    “ …… …… …… …… …… 这是为什么?在没有传值返回的情况下,我们总可以捕捉到发生的异常并加以处理啊?”,zero 谨慎的发问。

    Solmyr 赞许的看着 zero 。“发问之前先自行思考,习惯不错。”,Solmyr 心想,但是脸上一点也没表现出来:“没错,但捕捉到异常不代表你总能正确的处理它。考虑一下 stack 的赋值运算符,如果我们用数组来实现,那么在拷贝数据的时候肯定会有类似这样的一个循环:”


    // 各变量的意义与上面相同
    template <typename T>
    stack<T>& stack<T>::operator=(const stack<T>& rhs)
    {
    ... ...
    for(int i=0; i<rhs.top; i++)
    data[i] = rhs.data[i];
    ... ...
    }


    “现在考虑类型 T 的赋值运算符可能抛出异常,该怎样修改上面的代码。” Solmyr 停了下来,再度捧起了杯子。

    “用 try 把 …… 哦 …… …… …… …… …… ……”,zero 似乎发现了问题所在,沉默良久,才接着说到:“这个循环可能在运行到一半的时候抛出异常,这样会导致一部分数据已经成功赋值,另一部分却还是老的。除非我们用 catch(...) 捕捉所有异常,忽略之并继续赋值。”

    “但是这样 ……”,Solmyr 有意识的引导 zero 继续深入思考。

    “…… 但是这样,赋值运算符抛出的异常就被我们‘吃掉了’,异常总是代表着某些不该发生的事情发生了,所以应该让客户接收到这个异常才对。” zero 皱着眉头,一字一顿,显得相当辛苦。
  • Solmyr 的小品文系列之六:成对出现
    Elminster

    --------------------------------------------------------------------------------

    “呼 ~~~~ 啪!”

    一个文件夹划出一道优美的弧线,越过四张桌子,两堵隔墙,一条走道,不偏不倚的穿过了正在交谈的路人甲和路人乙,精准的命中了目标。放眼公司上下,拥有这般投掷手法的,只有 Solmyr ,而他的目标,自然是 zero 了。

    “哎哟!”,zero 摸了摸被击中的后脑勺,一半不甘一半认命的叹了一口气:不用问,他一定又有什么把柄被 Solmyr 抓住了。

    “这次我又犯了什么错误了?”,zero 匆匆中断了与方圆五十米内唯一的女程序员 pisces 之间愉快的闲聊,来到 Solmyr 身边看看究竟哪里出了不妥。

    “你刚刚提交的代码会导致线程死锁”,Solmyr 指着 zero 提交的一个函数:

    void some_func()
    {
    pthread_mutex_lock(&mtx);
    ……
    ……
    pthread_mutex_unlock(&mtx);
    }

    “会吗?我明明在函数末尾释放了互斥变量的呀?”

    Solmyr 看了看 zero ,那表情分明在说:朽木不可雕也。他顺手标出了函数中间的两行代码:

    void some_func()
    {
    pthread_mutex_lock(&mtx);
    ……
    if( status == E_FAIL )
    return;
    ……
    pthread_mutex_unlock(&mtx);
    }

    “Oops!”,zero 拍了一下脑门,“我知道了我知道了,我这就改。”

    “你知道了?说说看你犯了什么错误?”

    “我忘了在中间的函数返回点解锁。”

    “那你准备怎么解决这个问题”,很明显,Solmyr 不打算就此轻轻放过 zero。

    “嗯 …… 很简单啊,在这里加上一行代码,象这样:”

    if( status == E_FAIL )
    {
    pthread_mutex_unlock(mtx);
    return;
    }

    Solmyr 摇摇头:“你这是头痛医头,脚痛医脚。如果你这个函数里不只一个锁,不只一个返回点,你打算怎么做?在每个返回点解开每个锁么?”

    “嗯 …… 你是指我应该遵循一个函数只有一个返回点的原则?”,zero 挠挠头,有些不太确定。

    “我不是指这个。有些情况下,硬要让函数只有一个返回点会导致巨大的 if/else 结构,降低代码的可读性。而且,即使你的函数只有一个返回点,你还是有可能遇到这个问题。考虑这样的函数:”,Solmyr 飞快的键入:

    void some_func()
    {
    pthread_mutex_lock(&mtx);
    ……
    // 中间没有其他返回点
    ……
    foo(); // 由其他程序员实现的函数
    ……
    pthread_mutex_unlock(&mtx);
    }

    “看起来一点问题也没有,可是如果 foo 这个函数丢出异常的话,会出现什么情况?”

    “嗯 …… 如果我们函数里没有捕获这个异常的话 …… 它会导致 some_func 函数在调用 foo 的这一点中断 …… 哎呀 ……”,zero 发现了问题所在。“那么只能在每个可能抛出异常的函数调用点用 try 捕获所有异常,然后 ……”,zero 越说越小声,“ …… 然后在 catch 里面解锁,再重新抛出 ……” zero 停了下来,烦恼的挠着头,发现他连自己都说服不了:这样的解法实在是太繁琐、太容易引入错误了。

    “嗯?”

    “好吧,我承认我不知道该怎么办了,Solmyr ,这种情况应该怎么处理呢?”

    “回忆一下,前两天我们在饭桌上讨论过什么?”(参见“Solmyr 的小品文系列”的前一期,“垃圾收集”)

    “你是说垃圾收集吗?哎 …… 可是 …… 那是处理内存泄漏的呀?和这个问题有什么关系?”

    “我不是指具体的解法,”,Solmyr 摇摇头,“关键是上次讨论中引入的具有普遍性的原则,也就是 ……” Solmyr 停了下来,转头看着 zero 。

    “…… ……”

    “唉 ……”,Solmyr 用别人模仿不来的无奈表情 —— 按照他自己的说法,这是多年培训工作的积累 —— 叹了口气:“我说 zero,你还很年轻,不会这么早就记忆力衰退了吧?”

    …… 真是可恶的家伙,zero 心中恨恨的想。

    Solmyr 的声音再度在 zero 接近崩溃边缘的时候响了起来:“如果你希望保证某些事情成对出现,请使用 ……”

    “构造函数与析构函数!”,zero 生怕错过了显示自己并非“记忆力衰退”的机会。

    “不用喊那么大声。”,Solmyr 皱了皱眉,“你把前排观众都吓坏了。”

    “?!!!”,zero 迅速转身,发现附近不知什么时候围满了公司的同事,每个人都“正常”在做自己的事情,只是动作稍显忙乱而已 ……

    解决了四周的“观众”之后,zero 回到了显示器前,信心满满:“我知道了 Solmyr ,这里我们可以用和上次处理 分配/释放 内存非常类似的手段来处理 加锁/解锁,只要写一个非常简单的类就行了,象这样:”,zero 一边说,一边键入:

    class auto_lock
    {
    public:
    auto_lock(pthread_mutex_t mtx) : m_mtx(mtx)
    {
    pthread_mutex_lock(&m_mtx); // 构造时加锁
    }
    ~auto_lock()
    {
    pthread_mutex_unlock(&m_mtx); // 析构时解锁
    }

    private:
    pthread_mutex_t& m_mtx;
    }

    void some_func()
    {
    auto_lock(mtx);
    ……
    // return 、foo ,随便什么东西都行
    ……
    // 结束的时候同样不用解锁
    }

    “这样一来,我之前遇到
  • Solmyr 的小品文系列之五:垃圾收集
    Elminster

    --------------------------------------------------------------------------------

    午餐时间。

    zero 坐在餐桌前,机械的重复“夹菜 -> 咀嚼 -> 吞咽”的动作序列,脸上用无形的大字写着:我心不在焉。在他的对面坐着 Solmyr ,慢条斯理的吃着他那份午餐,维持着他一贯很有修养的形象 ——— 或者按照 zero 这些熟悉他本质的人的说法:假象。

    “怎么了 zero ?胃口不好么?”,基本填饱肚子之后,Solmyr 觉得似乎应该关心一下他的学徒了。

    “呃,没什么,只是 …… Solmyr ,C++ 为什么不支持垃圾收集呢?(注:垃圾收集是一种机制,保证动态分配了的内存块会自动释放,Java 等语言支持这一机制。)”

    Solmyr 叹了口气,用一种平静的眼神盯着 zero :“是不是在 BBS 上和人吵 C++ 和 Java 哪个更好?而且吵输了?我早告诉过你,这种争论再无聊不过了。”

    “呃 …… 是”,zero 不得不承认 ——— Solmyr 的眼神虽然一点也不锐利,但是却莫名其妙的让 zero 产生了微微的恐惧感。

    “而且,谁告诉你 C++ 不支持垃圾收集的?”

    “啊!Solmyr 你不是开玩笑吧?!”

    “zero 你得转变一下观念。我问你,C++ 支不支持可以动态改变大小的数组?”

    “这 …… 好象也没有吧?”

    “那 vector 是什么东西?”

    “呃 ……”

    “支持一种特性,并不是说非得把这个特性加到语法里去,我们也可以选择用现有的语言机制实现一个库来支持这个特征。以垃圾收集为例,这里我们的任务是要保证每一个被动态分配的内存块都能够被释放,也就是说 ……”,Solmyr 不知从哪里找出了一张纸、一支笔,写到:

    int* p = new int;
  • Solmyr 的小品文系列之四:对象计数(下)
    Elminster

    --------------------------------------------------------------------------------

    “空泛的讨论让人厌烦。”,Solmyr 笑容可掬的说道,“不如我们设定一个简单的场景来看看你的计数器怎么使用吧。假设你是暴雪的程序员,要为星际争霸设计程序表示神族的单位,那么最简单的方案是 ——”,Solmyr 停了下来,望向 zero 。

    zero 松了一口气 —— 这个问题还不算困难。他在脑中整理了一下思路:“神族的单位应该设计为一个基类,然后每种特定的兵种从这个类派生,每个单位就是这样一个类的对象。”想到这里,他飞快的在百板上写下:

    class ProtossUnit
    {
  • Solmyr 的小品文系列之三:对象计数(上)
    Elminster

    --------------------------------------------------------------------------------

    台下的座位已经坐满了,除了 Solmyr 的位子。zero 手足无措的望着那唯一的空位,开始第一百次的哀叹为什么自己会落到这样一个尴尬的位置。仅仅几分钟前,一切都还很正常,直到 …………

    …………

    主持人:“下一个议程,题为‘对象计数’的 C++ 编程技术讲座,主讲人是zero。”

    zero: “什 …… 什么?!等一等,这个讲座不是应该由 Solmyr 主讲吗?!”

    主持人:“嗯,原定是由 Solmyr 来讲,不过临时有要事出去了,离开之前他指定你顶替。他没有告诉你吗?”

    zero: “他压根没有和我提过!我 …… 我什么准备也没做!这怎么行?别开玩笑了?!”

    主持人:“你不用谦虚,Solmyr 临走前对我说过你完全能够胜任这个议题。啊对了,这里有一张他留给你的条子。”

    zero 打开条子,但见上面写到:“《50 诫》(注:指《More Effective C++ 2/e》一书)看得怎么样了?如果你认真看过,就没问题。如果你敢拒绝或者出了岔子,嘿嘿 ……”

    …………

    “唉!”,zero 认命的叹了口气,“面对现实,硬着头皮上吧!”他决定就讲最简单的那部分,反正把这个场面搪塞过去就行了。他望着白板上“对象计数”四个大字,开口说到:“今天 …… 这个 …… 今天讨论的议题是‘对象计数’。所谓对象计数 …… 啊 …… 就是对计算某个类有多少个对象”。

    开场白糟透了,zero 觉得还是尽快转入实际的东西比较好。

    “对于这个问题 …… 最简单的做法是在需要计数的类中添加一个静态变量,保存当前的对象个数,并利用构造函数和析构函数增减它的值,象这样:”

    class Wedget
    {
    public:
  • Solmyr 的小品文系列之二:模棱两可的陷阱
    Elminster

    --------------------------------------------------------------------------------

    “为什么会这样?!”,zero 一边喝水一边嘟囔着,恨恨的看着面前显示器上的代码,“为什么这么简单的一个调用也会出现编译错误 …… ”

    “这是因为你的设计太差!”

    噗!zero 被幽灵一样出现在背后的 Solmyr 吓了一大跳,一口水差点全喷出来。

    “咳!咳咳!S …… Solmyr ,你什么时候站在我背后的?”,zero 很费力的平息了咳嗽,同时努力回想刚才自己有没有把柄会被 Solmyr 抓到。

    Solmyr 抓过一张椅子坐了下来:“在你一开始干傻事的时候我就在了,正是这个糟糕的设计导致了现在困扰你的编译错误。”

    “哪 …… 哪里?”

    “这儿。” Solmyr 抓过键盘,标出了下面这段代码:

    void SomeFunc(int i)
    …………

    void SomeFunc(float f)
    …………

    int main()
    {
  • Solmyr 的小品文系列之一:字符串放在哪里?
    Elminster

    --------------------------------------------------------------------------------

    画外音:今天是个大晴天,温暖的阳光透过窗子照进了这间宽敞的办公室,办公室里三三两两的人们正在各自的计算机前努力工作,一切都显得那么的安静、祥和、有条不紊 ……

    “啊~!救命啊!Solmyr 你又用文件夹砸我!”

    “愚蠢者是应该受到惩罚的。”

    画外音: …… 呃,好吧,我得承认有点小小的例外。这里是一家软件公司,发出惨叫的这位是 zero ,新进的大学生;这边一脸优雅,看上去很有修养一点也不象刚刚砸过人的这位,是 Solmyr ,资深程序员,负责 zero 这一批新人的培训。啊,故事开始了 ……

    “我干了什么啦?”zero 揉着鼻子问道,“这次你拿来砸我的文件夹又大了一号!”

    “你过来自己看看你犯下的错误。”Solmyr 翻出了 zero 刚刚交上来的一段代码:

    ……
    char* msg = “Connectting ... Please wait“
    ……
    if( Status == S_CONNECTED )
  • 作者简介
    Elminster简介

    --------------------------------------------------------------------------------

    姓名:谢之易

    年龄:25

    目前主攻方向:C/C++ 、面向对象、泛型编程

    接下来说些什么呢?真伤脑筋啊 ……

    如果从 BASIC 算起,我学习编程已经七年了。时间不算短,然而如果统计一下的话,在这七年里走弯路的时间和扎扎实实进步的时间,大概是五比一的比例。

    这不是个令人愉快的结论,不过反过来讲,常常迷路的人,往往最明白路标的可贵。值得庆幸的是,在这方面我们已经有了很多财富:它们来自 Bjarne Stroustrup 、Scott Meyers 、Herb Sutter 这些先辈大师们,对于学习标准 C++ 的人而言,这些大师的著作是真正的宝藏。而我,特别希望能够和大家分享各自在学习中获得的经验教训,让每个人的心得都能够传播开去 —— 以一种轻松愉快的方式,并加上自己的一点小小创意 —— 如果有的话。

    于是,就有了下面这些文字。

    自我简介
    我名叫陶文,现在仍在大学学习。很高兴你能够看到我制作的这个chm文件。我所制作的这些东西都是我喜欢的,如果你也喜欢,我们就算有共同喜好了。特别希望你能造访我的主页,或者给我发mail一起来学习这些有趣的东西:

    taowen.cn.st

    mo2mo@163.com
  • Solmyr和Zero的故事——临时工
    Zero

    --------------------------------------------------------------------------------

    “Z z z ……”,Solmyr又在打鼾了,虽然说上班时打呼噜是被小组禁止的,但没有人能阻止Solmyr在睡梦中梦想自己成为大虾,教训现实中的大虾Zero。梦中他正扁Zero扁的高兴,口水流了一写字台,没想到突然听到一声“Stupid”,猛地惊醒,看见原来是测试部门发来的Email,抱怨他写的程序通不过测试。

    他把程序代码装入了UltraEdit,看了一下,原来是那段字符串处理程序:

    void f(string& s1, string& s2)

    {

    const char* cs = (s1 + s2).c_str();

    cout << cs;

    }

    在他看来,这段程序没什么问题,他试着测试了一下,没什么问题,cs正确的显示了结果,不是么。“该死的测试部门,总是莫名其妙的发来这些毫无意义的邮件……”Solmyr嘴里嘟囔着,突然听到身后传来的声音“注意临时对象的生存期,孩子。”

    Solmyr吓了一跳,是Zero,他总是在你受窘的时候出现,并无私的帮助你(虽然偶尔会带几句嘲笑和讽刺),这次他又想怎么样呢?“孩子,你知道临时对象的生存期吗?”“唔,我想,大概是,应该是在退出它的作用域(scope)之后,它被析构吧。”Solmyr脸色苍白,支支吾吾的答道。

    “不,不对,他们将会在创建他们的表达式的结尾被析构(“TCPL": a temporary object is destroyed at the end of the full expression in which it was created. A full expression is an expression that is not a subexpression of some other expression),不妨你再运行一下你的程序看看。”

    Solmyr又运行了一次,令人惊讶的是这次的结果竟然和上次不一样,太夸张了。这时,Zero的声音又在耳旁响起“现在,说说为什么会是这样。Solmyr想了一下,突然大有领悟的说:“由于s1 + s2所产生的临时对象在表达式结束之后就被析构了,所以cs指向的内存就不一定存在了,可能还是原来的s1+s2,也可能是别的,所以就不能保证显示正确。”

    “很好,可情况并不是总是那么简单,C++规定,临时对象可以做为常量引用和命名对象(named object)的初始器(initializer),就像下面一样:

    void f(const string&, const string&);

    void h(string& s1, string& s2)

    {

    const string& s = s1 + s2;

    string ss = s1 + s2;

    f(s, ss);

    }

    上面的代码将会运行的很好,而临时对象也会在常量引用和命名对象退出他们的作用域后被摧毁。 临时对象常会出现在以下场合:类型转换和函数返回。函数返回值一般能被编译器优化掉,所以你可以不必担心它带来的开销。而类型转换则破费思量,它的目的一般是为了使函数调用能够成功,如下:

    void uppercasify(string& str);

    // changes all chars in str to upper case

    char subtleBookPlug[] = "Effective C++";

    uppercasify(subtleBookPlug); // error!

    为什么呢,你能告诉我吗?”

    “因为要使函数调用成功,必须将subtleBookPlug转换成string类型,而编译器认为你要改变的subtleBookPlug,而类型转换后将产生一个类型为string的临时对象,而在void uppercasify(string& str)中,被改变的将是这个临时对象,而不是subtleBookPlug,这显然不是程序员所期望的,所以C++明智地禁止了这种行为。”

    “很好,今天你表现的很好,我的孩子,但记住,千万不要在背后说测试部门的坏话,否则的话,哼哼……”


    --------------------------------------------------------------------------------

    注:
    本文所有例子均参考了

    The C++ Programming Language 3rd

    More Effective in C++

    如读者觉得没弄明白或没过瘾的话,可以参考TCPL Pg254-255, MEC Item 19, Item 20
  • 程序员常常需要实现回调。本文将讨论函数指针的基本原则并说明如何使用函数指针实现回调。注意这里针对的是普通的函数,不包括完全依赖于不同语法和语义规则的类成员函数(类成员指针将在另文中讨论)。

    声明函数指针

    回调函数是一个程序员不能显式调用的函数;通过将回调函数的地址传给调用者从而实现调用。要实现回调,必须首先定义函数指针。尽管定义的语法有点不可思议,但如果你熟悉函数声明的一般方法,便会发现函数指针的声明与函数声明非常类似。请看下面的例子:

    void f();// 函数原型

    上面的语句声明了一个函数,没有输入参数并返回void。那么函数指针的声明方法如下:

    void (*) ();

    让我们来分析一下,左边圆括弧中的星号是函数指针声明的关键。另外两个元素是函数的返回类型(void)和由边圆括弧中的入口参数(本例中参数是空)。注意本例中还没有创建指针变量-只是声明了变量类型。目前可以用这个变量类型来创建类型定义名及用sizeof表达式获得函数指针的大小:

    // 获得函数指针的大小
    unsigned psize = sizeof (void (*) ());

    // 为函数指针声明类型定义
    typedef void (*pfv) ();

    pfv是一个函数指针,它指向的函数没有输入参数,返回类行为void。使用这个类型定义名可以隐藏复杂的函数指针语法。

    指针变量应该有一个变量名:

    void (*p) (); //p是指向某函数的指针

    p是指向某函数的指针,该函数无输入参数,返回值的类型为void。左边圆括弧里星号后的就是指针变量名。有了指针变量便可以赋值,值的内容是署名匹配的函数名和返回类型。例如:

    void func()
    {
    /* do something */
    }
    p = func;

    p的赋值可以不同,但一定要是函数的地址,并且署名和返回类型相同。

    传递回调函数的地址给调用者

    现在可以将p传递给另一个函数(调用者)- caller(),它将调用p指向的函数,而此函数名是未知的:

    void caller(void(*ptr)())
    {
    ptr(); /* 调用ptr指向的函数 */
    }
    void func();
    int main()
    {
    p = func;
    caller(p); /* 传递函数地址到调用者 */
    }

    如果赋了不同的值给p(不同函数地址),那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。

    调用规范

    到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C++的编译器规范。许多编译器有几种调用规范。如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder也支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。

    将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。例如:

    // 被调用函数是以int为参数,以int为返回值
    __stdcall int callee(int);

    // 调用函数以函数指针为参数
    void caller( __cdecl int(*ptr)(int));

    // 在p中企图存储被调用函数地址的非法操作
    __cdecl int(*p)(int) = callee; // 出错


    指针p和callee()的类型不兼容,因为它们有不同的调用规范。因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列。


    本文出处:http://www.vckbase.com/document/viewdoc/?id=195
  • 1、李登辉、连战、陈水扁同坐直升机巡视。 李登辉说:“如果我丢一千块下去,捡到那
      一个人一定很高兴。“连战说:“如果我丢两张五百元下去,那就有两个人很高兴了。陈
      水扁说:“如果我丢十张一百元下去,就有十个人很高兴了。” 这个时候 ........?驾驶
      员喃喃自语地说:“何不把自己都丢下去,让两千一百万人都高兴呢?


      2、总统阿扁希望提高自己的声望,想要发行一款有自己肖像的邮票..... 发行过了一个多
      月之后,阿扁想要问看看视察看看销路如何..... 阿扁:“销售情形怎么样?” 邮政总局
      局长:“还算不错,只不过常常有人抱怨黏不牢!” 阿扁:“怎么会呢?” 阿扁随手拿
      了一张邮票,涂了一点口水在邮票背面,便试贴在信封上.... 阿扁:“这样不是黏得很紧
      吗?” 邮政总局局长:“可是......大家....都把口水吐在正面啊......”


      3、深夜,阿扁总统要去帮阿珍买夜宵。 结果在