当前位置:  开发笔记 > 编程语言 > 正文

C#开发人员的C++

如何解决《C#开发人员的C++》经验,为你挑选了1个好方法。

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

引用/使用其他库

公开我的库以供其他人使用

字符串处理

数据类型转换

良好的项目结构

要使用的数据结构(即在C#中,我经常使用,我List在C++中使用的是什么,它可以同时工作?)

它几乎感觉就像你使用的IDE一样,指南是不同的,所以我真的在寻找一些可能更普遍的东西.或者最糟糕的是,专注于使用Microsoft的编译器/ IDE.另外,为了清楚起见,我不是在寻找有关通用编程实践(设计模式,代码完成等)的任何内容,因为我觉得我非常精通这些主题.



1> jalf..:

我知道你说你已经很好地掌握了指针和内存管理,但我仍然想解释一个重要的技巧.作为一般经验法则,您的用户代码中永远不会有新/删除.

每个资源获取(无论是同步锁,数据库连接还是一块内存还是必须获取和释放的任何其他内容)都应该包装在一个对象中,以便构造函数执行获取,析构函数释放资源.该技术称为RAII,基本上避免内存泄漏的方法.习惯它.C++标准库显然广泛使用它,因此您可以了解它在那里的工作原理.在您的问题跳了一下,相当于Liststd::vector,它使用RAII来管理其内存.你会用这样的东西:

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 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.
} 

如果我必须选择一个C++程序员必须学习和拥抱的原则,那就是上面的原则.让范围规则和析构函数适合您.它们提供了编写安全代码所需的所有保证.

字符串处理:

std::string是你的朋友.在C中,你会使用char(或char指针)数组,但这些数组很讨厌,因为它们不像字符串那样.在C++中,你有一个std :: string类,它的行为与你期望的一样.唯一要记住的是"hello world"是char [12]类型而不是std :: string.(对于C兼容性),所以有时你必须显式地将你的字符串文字(用引号括起来的东西,比如"hello world")转换为std :: string来获得你想要的行为:你仍然可以写

std::string s = "hello world";

因为C风格的字符串(例如文字,比如"hello world")可以隐式转换为std :: string,但它并不总是有效:"hello"+"world"将无法编译,因为+运算符不是没有为两个指针定义."hello worl"+'d'然而,编译,但它不会做任何明智的事情.它不是将char附加到字符串,而是取char的整数值(将其提升为int),并将其添加到指针的值.

std :: string("hello worl")+"d"就像你期望的那样,因为左侧已经是std :: string了,并且为了std :: string而重载了加法运算符我希望,即使右手边是char*或单个角色.

关于字符串的最后一点说明:std :: string使用char,它是单字节数据类型.也就是说,它不适合unicode文本.C++提供宽字符类型wchar_t,取决于平台,为2或4个字节,通常用于unicode文本(尽管在这两种情况下,C++标准都没有真正指定字符集).而一串wchar_t被称为std :: wstring.

图书馆:

从根本上说,它们并不存在.C++语言没有库的概念,这需要一些人习惯.它允许你#include另一个文件(通常是扩展名为.h或.hpp的头文件),但这只是一个逐字复制/粘贴.预处理器简单地组合了两个文件,从而产生了所谓的翻译单元.多个源文件通常包含相同的头文件,并且仅在某些特定情况下有效,因此这一点对于理解C++编译模型至关重要,这是众所周知的古怪.而不是编译一堆独立的模块,并在它们之间排出某种元数据,而不像C#编译器那样,每个翻译单元都是独立编译的,

当然,有特定于平台的方法来编写库.在Windows上,您可以创建.dll或.libs,区别在于.lib链接到您的应用程序,而.dll是一个单独的文件,您必须与您的应用程序捆绑,就像在.NET中一样.在Linux上,等效的文件类型是.so和.a,并且在所有情况下,您还必须提供相关的头文件,以便能够针对您的库进行开发.

数据类型转换:

我不确定你到底在找什么,但我觉得有一点重要的是,下面的"传统"演员是不好的:

int i = (int)42.0f; 

有几个原因.首先,它尝试按顺序执行几种不同类型的强制转换,您可能会惊讶于编译器最终应用哪一种.其次,在搜索中很难找到,第三,它不够丑陋.通常最好避免使用强制转换,而在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(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(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(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(T);

正如您将注意到的,这些强制转换更具体,这意味着如果强制转换无效,编译器可能会给您一个错误(与传统语法不同,它只是尝试上述任何一个强制转换,直到它找到一个有效的),它又大又冗长,允许你搜索它,并提醒你应该尽可能避免它们.;)

标准库:

最后,回到数据结构,努力理解标准库.它很小,但功能多样,一旦你学会了如何使用它,你就会处于更好的位置.

标准库由几个非常不同的构建块组成(库随着时间的推移而累积.它的一部分从C端口移植.I/O流库从一个地方采用,并且采用容器类及其相关功能来自一个完全不同的库,并且设计明显不同.后者是通常被称为STL(标准模板库)的一部分.严格地说,这是图书馆的名称,稍加修改,被采纳到C++标准库.

STL是理解"现代C++"的关键.它由三个支柱,容器,迭代器和算法组成.简而言之,容器公开迭代器,算法在迭代器对上工作.

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

int add1(int i) { return i+1; } // The function we wish to apply

void foo() {
  std::vector 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 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
}

上面的风格需要一些习惯,但它非常强大和简洁.因为转换函数是模板化的,所以它可以接受任何转换函数类型作为输入,只要它们表现为迭代器.这意味着只要迭代器被设计为与STL兼容,该函数就可以用于组合任何类型的容器,甚至是流或其他任何可以迭代的容器.我们也不必使用开始/结束对.我们可以通过一个指向第三个元素而不是结束迭代器,然后算法就会停在那里.或者我们可以编写自定义迭代器,跳过其他所有元素,或者我们喜欢的任何其他元素.以上是三个支柱中每个支柱的基本例子.我们使用容器来存储我们的数据,但是我们用来处理它的算法实际上并不需要知道容器.它只需知道它必须工作的迭代器范围.

从某种意义上说,这与LINQ非常相似,所以既然你来自.NET,你可能会看到一些类比.STL对应物稍微灵活一点,但代价是略微怪异的语法.:)(正如评论中提到的,它也更有效.一般来说,STL算法的开销为零,它们可以像手动编码循环一样高效.这通常是令人惊讶的,但是可能因为所有相关类型都是在编译时已知(这是模板工作的要求),并且C++编译器倾向于积极地内联.)


美丽的答案,我唯一要添加的是使用std :: swap,因为它是现代C++的一部分,与构造,销毁和赋值一样重要,有助于以异常安全的方式实现赋值并支持转移所有权像std :: vectors这样的大事.
教科书答案.值得指出的是,与LINQ不同,这些算法在没有*任何*封装相关开销的情况下执行,与组装级别的硬编码循环一样高效(至少在具有良好STL实现的正常编译器中).这总是令人惊讶.
推荐阅读
手机用户2402851335
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有