C++ для разработчика C#


Я разработчик .NET, и работал с VB6 до этого. Я очень хорошо знаком с этими средами и работаю в контексте языков сбора мусора. Однако теперь я хочу укрепить свой набор навыков с помощью родного C++ и немного перегружен. По иронии судьбы, это не то, что я себе представляю, это обычные камни преткновения для начинающих, поскольку я чувствую, что у меня есть понимание указателей и управления памятью довольно хорошо. То, что меня немного смущает, больше похоже на строки:

  • ссылки/использование других библиотек
  • разоблачение мой библиотеки для других, чтобы использовать
  • обработка строк
  • преобразование типов данных
  • хорошая структура проекта
  • структуры данных (т. е. в C#, я использую List<T> много, что я использую в C++, который работает аналогично?)

это почти похоже на то, что в зависимости от IDE, которую вы используете, рекомендации разные, поэтому я действительно искал за то, что, возможно, немного более универсальным. Или, в худшем случае, сосредоточены на использовании компилятора/IDE Microsoft. Кроме того, чтобы быть ясным, я ничего не ищу об общих методах программирования (шаблоны проектирования, полный код и т. д.) как я чувствую, я довольно хорошо разбираюсь в этих темах.

6 52
c++

6 ответов:

Я знаю, что вы говорите, что у вас есть хорошее понимание указателей и управления памятью, но я бы хотел объяснить важную хитрость. Как правило, никогда есть новый / удалить в коде пользователя.

каждый сбор ресурсов (будь то блокировка синхронизации, соединение с базой данных или фрагмент памяти или что-либо еще, что должно быть получено и выпущено) должен быть обернут в объект, чтобы конструктор выполнял сбор, а деструктор освобождает ресурс. Этот метод известен как RAII, а фактически the способ избежать утечек памяти. Привыкай к этому. Стандартная библиотека C++, очевидно, широко использует это, поэтому вы можете почувствовать, как она работает там. Прыжки немного в ваших вопросах, эквивалент List<T> и std::vector<T>, и он использует 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<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.
} 

если бы мне пришлось выбрать один единственный принцип, который должен программист C++ учись и обнимайся, это все вышесказанное. Пусть правила определения области и деструкторы работают на вас. Они предлагают все гарантии, необходимые для написания безопасного кода.

обработка строк:

std::string ваш друг там. В C вы бы использовали массивы символов (или указателей символов), но они неприятны, потому что они не ведут себя как строки. В 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 ' однако, будет компилировать, но он не сделает ничего толкового. Вместо того, чтобы добавлять символ к строке, он будет принимать целочисленное значение символа (который получает повышение до int) и добавлять его к значению указателя.

std:: string ("hello worl") + " d " делает так, как вы ожидали, однако, потому что левая сторона уже является std:: string, и оператор сложения перегружен для std:: string, чтобы сделать так, как вы ожидали, даже когда правая сторона является символом* или одним характер.

одна последняя нота на строках: std:: string использует char, который является однобайтовым типом данных. То есть он не подходит для текста в юникоде. C++ предоставляет широкий тип символов wchar_t, который составляет 2 или 4 байта, в зависимости от платформы, и обычно используется для текста unicode (хотя ни в одном случае стандарт C++ действительно не определяет набор символов). И строка wchar_t называется std:: wstring.

библиотеки:

они не существуют, в корне. Язык C++ не имеет понятия о библиотеках, и это требует некоторого привыкания. Это позволяет #включить другой файл (как правило, файл заголовка с расширением .ч или .hpp), но это просто дословное копирование/вставка. Препроцессор просто объединяет два файла, что приводит к тому, что называется единицей перевода. Несколько исходных файлов обычно содержат одни и те же заголовки, и это работает только при определенных обстоятельствах, поэтому этот бит является ключом к пониманию C++ модель компиляции, которая, как известно, ушлый. Вместо того, чтобы компилировать кучу отдельных модулей и выдавать какие-то метаданные между ними, как это сделал бы компилятор C#, каждая единица перевода компилируется изолированно, а полученные объектные файлы передаются компоновщику, который затем пытается объединить общие биты вместе (если несколько единиц перевода включали один и тот же заголовок, у вас по существу есть код, дублированный через единицы перевода, поэтому компоновщик объединяет их обратно в один определение);)

конечно, существуют специфические для платформы способы написания библиотек. На Windows, вы можете сделать .dll или .библиотеки, с той разницей, что .lib связан с вашим приложением, в то время как a .dll-это отдельный файл, который вы должны связать с вашим приложением, как и в. NET .On 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<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);

как вы заметите, эти приведения гораздо более специфичны, что означает, что компилятор может дать вам ошибку, если приведение недействительно (в отличие от традиционного синтаксиса, где он просто попробует любой из вышеперечисленных приведений, пока не найдет тот, который работает), и он большой и подробный, позволяя вам искать его и напоминает вам, что их следует избегать, когда это возможно. ;)

стандартная библиотека:

наконец, возвращаясь к структурам данных, приложите некоторые усилия для понимания стандартной библиотеки. Он небольшой, но удивительно универсальный, и как только вы узнаете, как его использовать, вы будете в гораздо лучшем положении.

стандартная библиотека состоит из нескольких довольно различных строительных блоков (библиотека имеет вид накопленных с течением времени. Части его были перенесены из C. библиотека потоков ввода-вывода принимается из одного места, а классы контейнеров и связанные с ними функциональные возможности принимаются из совершенно другая библиотека, и оформлены заметно по-другому. Последние являются частью того, что часто называют STL (стандартная библиотека шаблонов). Строго говоря, это имя библиотеки, которая, слегка измененная, была принята в стандартную библиотеку C++.

STL является ключом к пониманию "современного C++". Он состоит из трех столпов, контейнеров, итераторов и алгоритмов. В двух словах, контейнеры предоставляют итераторы, а алгоритмы работают на итераторе пары.

в следующем примере вектор типа 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
}

вышеупомянутый стиль требует некоторого привыкания, но он чрезвычайно мощный и лаконичный. Поскольку функция преобразования шаблонна, она может принимать любой типы в качестве входных данных, пока они ведут себя как итераторы. Это означает, что функция может использоваться для объединения любого типа контейнеров или даже потоков или что-нибудь еще, что может быть повторено, если итератор разработан, чтобы быть совместимым с STL. Мы также не должны использовать начальную/конечную пару. Вместо конечного итератора мы могли бы передать одно указание на третий элемент, и алгоритм остановился бы там. Или мы могли бы написать пользовательские итераторы, которые пропустили все остальные элементы или что-то еще, что нам понравилось. Выше приведен пример каждого из трех компонентов. Мы используем контейнер для хранения наших данных, но алгоритм, который мы используем для его обработки, на самом деле не должен знать о контейнере. Он просто должен знать о диапазоне итератора, на котором он должен работать. И, конечно же, каждый из этих трех столпов может быть расширен путем написания новых классов, которые затем будут работать плавно вместе с остальной частью STL.

в некотором смысле это очень похоже на LINQ, поэтому, поскольку вы пришли из .NET, вы, вероятно, можете увидеть некоторые аналогии. Аналог STL немного более гибкий, хотя, в то же время стоимость чуть более странного синтаксиса. :) (Как уже упоминалось в комментариях, это также более эффективно. В общем, есть ноль накладные расходы для алгоритмов STL, они могут быть столь же эффективными, как и ручные циклы. Это часто удивительно, но это возможно, потому что все соответствующие типы известны на этапе компиляции (что является требованием для шаблоны для работы), и C++, как правило, агрессивно рядный.)

У вас есть некоторые инструменты доступны. Например, есть STL (стандартная библиотека шаблонов) и Boost/TR1 (расширения для STL), которые считаются отраслевыми стандартами (ну, STL, по крайней мере). Они предоставляют списки, карты, наборы, общие указатели, строки, потоки и всевозможные другие удобные инструменты. Лучше всего, они широко поддерживаются во всех компиляторах.

Что касается преобразования данных, вы можете либо выполнять приведения, либо создавать явные функции конвертера.

библиотеки - Вы можете либо создавать статические библиотеки (поглощаться конечным исполняемым файлом), либо библиотеки DLL (вы уже знакомы с ними). MSDN-это удивительный ресурс для библиотек DLL. Статические библиотеки зависят от среды сборки.

В общем, это мой совет: - Познакомьтесь с вашей IDE выбора очень хорошо - Купите "C++ The Complete Reference" Герберта Шильдта, который я считаю отличным Томом по всем вещам C++ (включая STL)

учитывая ваше прошлое, вы должно быть хорошо установлено, как только вы сделаете оба из них.

Я не буду повторять то, что другие говорили о библиотеках и тому подобном, но если вы серьезно относитесь к C++, сделайте себе одолжение и возьмите "язык программирования C++" Бьярне Страуструпа."

мне потребовались годы работы в C++, чтобы, наконец, забрать копию, и как только я это сделал, я провел день, хлопая себя по лбу, говоря: "конечно! Я должен был догадаться! так далее."

(по иронии судьбы, у меня был точно такой же опыт работы с K&R "язык программирования C."Когда-нибудь, я научитесь просто идти за "книгой" в день 1.)

Я написал небольшую шпаргалку для таких программистов. Вас также могут заинтересовать более сложные случаи, такие как с varargs

ссылка и использование других библиотек, если вы включаете источник, выполняется просто путем #включения заголовочных файлов для библиотеки во что угодно .cpp-файл, в котором они вам нужны (а затем скомпилируйте источник для библиотеки вместе с вашим проектом). Большую часть времени, однако, вы вероятно будете использовать .lib (статическая библиотека )или.dll (динамическая библиотека). Большинство (все?) Библиотеки DLL поставляются с собой .lib-файл, поэтому процедура для обоих типов одинакова: включите соответствующие заголовочные файлы, где вам нужны они, а затем добавить связанные .lib-файл на этапе компоновки (в visual studio, я думаю, вы можете просто добавить файл в проект).

прошло много времени с тех пор, как я создал свои собственные библиотеки для других, поэтому я позволю кому-то еще ответить на эту часть. Или я вернусь и отредактирую этот ответ завтра, так как мне нужно будет создать .lib для работы завтра:)

строковый материал обычно выполняется с помощью std:: string. При особых обстоятельствах, вы может также использовать старую функцию sprintf() в стиле C, но это обычно не рекомендуется.

Что касается структур данных, которые вы ищете, проверьте STL (стандартная библиотека шаблонов). Он включает в себя список, вектор, карту, строку и т. д., которые должны быть вам знакомы. Я не уверен, что вы подразумеваете под преобразованиями типов... Я предполагаю, что вы знаете о кастинге, поэтому вы должны иметь в виду что-то более сложное, чем это, и в этом случае это, вероятно, относится к типам, которые вы пытаетесь преобразовать. Может быть, кто-то остальное может предложить больше информации.

в ответ на "ссылки/использование других библиотек"

информация о явной загрузке DLL в C / C++ для windows и linux включает в себя...

Windows:

Windows DLL учебник

функции: LoadLibrary, GetProcAddress, FreeLibrary

Linux:

функции: dlopen, dlsym, dlerror, dlclose

Linux DLL учебник