面向对象程序设计与c语言答案.docx
《面向对象程序设计与c语言答案.docx》由会员分享,可在线阅读,更多相关《面向对象程序设计与c语言答案.docx(14页珍藏版)》请在冰豆网上搜索。
面向对象程序设计与c语言答案
面向对象程序设计与c语言答案
【篇一:
在过程式程序设计(①)、数据抽象程序设计(②)、面向对象程序设计(③】
紧扣教材和考试说明,从考生熟悉的基础知识入手,多角度、多层次地考查了学生的数学理性思维能力及对数学本质的理解能力,立足基础,先易后难,难易适中,强调应用,不偏不怪,达到了“考基础、考能力、考素质”的目标。
试卷所涉及的知识内容都在考试大纲的范围内,几乎覆盖了高中所学知识的全部重要内容,体现了“重点知识重点考查”的原则。
1.回归教材,注重基础
试卷遵循了考查基础知识为主体的原则,尤其是考试说明中的大部分知识点均有涉及,其中应用题与抗战胜利70周年为背景,把爱国主义教育渗透到试题当中,使学生感受到了数学的育才价值,所有这些题目的设计都回归教材和中学教学实际,操作性强。
2.适当设置题目难度与区分度
选择题第12题和填空题第16题以及解答题的第21题,都是综合性问题,难度较大,学生不仅要有较强的分析问题和解决问题的能力,以及扎实深厚的数学基本功,而且还要掌握必须的数学思想与方法,否则在有限的时间内,很难完成。
3.布局合理,考查全面,着重数学方法和数学思想的考察
在选择题,填空题,解答题和三选一问题中,试卷均对高中数学中的重点内容进行了反复考查。
包括函数,三角函数,数列、立体几何、概率统计、解析几何、导数等几大版块问题。
这些问题都是以知识为载体,立意于能力,让数学思想方法和数学思维方式贯穿于整个试题的解答过程之中。
【篇二:
c语言中的面向对象思想】
t>经常听见别人说面向对象的程序设计,以前在学校上课的时候,也有开面向对象程序设计这门课。
可是不幸的是,这些都是以c++,甚至vc++为基础的。
而更加不幸的是,多年以来我一直是一个c的使用者。
在学校的时候,我主要做的是硬件上的驱动层,和底层功能层。
在工作以后,又做的是手机上的软件开发,所有这些都是和c离不开的。
虽然我不得不说,c++是一门很好的语言,但是它的编译速度,代码效率,编译后的代码大小都限制了它在嵌入式上的应用。
(但现在的嵌入式cpu越来越快,内存容量变大。
我觉得用c++也应该没有什么问题。
这使我觉得似乎是嵌入式编译器的限制。
虽然菲利普和ti好像都有c++的编译器,但是似乎没人用这个。
难道是太贵了?
但不管怎么说,嵌入式应用中,c语言的普遍使用是肯定的)
那么在面向过程的时代产生的c语言能否使用面向对象的思想呢?
我认为是肯定可以的,c++不过是在语言级别上加入了对对象的支持,同时提供了丰富的对象库。
而在c语言下,我们只好自力更生了。
一、面向对象思想的目的是框架化,手段是抽象
相信很多人都明白面向对象讲了什么:
类,抽象类,继承,多态。
但是是什么原因促使这些概念的产生呢?
打个比方说:
你去买显示器,然而显示器的品牌样式是多种多样的,你在买的过程中发生的事情也是不可预测的。
对于这样的事情,我们在程序语言中如何去描述呢。
面向对象的思想就是为了解决这样的问题。
编写一个程序(甚至说是一个工程),从无到用是困难的,从有到丰富是更加困难的。
面向对象将程序的各个行为化为对象,而又用抽象的办法将这些对象归类(抽象),从而将错综复杂的事情简化为几个主要的有机组合(框架化)。
其实我们的身边很多东西都是这样组成的:
比如说电脑:
电脑是由主板,cpu加上各种卡组成的。
这就是一个框架化。
而忽略不同的cpu,不同的主板,不同的声卡,网卡,显卡的区别,这就是抽象。
再比如说现在的教育网:
是由主核心节点:
清华,北大,北邮等几个,然后是各个子节点,依次组成了整个教育网网络。
所以我觉得面向对象的编程思想就是:
一个大型工程是分层次结构的,每层又由抽象的结构连接为整体(框架化),各个抽象结构之间是彼此独立的,可以独立进化(继承,多态)。
层次之间,结构之间各有统一的通讯方式(通常是消息,事件机制)。
二、以前c语言编程中常用的“面向对象”方法
其实c语言诞生以来,人们就想了很多办法来体现“面向对象”的思想。
下面就来说说我所知道的方法。
先说一些大家熟悉的东东,慢慢再讲诡异的。
呵呵
1.宏定义:
有的人不禁要问,宏定义怎么扯到这里来了,我们可以先看一个简单的例子:
#definemacrofunctionafunction
然后在程序里面你调用了大量的afunction,但是有一天,你突然发现你要用bfunction了,(不过afunction又不能不要,很有可能你以后还要调用),这个时候,你就可以#definemacrofunctionbfunction来达到这样的目的。
当然,不得不说这样的办法是toosimple,sometimena?
ve的,因为一个很滑稽的问题是如果我一般要改为bfunction,一半不变怎么办?
那就只好查找替换了。
2.静态的入口函数,保证函数名相同,利用标志位调用子函数:
这样的典型应用很多,比如说网卡驱动里面有一个入口函数nilan(intfunctioncode,para*)。
具体的参数是什么记不清楚了。
不过nilan的主体是这样的:
longnilan(intfunctioncode,para*)
{
switch(functioncode){
casesendpacket:
send(….)
casereceivepacket:
receive(…)
…..
}
写到这里大家明白什么意思了吧。
保证相同的函数名就是说:
网卡驱动是和pna+协议栈互连的,那么如何保证pna+协议栈和不同的驱动都兼容呢,一个简单的办法就是仅仅使用一个入口函数。
通过改变如果函数的参数值,来调用内部的各个函数。
这样的做法是可以进化的:
如果以后想调用新的函数,增加相应的函数参数值就好了。
如果我们将网卡驱动和pna+协议栈看作两个层的话,我们可以发现:
层与层之间的互连接口是很小的(这里是一个入口函数),一般是采用名字解析的办法而不是具体的函数调用(利用functioncode调用函数,nilan仅仅实现名字解析的功能)!
接口限制和名字解析:
接口限制:
层与层之间仅仅知道有限的函数
名字解析:
层与层之间建立共同的名字与函数的对应关系,之间利用名字调用功能。
3.callback函数。
我觉得这是c语言的一个创举,虽然它很简单,就象如何把鸡蛋竖起来一样,但是你如果没想到的话,嘿嘿。
如果说静态入口函数实现了一个可管理的宏观的话,callback就是实现了一个可进化的微观:
它使得一个函数可以在不重新编译的情况下实现功能的添加!
但是在最最早期的时候,也有蛮多人持反对态度,因为它用了函数指针。
函数指针虽然灵活,但是由于它要访问内存两次才可以调用到函数,第一次访问函数指针,第二次才是真正的函数调用。
它的效率是不如普通函数的。
但是在一个不太苛刻的环境下,函数调用本身就不怎么耗时,函数指针的性能又不是特别糟糕,使用函数指针其实是一个最好的选择。
但是函数指针除了性能,最麻烦的地方就是会导致程序的“支离破碎”。
试想:
在程序中,你读到一个函数指针的时候,如果你愣是不知道这个函数指针指向的是哪个函数,那个感觉真的很糟糕。
(可以看后面的文章,要使用先进的程序框架,避免这样的情况)
三、event和message
看了上面的描述,相信大家多少有些明白为什么要使用event和message了。
具体的函数调用会带来很多的问题(虽然从效率上讲,这样做是很好的)。
为了提高程序的灵活性,event
和message的办法产生了。
用名字解析的办法代替通常的函数调用,这样,如果双方对这样的解析是一致的话,就可以达到一个统一。
不过event和message的作用还不仅仅是如此。
event和message还有建立进程间通信的功能。
进程将自己的消息发给“控制中心”(简单的就是一个消息队列,和一个while循环不断的取消息队列的内容并执行),控制程序得到消息,分发给相应的进程,这样其他进程就可以得到这个消息并进行响应。
event和message是很灵活的,因为你可以随时添加或者关闭一个进程,(仅仅需要添加分发消息的列表就可以了)event和message从程序实现上将我觉得是一样的,只不过概念不同。
event多用于指一个动作,比如硬件发生了什么事情,需要调用一个什么函数等等。
message多用于指一个指示,比如什么程序发生了什么操作命令等等。
四、小结
其实编程序和写文章一样,都是先有一个提纲,然后慢慢的丰富。
先抽象化得到程序的骨架,然后再考虑各个方面的其他内容:
程序极端的时候会发生什么问题?
程序的这个地方的功能现在还不完善,以后再完善会有什么问题?
程序是不是可以扩展的?
【篇三:
c语言的模块化设计和面向对象编程】
=txt>分类:
c面向对象/c++/java2011-03-0221:
04108人阅读评论(0)收藏举报
来自网易杭州研发技术总监“云风”blog的几篇面向对象设计的文章
c语言对模块化支持的欠缺
继续昨天的话题。
随便列些以后成书可能会写的东西。
既然书的主题是:
怎样构建一个(稍具规模的)软件。
且我选择用c为实现工具来做这件事情。
就不得不谈语言还没有提供给我们的东西。
模块化是最高原则之一(在《unix
编程艺术》一书中,unix哲学第一条即:
模块原则),我们就当考虑如何简洁明快的使用c语言实现模块化。
除开c/c++,在其它现在流行的开发语言中,缺少标准化的模块管理机制是很难想象的。
但这也是c语言本身的设计哲学决定的:
把尽可能多的可能性留给程序员。
根据实际的系统,实际的需要去定制自己需要的东西。
对于巨型的系统(比如windows这样的操作系统),一般会考虑使用一种二进制级的模块化方案。
由模块自己提供元信息,或是使用统一的管理方案(比如注册表)。
稍小一点的系统(我们通常开发接触到的),则会考虑轻量一些的源码级方案。
首先要考虑的往往是模块的依赖关系和初始化过程。
依赖关系可以放由链接器或加载器来解决。
尤其在使用c语言时,简单的静态库或动态库,都不太会引起大的麻烦。
c++则不然,c++的某些特性(比如模板类静态成员的构造)必须对早期只供c语言使用的链接器做一些增强。
即使是精心编写的c++库,也有可能出现一些意外的bug。
这些bug往往需要对编译,链接,加载过程很深刻的理解,才能查出来。
注:
我并不想以此来反对使用c++做开发。
我们需要着重管理的,是模块的初始化过程。
对于打包在一起的一个库(例如glibc,或是msvcrt),会在加载时有初始化入口,以及卸载时有结束代码。
我想说的不是这个,而是我们自己内部拆分的更小的模块的相互依赖关系。
谁先初始化,谁后初始化,这是一个问题。
在c++的语言级解决方案中,使用的是单件模块。
要么由链接器决定以怎样的次序来生成初始化代码,这,通常会因为依赖关系和实际构造次序不同而导致bug(注:
我在某几本c++书中都见过,待核实。
自己好久不写c++也没有实际的错误例子);要么使用惰性初始化方案。
这个惰性初始化也不是万能的,并且有些额外的开销。
(多线程环境中尤其需要注意)
我使用c语言做初期设计的时候,采用的是一种足够简单的方法。
就是,以编码规范来规定,每个模块必须存在一个初始化函数,有规范的名字。
比如foo模块的初始化入口叫intfoo_init()
规定:
凡使用特定模块,必须调用模块初始化函数。
为了避免模块重复初始化,初始化函数并不直接调用,而是间接的。
类似这样:
mod_using(foo_init);
mod_using负责调用初始化函数,并保证不重复调用,也可以检查循环依赖。
在这里,我们还约定了初始化成功于否的返回值。
(在我们的系统中,返回0表示正确,1表示失败)然后定义了一个宏来做这个使用。
#defineusing(m)if(mod_using(m##_init,#m)){return1;}
注:
我个人反对滥用宏。
也尽可能的避免它。
这里使用宏,经过了慎重的考虑。
我希望可以有一个代码扫描器去判断我是否漏掉了模块初始化(可能我使用了一个模块,但忘记初始化它)。
宏可以帮助代码扫描分析器更容易实现。
而且,使用宏更像是对语言做的轻微且必要的扩展。
这样,我的系统中模块模块的实现代码最后,都有一个init函数,里面只是简单的调用了using来引用别的模块。
例如:
#includemodule.h
/*
我个人偏爱把module.h的引入放在源文件最后,初始化入口之前。
它里面之定义了using宏,以及相关管理函数。
这样做是为了避免在代码的其它地方去引入别的模块。
*/
int
foo_init()
{
using(memory);//引用内存管理模块
using(log);//引用log模块
return0;
}
至于模块的卸载,大部分需求下是不需要的。
今天在这里就不论证这一点了。
浅谈c语言中模块化设计的范式
今天继续谈模块化的问题。
这个想慢慢写成个系列,但是不一定连续写。
基本是想起来了,就整理点思路出来。
主要还是为以后集中整理做点铺垫。
我们都知道,层次分明的代码最容易维护。
你可以轻易的换掉某个层次上的某个模块,而不用担心对整个系统造成很大的副作用。
层次不清的设计中,最糟糕的一种是模块循环依赖。
即,分不清两个模块谁在上,谁在下。
这个时候,最容易牵扯不清,其结果往往是把两者看做一体去维护算了。
这里面还涉及一些初始化次序等繁杂的细节。
其次,就是越层的模块联系。
当模块a是模块b的上层,而模块b又是模块c的上层,这个时候,让模块c对模块a可见,在模块a中有对c导出接口的直接调用,对于清晰的设计是很忌讳的一件事。
虽然,我们很难完全避免这个问题,去让a对c的调用完全通过b。
但通常应尽力为之。
(注:
以后写书的话,我争取补充一些实际的例子来说明)不过,对语言不原生支持的数据类型,以及基础设施,但却有必要创造出来给系统用的。
可以有些例外。
比如内存管理,log管理,字符串(c语言用原始库函数管理比较麻烦)等等,我们可能以基础模块的形式提供。
但却可能被不同层次的模块直接使用。
但,上到一定层次后,还是需要去隐藏它们的。
下面来一点更实际的分析。
以c语言为例,由于c语言缺乏namespace的原生支持,我们通常给api加上统一前缀来区分。
这倒也不麻烦。
那么模块a看起来就是一堆a_xxxxx为名字的方法。
我个人主张单个模块不宜过大,在实现时适合放在同一个.c文件里即可。
通常,一个模块会围绕一类对象处理。
这些对象可以用整数handle来表示,也可以用一个特定类型的对象指针。
两种方案各有千秋。
先来谈
对象指针的方案。
一个模块a的接口描述文件很可以是这样的(希望以后能补上更现实的代码):
#ifndef_a_h
#define_a_h
structa;
structb;
structa*a_create(void);
voida_release(structa*self);
voida_bind(structa*self,structb*b);
voida_commit(structa*self);
voida_update(void);
inta_init(void);
#endif
这里,我们定义了a这种数据类型。
我个人反对用typedef或宏来减少代码输入。
除非有特别的理由,都写上struct前缀,而不是定义出新类型。
尤其是在较底层的模块设计时更是如此。
在接口描述时,structa的细节是绝对不应该暴露出来的,它的数据结构应该仅存在于实现的文件a.c中。
关于a的接口通常分两类,一类是对structa*做一些处理的,那么就让第一个参数传入self指针。
这相当于c++的this指针。
比如上例中的a_commit;另一类接近于c++类的静态成员函数,通常用于对这一类对象全部做一个处理,如a_update。
注:
我无意用c去模拟c++,但基于一类数据类型做一些处理的方法,对于c,这样的写法也是一个常规的范式而已。
至于面向对象等在构建复杂系统时常用到的方法,以后我会谈谈我自己常用的另一些范式。
或许像c++,也可以不像。
怎么写更好,是个见任见智的问题。
不用过于拘泥。
这里的例子中,我们还提到了另一个数据类型b。
显然,它是放在b模块中的。
我们通常不会在a.h中去includeb.h,而只是声明一下structb。
(对于c语言来说,这并不必要,但写上是个好习惯)。
这是因为,如果b是位于a之下的模块,既在a模块的实现中,会用到b的方法,我们通常不会让用到a模块的人,可以看见b的接口。
包含a.h的同时隐式包含b.h就是不必要的了。
从范例代码中,我们可以猜想,structa是对structb的某种封装,可以通过对a的操作,间接操作到其中的b类型。
在a的模块初始化a_init中一定就会初始化b了。
如果是这样,b的层次就位于a之下。
往往structb中还会保留一个structa类型的引用。
首先,我们应该尽力避免这种情况。
即:
位于下层的b应该对上层的a一无所知是最好的。
如果在b模块中必须出现structa,那么我们应该至少保证,仅仅是structa*,一个引用,而绝对不能出现任何对a模块内接口的调用。
不要认为使用巧妙的方法,绕过循环依赖初始化问题就够了。
这应该是一个设计原则,不要去违反。
btw,草率的接口设计往往是日后系统脆弱的根源。
图一时之快,随意暴露一些接口,或是自以为聪明的用一些“巧妙”的方法,甚至是语法糖来绕过设计原则,都是很危险的。
一个常见的难处理的问题是:
如果structa和structb相互有双向引用。
怎样建立这个引用关系?
这个建立的过程,到底是a的方法,还是b的方法?
我的答案是,谁在上层,就是谁的方法。
但是a和b相互都看不见内部数据布局的细节,让b的内部对a类型做一个引用,比如也需要从b模块中暴露一个接口出来。
这个接口,可能仅供a使用。
在这个例子里,
就是仅供a_bind这个方法去使用。
如果是c++,我们或许会采用friend。
也可能使用其它一些技巧。
反正c++里可以挖掘的语法太多了。
但c怎么办?
下面给个我自己的方案。
原本,我们在b中导出的api是这样的:
voidb_set_a(structb*self,structa*a);
现在写成:
structi_a;
voidb_set_a(structb*self,structi_a*a);
在b.c的实现中,加一个函数用于structi_a*到structa*的转换。
staticinlinestructa*a(structi_a*a){return(structa*)a;}
然后在a.c的实现中,加一个类似函数用于转换structa*到structi_a*。
这样,在a.c之外,其它模块因为不能得到任何structi_a类型,而不会错误的使用b_set_a这个接口了。
我所偏爱的c语言面向对象编程范式
面向对象编程不是银弹。
大部分场合,我对面向对象的使用非常谨慎,能不用则不用。
相关的讨论就不展开了。
但是,某些场合下,采用面向对象的确是比较好的方案。
比如ui框架,又比如3d渲染引擎中的场景管理。
c语言对面向对象编程并没有原生支持,但没有原生支持并不等于不适合用c写面向对象程序。
反而,我们对具体实现方式有更多的选择。
大部分用c写面向对象程序的程序员受c++影响颇深。
企图用宏模拟出一个常见c++编译器已经实现的对象模型。
于我愚见,这并不是一个好的方向。
c++的对象模型,本质上是为了追求实现层的性能,并直接体现出来。
就有如在c++中被滥用的inline,的确有效,却破坏了分离原则。
c++的继承是过紧的耦合。
我所理解的面向对象,是让不同的数据元有共同的操作方式,适合成组的处理。
根据操作方式的不同,我们会对数据元做不同的分组。
一个数据可能出现在这个组里,也可以出现在那个组里。
这取决于你从不同的方面提取的共性。
这些可供统一操作的共性称之为接口(interface),接口在c语言中,表现为一组函数指针的集合。
放在c++中,即为虚表。
我所偏爱的面向对象实现方式(使用c语言)是这样的:
若有一组数据,我们需要让他们看起来都有一种叫作foo的共性。
把符合这样的数据都称为foo_object。
通常,我们会有如下api去操控foo_object。
structfoo_object;
structfoo_object*foo_create();
voidfoo_release(structfoo_object*);
voidfoo_dosomething(structfoo_object*);
在具体实现时,会在一个叫foo.c的实现文件中,定义出foo_object结构,里面有一些foo_dosomething所需的数据成员。
但是,以上还不能满足要求。
因为,我们会有不同的数据,他们只是表现出foo_object某些方面的特性。
对于不同的数据,它们在dosomething时,实际所做的操作也有所区别。
这时,我们需要定义出一个接口,供foo.c内部使用。
那么,以上的头文件就需要做一些修改,把接口i_foo的定义加进去,并修改create函数。
structi_foo{
void(*foobar)(void*);
};
structfoo_object*foo_create(structi_foo*iface,void*data);
这里稍做解释。
i_foo是供foo_dosomething内部使用的一组接口。
构造foo_object时,我们把一个外部数据data和为foo_object相关特性定义出的i_foo接口捆绑在一起,传入构造函数foo_create。
一般,我还会会每个符合foo_object特性的对象实现一个方法来得到对应的i_foo,如:
structfoobar;
structi_foo*foobar_foo(void);
structfoobar*foobar_create(void);
voidfoobar_release(structfoobar*);
创建一个foo_object对象的代码看起来是这样:
structfoobar*foobar=foobar_create();
structfoo_object*fobj=foo_create(foobar_foo(),foobar);
structfoo_object的定义中,必然要记录i_foo的接口指针和data数据指针。
从c++的观点看,foo_object是基类,它也会有一些基类成员和非虚的