当前位置:文档之家› c程序设计教程课件完整版

c程序设计教程课件完整版

c程序设计教程课件完整版
c程序设计教程课件完整版

c程序设计教程课件集团标准化办公室:[VV986T-J682P28-JP266L8-68PNN]

本文由x4168138贡献

ppt文档可能在WAP端浏览体验不佳。建议您优先选择TXT,或下载源文件到

本机查看。

第9章多线程

本章内容

9.1 一个简单的多线程应用程序 9.2 线程及其实现方法 9.3 线程的同步控制9.4 线程池 9.5 线程对控件的访问

9.1 一个简单的多线程应用程序 C#程序设计教程——蒙祖强编着

本小节创建的多线程应用程序一共包含两个线程,本小节创建的多线程应用

程序一共包含两个线程,这两个线程并发地在屏幕上输出相关的字符串。发地在屏幕上输出相关的字符串。程序的关键代码如下:程序的关键代码如下:

class A { public static int n = 0; public void f() { for (int i = 0;

i < 10; i ) { Console.WriteLine("f()在输出:{0}", A.n); 在输出:在输出

A.n ; 让当前线程 Thread.Sleep(100); 睡眠100毫秒睡眠毫秒 } } }

9.1 一个简单的多线程应用程序 C#程序设计教程——蒙祖强编着

class B { public static void g() { for (int i = 0; i < 10; i )

{ Console.WriteLine("g()在输出:{0}", A.n); 在输出:在输出 A.n ; 让当

前线程 Thread.Sleep(100); 睡眠100毫秒睡眠毫秒 } } } static void

Main(string[] args) { A a = new A(); ThreadStart thst1 = new

ThreadStart(a.f); 建立委托对象,建立委托对象,使之创建线程thst1 创建

线程每个线程实际上是可见,与给定的方法相关联类可见,每个线程实际上

是Thread类 ThreadStart thst2 = new ThreadStart(B.g); 的对象,它是通过Thread类的构造的对象,它是通过类的构造 Thread th1 = new Thread(thst1); 函数来创建;函数来创建;并且每个线程都与既 Thread th2 = new

Thread(thst2); 定的方法相关联 th1.Start(); 启动线程创建线程thst2 创建

线程 th2.Start(); Console.ReadKey(); }

——由这个例子可以看到,Thread类、委托类型由这个例子可以看到,由

这个例子可以看到类委托类型ThreadStart等——执行线程和th2实际上是执行方法执行线程th1和实际上是执行方法a.f()和方法和方法B.g()。等。

执行线程实际上是执行方法和方法是多线程程序设计中的核心内容。是多线程程序设计中的核心内容。

9.2 线程及其实现方法

9.2.1 线程的概念

C#程序设计教程——蒙祖强编着

线程的概念与程序、进程的概念密切相关。线程的概念与程序、进程的概念

密切相关。程序是程序员编写的静态代码文本。程序是程序员编写的静态代码文本。是程序员编写的静态代码文本进程则是程序的一次动态执行过程,进程则

是程序的一次动态执行过程,进程运行时需要占用装载则是程序的一次动态执行

过程程序代码(编译后的可执行代码)程序代码(编译后的可执行代码)以及存放其所需数据的内存空间和其他的机器资源(如文件等),),当进程终止时这些内存空间间和其他的机器资源(如文件等),当进程终止时这些内存空间和资

源也随之释放。显然,同一个程序,和资源也随之释放。显然,同一个程序,它可以被多次加载到不同的内存区域中、使用不同的机器资源,同的内存区域中、使用不同的机器资源,从而形成多个不同的进即一个程序可以形成多个进程。程,即一个程序可以形成多个进程。一个进程是由多个执行单元组成,每个执行单元就是一个线程,一个进程是由多个执行单元组成,每个执行单元就是一个线程,即进程是由多个线程组成。即进程是由多个线程组成。每个线程都共享着其进程所占用的内存空间和机器资源(如堆栈、),实际上存空间和机器资源(如堆栈、CPU、寄存器等),实际上,一个、寄存器等),实际上,线程是一组机器指令以及它共享的内存和资源是一组机器指令以及它共享的内存和资源。线程是一组机器指令以及它共享的内存和资源。

9.2 线程及其实现方法

9.2.1 线程的概念线程和进程的主要区别在于:线程和进程的主要区别在于:

C#程序设计教程——蒙祖强编着

进程是由多个线程组成,即线程是进程的一个组成部分。进程是由多个线程组成,即线程是进程的一个组成部分。线程的划分尺度小,具有较高的并发效率。线程的划分尺度小,具有较高的并发效率。进程独占相应的内存和资源(其他进程不能使用),线程则是进程独占相应的内存和资源(其他进程不能使用),线程则是),共享进程所拥有的内存和资源(其他线程也可以使用),),从而极共享进程所拥有的内存和资源(其他线程也可以使用),从而极大地提高运行效率。大地提高运行效率。进程提供多个线程执行控制,而每个线程只能有一个运行入口、进程提供多个线程执行控制,而每个线程只能有一个运行入口、顺序执行序列和出口(线序”执行)。顺序执行序列和出口(“线序”执行)。进程可以独立执行,但线程不能独立执行,进程可以独立执行,但线程不能独立执行,而必须依赖于进程所提供的环境。所提供的环境。 9.2 线程及其实现方法

9.2.2 线程的实现方法

C#程序设计教程——蒙祖强编着

线程的创建和应用主要是由Thread类和线程的创建和应用主要是由类和ThreadStart委托来实现。委托来实现。类和委托来实现 Thread类的构造函数和主要方法说明如下:类的构造函数和主要方法说明如下:类的构造函数和主要方法说明如下构造函数 Thread类构造函数的作用是用于创建线程,它主要有两个重载版类构造函数的作用是用于创建线程,类构造函数的作用是用于创建线程本:构造函数创建的线程关联没

有参数的方法

public Thread(ThreadStart start) public

Thread(ParameterizedThreadStart start) ——每个线程都必须关联一个无返回类型的方法(称为线程方每个线程都必须关联一个无返回类型的方法(每个线程都必须关联一个无返回类型的方法),如果关联的方法无参数类型或构造函数创建的线程关联带法),如果关联的方法无参数,则用第一个构造函数创建线程;其中,参数参数start是ThreadStart类型或类型或

ParameterizedThreadStart 其中,如果关联的方法无参数,则用第一个构造函数

创建线程;是一个object类型参数的方法一个类型参数的方法如果关联的方法带一个参数,则用第二个构造函数创建线程。如果关联的方法带一个参数,则

用第二个构造函数创建线程。类型的变量。这两种委托类型的声明如下:类型的变量。这两种委托类型的声明如下:——当然,线程间数据的传递也可以使用对

象的成员变量或方法当然,当然 public delegate void ThreadStart() 来实现,这也是常用的方法,但要在线程的同步控制下进行。来实现,这也是常用的

方法,但要在线程的同步控制下进行。 public delegate void ParameterizedThreadStart(Object obj)

9.2 线程及其实现方法

9.2.2 线程的实现方法

C#程序设计教程——蒙祖强编着

例如,下面代码先定义类A,它有两个静态方法f()和g(),其中后例如,下

面代码先定义类,它有两个静态方法和,者带有object类型的参数:类型

的参数obj:者带有类型的参数 class A { public static void f()

{ Console.WriteLine("这是关联方法的线程这是关联方法f()的线程这是关联

方法的线程"); } public static void g(object obj) { Console.WriteLine("

这是关联方法的线程:这是关联方法g()的线程:这是关联方法的线程 "

obj.ToString()); } }

9.2 线程及其实现方法

9.2.2 线程的实现方法

C#程序设计教程——蒙祖强编着

然后用上述两种构造函数分别通过委托类型ThreadStart和然后用上述两种

构造函数分别通过委托类型和 ParameterizedThreadStart创建线程和th2,它

们分别关联方法创建线程th1和,创建线程 f()和g(),并(在Main()方法

中)执行它们:方法中)和,方法中执行它们:

ThreadStart thst = new ThreadStart(A.f); ParameterizedThreadStart pthst = new ParameterizedThreadStart(A.g); Thread th1 = new Thread(thst); //关联方法关联方法f() 关联方法 Thread th2 = new Thread(pthst); //关联

方法,带一个参数关联方法g(),关联方法 th1.Start(); //启动线程(执行

方法)启动线程th1(执行方法f())启动线程 th2.Start(200); //启动线程(执行方法,并将启动线程th2(执行方法g(),并将200作为参数值传启动线程作为参数值传给该方法)给该方法)

执行后,将输出如下结果:执行后,将输出如下结果:需要注意的是,线程关联的方法必须与所使用的委托类型相一致,需要注意的是,线程关联的方法必

须与所使用的委托类型相一致,这是关联方法f()的线程这是关联方法返回类

型必须为void,且在创建委托对象时关联的方法必须是已返回类型必须为的线程,这是关联方法g()的线程的线程:这是关联方法的线程:200 经确定了的。这些方法通常是类的静态方法和对象的方法。经确定了的。这些方法通常是

类的静态方法和对象的方法。

9.2 线程及其实现方法

9.2.2 线程的实现方法

C#程序设计教程——蒙祖强编着

Start()方法方法该方法的作用是用于启动已经创建的线程,线程将进入Running 该方法的作用是用于启动已经创建的线程,线程将进入状态(线程刚创建完时是处于Unstarted状态)。状态)。状态(线程刚创建完时是处于状态例如,以下代码是在前面代码中已经出现过的调用语句:例如,以下代码是在前面代码中已经出现过的调用语句: th1.Start(); th2.Start(200); Abort()方法方法该方法用于终止线程,使线程进入AbortRequested状态。该方法用于终止线程,使线程进入状态。状态例如,终止线程的语句是的语句是:例如,终止线程th的语句是: th.Abort();

9.2 线程及其实现方法

9.2.2 线程的实现方法

C#程序设计教程——蒙祖强编着

Suspend()方法和方法和Resume()方法方法方法和 Suspend()方法用于挂起线程,使线程进入方法用于挂起线程,状态;方法用于挂起线程使线程进入SuspendRequested状态;状态 Resume()方法则用于将被挂起的线程重新工作,使得它进入方法则用于将被挂起的线程重新工作,方法则用于将被挂起的线程重新工作 Running状态。状态。状态 Join()方法方法假设在线程th1中对线程执行下列语句:中对线程th2执行下列语句假设在线程中对线程执行下列语句: th2.Join(); 这表示,将阻止线程th1的执行,直到th2执行完为止(才继续执这表示,将阻止线程的执行,直到执行完为止(的执行执行完为止)。如果写成下列的形式行th1)。如果写成下列的形式,则表示阻止线程,直到)。如果写成下列的形式,则表示阻止线程th1,直到500毫毫秒以后th1才运行才运行:秒以后才运行: th2.Join(500);

9.2 线程及其实现方法

C#程序设计教程——蒙祖强编着

9.2.2 线程的实现方法以下是Thread类的主要属性:类的主要属性:以下是类的主要属性 CurrentCulture属性属性该属性用于获取或设置当前线程的区域性。该属性用于获取或设置当前线程的区域性。 CurrentThread属性属性获取当前正在运行的线程。获取当前正在运行的线程。 CurrentUICulture属性属性获取或设置资源管理器使用的当前区域性,获取或设置资源管理器使用的当前区域性,以便在运行时查找区域性特定的资源。域性特定的资源。 IsAlive 属性属性该属性返回指示当前线程执行状态的值。该属性返回指示当前线程执行状态的值。 IsBackground属性属性该属性用于获取或设置指示当前线程是否为后台线程。值为true 该属性用于获取或设置指示当前线程是否为后台线程。值为时表示为后台线程,这时该线程随着主进程的结束而结束,时表示为后台线程,这时该线程随着主进程的结束而结束,而不管该线程是否已经运行结束;值为false(默认值)时表示为前台管该线程是否已经运行结束;值为(默认值)线程,只有所有的前台线程运行结束后,主线程才能终止。线程,只有所有的前台线程运行结束后,主线程才能终止。

9.2 线程及其实现方法

9.2.2 线程的实现方法 ManagedThreadId属性属性获取当前托管线程的唯

一标识符。获取当前托管线程的唯一标识符。 Name属性属性获取或设置线程

的名称。获取或设置线程的名称。

C#程序设计教程——蒙祖强编着

ThreadState属性属性返回当前线程的状态。线程的状态包括Running、StopRequested、返回当前线程的状态。线程的状态包括、、SuspendRequested、Background、Unstarted、Stopped、、、、、WaitSleepJoin、Suspended、AbortRequested和Aborted。、、和。

9.2 线程及其实现方法

9.2.3 线程的优先级

C#程序设计教程——蒙祖强编着

线程的优先级是用Thread类的类的Priority属性来设置,其值集是一属性来设置,线程的优先级是用类的属性来设置个枚举,个枚举,即{Lowest, BelowNormal, Normal, AboveNormal, Highest},它们的优先级别依次从低到高,Priority属性的默认设,它们的优先级别依次从低到高,属性的默认设置是ThreadPriority.Normal。置是。需要注意的是,需要注意的是,操作系统并不能够保证拥有高优先级的线程每次都能够获得比低优先级线程更高执行权限,都能够获得比低优先级线程更高执行权限,这跟操作系统的调度算法有关。算法有关。

9.3 线程的同步控制

9.3.1 为什么要同步控制

C#程序设计教程——蒙祖强编着

【例9.1】存在同步访问问题的多线程程序。(为什么要同步控制)】存

在同步访问问题的多线程程序。为什么要同步控制)创建控制台应用程序BankTransfering,它只是简单地模拟银行用创建控制台应用程序,户进行转帐和取款的程序:户进行转帐和取款的程序:

class Bank { private double account1 = 2500; private double account2 = 1000; public void transfering() //转帐转帐 { Console.WriteLine("转帐前帐户account1还剩余的金额:" 转帐帐户还剩余的金额:还剩余的金额account1.ToString()); Console.Write("转帐金额(元):转帐金额():"); 转帐金额 double sum = double.Parse(Console.ReadLine()); //输入转帐金额

输入转帐金额 if (sum > account1) { Console.WriteLine("转帐金额超出了帐户转帐金额超出了帐户account1所剩的金额," 所剩的金额,转帐金额超出了帐户所剩的金额 "转帐失败!"); 转帐失败!转帐失败 return; }

9.3 线程的同步控制

9.3.1 为什么要同步控制

C#程序设计教程——蒙祖强编着

account1 = account1 - sum; account2 = account2 sum;

Console.WriteLine("转帐后帐户还剩余的金额:转帐帐户account1还剩余的金额:" 还剩余的金额 account1.ToString()); } public void fetching() //取款取款 { Thread.Sleep(100); account1 = account1 - 2000; //取款取款

2000元取款元 } } static void Main(string[] args) { Bank a = new

Bank(); Thread user1 = new Thread(new ThreadStart(a.transfering)); Thread user2 = new Thread(new ThreadStart(a.fetching)); user1.Start(); user2.Start(); Console.ReadKey(); }

9.3 线程的同步控制

9.3.1 为什么要同步控制

C#程序设计教程——蒙祖强编着

程序中的类Bank定义了两个方法: fetching()和transfering(),它程序

中的类定义了两个方法:和,定义了两个方法们分别用于实现取款和转帐操作,们分别用于实现取款和转帐操作,并基于这两个方法分别创建了线程user1和user2。程序运行时,user1和user2几乎是同时开始工线程和。程序运行时,和几乎是同时开始工随后user1 从键盘接收转帐金额,然后完成转帐操作;但作,随后 user2的“ 动作” 比较快,立刻就取出的动作”

比较快,立刻就取出2000元。程序运行结果如元下图所示。下图所示。

可以看到,查询帐户account1时,明明显示了还剩元的信息,可以看到,user1查询帐户查询帐户时明明显示了还剩2500元的信息,元的信息但在执行从account1向account2转2000元时,却出现了操作失败的提示元时,但在执行从向转元时即使转帐操作成功了,结果显示的剩余金额也不对)。其原因在于,)。其原因在于(即使转帐操作成功了,结果显示的剩余金额也不对)。其原因在于,恰好在user1等待接收从键盘输入的转帐金额时,user2从帐户等待接收从键盘输入的转帐金额时,从帐户account1上恰好在等待接收从键盘输入的转帐金额时从帐户上提走了2000元。显然,我们不希望发生这种情况,这就需要线程的同步提走了元显然,我们不希望发生这种情况,控制来解决。

控制来解决。

9.3 线程的同步控制

9.3.2 使用使用ManualResetEvent类类

C#程序设计教程——蒙祖强编着

ManualResetEvent类的作用是:通知一个或多个正在等待的线程已类的作用是:类的作用是发生事件。发生事件。 ManualResetEvent类对象有两种状

态:有信号状态和无信号状态。类对象有两种状态:有信号状态和无信号状态。类对象有两种状态其状态常通过两种方法设置:一种是使用构造函数,其状态常通过两种方法设置:一种是使用构造函数,另一种是对象方法。例如:方法。例如:使用构造函数

ManualResetEvent mre = new ManualResetEvent(false); ManualResetEvent mre = new ManualResetEvent(true); //初始化初始化mre为无信号状态初始化为无信号状态 //初始化初始化mre为有信号状态初始化为有信号状态

mre.Reset(); mre.Set();

//使mre处于无信号状态使处于无信号状态 //使mre处于有信号状态使

处于有信号状态

使用对象方法

9.3 线程的同步控制

9.3.2 使用使用ManualResetEvent类类

C#程序设计教程——蒙祖强编着

当ManualResetEvent类对象处于无信号状态时,调用该对象类对象处于无信号状态时,类对象处于无信号状态时 WaitOne()方法的线程将被阻止运行(暂停);方法的线程将被阻止运行方法的线程将被阻止运行(暂停);当该对象变为处于有信号状态(方法收到信号)当该对象变为处于有信号状态(WaitOne()方法收到信号)时,方法收到信号 WaitOne()方法将解除该线程的暂停状态,使它继续运行。方法将解除该线程的暂停状态,方法将解除该线程的暂停状态使它继续运行。据此,我们就可以实现多线程的同步控制。方法是:将被视为据此,我们就可以实现多线程的同步控制。方法是:一体的语句序列置于Reset()

和Set()方法之间(称为“加锁”),方法之间(一体的语句序列置于和方法之间称为“加锁” 与它们并发的线程,在读取共享变量前先调用WaitOne()方法;方法;与它们并发的线程,在读取共享变量前先调用方法这样在执行这些语句序列时由于ManualResetEvent类对象无信这样在执行这些语句序列时由于

类对象无信因此该线程被暂停,直到它们执行完了以后才有信号,号,因此该线程被暂停,直到它们执行完了以后才有信号,该线程才能继续执行,因而避免读取不正确的数据,线程才能继续执行,因而避免读取不正确的数据,从而实现线程的同步控制。程的同步控制。

9.3 线程的同步控制

9.3.2 使用使用ManualResetEvent类类

C#程序设计教程——蒙祖强编着

下面分两种情况来介绍如何对多线程进行同步控制。下面分两种情况来介绍如何对多线程进行同步控制。 1. 单线程的加锁对于上节介绍的程序BankTransfering,为解决其同步问题,可将对于上节介绍的程序,为解决其同步问题,代码修改如下(红色部分):代码修改如下(红色部分):

class Bank { private double account1 = 2500; private double account2 = 1000; //创建 //创建ManualResetEvent类的对象mre 创建ManualResetEvent 类的对象类的对象mre public ManualResetEvent mre = new ManualResetEvent(false); public void transfering() //转帐转帐

{ mre.Reset(); //设置对象设置对象mre处于无信号状态设置对象处于无信号状态 Console.WriteLine("转帐前帐户还剩余的金额:转帐帐户account1还剩余的金额:" 还剩余的金额 account1.ToString()); Console.Write("转帐金额(元):转帐金额():"); 转帐金额 double sum =

double.Parse(Console.ReadLine());

9.3 线程的同步控制

9.3.2 使用使用ManualResetEvent类类

C#程序设计教程——蒙祖强编着

double sum = double.Parse(Console.ReadLine()); if (sum > account1) { Console.WriteLine("转帐金额超出了帐户转帐金额超出了帐户account1所剩的金额," 所剩的金额,转帐金额超出了帐户所剩的金额 "转帐失败!"); 转帐失败!转帐失败 return; } account1 = account1 - sum; account2 =

account2 sum; Console.WriteLine("转帐后帐户还剩余的金额:转帐帐户account1还剩余的金额:" 还剩余的金额 account1.ToString()); mre.Set(); //设置对象设置对象mre处于有信号状态设置对象处于有信号状态 } public void fetching() //取款取款 { //阻止当前线程(线程user2)的运行,直到收到对象mre发的信息阻止当前线程(线程)的运行,直到收到对象发的信息阻止当前线程 mre.WaitOne(); Thread.Sleep(100); account1 = account1 - 2000; } }

9.3 线程的同步控制

9.3.2 使用使用ManualResetEvent类类

C#程序设计教程——蒙祖强编着

double sum = double.Parse(Console.ReadLine()); if (sum > account1) { Console.WriteLine("转帐金额超出了帐户转帐金额超出了帐户account1所剩的金额," 所剩的金额,转帐金额超出了帐户所剩的金额 "转帐失败!"); 转帐失败!转帐失败 return; } account1 = account1 - sum; account2 = account2 sum; Console.WriteLine("转帐后帐户还剩余的金额:转帐帐户account1还剩余的金额:" 还剩余的金额 account1.ToString()); 上述代码只使用了一个ManualResetEvent类对象,仅对一个线类对象,上述代码只使用了一个处于有信号状态类对象 mre.Set(); //设置对象设置对象mre处于有信号状态设置对象程中的语句序列进行加锁。程中的语句序列进行加锁。如果有多个线程中的语句序列需要 } public void fetching() //取款取款加锁,那应该怎么办呢这就涉及到多线程的加锁问题。加锁,那应该怎么办呢这就涉及到多线程的加锁问题。 { //阻止当前线程(线程user2)的运行,直到收到对象mre发的信息阻止当前线程(线程)的运行,直到收到对象发的信息阻止当前线程mre.WaitOne(); Thread.Sleep(100); account1 = account1 - 2000; } }

9.3 线程的同步控制

9.3.2 使用使用ManualResetEvent类类

C#程序设计教程——蒙祖强编着

2. 多线程的加锁一个ManualResetEvent类对象只能对一个线程中的语句序列进行加一个类对象只能对一个线程中的语句序列进行加如果需要对多个线程中的语句序列进行加锁,锁;如果需要对多个线程中的语句序列进行加锁,就需要创建与线程数量一样多的ManualResetEvent类对象。类对象。程数量一样多的类对象【例9.2】下列是控制台应用程序BankTransfering2中文件】下列是控制台应用程序中文件 Program.cs的代码,它仍然模拟银行转帐、查账的功能,但对代码的代码,的代码它仍然模拟银行转帐、查账的功能,进行了简化(该程序需要解决多线程的同步控制问题):进行了简化(该程序需要解决多线程的同步控制问题):

9.3 线程的同步控制

9.3.2 使用使用ManualResetEvent类类

C#程序设计教程——蒙祖强编着

class Bank { private double account1 = 2500; private double account2 = 1000; public void transfering() //将100元从帐户将元从帐户account1

转到帐户转到帐户account2 元从帐户转到帐户 { account1 = account1 - 100; Thread.Sleep(100); account2 = account2 100; } public void

transfering2() //将300元从帐户元从帐户account2转到帐户转到帐户

account1 将元从帐户转到帐户 { account1 = account1 300;

Thread.Sleep(200); account2 = account2 - 300; } public void querying()

//查询帐户查询帐户account1和account2上的余额查询帐户和上的余额

{ Console.WriteLine("帐户帐户account1上的余额为:{0} 元", account1);

上的余额为:帐户上的余额为 Console.WriteLine("帐户帐户account2上的余额为:{0} 元", account2); 上的余额为:帐户上的余额为 } }

9.3 线程的同步控制

9.3.2 使用使用ManualResetEvent类类

C#程序设计教程——蒙祖强编着

static void Main(string[] args) { Bank a = new Bank(); Thread user1 = new Thread(new ThreadStart(a.transfering)); //转帐用户转帐用户1 转帐

用户 Thread user2 = new Thread(new ThreadStart(a.transfering2)); //转帐

用户转帐用户2 转帐用户 Thread user3 = new Thread(new

ThreadStart(a.querying)); //查账用户查账用户 user1.Start(); //执行转帐(account1到account2)执行转帐(执行转帐到) user2.Start(); //执行

转帐(account2到account1)执行转帐(执行转帐到) user3.Start(); //

查账用户查账用户 Console.ReadKey(); }

显然,该结果并不是我们预期的结果。为了查账用户(线程显然,该结果并

不是我们预期的结果。为了查账用户(线程user3)能看)到一致的数据,需要

对方法transfering()和方法到一致的数据,需要对方法和方法transfering2()中的代码都中的代码都和方法应该加锁,这就是涉及到对两个线程中的语句进

行加锁的问题。应该加锁,这就是涉及到对两个线程中的语句进行加锁的问题。

9.3 线程的同步控制

9.3.2 使用使用ManualResetEvent类类

C#程序设计教程——蒙祖强编着

实现对两个线程中的语句进行加锁:实现对两个线程中的语句进行加锁:(1)先创建一个包含两个)先创建一个包含两个ManualResetEvent类对象的类对象的 ManualResetEvent数组数组mres:数组: ManualResetEvent[] mres = { new ManualResetEvent(false), new ManualResetEvent(false) }; 中的两个对象分别对方法transfering()和方法(2)用数组)用数组mres中的两个对象

分别对方法中的两个对象分别对方法和方法 transfering2()中的代码进行加

锁;中的代码进行加锁;中的代码进行加锁中查询语句之前调用

WaitHandle.WaitAll() (3)在方法)在方法querying()中查询语句之前调用

中查询语句之前调用方法,该方法的参数类型是ManualResetEvent数组,其作用是:当数组,方法,该方法的参数类型是数组其作用是:数组中所有的对象

都接收到信号后才允许方法querying()继续执行。继续执行。数组中所有的对

象都接收到信号后才允许方法继续执行

9.3 线程的同步控制

9.3.2 使用使用ManualResetEvent类类修改后的代码:修改后的代码: C#程序设计教程——蒙祖强编着

class Bank { private double account1 = 2500; private double account2 = 1000; ManualResetEvent[] mres = { new ManualResetEvent(false), new ManualResetEvent(false) }; //创建包含两个创建包含两个ManualResetEvent

类对象的数组创建包含两个类对象的数组 public void transfering() //将

100元从帐户元从帐户account1转到帐户转到帐户account2 将元从帐户转到帐户 { mres[0].Reset(); account1 = account1 - 100; Thread.Sleep(100); account2 = account2 100; mres[0].Set(); }

9.3 线程的同步控制

9.3.2 使用使用ManualResetEvent类类

C#程序设计教程——蒙祖强编着

public void transfering2() //将300元从帐户元从帐户account2转到帐户转到帐户account1 将元从帐户转到帐户 { mres[1].Reset(); account1 = account1 300; Thread.Sleep(200); account2 = account2 - 300;

mres[1].Set(); } public void querying() //查询帐户查询帐户account1和account2上的余额查询帐户和上的余额 { WaitHandle.WaitAll(mres); Console.WriteLine("帐户帐户account1上的余额为:{0} 元", account1); 上的余额为:帐户上的余额为 Console.WriteLine("帐户帐户account2上的余额为:{0} 元", account2); 上的余额为:帐户上的余额为 } }

——该结果与预想的完全一致,这说明已经正确实现线程的同步控制。该结果与预想的完全一致,这说明已经正确实现线程的同步控制。该结果与预想的完全一致

9.3 线程的同步控制

9.3.3 使用使用AutoResetEvent类类

C#程序设计教程——蒙祖强编着

ManualResetEvent的缺点:当使用ManualResetEvent类来进行多线的缺

点:当使用的缺点类来进行多线程的同步控制时,创建的ManualResetEvent类对象的数量要与线程程的同步控制时,创建的类对象的数量要与线程的个数相同,这使程序代码显得比较累赘。的个数相同,这使程序代码显得比较累赘。AutoResetEvent的特点:与ManualResetEvent类不同的是,在执行的特点:类不同的是,的特点类不同的是 AutoResetEvent的Set()方法时,AutoResetEvent对象仅发出“一条” 方法时,对象仅发出“ 的方法时对象仅发出一条” 信号,这样也就仅仅“消掉”一个WaitOne()方法;如果还有其他

方法;信号,这样也就仅仅“消掉”一个方法 WaitOne()方法在等待信号,那么方法在等待信号,方法在等待信号那么AutoResetEvent对象会自动变为对象会自动变为无信号状态(如果没有就不改变其状态),直到再次执行一个

Set() ),直到再次执行一个无信号状态(如果没有就不改变其状态),直到再

次执行一个方法才能“消掉”下一个WaitOne()方法。方法。方法才能“消

掉”下一个方法——因此,使用AutoResetEvent类来实现线程的同步控制,程因此,使用类来实现线程的同步控制,因此类来实现线程的同步控制序代码会显得更为简洁、编写效率也会更高。序代码会显得更为简洁、编写效率也会更高。——根据这一点,我们只需要创建一个AutoResetEvent对象,根据这一点,我们只需要创建一个对象,根据这一点对象就可以完成对多个线程的同步控制。就可以完成对多个线程的同步控制。

9.3 线程的同步控制

C#程序设计教程——蒙祖强编着

9.3.3 使用使用AutoResetEvent类类【例 9.3】修改程序BankTransfering2 (见例 9.2 ),使用】 AutoResetEvent类实现对其所涉及线程的同步控制。类实现对其所涉及线程的同步控制。类实现对其所涉及线程的同步控制

class Bank { private double account1 = 2500; private double account2 = 1000; AutoResetEvent are = new AutoResetEvent(false); public void transfering() //将100元从帐户元从帐户account1转到帐户转到帐户

account2 将元从帐户转到帐户 { are.Reset(); account1 = account1 - 100; Thread.Sleep(100); account2 = account2 100; are.Set(); }

9.3 线程的同步控制

9.3.3 使用使用AutoResetEvent类类

C#程序设计教程——蒙祖强编着

public void transfering2() //将300元从帐户元从帐户account2转到帐户转到帐户account1 将元从帐户转到帐户 { are.Reset(); account1 = account1 300; Thread.Sleep(200); account2 = account2 - 300;

are.Set(); } public void querying() //查询帐户查询帐户account1和account2上的余额查询帐户和上的余额 { are.WaitOne(); are.WaitOne(); Console.WriteLine("帐户帐户account1上的余额为:{0} 元", account1); 上的余额为:帐户上的余额为 Console.WriteLine("帐户帐户account2上的余额为:{0} 元", account2); 上的余额为:帐户上的余额为 } } ——可以看到,在该程序中只创建了一个AutoResetEvent类对象,就可以实现对多个可以看到,在该程序中只创建了一个类对象,可以看到类对象线程的同步控制,这就是AutoResetEvent类的优势。但要注意,有多少个类的优势。线程的同步控制,

这就是类的优势但要注意,有多少个Set()方法就方法就应该多少个WaitOne()方法与之对应,否则会出现无限等待或其他问题。方法与之对应,应该多少个

方法与之对应否则会出现无限等待或其他问题。

9.4 线程池

C#程序设计教程——蒙祖强编着

为什么要用线程池为什么要用线程池如果频繁地对大量的线程执行创建、销毁等操作,其代价是昂贵的,如果频繁地对大量的线程执行创建、销毁等操作,其代价是昂贵的,可能导致系统性能严重下降。为避免创建、可能导致系统性能严重下降。为避免创建、销毁线程所付出的大量额外时间,一种解决方法是使用

线程池。额外时间,一种解决方法是使用线程池。什么是线程池什么是线程池线程池(线程池(ThreadPool)可以简单地理解为存放线程的容器。线程池)可以简单地理解为存放线程的容器。中存放若干线程,当有任务要执行的时候,中存放若干线程,当有任务要执行的时候,从线程池中唤醒一个线令它执行该任务;任务执行完毕后,程,令它执行该任务;任务执行完毕后,重新将线程放回线程池而不是销毁),并令其处于休眠状态。这样,),并令其处于休眠状态(而不是销毁),并令其处于休眠状态。这样,就不需要对线程进行创建和销毁操作,从而节省时间并使系统更加稳定。行创建和销毁操作,从而节省时间并使系统更加稳定。

9.4 线程池

C#程序设计教程——蒙祖强编着

实际上,线程池是一种线程管理器,实际上,线程池是一种线程管理器,由ThreadPool类提供的方类提供的方法来维护线程。其中,法来维护线程。其中,ThreadPool.QueueUserWorkItem()方法用于方法用于将线程存放到线程池中,该方法原型如下:将线程存放到线程池中,该方法原型如下: public static bool QueueUserWorkItem(WaitCallback); 被放到线程池中的线程的

Start()方法将调用方法将调用WaitCallback代理被放到线程池中的线程的方法将调用代理对象代表的函数。该方法的重载定义如下:对象代表的函数。该方法的重载定义如下: public static bool QueueUserWorkItem(WaitCallback, object); 参数object将被传递给将被传递给WaitCallback所代表的方法,由此可以实现所代表的方法,参数将被传递给所代表的方法参数传递。参数传递。

9.4 线程池

C#程序设计教程——蒙祖强编着

实际上,线程池是一种线程管理器,实际上,线程池是一种线程管理器,由ThreadPool类提供的方类提供的方法来维护线程。其中,法来维护线程。其中,ThreadPool.QueueUserWorkItem()方法用于方法用于将线程存放到线程池中,该方法原型如下:将线程存放到线程池中,该方法原型如下: public static bool QueueUserWorkItem(WaitCallback); 被放到线程池中的线程的

Start()方法将调用方法将调用WaitCallback代理被放到线程池中的线程的方法将调用代理对象代表的函数。该方法的重载定义如下:对象代表的函数。该方法的重载定义如下: public static bool QueueUserWorkItem(WaitCallback, object); 参数object将被传递给将被传递给WaitCallback所代表的方法,由此可以实现所代表的方法,参数将被传递给所代表的方法参数传递。参数传递。

——利用线程池,我们无需显式创建线程,而只需将要完成的任务写成利用线程池,我们无需显式创建线程,利用线程池函数,然后将之作为参数通过WaitCallback代理对象传递给函数,然后将之作为参数通过代理对象传递给QueueUserWorkItem()方法即可,而后由线程池自动建立、管理、运行方法即可,方法即可而后由线程池自动建立、管理、相应的线程。相应的线程。

9.4 线程池

C#程序设计教程——蒙祖强编着

【例9.4】使用线程池的简单例子。】使用线程池的简单例子。

public class MyThreadClass { public void MyMethod(object parameter) { string str = (string)parameter; for (int i = 1; i < 10; i )

{ Console.Write(str "(任务 i ")\n"); (任务" )

Thread.Sleep(100); } } }

9.4 线程池

C#程序设计教程——蒙祖强编着

static void Main(string[] args) { MyThreadClass instance = new MyThreadClass(); string myParameter = "线程线程1……"; 线程ThreadPool.QueueUserWorkItem(new WaitCallback(instance.MyMethod), myParameter); myParameter = "线程线程2……"; 线程

ThreadPool.QueueUserWorkItem(new WaitCallback(instance.MyMethod), myParameter); myParameter = "线程线程3……"; 线程

ThreadPool.QueueUserWorkItem(new WaitCallback(instance.MyMethod), myParameter); Console.ReadLine(); }

9.4 线程池

线程1……(任务)线程(任务1)线程2……(任务1)线程(任务)线程1……(任务2)线程(任务)线程2……(任务2)线程(任务)线程1……(任务3)线程(任务)线程2……(任务3)线程(任务)线程1……(任务4)线程(任务)线程2……(任务4)线程(任务)线程1……(任务5)线程(任务)线程2……(任务5)线程(任务)线程1……(任务6)线程(任务)线程2……(任务6)线程(任务)线程1……(任务7)线程(任务)线程2……(任务7)线程(任务)线程1……(任务8)线程(任务)线程2……(任务8)线程(任务)线程1……(任务9)线程(任务)线程2……(任务9)线程(任务)线程3……(任务1)线程(任务)线程3……(任务2)线程(任务)线程3……(任务3)线程(任务)线程3……(任务4)线程(任务)线程3……(任务5)线程(任务)线程3……(任务6)线程(任务)线程3……(任务7)线程(任务)线程3……(任务8)线程(任务)线程3……(任务9)线程(任务)

C#程序设计教程——蒙祖强编着

执行结果

9.4 线程池

C#程序设计教程——蒙祖强编着

对象结合使用实例。【例9.5】线程池与】线程池与ManualResetEvent对象结合使用实例。对象结合使用实例本例将对5到间的个随机产生的整数求阶乘(),分别用10 间的10个随机产生的整数求阶乘),分别用本例将对到10间的个随机产生的整数求阶乘(n!),分别用个线程来完成这些任务。程序中,个线程来完成这些任务。程序中,用ManualResetEvent对象的信对象的信

号来标记计算任务是否完成,号来标记计算任务是否完成,当所有的计算任务都

完成后才显示计算结果。计算结果。

public class Fac { public int N { get { return _n; } } private int

_n; public int FacOfN { get { return _facOfN; } } private int _facOfN; private ManualResetEvent _doneEvent; public Fac(int n, ManualResetEvent doneEvent) { _n = n; _doneEvent = doneEvent; }

9.4 线程池

C#程序设计教程——蒙祖强编着

public void ThreadPoolCallback(Object threadContext) { int threadIndex = (int)threadContext; Console.WriteLine("thread {0} started……", threadIndex); _facOfN = Calculate(_n);

Console.WriteLine("thread {0} result calculated……", threadIndex);

_doneEvent.Set(); // 将_doneEvent设置为有信号状态设置为有信号状态 } public int Calculate(int n) //计算阶乘(n!)计算阶乘()计算阶乘 { if ((n == 0) || (n == 1)) { return 1; } return n * Calculate(n -

1); } }//end public class Fac

9.4 线程池

C#程序设计教程——蒙祖强编着

static void Main(string[] args) { const int FacCalculations = 10; ManualResetEvent[] doneEvents = new ManualResetEvent[FacCalculations]; Fac[] facArray = new Fac[FacCalculations]; Random r = new Random(); Console.WriteLine("launching {0} tasks……", FacCalculations); for (int i = 0; i < FacCalculations; i ) 创建ManualResetEvent对象,并初始化为无信号状态对象, { //创建创建对象 doneEvents[i] = new

ManualResetEvent(false); Fac f = new Fac(r.Next(5, 10), doneEvents[i]); facArray[i] = f; ThreadPool.QueueUserWorkItem(f.ThreadPoolCallback, i); } //等待知道等待知道doneEvents中的各个中的各个ManualResetEvent对象均有信号为止(所有计对象均有信号为止(等待知道中的各个对象均有信号为止

算任务均完成)算任务均完成) WaitHandle.WaitAll(doneEvents);

Console.WriteLine("All calculations are complete."); //显示结果显示结果for (int i = 0; i < FacCalculations; i ) { Fac f = facArray[i]; Console.WriteLine("Fac({0}) = {1}", f.N, f.FacOfN); }

Console.ReadLine(); }

9.4 线程池

C#程序设计教程——蒙祖强编着

——已经成功运行个线程,并得到有效控制,结果是正确的。已经成功运行10个线程并得到有效控制,结果是正确的。已经成功运行个线程,

9.5 线程对控件的访问

C#程序设计教程——蒙祖强编着

在多线程编程设计中,在多线程编程设计中,不允许一个线程访问在另外一

个线程中创建的对象,这是一个基本的原则。但在许多时候,我们恰恰需要建的

对象,这是一个基本的原则。但在许多时候,这么做,例如一个线程需要访问其主线程中的控件(这么做,例如一个线程需要访问其主线程中的控件(以显示数据或获得数据等),那我们应该怎么办呢),那我们应该怎么办呢或获得数据等),那我们应该怎么办呢Control类提供的类提供的 Invoke()方法可以帮助我们解决这个问题。方法可以帮助我们解决这个问题。方法可以帮助我们解决这个问题 Invoke()方法可以调用窗体界面线程(主线程)中的任何一个委方法可以调用窗体界面线程(主线程)方法可以调用窗体界面线程托对象,其原型如下:托对象,其原型如下: object Control.Invoke(Delegate method) object Control.Invoke(Delegate method, params object[] args) 参数method用于传递已创建的委托对象,该对象关联的方法的参用于传递已创建的委托对象,参数用于传递已创建的委托对象数值则放在数组args中。如果关联的方法没有参数,则使用第一数值则放在数组中如果关联的方法没有参数,方法。个Invoke()方法。方法

9.5 线程对控件的访问

C#程序设计教程——蒙祖强编着

【例9.6】线程访问控件的例子】创建窗体应用程序ThreadVisitingControl,在窗体上添加一个创建窗体应用程序, ListBox控件和一个控件和一个Button控件,并适当设置它们的属性、调整它控件,控件和一个控件并适当设置它们的属性、们的位置和大小。们的位置和大小。关键代码如下:关键代码如下:

public partial class Form1 : Form { public Form1()

{ InitializeComponent(); } //本例中的线程要通过这个方法来访问主线程中的控件本例中的线程要通过这个方法来访问主线程中的控件 private void showStuIfo(string no, string name, double score) { listBox1.Items.Add("学号:" no); 学号:学号 listBox1.Items.Add("姓名:" name); 姓名:姓名 listBox1.Items.Add("成绩:" score.ToString()); 成绩:成绩 }

9.5 线程对控件的访问

C#程序设计教程——蒙祖强编着

//声明与方法声明与方法showStuIfo(string no, string name, double score)匹配的委托类型声明与方法匹配的委托类型 public delegate void stuInfoDelegate(string no, string name, double score); private void stuThread() //线程方法线程方法 { //线程通过方法的委托执行线程通过方法的委托执行showStuIfo(),实现对线程通过方法的委托执行,实现对ListBox 控件的访问控件的访问 Invoke(new stuInfoDelegate(showStuIfo), new object[] { "20101001", "张三 95.5 }); 张三", 张三 } private void

button1_Click(object sender, EventArgs e) { Thread stuth = new

Thread(new ThreadStart(stuThread)); //创建线程创建线程 stuth.Start(); //执行线程执行线程 } } }

本例中基于stuThread()方法创建了线程方法创建了线程stuth,该线程调用本例中基于方法创建了线程, Invoke()方法来执行由方法来执行由

stuInfoDelegate关联的方法关联的方法showStuIfo(),方法来执行由关联的

方法,该方法使用的三个参数放在对象数组中,该方法使用的三个参数放在对

象数组中,从而使得线程能够实现将学生信息(学号、姓名和成绩)输出到ListBox控件中的功现将学生信息(学号、姓名和成绩)输出到控件中的功

能。

9.5 线程对控件的访问

C#程序设计教程——蒙祖强编着

【例9.7】创建一个线程,用ProgressBar控件形象显示该线程的】创建

一个线程,控件形象显示该线程的运行进度,并利用ManualResetEvent类实现

线程的暂停运行进度,并利用类实现线程的暂停方法)方法)(Suspend()方法)和继续(Resume()方法)功能。方法和继续(方法功能。实现的基本原

理是:方法WaitOne()在收到信号时,它就“走”,实现的基本原理是:方法在

收到信号时,它就“ 在收到信号时否则就“ 利用这一点,否则就“停”。利用这一点,我们在线程的循环语句中调用方法 WaitOne()::暂停】按钮中调用方法Reset(),使得事件对象处于无信号在【暂停】按钮中调用方法,状态,这

时线程将处于等待状态;状态,这时线程将处于等待状态;在【继续】按钮中调用方法Set(),使得事件对象处于有信号状继续】按钮中调用方法,这时由于

方法WaitOne()收到信号而使得线程得以继续执行。收到信号而使得线程得以继

续执行。态,这时由于方法收到信号而使得线程得以继续执行

9.5 线程对控件的访问

C#程序设计教程——蒙祖强编着

基于上述考虑,创建窗体应用程序基于上述考虑,创建窗体应用程序SuspendResume,在窗体上添,加一个ProgressBar控件、三个控件、按钮以

及一个Label控件,控件,加一个控件三个Button按钮以及一个按钮以及一个控件适当设置它们的属性、位置和大小:适当设置它们的属性、位置和大

小:

9.5 线程对控件的访问

C#程序设计教程——蒙祖强编着

public partial class Form1 : Form { public Form1()

{ InitializeComponent(); } Thread progressBar=null; public delegate void ShowProgressDelegate(int i); public delegate void ShowCopyCartoonDelegate(bool isShow); public ManualResetEvent mre = new ManualResetEvent(false); private void setProgressBar(int i)

{ progressBar1.Value = i; } private void showProgressThread() //线程方法(模拟线程运行进度)线程方法(线程方法模拟线程运行进度)

{ ShowProgressDelegate sid = new ShowProgressDelegate(setProgressBar); object[] objs = new object[1];

9.5 线程对控件的访问

for (int i = 0; i < 10000; i = 10) { objs[0] = i; Invoke(sid, objs); Thread.Sleep(1); mre.WaitOne(); }

C#程序设计教程——蒙祖强编着

} private void button1_Click(object sender, EventArgs e) //【运行】

按钮【运行】 { //避免连续按【运行】按钮而创建多个线程避免连续按【运

行】避免连续按 if (progressBar != null && progressBar.IsAlive) progressBar.Abort(); progressBar = new Thread(new

ThreadStart(showProgressThread)); mre.Set(); //设置对象设置对象mre处于有信号状态设置对象处于有信号状态 progressBar.Start(); } private void button2_Click(object sender, EventArgs e) //【暂停】按钮【暂停】

{ mre.Reset(); //设置对象设置对象mre处于无信号状态设置对象处于无信号状态 }

9.5 线程对控件的访问

C#程序设计教程——蒙祖强编着

private void button3_Click(object sender, EventArgs e) //【继续】按

钮【继续】 { mre.Set(); //设置对象设置对象mre处于有信号状态设置对象

处于有信号状态 } private void Form1_Load(object sender, EventArgs e)

{ progressBar1.Maximum = 10000; progressBar1.Minimum = 0; } //在关闭窗口的时候,先终止线程,否则会产生异常在关闭窗口的时候,在关闭窗口的时候

先终止线程, private void Form1_FormClosing(object sender, FormClosingEventArgs e) { if (progressBar != null && progressBar.IsAlive) progressBar.Abort(); } }

1本文由x4168138贡献

ppt文档可能在WAP端浏览体验不佳。建议您优先选择TXT,或下载源文件到

本机查看。

第9章多线程

本章内容

9.1 一个简单的多线程应用程序 9.2 线程及其实现方法 9.3 线程的同步控制9.4 线程池 9.5 线程对控件的访问

9.1 一个简单的多线程应用程序 C#程序设计教程——蒙祖强编着

本小节创建的多线程应用程序一共包含两个线程,本小节创建的多线程应用

程序一共包含两个线程,这两个线程并发地在屏幕上输出相关的字符串。发地在屏幕上输出相关的字符串。程序的关键代码如下:程序的关键代码如下:

class A { public static int n = 0; public void f() { for (int i = 0;

i < 10; i ) { Console.WriteLine("f()在输出:{0}", A.n); 在输出:在输出

A.n ; 让当前线程 Thread.Sleep(100); 睡眠100毫秒睡眠毫秒 } } }

9.1 一个简单的多线程应用程序 C#程序设计教程——蒙祖强编着

class B { public static void g() { for (int i = 0; i < 10; i )

{ Console.WriteLine("g()在输出:{0}", A.n); 在输出:在输出 A.n ; 让当

前线程 Thread.Sleep(100); 睡眠100毫秒睡眠毫秒 } } } static void

Main(string[] args) { A a = new A(); ThreadStart thst1 = new

ThreadStart(a.f); 建立委托对象,建立委托对象,使之创建线程thst1 创建

线程每个线程实际上是可见,与给定的方法相关联类可见,每个线程实际上

是Thread类 ThreadStart thst2 = new ThreadStart(B.g); 的对象,它是通过

《C语言程序设计实践教程》答案-完整版

4.2练习题 一、选择题 1.D 2.B 3.A 4.B和D 5.C 6.A 7.B 二、填空题 1.//或/* */ 2.主或main 3.函数首部和函数体4.编译和连接 5.分号 5.2练习题 一、选择题 1.A 2.D 3.B 4.B 5.C 6.C 7.D 8.A 9.B 10.B 11.D 12.B 13.A或B 14.C 15.B

17.D 18.D 19.C 20.D 21.B 22.A 23.D 24.C 25.C 26.B 27.C 28.D 29.A 30.B 二、填空题 1.102,10 2.#define 宏名字符串 3.1 4.n=1 5.-4 6.a=1,b= ,c=2 7.c=A 8.n1=%d\nn2=%d 9.a+b>c&&a+c>b&&b+c>a 10.ch>=’a’&&ch<=’z’|| ch>=’A’&&ch<=’Z’11.7 12.0 13.11110000 14.8,4 6.2练习题 一、选择题 1.A 2.C 3.D 4.C 5.A

7.A 8.D 9.B 10.C 11.A 12.A 13.C 14.B 15.正确答案为:12345678 二、填空题 1.10 2.y=1 x%i==0 3.屏幕中间输出一个由星号组成的菱形4.1 5.13 6.(cx=getchar())!=-1 front=cx; 7.m%n 8.4 9.*p px=&x py=&y 三、读程序,写结果 1.-1 2.3,1,-1, 3.a=16,y=60 4.x=12,y=4 5.59 7.2练习题 一、选择题 1.B 2.C 3.C 4.A 5.D

c程序设计教程与实验__吉顺如__实验一到七

实验1熟悉Visual C++环境及运行C程序 一、实验目的 1.掌握在Visual C++环境下C程序的建立、编辑、编译和执行过程。 2.掌握C程序的最基本框架结构,完成简单程序的编制与运行和调试。 3.掌握发现语法错误、逻辑错误的方法以及排除简单错误的操作技能。 二、实验内容 1.从键盘输入两个数a和b,求它们的平方和,并在屏幕上输出。输入该C程序,编译并运行之,记下屏幕的输出结果,以文件名保存。 #include <> main() { int a,b,sum; /*定义整型变量a、b、sum */ printf("Please Input a,b \n "); /*输出提示信息*/ scanf("%d%d",&a,&b); /*从键盘输入两个整数分别赋予a和b*/ sum=a*a+b*b; /*赋值语句,把a2+b2的结果赋给变量sum*/ printf("%d*%d+ %d*%d=%d\n",a,a,b,b,sum); /*输出语句*/ }

改错题 (1计算x*y 的值并将结果输出。试纠正程序中存在的错误,以实现其功能。程序以文件名保存。 #include <> main main () { int x=2;y=3;a int x=2,y=3,a ; A=x*y a =x*y ; print ('a=%d",A); print f ("a=%d",a ); printf("\n"); } (2)下程序的功能为:求两数中的较大数据并输出。纠正程序中存在的错误,以实现其功能。程序以文件名保存。 #include <> viod main() void main() { int a ,b , max; Scanf(“%d,%d ”,&a,&b); s canf(“%d,%d ”,&a,&b); Max=a; m ax=a; If (max

C语言程序设计项目教程习题答案资料

C语言程序设计项目教程— 习题答案 说明:本文档在“文档结构图”视图方式下应用比较方便。 第1章第2章第3章第4章第5章第6章第7章第8章第9章 第1章 1. 什么是软件?软件有哪些特点? 答:软件是程序、数据及其相关文档的完整集合。 软件具有以下特点: ①软件是一种逻辑产品,它以程序和文档的形式出现,保存在计算机的存储器中(磁盘或光盘),通过计算机的运行才能体现它的功能和作用。 ②软件产品的生产主要是研制。 ③软件产品不会用坏,不存在磨损、消耗的问题,但是软件在使用过程中,往往需要根据用户需求变化或软硬件环境的变化对软件进行修改,这种修改被称为软件维护。 ④软件产品的生产主要是脑力劳动。 ⑤软件的费用是不断增加的,软件成本相当昂贵。 2. 什么是软件工程? 答:软件工程是一门用工程化方法,指导计算机软件开发和维护的学科,它采用工程的概念、原理、技术和方法来开发、维护以及管理软件。 3. 什么是软件生命周期?软件生命周期包括哪些阶段? 答:软件生命周期是指从开发软件概念的提出起,直到该软件的使用、失去使用价值而被废弃的整个过程,也可以称作软件生存周期。 软件生命周期包括可行性研究与计划制订、需求分析、软件设计、软件实现、软件测试、运行与维护。

4. 什么是软件开发模型? 答:软件开发模型是软件开发全部过程、活动和任务的结构框架。 5. 写出数据结构的概念。 答:数据结构是指相互之间存在一种或多种特定关系的数据元素的集合,是计算机存储、组织数据的方式。数据结构主要研究和讨论数据的逻辑结构、存储结构和数据的运算。 6. 填空: ⑴数据结构包括逻辑结构、存储结构和数据的运算三个方面。 ⑵据结构的逻辑结构包括线性结构和非线性结构两大类。 ⑶据结构的存储结构包括顺序存储结构、链式存储结构、索引存储结构和散列存 储结构四类。 ⑷表是一种采用链式存储结构的线性表。 7. 什么是算法?算法有哪些特性? 答:算法是解决问题所采取的步骤和方法。算法具有以下特性:有穷性、确定性、有效性、有零个或多个输入、有一个或多个输出。 8. 什么是算法的时间复杂度?什么是算法的空间复杂度? 答:时间复杂度是指执行算法所耗费的时间。空间复杂度是指算法在计算机内执行时所占用的内存开销规模。 9~12. DCDA 13. 分别用流程图、N-S图描述以下问题的算法。 (1)输入一个年份,判断是否为闰年。 提示:闰年的判断条件为:①能被4整除,但不能被100整除;②能被400整除。满足两个条件之一,即为闰年。

c程序设计教程钱能课后答案.doc

c 程序设计教程钱能课后答案【篇 一:c 语言程序设计教程课后习题参考答案】 t>课后习题参考答案 习题1 1. (1)编译、链接.exe (2)函数主函数(或main 函数) (3)编辑编译链接 2. (1)-(5):ddbbc (6)-(10):abbbc 3. (1)答:c 语言简洁、紧凑,使用方便、灵活; c 语言是高级语言,同时具备了低级语言的特征; c 语言是结构化程序设计语言,具有结 构化的程序控制语句; c 语言有各种各样的数据类型; c 语言可移植性好;生成目标代码质量高,程序执行效率高。 (2)编辑、编译、链接、执行 (3)一个 c 程序由一或多个函数组成,一函数若干条语句构成,每 条语句的末尾必须以分号结束。 (4)标识符,关键字,运算符,分隔符,常量,注释符等 4. 从键盘输入一个双精度小数,打印出它的余弦值。 #include stdio.h #include math.h main( ) { double x; scanf( “%lf ”, x); printf( “%n l f”, cos(x) ); } 第2 章 1. (1)bde 、acfg (2)d (3) c (4) c 2. (1)错(2)错(3)错(4)对(5)错3. (1)a=3,b=-27

(2)a=11,b=6,c=6 (3)3 (4)1 0 1 0 1 1 0 (5)-9 9 8 (6)1)20 2 )8 3 )70 4 )0 5 )0 6 )0 4. (1) #include stdio.h main( ) { double r, h ,v; r = 2.5; h = 3.5; v = 3.14*r*r*h; printf( “v=%nl f”, v); } (2) #include stdio.h main( ) { char ch; ch = getchar( ); printf( “%n c”, ch + 32); } (3) #include stdio.h main( ) { printf( “n*”); printf( “*n**”); printf( “***n**”); printf( “******n*”); } (4) #include stdio.h main( ) { double x; scanf( “%lf ”, x); printf( “%d , %l n f”, (int)x, x (int)–x );

C语言程序设计基础教程习题答案

习题答案 第1章 1.1 填空题 1.1.1 应用程序ONEFUNC.C中只有一个函数,这个函数的名称是__main 。 1.1.2 一个函数由__函数头__和__函数体__两部分组成。 1.1.3 在C语言中,输入操作是由库函数__scanf 完成的,输出操作是由库函数_printf_完 成的。 1.1.4 通过文字编辑建立的源程序文件的扩展名是_.c__;编译后生成目标程序文件,扩展 名是__.obj__;连接后生成可执行程序文件,扩展名是_.exe_;运行得到结果。 1.1.5 C语言程序的基本单位或者模块是__函数__。 1.1.6 C语言程序的语句结束符是_;___。 1.1.7 编写一个C程序,上机运行要经过的步骤:______________________________。 1.1.8 在一个C语言源程序中,注释部分两侧的分界符分别为_/*__和__*/__。 1.1.9 C语言中的标识符只能由三种字符组成,它们是字母、数字和下划线。 且第一个字符必须为字母或下划线。 1.1.10 C语言中的标识符可分为关键字、预定义标识符和用户标识符3类。 1.2 选择题 1.2.1 一个C程序的执行是从( A )。 A)本程序的main函数开始,到main函数结束 B)本程序文件的第一个函数开始,到本程序文件的最后一个函数结束 C)本程序的main函数开始,到本程序文件的最后一个函数结束 D)本程序文件的第一个函数开始,到本程序main函数结束 1.2.2 以下叙述不正确的是(C)。 A)一个C源程序可由一个或多个函数组成 B)一个C源程序必须包含一个main函数 C) 在C程序中,注释说明只能位于一条语句的后面 D) C程序的基本组成单位是函数 1.2.3 C语言规定:在一个源程序中,main函数的位置( C )。 A)必须在程序的开头B)必须在系统调用的库函数的后面 C)可以在程序的任意位置D)必须在程序的最后 1.2.4 C编译程序是(A)。 A)将C源程序编译成目标程序的程序 B)一组机器语言指令 C) 将C源程序编译成应用软件 D) C程序的机器语言版本 1.2.5 要把高级语言编写的源程序转换为目标程序,需要使用(D)。 A) 编辑程序B) 驱动程序C) 诊断程序D) 编译程序 1.2.6 以下叙述中正确的是(C)。 A) C语言比其他语言高级 B) C语言可以不用编译就能被计算机识别执行 C) C语言以接近英语国家的自然语言和数学语言作为语言的表达形式 D) C语言出现的最晚,具有其他语言的一切优点 1.2.7 以下叙述中正确的是(A)。 A) C程序中注释部分可以出现在程序中任意合适的地方 B) 花括号“{”和“}”只能作为函数体的定界符 C) 构成C程序的基本单位是函数,所有函数名都可以由用户命名 D) 分号是C语句之间的分隔符,不是语句的一部分 1.2.8 以下叙述中正确的是(B)。

C语言程序设计教程

C语言程序设计教程 第一章程序设计基础知识 一、计算机程序:计算机的工作是用程序来控制的;程序是指令的集合,指令是计算机可以识别的命令。 二、程序设计语言:程序设计语言经历了从机器语言、汇编语言到高级语言这样一个发展过程。C语言属于高级语言,但由于它同时具有一些低级语言的特点所以,有人又把C语言称为中级语言。 1、机器语言:由计算机能识别的二进制指令来书写程序设计语言就是机器语言。机器语言是直接对计算机硬件产生作用的,所以不同型号的计算机的“机器语言”不一样。这样就出现了程序移植性差的问题。对人来说很难掌握、操作和学习。只有少数计算机专家或者专业技术人员才使用。 2、汇编语言:汇编语言是一种符号化的指令,如用机器语言10110110代表加法运算,而在汇编语言中用肋记符ADD来代表加法运算。这样一个英文单词虽然简洁、直观且好记,但汇编语言编写的程序要通过翻译才能执行,它的翻译过程就叫汇编过程。(源——目)。 3、高级语言:由接近人类的自然语言(或英语)编写的程序,称为高级语言。高级语言易学,易用,易懂,写出的程序更简洁。 例如:if(y<3) z=x+y; 用高级语言编写的程序不能被计算机立即执行,由编译方式或解释方式翻译成计算机识别的二进制语言。 4、C语言具有高级语言易学、易用、可移植强等优点,而且具有低级语言的执行效率高,可对硬件直接进行操作等优点。 5、C语言具备以下几方面的特点: ①简洁、紧凑,使用方便,灵活。易于学习和应用(32个关键字,9种控制语句)。 由ANSI标准定义的共32个: auto double int struct break else long switch case enum register typedef char extern return union const float short unsigned continue for signed void default goto sizeof volatile do if while static 注:关键字auto用于说明自动变量,通常不用;volatile(易变的)表示该变量不经过赋值,其值也可能被改变(例如表示时钟的变量、表示通信端口的变量等)。 auto :声明自动变量一般不使用 double :声明双精度变量或函数 int:声明整型变量或函数 struct:声明结构体变量或函数 break:跳出当前循环 else :条件语句否定分支(与if 连用) long :声明长整型变量或函数 switch :用于开关语句 case:开关语句分支 enum :声明枚举类型 register:声明积存器变量 typedef:用以给数据类型取别名(当然还有其他作用) char :声明字符型变量或函数 extern:声明变量是在其他文件正声明(也可以看做是引用变量) return :子程序返回语句(可以带参数,也看不带参数) union:声明联合数据类型

C语言程序设计教程(第二版)电子工业出版社,黄皮书课后答案

习题1 1-1 填空题 1.函数 2.主函数main(),主函数main() 3.主函数main() 4.函数首部,函数体 5.{, } 6./*, */ 7.顺序结构,选择结构,循环结构 8..c, .obj, .exe 1-2 思考题 1.结构化程序设计是指:为使程序具有一个合理的结构以保证程序正确性而规定的一套如何进行程序设计的原则。顺序结构,选择结构,循环结构 2.算法是对具体问题求解步骤的一种描述。计算机算法的表达工具通常采用以下几种方法:(1)用自然语言表示算(2)用流程图表示算法(3)用伪代码表示算法(4)用程序设计语言表示算法 3.语言简洁、紧凑,使用方便、灵活; 支持结构化程序设计;运算符丰富;数据类型丰富;较强的编译预处理功能;C语言的可移植性好;C语言本身既有一般高级语言的优点,又有低级(汇编)语言的特点;语法限制不太严格,程序设计自由度大。 4. 略 5. 略 1-3 编程题 1. main() { float a=3, b=4, c=5, s, area; s=(a+b+c)/2; area=sqrt(s*(s-a)*(s-b)*(s-c)); p rintf(“area=%f ” , area ); } 2. main() { printf(“******************************”); printf(“* hello world *”); printf(“******************************”);

} 习题2 2-1 单选题 1~5 DBDCA 6~10 DCABA 11~14 BCA A 2-2 思考题 1.2.00000 2.1,0.5 3.9,2 4.6 5.100,d 6.(1)20 (2)0 (3)60 7. (1)10,6,4 (2)6,9,15 (3)3,60,83 8. 55 9.70 习题3 3-1单选题 1-5BBDAB 6-10DDBDC 11-15AADCA 16-20CBACC 21-25ABDBB 3-2填空题 1. 3 2.0261 3.0x10 4.0 5. 2, 1 互换a,b的值 6. 6.6 7.–03 8.7 9. 5.0,4,c=3 10.i=10,j=20 11.(1) 65 (2) 65,A

C语言程序设计教程_李含光_郑关胜_清华大学出版社习题答案习题答案[完美打印版]

第1章习题参考答案 1.单项选择题 (1)A (2)C (3)D (4)C (5)B 2.填空题 (1)函数 (2)主函数(main) (3)printf() ,scanf() 第2章习题参考答案 1.单项选择题 1-5 CBCCC 6-10 CDCDC 11-13 DBB 2.填空题 (1)1 (2)26 (3)6 , 4 , 2 (4)10 , 6(5)3.000000 (6)双精度(double)(7)9 (8)字母,数字,下划线(9)13.700000 (10)11(11)((m/10)%10)*100+(m/100)*10+m%10(12)0 (13)10 ,9 ,11(15)(x<0&&y<0)||(x<0&&z<0)||(y<0||z<0)(16)double (17)x==0(18)sqrt(fabs(a-b))/(3*(a+b))(19)sqrt((x*x+y*y)/(a+b)) 第3章习题参考答案 1.单项选择题 1-5 CCCDD 6-10 BCDBC 11-15 BCBBB 16 A 2.填空题 (1)用;表示结束(2){ }(3)y=x<0?1:x==0?0:-1 (4)y%4==0&&y%100!=0||y%400==0(5)上面未配对(6)default标号(7)while ,do while ,for(8)do while(9)本次(10)本层 3.阅读程序,指出结果 (1)yes(2)*&(3)ABother(4)28 70(5)2,0(6)8(7)36 (8)1(9)3,1,-1,3,1,-1(10)a=12 ,y=12(11)i=6,k=4 (12)1,-2 4.程序填空 (1)x:y ,u:z (2)m=n ,m!=0 ,m=m/10 (3)t

c程序设计教程课件完整版

c程序设计教程课件集团标准化办公室:[VV986T-J682P28-JP266L8-68PNN]

本文由x4168138贡献 ppt文档可能在WAP端浏览体验不佳。建议您优先选择TXT,或下载源文件到 本机查看。 第9章多线程 本章内容 9.1 一个简单的多线程应用程序 9.2 线程及其实现方法 9.3 线程的同步控制9.4 线程池 9.5 线程对控件的访问 9.1 一个简单的多线程应用程序 C#程序设计教程——蒙祖强编着 本小节创建的多线程应用程序一共包含两个线程,本小节创建的多线程应用 程序一共包含两个线程,这两个线程并发地在屏幕上输出相关的字符串。发地在屏幕上输出相关的字符串。程序的关键代码如下:程序的关键代码如下: class A { public static int n = 0; public void f() { for (int i = 0; i < 10; i ) { Console.WriteLine("f()在输出:{0}", A.n); 在输出:在输出 A.n ; 让当前线程 Thread.Sleep(100); 睡眠100毫秒睡眠毫秒 } } } 9.1 一个简单的多线程应用程序 C#程序设计教程——蒙祖强编着 class B { public static void g() { for (int i = 0; i < 10; i ) { Console.WriteLine("g()在输出:{0}", A.n); 在输出:在输出 A.n ; 让当 前线程 Thread.Sleep(100); 睡眠100毫秒睡眠毫秒 } } } static void Main(string[] args) { A a = new A(); ThreadStart thst1 = new ThreadStart(a.f); 建立委托对象,建立委托对象,使之创建线程thst1 创建 线程每个线程实际上是可见,与给定的方法相关联类可见,每个线程实际上 是Thread类 ThreadStart thst2 = new ThreadStart(B.g); 的对象,它是通过Thread类的构造的对象,它是通过类的构造 Thread th1 = new Thread(thst1); 函数来创建;函数来创建;并且每个线程都与既 Thread th2 = new Thread(thst2); 定的方法相关联 th1.Start(); 启动线程创建线程thst2 创建 线程 th2.Start(); Console.ReadKey(); } ——由这个例子可以看到,Thread类、委托类型由这个例子可以看到,由 这个例子可以看到类委托类型ThreadStart等——执行线程和th2实际上是执行方法执行线程th1和实际上是执行方法a.f()和方法和方法B.g()。等。 执行线程实际上是执行方法和方法是多线程程序设计中的核心内容。是多线程程序设计中的核心内容。 9.2 线程及其实现方法 9.2.1 线程的概念 C#程序设计教程——蒙祖强编着 线程的概念与程序、进程的概念密切相关。线程的概念与程序、进程的概念 密切相关。程序是程序员编写的静态代码文本。程序是程序员编写的静态代码文本。是程序员编写的静态代码文本进程则是程序的一次动态执行过程,进程则 是程序的一次动态执行过程,进程运行时需要占用装载则是程序的一次动态执行 过程程序代码(编译后的可执行代码)程序代码(编译后的可执行代码)以及存放其所需数据的内存空间和其他的机器资源(如文件等),),当进程终止时这些内存空间间和其他的机器资源(如文件等),当进程终止时这些内存空间和资

C语言程序设计教程课后习题答案

第1章 1-3 CAB 4 .c .obj .exe 5 /* */ 6 ; 7 算法 8 ①中级语言:C语言具有高级语言的先进思想又能直接对存储器进行操作,能进行位运算,能实现汇编语言的大部分功能,生成目标代码质量高,程序执行效率高。 ②结构化语言:C语言用函数作为程序模块,以实现程序的模块化,语言简洁、紧凑,具有结构化的特点。 ③可移植性好:C语言不包含依赖硬件的输入输出机制,使C语言本身不依赖于硬件系统,可移植性好。 9 #include<> main( ) { ; } 10 #include “” main() { printf(“This is my first C Program!”); } 第2章 1.yes 2.-3 3.2,1 4.1)a!=b||a<=c 2)x>=4||x<=-4 5.x>20&&x<30||x<-100 6.#include <> main() { int x; printf(“please input an integar:”); scanf("%d",&x); if(x%5==0&&x%7==0) printf("yes\n"); else printf("no\n"); } 7. #include <> main() { int year,month; printf("please input the year and month:"); scanf("%d%d",&year,&month);

switch(month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12:printf("this month have 31 days."); break; case 4: case 6: case 9: case 11:printf("this month have 30 days."); break; case 2:if(year%4==0&&year%100!=0||year%400==0) { printf("this month have 29 days."); break; } else { printf("this month have 28 days."); break; } } } 8. #include <> main() { float money; int year; printf("\nplease input the money and the year:"); scanf("%f%d",&money,&year); if(year==1) money+=money**12*year; if(year==2) money+=money**12*year; if(year==3||year==4) money+=money**12*year; if(year>=5&&year<=7) money+=money**12*year; if(year>=8) money+=money**12*year; printf("the money is:%f",money); } 第3章

相关主题
文本预览
相关文档 最新文档