??xml version="1.0" encoding="utf-8" standalone="yes"?>
文g的打开Qfopen函数Q?br />
fopen函数用来打开一个文Ӟ其调用的一般Ş式ؓQ文件指针名=fopenQ文件名Q用文件方式)Q?其中Q?文g指针?必须是被说明为FILE cd的指针变量;"文g?是被打开文g的文件名Q?使用文g方式"是指文g的类型和操作要求?"文g?是字W串帔R或字W串数组?br />
相关函数 :open,fclose
表头文g :#include<stdio.h>
定义函数 :FILE * fopenQconst char * path,const char * modeQ;
函数说明
参数path字符串包含欲打开的文件\径及文g名,参数mode字符串则代表着Ş态?br />
mode有下列几UŞ态字W串Q?br />
r 打开只读文gQ该文g必须存在?br />
r+ 打开可读写的文gQ该文g必须存在?br />
w 打开只写文gQ若文g存在则文仉度清?,卌文g内容会消失。若文g不存在则建立该文件?br />
w+ 打开可读写文Ӟ若文件存在则文g长度清ؓӞ卌文g内容会消失。若文g不存在则建立该文件?br />
a 以附加的方式打开只写文g。若文g不存在,则会建立该文Ӟ如果文g存在Q写入的数据会被加到文g,x件原先的内容会被保留?br />
a+ 以附加方式打开可读写的文g。若文g不存在,则会建立该文Ӟ如果文g存在Q写入的数据会被加到文gQ即文g原先的内容会被保留?br />
上述的Ş态字W串都可以再加一个b字符Q如rb、w+b或ab+{组合,加入b 字符用来告诉函数库打开的文件ؓ二进制文Ӟ而非U文字文件。不q在POSIXpȝQ包含Linux都会忽略该字W。由fopenQ)所建立的新文g会具有S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTHQ?666Q权限,此文件权限也会参考umask 倹{?br />
q回?br />
文g利打开后,指向该流的文件指针就会被q回。若果文件打开p|则返回NULL,q把错误代码存在errno 中?br />
附加说明
一般而言Q开文g后会作一些文件读取或写入的动作,若开文gp|Q接下来的读写动作也无法利q行Q所以在fopenQ)后请作错误判断及处理?br />
范例
#include<stdio.h>
mainQ)
{
FILE * fp;
fp=fopenQ?noexist","a+"Q;
ifQfp= =NULLQ?return;
fcloseQfpQ;
}
]]>
一、程序结构的优化
1、表辑ּ
对于一个表辑ּ中各U运执行的优先序不太明确或容易淆的地方Q应当采用圆括号明确指定它们的优先顺序。一个表辑ּ通常不能写得太复杂,如果表达式太复杂Q时间久了以后,自己也不Ҏ看得懂,不利于以后的l护?br />
2、程序的书写l构
虽然书写格式q不会媄响生成的代码质量Q但是在实际~写E序时还是应该尊循一定的书写规则Q一个书写清晰、明了的E序Q有利于以后的维护。在书写E序Ӟ特别是对于While、for、do…while、if…elst、switch…case{语句或q些语句嵌套l合Ӟ应采?~格"的书写Ş式,
3、减判断语?br />
能够使用条g~译QifdefQ的地方׃用条件编译而不使用if语句Q有利于减少~译生成的代码的长度Q能够不用判断语句则用判断用语句?br />
4、标识符
E序中用的用户标识W除要遵循标识符的命名规则以外,一般不要用代数W号Q如a、b、x1、y1Q作为变量名Q应选取h相关含义的英文单词(或羃写)或汉语拼音作为标识符Q以增加E序的可L,如:count、number1、red、work{?br />
5、定义常?br />
在程序化设计q程中,对于l常使用的一些常敎ͼ如果它直接写到E序中去Q一旦常数的数值发生变化,必逐个扑ևE序中所有的常数Qƈ逐一q行修改Q这样必然会降低E序的可l护性。因此,应尽量当采用预处理命令方式来定义常数Q而且q可以避免输入错误?br />
二、代码的优化
1、用自加、自减指?br />
通常使用自加、自减指令和复合赋D辑ּQ如a-=1及a+=1{)都能够生成高质量的程序代码,~译器通常都能够生成inc和dec之类的指令,而用a=a+1或a=a-1之类的指令,有很多C~译器都会生成二C个字节的指o。在AVR单片适用的ICCAVR、GCCAVR、IAR{C~译器以上几U书写方式生成的代码是一LQ也能够生成高质量的inc和dec之类的的代码?br />
2、查?br />
在程序中一般不q行非常复杂的运,如QҎ的乘除及开方等Q以及一些复杂的数学模型的插补运,对这些即消耗时间又消费资源的运,应尽量用查表的方式Qƈ且将数据表置于程序存储区。如果直接生成所需的表比较困难Q也量在启动时先计,然后在数据存储器中生成所需的表Q后以在E序q行直接查表可以了Q减了E序执行q程中重复计的工作量?br />
3、用尽量小的数据类?br />
能够使用字符型(charQ定义的变量Q就不要使用整型QintQ变量来定义Q能够用整型变量定义的变量׃要用长整型(long intQ,能不使用点型(floatQ变量就不要使用点型变量。当Ӟ在定义变量后不要过变量的作用范_如果过变量的范围赋|C~译器ƈ不报错,但程序运行结果却错了Q而且q样的错误很隑֏现。在ICCAVR中,可以在Options中设定用printf参数Q尽量用基本型参数Q?c?d?x?X?u?s格式说明W)Q少用长整型参数Q?ld?lu?lx?lX格式说明W)Q至于Q点型的参敎ͼ%fQ则量不要使用Q其它C~译器也一栗在其它条g不变的情况下Q?f参数Q会使生成的代码的数量增加很多,执行速度降低?br />
4、选择合适的法和数据结?br />
应该熟悉法语言Q知道各U算法的优缺点,具体资料请参见相应的参考资料,有很多计机书籍上都有介l。将比较慢的序查找法用较快的二分查找或乱序查找法代替,插入排序或冒泡排序法用快速排序、合q排序或Ҏ序代替,都可以大大提高程序执行的效率选择一U合适的数据l构也很重要Q比如你在一堆随机存攄C使用了大量的插入和删除指令,那用链表要快得多。数l与指针语句h十分密码的关p,一般来_指针比较灉|z,而数l则比较直观Q容易理解。对于大部分的编译器Q用指针比使用数组生成的代码更短,执行效率更高。但是在Keil中则相反Q用数l比使用的指针生成的代码更短?img src ="http://www.shnenglu.com/idc/aggbug/176283.html" width = "1" height = "1" />
]]>
void swap1Qint x,int yQ?/p>
{
int temp;
temp=x;
x=y;
y=temp;
}
void swap2Qint *x,int *yQ?/p>
{
int *temp;
temp=x;
x=y;
y=temp;
}
void swap3Qint *x,int *yQ?/p>
{
int temp;
temp=*x;
*x=*y;
*y=temp;
}
void swap4Qint a[],int b[]Q?/p>
{
int temp;
temp=a[0];
a[0]=b[0];
b[0]=temp;
}
void swap5Qint a[],int b[]Q?/p>
{
int temp;
temp=*a;
*a=*b;
*b=temp;
}
int mainQ)
{
int x,y;
x=4;
y=3;
swap1Qx,yQ;
printfQ?swap1: x:%d,y:%d\n",x,yQ;//形参传|不能交换Q实际传q去是拷贝的一份,没改变主函数中x,y
swap2Q?amp;x,&yQ;
printfQ?swap2: x:%d,y:%d\n",x,yQ;//不能交换Q函C只是地址交换了下Q地址指向的内Ҏ有交?/p>
swap3Q?amp;x,&yQ;
printfQ?swap3: x:%d,y:%d\n",x,yQ;//能交换,地址指向的内容进行了交换
swap4Q?amp;x,&yQ;
printfQ?swap4: x:%d,y:%d\n",x,yQ;//能交换,地址指向的内容进行交?/p>
swap5Q?amp;x,&yQ;
printfQ?swap5: x:%d,y:%d\n",x,yQ;//能交换,地址指向的内容进行交?/p>
return 0;
}
swap1: x:4,y:3
swap2: x:4,y:3
swap3: x:3,y:4
swap4: x:4,y:3
swap5: x:3,y:4
在?Makefile 之前Q只需对它q行一些简单的讄卛_Q而且一l设|,即以后ҎE序文g有所增减一般也不再需要改?Makefile.因此Q即便是一个没有学习过 Makefile 书写规则的hQ也可以q C/C++ E序快速徏立一个可工作?Makefile.
q个 Makefile 可以?GNU Make ?GCC ~译器下正常工作。但是不能保证对于其它版本的 Make 和编译器也能正常工作?/p>
如果你发C本文中的错误Q或者对本文有什么感xQ可通过 whyglinux AT hotmail DOT com 邮箱和作者联pR?/p>
?Makefile 的用方法如下:[list=1][*]E序目录的组l尽量将自己的源E序集中在一个目录中Qƈ且把 Makefile 和源E序攑֜一Pq样用v来比较方ѝ当Ӟ也可以将源程序分cd攑֜不同的目录中?/p>
在程序目录中创徏一个名?Makefile 的文本文Ӟ后面列出的 Makefile 的内容复制到q个文g中。(注意Q在复制的过E中QMakfile 中各命o前面?Tab 字符有可能被转换成若q个I格。这U情况下需要把 Makefile 命o前面的这些空格替换ؓ一?Tab.Q?/p>
当前工作目录切换到 Makefile 所在的目录。目前,q个 Makefile 只支持在当前目录中的调用Q不支持当前目录?Makefile 所在的路径不是同一目录的情c?/p>
[*]指定可执行文件程序编译和q接成功后生的可执行文件在 Makefile 中的 PROGRAM 变量中设定。这一不能ؓI。ؓ自己E序的可执行文g起一个有意义的名子吧?/p>
[*]指定源程序要~译的源E序由其所在的路径和文件的扩展名两Ҏ定。由于头文g是通过包含来用的Q所以在q里说的源程序不应包含头文g?/p>
E序所在的路径?SRCDIRS 中设定。如果源E序分布在不同的目录中,那么需要在 SRCDIRS 中一一指定Qƈ且\径名之间用空格分隔?/p>
?SRCEXTS 中指定程序中使用的文件类型。C/C++ E序的扩展名一般有比较固定的几UŞ式:。c、。C、。cc、。cpp、。CPP、。c++、。cp、或者。cxxQ参?man gccQ。扩展名军_了程序是 C q是 C++ E序Q。c ?C E序Q其它扩展名表示 C++ E序。一般固定用其中的一U扩展名卛_。但是也有可能需要用多U扩展名Q这可以?SOURCE_EXT 中一一指定Q各个扩展名之间用空格分隔?/p>
虽然q不常用Q但?C E序也可以被作ؓ C++ E序~译。这可以通过?Makefile 中设|?CC = $QCXXQ??CFLAGS = $QCXXFLAGSQ?两项卛_实现?/p>
q个 Makefile 支持 C、C++ 以及 C/C++ 混合三种~译方式Q[list][*]如果只指?.c 扩展名,那么q是一?C E序Q用 $QCCQ?表示的编译命令进行编译和q接?/p>
[*]如果指定的是?.c 之外的其它扩展名Q如 .cc、。cpp、。cxx {)Q那么这是一?C++ E序Q用 $QCXXQ?q行~译和连接?/p>
[*]如果既指定了 .cQ又指定了其?C++ 扩展名,那么q是 C/C++ 混合E序Q将?$QCCQ?~译其中?C E序Q用 $QCXXQ?~译其中?C++ E序Q最后再?$QCXXQ?q接E序?/p>
[/list]q些工作都是 make Ҏ?Makefile 中提供的E序文gcdQ扩展名Q自动判断进行的Q不需要用户干预?/p>
[*]指定~译选项~译选项׃部分l成Q预处理选项、编译选项以及q接选项Q分别由 CPPFLAGS、CFLAGS与CXXFLAGS、LDFLAGS 指定?/p>
CPPFLAGS 选项可参?C 预处理命?cpp 的说明,但是注意不能包含 -M 以及?-M 有关的选项。如果是 C/C++ 混合~程Q也可以在这里设|?C/C++ 的一些共同的~译选项?/p>
CFLAGS ?CXXFLAGS 两个变量通常用来指定~译选项。前者仅仅用于指?C E序的编译选项Q后者仅仅用于指?C++ E序的编译选项。其实也可以在两个变量中指定一些预处理选项Q即一些本来应该放?CPPFLAGS 中的选项Q,?CPPFLAGS q没有明的界限?/p>
q接选项?LDFLAGS 中指定。如果只使用 C/C++ 标准库,一般没有必要设|。如果用了非标准库Q应该在q里指定q接需要的选项Q如库所在的路径、库名以及其它联接选项?/p>
现在的库一般都提供了一个相应的 .pc 文g来记录用库所需要的预编译选项、编译选项和连接选项{信息,通过 pkg-config 可以动态提取这些选项。与qh式指定各个选项相比Q?pkg-config 来访问库提供的选项更方ѝ更具通用性。在后面可以看到一?GTK+ E序的例子,其编译和q接选项的指定就是用 pkg-config 实现的?/p>
[*]~译和连接上面的各项讄好之后保?Makefile 文g。执?make 命oQ程序就开始编译了?/p>
命o make 会根?Makefile 中设|好的\径和文gcd搜烦源程序文Ӟ然后Ҏ文g的类型调用相应的~译命o、用相应的~译选项对程序进行编译?/p>
~译成功之后E序的连接会自动q行。如果没有错误的话最l会产生E序的可执行文g?/p>
注意Q在对程序编译之后,会生和源程序文件一一对应?.d 文g。这是表CZ赖关pȝ文gQ通过它们 make 军_在源E序文g变动之后要进行哪些更新。ؓ每一个源E序文g建立相应?.d 文gq也?GNU Make 推荐的方式?/p>
[*]Makefile 目标QTargetsQ?/p>
下面是关于这?Makefile 提供的目标以及它所完成的功能:[list][*]make~译和连接程序。相当于 make all. [*]make objs仅仅~译E序产生 .o 目标文gQ不q行q接Q一般很单独用)?/p>
[*]make clean删除~译产生的目标文件和依赖文g?/p>
[*]make cleanall删除目标文g、依赖文件以及可执行文g?/p>
[*]make rebuild重新~译和连接程序。相当于 make clean && make all. [/list][/list]关于q个 Makefile 的实现原理不准备详细解释了。如果有兴趣的话Q可参考文末列出的“参考资?#8221;?/p>
Makefile 的内容如下:############################################################################### # # Generic Makefile for C/C++ Program # # AuthorQ?whyglinux Qwhyglinux AT hotmail DOT comQ?# DateQ?nbsp; 2006/03/04 # DescriptionQ?# The makefile searches in <SRCDIRS> directories for the source files # with extensions specified in <SOURCE_EXT>Q?then compiles the sources # and finally produces the <PROGRAM>Q?the executable fileQ?by linking # the objectives. # UsageQ?# $ make compile and link the program. # $ make objs compile only Qno linking. Rarely usedQ?# $ make clean clean the objectives and dependencies. # $ make cleanall clean the objectivesQ?dependencies and executable. # $ make rebuild rebuild the program. The same as make clean && make all. #============================================================================== ## Customizing SectionQ?adjust the following if necessary. ##============================================================================= # The executable file name. # It must be specified. # PROGRAM Q? a.out # the executable name PROGRAM Q? # The directories in which source files reside. # At least one path should be specified. # SRCDIRS Q? . # current directory SRCDIRS Q? # The source file types Qheaders excludedQ?# At least one type should be specified. # The valid suffixes are among of .cQ?.CQ?.ccQ?.cppQ?.CPPQ?.c++Q?.cpQ?or .cxx. # SRCEXTS Q? .c # C program # SRCEXTS Q? .cpp # C++ program # SRCEXTS Q? .c .cpp # C/C++ program SRCEXTS Q? # The flags used by the cpp Qman cpp for moreQ?# CPPFLAGS Q? -Wall -Werror # show all warnings and take them as errors CPPFLAGS Q? # The compiling flags used only for C. # If it is a C++ programQ?no need to set these flags. # If it is a C and C++ merging programQ?set these flags for the C parts. CFLAGS Q? CFLAGS += # The compiling flags used only for C++. # If it is a C programQ?no need to set these flags. # If it is a C and C++ merging programQ?set these flags for the C++ parts. CXXFLAGS Q? CXXFLAGS += # The library and the link options Q?C and C++ commonQ?LDFLAGS Q? LDFLAGS += ## Implict SectionQ?change the following only when necessary. ##============================================================================= # The C program compiler. Uncomment it to specify yours explicitly. #CC = gcc # The C++ program compiler. Uncomment it to specify yours explicitly. #CXX = g++ # Uncomment the 2 lines to compile C programs as C++ ones. #CC = $QCXXQ?#CFLAGS = $QCXXFLAGSQ?# The command used to delete file. #RM = rm -f ## Stable SectionQ?usually no need to be changed. But you can add more. ##============================================================================= SHELL = /bin/sh SOURCES = $Qforeach dQ?QSRCDIRSQ,$Qwildcard $Qaddprefix $QdQ?*Q?QSRCEXTSQ)Q) OBJS = $Qforeach xQ?QSRCEXTSQ, \ $Qpatsubst %$QxQ,%.oQ?Qfilter %$QxQ,$QSOURCESQ)Q) DEPS = $Qpatsubst %.oQ?.dQ?QOBJSQ) .PHONY Q?all objs clean cleanall rebuild all Q?$QPROGRAMQ?# Rules for creating the dependency files Q。dQ?#—— %.d Q?%.c @$QCCQ?-MM -MD $QCFLAGSQ?$< %.d Q?%.C @$QCCQ?-MM -MD $QCXXFLAGSQ?$< %.d Q?%.cc @$QCCQ?-MM -MD $QCXXFLAGSQ?$< %.d Q?%.cpp @$QCCQ?-MM -MD $QCXXFLAGSQ?$< %.d Q?%.CPP @$QCCQ?-MM -MD $QCXXFLAGSQ?$< %.d Q?%.c++ @$QCCQ?-MM -MD $QCXXFLAGSQ?$< %.d Q?%.cp @$QCCQ?-MM -MD $QCXXFLAGSQ?$< %.d Q?%.cxx @$QCCQ?-MM -MD $QCXXFLAGSQ?$< # Rules for producing the objects. #—— objs Q?$QOBJSQ?%.o Q?%.c $QCCQ?-c $QCPPFLAGSQ?$QCFLAGSQ?$< %.o Q?%.C $QCXXQ?-c $QCPPFLAGSQ?$QCXXFLAGSQ?$< %.o Q?%.cc $QCXXQ?-c $QCPPFLAGSQ?$QCXXFLAGSQ?$< %.o Q?%.cpp $QCXXQ?-c $QCPPFLAGSQ?$QCXXFLAGSQ?$< %.o Q?%.CPP $QCXXQ?-c $QCPPFLAGSQ?$QCXXFLAGSQ?$< %.o Q?%.c++ $QCXX -c $QCPPFLAGSQ?$QCXXFLAGSQ?$< %.o Q?%.cp $QCXXQ?-c $QCPPFLAGSQ?$QCXXFLAGSQ?$< %.o Q?%.cxx $QCXXQ?-c $QCPPFLAGSQ?$QCXXFLAGSQ?$< # Rules for producing the executable. #—— $QPROGRAMQ?Q?$QOBJSQ?ifeq Q?Qstrip $QSRCEXTSQ)Q?.cQ?nbsp; # C file $QCCQ?-o $QPROGRAMQ?$QOBJSQ?$QLDFLAGSQ?else # C++ file $QCXXQ?-o $QPROGRAMQ?$QOBJSQ?$QLDFLAGSQ?endif -include $QDEPSQ?rebuildQ?clean all clean Q?@$QRMQ?*.o *.d cleanallQ?clean @$QRMQ?$QPROGRAMQ?$QPROGRAMQ。exe ### End of the Makefile ## Suggestions are welcome ## All rights reserved ### ###############################################################################
下面提供两个例子来具体说明上?Makefile 的用法?/p>
[color=darkred]例一 Hello World E序[/color]
q个E序的功能是输出 HelloQ?worldQ?q样一行文字。由 hello.h、hello.c、main.cxx 三个文gl成。前两个文g?C E序Q后一个是 C++ E序Q因此这是一?C ?C++ LE序?/p>
/* File nameQ?hello.h * C header file */ #ifndef HELLO_H #define HELLO_H #ifdef __cplusplus extern "C" { #endif void print_helloQ)Q?#ifdef __cplusplus } #endif #endif
/* File nameQ?hello.c * C source file. */ #include "hello.h" #include <stdio.h> void print_helloQ) { putsQ?"HelloQ?worldQ? Q; }
/* File nameQ?main.cxx * C++ source file. */ #include "hello.h" int mainQ) { print_helloQ)Q?nbsp; return 0Q?}
建立一个新的目录,然后把这三个文g拯到目录中Q也?Makefile 文g拯到目录中。之后,?Makefile 的相关项目进行如下设|:PROGRAM Q? hello # 讄q行E序?SRCDIRS Q? . # 源程序位于当前目录下 SRCEXTS Q? .c .cxx # 源程序文件有 .c ?.cxx 两种cd CFLAGS Q? -g # ?C 目标E序包含 GDB 可用的调试信?CXXFLAGS Q? -g # ?C++ 目标E序包含 GDB 可用的调试信?/p>
׃q个单的E序只用了 C 标准库的函数QputsQ,所以对?CFLAGS ?CXXFLAGS 没有q多的要求,LDFLAGS ?CPPFLAGS 选项也无需讄?/p>
l过上面的设|之后,执行 make 命o可以编译程序了。如果没有错误出现的话,?hello 可以运行程序了?/p>
如果修改了源E序的话Q可以看到只有和修改有关的源文g被编译。也可以再ؓE序d新的源文Ӟ只要它们的扩展名是已l在 Makefile 中设|过的,那么没有必要修攏VMakefile.
[color=darkred]例二 GTK+ ?Hello World E序[/color]
q个 GTK+ 2.0 版的 Hello World E序可以从下面的|址上得刎ͼhttp://www.gtk.org/tutorial/c58.html#SEC-HELLOWORLD.当然Q要~译 GTK+ E序Q还需要你的系l上已经安装好了 GTK+.
跟第一个例子一P单独创徏一个新的目录,把上面网中提供的程序保存ؓ main.c 文g。对 Makefile 做如下设|:PROGRAM Q? hello # 讄q行E序?SRCDIRS Q? . # 源程序位于当前目录下 SRCEXTS Q? .c # 源程序文件只?.c 一U类?CFLAGS Q? `pkg-config ——cflags gtk+-2.0` # CFLAGS LDFLAGS Q? `pkg-config ——libs gtk+-2.0` # LDFLAGS
q是一?C E序Q所?CXXFLAGS 没有必要讄——即被设|了也不会被使用?/p>
~译和连?GTK+ 库所需要的 CFLAGS ?LDFLAGS ?pkg-config E序自动产生?/p>
现在可以运?make 命o~译、?hello 执行q个 GTK+ E序了?/p>
class <zcd>:<l承方式1><基类?>,<l承方式2><基类?>,…
{
<zcȝ?gt;
};
其中Q?lt;l承方式1>,<l承方式2>,…是三U承方式:public、private、protected之一。例如:
class A
{
…
};
class B
{
…
};
class C : public A, public B
{
…
};
其中Q派生类Ch两个基类Q类A和类BQ,因此Q类C是多l承的。按照承的规定Q派生类C的成员包含了基类A, B中成员以及该cLw的成员?/p>
多承的构造函?/p>
在多l承的情况下Q派生类的构造函数格式如下:
<zcd>Q?lt;d数表>Q:<基类?>Q?lt;参数?>Q,<基类?>Q?lt;参数?>Q,…
<子对象名>Q?lt;参数表n+1>Q,…
{
<zcL造函C>
}
其中Q?lt;d数表>中各个参数包含了其后的各个分参数表?/p>
多承下zcȝ构造函C单承下zcL造函数相|它必d时负责该zcL有基cL造函数的调用。同Ӟzcȝ参数个数必须包含完成所有基cd始化所需的参C数?/p>
zcL造函数执行顺序是先执行所属基cȝ构造函敎ͼ再执行派生类本n构造函敎ͼ处于同一层次的各基类构造函数的执行序取决于定义派生类时所指定的各基类序Q与zcL造函C所定义的成员初始化列表的各w序无兟뀂也是_执行基类构造函数的序取决于定义派生类时基cȝ序。可见,zcL造函数的成员初始化列表中各项序可以L地排列?/p>
下面通过一个例子来说明zcL造函数的构成及其执行序?/p>
#include <iostream.h>
class B1
{
public:
B1Qint iQ?/p>
{
b1 = i;
cout?构造函?B1."《i?endl;
}
void printQ)
{
cout?B1.printQ)"《b1《endl;
}
private:
int b1;
};
class B2
{
public:
B2Qint iQ?/p>
{
b2 = i;
cout?构造函?B2."《i?endl;
}
void printQ)
{
cout?B2.printQ)"《b2《endl;
}
private:
int b2;
};
class B3
{
public:
B3Qint iQ?/p>
{
b3 = i;
cout?构造函?B3."《i《endl;
}
int getb3Q)
{
return b3;
}
private:
int b3;
};
class A : public B2, public B1
{
public:
AQint i, int j, int k, int lQ:B1QiQ, B2QjQ, bbQkQ?/p>
{
a = l;
cout?构造函?A."《a《endl;
}
void printQ)
{
B1::printQ)Q?/p>
B2::printQ)Q?/p>
cout?A.printQ)"《a?,"《bb.getb3Q)《endl;
}
private:
int a;
B3 bb;
};
void mainQ)
{
A aaQ?, 2, 3, 4Q;
aa.printQ)Q?/p>
}
构造函?B2.2
构造函?B1.1
构造函?B3.3
构造函?A.4
B1.printQ)?
B2.printQ)2
A.printQ)4, 3
在该E序中,作用域运符Q:用于解决作用域冲H的问题。在zcA中的printQ)函数的定义中Q用了B1::print;和B2::printQ)Q语句分别指明调用哪一个类中的printQ)函数Q这U用法应该学会?/p>
二义性问?/p>
一般说来,在派生类中对基类成员的访问应该是唯一的,但是Q由于多l承情况下,可能造成对基cM某成员的讉K出现了不唯一的情况,则称为对基类成员讉K的二义性问题?/p>
实际上,在上例已l出现过q一问题Q回忆一下上例中Q派生类A的两基类B1和B2中都有一个成员函数printQ)。如果在zcM讉K printQ)函数Q到底是哪一个基cȝ呢?于是出现了二义性。但是在上例中解决了q个问题Q其办法是通过作用域运符Q:q行了限定。如果不加以限定Q则会出C义性问题?/p>
下面再D一个简单的例子Q对二义性问题进行深入讨论。例如:
class A
{
public:
void fQ)Q?/p>
};
class B
{
public:
void fQ)Q?/p>
void gQ)Q?/p>
};
class C : public A, public B
{
public:
void gQ)Q?/p>
void hQ)Q?/p>
};
如果定义一个类C的对象c1:
C c1;
则对函数fQ)的访?/p>
c1.fQ)Q?/p>
便具有二义性:是访问类A中的fQ)Q还是访问类B中的fQ)呢?
解决的方法可用前面用q的成员名限定法来消除二义性,例如Q?/p>
c1.A::fQ)Q?/p>
或?/p>
c1.B::fQ)Q?/p>
但是Q最好的解决办法是在cC中定义一个同名成员fQ)Q类C中的fQ)再根据需要来军_调用A::fQ)Q还是B::fQ)Q还是两者皆有,q样Qc1.fQ)调用C::fQ)?/p>
同样圎ͼcC中成员函数调用fQ)也会出现二义性问题。例如:
viod C::hQ)
{
fQ)Q?/p>
}
q里有二义性问题,该函数应修改为:
void C::hQ)
{
A::fQ)Q?/p>
}
或?/p>
void C::hQ)
{
B::fQ)Q?/p>
}
或?/p>
void C::fQ)
{
A::fQ)Q?/p>
B::fQ)Q?/p>
}
另外Q在前例中,cB中有一个成员函数gQ)Q类C中也有一个成员函数gQ)。这Ӟ
c1.gQ)Q?/p>
不存在二义性,它是指C::gQ)Q而不是指B::gQ)。因两个gQ)函数Q一个出现在基类BQ一个出现在zcCQ规定派生类的成员将支配基类中的同名成员。因此,上例中类C中的gQ)支配cB中的gQ)Q不存在二义性,可选择支配者的那个名字?/p>
当一个派生类从多个基cL生类Q而这些基cd有一个共同的基类Q则对该基类中说明的成员q行讉KӞ也可能会出现二义性。例如:
class A
{
public:
int a;
};
class B1 : public A
{
private:
int b1;
};
class B2 : public A
{
private:
int b2;
};
class C : public B1, public B2
{
public:
int fQ)Q?/p>
private:
int c;
};
已知QC c1;
下面的两个访问都有二义性:
c1.a;
c1.A::a;
而下面的两个讉K是正的Q?/p>
c1.B1::a;
c1.B2::a;
cC的成员函数fQ)用如下定义可以消除二义性:
int C::fQ)
{
retrun B1::a + B2::a;
}
׃二义性的原因Q一个类不可以从同一个类中直接承一ơ以上,例如Q?/p>
class A : public B, public B
{
…
}
q是错误的?/p>
位段l构也是一U结构体cdQ只不过其中含有以位为单位定义存储长度的整数cd位段成员。采用位D늻构既节省存储I间Q又可方便操作?/p>
位段l构中位D늚定义格式为:
unsigned <成员?gt;:<二进制位?gt;
例如Q?/p>
struct bytedata
{unsigned a:2; /*位段aQ占2?/
unsigned:6; /*无名位段Q占6位,但不能访?/
unsigned:0; /*无名位段Q占0位,表下一位段从下一字边界开?/
unsigned b:10; /*位段bQ占10?/
int i; /*成员iQ从下一字边界开?/
}data;
位段数据的引用:
同结构体成员中的数据引用一P但应注意位段的最大取D围不要超Zq制位数定的范围Q否则超出部分会丢弃?/p>
例如Qdata.a=2; ?nbsp; data.a=10;p_a?位,最?Q?/p>
关于位段数据Q注意以下几点:
Q?Q一个位D必d储在同一存储单元Q即字)之中Q不能跨两个单元。如果其单元I间不够Q则剩余I间不用Q从下一个单元v存放该位Dc?/p>
Q?Q可以通过定义长度?的位D늚方式使下一位段从下一存储单元开始?/p>
Q?Q可以定义无名位Dc?/p>
Q?Q位D늚长度不能大于存储单元的长度?/p>
Q?Q位D|地址Q不能对位段q行取地址q算?/p>
Q?Q位D可以以%dQ?oQ?x格式输出?/p>
Q?Q位D若出现在表辑ּ中,被pȝ自动转换成整数?/p>
-------------------------------------------------------
C语言中用l构实现位段Q-个h心血Q值得一看哦QC语言中的l构是有实现位段的能力的Q噢Q你问它到底是什么Ş式是吧?q个问题呆会l你{案。让我们先看看位D늚作用Q位D|在字D늚声明后面加一个冒号以及一个表C字D位长的整数来实现的。这U用法又被就叫作“深入逻辑元g的编E?#8221;Q如果你对系l编E感兴趣Q那么这文章你׃应该错过Q?/p>
我把使用位段的几个理由告诉大Ӟ1、它能把长度为奇数的数据包装在一P从而节省存储的I间Q?、它可以很方便地讉K一个整型值的部分内容?/p>
首先我要提醒大家注意几点Q?、位D|员只有三U类型:int ,unsigned int 和signed intq三U(当然了,int型位D|不是可以取负C是我说了的Q因是和你的~译器来军_的。位D,位段Q它是用来表C字D位长(bitQ的Q它只有整型|不会?.2q种floatcd的,如果你说有,那你q于承认了?.2个hq个概念Q当然也没有charq个cd的)Q?、成员名后面的一个冒号和一个整敎ͼq个整数指定该位D늚位长QbitQ;3、许多编译器把位D|员的字长限制在一个int的长度范围之内;4、位D|员在内存的实现是从左到右q是从右到左是由~译器来军_的,但二者皆寏V?/p>
下面我们来看看Q它到底是什么东西(我先假定大家的机器字长ؓ32位)Q?/p>
Struct WORD
{
unsigned int chara: 6:
unsigned int font : 7;
unsigned int maxsize : 19;
};
Struct WORD chone;
q一D|从我~写的一个文字格式化软g摘下来的Q它最多可以容U?4Q既我说的unsigned int chara :6; 它d?位)个不同的字符|可以处理128Q既unsigned int font : 7 ;??ơ方Q种不同的字体,??9ơ方的单位长度的字。大安可以看到maxsize?9位,它是无法被一个short int cd的值所容纳的,我们又可以看到其余的成员的长度比charq小Q这p我们惌v让他们共?2位机器字长,q就避免用一?2位的整数来表Cmaxsize的位Dc怎么Pq要注意的是刚才的那一D代码在16位字长的机器上是无法实现的,Z么?提醒你一下,看看上面提醒的第3点,你会明白的!
你是不是发现q个东西没有用啊Q如果你点头了,那你错了!q么伟大的创造怎么会没有用呢(你对pȝ~程不感兴趣Q相信你会改变这么一个观点的Q?盘控制器大家应该知道吧QY׃它的通信我们来看看是怎么实现的下面是一个磁盘控制器的寄存器Q?/p>
│←5→│←5→│←9→│←8→│←1→│←1→∣←1→∣←1→∣←1→∣
上面位段从左到右依次代表的含义ؓQ?位的命oQ?位的扇区Q?位的道Q?位的错误代码Q?位的HEAD LOADED,1位的写保护,1位的DISK SPINNINGQ?位的错误判断W,q有1位的READY位。它要怎么来实现呢Q你先自己写写看Q?/p>
struct DISK_FORMAT
{
unsigned int command : 5;
unsigned sector : 5;
unsigned track : 9 ;
unsigned err_code : 8;
unsigned ishead_loaded : 1;
unsigned iswrit_protect : 1;
unsigned isdisk_spinning : 1;
unsigned iserr_ocur : 1;
undigned isready :1 ;
};
注:代码中除了第一行用了unsigned int 来声明位D后qMint Q这是可行的Q详见ANCI C标准?/p>
如果我们要对044c18bfH的地址q行讉K的话Q那pP
#define DISK Q(struct DISK_FORMAT *Q?x044c18bfQ?/p>
DISK->sector=fst_sector;
DISK->track=fst_track;
DISK->command=WRITE;
当然那些都是要宏定义的哦Q?/p>
我们用位D|实现q一目的是很方便的,其实q也可以用移位或屏蔽来实玎ͼ你尝试过q道哪个更方便了!
1.回调函数
以下案例解析xml文g中的elmentQattribute和text。expat使用回调方式q回xml数据Q解析器解析C个element及其内部属性后Q将调用事先讄好的函数Q同P当elementl束和textl束后,也会分别调用对应的函数?/span>
2.如何处理数据之间的包含关p?/span>
典型的方式是定义三个函数分别处理elment开始(含属性)、elementl束和文本内宏V回调函数的W一个参数是自定义的Q通常用于存储 XML文档的上下文信息Q用XML_SetUserData可以讄q个参数Q下例中传递一个整数指针,以便在每ơ回调时能知道该元素是第几层元素?/span>
该参C可以是一个栈对象的地址Q开始一个元素时Q将新元素对应的数据压入堆栈Q处理下一U元素时Q新元素是栈元素在子元素,然后处理完了l箋把该元素压入堆栈Ql下一U新的子元素。当元素l束后,需要出栈,以便解析下个兄弟元素E时能取到父节点?/span>
好啦Q基本应用还是很单的Q实际上Expat的API函数不多?/span>
3.如何处理属?/span>
属性通过ElementHandler回调函数传入Q这里有一个char** atts是属性,q是一个字W指针数l,如果有N个属性,数组大小是2*N+1Q最后一个素l元素ؓI指针,奇数指针对应属性名Uͼ偶数指针对应属性|字符串格式)。可以在一个@环中处理多个属性,当遇到空指针Ӟ表示没有更多属性了?/span>
好啦Q先看sample吧:
#include <stdio.h>
#include "expat.h"
#pragma warningQdisable:4996Q?/span>
#define XML_FMT_INT_MOD "l"
static void XMLCALL startElementQvoid *userData, const char *name, const char **attsQ?/span>
{
int i;
int *depthPtr = Qint *QuserData;
for Qi = 0; i < *depthPtr; i++Q?/span>
printfQ? "Q;
printfQnameQ;
*depthPtr += 1;
forQi=0;atts[i]!=0;i+=2Q?/span>
{
printfQ? %s=%s",atts[i],atts[i+1]Q;
}
printfQ?\n"Q;
}
static void XMLCALL endElementQvoid *userData, const char *nameQ?/span>
{
int *depthPtr = Qint *QuserData;
*depthPtr -= 1;
}
int mainQint argc, char *argv[]Q?/span>
{
char buf[BUFSIZ]; XML_Parser parser = XML_ParserCreateQNULLQ;
int done; int depth = 0;
XML_SetUserDataQparser, &depthQ;
XML_SetElementHandlerQparser, startElement, endElementQ;
FILE* pFile= argc<2 ?stdin : fopenQargv[1],"rb"Q;
do
{ int len = QintQfreadQbuf, 1, sizeofQbufQ, pFileQ;
done = len < sizeofQbufQ;
if QXML_ParseQparser, buf, len, doneQ == XML_STATUS_ERRORQ?/span>
{
fprintfQstderr,"%s at line %" XML_FMT_INT_MOD "u\n",
XML_ErrorStringQXML_GetErrorCodeQparserQ)Q?/span>
XML_GetCurrentLineNumberQparserQ)Q?/span>
return 1;
}
}
while Q!doneQ;
XML_ParserFreeQparserQ;
fcloseQpFileQ;
return 0;
}
4.其他ElementHanlder
expatq可以设|CDataQComment的handlerQ另外一些函数本没用过Q涉及到更多的xml标准的知识,如果需要,可以参考官方的手册?/span>
JSON 的全UCؓQJavaScript Object NotationQ顾名思义QJSON 是用于标?Javascript 对象的,JSON 官方的解释ؓQJSON 是一U轻量的数据传输格式?/p>
本文q不详细介绍 JSON 本n的细节,旨在讨论如何使用 C++ 语言来处?JSON。关?JSON 更具体的信息Q可参见 JSON 官网Qhttp://www.json.org?/p>
二、本文选择处理 JSON?/strong> C++ ?/strong>
本文选择一个第三方?jsoncpp 来解?JSON。jsoncpp 是比较出名的 C++ JSON 解析库。在 JSON 官网也是首推的?/p>
下蝲地址为:http://sourceforge.net/projects/jsoncpp。本文用的 jsoncpp 版本为:0.5.0?/p>
三?/strong>jsoncpp ?/strong> Windows 下的~译
要用第三方源码库,W一步少不了的就是编译,源码文件编译成我们方便使用的动态链接库、静态链接库或者静态导入库[1]?/p>
jsconcpp q行 JSON 解析的源码文件分布在 include/json、src/lib_json 下。其?jsoncpp 源码q不多,Z方便产品理Q此处没必要其~译为动态链接库或者静态导入库Q所以我们选择使用静态链接库[2]?/p>
jsoncpp 已经处理的很完善了,所有编译选项都已l配|好Q打开makefiles/vs71/jsoncpp.sln 便可以开始编译(默认是?VS2003 ~译器的Q打开时直接按?VS2005 提示转换卛_Q?/p>
四?/strong>jsoncpp 使用详解
jsoncpp 主要包含三种cd?classQValue、Reader、Writer。jsoncpp 中所有对象、类名都?namespace Json 中,包含 json.h 卛_?/p>
Json::Value 只能处理 ANSI cd的字W串Q如?C++ E序是用 Unicode ~码的,最好加一?Adapt cL适配?/p>
1?/strong>Value
Json::Value 是jsoncpp 中最基本、最重要的类Q用于表C各U类型的对象Qjsoncpp 支持的对象类型可?Json::ValueType 枚D倹{?/p>
可如下是?Json::Value c:
Json::Value json_temp; // 临时对象Q供如下代码使用
json_temp["name"] = Json::Value("huchao");
json_temp["age"] = Json::Value(26);
Json::Value root; // 表示整个 json 对象
root["key_string"] = Json::Value("value_string"); // 新徏一?KeyQ名为:key_stringQ,赋予字符串|"value_string"?/p>
root["key_number"] = Json::Value(12345); // 新徏一?KeyQ名为:key_numberQ,赋予数|12345?/p>
root["key_boolean"] = Json::Value(false); // 新徏一?KeyQ名为:key_booleanQ,赋予bool|false?/p>
root["key_double"] = Json::Value(12.345); // 新徏一?KeyQ名为:key_doubleQ,赋予 double |12.345?/p>
root["key_object"] = Json_temp; // 新徏一?KeyQ名为:key_objectQ,赋予 json::Value 对象倹{?/p>
root["key_array"].append("array_string"); // 新徏一?KeyQ名为:key_arrayQ,cd为数l,对第一个元素赋gؓ字符Ԍ"array_string"?/p>
root["key_array"].append(1234); // 为数l?key_array 赋|对第二个元素赋gؓQ?234?/p>
Json::ValueType type = root.type(); // 获得 root 的类型,此处?objectValue cd?/p>
注:跟C++ 不同QJavaScript 数组可以ZQ意类型的|所?jsoncpp 也可以?/p>
如上几个用法已经可以满l大部分 json 应用了,当然 jsoncpp q有一些其他同能,比如说设|注释、比?json 大小、交?json 对象{,都很Ҏ使用Q大家自己尝试吧?/p>
2?/strong>Writer
如上说了 Json::Value 的用方式,现在C该查看刚才赋值内容的时候了Q查?json 内容Q?Writer cd可?/p>
Jsoncpp ?Json::Writer cL一个纯虚类Qƈ不能直接使用。在此我们?Json::Writer 的子c:Json::FastWriter、Json::StyledWriter、Json::StyledStreamWriter?/p>
思义Q用 Json::FastWriter 来处?json 应该是最快的Q下面我们来试试?/p>
Json::FastWriter fast_writer;
std::cout << fast_writer.write(root) << std::endl;
输出l果为:
{"key_array":["array_string",1234],"key_boolean":false,"key_double":12.3450,"key_number":12345,"key_object":{"age":26,"name":"huchao"},"key_string":"value_string"}
再次思义Q用 Json::StyledWriter 是格式化后的 jsonQ下面我们来看看 Json::StyledWriter 是怎样格式化的?/p>
Json::StyledWriter styled_writer;
std::cout << styled_writer.write(root) << std::endl;
输出l果为:
{
"key_array" : [ "array_string", 1234 ],
"key_boolean" : false,
"key_double" : 12.3450,
"key_number" : 12345,
"key_object" : {
"age" : 26,
"name" : "huchao"
},
"key_string" : "value_string"
}
3?/strong>Reader
Json::Reader 是用于读取的Q说的确切点Q是用于字W串转换?Json::Value 对象的,下面我们来看个简单的例子?/p>
Json::Reader reader;
Json::Value json_object;
const char* json_document = "{\"age\" : 26,\"name\" : \"huchao\"}";
if (!reader.parse(json_document, json_object))
return 0;
std::cout << json_object["name"] << std::endl;
std::cout << json_object["age"] << std::endl;
输出l果为:
"huchao"
26
可见Q上qC码已l解析出?json 字符丌Ӏ?/p>
现在我们讨论C/C++里我认ؓ哪一本书都没有完全说清楚Q也是涉及概늻节最多,语言中最隄技术之一的动态内存的传递。ƈ且在软g开发中很多专业人员q不能写出相关的合格的代码?/p>
一、引?/p>
看下面的例子Q这是我们在~写库函数或者项目内的共同函数经常希望的?/p>
void MyFuncQchar *pReturnQ?size_t sizeQ?/p>
{………
pReturn = Qchar *QmallocQsizeofQcharQ?* numQ;………
}我们可以很明昑֜看出代码作者的意图Q他惛_函数调用处声明一个指针 char *pMyReturn=NULLQ然后调用MyFunc处理q返回一D长度ؓsize的一D动态内存?/p>
那么作者能辑ֈ预期的效果吗Q?/p>
那么我可以告诉作者,他的E序在编译期很幸q地通过了,可是在运行期他的E序崩溃l止。原因何在,是他触犯了系l不可R犯的条款Q错误地操作内存?/p>
二、内存操作及问题相关知识?/p>
Z能彻底解军_态内存传递的问题Q我们先回顾一下内存管理的知识要点?/p>
Q?Q内存分配方式有三种Q?/p>
从静态存储区域分配。内存在E序~译的时候就已经分配好,q块内存在程序的整个q行期间都存在。例如全局变量Qstatic变量?/p>
在栈上创建。在执行函数Ӟ函数内局部变量的存储单元都可以在栈上创徏Q函数执行结束时q些存储单元自动被释放。栈内存分配q算内置于处理器的指令集中,效率很高Q但是分配的内存定w有限?/p>
从堆上分配,亦称动态内存分配。程序在q行的时候用malloc或new甌L多少的内存,E序员自p责在何时用free或delete释放内存。动态内存的生存期由我们军_Q用非常灵zR?/p>
Q?Q指针的操作程
甌q初始化或设|ؓI:
int *pInt=NULLQ开辟空间或者其指向对象:
pInt=new IntQ?Q;或者int i=3Qpint=&iQ用指针Q更切地说是操作内存,在用之前加ifQpintQ?NULLQ或者assertQpIntQ?NULLQ后再用,以防内存甌p|的情况下使用指针Q:
ifQpQ?NULLQ?{use pint}Q释放用完的内?/p>
freeQpIntQ;|指针ؓI?/p>
pInt=NULLQ(避免野指针的出现Q?/p>
Q?Q在函数的参C递中Q编译器L要ؓ函数的每个参数制作时副本,如果参数为p的话Q那么编译器会生p的副本_pQ_p=pQ?如果函数体内的程序修改了_p的内容,导致参数p的内容作相应的修攏V这是指针可以用作输出参数的原因?/p>
三、问题分?/p>
Ҏ上面的规则我们可以很Ҏ分析例子中失败的原因?/p>
void MyFuncQchar *pReturnQ?size_t sizeQ?/p>
{………
pReturn = Qchar *QmallocQsizeofQcharQ?* numQ;………
} void mainQvoidQ{ char *pMyReturn=NULLQMyFuncQpMyReturnQ?0Q;}在MyFuncQchar *pReturnQ?size_t sizeQ中_pMyReturn真实地申请到了内存, pMyReturn甌了新的内存,只是把_pMyReturn 所指的内存地址改变了,但是pMyReturn丝毫未变。所以函数MyFuncq不能输ZQ何东ѝ事实上Q每执行一ơMyFunc׃泄露一块内存,因ؓ没有用free释放内存?/p>
四、问题解x?/p>
函数间传递动态数据我们可以有三种解决Ҏ?/p>
Ҏ一Q如果我们是用C++~程Q我们可以很方便地利用引用这个技术。我也极力推荐你用引用,因ؓ它会使你犯一些错误。以下是一个例子?/p>
void MyFuncQchar* &pReturnQsize_t sizeQ{ pReturn=Qchar*QmallocQsizeQ;memsetQpReturnQ?x00QsizeQ;ifQsize>=13Q?/p>
strcpyQpReturnQ?Hello WorldQ?Q;}
void mainQ){ char *pMyReturn=NULLQMyFuncQpMyReturnQ?5Q;ifQpMyReturnQ?NULLQ?/p>
{ char *pTemp=pMyReturnQwhileQ?pTempQ?''\0''Q?/p>
cout<<*pTemp++QpTemp=NULLQstrcpyQpMyReturnQ?AAAAAAAA"Q;freeQpMyReturnQ;pMyReturn=NULLQ}Ҏ二:利用二指针
void MyFunc Qchar ** pReturnQ?size_t sizeQ?/p>
{ * pReturn = Qchar *QmallocQsizeQ;} void mainQvoidQ?/p>
{ char * pMyReturn = NULLQMyFunc Q?amp;pMyReturnQ?100Q;// 注意参数?& pMyReturn ifQpMyReturnQ?NULLQ{ strcpyQpMyReturnQ?"hello"Q;cout<< pMyReturn << endlQfreeQpMyReturnQ;pMyReturn=NULLQ}}Z么二U指针就可以了。原因通过函数传递规则可以很Ҏ地分析出来。我们将& pMyReturn传递了q去Q就是将双重指针的内容传递到了函C。函数过E利用改变指针的内容Q这样pMyReturn很明显指向了开辟的内存 .
Ҏ三:用函数返回值来传递动态内?/p>
char * MyFunc QvoidQ?/p>
{ char *p =new char[20]QmemsetQpQ?x00QsizeofQpQ)Qreturn pQ} void mainQvoidQ?/p>
{ char *str = NULLQstr = MyFuncQ)QifQstrQ?NULLQ?/p>
{ strcpyQstrQ?HelloQbaby"Q;cout<< str << endlQfreeQstrQ;str=NULLQ}h意的是函数写成这L话,你是不能q回什么动态内存的Q因为p指向的是字符串常量。内存在位于静态存储区上分配,你无法改变。(你想要得到动态内存我们一定要看到malloc或者newQ?/p>
char * MyFunc QvoidQ?/p>
{ char *p =“Hello World”
return pQ}l束?/p>
操作内存是C/C++一个难点,我们作ؓ专业的Y件开发h员。应该深入理解ƈ能灵zd掌握指针和内存的操作?/p>