多进程编程的相关知识总结

时间:2019-05-12 03:57:21下载本文作者:会员上传
简介:写写帮文库小编为你整理了多篇相关的《多进程编程的相关知识总结》,但愿对你工作学习有帮助,当然你在写写帮文库还可以找到更多《多进程编程的相关知识总结》。

第一篇:多进程编程的相关知识总结

多进程编程的相关知识总结

(一)作者:不详

阅读人次:2138

文章来源:vczx.com

发布时间:2007-8-29

友评论(0)条

一.多进程程序的特点

由于UNIX系统是分时多用户系统, CPU按时间片分配给各个用户使用, 而在实质上 应该

说CPU按时间片分配给各个进程使用, 每个进程都有自己的运行环境以使得在CPU 做进程

切换时不会“忘记”该进程已计算了一半的“半成品”.以DOS的概念来说, 进程的切 换都

是一次“DOS中断”处理过程, 包括三个层次:

(1)用户数据的保存: 包括正文段(TEXT), 数据段(DATA,BSS), 栈段(STACK), 共享

内存段(SHARED MEMORY)的保存.(2)寄存器数据的保存: 包括PC(program counter,指向下一条要执行的 指

令的地址), PSW(processor status word,处理机状态字), SP(stack pointer,栈 指

针), PCBP(pointer of process control block,进程控制块指针), FP(frame pointer,指向栈中一个函数的local变量的首地址), AP(augument pointer,指向 栈中函

数调用的实参位置), ISP(interrupt stack pointer,中断栈指针), 以及其他的 通用寄 存器等.(3)系统层次的保存: 包括proc,u,虚拟存储空间管理表格,中断处理栈.以便于

该进程再一次得到CPU时间片时能正常运行下去.既然系统已经处理好所有这些中 断处理 的过程, 我们做程序还有什么要担心的呢? 我们尽可以使用系统提供的多进程的 特点, 让几个程序精诚合作, 简单而又高效地把结果给它搞出来.另外,UNIX系统本身也是用C语言写的多进程程序,多进程编程是UNIX的特点, 当我们

熟悉了多进程编程后,将会对UNIX系统机制有一个较深的认识.首先我介绍一下多 进程程

序的一些突出的特点:

1.并行化

一件复杂的事件是可以分解成若干个简单事件来解决的, 这在程序员

的大脑中早就形成了这种概念, 首先将问题分解成一个个小问题, 将小问

题再细分, 最后在一个合适的规模上做成一个函数.在软件工程中也是这

么说的.如果我们以图的方式来思考, 一些小问题的计算是可以互不干扰

的, 可以同时处理, 而在关键点则需要统一在一个地方来处理, 这样程序

的运行就是并行的, 至少从人的时间观念上来说是这样的.而每个小问题

的计算又是较简单的.2.简单有序

这样的程序对程序员来说不亚于管理一班人, 程序员为每个进程设计

好相应的功能, 并通过一定的通讯机制将它们有机地结合在一起, 对每个

进程的设计是简单的, 只在总控部分小心应付(其实也是蛮简单的), 就可

完成整个程序的施工.3.互不干扰

这个特点是操作系统的特点, 各个进程是独立的, 不会串位.4.事务化

比如在一个数据电话查询系统中, 将程序设计成一个进程只处理一次

查询即可, 即完成一个事务.当电话查询开始时, 产生这样一个进程对付

这次查询;另一个电话进来时, 主控程序又产生一个这样的进程对付, 每 个进程完成查询任务后消失.这样的编程多简单, 只要做一次查询的程序

就可以了.二.常用的多进程编程的系统调用

1.fork()

功能:创建一个新的进程.语法:#include

#include

pid_t fork();

说明:本系统调用产生一个新的进程, 叫子进程, 是调用进程的一个复

制品.调用进程叫父进程, 子进程继承了父进程的几乎所有的属

性:

.实际UID,GID和有效UID,GID..环境变量..附加GID..调用exec()时的关闭标志..UID设置模式比特位..GID设置模式比特位..进程组号..会话ID..控制终端..当前工作目录..根目录..文件创建掩码UMASK..文件长度限制ULIMIT..预定值, 如优先级和任何其他的进程预定参数, 根据种类不同

决定是否可以继承..还有一些其它属性.但子进程也有与父进程不同的属性:

.进程号, 子进程号不同与任何一个活动的进程组号..父进程号..子进程继承父进程的文件描述符或流时, 具有自己的一个拷贝

并且与父进程和其它子进程共享该资源..子进程的用户时间和系统时间被初始化为0..子进程的超时时钟设置为0..子进程的信号处理函数指针组置为空..子进程不继承父进程的记录锁.返回值: 调用成功则对子进程返回0, 对父进程返回子进程号, 这也是

最方便的区分父子进程的方法.若调用失败则返回-1给父进程,子进程不生成.例子:pid_t pid;

if((pid=fork())>0){

/*父进程处理过程*/

}

else if(pid==0){

/*子进程处理过程*/

exit(0);

/*注意子进程必须用exit()退出运行*/

}

else {

printf(“fork errorn”);

exit(0);

}

2.system()

功能:产生一个新的进程, 子进程执行指定的命令.语法:#include

#include

int system(string)

char *string;

说明:本调用将参数string传递给一个命令解释器(一般为sh)执行, 即

string被解释为一条命令, 由sh执行该命令.若参数string为一

个空指针则为检查命令解释器是否存在.该命令可以同命令行命令相同形式, 但由于命令做为一个参数放

在系统调用中, 应注意编译时对特殊意义字符的处理.命令的查

找是按PATH环境变量的定义的.命令所生成的后果一般不会对父

进程造成影响.返回值:当参数为空指针时, 只有当命令解释器有效时返回值为非零.若参数不为空指针, 返回值为该命令的返回状态(同waitpid())

的返回值.命令无效或语法错误则返回非零值,所执行的命令被

终止.其他情况则返回-1.例子:char command[81];

int i;

for(i=1;i<8;i++){ sprintf(command,“ps-t tty%02i”,i);system(command);} 3.exec()功能:执行一个文件 语法:#include

int execl(path,arg0,...,argn,(char*)0)

char *path,*arg0,...,*argn;

int execv(path,argv)

char *path,*argv[];

int execle(path,arg0,...,argn,(char*)0,envp)

char *path,*arg0,...,*argn,*envp[];

int execve(path,argv,envp)

char *path,*argv[],*envp[];

int execvp(file,argv)

char *file,*argv[];

说明:这是一个系统调用族, 用于将一个新的程序调入本进程所占的内

存, 并覆盖之, 产生新的内存进程映象.新的程序可以是可执行

文件或SHELL批命令.当C程序被执行时,是如下调用的:

main(int argc,char *argv[],char *envp[]);

argc是参数个数,是各个参数字符串指针数组,envp是新进程的环

境变量字符串的指针数组.argc至少为1,argv[0]为程序文件名,所以,在上面的exec系统调用族中,path为新进程文件的路径名,file为新进程文件名,若file不是全路径名,系统调用会按PATH环

境变量自动找对应的可执行文件运行.若新进程文件不是一个可

执行的目标文件(如批处理文件),则execlp()和execvp()会将该

文件内容作为一个命令解释器的标准输入形成system().arg0,...等指针指向''结束的字符串,组成新进程的有效参数,且该参数列表以一个空指针结束.反过来,arg0至少必须存在并指

向新进程文件名或路径名.同样,argv是字符串指针数组,argv[0]指向新进程文件名或路径

名,并以一空指针结束.envp是一个字符串指针数组,以空指针结束,这些字符串组成新进

程的环境.在调用这些系统调用前打开的文件指针对新进程来说也是打开的,除非它已定义了close-on-exec标志.打开的文件指针在新进程中

保持不变,所有相关的文件锁也被保留.调用进程设置并正被捕俘的信号在新进程中被恢复为缺省设置,其它的则保持不变.新进程启动时按文件的SUID和SGID设置定义文件的UID和GID为有

效UID和GID.新进程还继承了如下属性:

.附加GID..进程号..父进程号..进程组号..会话号..控制终端..alarm时钟信号剩下的时间..当前工作目录..根目录..文件创建掩码..资源限制..用户时间,系统时间,子进程用户时间,子进程系统时间..记录锁..进程信号掩码..信号屏蔽..优先级..预定值.调用成功后,系统调用修改新进程文件的最新访问时间.返回值:该系统调用一般不会有成功返回值, 因为原来的进程已荡然无

存.例子:printf(“now this process will be ps commandn”);

execl(“/bin/ps”,“ps”,“-ef”,NULL);

4.popen()

功能:初始化从/到一个进程的管道.语法:#include

FILE *popen(command,type)

char *command,type;说明:本系统调用在调用进程和被执行命令间创建一个管道.参数command做为被执行的命令行.type做为I/O模式,“r”为从被

执行命令读,“w”为向被执行命令写.返回一个标准流指针,做为管

道描述符,向被执行命令读或写数据(做为被执行命令的STDIN或

STDOUT)该系统调用可以用来在程序中调用系统命令,并取得命令

的输出信息或者向命令输入信息.返回值:不成功则返回NULL,成功则返回管道的文件指针.5.pclose()

功能:关闭到一个进程的管道.语法:#include

int pclose(strm)

FILE *strm;

说明:本系统调用用于关闭由popen()打开的管道,并会等待由popen()

激活的命令执行结束后,关闭管道后读取命令返回码.返回值:若关闭的文件描述符不是由popen()打开的,则返回-1.例子:printf(“now this process will call popen system calln”);

FILE * fd;

if((fd=popen(“ps-ef”,“r”))==NULL){

printf(“call popen failedn”);

return;

}

else {

char str[80];

while(fgets(str,80,fd)!=NULL)

printf(“%sn”,str);

}

pclose(fd);

6.wait()

功能:等待一个子进程返回并修改状态

语法:#include

#include

pid_t wait(stat_loc)

int *stat_loc;

说明:允许调用进程取得子进程的状态信息.调用进程将会挂起直到其

一个子进程终止.返回值:等待到一个子进程返回时,返回值为该子进程号,否则返回值为

-1.同时stat_loc返回子进程的返回值.例子:/*父进程*/

if(fork()>0){

wait((int *)0);

/*父进程等待子进程的返回*/

}

else {

/*子进程处理过程*/

exit(0);

}

7.waitpid()

功能:等待指定进程号的子进程的返回并修改状态

语法:#include

#include

pid_t waitpid(pid,stat_loc,options)

pid_t pid;

int *stat_loc,options;

说明:当pid等于-1,options等于0时,该系统调用等同于wait().否则该

系统调用的行为由参数pid和options决定.pid指定了一组父进程要求知道其状态的子进程:

-1:要求知道任何一个子进程的返回状态.>0:要求知道进程号为pid值的子进程的状态.<-1:要求知道进程组号为pid的绝对值的子进程的状态.options参数为以比特方式表示的标志以或运算组成的位图,每个

标志以字节中某个比特置1表示: WUNTRACED:报告任何未知而又已停止运行的指定进程号的子进

程的状态.该子进程的状态自停止运行时起就没有被报告 过.WCONTINUED:报告任何继续运行的指定进程号的子进程的状态,该子进程的状态自继续运行起就没有被报告过.WHOHANG:若调用本系统调用时,指定进程号的子进程的状态目 前并不是立即有效的(即可被立即读取的),调用进程并被 暂停执行.WNOWAIT:保持将其状态设置在stat_loc的进程在可等待状态.该进程将等待直到下次被要求其返回状态值.返回值:等待到一个子进程返回时,返回值为该子进程号,否则返回值为

-1.同时stat_loc返回子进程的返回值.例子:pid_t pid;int stat_loc;/*父进程*/ if((pid=fork())>0){

waitpid(pid,&stat_loc,0);

/*父进程等待进程号为pid的子进程的返回*/

}

else {

/*子进程的处理过程*/

exit(1);

}

/*父进程*/

printf(“stat_loc is [%d]n”,stat_loc);

/*字符串“stat_loc is [1]”将被打印出来*/

8.setpgrp()

功能:设置进程组号和会话号.语法:#include

pid_t setpgrp()

说明:若调用进程不是会话首进程.将进程组号和会话号都设置为与它的进程号相等.并释放调用进程的控制终端.返回值:调用成功后,返回新的进程组号.例子:/*父进程处理*/

if(fork()>0){

/*父进程处理*/

}

else {

setpgrp();

/*子进程的进程组号已修改成与它的进程号相同*/

exit(0);

}

9.exit()

功能:终止进程.语法:#include

void exit(status)

int status;

说明:调用进程被该系统调用终止.引起附加的处理在进程被终止前全

部结束.返回值:无

10.signal()

功能:信号管理功能

语法:#include

void(*signal(sig,disp))(int)

int sig;

void(*disp)(int);

void(*sigset(sig,disp))(int)

int sig;

void(*disp)(int);

int sighold(sig)

int sig;

int sigrelse(sig)

int sig;

(除了

捉到).SIGILL,SIGTRAP

SIG_DF L,将该

句柄

外,程的

int sigignore(sig)

int sig;

int sigpause(sig)

int sig;说明:这些系统调用提供了应用程序对指定信号的简单的信号处理.signal()和sigset()用于修改信号定位.参数sig指定信号

SIGKILL和SIGSTOP,这两种信号由系统处理,用户程序不能捕

disp指定新的信号定位,即新的信号处理函数指针.可以为

SIG_IGN,SIG_DFL或信号句柄地址.若使用signal(),disp是信号句柄地址,sig不能为

或SIGPWR,收到该信号时,系统首先将重置sig的信号句柄为

然后执行信号句柄.若使用sigset(),disp是信号句柄地址,该信号时,系统首先

信号加入调用进程的信号掩码中,然后执行信号句柄.当信号

运行结束

后,系统将恢复调用进程的信号掩码为信号收到前的状态.另

使用sigset()时,disp为SIG_HOLD,则该信号将会加入调用进

信号掩码中而信号的定位不变.sighold()将信号加入调用进程的信号掩码中.sigrelse()将信号从调用进程的信号掩码中删除.sigignore()将信号的定位设置为SIG_IGN.sigpause()将信号从调用进程的信号掩码中删除,同时挂起调用

进程直到收到信号.若信号SIGCHLD的信号定位为SIG_IGN,则调用进程的子进程在终

止时不会变成僵死进程.调用进程也不用等待子进程返回并做相

应处理.返回值:调用成功则signal()返回最近调用signal()设置的disp的值.否则返回SIG_ERR.例子一:设置用户自己的信号中断处理函数,以SIGINT信号为例:

int flag=0;

void myself()

{

flag=1;

printf(“get signal SIGINTn”);

/*若要重新设置SIGINT信号中断处理函数为本函数则执行以

*下步骤*/

void(*a)();

a=myself;

signal(SIGINT,a);

flag=2;

}

main()

{

while(1){

sleep(2000);/*等待中断信号*/

if(flag==1){

printf(“skip system call sleepn”);

exit(0);

}

if(flag==2){

printf(“skip system call sleepn”);

printf(“waiting for next signaln”);

}

}

}

11.kill()

功能:向一个或一组进程发送一个信号.语法:#include

#include

int kill(pid,sig);

pid_t pid;

int sig;

说明:本系统调用向一个或一组进程发送一个信号,该信号由参数sig指

定,为系统给出的信号表中的一个.若为0(空信号)则检查错误但

实际上并没有发送信号,用于检查pid的有效性.pid指定将要被发送信号的进程或进程组.pid若大于0,则信号将

被发送到进程号等于pid的进程;若pid等于0则信号将被发送到所

有的与发送信号进程同在一个进程组的进程(系统的特殊进程除

外);若pid小于-1,则信号将被发送到所有进程组号与pid绝对值

相同的进程;若pid等于-1,则信号将被发送到所有的进程(特殊系

统进程除外).信号要发送到指定的进程,首先调用进程必须有对该进程发送信

号的权限.若调用进程有合适的优先级则具备有权限.若调用进程

的实际或有效的UID等于接收信号的进程的实际UID或用setuid()

系统调用设置的UID,或sig等于SIGCONT同时收发双方进程的会话

号相同,则调用进程也有发送信号的权限.若进程有发送信号到pid指定的任何一个进程的权限则调用成功,否则调用失败,没有信号发出.返回值:调用成功则返回0,否则返回-1.例子:假设前一个例子进程号为324,现向它发一个SIGINT信号,让它做

信号处理:

kill((pid_t)324,SIGINT);

12.alarm()

一次

功能:设置一个进程的超时时钟.语法:#include

unsigned int alarm(sec)

unsigned int sec;说明:指示调用进程的超时时钟在指定的时间后向调用进程发送一个

SIGALRM信号.设置超时时钟时时间值不会被放入堆栈中,后

设置会把前一次(还未到超时时间)冲掉.若sec为0,则取消任何以前设置的超时时钟.exec()

数则执行

信号*/

leepn“);

leepn”);

fork()会将新进程的超时时钟初始化为0.而当一个进程用

族系统调用新的执行文件时,调用前设置的超时时钟在调用后

有效.返回值:返回上次设置超时时钟后到调用时还剩余的时间秒数.例子:int flag=0;

void myself()

{

flag=1;

printf(“get signal SIGALRMn”);

/*若要重新设置SIGALRM信号中断处理函数为本函

*以下步骤*/

void(*a)();

a=myself;

signal(SIGALRM,a);

flag=2;

}

main()

{

alarm(100);

/*100秒后发超时中断

while(1){

sleep(2000);/*等待中断信号*/

if(flag==1){

printf(“skip system call s

exit(0);

}

if(flag==2){

printf(”skip system call s

printf(“waiting for next s

ignaln”);

}

}

}

13.msgsnd()

功能:发送消息到指定的消息队列中.语法:#include

#include

#include

域应

正文

到系

调用

到下

进程继续

int msgsnd(msqid,msgp,msgsz,msgflg)

int msqid;

void *msgp;

size_t msgsz;

int msgflg;说明:发送一个消息到由msqid指定消息队列标识号的消息队列.参数msgp指向一个用户定义的缓冲区,并且缓冲区的第一个

为长整型,指定消息类型,其他数据放在缓冲区的消息中其他

区内.下面是消息元素定义:

long mtype;

char mtext[];

mtype是一个整数,用于接收进程选择消息类型.mtext是一个长度为msgsz字节的任何正文,参数msgsz可从0

统允许的最大值间变化.msgflg指定操作行为:

.若(msgflg&IPC_NOWAIT)是真的,消息并不是被立即发送而

进程会立即返回..若(msgflg&IPC_NOWAIT)不是真的,则调用进程会被挂起直

面情况之一发生:

* 消息被发送出去.* 消息队列标志被系统删除.系统调用返回-1.* 调用进程接收到一个未被忽略的中断信号,调用

执行或被终止.调用成功后,对应指定的消息队列的相关结构做如下动作:

.消息数(msg_qnum)加1..消息队列最近发送进程号(msg_lspid)改为调用进程号..消息队列发送时间(msg_stime)改为当前系统时间.以上信息可用命令ipcs-a看到.返回值:成功则返回0,否则返回-1.

第二篇:多线程编程知识总结

多线程编程

一、问题的提出

1.1问题的引出

编写一个耗时的单线程程序:

新建一个基于对话框的应用程序SingleThread,在主对话框IDD_SINGLETHREAD_DIALOG添加一个按钮,ID为IDC_SLEEP_SIX_SECOND,标题为“延时6秒”,添加按钮的响应函数,代码如下:

void CSingleThreadDlg::OnSleepSixSecond(){ Sleep(6000);//延时6秒 } 编译并运行应用程序,单击“延时6秒”按钮,你就会发现在这6秒期间程序就象“死机”一样,不在响应其它消息。为了更好地处理这种耗时的操作,我们有必要学习——多线程编程。

1.2多线程概述

进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭。

线程是进程内部的一个执行单元。系统创建好进程后,实际上就启动执行了该进程的主执行线程,主执行线程以函数地址形式,比如说main或WinMain函数,将程序的启动点提供给Windows系统。主执行线程终止了,进程也就随之终止。

每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也较为广泛。

多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。要说明的一点是,对于单处理器(CPU)的,为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。由此可见,如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,反而会降低系统的性能。这一点在多线程编程时应该注意。

Win32 SDK函数支持进行多线程的程序设计,并提供了操作系统原理中的各种同步、互斥和临界区等操作。Visual C++中,使用MFC类库也实现了多线程的程序设计,使得多线程编程更加方便。1.3 Win32 API对多线程编程的支持

Win32 提供了一系列的API函数来完成线程的创建、挂起、恢复、终结以及通信等工作。下面将选取其中的一些重要函数进行说明。

1、HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadId);该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄,其中各参数说明如下:

lpThreadAttributes:指向一个 SECURITY_ATTRIBUTES 结构的指针,该结构决定了线程的安全属性,一般置为 NULL;

dwStackSize:指定了线程的堆栈深度,一般都设置为0;

lpStartAddress:表示新线程开始执行时代码所在函数的地址,即线程的起始地址。一般情况为(LPTHREAD_START_ROUTINE)ThreadFunc,ThreadFunc 是线程函数名;

lpParameter:指定了线程执行时传送给线程的32位参数,即线程函数的参数;

dwCreationFlags:控制线程创建的附加标志,可以取两种值。如果该参数为0,线程在被创建后就会立即开始执行;如果该参数为CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态,并不马上执行,直至函数ResumeThread被调用;

lpThreadId:该参数返回所创建线程的ID;

如果创建成功则返回线程的句柄,否则返回NULL。

2、DWORD SuspendThread(HANDLE hThread);该函数用于挂起指定的线程,如果函数执行成功,则线程的执行被终止。

3、DWORD ResumeThread(HANDLE hThread);该函数用于结束线程的挂起状态,执行线程。

4、VOID ExitThread(DWORD dwExitCode);该函数用于线程终结自身的执行,主要在线程的执行函数中被调用。其中参数dwExitCode用来设置线程的退出码。

5、BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);

一般情况下,线程运行结束之后,线程函数正常返回,但是应用程序可以调用TerminateThread强行终止某一线程的执行。各参数含义如下: hThread:将被终结的线程的句柄;

dwExitCode:用于指定线程的退出码。

使用TerminateThread()终止某个线程的执行是不安全的,可能会引起系统不稳定;虽然该函数立即终止线程的执行,但并不释放线程所占用的资源。因此,一般不建议使用该函数。

6、BOOL PostThreadMessage(DWORD idThread,UINT Msg,WPARAM wParam,LPARAM lParam);该函数将一条消息放入到指定线程的消息队列中,并且不等到消息被该线程处理时便返回。idThread:将接收消息的线程的ID;

Msg:指定用来发送的消息;

wParam:同消息有关的字参数;

lParam:同消息有关的长参数;

调用该函数时,如果即将接收消息的线程没有创建消息循环,则该函数执行失败。

1.4.Win32 API多线程编程例程

例程1 [MultiThread1] 一个简单的线程。注意事项:

 Volatile:关键字:

volatile是要求C++编译器不要自作聪明的把变量缓冲在寄存器里.因为该变量可能会被意外的修改。(多个线程或其他原因)

如从串口读数据的场合,把变量缓冲在寄存器里,下次去读寄存器就没有意义了.因为串口的数据可能随时会改变的.加锁访问用于多个线程的场合.在进入临界区时是肯定要加锁的.volatile也加上,以保证从内存中读取变量的值. 终止线程:

Windows终止线程运行的四种方法 终止线程运行

若要终止线程的运行,可以使用下面的方法:

• 线程函数返回(最好使用这种方法)。

• 通过调用 ExitThread 函数,线程将自行撤消(最好不要使用这种方法)。

• 同一个进程或另一个进程中的线程调用 TerminateThread 函数(应该避免使用这种方法)。

• 包含线程的进程终止运行(应该避免使用这种方法)。

下面将介绍终止线程运行的方法,并且说明线程终止运行时会出现什么情况。

 线程函数返回

始终都应该将线程设计成这样的形式,即当想要线程终止运行时,它们就能够返回。这是确保所有线程资源被正确地清除的唯一办法。

如果线程能够返回,就可以确保下列事项的实现:

• 在线程函数中创建的所有 C++ 对象均将通过它们的撤消函数正确地撤消。

• 操作系统将正确地释放线程堆栈使用的内存。

• 系统将线程的退出代码(在线程的内核对象中维护)设置为线程函数的返回值。

• 系统将递减线程内核对象的使用计数。 使用 ExitThread 函数

可以让线程调用 ExitThread 函数,以便强制线程终止运行:

VOID ExitThread(DWORD dwExitCode);

该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是,C++ 资源(如 C++ 类对象)将不被撤消。由于这个原因,最好从线程函数返回,而不是通过调用 ExitThread 来返回。

当然,可以使用 ExitThread 的 dwExitThread 参数告诉系统将线程的退出代码设置为什么。ExitThread 函数并不返回任何值,因为线程已经终止运行,不能执行更多的代码。 使用 TerminateThread 函数

调用 TerminateThread 函数也能够终止线程的运行:

BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode);

与 ExitThread 不同,ExitThread 总是撤消调用的线程,而 TerminateThread 能够撤消任何线程。hThread 参数用于标识被终止运行的线程的句柄。当线程终止运行时,它的退出代码成为你作为 dwExitCode 参数传递的值。同时,线程的内核对象的使用计数也被递减。

注意 TerminateThread 函数是异步运行的函数,也就是说,它告诉系统你想要线程终止运行,但是,当函数返回时,不能保证线程被撤消。如果需要确切地知道该线程已经终止运行,必须调用 WaitForSingleObject 或者类似的函数,传递线程的句柄。

设计良好的应用程序从来不使用这个函数,因为被终止运行的线程收不到它被撤消的通知。线程不能正确地清除,并且不能防止自己被撤消。

注意 当使用返回或调用 ExitThread 的方法撤消线程时,该线程的内存堆栈也被撤消。但是,如果使用 TerminateThread,那么在拥有线程的进程终止运行之前,系统不撤消该线程的堆栈。Microsoft故意用这种方法来实现 TerminateThread。如果其他仍然正在执行的线程要引用强制撤消的线程堆栈上的值,那么其他的线程就会出现访问违规的问题。如果将已经撤消的线程的堆栈留在内存中,那么其他线程就可以继续很好地运行。

此外,当线程终止运行时,DLL 通常接收通知。如果使用 TerminateThread 强迫线程终止,DLL 就不接收通知,这能阻止适当的清除,在进程终止运行时撤消线程。当线程终止运行时,会发生下列操作:

• 线程拥有的所有用户对象均被释放。在 Windows 中,大多数对象是由包含创建这些对象的线程的进程拥有的。但是一个线程拥有两个用户对象,即窗口和挂钩。当线程终止运行时,系统会自动撤消任何窗口,并且卸载线程创建的或安装的任何挂钩。其他对象只有在拥有线程的进程终止运行时才被撤消。

• 线程的退出代码从 STILL_ACTIVE 改为传递给 ExitThread 或 TerminateThread 的代码。

• 线程内核对象的状态变为已通知。

• 如果线程是进程中最后一个活动线程,系统也将进程视为已经终止运行。

• 线程内核对象的使用计数递减 1。

当一个线程终止运行时,在与它相关联的线程内核对象的所有未结束的引用关闭之前,该内核对象不会自动被释放。

一旦线程不再运行,系统中就没有别的线程能够处理该线程的句柄。然而别的线程可以调用 GetExitcodeThread 来检查由 hThread 标识的线程是否已经终止运行。如果它已经终止运行,则确定它的退出代码:

BOOL GetExitCodeThread(HANDLE hThread, PDOWRD pdwExitCode);退出代码的值在 pdwExitCode 指向的 DWORD 中返回。如果调用 GetExitCodeThread 时线程尚未终止运行,该函数就用 STILL_ACTIVE 标识符(定义为 0x103)填入 DWORD。如果该函数运行成功,便返回 TRUE。

 线程的定义:

例程2[MultiThread2] 传送一个一个整型的参数到一个线程中,以及如何等待一个线程完成处理。

DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);

hHandle:为要监视的对象(一般为同步对象,也可以是线程)的句柄;

dwMilliseconds:为hHandle对象所设置的超时值,单位为毫秒;

当在某一线程中调用该函数时,线程暂时挂起,系统监视hHandle所指向的对象的状态。如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。

例程3[MultiThread3] 传送一个结构体给一个线程函数,可以通过传送一个指向结构体的指针参数来完成。补充一点:如果你在void CMultiThread3Dlg::OnStart()函数中添加/* */语句,编译运行你就会发现进度条不进行刷新,主线程也停止了反应。什么原因呢?这是因为WaitForSingleObject函数等待子线程(ThreadFunc)结束时,导致了线程死锁。因为WaitForSingleObject函数会将主线程挂起(任何消息都得不到处理),而子线程ThreadFunc正在设置进度条,一直在等待主线程将刷新消息处理完毕返回才会检测通知事件。这样两个线程都在互相等待,死锁发生了,编程时应注意避免。

例程4[MultiThread4] 测试在Windows下最多可创建线程的数目。

二、MFC中的多线程开发

2.1 MFC对多线程编程的支持

MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。

工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务。

在MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程。两种重载函数原型和参数分别说明如下:

(1)CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,LPVOID pParam,nPriority=THREAD_PRIORITY_NORMAL,UINT nStackSize=0,DWORD dwCreateFlags=0,LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);

PfnThreadProc:指向工作者线程的执行函数的指针,线程函数原型必须声明如下: UINT ExecutingFunction(LPVOID pParam);请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。

pParam:传递给线程函数的一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略;

nPriority:线程的优先级。如果为0,则线程与其父线程具有相同的优先级;

nStackSize:线程为自己分配堆栈的大小,其单位为字节。如果nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小; dwCreateFlags:如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起;

lpSecurityAttrs:线程的安全属性指针,一般为NULL;

(2)CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,int nPriority=THREAD_PRIORITY_NORMAL,UINT nStackSize=0,DWORD dwCreateFlags=0,LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);

pThreadClass 是指向 CWinThread 的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等;其它参数的意义同形式1。使用函数的这个原型生成的线程也有消息机制,在以后的例子中我们将发现同主线程的机制几乎一样。下面对CWinThread类的数据成员及常用函数进行简要说明。

   m_hThread:当前线程的句柄;

m_nThreadID:当前线程的ID;

m_pMainWnd:指向应用程序主窗口的指针

virtual BOOL CWinThread::InitInstance();重载该函数以控制用户界面线程实例的初始化。初始化成功则返回非0值,否则返回0。用户界面线程经常重载该函数,工作者线程一般不使用InitInstance()。

virtual int CWinThread::ExitInstance();在线程终结前重载该函数进行一些必要的清理工作。该函数返回线程的退出码,0表示执行成功,非0值用来标识各种错误。同InitInstance()成员函数一样,该函数也只适用于用户界面线程。

2.2 MFC多线程编程实例

例程5 MultiThread5 为了与Win32 API对照,使用MFC 类库编程实现例程3 MultiThread3。

例程6 MultiThread6[用户界面线程]  创建用户界面线程的步骤:

1.使用ClassWizard创建类CWinThread的派生类(以CUIThread类为例)class CUIThread : public CWinThread { DECLARE_DYNCREATE(CUIThread)protected: CUIThread();// protected constructor used by dynamic creation

// Attributes public: // Operations public:

// Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CUIThread)public: virtual BOOL InitInstance();virtual int ExitInstance();//}}AFX_VIRTUAL // Implementation protected: virtual ~CUIThread();// Generated message map functions //{{AFX_MSG(CUIThread)

// NOTE-the ClassWizard will add and remove member functions here.//}}AFX_MSG

DECLARE_MESSAGE_MAP()};

2.重载函数InitInstance()和ExitInstance()。BOOL CUIThread::InitInstance(){ CFrameWnd* wnd=new CFrameWnd;wnd->Create(NULL,“UI Thread Window”);wnd->ShowWindow(SW_SHOW);wnd->UpdateWindow();m_pMainWnd=wnd;return TRUE;}

3.创建新的用户界面线程 void CUIThreadDlg::OnButton1(){

}

请注意以下两点:

A、在UIThreadDlg.cpp的开头加入语句: #include “UIThread.h” B、把UIThread.h中类CUIThread()的构造函数的特性由 protected 改为 public。CUIThread* pThread=new CUIThread();pThread->CreateThread();

用户界面线程的执行次序与应用程序主线程相同,首先调用用户界面线程类的InitInstance()函数,如果返回TRUE,继续调用线程的Run()函数,该函数的作用是运行一个标准的消息循环,并且当收到WM_QUIT消息后中断,在消息循环过程中,Run()函数检测到线程空闲时(没有消息),也将调用OnIdle()函数,最后Run()函数返回,MFC调用ExitInstance()函数清理资源。

你可以创建一个没有界面而有消息循环的线程,例如:你可以从CWinThread派生一个新类,在InitInstance函数中完成某项任务并返回FALSE,这表示仅执行InitInstance函数中的任务而不执行消息循环,你可以通过这种方法,完成一个工作者线程的功能。

三、线程间通讯

3.1通讯方式

一般而言,应用程序中的一个次要线程总是为主线程执行特定的任务,这样,主线程和次要线程间必定有一个信息传递的渠道,也就是主线程和次要线程间要进行通信。这种线程间的通信不但是难以避免的,而且在多线程编程中也是复杂和频繁的,下面将进行说明。

3.1.1使用全局变量进行通信

由于属于同一个进程的各个线程共享操作系统分配该进程的资源,故解决线程间通信最简单的一种方法是使用全局变量。对于标准类型的全局变量,建议使用volatile 修饰符,它告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。如果线程间所需传递的信息较复杂,可以定义一个结构,通过传递指向该结构的指针进行传递信息。

3.1.2使用自定义消息

可以在一个线程的执行函数中向另一个线程发送自定义的消息来达到通信的目的。一个线程向另外一个线程发送消息是通过操作系统实现的。利用Windows操作系统的消息驱动机制,当一个线程发出一条消息时,操作系统首先接收到该消息,然后把该消息转发给目标线程,接收消息的线程必须已经建立了消息循环。

3.2例程

例程GlobalObjectTest 该例程演示了如何利用全局变量进行通信

例程7[MultiThread7] 该例程演示了如何使用自定义消息进行线程间通信。首先,主线程向CCalculateThread线程发送消息WM_CALCULATE,CCalculateThread线程收到消息后进行计算,再向主线程发送WM_DISPLAY消息,主线程收到该消息后显示计算结果。步骤:

四、线程的同步

4.1基本概念

虽然多线程能给我们带来好处,但是也有不少问题需要解决。例如,对于像磁盘驱动器这样独占性系统资源,由于线程可以执行进程的任何代码段,且线程的运行是由系统调度自动完成的,具有一定的不确定性,因此就有可能出现两个线程同时对磁盘驱动器进行操作,从而出现操作错误;又例如,对于银行系统的计算机来说,可能使用一个线程来更新其用户数据库,而用另外一个线程来读取数据库以响应储户的需要,极有可能读数据库的线程读取的是未完全更新的数据库,因为可能在读的时候只有一部分数据被更新过。

使隶属于同一进程的各线程协调一致地工作称为线程的同步。MFC提供了多种同步对象,下面只介绍最常用的四种:

临界区(CCriticalSection)

事件(CEvent)

互斥量(CMutex)

信号量(CSemaphore)

通过这些类,可以比较容易地做到线程同步。

4.2使用 CCriticalSection 类

当多个线程访问一个独占性共享资源时,可以使用“临界区”对象。任一时刻只有一个线程可以拥有临界区对象,拥有临界区的线程可以访问被保护起来的资源或代码段,其他希望进入临界区的线程将被挂起等待,直到拥有临界区的线程放弃临界区时为止,这样就保证了不会在同一时刻出现多个线程访问共享资源。

CCriticalSection类的用法非常简单,步骤如下:

1.定义CCriticalSection类的一个全局对象(以使各个线程均能访问),如CCriticalSection critical_section;

2.在访问需要保护的资源或代码之前,调用CCriticalSection类的成员Lock()获得临界区对象: critical_section.Lock();3.在线程中调用该函数来使线程获得它所请求的临界区。如果此时没有其它线程占有临界区对象,则调用Lock()的线程获得临界区;否则,线程将被挂起,并放入到一个系统队列中等待,直到当前拥有临界区的线程释放了临界区时为止。

4.访问临界区完毕后,使用CCriticalSection的成员函数Unlock()来释放临界区:critical_section.Unlock();通俗讲,就是线程A执行到critical_section.Lock();语句时,如果其它线程(B)正在执行critical_section.Lock();语句后且critical_section.Unlock();语句前的语句时,线程A就会等待,直到线程B执行完critical_section.Unlock();语句,线程A才会继续执行。

例程8 MultiThread8 4.3使用 CEvent 类

CEvent 类提供了对事件的支持。事件是一个允许一个线程在某种情况发生时,唤醒另外一个线程的同步对象。例如在某些网络应用程序中,一个线程(记为A)负责监听通讯端口,另外一个线程(记为B)负责更新用户数据。通过使用CEvent 类,线程A可以通知线程B何时更新用户数据。每一个CEvent 对象可以有两种状态:有信号状态和无信号状态。线程监视位于其中的CEvent 类对象的状态,并在相应的时候采取相应的操作。

在MFC中,CEvent 类对象有两种类型:人工事件和自动事件。一个自动CEvent 对象在被至少一个线程释放后会自动返回到无信号状态;而人工事件对象获得信号后,释放可利用线程,但直到调用成员函数ReSetEvent()才将其设置为无信号状态。在创建CEvent 类的对象时,默认创建的是自动事件。CEvent 类的各成员函数的原型和参数说明如下:

1、CEvent(BOOL bInitiallyOwn=FALSE,BOOL bManualReset=FALSE,LPCTSTR lpszName=NULL,LPSECURITY_ATTRIBUTES lpsaAttribute=NULL);bInitiallyOwn:指定事件对象初始化状态,TRUE为有信号,FALSE为无信号;

bManualReset:指定要创建的事件是属于人工事件还是自动事件。TRUE为人工事件,FALSE为自动事件;

后两个参数一般设为NULL,在此不作过多说明。

2、BOOL CEvent::SetEvent();

将 CEvent 类对象的状态设置为有信号状态。如果事件是人工事件,则 CEvent 类对象保持为有信号状态,直到调用成员函数ResetEvent()将 其重新设为无信号状态时为止。如果CEvent 类对象为自动事件,则在SetEvent()将事件设置为有信号状态后,CEvent 类对象由系统自动重置为无信号状态。

如果该函数执行成功,则返回非零值,否则返回零。

3、BOOL CEvent::ResetEvent();

该函数将事件的状态设置为无信号状态,并保持该状态直至SetEvent()被调用时为止。由于自动事件是由系统自动重置,故自动事件不需要调用该函数。如果该函数执行成功,返回非零值,否则返回零。一般通过调用WaitForSingleObject函数来监视事件状态。前面已经介绍了该函数。由于语言描述的原因,CEvent 类的理解确实有些难度,只要通过下面例程,多看几遍就可理解。例程9 MultiThread9 仔细分析这两个线程函数, 就会正确理解CEvent 类。线程WriteD执行到 WaitForSingleObject(eventWriteD.m_hObject,INFINITE);处等待,直到事件eventWriteD为有信号该线程才往下执行,因为eventWriteD对象是自动事件,则当WaitForSingleObject()返回时,系统自动把eventWriteD对象重置为无信号状态。

4.4使用CMutex 类

互斥对象与临界区对象很像.互斥对象与临界区对象的不同在于:互斥对象可以在进程间使用,而临界区对象只能在同一进程的各线程间使用。当然,互斥对象也可以用于同一进程的各个线程间,但是在这种情况下,使用临界区会更节省系统资源,更有效率。

4.5使用CSemaphore 类

当需要一个计数器来限制可以使用某个线程的数目时,可以使用“信号量”对象。CSemaphore 类的对象保存了对当前访问某一指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程的数目。如果这个计数达到了零,则所有对这个CSemaphore 类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零时为止。一个线程被释放已访问了被保护的资源时,计数值减1;一个线程完成了对被控共享资源的访问时,计数值增1。这个被CSemaphore 类对象所控制的资源可以同时接受访问的最大线程数在该对象的构建函数中指定。

CSemaphore 类的构造函数原型及参数说明如下:

CSemaphore(LONG lInitialCount=1,LONG lMaxCount=1,LPCTSTR pstrName=NULL,LPSECURITY_ATTRIBUTES lpsaAttributes=NULL);lInitialCount:信号量对象的初始计数值,即可访问线程数目的初始值;

lMaxCount:信号量对象计数值的最大值,该参数决定了同一时刻可访问由信号量保护的资源的线程最大数目;

后两个参数在同一进程中使用一般为NULL,不作过多讨论;

在用CSemaphore 类的构造函数创建信号量对象时要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时,则说明当前占用资源的线程数已经达到了所允许的最大数目,不能再允许其它线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源数加1。例程10 MultiThread10 为了文件中能够正确使用同步类,在文件开头添加: #include “afxmt.h” 定义信号量对象和一个字符数组,为了能够在不同线程间使用,定义为全局变量:CSemaphore semaphoreWrite(2,2);//资源最多访问线程2个,当前可访问线程数2个

在信号量对象有信号的状态下,线程执行到WaitForSingleObject语句处继续执行,同时可用线程数减1;若线程执行到WaitForSingleObject语句时信号量对象无信号,线程就在这里等待,直到信号量对象有信号线程才往下执行。

第三篇:C++编程知识总结

1.数组

1.1数组定义时的注意点

1在C++中不提供可变化大小的数组,○即数组定义中的常量表达式不能包含变量。(来源:C++书6.1.1)

int n;cin>>n;float t[n];上例在定义数组t时,变量n没有确定的值,即在程序执行之前,无法知道数组t的元素个数,所以这种声明不被允许。但是可以用new动态分配,如: int n;cin>>n;float *t;t=new float[n];

2在定义数组时,可以不直接指定数组的大小,由C++编译器根据初值表中元素的个数来自○动确定数组元素的个数。例如: int z[]={0,1,2,3,4,5,6,7,8} 3C++语言规定只能对数组中的元素进行赋值或引用,不能把整个数组作为一个整体进行赋○值或引用。(2.3是一个实例)(来源:C++书4同类型的数组之间不能相互赋值 ○如int a[5],b[5];a=b;//错误

strcpy(b,a);//正确

6.1.1)

1.2数组和指针的关系(来源:C++书8.2节8.2.1)

char s[5];在C++中说明了一个数组后,数组名可以作为一个指针来使用,因此s可作为一个指针使用(但它不同于指针,不能赋值运算、算术运算等)。

2.字符数组

2.1输入字符数据 char c;cin>>c;// cin不能将输入的空格赋给字符型变量。

cin.get();//可获得键盘上输入的每一个字符,包括空格和回车键。

2.2字符数组的输入/输出(来源:C++书6.2.4)2.2.1逐个字符输入 char c[10];for(int i=0;i<10;i++)cin>>c[i];2.2.2字符串输入 方法1 char c[10];cin>>c;//即在输入输出时只给数组名

此法在输入字符串时,遇到空格和回车就认为一个字符结束。方法2 cin.getline(字符数组名,允许输入的最大字符个数)此法可把输入的一行作为一个字符串送到字符数组中。

2.3字符数组和字符指针的初始化 2.3.1字符数组初始化 char tx[5]=“";2.3.2字符指针初始化 char *ptx=new char[5];ptx[0]='';2.4字符串赋值

方法1 char tx[4]=”abcd“;方法2 char tx[4];//tx=”abcd“;//错误,tx是数组名,不分配内存空间,不可以进行赋值操作;但是数组名可当指针使用(C++书8.2.1)。strcpy(tx,”abcd“);以上两种方法是数组

方法3是指向数组的指针方法 方法3 char *tx;tx=new char[4];tx=”abcd“ 方法4 char *tx=”abcde“;//这相当于根据数组元素的个数,确定数组的大小。tx指针指向这个数组。

//下面实例告诉我们,不仅字符数组与字符指针有区别,用new给定内存空间大小的字符指针与没给定内存空间大小的字符指针也是有区别的 voidmain(){

/*char s[6]=”“;

strcpy(s,”abcd“);cout<

/*char *s=new char[5];strcpy(s,”abcd“);

} cout<

char *s=”“;//分配了内存空间,但不知道大小 strcpy(s,”abcd“);//错误,使用时要注意!!cout<

3.指针

3.1指针可执行的运算

指针可以进行赋值运算、算术运算、关系运算。

1可以将与指针变量同类型的任一变量的地址赋给指针○2在C++中,可以(1)赋值运算:○

3同类型的指针变量之间可以将0赋给任一指针变量,其含义是初始化指针变量,使其为空○相互赋值,不同类型的经强制转换后也可以,通常也没意义。(2)算术运算:指针变量执行“++”或“——”,其含义是使指针变量指向下一个或上一个元素

3.2指针和数组(同1.2)3.3指向数组的指针变量

char(*p)[10];(*p)指明p是一个指针变量,再与[10]结合,表示该指针变量所指向的数据是一个一维数组,该数组由10个元素组成。3.4指针数组

由若干个同类型的指针所组成的数组称为指针数组,数组的每个元素都是一个指针变量。定义指针数组的格式:如char *p[10];由于“[]”的优先级比“*”高,p[10]构成一个数组,再与“*”结合,指明是一个指针数组。3.5指向指针的指针变量 char **pp;3.6 new运算符

注意点:

用new运算符分配的内存空间的指针值必须保存起来,以便于delete运算符归还已动态分配的内存,否则会出现不可预测的错误。3.6.1指向数组的指针 char* m_p1;m_p1=new char[10];//指针m_p1指向含有10个元素的数组空间。for(i=0;i<10;i++)m_p2[i]表示这10个数组元素。

voidmain(){ char *p1;//char b;p1=newchar[5];

//p1=”abcde“;//直接给p1赋字符串,下面for循环中是给每个元素赋值。for(inti=0;i<5;i++){ //b='c';

} p1[i]='a';//p1[i]是数组元素,不是指针

cout<

3.6.2指向指针的指针变量 char**m_p2;//指向指针的指针

m_p2=new char*[10];//指针m_p2指向含有10个元素的指针数组。for(i=0;i<10;i++)m_p2[i]表示这10个指针。

void main(){ char *p1;char **pp;p1=new char[5];pp=new char*[5];for(int i=0;i<5;i++){

p1[i]='a';=&p1[i];//pp[i]是指针

cout<

cout<

3.7 delete运算符

delete释放的不是指针本身,而是指针所指的对象。

4.容器类std::string #include #include

int main(int argc, char * argv[]){

std::string str=”abc“;

std::string::iterator cit=str.begin();

for(;cit!=NULL;++cit)//NULL比较,我估计肯定不对,虽然你说是可以通过编译

{

std::cout<<*cit<

}

return 0;}

4.容器类Vector 4.1迭代器和指针的区别

有时需要使用指向vector的一个指针,我们可以这样来做。

vector v;

表达式v[0]生产一个指向vector中第一个元素的引用,所以,&v[0]是指向那个首元素的指针。vector中的元素被C++标准限定为存储在连续内存中,就像是一个数组。

如果你在一个不好的环境中,他们会告诉你说可以用v.begin()代替&v[0],因为(这些讨厌的家伙将会告诉你)begin返回指向vector内部的迭代器,而对于vector,其迭代器实际上是指针。那经常是正确的,但正如条款50所说,并不总是如此,你不该依赖于此。

begin的返回类型是iterator,而不是一个指针,当你需要一个指向vector内部数据的指针时绝不该使用begin。如果你基于某些原因决定键入v.begin(),就应该键入&*v.begin(),因为这将会产生和&v[0]相同的指针。

这表明迭代器的内容*v.begin()才是vector中第一个元素。

4.2 容器vector的函数clear()

清空vector里所有的元素。因此,如AMProcessList析构函数里一个个删除vector中所有的元素是多此一举。

5.关键字operator 它是说明符,用于重载运算符。

6.函数可以将一个处理的结果值通过函数的Return语句返回,也可以通过参数将处理的多个结果带给调用者。

C++语言在处理函数调用时,参数是自右向左依次入栈的

7.类的前置声明

8.Const char* Bjarne在他的The C++ Programming Language里面给出过一个助记的方法: 把一个声明从右向左读。

char * constcp;(* 读成 pointer to)cp is a const pointer to char--->cp是一个指向字符char的固定指针

const char * ptr;ptr is a pointer to const char;--->ptr是一个指向固定字符char的指针

char const * p;--->无此形式 也就是说,cp和ptr都是指针,cp的值是不可改变的cp指向的内容是可变的;而ptr的值是可以改变的,ptr指向的内容是不可变的

9.String转化为Constchar*,Const char*转化为char* 1.在string里面string.c_str()函数把string转换为了const char*.○代码如下:

stringa=”abcd“;const char*p=a.c_str();2const_cast将const char*转换为char*.○ char*p=const_cast(a.c_str());

10.初始化

char *p;char *s=”“;char *t=NULL;p没分配内存,s分配了内存,t为空,11.变量的初始化 1指针需要初始化; ○2基本数据类型声明的变量需要初始化;如double m_dvalue;m_dvalue=0; ○3类声明的对象不需要初始化。○

12.派生类中的一般成员函数和虚函数 classA { public: voidsolid(){cout<<”基类实函数“<<'n';} virtualvoidvir(){cout<<”基类虚函数“<<'n';} };classAA:publicA { public: voidsolid(){cout<<”派生类实函数“<<'n';} virtualvoidvir(){cout<<”派生类虚函数“<<'n';} };

voidmain(){ A* a=newA;AA* aa=newAA;a=aa;

a->vir();//vir()是虚函数。它是运行时的多态性,即在程序运行时,根据具体的执行情况来动态的确定。因此输出”派生类虚函数“,而不是“基类虚函数”

a->solid();//solid()是一般成员函数。它是编译时的多态性,即程序编译时就觉得会调用哪个函数。因为a是A类对象的指针,即使派生类AA对象的指针aa赋给a,在编译是已经觉得调用基类A的solid函数,因此输出“基类虚函数”而不是“派生类虚函数”

aa->vir();aa->solid();

aa->A::solid();aa->A::vir();}

Button newBtn = new Button();newBtn.Location = new System.Drawing.Point(128, 110);newBtn.Name = ”newBtn“;newBtn.Size = new System.Drawing.Size(75, 23);newBtn.Text = ”button2";newBtn.UseVisualStyleBackColor = true;this.Controls.Add(newBtn);

///清除新生成的Btn this.Controls.Remove(newBtn);

CButton* CTextEditorView::NewMyButton(int nID,CRect rect,int nStyle){ CString m_Caption;m_Caption.LoadString(nID);//取按钮标题 CButton *p_Button = new CButton();ASSERT_VALID(p_Button);p_Button->Create(m_Caption, WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | nStyle, rect, this, nID);//创建按钮 return p_Button;}

第四篇:编程知识总结C难点总结

《编程知识总结》—C#难点总结

1.编写菜单中对应子菜单项的消息响应函数,考虑的方面很多,例 如,当前打开一个文件,此文件已经被修改,如果用户需要新建 或者打开另外一个文件,程序要询问用户是否保存当前的文件。

问题的解决:先用纸张写一份详细的业务流程图,在之后的编写 过程中,按照业务流程的规定进行功能的逐步实现。

2.编写判断是否要提醒用户保存的程序,判断是否提醒保存的根据 是什么。我的判断根据验证文件内容是否被修改,如果被修改,提醒用户保存。如果被保存的文件没有文件路径,调用另存为的 响应函数进行执行。

3.无法关闭窗体,当我响应文件菜单下退出菜单项的时候,如果使 用 Close()函数的话,可以就直接退出,但是当我需要以同样的 方式响应右上角关闭按钮的话,事件信息就会进入死循环,而且 永远不会结束。所以,在关闭窗体的时候,目前我用的就是 Application.exit();这个函数,强制性终结这个窗口

4.保存文件,我们打开文件和直接输入信息的文件的情况,对于保 存来说是不一样的,因为打开的时候,应该是直接保存到指定的 文件中,而直接输入的应该提示保存,通过另存为的方式保存。5.文件的删除。C#的 textBox 没有直接为我们提供文本删除的函数,我弄了许久,后来,无意间突然发现通过已经选择的字符串的下 标来操作,就可以删除文件了,具体操作就是,先获取被选择文 件的前半部分,在获取后半部分,而略过中间被选中的部分即可。6.开始的时候没有使用 Using System.IO,系统报错;

7.由于在 MessageBox.Show 后没有写 return 导致错误;

8.初步创建简易记事本后点击打开文件选项并不显示文件的内容,且编译器报错了,是由于没有判断文件名为空,且文件的完整路 径名获取的不准确,将文件名为空的情况及其文件的完整路径名 获取之后,读取文件正确;

9.首先,我在添加单击“新建”响应的代码时,单击进去,结果代 码全部不能运行,必须是双击。

10.获取文件名时,文件名不需要特别处理,程序内部直接处理好的,我在这里画蛇添足了。

11.操作对象必须指代明确,不如要出错。

12.在用 WinForm 做文本文档的时候,在“新建文本文档”项的代码 中,需要在项目中新建一个 new 窗体作为“新建文本文档”,在 form1 的代码中写如下代码: Form frm = new NewForm();frm.Show();this.Hide();然后在 Form2 中下如同样的代码即可。

13.还发现“保存”与“另存为”项的代码编写没有任何差异,考虑 并修改程序无果。代码如下: private void 保存SToolStripMenuItem_Click(object sender, EventArgs e)// { DialogResult save = saveFileD

ialog1.ShowDialog();

if

(save

== DialogResult.OK){ string filepath = saveFileDialog1.FileName;//FileInfo fi = new FileInfo(filepath);string content = richTextBox1.Text;File.WriteAllText(filepath, content, Encoding.Default);} } 14.在保存的时候必须输入保存路径才能正常执行,没有实现文本文 档的基本保存功能。并且在打开一个原本有内容的文本文档之后,加以修改再保存就不能保存新的内容。

15.获取 TextBox 中获取一行的值,使用 TextBox 的 Lines 属性来获 取

16.做删除操作时,由 MessageBoxv.show 来弹出一个确认窗体,把返 回的值赋给一个 DialogResult 类型的变量

17.在复制文件时,CopyTo 函数将第二个参数置为 TRUE 则在复制时,如果文件存在会被覆盖掉。

18.在向文件写入数据时,使用 Encoding 的 Default 的属性可避免输 入中文乱码问题。19.关于上午的文件编程,刚开始对程序要执行的具体操作不够明确,在编程时,功能实现的矛盾性问题很多,其问题在于编程前分析 不够到位;

20.文件编程中起先将打开文件定位为文件夹,导致弹出对话框显示 “不存在此文件”; 21.File.WriteAllLines(),无法将临时修改后的文件信息进行正 确保存,文本没有实现正确的回车换行;File.WriteAllText()能实现;

22.this.Close()无法实现程序的结束 Close()方法是关闭 Form 23.当创建了控件的响应事件后,修改控件的 name 属性,响应事件 名为改变,但并不影响进程的进行,要修改方法名应删除该响应 方法,双击控件,重新获得响应事件。

24.代码经常会出现命名不规范的情况,同时没有写注释的习惯

25.经常会丢掉方法后的括号,如 savedialog1.showdialog()26.一 些 方 法 中 的 细 微 差 别 如 File.Write.AllLine 27.对于异常的处理经常忽略,导致程序的容错处理比较差。

28.当编辑框改变时有两种情况,一种是打开,一种是编辑,当打开 时不应该记录改变标志。解决的办法:做一个打开标志,当打开操作时,赋值为 true,在 文本框的改变函数中判断,如果是打开操作,将打开操作之位 false,同时文本改变标志值为 false

29.此处如何把长整形变量赋值给一个 string 类型? 使用 ToString()30.如何在文本框上添加滚动条。设置 TextBox.ScrollBars

31.textbox 控件大小跟窗体一起变化:刚开始想通过相应窗体大小 改变事件的办法解决,后来通过其他同学得知可以通过更改 textbox 的 Dock 属性(改为 Fill)来解决。

32.当 textbox 空间的文本改变时,进行新建、打开、退出等操作时 应提示用户是否保存更改,通过设置文件更改标志(flag)并相 File.WriteAllText 和 响 textbox 的 textchanged 事件来解决。

33.由于需要提示用户保存更改,刚

开始时代码控制结构写的很复杂,连自己也有点难理解。后来用调用“保存”事件的响应函数(刚 开始一位不能这样)后才解决这一问题。

34.由于刚开始做,命名方面不是很规范,导致了变量、方法和类的 名字不易区分,不能一目了然,间接增加了编写程序所需的时间。解决方法是在编写程序中时刻遵守命名规范。

35.在对文件操作时,文件的路径经常出错,原因是少了转换为绝对 路径的@。

36.对文件注释太少,经常导致前面编过的代码过一段时间就完全看 不懂了,所以,在编程中也要养成随时加注释的习惯,其实这样 做表面上是增加了时间,统筹来看,这样做既方便了自己日后修 改程序,又为阅读我们程序的人提供了便利。

37.文件操作经常出错,程序无法运行,要因此要异常处理,比如说 用 try,catch 对异常处理。

38.在昨天上午的文件操作中,第一个问题就是路径的合法性检测。由于不知道 c#是否存在像 c++ access()一样的方法。最后使用 OpenFileDialog 来限定用户输入。可以使用正则表达式验证 39.再有就是文件操作异常处理。C#的异常处理还是很方便的,昨天 的经验是只要使用 try catch 语句,在 catch 中不作任何处理程 序也不会当掉。

40.MessageBox.Show(“你还没有保存上次的修改,是否保存修改?”, “保存”, MessageBoxButtons.YesNoCancel);选项的判断 问 同 学 得 : 返 回 值 为 DialogResult.No DialogResult.Yes DialogResult.Cancel 41.2 . private

void

Form1_FormClosing(object

sender, FormClosingEventArgs e)中如何取消关闭 问同学得:e.Cancel = true;42.如何实现修改后 “新建”“打开”等提示保存 点打开保存后记 录存储路径

43.新建 2 个成员记录:private string Pathname;private bool savetxt=true;

44.如何在两个窗体间传递值。通过向同学和老师询问,知道传值有 很多种方法,我用的就是调用构造函数,可是老师也说了如果值 比较多的时候,这种方法还是有缺陷的。使用构造函数可以,只是也用对象会好一些。

45.当汉字改成英文时,函数中仍然是汉字,如果直接在 program.cs 中改容易出问题,怎么改? 利用 VS 的代码重构

46.如何改窗体的默认主题样式 可以使用第三方控件完成

47.TextBox 中文本值改变时的响应事件(可以添加事件响应函数)。

48.文 件 打 开 中 将 文 件 写 到 textbox 中 : 定 义 string[] 调 用 File.ReadAllLines 函数将文件内容逐行读入到字符串数组中,然后利用 for 循环将文件内容逐行写出到控件中。

49.填写邮箱和文件路径的时候需要有正确的规格,因为之前接触过 c#不知道该如何完成,后来在经过询问他人和网上资料查询得知 用正则表达式来规范正确

的格式,便避免了这方面因格式而导致 的问题。

50.当输入多行字符串,保存过后进行查看时,当有换行就会出现黑 乎乎的正方形。换行:rn

51.假设某个事件已经做好,可以作为一个模块使用,那么如何在其 他代码中调用此模块

52.记事本的查找、替换模块不知道如何实现 需要使用字符串函数

53.打开 txt 文件的时候中文没有乱码但是保持了之后在打开就有了 乱码 实现:原先是因为保持的时候没有使用 encoding.default 54.保存时文件不能自动保存文件 ?自动保存文件

55.在做简易记事本的途中,创建快捷键的时候遇到一点问题,当在 杂项的 Shortcutkeys 中选择 shift 的时候,它会报属性值无效的 错误,后来无奈之下选择了 Ctrl,居然就正确了

56.还有在 TextBox 的属性 Dock 选择了 fill 的时候,依然没有变成 全部填充,真是百思不得其解!需要将 TextBox 设置为多行文本

57.发现了一种比较简便的方法: 已经做完的某个控件出发的事件,可以作为一个法方来使用。例: private void SaveMenu_Click(object sender, EventArgs e){ //判断文件是否已有路径,若无,选定路径,若有,保存在最新操作的文件路径下 if(path == string.Empty){ if(SolgSave.ShowDialog()== DialogResult.OK){ path = SolgSave.FileName;File.AppendAllText(path, txtEdit.Text, Encoding.Default);} } else { File.AppendAllText(path, txtEdit.Text, Encoding.Default);} } private void ExitMenu_Click(object sender, EventArgs e){ if(txtEdit.Text!= string.Empty){ DialogResult dr = MessageBox.Show(“是否保存当前文本至

”+path,“

”, MessageBoxButtons.OKCancel,MessageBoxIcon.Warning);if(dr == DialogResult.OK){ //使用保存文本方法 this.SaveMenu_Click(sender,e);//SaveMenu 的单击事件 } } //退出程序 Application.Exit();}

58.RichTextBox 与 TextBox 的换行区别问题。在 TextBox 中是以rn 为换行标志,而在 RichTextBox 中是以n 为换行标志。我在 C#程序中使用 RichTextBox 将其文本内容保存为文本文件,即(*.txt)类型的文件时,文本不能正常的换行,且出现一些乱码。这是由于文本文件是以rn 作为换行符,当我改用 TextBox 时,就 没有这样的问题了。

59.文本的选中问题。SelectedText 这一属性可以指向当前控件中选中的文本,60.通常我们会发现,保存操作在文本没有保存的情况下时才会出现 保存文件对话框,当文本已经保存过时,新增的文本会保存到原 文件中,这两个操作需要对文本是否保存过进行判断。我采用了 判断路径的方法对我文本是否保存过进行了判断,当文本路径存 在时,表明文本已经保存过,反之,则没有保存过。

第五篇:多文件编程方法总结

编写一个程序如下:

#include void delay(unsigned char k){

}

void main(){

}

/*此时程序较少看上去也不会觉得乱,但是如果程序很多就比较乱了。作如下多文件编程,看着方便且易于移植*/

我们需要建立如下文件,并放在同一目录下:

1.main.c 2.main.h 3.delay.c 4.delay.h

此时,main.c中写: #include #include void main(){

while(1){ } delay(100);while(1){ } delay(100);unsigned char i,j;for(i=k;i>0;i--)for(j=110;j>0;j--);} main.h中: #ifndef _main_h #define _main_h #include #include

#endif

delay.c中: #include

void delay(unsigned char k){

} unsigned char i,j;for(i=k;i>0;i--)for(j=110;j>0;j--);delay.h中: #ifndef _delay_h #define _delay_h

void delay(unsigned char k);

#endif

这样就可以实现同样的功能了。

这就是一般的格式,大家在学习的过程中可以可以直接套用.这样以后一直也很方便.

下载多进程编程的相关知识总结word格式文档
下载多进程编程的相关知识总结.doc
将本文档下载到自己电脑,方便修改和收藏,请勿使用迅雷等下载。
点此处下载文档

文档为doc格式


声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:645879355@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。

相关范文推荐

    编程心得(精选多篇)

    由于大学的时候是网络方向的,没大接触编程,来到这个学校就开始自学C++,但是感觉不知道具体该做什么。听人说只学习C++的理论知识是不可能做出什么实际的东西的,而且VC和C++不是......

    刀具和编程总结

    ① 白钢刀(即高速钢刀具)因其通体银白色而得名,主要用于直壁加工。白钢刀价格便宜,但切削寿命短、吃刀量小、进给速度低、加工效率低,在数控加工中较少使用。 ② 飞刀(即镶嵌式刀......

    编程题总结(范文大全)

    C作业汇总 1. short a,b=32767; /*short类型在内存中占2B*/ a=b+1; 问:a的值是多少?并分析原因。 2. 有一4位数整数,假设用abcd表示,请把这个4位数的每个数位用表达式表示出来......

    数据库编程总结(推荐)

    数据库编程总结 当前各种主流数据库有很多,包括Oracle, MS SQL Server, Sybase, Informix, MySQL, DB2, Interbase / Firebird, PostgreSQL, SQLite, SAP/DB, TimesTen, MS A......

    一点Duilib编程总结

    一点Duilib编程总结 1.duilib简介 duilib是一个开源的DirectUI界面库,简洁但是功能强大。而且还是BSD的license,所以即便是在商业上,大家也可以安心使用。 现在大家可以从这个......

    Mudos编程总结[推荐]

    Mudos编程总结1,Mudos系统调用系统 MudLib系统文件 的过程和一些特点Mudos启动以后先要寻找一个配置文件,用来配置MudLib文件系统的一些信息(这里时config.cfg),找不到就无法启......

    描述性编程总结(范文)

    一、描述性编程 1、 QTP的运行原理 封装被测对象(TO)到对象仓库 对比对象仓库里的对象属性(TO)和运行时的真实被测对象的属性(RO) 对比一致后,找得到相应的对象(RO),按照脚本......

    编程题总结

    大家必须掌握每种类型的1-3题。题号考点要求48、 将两个两位数的整数合并成一个整数 65、 两个正整数合并成一个整数 71、 两个正整数合并成一个整数 77、 两个正整数合并成......