首页 > 工作范文 > 范文大全 >

c语言读后感4篇

网友发表时间 2041799

【请您参阅】下面供您参考的“c语言读后感4篇”是由阿拉网友精心整理分享的,供您阅读参考之用,希望此例范文对您有所帮助,喜欢就复制下载支持一下小编了!

c语言读后感篇1

c++编程书评

最近一段时间都在看c++编程,想让自己对计算机有更多的了解,和更好的运用,这也是对自我的一种知识面的一种提升。更多的可能还是出于自己对这方面的热爱吧。那我就把我读后对书本的了解,不过大多数都是书上的一些重点还有一些专业术语。主要也是对这本书的大概介绍吧!

用c 语言编写的过程程序就是一些数据定义和函数调用。要理解这种程序的含义,程序员必须掌握函数调用和函数实现的本身。这就是过程程序需要中间表示的原因。中间表示容易引起混淆,因为中间表示的表述是原始的,更偏向于计算机,而不偏向于所解决的问题。

通常,面向对象程序需要较少的代码,因为问题中的许多部分都可以用已存在的库代码。

c+ +成功的原因是经济上的:转变到o o p需要代价,而转变到c + +所花的代价较小。尽可能地为程序员提供最大便利。

为c + +堵塞了c语言中的一些漏洞,并提供更好的类型检查和编译时的分析。程序员必须先说明函数,使编译器能检查它们的使用情况。预处理器虚拟删除值替换和宏,这就减少了查找疵点的困难。c + +有一个性能,称为r e f e r e n c e s(引用),它允许对函数参数和返回值的地址进行更方便的处理。函数重载改进了对名字的处理,使程序员能对不同的函数使用相同的名字。另外,名字空间也加强了名字的控制。许多性能使c的更安全。面向对象的c + +程序的速度与用c写的程序速度相差在± 1 0 %之内,而且常常更接近。用o o p方法设计的程序可能比c的对应版本更有效。

c+ +的主要目标是让程序员能更容易地使用库,这是通过将库转换为新数据类型(类)来完成的。引入一个库,就是向该语言增加一个新类型。编译器负责这个库如何使用,保证适当的初始化和清除,保证函数被正确地调用。

模板的源代码

一些重要的类型要求修改源代码以便有效地重用。模板可以自动完成对代码的修改,因而是重用库代码特别有用的工具。用模板设计的类型很容易与其他类型一起工作。因为模板对程序员隐藏了这类代码重用的复杂性,所以特别好用。c语言同样有这样的限制,例如当程序超过50 000行时,名字冲突就开始成为问题。简言之,程序员用光了函数和变量名。设计c + +的目的是为了辅助大程序设计,也就是说,去掉小程序和大程序之间复杂性的分界。

程序设计有两个原则

1)内部原则体现在程序自身的结构中,机灵而有见解的程序员可以通过程序设计语言的表达方式了解这种内部原则。

2)外部原则体现在程序的源信息中,一般被描述为“设计文档”(不要与产品文档混淆)。

对象设计的五个阶段

1)对象发现这个阶段出现在程序的最初分析期间。可以通过寻找外部因素与界线、系统中的元素副本和最小概念单元而发现对象。如果已经有了一组类库,某些对象是很明显的。类之间的共同性(暗示了基类和继承类),可以立刻出现或在设计过程的后期出现。

2)对象装配我们在建立对象时会发现需要一些新成员,这些新成员在对象发现时期未出现过。对象的这种内部需要可能要用新类去支持它。

3)系统构造对对象的更多要求可能出现在以后阶段。随着不断的学习,我们会改进我们的对象。与系统中其它对象通讯和互相连接的需要,可能改变已有的类或要求新类。

4)系统扩充当我们向系统增添新的性能时,可能发现我们先前的设计不容易支持系统扩充。这时,我们可以重新构造部分系统,并很可能要增加新类。

5)对象重用这是对类的真正的重点测试。如果某些人试图在全新的情况下重用它,他们会发现一些缺点。当我们修改一个类以适应更新的程序时,类的一般原则将变得更清楚,直到我们有了一个真正可重用的对象。

对象开发让特殊问题生成一个类,然后在解其他问题时让这个类生长和成熟。2 记住,发现所需要的类,是设计系统的主要内容。如果已经有了那些类,这个项目就不困难了。不要强迫自己在一开始就知道每一件事情,应当不断地学习。

4开始编程,让一部分能够运行,这样就可以证明或反驳已生成的设计。不要害怕过程语言风格的细面条式的代码—类分割可以控制它们。坏的类不会破坏好的类。尽量保持简单。具有明显用途的不太清楚的对象比很复杂的接口好。我们总能够从小的和简单的类开始,当我们对它有了较好地理解时再扩展这个类接口,但不可能简化已存在的类接口。

简单地说就是一些人已经写的代码,按某种方式包装在一起。通常,最小的包是带有扩展名如l i b的文件和向编译器声明库中有什么的一个或多个头文件。连接器知道如何在l i b文件中搜索和提取相应的已编译的代码。但是,这只是提供库的一种方法。在跨越多种体系结构的平台上,例如u n i x,通常,提供库的最明智的方法是用源代码,这样在新的目标机上它能被重新编译。而在微软wi n d o w s上,动态连接库是最明智的方法,这使得我们能够利用新发布的d d l经常修改我们的程序。

声明与定义

“声明”向计算机介绍名字,它说,“这个名字是什么意思”。而“定义”为这个名字分配存储空间。无论涉及到变量时还是函数时含义都一样。无论在哪种情况下,编译器都在“定义”处分配存储空间。对于变量,编译器确定这个变量占多少存储单元,并在内存中产生存放它们的空间。对于函数,编译器产生代码,并为之分配存储空间。函数的存储空间中有一个由使用不带参数表或带地址操作符的函数名产生的指针。定义也可以是声明。如果该编译器还没有看到过名字a,程序员定义int a,则编译器马上为这个名字分配存储地址。声明常常使用于e x t e r n关键字。如果我们只是声明变量而不是定义它,则要求使用e x t e r n。对于函数声明,e x t e r n是可选的,不带函数体的函数名连同参数表或返回值,自动地作为一个声明。

或许你看过后或许懂了一点,也对编程有了初步的了解,希望我的书评对你能有所帮助!

本文地址:http:///zuowen/

c语言读后感篇2

1.数组

数组定义时的注意点

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

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++语言规定只能对数组中的元素进行赋值或引用,不能把整个数组作为一个整体进行赋○值或引用。(是一个实例)(来源:c++书4同类型的数组之间不能相互赋值 ○如int a[5],b[5];a=b;//错误

strcpy(b,a);//正确

数组和指针的关系(来源:c++书节)

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

2.字符数组

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

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

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

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

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

方法1 char tx[4]=”abcd“;方法2 char tx[4];//tx=”abcd“;//错误,tx是数组名,不分配内存空间,不可以进行赋值操作;但是数组名可当指针使用(c++书)。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.指针

指针可执行的运算

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

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

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

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

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

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

注意点:

用new运算符分配的内存空间的指针值必须保存起来,以便于delete运算符归还已动态分配的内存,否则会出现不可预测的错误。指向数组的指针 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<

指向指针的指针变量 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<

delete运算符

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

4.容器类std::string #include

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

std::string str=”abc“;

std::string::iterator cit=();

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

{

std::cout<<*cit<

}

return 0;}

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

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

vector

v;

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

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

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

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

容器vector的函数clear()

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

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

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

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

7.类的前置声明

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指向的内容是不可变的

转化为constchar*,const char*转化为char* 1.在string里面_str()函数把string转换为了const char*.○代码如下:

stringa=”abcd“;const char*p=_str();2const_cast

将const char*转换为char*.○ char*p=const_cast

(_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();on = new (128, 110); = ”newbtn“; = new (75, 23); = ”button2";ualstylebackcolor = true;(newbtn);

///清除新生成的btn (newbtn);

cbutton* ctexteditorview::newmybutton(int nid,crect rect,int nstyle){ cstring m_caption;ring(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语言读后感篇3

unit one对象的演化

oop技术能够很容易地将大量问题归纳为一个简单的解,这一发现产生了大量的oop语言,其中最著名的是smalltalk—c++之前最成功的oop语言。

继承表示了基本类型和派生类型之间的相似性,程序员创建一个基本类型以描述系统中一些对象的思想核心。由这个基本类型派生出其他类型,表达了认识该核心的不同途径。

早捆绑意味着编译器对特定的函数名产生调用,而连接器确定调用执行代码的绝对地址。对于。oop采用动态绑定。当给对象发送消息时,在程序运行之前不去确定被调用的代码。编译器保证这个被调用的函数存在,并完成参数和返回值的类型检查,但是它不知道将执行的准确代码。为了实现晚捆绑,编译器在真正调用的地方插入一段特殊的二进制代码。通过使用存放在对象自身中的信息,这段代码在运行时计算被调用函数的地址。这样,每个对象就能根据一个指针的内容有不同的行为。当一个对象接收到消息时,它根据这个消息判断应当做什么。

程序员可以用关键字v i r t u a l表明他希望某个函数有晚捆绑的灵活性,而并不需要懂得v i r t u a l的使用机制。没有它,就不能用c + +做面向对象的程序设计。vi r t u a l函数(虚函数)表示允许在相同家族中的类有不同的行为。这些不同是引起多态行为的原因。

用c 语言编写的过程程序就是一些数据定义和函数调用。要理解这种程序的含义,程序员必须掌握函数调用和函数实现的本身。这就是过程程序需要中间表示的原因。中间表示容易引起混淆,因为中间表示的表述是原始的,更偏向于计算机,而不偏向于所解决的问题。

通常,面向对象程序需要较少的代码,因为问题中的许多部分都可以用已存在的库代码。

c+ +成功的原因是经济上的:转变到o o p需要代价,而转变到c + +所花的代价较小。尽可能地为程序员提供最大便利。

为c + +堵塞了c语言中的一些漏洞,并提供更好的类型检查和编译时的分析。程序员必须先说明函数,使编译器能检查它们的使用情况。预处理器虚拟删除值替换和宏,这就减少了查找疵点的困难。c + +有一个性能,称为r e f e r e n c e s(引用),它允许对函数参数和返回值的地址进行更方便的处理。函数重载改进了对名字的处理,使程序员能对不同的函数使用相同的名字。另外,名字空间也加强了名字的控制。许多性能使c的更安全。面向对象的c + +程序的速度与用c写的程序速度相差在± 1 0 %之内,而且常常更接近。用o o p方法设计的程序可能比c的对应版本更有效。

c+ +的主要目标是让程序员能更容易地使用库,这是通过将库转换为新数据类型(类)来完成的。引入一个库,就是向该语言增加一个新类型。编译器负责这个库如何使用,保证适当的初始化和清除,保证函数被正确地调用。

• 模板的源代码重用

一些重要的类型要求修改源代码以便有效地重用。模板可以自动完成对代码的修改,因而是重用库代码特别有用的工具。用模板设计的类型很容易与其他类型一起工作。因为模板对程序员隐藏了这类代码重用的复杂性,所以特别好用。

c + +的异常处理(见第1 7章的内容)保证能检查到错误并进行处理。

c语言同样有这样的限制,例如当程序超过50 000行时,名字冲突就开始成为问题。简言之,程序员用光了函

数和变量名。设计c + +的目的是为了辅助大程序设计,也就是说,去掉小程序和大程序之间复杂性的分界。

程序设计有两个原则:

1)内部原则体现在程序自身的结构中,机灵而有见解的程序员可以通过程序设计语言的表达方式了解这种内部原则。

2)外部原则体现在程序的源信息中,一般被描述为“设计文档”(不要与产品文档混淆)。

过程语言:为科学工作者使用的f o rt r a n(f o r m u l a-t r a n s l a t i o n)和为商业者使用的c o b o l

(common business-oriented language)。纯计算机科学中很成功的语言是l i s p(l i s t-p r o c e s s i n g),而面向数学的语言应当是a p l(a programming l a n g u a g e)。

对象设计的五个阶段

1)对象发现这个阶段出现在程序的最初分析期间。可以通过寻找外部因素与界线、系统中的元素副本和最小概念单元而发现对象。如果已经有了一组类库,某些对象是很明显的。类之间的共同性(暗示了基类和继承类),可以立刻出现或在设计过程的后期出现。

2)对象装配我们在建立对象时会发现需要一些新成员,这些新成员在对象发现时期未出现过。对象的这种内部需要可能要用新类去支持它。

3)系统构造对对象的更多要求可能出现在以后阶段。随着不断的学习,我们会改进我们的对象。与系统中其它对象通讯和互相连接的需要,可能改变已有的类或要求新类。

4)系统扩充当我们向系统增添新的性能时,可能发现我们先前的设计不容易支持系统扩充。这时,我们可以重新构造部分系统,并很可能要增加新类。

5)对象重用这是对类的真正的重点测试。如果某些人试图在全新的情况下重用它,他们会发现一些缺点。当我们修改一个类以适应更新的程序时,类的一般原则将变得更清楚,直到我们有了一个真正可重用的对象。

对象开发原则

1)让特殊问题生成一个类,然后在解其他问题时让这个类生长和成熟。

2)记住,发现所需要的类,是设计系统的主要内容。如果已经有了那些类,这个项目就不困难了。

3)不要强迫自己在一开始就知道每一件事情,应当不断地学习。

4)开始编程,让一部分能够运行,这样就可以证明或反驳已生成的设计。不要害怕过程语言风格的细面条式的代码—类分割可以控制它们。坏的类不会破坏好的类。

5)尽量保持简单。具有明显用途的不太清楚的对象比很复杂的接口好。我们总能够从小的和简单的类开始,当我们对它有了较好地理解时再扩展这个类接口,但不可能简化已存在的类接口。

第2章数据抽象

库,简单地说就是一些人已经写的代码,按某种方式包装在一起。通常,最小的包是带有扩展名如l i b的文件和向编译器声明库中有什么的一个或多个头文件。连接器知道如何在l i b文件中搜索和提取相应的已编译的代码。但是,这只是提供库的一种方法。在跨越多种体系结构的平台上,例如u n i x,通常,提供库的最明智的方法是用源代码,这样在新的目标机上它能被重新编译。而在微软wi n d o w s上,动态连接库是最明智的方法,这使得我们能够利用新发布的d d l经常修改我们的程序,我们的库函数销售商可能已经将新d d l发送给我们了。

声明与定义

“声明”向计算机介绍名字,它说,“这个名字是什么意思”。而“定义”为这个名字分配存储空间。无论涉及到变量时还是函数时含义都一样。无论在哪种情况下,编译器都在“定义”处分配存储空间。对于变量,编译器确定这个变量占多少存储单元,并在内存中产生存放它们的空间。对于函数,编译器产生代码,并为之分配存储空间。函数的存储空间中有一个由使用不带参数表或带地址操作符的函数名产生的指针。定义也可以是声明。如果该编译器还没有看到过名字a,程序员定义int a,则编译器马上为这个名字分配存储地址。声明常常使用于e x t e r n关键字。如果我们只是声明变量而不是定义它,则要求使用e x t e r n。对于函数声明,e x t e r n是可选的,不带函数体的函数名连同参数表或返回值,自动地作为一个声明。

c+ +要求必须写出函数原型(的全部信息),因为它增加了一个重要的安全层。

c语言读后感篇4

转载▼

标签:

it

c++本身并没有提供任何多线程机制,但是在windows下,我们可以调用sdk win32 api来编写多线程的程序,下面就此简单的讲一下:

创建线程的函数

handle createthread(lpsecurity_attributes lpthreadattributes, // sd

size_t dwstacksize,// initial stack size

lpthread_start_routine lpstartaddress,// thread function

lpvoid lpparameter,// thread argument

dword dwcreationflags,// creation option

lpdword lpthreadid// thread identifier);

在这里我们只用到了第三个和第四个参数,第三个参数传递了一个函数的地址,也是我们要指定的新的线程,第四个参数是传给新线程的参数指针。

eg1:

#include#includeusing namespace std;

dword winapi fun(lpvoid lpparamter)

{

while(1){ cout<<“fun display!”<

}

int main()

{

handle hthread = createthread(null, 0, fun, null, 0, null);

closehandle(hthread);

while(1){ cout<<“main display!”<

return 0;

}

我们可以看到主线程(main函数)和我们自己的线程(fun函数)是随机地交替执行的,但是两个线程输出太快,使我们很难看清楚,我们可以使用函数

void sleep(dword dwmilliseconds// sleep time);

来暂停线程的执行,dwmilliseconds表示千分之一秒,所以

sleep(1000);

表示暂停1秒

eg2:

#include#includeusing namespace std;

dword winapi fun(lpvoid lpparamter)

{

while(1){ cout<<“fun display!”<

}

int main()

{

handle hthread = createthread(null, 0, fun, null, 0, null);

closehandle(hthread);

while(1){ cout<<“main display!”<

return 0;

}

执行上述代码,这次我们可以清楚地看到在屏幕上交错地输出fun display!和main display!,我们发现这两个函数确实是并发运行的,细心的读者可能会发现我们的程序是每当fun函数和main函数输出内容后就会输出换行,但是我们看到的确是有的时候程序输出换行了,有的时候确没有输出换行,甚至有的时候是输出两个换行。这是怎么回事?下面我们把程序改一下看看:

eg3:

#include#includeusing namespace std;

dword winapi fun(lpvoid lpparamter)

{

while(1){ cout<<“fun display!n”;sleep(1000);}

}

int main()

{

handle hthread = createthread(null, 0, fun, null, 0, null);

closehandle(hthread);

while(1){ cout<<“main display!n”;sleep(2000);}

return 0;

}

我们再次运行这个程序,我们发现这时候正如我们预期的,正确地输出了我们想要输出的内容并且格式也是正确的。下面我就来讲一下此前我们的程序为什么没有正确的运行。多线程的程序时并发地运行的,多个线程之间如果公用了一些资源的话,我们并不能保证这些资源都能正确地被利用,因为这个时候资源并不是独占的,举个例子吧:

eg4:

加入有一个资源 int a = 3

有一个线程函数 selfadd()该函数是使 a += a;

又有一个线程函数 selfsub()该函数是使a-= a;

我们假设上面两个线程正在并发欲行,如果selfadd在执行的时候,我们的目的是想让a编程6,但此时selfsub得到了运行的机会,所以a变成了0,等到selfadd的到执行的机会后,a += a,但是此时a确是0,并没有如我们所预期的那样的到6,我们回到前面eg2,在这里,我们可以把屏幕看成是一个资源,这个资源被两个线程所共用,加入当fun函数输出了fun display!后,将要输出endl(也就是清空缓冲区并换行,在这里我们可以不用理

解什么事缓冲区),但此时main函数确得到了运行的机会,此时fun函数还没有来得及输出换行就把cpu让给了main函数,而这时main函数就直接在fun display!后输出main display!,至于为什么有的时候程序会连续输出两个换行,读者可以采用同样的分析方法来分析,在这里我就不多讲了,留给读者自己思考了。

那么为什么我们把eg2改成eg3就可以正确的运行呢?原因在于,多个线程虽然是并发运行的,但是有一些操作是必须一气呵成的,不允许打断的,所以我们看到eg2和eg3的运行结果是不一样的。

那么,是不是eg2的代码我们就不可以让它正确的运行呢?答案当然是否,下面我就来讲一下怎样才能让eg2的代码可以正确运行。这涉及到多线程的同步问题。对于一个资源被多个线程共用会导致程序的混乱,我们的解决方法是只允许一个线程拥有对共享资源的独占,这样就能够解决上面的问题了。

handle createmutex(lpsecurity_attributes lpmutexattributes,// sd

bool binitialowner,// initial owner

lpctstr lpname// object name);

该函数用于创造一个独占资源,第一个参数我们没有使用,可以设为null,第二个参数指定该资源初始是否归属创建它的进程,第三个参数指定资源的名称。

handle hmutex = createmutex(null,true,“screen”);

这条语句创造了一个名为screen并且归属于创建它的进程的资源

bool releasemutex(handle hmutex// handle to mutex);

该函数用于释放一个独占资源,进程一旦释放该资源,该资源就不再属于它了,如果还要用到,需要重新申请得到该资源。申请资源的函数如下

dword waitforsingleobject(handle hhandle,// handle to object

dword dwmilliseconds// time-out interval);

第一个参数指定所申请的资源的句柄,第二个参数一般指定为infinite,表示如果没有申请到资源就一直等待该资源,如果指定为0,表示一旦得不到资源就返回,也可以具体地指定等待多久才返回,单位是千分之一秒。好了,该到我们来解决eg2的问题的时候了,我们可以把eg2做一些修改,如下

eg5:

#include#includeusing namespace std;

handle hmutex;

dword winapi fun(lpvoid lpparamter)

{

while(1){

waitforsingleobject(hmutex, infinite);

cout<<“fun display!”<

sleep(1000);

releasemutex(hmutex);

}

}

int main()

{

handle hthread = createthread(null, 0, fun, null, 0, null);hmutex = createmutex(null, false, “screen”);

closehandle(hthread);

while(1){

waitforsingleobject(hmutex, infinite);

cout<<“main display!”<

sleep(2000);

releasemutex(hmutex);

}

return 0;

}

运行代码正如我们所预期的输出的内容。

相关推荐

热门文档

48 2041799