用于C#开发人员的C ++

我是一个.NET开发人员,并在此之前使用VB6。 我已经非常熟悉这些环境,并在垃圾收集语言的环境下工作。 不过,我现在希望用本地C ++加强自己的技能,发现自己有点不知所措。 具有讽刺意味的是,我并不认为这是初学者的绊脚石,因为我觉得我已经很好地掌握了指针和内存pipe理。 对我来说有点混乱的事情更多的是:

  • 引用/使用其他库
  • 公开我的图书馆供他人使用
  • string处理
  • 数据types转换
  • 良好的项目结构
  • 要使用的数据结构(即在C#中,我使用List<T>很多,我在C ++中使用什么工作类似?)

它几乎感觉取决于你使用的IDE,指南是不同的,所以我真的在寻找一些可能更普遍的东西。 或者在最坏的情况下,专注于使用微软的编译器/ IDE。 另外,为了清楚起见,我并不是在寻找关于一般编程实践(devise模式,代码完成等)的东西,因为我觉得我对这些主题非常熟悉。

我知道你说你已经掌握了指针和内存pipe理,但是我仍然想解释一个重要的技巧。 作为一般的经验法则, 不要在你的用户代码中有新的/删除。

每个资源获取(无论是同步锁,数据库连接或内存块还是其他必须获取和释放的内容)都应该包装在一个对象中,以便构造函数执行采集,并且析构函数释放资源。 该技术被称为RAII ,基本上避免内存泄漏的方法。 习惯它。 C ++标准库显然是广泛使用的,所以你可以感受一下它是如何工作的。 在你的问题中跳转一点,相当于List<T>std::vector<T> ,它使用RAII来pipe理它的内存。 你会用这样的东西:

 void foo() { // declare a vector *without* using new. We want it allocated on the stack, not // the heap. The vector can allocate data on the heap if and when it feels like // it internally. We just don't need to see it in our user code std::vector<int> v; v.push_back(4); v.push_back(42); // Add a few numbers to it // And that is all. When we leave the scope of this function, the destructors // of all local variables, in this case our vector, are called - regardless of // *how* we leave the function. Even if an exception is thrown, v still goes // out of scope, so its destructor is called, and it cleans up nicely. That's // also why C++ doesn't have a finally clause for exception handling, but only // try/catch. Anything that would otherwise go in the finally clause can be put // in the destructor of a local object. } 

如果我必须select一个C ++程序员必须学习和拥抱的单一原则,那就是上面的。 让范围规则和析构函数为你工作。 他们提供所有需要编写安全代码的保证。

string处理:

std::string是你的朋友。 在C中,你会使用char(或char指针)数组,但这些都是讨厌的,因为它们不像string。 在C ++中,您有一个std :: string类,其行为与您所期望的相同。 唯一要记住的是“hello world”的types是char [12]而不是std :: string。 (对于C兼容性),所以有时你必须显式地将你的string文字(用引号括起来,比如“hello world”)转换成std :: string来得到你想要的行为:你仍然可以写

 std::string s = "hello world"; 

因为C风格的string(比如像“hello world”这样的文字)可以隐式转换为std :: string,但是它并不总是工作:“hello”+“world”不会编译,因为+运算符isn没有定义为两个指针。 “hello worl”+'d'然而编译,但它不会做任何明智的事情。 而不是追加一个string,它将采取char(它被提升为一个int)的整数值,并将其添加到指针的值。

std :: string(“hello world”)+“d”会像你期望的那样做,因为左边已经是一个std :: string,并且加法运算符被std :: string重载,即使在右侧是char *或单个字符的情况下也是如此。

关于string的最后一个注意事项:std :: string使用char,它是一个单字节的数据types。 也就是说,它不适合unicode文本。 C ++提供的宽字符typeswchar_t是2或4个字节,具体取决于平台,通常用于Unicode文本(尽pipeC ++标准没有真正指定字符集)。 而一个wchar_t的string被称为std :: wstring。

图书馆:

它们根本不存在。 C ++语言没有库的概念,这需要一些习惯。 它允许你#包括另一个文件(通常是扩展名为.h或.hpp的头文件),但这只是一个逐字拷贝/粘贴。 预处理器简单地组合这两个文件,产生所谓的翻译单元。 多个源文件通常会包含相同的头文件,并且只能在某些特定的情况下使用,所以这一点对于理解C ++编译模型是非常关键的,而C ++编译模型是古怪的。 不是编译一堆单独的模块,而是在它们之间传播某种元数据,就像C#编译器所做的那样,每个翻译单元都是独立编译的,所生成的目标文件被传递给一个链接器,然后尝试合并公共位(如果多个翻译单元包含相同的标题,则实际上代码跨翻译单元复制,因此链接程序将它们合并成单个定义);)

当然有平台特定的方式来编写库。 在Windows上,您可以制作.dll或.libs,不同之处在于.lib链接到您的应用程序,而.dll是一个单独的文件,您必须与应用程序捆绑在一起,就像在.NET中一样。 在Linux上,等效的文件types是.so和.a,而且在任何情况下,您都必须提供相关的头文件,以便人们能够针对您的库进行开发。

数据types转换:

我不确定你到底在找什么,但有一点我觉得很重要的是,下面这个“传统”的performance很糟糕:

 int i = (int)42.0f; 

有几个原因。 首先,它试图按顺序执行几种不同types的转换,而编译器最终会应用哪一种types。 其次,在search中很难find,第三,它不够丑陋。 通常最好的方法是避免使用Cast,而在C ++中,它们会让人觉得这很难看。 ;)

 // The most common cast, when the types are known at compile-time. That is, if // inheritance isn't involved, this is generally the one to use static_cast<U>(T); // The equivalent for polymorphic types. Does the same as above, but performs a // runtime typecheck to ensure that the cast is actually valid dynamic_cast<U>(T); // Is mainly used for converting pointer types. Basically, it says "don't perform // an actual conversion of the data (like from 42.0f to 42), but simply take the // same bit pattern and reinterpret it as if it had been something else). It is // usually not portable, and in fact, guarantees less than I just said. reinterpret_cast<U>(T); // For adding or removing const-ness. You can't call a non-const member function // of a const object, but with a const-cast you can remove the const-ness from // the object. Generally a bad idea, but can be necessary. const_cast<U>(T); 

正如你会注意到的,这些转换更具体,这意味着如果转换是无效的,编译器会给你一个错误(不同于传统的语法,它只是尝试上述任何一个转换,直到find一个转换),而且它很大很冗长,可以让你search它,并提醒你尽可能避免它们。 ;)

标准库:

最后,回到数据结构,花点力气去了解标准库。 它很小,但非常灵活,一旦你学会了如何使用它,你将处于一个更好的位置。

标准库由几个完全不同的构build块组成(库随着时间而积累,部分从C中移植过来)。I / Ostream库从一个地方采用,采用容器类及其相关的function来自一个完全不同的库,并且被devise得明显不同,后者是通常被称为STL(标准模板库)的一部分,严格来说,就是稍微修改过的库的名称。 C ++标准库。

STL是理解“现代C ++”的关键。 它由三个支柱,容器,迭代器和algorithm组成。 简而言之,容器展示迭代器,algorithm在迭代器对上工作。

下面的示例使用int的向量,将每个元素加1,并将其复制到链接列表中,仅作为示例:

 int add1(int i) { return i+1; } // The function we wish to apply void foo() { std::vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); v.push_back(5); // Add the numbers 1-5 to the vector std::list<int> l; // Transform is an algorithm which applies some transformation to every element // in an iterator range, and stores the output to a separate iterator std::transform ( v.begin(), v.end(), // Get an iterator range spanning the entire vector // Create a special iterator which, when you move it forward, adds a new // element to the container it points to. The output will be assigned to this std::back_inserter(l) add1); // And finally, the function we wish to apply to each element } 

上面的风格需要一些习惯,但它是非常强大和简洁。 由于转换函数是模板化的,只要它们像迭代器那样工作,就可以接受任何types的input。 这意味着,只要迭代器被devise为与STL兼容,函数就可以用来组合任何types的容器,甚至可以迭代的任何其他types的容器。 我们也不必使用开始/结束对。 而不是结束迭代器,我们可以通过一个指向第三个元素,然后algorithm将停止在那里。 或者我们可以编写自定义的迭代器,它跳过了其他所有元素,或者其他任何我们喜欢的东西。 以上是三大支柱的基本范例。 我们使用一个容器来存储我们的数据,但是我们用来处理它的algorithm实际上并不需要知道容器。 它只需要知道它必须工作的迭代器范围。 当然这三个支柱中的每一个都可以通过编写新的课程来扩展,这些课程将与STL的其他部分一起顺利运行。

从某种意义上说,这与LINQ非常相似,所以既然你是从.NET来的,那么你可能会看到一些类比。 然而,STL的对应方式稍微灵活一点,但代价是稍微有一些怪异的语法。 :)(正如在注释中提到的那样,它也更有效率,一般来说,STLalgorithm没有任何开销,它们可以和手写代码循环一样高效,这通常是令人惊讶的,但是可能的原因是所有相关的types都是在编译时已知(这是模板工作的要求),C ++编译器倾向于内联激进)。

你有一些可用的工具包。 例如,STL(标准模板库)和Boost / TR1(STL的扩展)被认为是行业标准(至lessSTL是)。 这些提供列表,地图,集合,共享指针,string,stream和各种其他方便的工具。 最重要的是,它们在编译器中得到了广泛的支持。

至于数据转换,您可以执行转换或创build显式转换器function。

库 – 您可以创build静态库(吸收到最终的可执行文件)或DLL(您已经熟悉这些库)。 MSDN是一个很棒的DLL资源。 静态库取决于您的构build环境。

一般来说,这是我的build议: – 非常熟悉你的IDEselect – 购买Herbert Schildt的“C ++ The Complete Reference”,我认为它是所有C ++(包括STL)

考虑到你的背景,一旦你做了这两件事,就应该做好准备。

我不会重复别人对图书馆等方面的评论,但是如果你对C ++认真的话,请帮个忙,拿起Bjarne Stroustrup的“C ++编程语言”。

我用C ++工作了好几年,终于拿了一份,一旦我做了,我花了一个下午,拍了一下额头,说:“当然了,我应该已经实现了!

(具有讽刺意味的是,我和K&R的“The C Programming Language”有着相同的经验。有一天,我将学会在第一天学习“The Book”。)

我为这样的程序员写了一个小抄表 。 您也可能对更复杂的案例感兴趣,例如可变参数

引用和使用其他库,如果你包含源代码,只需简单地通过#include库的头文件到你所需要的任何.cpp文件(然后和你的项目一起编译库的源文件)来完成。 但是,大多数情况下,您可能会使用.lib(静态库)或.dll(dynamic库)。 大多数(所有?)DLL都带有.lib文件,因此两种types的过程是相同的:在需要的地方包含适当的头文件,然后在链接步骤中添加相关的.lib文件(在Visual Studio中,I认为你可以把文件添加到项目中)。

自从我创build了自己的库供其他人使用已经很长时间了,所以我会让其他人回答这个问题。 否则我会回来编辑明天的答案,因为我将不得不为明天的工作创build一个.lib文件:)

string的东西通常用std :: string来完成。 在特殊情况下,你也可以使用旧式的C型sprintf()函数,但这一般是不鼓励的。

至于你正在寻找的数据结构,检查出STL(标准模板库)。 它包括你应该熟悉的List,Vector,Map,String等等。 我不知道你的意思是什么types的转换…我假设你知道铸造,所以你必须意味着比这更复杂的东西,在这种情况下,它可能是特定于您要转换的types。 也许别人可以提供更多的信息。

为了响应“引用/使用其他库”

有关在Windows和Linux的C / C + +中显式加载DLL的信息包括…

视窗:

Windows DLL教程

函数: LoadLibrary,GetProcAddress,FreeLibrary

Linux的:

函数: dlopen,dlsym,dlerror,dlclose

Linux DLL教程