c 项目 c语言适合学生做的小项目

c 项目 c语言适合学生做的小项目

【CSDN编者按】C和C++一直是优秀的编程语言。然而,虽然两种语言的名称有些相似,但在应用场景上却有着巨大的差异。对于C语言来说,主要用于操作系统、容器、物联网、数据库等领域的开发,而C++是开发桌面软件、图形处理、游戏、网站的最佳工具。在这篇文章中,作者认为C++在开发基础设施方面会更好,但在尝试与C语言进行比较后,发现事实并非如此。

原文链接:https://250bpm.com/blog:4/? continue flag = c 778 B3 c 9525d 12 a 012 a 35269d 830 ebcc

声明:本文由CSDN翻译,转载时应注明出处。

作者 | Martin Sústrik 译者 | 弯月 责编 | 屠敏
出品 | CSDN(ID:CSDNnews)

以下是翻译文本:

首先,我整个职业生涯都在使用C++,C++仍然是我大多数项目的首选。

因此,当我开始构建我的个人项目ZeroMQ(一个用于可伸缩分布式或并发应用程序设计的高性能异步消息库)时,我也选择了C++,原因如下:

C++包含一些数据结构和算法库。如果我用C语言,我就得依赖第三方库,或者自己写基本算法。

C++将迫使我在编程风格上保持一些基本的统一。例如,该参数不允许几种不同的机制将指针传递给正在处理的对象,这是C项目中的常见问题。同样,成员变量也不能显式标记为私有,C++还有一些其他的特性。

用C语言实现虚函数非常复杂,会增加代码理解和管理的难度。不过严格来说,这个问题其实是上一个问题的子集,只是我觉得有必要单独指出来。

最后,大家都喜欢在代码末尾自动调用析构函数。

然而,到现在为止,我不得不承认C++是一个糟糕的选择。现在,让我解释一下为什么。

首先,我个人的项目ZeroMQ是一个持续运行的基础设施,它应该永远不会崩溃,永远不会出现未定义的行为。所以错误处理很重要,一定要清晰严格。

但是C++中的异常处理不能满足我的需求。如果程序不能出错,那么选择C++是没有问题的,只需要在try/catch中包装主函数,一个地方处理所有的错误。

如果你的目标是确保不会出现未定义的行为,那么C++的异常处理就会成为一场噩梦。因为C++将异常的发生和处理解耦,所以处理错误非常容易,但这也使得几乎不可能保证程序永远不会运行未定义的行为。

在C语言中,错误的生成和处理紧密地结合在同一个源代码中。因此,很容易理解出错时会发生什么:

int RC = FX();如果(rc!= 0)handle _ error();在C++中,你只能抛出一个错误,但你不知道发生了什么:

int RC = FX();如果(rc!= 0)抛出STD::exception();问题是你不知道在哪里处理异常。处理错误的代码在同一个函数中会更容易理解,尽管它不容易阅读:

尝试{…int RC = FX();如果(rc!= 0)抛出std::exception (“Error!”);…异常& ampe) {handle_exception()。然而,让我们考虑一下当同一个函数抛出两个不同的错误时会发生什么:

类异常1 { };类异常2 { };尝试{…if (condition1)抛出my _ exception 1();…if (condition2)抛出my _ exception 2();…} catch(my _ exception 1 & amp;e){ handle _ exception 1();} catch(my _ exception 2 & amp;e){ handle _ exception 2();}下面是等价的C代码:

…if(condition 1)handle _ exception 1();…if(condition 2)handle _ exception 2();…相比之下,C语言更便于阅读,编译器会生成更高效的代码。

但是,C++的问题不仅限于此。考虑函数抛出异常但不处理它的情况。在这种情况下,错误处理可以放在任何地方,这取决于调用函数的位置。

根据不同的情况用不同的方式处理异常?这种方法听起来很合理,但很快就会变成噩梦。

在修复一个Bug的时候,你会发现很多其他地方也有同样的Bug,因为他们都复制了同样的错误处理代码。每次添加函数调用时,都有可能添加新的异常。如果调用代码没有正确处理异常,就意味着添加了一个新的Bug。

如果你还想坚持“没有未定义行为”的原则,你就不得不引入新的异常来区分不同的失效模式。然而,增加一个新的例外意味着它将上升到一个不同的地方。必须到处添加相应的异常处理,否则会出现未定义的行为。

看到这里,你可能想说:这是exception的正确用法吗?

但问题是,exception只是一个以更系统的方式管理指数级增长的错误处理代码的工具,却无法解决根本问题。甚至可以说,异常可能会使情况恶化,因为你不仅需要编写新的异常类型,还需要为新类型编写异常处理代码。

考虑到以上问题,我决定用C++,但不例外。现在我的这个项目就是这样实现的。

不幸的是,问题不止于此…

想想看,如果对象初始化失败会怎么样?构造函数没有返回值,所以只能通过抛出异常来报告失败。然而,我决定不使用异常。因此,我们必须对其进行如下处理:

类foo { public:foo();int init();…};创建实例时,调用构造函数(此函数不会失败),然后调用init函数(此函数可能会失败)。

与C语言相比,C++代码更复杂:

结构foo{…};int foo _ init(struct foo * self);然而,C++代码的真正问题是,如果开发人员在构造函数中编写一些代码,会发生什么?

在这种情况下,将出现一个特殊的新对象状态。因为对象已经构造好了,但是init函数还没有调用,所以处于“半初始化”状态。我们应该修改对象(尤其是析构函数)来处理这个新状态。这意味着向每个方法添加新的条件。

有人可能想说,这不就是你人为加了不使用异常的限制吗?!如果在构造函数中抛出异常,C++运行时将正确清理对象,不会出现“半初始化”状态。

然而,话虽如此,问题是如果使用异常,如上所述,所有与异常相关的复杂性都必须处理。对于需要在发生故障时表现出出色健壮性的基础设施组件来说,这不是一个合理的选择。

另外,即使初始化没有问题,对象的销毁也一定会遇到问题。你不能在析构函数中抛出异常。这不是我人为强加的限制,而是因为如果在进程中调用了析构函数,或者恢复堆栈时恰好抛出了异常,整个进程就会崩溃。

因此,如果销毁可能失败,您需要两个单独的函数来处理它:

foo类{公共:…int term();~ foo();};这遇到了和初始化一样的问题:一个“半终止”状态,我们必须以某种方式处理它,并给每个成员函数添加新的条件。

类foo { public:foo():state(semi _ initialized){…}int init (){if(状态!= semi _ initialised)handle _ state _ error();…state =初始化;}int term (){if (state!=初始化)handle _ state _ error();…状态= semi _ terminated}~foo (){if (state!= semi _ terminated)handle _ state _ error();…}int bar (){if(状态!=初始化)handle _ state _ error();…}};相比之下,C语言的代码如下。只有两种状态。对象/内存没有初始化,所以我们不需要担心上面的问题,结构可以包含任意数据。而且只要对象进入初始化状态,就可以正常工作。因此,对象中不需要状态机:

结构foo{…};Intfoo _ init () {…Intfoo _ $ TERM () {…} Intfoo _ bar () {…}想想如果在上面的代码中加入继承会怎么样。C++允许基类作为派生类的构造函数的一部分被初始化。如果抛出异常,成功初始化的对象将被销毁:

类foo:public bar { public:foo():bar(){ }…};然而,一旦引入单独的init函数,状态的数量将开始增加。除了未初始化、半初始化、初始化和半终止状态,你还会遇到这些状态的组合。可以想象一个对象,它的基类是完全初始化的,但它的派生类是半初始化的。

对于这样一个对象,几乎不可能保证它的行为没有问题。对象的半初始化和半终止部分有许多不同的组合,由于它们只在极少数情况下引起错误,大多数相关代码可能不经测试就进入生产。

综上所述,我觉得如果你的需求是不允许未定义的行为,那就不适合面向对象编程。这个问题不仅限于C++,任何带有构造函数和析构函数的面向对象语言都不适合。

所以更适合面向对象语言的项目是:对开发速度有要求,但对“没有未定义行为”没有太高的要求。

这个问题没有灵丹妙药。c语言更适合系统编程。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。

发表回复

登录后才能评论