Java,C#等许多语言不会将声明与实现分开。C#具有部分类的概念,但是实现和声明仍然保留在同一文件中。
为什么C ++没有相同的模型?头文件更实用吗?
我指的是当前和即将发布的C ++标准版本。
Java,C#等许多语言不会将声明与实现分开。C#具有部分类的概念,但是实现和声明仍然保留在同一文件中。
为什么C ++没有相同的模型?头文件更实用吗?
我指的是当前和即将发布的C ++标准版本。
Answers:
我通常在C#和C ++之间切换,而C#中缺少头文件是我最大的烦恼之一。我可以看一下头文件,了解有关类的所有知识,包括什么叫它的成员函数,它们的调用语法等,而无需遍历实现该类的代码页。
是的,我知道部分类和#region,但这并不相同。局部类实际上使问题变得更糟,因为一个类定义分布在多个文件中。就#region而言,它们似乎从来没有按照我现在想要的方式进行扩展,因此我必须花时间扩展那些小的优点,直到我正确地看待。
也许如果Visual Studio的intellisense对于C ++更好用,我就没有令人信服的理由不得不如此频繁地引用.h文件,但是即使在VS2008中,C ++的intellisense也无法触及C#。
头文件允许独立编译。您无需访问甚至不需要实现文件即可编译文件。这可以简化分布式构建。
这也使SDK的制作变得容易一些。您可以只提供标题和一些库。当然,还有其他语言可以使用的解决方法。
甚至Bjarne Stroustrup也将头文件称为kudge。
但是如果没有包含必要的元数据的标准二进制格式(例如Java类文件或.Net PE文件),我看不到任何实现此功能的方法。剥离的ELF或a.out二进制文件没有很多您需要提取的信息。而且我认为信息永远不会存储在Windows XCOFF文件中。
使用C可以轻松编写编译器。基于这一原则,它做了很多事情。指针的存在只是为了使编写编译器变得容易,而头文件也是如此。继承到C ++的许多东西都是基于与这些功能的兼容性而实现的,这些功能使编译器的编写更加容易。
实际上,这是个好主意。创建C时,C和Unix是一对。C移植了Unix,Unix运行了C。通过这种方式,C和Unix可以在平台之间迅速传播,而基于程序集的OS必须完全重写才能移植。
在一个文件中指定接口并在另一个文件中实现的概念根本不是一个坏主意,但这不是C头文件。它们只是限制编译器必须通过您的源代码进行传递的次数的一种方法,并且允许对文件之间的合同进行一些有限的抽象,以便它们可以进行通信。
这些项目,指针,头文件等……实际上并没有提供优于其他系统的任何优势。通过将更多的精力投入到编译器中,您可以像完全相同的对象代码的指针一样轻松地编译参考对象。这就是C ++现在要做的。
C是一种很棒的简单语言。它的功能集非常有限,您无需费力即可编写编译器。移植通常很简单!我并不是要说这是一种不好的语言,而是C语言的主要目标,可能是它在创建时的主要目标可能会残留现在或多或少不必要的语言,但为了兼容起见,它们会保留下来。
似乎有些人真的不相信C是写给Unix的端口,所以在这里:(from)
UNIX的第一个版本是用汇编语言编写的,但是Thompson的意图是将它用高级语言编写。
汤普森(Thompson)于1971年首次尝试在PDP-7上使用Fortran,但第一天就放弃了。然后,他写了一种非常简单的语言,称为B,后来在PDP-7上使用。它起作用了,但是有问题。首先,由于对实现进行了解释,因此它总是很慢。其次,基于面向字的BCPL的B的基本概念对于像新的PDP-11这样的面向字节的机器不合适。
里奇(Ritchie)使用PDP-11向B添加类型,有一段时间,B被称为“ New B”的NB,然后他开始为其编写一个编译器。Ritchie说:“因此C的第一阶段实际上是这两个阶段的短暂继承,首先是B的某些语言更改,实际上是在不对语法进行太多更改的情况下添加了类型结构;然后进行了编译器。”
他谈到用C重写UNIX时说:“第二阶段比较慢。” Thompson于1972年夏天开始,但是有两个问题:弄清楚如何运行基本的协同程序,即如何将控制从一个进程切换到一个进程。另一个; 由于原始版本的C没有结构,因此很难获得正确的数据结构。
里奇说:“事情的结合使肯在夏天放弃了。” “一年来,我添加了结构,并可能使编译器代码更好一些-更好了,因此在第二个夏天,那时我们共同努力,实际上用C重做了整个操作系统。”
这是我的意思的完美例子。从评论:
指针仅仅是为了使编写编译器更容易?不会。指针之所以存在,是因为它们是对间接概念最简单的抽象。–亚当·罗森菲尔德(一个小时前)
你是对的。为了实现间接,指针是最简单的可能实现的抽象。它们绝不是最容易理解或使用的。数组要容易得多。
问题?为了像指针一样高效地实现数组,您必须向编译器添加大量的代码。
没有理由他们不能没有指针而设计C,而是使用这样的代码:
int i=0;
while(src[++i])
dest[i]=src[i];
需要花很多精力(在编译器部分),以排除显式的i + src和i + dest添加项,并使其创建与此相同的代码:
while(*(dest++) = *(src++))
;
在事实之后排除该变量“ i”是HARD。新的编译器可以做到这一点,但那时还不可能,并且在如此糟糕的硬件上运行的OS几乎不需要这样的优化。
现在很少有系统需要这种优化(我使用的是最慢的平台之一-电缆机顶盒,而我们的大部分东西都用Java编写),而在极少数情况下,可能需要使用新的C编译器应该足够聪明才能自行进行这种转换。
在《 C ++的设计和演进》中,Stroustrup提出了另一个原因...
同一头文件可以具有两个或多个实现文件,这些文件可以由一个以上的程序员同时进行处理,而无需源代码控制系统。
这些天看起来似乎很奇怪,但是我想这是C ++发明时的一个重要问题。
#ifdef
-fu的情况下进行便携式应用程序的天赐之物。(只需使用myclass.o
从依赖于平台的myclass-win.cpp
/生成正确的makefile myclass-linux.cpp
,而外部只是在乎myclass.h
)
如果您想要不带头文件的C ++,那么对您来说是个好消息。
它已经存在,称为D(http://www.digitalmars.com/d/index.html)
从技术上讲,D似乎比C ++好很多,但目前它还不足以在许多应用程序中使用。
C ++的目标之一是成为C的超集,如果它不支持头文件,则很难做到这一点。而且,通过扩展,如果您希望删除头文件,则还可以考虑完全删除CPP(预处理器,而不是加号)。C#和Java均未使用其标准指定宏预处理器(但应注意,在某些情况下它们甚至可以与这些语言一起使用)。
由于C ++是目前设计的,因此您需要原型(就像在C中一样)来静态检查引用外部函数和类的任何已编译代码。如果没有头文件,则在使用它们之前必须先键入这些类定义和函数声明。为了使C ++不使用头文件,您必须使用一种语言来支持Javaimport
关键字之类的功能。那将是一个重要的补充,并且会改变;回答您是否可行的问题:我不这么认为-完全没有。
许多人意识到头文件的缺点,并且有一些想法可以将更强大的模块系统引入C ++。您可能想看看Daveed Vandevoorde撰写的C ++ Modules(修订版5)。
在实现文件的单独组件中定义类接口很有价值。
它可以通过接口来完成,但是如果您走那条路,那么您就隐含地说类在将实现与契约分离方面是不足的。
模块化2具有正确的想法,定义模块和实现模块。 http://www.modula2.org/reference/modules.php
Java / C#的答案是相同的隐式实现(尽管是面向对象的。)
头文件令人费解,因为头文件表达了实现细节(例如私有变量)。
在转向Java和C#时,我发现如果一种语言需要IDE支持开发(例如,公共类接口可以在类浏览器中导航),那么这也许是一条声明,说明代码并不具有自身的优点,因为特别可读。
我发现接口与实现细节的混合非常可怕。
至关重要的是,缺乏将公共类签名记录在与实现无关的简洁注释的文件中的能力,这向我表明,语言设计的编写是为了方便作者,而不是为了维护。好吧,我现在正在谈论Java和C#。
如果您想要这种情况永远不会发生的原因:它将破坏几乎所有现有的C ++软件。如果您查看一些C ++委员会设计文档,他们将查看各种替代方法以查看破坏多少代码。
将switch语句更改为一半智能将容易得多。那只会破坏一点代码。它仍然不会发生。
为新想法而编辑:
使C ++头文件成为必需的C ++和Java之间的区别是C ++对象不一定是指针。在Java中,所有类实例均由指针引用,尽管看起来并非如此。C ++在堆和堆栈上分配了对象。这意味着C ++需要一种方法来知道对象的大小以及数据成员在内存中的位置。
没有头文件,不存在任何语言。这是一个神话。
查看Java的任何专有库发行版(我没有C#经验,但是我希望它是相同的)。他们没有给您完整的源文件。他们只是给您一个文件,每个方法的实现都被遮盖({}
或{return null;}
类似内容),并且隐藏起来可以摆脱一切。除了标题,您什么都不能叫。
但是,没有技术上的原因,为什么C或C ++编译器可以将带有适当标记的文件中的所有内容都计为,extern
除非直接编译该文件。但是,由于C和C ++都无法快速解析,因此编译成本非常高,这是一个非常重要的考虑因素。任何更复杂的合并标题和源代码的方法都会很快遇到技术问题,例如需要编译器知道对象的布局。
头文件是语言的组成部分。没有头文件,所有静态库,动态库以及几乎所有预编译的库都将变得无用。头文件还使记录所有内容变得更加容易,并使查看库/文件的API成为可能,而无需遍历每一位代码。
它们还使组织程序变得更加容易。是的,您必须不断地从源代码切换到标头,但是它们还允许您在实现中定义内部和私有API。例如:
MySource.h:
extern int my_library_entry_point(int api_to_use, ...);
MySource.c:
int private_function_that_CANNOT_be_public();
int my_library_entry_point(int api_to_use, ...){
// [...] Do stuff
}
int private_function_that_CANNOT_be_public() {
}
如果有#include <MySource.h>
,那你就得到了my_library_entry_point
。
如果有的话#include <MySource.c>
,你也会得到private_function_that_CANNOT_be_public
。
您会发现,如果拥有一个用于获取密码列表的功能,一个实现了加密算法的功能,一个可以公开操作系统内部的功能,或一个覆盖特权的功能,那将是一件非常糟糕的事情,等等
哦,是的!
用Java和C#编码后,每个类都有2个文件确实很烦人。所以我在想如何在不破坏现有代码的情况下合并它们。
实际上,这确实很容易。只需将定义(实现)放在#ifdef节中,然后在编译器命令行上添加一个定义即可编译该文件。而已。
这是一个例子:
/* File ClassA.cpp */
#ifndef _ClassA_
#define _ClassA_
#include "ClassB.cpp"
#include "InterfaceC.cpp"
class ClassA : public InterfaceC
{
public:
ClassA(void);
virtual ~ClassA(void);
virtual void methodC();
private:
ClassB b;
};
#endif
#ifdef compiling_ClassA
ClassA::ClassA(void)
{
}
ClassA::~ClassA(void)
{
}
void ClassA::methodC()
{
}
#endif
在命令行上,使用以下命令编译该文件
-D compiling_ClassA
其他需要包含ClassA的文件也可以
#include "ClassA.cpp"
当然,可以通过宏扩展(Visual Studio编译器)或自动变量(gnu make)轻松地在命令行上添加定义,并为定义名称使用相同的命名法。