配色: 字号:
C语言提高笔记(干货)
2022-03-22 | 阅:  转:  |  分享 
  
传智播客C提高讲义1程序内存模型1.1就业班引言1.1.1问题引出企业需要能干活的人C学到什么程度可以找工作?对于C/C++初级开发
者,怎么达到企业的用人标准就业问题问:老师,有没有一个框框?有没有一个标准啊?我们学什么哪?C工程开发需要什么(培养什么能力)
成熟的、商业化的信息系统在分区、分层信息系统的技术模型在分层找出对我们初学者最近的那一层(哪些能力是你入行前,必须要掌握的)
C项目开发的套路(一套接口)//socket_clientpoolapi设计与实现intsckClient_poolin
it(voidhandle);intsckClient_getConnet(voidhandle,voidhC
onnect);intsckClient_sendData(voidhConnect,unsignedchardat
a,intdataLen);intsckClient_getData(voidhConnect,unsignedch
ardata,intdataLen);intsckClient_getData_Free(voidhConnec
t,unsignedchardata);intsckClient_putConnet(voidhandle,voi
dhConnect);intsckClient_pooldestory(voidhandle);总结:寻找到学习的标
准培养两种能力接口的封装和设计(功能抽象和封装)接口api的使用能力接口api的查找能力(快速上手)接口api的实现能力建立正
确程序运行内存布局图(印象图)内存四区模型图函数调用模型图1.1.2总体课程安排课程大纲C提高C++数据结构总体时间1个月实
用专题总:轻松入门实战应用形式1:专题的形式录制话题集中便于初学者学习形式2:知识点分段录制、细致讲解,从根本上提高初学者水
平项目开发中的重要点做剖析指针铁律12345678910===》企业用人标准1.1.3学员要求资料,时间空间管
理工作经验,记录和积累临界点事物认知规律挑战p,p,p提高课堂效率课堂例子,当堂运行。录制视频说明(不来,看视频
)C/C++学习特点Java:学习、应用、做项目C:学习、理解、应用、做项目多动手不动手,永远学不会关键点、关键时候,进行强化训练
和考试1.1.4小结建立信心接口的封装和设计指针教学,多年实践检验心态放轻松了分析有效时间尊重事物认知规律、给自己一次机
会1.2学员听课的标准C语言学到什么程度,就可以听懂传智播客就业班第一阶段的课程了。有没有一个标准?选择法或者冒泡法排序在一个函数
内排序通过函数调用的方式排序数组做函数参数的技术盲点和推演1.3内存四区专题讲座1.3.1数据类型本质分析数据类型概念“类型”是
对数据的抽象类型相同的数据有相同的表示形式、存储格式以及相关的操作程序中使用的所有数据都必定属于某一种数据类型数据类型的
本质思考思考数据类型和内存有关系吗?C/C++为什么会引入数据类型?数据类型的本质数据类型可理解为创建变量的模具(模子);是
固定内存大小的别名。数据类型的作用:编译器预算对象(变量)分配的内存空间大小程序举例,如何求数据类型的大小sizeof(int
)请问:数据类型可以有别名吗?数据类型可以自定义吗?数据类型大小intmain(){inta=10;intb[10]
;printf("inta:%d\n",sizeof(a));printf("inta:%d\n",sizeof(i
nt));printf("intb:%d\n",sizeof(b));printf("intb:%d\n",siz
eof(b[0]));printf("intb:%d\n",sizeof(b));printf("hello.....\n
");getchar();return0;}sizeof是操作符,不是函数;sizeof测量的实体大小为编译期间就已确定数据类
型别名数据类型可以理解为固定大小内存块的别名,请问数据类型可以起别名吗?intmain(){//Teachert1;print
f("Teacher:%d\n",sizeof(Teacher));printf("u32:%d\n",sizeof(u3
2));printf("u8:%d\n",sizeof(u8));printf("hello.....\n");getchar
();return0;}数据类型的封装1、void的字面意思是“无类型”,void则为“无类型指针”,void可以指向任
何类型的数据。2、用法1:数据类型的封装intInitHardEnv(voidhandle);典型的如内存操作函数me
mcpy和memset的函数原型分别为voidmemcpy(voiddest,constvoidsrc,si
ze_tlen);voidmemset(voidbuffer,intc,size_tnum);3
、用法2:void修饰函数返回值和参数,仅表示无。如果函数没有返回值,那么应该将其声明为void型如果函数没有参数,应该声
明其参数为voidintfunction(void){return1;}4、void指针的意义C语言规定只有相同类型的
指针才可以相互赋值void指针作为左值用于“接收”任意类型的指针void指针作为右值赋值给其它指针时需要强制类型转换in
tp1=NULL;charp2=(char)malloc(sizoeof(char)20);5、不存在v
oid类型的变量C语言没有定义void究竟是多大内存的别名6、扩展阅读《void类型详解.doc》数据类型总结与扩展1、数据类型
本质是固定内存大小的别名;是个模具,c语言规定:通过数据类型定义变量。2、数据类型大小计算(sizeof)3、可以给已存在的数据类
型起别名typedef4、数据类型封装概念(void万能类型)思考1:C一维数组、二维数组有数据类型吗?intarray[10
]。若有,数组类型又如何表达?又如定义?若没有,也请说明原因。抛砖:数组类型,压死初学者的三座大山1、数组类型2、数组指针3、数
组类型和数组指针的关系思考2:C语言中,函数是可以看做一种数据类型吗?a)若是,请说明原因并进一步思考:函数这种数据类型,能再重定
义吗?b)若不是,也请说明原因。抛砖:1.3.2变量本质分析变量概念概念:既能读又能写的内存对象,称为变量;若一旦初始化后不能修改
的对象则称为常量。变量定义形式:类型标识符,标识符,…,标识符;例如:intx;intwordC
ut,Radius,Height;doubleFlightTime,Mileage,Spee
d;变量本质1、程序通过变量来申请和命名内存空间inta=02、通过变量名访问内存空间(一段连续)内存空间的别名(
是一个门牌号)修改变量有几种方法?1、直接2、间接。内存有地址编号,拿到地址编号也可以修改内存;于是横空出世了!(编程案例)3、
内存空间可以再取给别名吗?4、数据类型和变量的关系通过数据类型定义变量5、总结及思考题1对内存,可读可写;2通过变量往内存读写数
据;3不是向变量读写数据,而是向变量所代表的内存空间中写数据。问:变量跑哪去了?思考1:变量三要素(名称、大小、作用域),变量的
生命周期?思考2:C++编译器是如何管理函数1,函数2变量之间的关系的?====》引出两个重要话题:内存四区模型函数调用模型重要
实验:intmain333(){////2种方法,通过变量直接操作内存//通过内存编号操作内存inti=0;printf
("&i:%d\n",&i);((int)(1245024))=10;printf("i:%d",i);printf
("hello....\n");getchar();return0;}1.3.3程序的内存四区模型内存四区的建立流程流程说明1、
操作系统把物理硬盘代码load到内存2、操作系统把c代码分成四个区3、操作系统找到main函数入口执行各区元素分析1.4函数调用模
型1.4.1基本原理1.4.2内存四区模型和函数调用模型变量传递分析1、一个主程序有n函数组成,c++编译器会建立有几个堆区?有几
个栈区?2、函数嵌套调用时,实参地址传给形参后,C++编译器如何管理变量的生命周期?分析:函数A,调用函数B,通过参数传递的变量(
内存空间能用吗?)1.4.3提示学好C语言的关键1.4.4如何建立正确的程序运行内存布局图内存四区模型&函数调用模型函数内元素
深入理解数据类型和变量“内存”属性一级指针内存布局图(int,char)二级指针内存布局图(intchar)函
数间主调函数分配内存,还是被调用函数分配内存主调函数如何使用被调用函数分配的内存(技术关键点:指针做函数参数)======》学习指
针的技术路线图1.5内存四区强化训练01全局区训练charp1=“abcdefg”;02堆栈区生命周期训练Charp1[
]=“abcdefg”;返回基本类型返回非基本类型03堆栈属性训练测试heap生长方向测试stack生长方向Heap、stack
生长方向和内存存放方向是两个不同概念野指针Malloc得到指针释放问题测试free(p)free(p+1),深入理解1.6作业强
化训练1划出内存四区voidmain26(){charbuf[100];//byteb1=newbyte[100];
inta=10;//分配4个字节的内存栈区也叫临时区intp;//分配4个字节的内存p=&a;//cpu执行的
代码,放在代码区p=20;//{charp=NULL;//分配4个字节的内存栈区也叫临时区p=(char
)malloc(100);//内存泄露概念if(p!=NULL){free(p);}}system("pause");}
全局区代码测试chargetstring1(){charp1="abcde";returnp1;}charg
etstring2(){charp2="abcde";returnp2;}voidmain(){inti=0;//
指针指向谁就把谁的地址赋给指针变量。charp1=getstring1();charp2=getstring2()
;charp3=NULL;//p3是个变量//指针变量和它所执行的内存空间变量是两个不同的概念st
rcmp(p1,p2);system("pause");}训练2划出内存四区voidmain01(){charbuf[10
0];//byteb1=newbyte[100];inta=10;//分配4个字节的内存栈区也叫临时区int
p;//分配4个字节的内存p=&a;//cpu执行的代码,放在代码区p=20;//{charp2=NULL;
//分配4个字节的内存栈区也叫临时区p2=(char)malloc(100);//内存泄露概念if(p2!=N
ULL){free(p2);//p2=NULL;若不写,实验效果,分析原因}if(p2!=NULL){free(p2)
;}}system("pause");}2指针知识体系搭建2.1前言先从整体上把握指针的知识体系。然后突破1级指针、二级指针、多级
指针。2.2指针强化铁律1:指针是一种数据类型指针也是一种变量,占有内存空间,用来保存内存地址测试指针变量占有内存空间大小2)
p操作内存在指针声明时,号表示所声明的变量为指针在指针使用时,号表示操作指针所指向的内存空间中的值p相当于通过地址(p
变量的值)找到一块内存;然后操作内存p放在等号的左边赋值(给内存赋值)p放在等号的右边取值(从内存获取值)3)指针变量和它指向
的内存块是两个不同的概念//含义1给p赋值p=0x1111;只会改变指针变量值,不会改变所指的内容;p=p+1;//p
++//含义2给p赋值p=''a'';不会改变指针变量的值,只会改变所指的内存块的值//含义3=左边p表示给内存赋
值,=右边p表示取值含义不同切结!//含义4=左边charp//含义5保证所指的内存块能修改4)指针是一种数据类
型,是指它指向的内存空间的数据类型含义1:指针步长(p++),根据所致内存空间的数据类型来确定p++=(unsignedch
ar)p+sizeof(a);结论:指针的步长,根据所指内存空间类型来定。注意:建立指针指向谁,就把把谁的地址赋值给指针。图和代
码和二为一。不断的给指针变量赋值,就是不断的改变指针变量(和所指向内存空间没有任何关系)。铁律2:间接赋值(p)是指针存在的最大
意义1)两码事:指针变量和它指向的内存块变量2)条件反射:指针指向某个变量,就是把某个变量地址否给指针3)p间接赋值成立条件:
3个条件a)2个变量(通常一个实参,一个形参)b)建立关系,实参取地址赋给形参指针c)p形参去间接修改实参的值Int
iNum=0;//实参intp=NULL;p=&iNum;iNum=1;p=2;//通过形参==
间接地改变实参的值p成立的三个条件:4)引申:函数调用时,用n指针(形参)改变n-1指针(实参)的值。//改变0级指针(in
tiNum=1)的值有2种方式//改变1级指针(egcharp=0x1111)的值,有2种方式//改变2级指针的
(egcharpp1=0x1111)的值,有2种方式//函数调用时,形参传给实参,用实参取地址,传给形参,在被调用函
数里面用p,来改变实参,把运算结果传出来。//指针作为函数参数的精髓。铁律3:理解指针必须和内存四区概念相结合主调函数被调函
数主调函数可把堆区、栈区、全局数据内存地址传给被调用函数被调用函数只能返回堆区、全局数据内存分配方式指针做函数参数,是有输入和输
出特性的。铁律4:应用指针必须和函数调用相结合(指针做函数参数)编号指针函数参数内存分配方式(级别+堆栈)主调函数实参被调函数形
参备注011级指针(做输入)堆分配使用一般应用禁用栈分配使用常用Intshowbuf(charp);intshowArra
y(intarray,intiNum)021级指针(做输出)栈使用结果传出常用intgeLen(charpFileN
ame,intpfileLen);032级指针(做输入)堆分配使用一般应用禁用栈分配使用常用intmain(intarc
,chararg[]);指针数组intshouMatrix(int[3][4],intiLine);二维字符串数组
042级指针(做输出)堆使用分配常用,但不建议用,转化成02intgetData(chardata,intdataL
en);IntgetData_Free(voiddata);IntgetData_Free(voiddata);/
/避免野指针053级指针(做输出)堆使用分配不常用intgetFileAllLine(charcontent,int
pLine);intgetFileAllLine_Free(charcontent,intpLine);指针
做函数参数,问题的实质不是指针,而是看内存块,内存块是1维、2维。如果基础类int变量,不需要用指针;若内存块是1维、2维。铁律5
:一级指针典型用法(指针做函数参数)一级指针做输入intshowbuf(charp)intshowArray(inta
rray,intiNum)一级指针做输出intgeLen(charpFileName,intpfileLen);理
解主调函数还是被调用函数分配内存被调用函数是在heap/stack上分配内存铁律6:二级指针典型用法(指针做函数参数)二级指针做输
入intmain(intarc,chararg[]);字符串数组intshouMatrix(int[3][4],
intiLine);二级指针做输出intDemo64_GetTeacher(TeacherppTeacher);int
Demo65_GetTeacher_Free(TeacherppTeacher);intgetData(charda
ta,intdataLen);IntgetData_Free(voiddata);IntgetData_Free2(
voiddata);//避免野指针理解主调函数还是被调用函数分配内存被调用函数是在heap/stack上分配内存铁律7:
三级指针输出典型用法三级指针做输出intgetFileAllLine(charcontent,intpLine);
intgetFileAllLine_Free(charcontent,intpLine);理解主调函数还是被调用
函数分配内存被调用函数是在heap/stack上分配内存铁律8:杂项,指针用法几点扩充1)野指针2种free形式intgetD
ata(chardata,intdataLen);intgetData_Free(voiddata);int
getData_Free2(voiddata);2)2次调用主调函数第一次调用被调用函数求长度;根据长度,分配内存,调用被调
用函数。3)返回值char/int/char4)C程序书写结构商业软件,每一个出错的地方都要有日志,日志级别铁律9:一般
应用禁用malloc/new2.3接口封装设计思想引导基于socketclient客户端接口设计与实现(仿真模拟)2.4附录【王
保明老师经典语录】1)指针也是一种数据类型,指针的数据类型是指它所指向内存空间的数据类型2)间接赋值p是指针存在的最大意义?3)
理解指针必须和内存四区概念相结合?4)应用指针必须和函数调用相结合(指针做函数参数)指针是子弹,函数是枪管;子弹只有沿着枪管发射才
能显示它的威力;指针的学习重点不言而喻了吧。接口的封装和设计、模块的划分、解决实际应用问题;它是你的工具。5)指针指向谁就把谁的地
址赋给指针?6)指针指向谁就把谁的地址赋给指针,用它对付链表轻松加愉快7)链表入门的关键是分清楚链表操作和辅助指针变量之间的逻辑关
系8)C/C++语言有它自己的学习特点;若java语言的学习特点是学习、应用、上项目;那么C/C++语言的学习特点是:学习、理解、
应用、上项目。多了一个步骤吧。9)学好指针才学会了C语言的半壁江山,另外半壁江山在哪里呢?你猜,精彩剖析在课堂。10)理解指针关
键在内存,没有内存哪来的内存首地址,没有内存首地址,哪来的指针啊。3字符串和一级指针内存模型专题3.1字符串基本操作字符数组初始化
方法intmain11(){//1大{}号法初始化列表//数组初始化有2种方法默认元素个数、指定元素个数charbuf1
[]={''a'',''b'',''c'',''d'',''e''};//若没有指定长度,默认不分配零//若指定长度,不够报错;buf长度
多于初始化个数,会自动补充零charbuf2[6]={''a'',''b'',''c'',''d'',''e''};charbuf3[
6]={''a'',''b'',''c'',''d'',''e''};//charbuf4[5]={''a'',''b'',''c'',''
d'',''e''};printf("buf3:%s",buf3);system("pause");}在C语言中使用字符数组来模拟字
符串C语言中的字符串是以’\0’结束的字符数组C语言中的字符串可以分配于栈空间,堆空间或者只读存储区//在C语言中使用字符数组来模
拟字符串//C语言中的字符串是以’\0’结束的字符数组//C语言中的字符串可以分配于栈空间,堆空间或者只读存储区intmain1
2(){//1用字符串来初始化数组charbuf2[]={''a'',''b'',''c'',''d'',''\0''};//2字符串常量
初始化一个字符数组charbuf3[]={"abcde"};//结论:会补充零charbuf4[]="abcde";c
harbuf5[100]="abcde";printf("strlen(buf5):%d\n",strlen(bu
f5));printf("sizeof(buf4):%d\n",sizeof(buf5));printf("sizeof
(buf4):%d\n",sizeof(buf4));}//strlen()求字符串的长度,注意字符串的长度不包含\0//s
izeof(类型)字符串类型,的大小,包括\0;02Sizeof与strlen的区别数组法和指针法操作字符串03字符串操作数组
法,下标法字符数组名,是个指针,是个常量指针;字符数组名,代表字符数组首元素的地址,不代表整个数组的。如果代表这个数组,那需要数组
数据类型的知识!下期分解//字符串操作方法数组下标法指针法intmain13(){inti=0;charbuf5[1
00]="abcde";charp=NULL;//下标法for(i=0;i<100;i++){printf("%
c",buf5[i]);}printf("\n");//指针法1for(i=0;i<100;i++){printf("%c
",(buf5+i));}//buf5是个指针,是个常量指针//指针法2printf("\n");p=buf5;for(
i=0;i<100;i++){printf("%c",(p+i));}//buf5是个指针,是个常量指针}推演过程为:i变
0+I,去[]号加号//其实本质:指针p间接寻址,操作内存;//[]编译器为我们做了p操作而已3.2字符串做函数参数深入
理解指针。。。。。。。。。。。的关键是什么?注意指针和数组的巨大区别charp=“abcdefg”;Charbuf=
“abcdefg”;一维字符串内存模型:两种voidcopy_str01(charfrom,charto){for
(;from!=''\0'';from++,to++){to=from;}to=''\0'';}voidcopy_
str02(charfrom,charto){while(from!=''\0''){to++=from++;}
to=''\0'';}voidcopy_str03(charfrom,charto){while((to=fro
m)!=''\0''){to++;from++;}}voidcopy_str04(charfrom,charto){w
hile((to++=from++)!=''\0''){;}}intcopy_str05_good(constchar
from,charto){if(from==NULL||to==NULL){printf("funccopy_str
05_good()err.(from==NULL||to==NULL)\n");return-1;}while((t
o++=from++)!=''\0''){;}return0;}典型错误知多少charstr_cnct(charx,c
hary)/简化算法/{charstr3[80];charz=str3;/指针z指向数组str3/
while(z++=x++);z--;/去掉串尾结束标志/while(z++=y++);z=str3;/将str
3地址赋给指针变量z/return(z);}修改字符常量结果会如何Charp=“abcdefg”;Modifyp[1]
=‘1’;04字符串操作易错//你往哪里输入数据intmain(){charbuf[2000];charp=NUL
L;p=buf;printf("\n请输入一个字符串:");scanf("%s",p);printf("%s",p);g
etchar();getchar();return0;}3.3库函数api快速的上手api是一种能力!建立正确的程序运行示意图,
(内存四区及函数调用堆栈图)是根本保障!!intmain31(){charbuf1[100];charbuf2[200];s
trcpy(buf1,"111");printf("%s",strcat(buf1,"222"));getchar();re
turn0;}intmain32(){charstring1="1234567890";charstring2=
"747DC8";intlength;//在字符str1中查找,与str2中任意字符有公共交集的位置length=strc
spn(string1,string2);printf("Characterwherestringsintersecti
satposition%d\n",length);getchar();return0;}//strnset函数有错误//
测试程序修改如下intmain33(){charstring[]="abcdefghijklmnopqrstuvwxyz"
;charletter=''x'';printf("stringbeforestrnset:%s\n",string);
strnset(string,letter,13);printf("stringafterstrnset:%s\n",
string);getchar();return0;}intmain44(){charstring1="abcdef
ghijklmnopqrstuvwxyz";charstring2="onm";charptr;ptr=strpb
rk(string1,string2);if(ptr)printf("strpbrkfoundfirstcharacte
r:%c\n",ptr);elseprintf("strpbrkdidn''tfindcharacterinset\
n");getchar();return0;}intmain55(){charinput[16]="abc,d";c
harp;/strtokplacesaNULLterminatorinfrontofthetoken,
iffound/p=strtok(input,",");if(p)printf("%s\n",p);
/AsecondcalltostrtokusingaNULLasthefirstparameterre
turnsapointertothecharacterfollowingthetoken/p=strt
ok(NULL,",");if(p)printf("%s\n",p);getchar();return0;}//典
型的状态函数intmain(){charstr[]="now#isthetimeforall#goodm
entocometothe#aidoftheircountry";//chardelims[]="#";c
hardelims="#";charresult=NULL;result=strtok(str,delim
s);while(result!=NULL){printf("resultis\"%s\"\n",result
);result=strtok(NULL,delims);}printf("----------==========-
---------\n");printf("%s",str);getchar();return0;}3.4字符串相关一级指针内
存模型voidmain(){charbuf[20]="aaaa";charbuf2[]="bbbb";charp1
="111111";charp2=malloc(100);strcpy(p2,"3333");system("pa
use");return;}3.5项目开发字符串模型strstr-whiledowhile模型两头堵模型字符串反转模型3.6
一级指针(char)易错模型分析01char(字符串)做函数参数出错模型分析建立一个思想:是主调函数分配内存,还是被调用函
数分配内存;//不要相信,主调函数给你传的内存空间,你可以写。。。。。。一级指针你懂了。但是二级指针,你就不一定懂。。。抛出。。。
。。。。。。voidcopy_str21(charfrom,charto){if(NULL=''\0''||
to!=’\0’){Printf(“funccopy_str21()err\n”);return;}for(;f
rom!=''\0'';from++,to++){to=from;}to=''\0'';}//字符串逆序intmain
(){//charp[1024]={0};charp={0};p=NULL;charto[100];copy_
str21(p,to);C语言中没有你不知道的,只有你不会调Java语言中没有你不会调的,只有你不知道不断修改内存指针变量0
2越界越界语法级别的越界charbuf[3]="abc";03不断修改指针变量的值越界voidcopy_str_err(
charfrom,charto){for(;from!=''\0'';from++,to++){to=fr
om;}to=''\0'';printf("to:%s",to);printf("from:%s",from);}04你向外
面传递什么1、临时str3内存空间//charstr_cnct(x,y)/简化算法///charx,y
;charstr_cnct(charx,chary)/简化算法/{charstr3[80];char
z=str3;/指针z指向数组str3/while(z++=x++);z--;/去掉串尾结束标志/while
(z++=y++);z=str3;/将str3地址赋给指针变量z/return(z);}2、经验要学习while(z+
+=x++);z--;/去掉串尾结束标志/charstr_cnct(charx,chary)/简
化算法/{charstr3=(char)malloc(80)charz=str3;/指针z指向数组str3
/while(z++=x++);z--;/去掉串尾结束标志/while(z++=y++);z=str3;/将s
tr3地址赋给指针变量z/return(z);}charstr_cnct(charx,chary)/简
化算法/{If(x==NULL){ReturnNULL;}charstr3=(char)malloc(80)
charz=str3;/指针z指向数组str3/while(z++=x++);z--;/去掉串尾结束标志/w
hile(z++=y++);z=str3;/将str3地址赋给指针变量z/note:return(z);}Main(
){Charp=str_cnct(“abcd”,“ddeee”);If(p!=NULL){Free(p);p
=NULL}//yezhizhen}intgetKeyByValude(charkeyvaluebuf,chark
eybuf,charvaluebuf,intvaluebuflen){intresult=0;charg
etbuf=newchar[100];memset(getbuf,0,sizeof(getbuf));chartri
mbuf=newchar[100];memset(trimbuf,0,sizeof(trimbuf));intdest
len=strlen(keyvaluebuf);if(keybuf==NULL||keyvaluebuf==NU
LL||valuebuf==NULL/||valuebuflen==NULL/){result=-1;re
turnresult;}if(strstr(keyvaluebuf,keybuf)==NULL){result=-
1;returnresult;}else{for(inti=0;ialuebuf==''=''){keyvaluebuf++;break;}keyvaluebuf++;}while(keyva
luebuf!=''\0''){valuebuf=keyvaluebuf;valuebuf++;keyvaluebuf++
;}valuebuf=''\0'';}intlen=strlen(valuebuf);returnresult;}//c
harp="abcd11111abcd2222abcdqqqqq";//字符串中"abcd"出现的次数。//要求你自己
写一个函数接口,并且写出测试用例。//完成功能为:求出“abcd”字串出现的次数//输入:intgetSubCount(char
str,charsubstr,intmycount){intret=0;charp=str;c
harsub=substr;intcount=0;if(str==NULL||substr==NULL||
mycount==NULL){ret=-1;returnret;}//charp="abcd11111abcd2
222abcdqqqqqabcd";//charp2=NULL;//p2=p;do{p=strstr(p,su
b);if(p!=NULL){count++;//++后缀操作符优先级高,所以先执行p操作然后地址++mycount++
;p=p+strlen(sub);}else{break;}}while(p!=''\0'');//printf("
count:%d\n",count);//mycount是实参的地址(实参的地址)mycount=count;ret
urnret;}05看图06重复的错误何时休#include"stdio.h"#include"stdlib.h"#in
clude"string.h"voidcopy_str21_modify(charfrom,charto){int
i=0;if(from!=''\0''){printf("ddddd");}for(;from!=''\0'';fro
m++,to++){to=from;}to=''\0'';printf("to:%s",to);printf("fr
om:%s",from);}voidcopy_str_err(charfrom,charto){for(;fr
om!=''\0'';from++,to++){to=from;}to=''\0'';printf("to:%s",t
o);printf("from:%s",from);}//字符串逆序intmainaaaa(){charbuf1[100]
="abcdefg";charto[100];copy_str_err(buf1,to);}//越界场景intmain00
000000000(){charfrom[5]="abcde";printf("\n%s",from);getchar()
;return0;}3.7const专题const基础知识(用法、含义、好处、扩展)intmain(){constint
a;//intconstb;constcharc;charconstd;charbuf[100]cons
tcharconste;return0;}Intfunc1(const)初级理解:const是定义常量==》c
onst意味着只读含义://第一个第二个意思一样代表一个常整形数//第三个c是一个指向常整形数的指针(所指向的内存数据不能被修
改,但是本身可以修改)//第四个d常指针(指针变量不能被修改,但是它所指向内存空间可以被修改)//第五个e一个指向常整形的常
指针(指针和它所指向的内存空间,均不能被修改)Const好处//合理的利用const,//1指针做函数参数,可以有效的提高代码可读
性,减少bug;//2清楚的分清参数的输入和输出特性结论://指针变量和它所指向的内存空间变量,是两个不同的概念。。。。。。//看
const是放在的左边还是右边看const是修饰指针变量,还是修饰所指向的内存空变量3.8考试强化训练1、有一个字符串开头
或结尾含有n个空格(”abcdefgdddd”),欲去掉前后空格,返回一个新字符串。要求1:请自己定义一个接口(函数),并实现功
能;70分要求2:编写测试用例。30分inttrimSpace(charinbuf,charoutbuf);2、有
一个字符串”1a2b3d4z”,;要求写一个函数实现如下功能,功能1:把偶数位字符挑选出来,组成一个字符串1。valude;20分
功能2:把奇数位字符挑选出来,组成一个字符串2,valude20功能3:把字符串1和字符串2,通过函数参数,传送给main,并打
印。功能4:主函数能测试通过。intgetStr1Str2(charsouce,charbuf1,charbuf
2);3、键值对(”key=valude”)字符串,在开发中经常使用;要求1:请自己定义一个接口,实现根据key获取valu
de;40分要求2:编写测试用例。30分要求3:键值对中间可能有n多空格,请去除空格30分注意:键值对字符串格式可能如下:“key
1=valude1”“key2=valude2“key3=valude3”“key4=
valude4”“key5=““key6=““key7=“intgetKeyByValude(char
keyvaluebuf,charkeybuf,charvaluebuf,intvaluebuflen);i
ntmain(){getKeyByValude(“key1=valude1”,”key1”,buf,&len);}4
二级指针和多级指针专题4.1二级指针的三种内存模型4.1.1二级指针输入和输出模型4.1.2二级指针三种内存模型思路:先把二级指
针的所用内存模型练一遍,然后我们再探究它的内存模型及本质。工程开发中二级指针的典型用法二级指针的第一种内存模型二级指针的第二种内
存模型二级指针的第三种内存模型4.1.3强化两个辅助指针变量挖字符串思想分享:强化训练到极致眼高手低练习到极致高屋建瓴一看都会
一练习都错眼高手低练习到极致高屋建瓴4.1.4二级指针内存模型建立voidmain2(){inti=0;//指针数组
charp1[]={"123","456","789"};//二维数组charp2[3][4]={"12
3","456","789"};//手工二维内存charp3=(char)malloc(3sizeof(
char));//intarray[3];for(i=0;i<3;i++){p3[i]=(char)mall
oc(10sizeof(char));//charbuf[10]sprintf(p3[i],"%d%d%d",i,i,
i);}}4.2数组类型和多维数组本质4.2.1数组概念概念1)元素类型角度:数组是相同类型的变量的有序集合测试指针变量占有
内存空间大小2)内存角度:联系的一大片内存空间数组初始化//数组元素的个数可以显示或隐式指定//分析数组初始化{0}与memset
比较intmain(){inti=0;inta[10]={1,2};//其他初始化为0intb[]={1,
2};intc[20]={0};?for(i=0;i<10;i++){printf("%d",a[i]);}mem
set(a,0,sizeof(a));getchar();return0;}数组名的技术盲点1)数组首元素的地址和数组地址是
两个不同的概念2)数组名代表数组首元素的地址,它是个常量。解释如下:变量本质是内存空间的别名,一定义数组,就分配内存,内存就固定了
。所以数组名起名以后就不能被修改了。3)数组首元素的地址和数组的地址值相等4、怎么样得到整个一维数组的地址?C语言规定:Inta
[10];printf("得到整个数组的地址a:%d\n",&a);printf("数组的首元素的地址a:%d\n",
a);怎么样表达inta[10]这种数据类型那?4.2.2数组类型、数组指针类型、数组指针类型变量数组类型1数据类型分为基础、
非基础,思考角度应该发生变化2C语言中的数组有自己特定的类型数组的类型由元素类型和数组大小共同决定例:intarray
[5]的类型为int[5]/typedefint(MYINT5)[5];//inttypedeffloat(MYFL
OAT10)[10];数组定义:MYINT5iArray;intarray[5];MYFLOAT10fArray/3定
义数组类型,并用数组类型定义变量intmain(){typedefint(MYINT5)[5];inti=0;MYIN
T5array;for(i=0;i<5;i++){array[i]=i;}?for(i=0;i<5;i++){p
rintf("%d",array[i]);}?getchar();return0;}数组指针类型数组指针用于指向一个数组i
nta[10]数组名是数组首元素的起始地址,但并不是数组的起始地址通过将取地址符&作用于数组名可以得到整个数组的起始地址//定义
数组指针有两种1)通过数组类型定义数组指针:typedefint(ArrayType)[5];intaArrayTyp
epointer;2)声明一个数组指针类型typedefint(MyPointer)[5];MyPointermy
Point;3)直接定义:int(pointer)[n];pointer为数组指针变量名type为指向的数组的类型n为指向
的数组的大小注意这个地方是type类型(比如int(pointer)[10])数组指针:用数组类型加定义一个数组指针//1
{inta[5];//声明一个数组类型typedefint(MYINT5)[5];//用数组类型加,定义一个数组指针变量M
YINT5array;array=&a;for(i=0;i<5;i++){(array)[i]=i;}//fo
r(i=0;i<5;i++){printf("\n%d%d",a[i],(array)[i]);}}数组指针:定义一
个数组指针类型,然后用类型定义变量{intb[5];//声明一个数组指针类型typedefint(MyPointer)[5
];//用数组指针类型,去定义一个变量MyPointermypoint;mypoint=&b;for(i=0;i<5;
i++){(mypoint)[i]=i;}//for(i=0;i<5;i++){printf("\n%d%d",b
[i],(mypoint)[i]);}}//3数组指针:直接定义一个数组指针变量{intc[5];//直接声明一个数组指针变
量int(pointer)[5]=&c;for(i=0;i<5;i++){(pointer)[i]=i;}fo
r(i=0;i<5;i++){printf("\n%d%d",c[i],(pointer)[i]);}}4.2.3多
维数组本质技术推演inta[10];charmyarray[3][5]PKint(p)[5]myarray名称到底是
什么?多维数组chara[i][j]==>((a+i)+j)转换技巧分析voidmain222(){inta[3
][5];intc[5];//&c+1;intb[10];//b代表数组首元素的地址&b代表这个数组的地址&b+1
相当于指针后移410个单位//a代表什么什么那?a是一个数组指针指向低维数组的指针//a+1;printf("a:%d,
a+1:%d\n",a,a+1);//45{inti=0,j=0,tmp=0;for(i=0;i<3;
i++){for(j=0;j<5;j++){a[i][j]=++tmp;}}printf("\n");for(i=0
;i<3;i++){for(j=0;j<5;j++){printf("%d\n",a[i][j]);}}}//a的本
质是一个数组指针,每次往后跳一维的维数{inti=0,j=0;//定义了一个数组指针变量int(myArrayP
oint)[5];//告诉编译给我开辟四个字节内存myArrayPoint=a;printf("\n");for(i
=0;i<3;i++){for(j=0;j<5;j++){//myArrayPoint[i][j]=++tmp;pr
intf("%d\n",myArrayPoint[i][j]);}}}/charcbuf[30];//cbuf(1级指
针)代表数组首元素的地址。。。&cbuf(二级指针)代表整个数组的地址chararray[10][30];//array是
二级指针(array+i)//相当于整个第i行的数组地址//二级指针&cbuf((array+i))//一维数组的首地址
cbuf((array+i))+j//相当于第i行第j列的地址了把。。。。&array[i][j](((array+i)
)+j)//相当于第i行第j列的地址了把。。。。<====>array[i][j]/system("pause");}结论:a
是一个指向intmyarray[5]的数组指针a+1向后跳54,跳一行。4.2.4多维数组做函数参数退化原因大剖析//证
明一下多维数组的线性存储//线性打印voidprintfArray411(intarray,intnum){inti
=0;for(i=0;irray412(int(array)[5],intnum){return;}voidprintfArrr333(int
c[3][4][5]){return;}voidmain(){inta[3][5];intc[3][4][5];int
i,j=0;inttmp=0;for(i=0;i<3;i++){for(j=0;j<5;j++){a[i
][j]=tmp++;}}printfArray411((int)a,15);system("pause");}4.2
.5多维数组做函数参数技术推演C语言中只会以机械式的值拷贝的方式传递参数(实参把值传给形参)intfun(chara[20],
size_tb){?printf("%d\t%d",b,sizeof(a));}原因1:高效原因2:C语言处理a[n]的时候,
它没有办法知道n是几,它只知道&n[0]是多少,它的值作为参数传递进去了虽然c语言可以做到直接intfun(chara[20]
),然后函数能得到20这个数字,但是,C没有这么做。2、二维数组参数同样存在退化的问题二维数组可以看做是一维数组二维数组中的每
个元素是一维数组二维数组参数中第一维的参数可以省略voidf(inta[5])====》voidf(inta[]);=
==》voidf(inta);voidg(inta[3][3])====》voidg(inta[][3]);=
===》voidg(int(a)[3]);3、等价关系数组参数等效的指针参数一维数组chara[30]指针char
指针数组chara[30]指针的指针chara二维数组chara[10][30]数组的指针char(a)[3
0]4.3指针数组的应用场景指针数组的两种用法(菜单命令行)操作系统拉起应用在框架下干活字符数组自我结束标志//NULL
0''\0''4.4强化训练课堂考试“上黑板”intsort(charp[],intcount,charp,
intncount);intsort(charp[],intcount,char(p)[30],int
ncount);intsort(char(p)[30],intncount,charp,intncoun
t);//把第一种内存模型第二种内存模型结果copy到第三种内存模型中,并排序,打印charsort(charp1,
intnum1,char(p)[30],intnum2,intnum3);//#include"stdio
.h"#include"stdlib.h"#include"string.h"intgetArray3_Free(char
p3,intp3num){inti;if(p3==NULL){return-1;}for(i=0;ium;i++){if(p3[i]!=NULL){free(p3[i]);}}free(p3);}intgetArray3_F
ree2(charp3,intp3num){inti;chartmp=NULL;if(p3==NUL
L){return-1;}tmp=p3;for(i=0;i{free(tmp[i]);}}free(tmp);p3=NULL;//通过间接赋值,去间接的修改实参的值,成0}int
getArray3_2(charmyp1,intnum1,char(myp2)[30],intnum2,c
harmyp3,intnum3){intret=0;inti,j;inttmpNum3=
0;chartmpp3=NULL;chartemp;/printf("111111111");if(myp3
==NULL){printf("222222222");}/printf("33333");if(myp1==NULL|
|myp2==NULL||num3==NULL||myp3==NULL){ret=-1;returnret;}//准
备内存tmpNum3=num1+num2;//分配第一维tmpp3=(char)malloc(tmpNum3
sizeof(char));if(tmpp3==NULL){returnNULL;}//分配第二维把第一种内存模型
数据和第二种内存模型数据,copy到第3中内存模型中for(i=0;i)malloc(strlen(myp1[i])+1);if(tmpp3[i]==NULL){puts("outofspa
ce");returnNULL;}strcpy(tmpp3[i],myp1[i]);}for(j=0;j++){tmpp3[i]=(char)malloc(strlen(myp2[j])+1);//notemodifyif
(tmpp3[i]==NULL){puts("outofspace");returnNULL;}strcpy(tmpp3[
i],myp2[j]);}//排序for(i=0;i){if(strcmp(tmpp3[i],tmpp3[j])>0){temp=tmpp3[i];tmpp3[i]=tmpp3[j
];tmpp3[j]=temp;}}}//通过间接赋值,把结果甩给实参num3=tmpNum3;myp3=tmpp3;/
/0=100;returnret;}chargetArray3(charmyp1,intnum1,cha
r(myp2)[30],intnum2,intnum3){inti,j;inttmpNum3=0;char
tmpp3=NULL;chartemp;if(myp1==NULL||myp2==NULL||num3==N
ULL){returnNULL;}//准备内存tmpNum3=num1+num2;//分配第一维tmpp3=(ch
ar)malloc(tmpNum3sizeof(char));if(tmpp3==NULL){return
NULL;}//分配第二维把第一种内存模型数据和第二种内存模型数据,copy到第3中内存模型中for(i=0;i;i++){tmpp3[i]=(char)malloc(strlen(myp1[i])+1);if(tmpp3[i
]==NULL){puts("outofspace");returnNULL;}strcpy(tmpp3[i],myp1[i
]);}for(j=0;jyp2[j])+1);//noteif(tmpp3[i]==NULL){puts("outofspace");
returnNULL;}strcpy(tmpp3[i],myp2[j]);}//排序for(i=0;i3;i++){for(j=i+1;j)>0){temp=tmpp3[i];tmpp3[i]=tmpp3[j];tmpp3[j]=temp;}}}nu
m3=tmpNum3;returntmpp3;}voidmain(){intnum3=0,i=0;intret
=0;charp1[]={"222222","1111111","33333333"};charp2[4][30
]={"bbbbb","aaaaa","zzzzzz","ccccccc"};charp3=NULL;cha
rmyerrp3=NULL;//p3=getArray3(p1,3,p2,4,&num3);//ret=
getArray3_2(p1,3,p2,4,&p3,&num3);ret=getArray3_2(p1,3,p2,
4,0,&num3);//错误做法if(ret!=0){return;}for(i=0;i){printf("%s\n",p3[i]);}//getArray3_Free(p3,num3);//p3=NULL;ge
tArray3_Free2(&p3,num3);printf("p3:%d\n",p3);system("pause");}
5结构体专题5.1大纲01、结构体类型定义及结构体变量定义charc1,charc2,charname[62];int
agecharname[62];intage,charc1,charc2结构体变量的引用.结构体变量的指针->0
2、结构体做函数参数结构体赋值编译器行为研究结构体变量做函数参数PK结构体指针做函数参数结构体做函数参数(//结构体赋值和实
参形参赋值行为研究)内存四区调用图画法//从键盘获取数据,给结构体变量初始化,并排序,打印结构体stack上分配结构数组和heap
上分配结构体数组03、工程开发中,结构体开发的常见模型及典型错误用法结构体嵌套一级指针结构体嵌套二级指针04、结构体中的深拷贝浅拷
贝问题抛出解决方法5.2结构体类型定义及变量定义///结构体类型定义及结构体变量定义结构体是一种构造数据类型用途:把不同类型的数
据组合成一个整体-------自定义数据类型结构体类型定义///声明一个结构体类型struct_Teacher{charnam
e[32];chartile[32];intage;charaddr[128];};//定义结构体变量的方法/1)定义类型用类
型定义变量2)定义类型的同时,定义变量;3)直接定义结构体变量;/struct_Student{charname[32];ch
artile[32];intage;charaddr[128];}s1,s2;//定义类型的同时,定义变量;struct{c
harname[32];chartile[32];intage;charaddr[128];}s3,s4;//直接定义结构体变量
//初始化结构体变量的几种方法//1)struct_Teachert4={"name2","tile2",2,"ad
dr2"};//2)structDog1{charname[32];chartile[32];intage;charaddr[1
28];}d5={"dog","gongzhu",1,"ddd"};//3)struct{charname[32];c
hartile[32];intage;charaddr[128];}d6={"dog","gongzhu",1,"ddd
"};//结构体变量的引用intmain11(){//struct_Teachert1,t2;//定义同时初始化{stru
ct_Teachert3={"name2","tile2",2,"addr2"};printf("%s\n",t3
.name);printf("%s\n",t3.tile);}//用指针法和变量法分别操作结构体{struct_Teacher
t4;struct_TeacherpTeacher=NULL;pTeacher=&t4;strcpy(t4.nam
e,"wangbaoming");strcpy(pTeacher->addr,"ddddd");printf("t4.name
:%s\n",t4.name);}printf("hello....\n");getchar();return0;}5.3结构
体做函数参数及结构体数组//测试两个结构体变量之间可以copy数据吗?//t2=t1;//测试实参传给形参,编译器的行为//结
果很出人意外//声明一个结构体类型struct_MyTeacher{charname[32];chartile[32];inta
ge;charaddr[128];};voidprintfMyteach01(struct_MyTeachert){prin
tf("\nt.name:%s",t.name);}voidprintfMyteach02(struct_MyTeacher
t){printf("\nt->name:%s",t->name);}//结构体赋值和实参形参赋值行为研究intmain2
1(){struct_MyTeachert1,t2;memset(&t1,0,sizeof(t1));strcpy(t1
.name,"name");strcpy(t1.addr,"addr");strcpy(t1.tile,"addr");t1
.age=1;//测试两个结构体变量之间可以copy数据吗?//t2=t1;//测试实参传给形参,编译器的行为//结果很出
人意外printfMyteach01(t1);printfMyteach02(&t1);getchar();return0;}/
/定义结构体数组intmain22(){inti=0;struct_MyTeacherteaArray[3];stru
ct_MyTeachertmp=NULL;for(i=0;i<3;i++){strcpy(teaArray[i].
name,"aaaaa");//printf("%s",teaArray[i].name);tmp=&teaArray[i
];printf("%s",tmp->name);}getchar();return0;}例子从键盘接受数据。。。。并排序in
tprintfArray(struct_MyTeacherteaArray,intcount){inti=0;/
/打印for(i=0;iy[i].name);printf("\n教师年龄:");printf("%d",teaArray[i].age);}}int
main23(){inti=0,j=0;struct_MyTeacherteaArray[3];struct_M
yTeachertmp;for(i=0;i<3;i++){printf("\n请键入教师名字:");scanf("%s",
teaArray[i].name);printf("\n请键入教师年龄:");scanf("%d",&teaArray[i].
age);}for(i=0;i<3;i++){for(j=i+1;j<3;j++){if(teaArray[i].a
ge>teaArray[j].age){tmp=teaArray[i];teaArray[i]=teaArray[j];
teaArray[j]=tmp;}}}//打印for(i=0;i<3;i++){printf("\n教师名字:");pr
intf("%s",teaArray[i].name);printf("\n教师年龄:");printf("%d",teaAr
ray[i].age);}printf("ddddd\n");printfArray(teaArray,3);system("p
ause");}5.4结构体在工程开发中的应用//测试输入//测试打印//测试malloc//测试typdef用法//定义结构体数
组struct_Student{charname[32];chartile[32];};//声明一个结构体类型struct_i
tTeacher{charname[32];chartile[32];intage;charaddr[128];};struct
_itAdvTeacher{charname;chartile;intage;charaddr;charp1;char
p2;};//测试输入//测试打印//测试malloc//测试typdef用法//定义结构体数组intmain(){inti
=0;struct_itTeacherteaArray[3];struct_itTeachertmp=NULL;f
or(i=0;i<3;i++){strcpy(teaArray[i].name,"aaaaa");//printf("%s
",teaArray[i].name);tmp=&teaArray[i];printf("%s",tmp->name);}
getchar();return0;}//内存四字节对齐//结构体实参传给形参,也是一个值copy,相当于t1=t2;//两
个结构体变量之间确实是可以copy,这个是编译器的行为,我们需要顺从6文件操作专题6.1c语言文件读写概念文件分类按文件的逻辑
结构:记录文件:由具有一定结构的记录组成(定长和不定长)流式文件:由一个个字符(字节)数据顺序组成按存储介质:普通文件:存储介质文
件(磁盘、磁带等)设备文件:非存储介质(键盘、显示器、打印机等)按数据的组织形式:文本文件:ASCII文件,每个字节存放一个字符
的ASCII码二进制文件:数据按其在内存中的存储形式原样存放每个文件都以文件名为标识,I/O设备的文件名是系统定义的,如:COM1
或AUX——第一串行口,附加设备COM2——第二串行口,此外,还可能有COM3、COM4等CON——控制台(console),键盘
(输入用)或显示器(输出用)LPT1或PRN——第一并行口或打印机LPT2——第二并行口,还可能有LPT3等NUL——空设备磁盘文
件可以由用户自己命名,但上述被系统(windows和dos下均是如此)保留的设备名字不能用作文件名,如不能把一个文件命名为CON(
不带扩展名)或CON.TXT(不带扩展名)。流概念流是一个动态的概念,可以将一个字节形象地比喻成一滴水,字节在设备、文件和程序之间
的传输就是流,类似于水在管道中的传输,可以看出,流是对输入输出源的一种抽象,也是对传输信息的一种抽象。通过对输入输出源的抽象,屏蔽
了设备之间的差异,使程序员能以一种通用的方式进行存储操作,通过对传输信息的抽象,使得所有信息都转化为字节流的形式传输,信息解读的过
程与传输过程分离。C语言中,I/O操作可以简单地看作是从程序移进或移出字节,这种搬运的过程便称为流(stream)。程序只需要关心
是否正确地输出了字节数据,以及是否正确地输入了要读取字节数据,特定I/O设备的细节对程序员是隐藏的。文件处理方法缓冲文件系统:高级
文件系统,系统自动为正在使用的文件开辟内存缓冲区非缓冲文件系统:低级文件系统,由用户在程序中为每个文件设定缓冲区缓冲文件系统理解:
文件句柄6.2文件操作API6.2.1文件api的分类01)文件读写apifgetcfputc按照字符读写文件fputs
fgets按照行读写文件(读写配置文件)freadfwirte按照块读写文件(大数据块迁移)fprintf按照格式化
进行读写文件fprintf(fp,"%s=%s\n",pKey,pValue);02)文件控制api文件是否结束文件指针
的定位、跳转fseek(fp,0L,SEEK_END);//把文件指针从0位置开始,移动到文件末尾//获取文件长度;leng
th=ftell(fp);fseek(fp,0L,SEEK_SET);03)api做项目6.2.2标准文件的读写1.文件
的打开fopen()文件的打开操作表示将给用户指定的文件在内存分配一个FILE结构区,并将该结构的指针返回给用户程序,以后用户程
序就可用此FILE指针来实现对指定文件的存取操作了。当使用打开函数时,必须给出文件名、文件操作方式(读、写或读写),如果该文件名不
存在,就意味着建立(只对写文件而言,对读文件则出错),并将文件指针指向文件开头。若已有一个同名文件存在,则删除该文件,若无同名文件
,则建立该文件,并将文件指针指向文件开头。fopen(charfilename,chartype);其中filename
是要打开文件的文件名指针,一般用双引号括起来的文件名表示,也可使用双反斜杠隔开的路径名。而type参数表示了对打开文件的操作方式
。其可采用的操作方式如下:方式含义"r"打开,只读"w"打开,文件指针指到头,只写"a"打开,指向文件
尾,在已存在文件中追加"rb"打开一个二进制文件,只读"wb"打开一个二进制文件,只写"ab"打开一个二进制文件,进行追加"
r+"以读/写方式打开一个已存在的文件"w+"以读/写方式建立一个新的文本文件"a+"以读/写方式打开一个文件文件进行追加"
rb+"以读/写方式打开一个二进制文件"wb+"以读/写方式建立一个新的二进制文件"ab+"以读/写方式打开一个二进制文件进
行追加当用fopen(0成功的打开一个文件时,该函数将返回一个FILE指针,如果文件打开失败,将返回一个NULL指针。如想打开te
st文件,进行写:FILEfp;if((fp=fopen("test","w"))==NULL){printf("Filec
annotbeopened\n");exit();}elseprintf("Fileopenedforwritin
g\n");……fclose(fp);DOS操作系统对同时打开的文件数目是有限制的,缺省值为5,可以通过修改CONFIG.SY
S文件改变这个设置。2.关闭文件函数fclose()文件操作完成后,必须要用fclose()函数进行关闭,这是因为对打开的文件进行
写入时,若文件缓冲区的空间未被写入的内容填满,这些内容不会写到打开的文件中去而丢失。只有对打开的文件进行关闭操作时,停留在文件缓冲
区的内容才能写到该文件中去,从而使文件完整。再者一旦关闭了文件,该文件对应的FILE结构将被释放,从而使关闭的文件得到保护,因为这
时对该文件的存取操作将不会进行。文件的关闭也意味着释放了该文件的缓冲区。intfclose(FILEstream);它表示该
函数将关闭FILE指针对应的文件,并返回一个整数值。若成功地关闭了文件,则返回一个0值,否则返回一个非0值。常用以下方法进行测试:
if(fclose(fp)!=0){printf("Filecannotbeclosed\n");exit(1);}else
printf("Fileisnowclosed\n");当打开多个文件进行操作,而又要同时关闭时,可采用fcloseall(
)函数,它将关闭所有在程序中打开的文件。intfcloseall();该函数将关闭所有已打开的文件,将各文件缓冲区未装满的内容写
到相应的文件中去,接着释放这些缓冲区,并返回关闭文件的数目。如关闭了4个文件,则当执行:n=fcloseall();时,n应为4
。3.文件的读写(1).读写文件中字符的函数(一次只读写文件中的一个字符):intfgetc(FILEstream);int
fgetchar(void);intfputc(intch,FILEstream);intfputchar(intc
h);intgetc(FILEstream);intputc(intch,FILEstream);其中fgetc
()函数将把由流指针指向的文件中的一个字符读出,例如:ch=fgetc(fp);将把流指针fp指向的文件中的一个字符读出,并赋给
ch,当执行fgetc()函数时,若当时文件指针指到文件尾,即遇到文件结束标志EOF(其对应值为-1),该函数返回一个-1给ch,
在程序中常用检查该函数返回值是否为-1来判断是否已读到文件尾,从而决定是否继续。#include"stdio.h"main(){
FILEfp;chch;if((fp=fopen("myfile.tex","r"))==NULL){printf("fil
ecannotbeopened\n");exit(1);}while((ch=fgetc(fp))!=EOF)fputc
(ch,stdout);fclose(fp);}该程序以只读方式打开myfile.txt文件,在执行while循环时,文件指针每
循环一次后移一个字符位置。用fgetc()函数将文件指针指定的字符读到ch变量中,然后用fputc()函数在屏幕上显示,当读到文件
结束标志EOF时,变关闭该文件。上面的程序用到了fputc()函数,该函数将字符变量ch的值写到流指针指定的文件中去,由于流指针用
的是标准输出(显示器)的FILE指针stdout,故读出的字符将在显示器上显示。又比如:putc(ch,fp);该函数执行结构,将
把ch表示的字符送到流指针fp指向的文件中去。在TC中,putc()等价于fput(),getc()等价于fgetc()。putc
har(c)相当于fputc(c,stdout);getchar()相当于fgetc(stdin)。注意,这里使用charch,
其实是不科学的,因为最后判断结束标志时,是看ch!=EOF,而EOF的值为-1,这显然和char是不能比较的。所以,某些使用,我们
都定义成intch。(2).读写文件中字符串的函数charfgets(charstring,intn,FILE
stream);chargets(chars);intfprintf(FILEstream,charformat
,variable-list);intfputs(charstring,FILEstream);intfscanf(F
ILEstream,charformat,variable-list);其中fgets()函数将把由流指针指定的文件中n-
1个字符,读到由指针stream指向的字符数组中去,例如:fgets(buffer,9,fp);将把fp指向的文件中的8个字符读
到buffer内存区,buffer可以是定义的字符数组,也可以是动态分配的内存区。注意,fgets()函数读到''\n''就停止,而不
管是否达到数目要求。同时在读取字符串的最后加上''\0''。fgets()函数执行完以后,返回一个指向该串的指针。如果读到文件尾或出错
,则均返回一个空指针NULL,所以长用feof()函数来测定是否到了文件尾或者是ferror()函数来测试是否出错,例如下面的程序
用fgets()函数读test.txt文件中的第一行并显示出来:#include"stdio.h"main(){FILEf
p;charstr[128];if((fp=fopen("test.txt","r"))==NULL){printf("cann
otopenfile\n");exit(1);}while(!feof(fp)){if(fgets(str,128,fp)!=
NULL)printf("%s",str);}fclose(fp);}gets()函数执行时,只要未遇到换行符或文件结束标志,将
一直读下去。因此读到什么时候为止,需要用户进行控制,否则可能造成存储区的溢出。fputs()函数想指定文件写入一个由string指
向的字符串,''\0''不写入文件。fprintf()和fscanf()同printf()和scanf()函数类似,不同之处就是pri
ntf()函数是想显示器输出,fprintf()则是向流指针指向的文件输出;fscanf()是从文件输入。下面程序是向文件test
.dat里输入一些字符:#includemain(){chars="That''sgoodnews"
;inti=617;FILEfp;fp=fopne("test.dat","w");fputs("Yoursco
reofTOEFLis",fp);fputc('':'',fp);fprintf(fp,"%d\n",i);fprin
tf(fp,"%s",s);fclose(fp);}用DOS的TYPE命令显示TEST.DAT的内容如下所示:屏幕显示
YourscoreofTOEFLis:617That''sgoodnews下面的程序是把上面的文件test.da
t里的内容在屏幕上显示出来:#includemain(){chars,m[20];inti;FIL
Efp;fp=fopen("test.dat","r");fgets(s,24,fp);printf("%s
",s);fscanf(fp,"%d",&i);printf("%d",i);putchar(fgetc(fp));
fgets(m,17,fp);puts(m);fclose(fp);getch();}运行后屏幕显示:Yours
coreofTOEFLis:617That''sgoodnews4.清除和设置文件缓冲区(1).清除文件缓冲区函数:i
ntfflush(FILEstream);intflushall();fflush()函数将清除由stream指向的文件
缓冲区里的内容,常用于写完一些数据后,立即用该函数清除缓冲区,以免误操作时,破坏原来的数据。flushall()将清除所有打开文件
所对应的文件缓冲区。(2).设置文件缓冲区函数voidsetbuf(FILEstream,charbuf);void
setvbuf(FILEstream,charbuf,inttype,unsignedsize);这两个函数将使得打
开文件后,用户可建立自己的文件缓冲区,而不使用fopen()函数打开文件设定的默认缓冲区。对于setbuf()函数,buf指出的缓
冲区长度由头文件stdio.h中定义的宏BUFSIZE的值决定,缺省值为512字节。当选定buf为空时,setbuf函数将使的文件
I/O不带缓冲。而对setvbuf函数,则由malloc函数来分配缓冲区。参数size指明了缓冲区的长度(必须大于0),而参数ty
pe则表示了缓冲的类型,其值可以取如下值:type值含义_IOFBF文件全部缓冲,即
缓冲区装满后,才能对文件读写_IOLBF文件行缓冲,即缓冲区接收到一个换行符时,才能对文件读写_IONBF
文件不缓冲,此时忽略buf,size的值,直接读写文件,不再经过文件缓冲区缓冲5.文件的随机读写函数前面介绍的文件的字符
/字符串读写,均是进行文件的顺序读写,即总是从文件的开头开始进行读写。这显然不能满足我们的要求,C语言提供了移动文件指针和随机读写
的函数,它们是:(1).移动文件指针函数:longftell(FILEstream);intrewind(FILE
stream);fseek(FILEstream,longoffset,intorigin);函数ftell()用来得
到文件指针离文件开头的偏移量。当返回值是-1时表示出错。rewind()函数用于文件指针移到文件的开头,当移动成功时,返回0,否则
返回一个非0值。fseek()函数用于把文件指针以origin为起点移动offset个字节,其中origin指出的位置可有以下几种
:origin数值代表的具体位置SEEK_SET0
文件开头SEEK_CUR1文件指针当前位置SEEK_END
2文件尾例如:fseek(fp,10L,0);把文件指针从文件开头移到第10
字节处,由于offset参数要求是长整型数,故其数后带L。fseek(fp,-15L,2);把文件指针从文件尾向前移动15字节。
(2).文件随机读写函数intfread(voidptr,intsize,intnitems,FILEstream)
;intfwrite(voidptr,intsize,intnitems,FILEstream);fread()函
数从流指针指定的文件中读取nitems个数据项,每个数据项的长度为size个字节,读取的nitems数据项存入由ptr指针指向的内
存缓冲区中,在执行fread()函数时,文件指针随着读取的字节数而向后移动,最后移动结束的位置等于实际读出的字节数。该函数执行结束
后,将返回实际读出的数据项数,这个数据项数不一定等于设置的nitems,因为若文件中没有足够的数据项,或读中间出错,都会导致返回的
数据项数少于设置的nitems。当返回数不等于nitems时,可以用feof()或ferror()函数进行检查。fwrite()函
数从ptr指向的缓冲区中取出长度为size字节的nitems个数据项,写入到流指针stream指向的文件中,执行该操作后,文件指针
将向后移动,移动的字节数等于写入文件的字节数目。该函数操作完成后,也将返回写入的数据项数。6.2.3非标准文件的读写这类函数最早用
于UNIX操作系统,ANSI标准未定义,但有时也经常用到,DOS3.0以上版本支持这些函数。它们的头文件为io.h。由于我们不常
用这些函数,所以在这里就简单说一下。1.文件的打开和关闭open()函数的作用是打开文件,其调用格式为:intopen(char
filename,intaccess);该函数表示按access的要求打开名为filename的文件,返回值为文件描述字,
其中access有两部分内容:基本模式和修饰符,两者用""("或")方式连接。修饰符可以有多个,但基本模式只能有一个。acc
ess的规定--------------------------------------------------------基本模
式含义修饰符含义------------------------------
--------------------------O_RDONLY只读O_APPEND文件指针指
向末尾O_WRONLY只写O_CREAT文件不存在时创建文件,属性按基本模式属性O_RDWR
读写O_TRUNC若文件存在,将其长度缩为0,属性不变O_BINARY打开一个二
进制文件O_TEXT打开一个文字文件----------------------------------------
-----------------open()函数打开成功,返回值就是文件描述字的值(非负值),否则返回-1。close()函
数的作用是关闭由open()函数打开的文件,其调用格式为:intclose(inthandle);该函数关闭文件描述字ha
ndle相连的文件。2.读写函数intread(inthandle,voidbuf,intcount);read
()函数从handle(文件描述字)相连的文件中,读取count个字节放到buf所指的缓冲区中,返回值为实际所读字节数,返回
-1表示出错。返回0表示文件结束。write()函数的调用格式为:intwrite(inthandle,voidb
uf,intcount);write()函数把count个字节从buf指向的缓冲区写入与handle相连的文件中,返回值为
实际写入的字节数。3.随机定位函数lseek()函数的调用格式为:intlseek(inthandle,longof
fset,intfromwhere);该函数对与handle相连的文件位置指针进行定位,功能和用法与fseek()函数相同。
tell()函数的调用格式为:longtell(inthandle);该函数返回与handle相连的文件现生位置指针,
功能和用法与ftell()相同6.2.4注意点文本文件:ASCII文件,每个字节存放一个字符的ASCII码二进制文件:数据按其
在内存中的存储形式原样存放项目开发中参考fgets函数的实现方法fgets(buf,bufMaxLen,fp);对fgets函
数来说,n必须是个正整数,表示从文件按中读出的字符数不超过n-1,存储到字符数组str中,并在末尾加上结束标志’\0’,换言之,n
代表了字符数组的长度,即sizeof(str)。如果读取过程中遇到换行符或文件结束标志,读取操作结束。若正常读取,返回指向str代
表字符串的指针,否则,返回NULL(空指针)。6.3文件操作案例-配置文件读写配置文件读写案例实现分析功能划分界面测试(功能集成
)自己动手规划接口模型。配置文件读写配置文件读(根据key,读取valude)配置文件写(输入key、valude)配置文件修改(
输入key、valude)优化===》接口要求紧模块要求松实现及代码讲解测试。6.4文件操作案例-大文件加解密功能实现分析1、
数据加密解密接口测试2、数据加密过程分析文件数据的movecopy+数据加密3、数据加解密功能集成数据加密和解密分为两个版本
打padding和不打padding数据加密解密原理7C接口的封装和设计专题Win32环境下动态链接库(DLL)编程原理比较
大的应用程序都由很多模块组成,这些模块分别完成相对独立的功能,它们彼此协作来完成整个软件系统的工作。其中可能存在一些模块的功能较为
通用,在构造其它软件系统时仍会被使用。在构造软件系统时,如果将所有模块的源代码都静态编译到整个应用程序EXE文件中,会产生一些问题
:一个缺点是增加了应用程序的大小,它会占用更多的磁盘空间,程序运行时也会消耗较大的内存空间,造成系统资源的浪费;另一个缺点是,在编
写大的EXE程序时,在每次修改重建时都必须调整编译所有源代码,增加了编译过程的复杂性,也不利于阶段性的单元测试。Windows
系统平台上提供了一种完全不同的较有效的编程和运行环境,你可以将独立的程序模块创建为较小的DLL(DynamicLinkable
Library)文件,并可对它们单独编译和测试。在运行时,只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将它们装载到内
存空间中。这种方式不仅减少了EXE文件的大小和对内存空间的需求,而且使这些DLL模块可以同时被多个应用程序使用。Microsof
tWindows自己就将一些主要的系统功能以DLL模块的形式实现。例如IE中的一些基本功能就是由DLL文件实现的,它可以被其它应
用程序调用和集成。一般来说,DLL是一种磁盘文件(通常带有DLL扩展名),它由全局数据、服务函数和资源组成,在运行时被系统加载到
进程的虚拟空间中,成为调用进程的一部分。如果与其它DLL之间没有冲突,该文件通常映射到进程虚拟空间的同一地址上。DLL模块中包含
各种导出函数,用于向外界提供服务。Windows在加载DLL模块时将进程函数调用与DLL文件的导出函数相匹配。在Win32环境
中,每个进程都复制了自己的读/写全局变量。如果想要与其它进程共享内存,必须使用内存映射文件或者声明一个共享数据段。DLL模块需要的
堆栈内存都是从运行进程的堆栈中分配出来的。DLL现在越来越容易编写。Win32已经大大简化了其编程模式,并有许多来自AppWiz
ard和MFC类库的支持。一、导出和导入函数的匹配DLL文件中包含一个导出函数表。这些导出函数由它们的符号名和称为标识号的整数
与外界联系起来。函数表中还包含了DLL中函数的地址。当应用程序加载DLL模块时时,它并不知道调用函数的实际地址,但它知道函数的符
号名和标识号。动态链接过程在加载的DLL模块时动态建立一个函数调用与函数地址的对应表。如果重新编译和重建DLL文件,并不需要修改
应用程序,除非你改变了导出函数的符号名和参数序列。简单的DLL文件只为应用程序提供导出函数,比较复杂的DLL文件除了提供导出函数
以外,还调用其它DLL文件中的函数。这样,一个特殊的DLL可以既有导入函数,又有导入函数。这并不是一个问题,因为动态链接过程可以处
理交叉相关的情况。在DLL代码中,必须像下面这样明确声明导出函数:__declspec(dllexport)intMyFun
ction(intn);但也可以在模块定义(DEF)文件中列出导出函数,不过这样做常常引起更多的麻烦。在应用程序方面,要求像下
面这样明确声明相应的输入函数:__declspec(dllimport)intMyFuncition(intn);仅有导入
和导出声明并不能使应用程序内部的函数调用链接到相应的DLL文件上。应用程序的项目必须为链接程序指定所需的输入库(LIB文件)。而且
应用程序事实上必须至少包含一个对DLL函数的调用。二、与DLL模块建立链接应用程序导入函数与DLL文件中的导出函数进行链接有两
种方式:隐式链接和显式链接。所谓的隐式链接是指在应用程序中不需指明DLL文件的实际存储路径,程序员不需关心DLL文件的实际装载。而
显式链接与此相反。采用隐式链接方式,程序员在建立一个DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件。该文件包含了每
一个DLL导出函数的符号名和可选的标识号,但是并不含有实际的代码。LIB文件作为DLL的替代文件被编译到应用程序项目中。当程序员
通过静态链接方式编译生成应用程序时,应用程序中的调用函数与LIB文件中导出符号相匹配,这些符号或标识号进入到生成的EXE文件中。
LIB文件中也包含了对应的DLL文件名(但不是完全的路径名),链接程序将其存储在EXE文件内部。当应用程序运行过程中需要加载DL
L文件时,Windows根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL函数的动态链接。显式链接方式对于集成化
的开发语言(例如VB)比较适合。有了显式链接,程序员就不必再使用导入文件,而是直接调用Win32的LoadLibary函数,并
指定DLL的路径作为参数。LoadLibary返回HINSTANCE参数,应用程序在调用GetProcAddress函数时使用这
一参数。GetProcAddress函数将符号名或标识号转换为DLL内部的地址。假设有一个导出如下函数的DLL文件:extern
"C"__declspec(dllexport)doubleSquareRoot(doubled);下面是应用程序对该
导出函数的显式链接的例子:c====》应用win/linux系统编程apitypedefdouble(SQRTPROC)(dou
ble);HINSTANCEhInstance;SQRTPROCpFunction;VERIFY(hInstance=::L
oadLibrary("c:\\winnt\\system32\\mydll.dll"));VERIFY(pFunction=(SQRTPROC)::GetProcAddress(hInstance,"SquareRoot"));doubled=(pFunction)(81.0);//调用该DLL函数在隐式链接方式中,所有被应用程序调用的DLL文件都会在应用程序EXE文件加载时被加载在到内存中;但如果采用显式链接方式,程序员可以决定DLL文件何时加载或不加载。显式链接在运行时决定加载哪个DLL文件。例如,可以将一个带有字符串资源的DLL模块以英语加载,而另一个以西班牙语加载。应用程序在用户选择了合适的语种后再加载与之对应的DLL文件。三、使用符号名链接与标识号链接在Win16环境中,符号名链接效率较低,所有那时标识号链接是主要的链接方式。在Win32环境中,符号名链接的效率得到了改善。Microsoft现在推荐使用符号名链接。但在MFC库中的DLL版本仍然采用的是标识号链接。一个典型的MFC程序可能会链接到数百个MFCDLL函数上。采用标识号链接的应用程序的EXE文件体相对较小,因为它不必包含导入函数的长字符串符号名。四、编写DllMain函数DllMain函数是DLL模块的默认入口点。当Windows加载DLL模块时调用这一函数。系统首先调用全局对象的构造函数,然后调用全局函数DLLMain。DLLMain函数不仅在将DLL链接加载到进程时被调用,在DLL模块与进程分离时(以及其它时候)也被调用。下面是一个框架DLLMain函数的例子。HINSTANCEg_hInstance;extern"C"intAPIENTRYDllMain(HINSTANCEhInstance,DWORDdwReason,LPVOIDlpReserved){if(dwReason==DLL_PROCESS_ATTACH){TRACE0("EX22A.DLLInitializing!\n");//在这里进行初始化}elseif(dwReason=DLL_PROCESS_DETACH){TRACE0("EX22A.DLLTerminating!\n");//在这里进行清除工作}return1;//成功}如果程序员没有为DLL模块编写一个DLLMain函数,系统会从其它运行库中引入一个不做任何操作的缺省DLLMain函数版本。在单个线程启动和终止时,DLLMain函数也被调用。正如由dwReason参数所表明的那样。五、模块句柄进程中的每个DLL模块被全局唯一的32字节的HINSTANCE句柄标识。进程自己还有一个HINSTANCE句柄。所有这些模块句柄都只有在特定的进程内部有效,它们代表了DLL或EXE模块在进程虚拟空间中的起始地址。在Win32中,HINSTANCE和HMODULE的值是相同的,这个两种类型可以替换使用。进程模块句柄几乎总是等于0x400000,而DLL模块的加载地址的缺省句柄是0x10000000。如果程序同时使用了几个DLL模块,每一个都会有不同的HINSTANCE值。这是因为在创建DLL文件时指定了不同的基地址,或者是因为加载程序对DLL代码进行了重定位。模块句柄对于加载资源特别重要。Win32的FindResource函数中带有一个HINSTANCE参数。EXE和DLL都有其自己的资源。如果应用程序需要来自于DLL的资源,就将此参数指定为DLL的模块句柄。如果需要EXE文件中包含的资源,就指定EXE的模块句柄。但是在使用这些句柄之前存在一个问题,你怎样得到它们呢?如果需要得到EXE模块句柄,调用带有Null参数的Win32函数GetModuleHandle;如果需要DLL模块句柄,就调用以DLL文件名为参数的Win32函数GetModuleHandle。六、应用程序怎样找到DLL文件如果应用程序使用LoadLibrary显式链接,那么在这个函数的参数中可以指定DLL文件的完整路径。如果不指定路径,或是进行隐式链接,Windows将遵循下面的搜索顺序来定位DLL:1.包含EXE文件的目录,2.进程的当前工作目录,3.Windows系统目录,4.Windows目录,5.列在Path环境变量中的一系列目录。这里有一个很容易发生错误的陷阱。如果你使用VC++进行项目开发,并且为DLL模块专门创建了一个项目,然后将生成的DLL文件拷贝到系统目录下,从应用程序中调用DLL模块。到目前为止,一切正常。接下来对DLL模块做了一些修改后重新生成了新的DLL文件,但你忘记将新的DLL文件拷贝到系统目录下。下一次当你运行应用程序时,它仍加载了老版本的DLL文件,这可要当心!七、调试DLL程序Microsoft的VC++是开发和测试DLL的有效工具,只需从DLL项目中运行调试程序即可。当你第一次这样操作时,调试程序会向你询问EXE文件的路径。此后每次在调试程序中运行DLL时,调试程序会自动加载该EXE文件。然后该EXE文件用上面的搜索序列发现DLL文件,这意味着你必须设置Path环境变量让其包含DLL文件的磁盘路径,或者也可以将DLL文件拷贝到搜索序列中的目录路径下。DLL分配的内存如何在EXE里面释放总结下面几个要点:1.保证内存分配和清除的统一性:如果一个DLL提供一个能够分配内存的函数,那么这个DLL同时应该提供一个函数释放这些内存。数据的创建和清除应该在同一个层次上。曾经遇到过这样的例子:在dll中分配了一块内存,通过PostMessage将其地址传给应用。然后应用去释放它,结果总是报异常。2.如果exe用MFCAppwizard方式生成,dll用win32方式生成,则运行时会出现错误。进一步用单步跟踪,发现mfc方式和win32方式下的new操作符是用不同方式实现的,源程序分别在VC目录的文件Afxmem.cpp和new.cpp中。有兴趣的话可以自已跟踪一下。因为dll输出函数后,并不知道是哪一个模拟调用它,因此new和delete配对时最好在一个文件中,这样可以保证一致性。3.问题主要在于DLL和EXE主程序中分配内存的堆不一样,你可以不用new和delete,而是用1)::HeapAlloc(::GetProcessHeap(),...)和::HeapFree(::GetProcessHeap(),...)2)::GlobalAlloc()和::GlobalFree()这两对API,这样无论在DLL中还是在主程序中都是在进程默认堆中分配,就不会出错了。4.还有一个办法,就是把dll的Settings的C/C++选项卡的CodeGeneration的UseRun-timeliberary改成DebugMultithreadedDLL,在Release版本中改成MultithreadedDLL,就可以直接使用new和delete了。不过MFC就不能用Shared模式了。
献花(0)
+1
(本文系zhengtu342原创)