FAQ:Container Classes篇

1、Why should I use container classes rather than simple arrays?(為什么應該使用容器類而不是簡單的數組?)

In terms of time and space, a contiguous array of any kind is just about the optimal construct for accessing a sequence of objects in memory, and if you are serious about performance in any language you will “often” use arrays.

從時間和空間的角度來看,任何類型的連續數組都是訪問內存中對象序列的最佳構造,如果真的考慮任何語言的性能,你都會“經常”使用數組。

However, there are good arrays (e.g., containers with contiguous storage such as std::array and std::vector) and there are bad arrays (e.g., C [] arrays). Simple C [] arrays are evil because a C array is a very low level data structure with a vast potential for misuse and errors and in essentially all cases there are better alternatives – where “better” means easier to write, easier to read, less error prone, and as fast.

然而,有好的數組(如具有連續存儲的容器,如 std::arraystd::vector),也有壞的數組(如C中的 [] 數組)。簡單的C [] 數組是很邪惡的,因為C數組是一種非常低級的數據結構,非常容易誤用和出現錯誤,本質上所有情況下都有更好的替代方案——“更好”意味著更容易編寫、更容易讀取、更少容易出錯,并且速度更快。

The two fundamental problems with C arrays are that

  • a C array doesn’t know its own size
  • the name of a C array converts to a pointer to its first element at the slightest provocation

C 數組的兩個基本問題是:

  • C數組不知道自己的大小
  • C數組的名稱會轉換為指向其第一個元素的指針

Consider some examples:

void f(int a[], int s)
{// do something with a; the size of a is sfor (int i = 0; i < s; ++i) a[i] = i;
}int arr1[20];
int arr2[10];void g()
{f(arr1,20);f(arr2,20);
}

The second call will scribble all over memory that doesn’t belong to arr2. Naturally, a programmer usually gets the size right, but it’s extra work and every so often someone makes a mistake. You should prefer the simpler and cleaner version using the standard library vector or array:

第二個調用會在內存中到處亂寫不屬于 arr2 的數據。自然地,程序員通常都能處理好大小,但這是額外的工作,而且不時會有人犯錯。你應該更傾向使用標準庫中的 vectorarray 來實現更簡單、更簡潔的版本。

void f(vector<int>& v)
{// do something with vfor (int i = 0; i < v.size(); ++i) v[i] = i;
}vector<int> v1(20);
vector<int> v2(10);void g()
{f(v1);f(v2);
}
template<size_t N> void f(array<int, N>& v)
{// do something with vfor (int i = 0; i < N; ++i) v[i] = i;
}array<int, 20> v1;
array<int, 10> v2;void g()
{f(v1);f(v2);
}

Since a C array doesn’t know its size, there can be no array assignment:

由于C語言的數組不知道其大小,因此不能對數組進行賦值:

void f(int a[], int b[], int size)
{a = b;            // not array assignmentmemcpy(a, b, size); // a = b// ...
}

Again, prefer vector or array:

// This one can result in a changing size
void g(vector<int>& a, vector<int>& b)
{a = b;  // ...
}// In this one, a and b must be the same size
template<size_t N>
void g(array<int, N>& a, array<int, N>& b)
{a = b;  // ...
}

Another advantage of vector here is that memcpy() is not going to do the right thing for elements with copy constructors, such as strings.

在這里,vector 的另一個優點是 memcpy() 不會對具有復制構造函數的元素(如字符串)進行正確的操作。

void f(string a[], string b[], int size)
{a = b;            // not array assignmentmemcpy(a,b,size); // disaster// ...
}void g(vector<string>& a, vector<string>& b)
{a = b;  // ...
}

A normal C array is of a fixed size determined at compile time (ignoring C99 VLAs, which currently have no analog in ISO C++):

普通的C數組在編譯時具有固定的大小(忽略C99 VLAs,目前在ISO c++中沒有類似的):

const int S = 10;void f(int s)
{int a1[s];    // errorint a2[S];    // ok// if I want to extend a2, I'll have to change to an array// allocated on free store using malloc() and use realloc()// ...
}

To contrast:
對比:

const int S = 10;void g(int s)
{vector<int> v1(s);    // okvector<int> v2(S);    // okv2.resize(v2.size()*2);// ...
}

C99 allows variable array bounds for local arrays, but those VLAs have their own problems. The way that array names “decay” into pointers is fundamental to their use in C and C++. However, array decay interacts very badly with inheritance. Consider:

C99允許本地數組有可變的數組邊界,但這些VLA有自己的問題。數組將“衰變”命名為指針的方式是C和c++中使用指針的基礎。然而,數組衰減與繼承的交互非常糟糕。考慮:

class Base { void fct(); /* ... */ };
class Derived : Base { /* ... */ };void f(Base* p, int sz)
{for (int i=0; i<sz; ++i) p[i].fct();
}Base ab[20];
Derived ad[20];void g()
{f(ab,20);f(ad,20);    // disaster!
}

In the last call, the Derived[] is treated as a Base[] and the subscripting no longer works correctly when sizeof(Derived)!=sizeof(Base) – as will be the case in most cases of interest. If we used vectors instead, the error would be caught at compile time:

在最后一個調用中,Derived[] 被視為 Base[],當 sizeof(Derived)!=sizeof(Base) 時,下標將不再正確工作——這將是我們感興趣的大多數情況。如果我們使用vector,錯誤將在編譯時捕獲:

void f(vector<Base>& v)
{for (int i=0; i<v.size(); ++i) v[i].fct();
}vector<Base> ab(20);
vector<Derived> ad(20);void g()
{f(ab);f(ad);    // error: cannot convert a vector<Derived> to a vector<Base>
}

We find that an astonishing number of novice programming errors in C and C++ relate to (mis)uses of C arrays. Use std::vector or std::array instead.

我們發現,C和C++中驚人數量的初學者編程錯誤與(錯誤)使用C數組有關。請使用std::vectorstd::array

Let’s assume the best case scenario: you’re an experienced C programmer, which almost by definition means you’re pretty good at working with arrays. You know you can handle the complexity; you’ve done it for years. And you’re smart — the smartest on the team — the smartest in the whole company. But even given all that, please read this entire FAQ and think very carefully about it before you go into “business as usual” mode.

讓我們假設最好的情況:你是一名經驗豐富的C程序員,這幾乎意味著你非常擅長使用數組。你知道你可以處理復雜的問題;你已經做了很多年了。你很聰明,是團隊中最聰明的,是整個公司最聰明的。但即便如此,在你進入“正常業務”模式之前,請仔細閱讀整個FAQ并仔細思考。

Fundamentally it boils down to this simple fact: C++ is not C. That means (this might be painful for you!!) you’ll need to set aside some of your hard earned wisdom from your vast experience in C. The two languages simply are different. The “best” way to do something in C is not always the same as the “best” way to do it in C++. If you really want to program in C, please do yourself a favor and program in C. But if you want to be really good at C++, then learn the C++ ways of doing things. You may be a C guru, but if you’re just learning C++, you’re just learning C++ — you’re a newbie. (Ouch; I know that had to hurt. Sorry.)

從根本上說,這可以歸結為一個簡單的事實:C++不是C。這意味著(這對你來說可能很痛苦!!)你需要將C中的豐富經驗中一些來之不易的智慧放到一邊。這兩種語言完全不同。用C語言做某事的“最佳”方式并不總是與用C++做某事的“最佳”方式相同。如果你真的想用C語言編程,請幫自己一個忙,用C語言編程。但是如果你想真正擅長C++,那就學習C++的做事方式。你可能是一個C大師,但如果你只是學習C++,那么你只是一個初學者。(哎喲;我知道那一定很痛苦。抱歉。)

Here’s what you need to realize about containers vs. arrays: 【以下是關于容器和數組你需要了解的:】

  1. Container classes make programmers more productive. So if you insist on using arrays while those around are willing to use container classes, you’ll probably be less productive than they are (even if you’re smarter and more experienced than they are!). 【容器類可以提高程序員的工作效率。因此,如果你堅持使用數組,而周圍的人愿意使用容器類,你可能會比他們更低效(即使你比他們更聰明、更有經驗!)。】
  2. Container classes let programmers write more robust code. So if you insist on using arrays while those around are willing to use container classes, your code will probably have more bugs than their code (even if you’re smarter and more experienced). 【容器類讓程序員寫出更健壯的代碼。因此,如果你堅持使用數組,而周圍的人都愿意使用容器類,那么你的代碼可能會比他們的代碼有更多的bug(即使你更聰明、更有經驗)。】
  3. And if you’re so smart and so experienced that you can use arrays as fast and as safe as they can use container classes, someone else will probably end up maintaining your code and they’ll probably introduce bugs. Or worse, you’ll be the only one who can maintain your code so management will yank you from development and move you into a full-time maintenance role — just what you always wanted!【如果你很聰明,很有經驗,你可以像他們使用容器類一樣快速和安全地使用數組,其他人可能最終會維護你的代碼,他們可能會引入錯誤。或者更糟的是,你將成為唯一一個可以維護你的代碼的人,因此管理人員將你從開發崗位抽調到全職維護崗位——這正是你一直想要的!】

Here are some specific problems with arrays: 【下面是數組的一些具體問題。】
4. Subscripts don’t get checked to see if they are out of bounds. (Note that some container classes, such as std::vector, have methods to access elements with or without bounds checking on subscripts.) 【下標不會被檢查是否越界。(注意,某些容器類,如 std::vector,有訪問元素的方法,可以對下標進行邊界檢查,也可以不進行邊界檢查。)】
5. Arrays often require you to allocate memory from the heap (see below for examples), in which case you must manually make sure the allocation is eventually deleted (even when someone throws an exception). When you use container classes, this memory management is handled automatically, but when you use arrays, you have to manually write a bunch of code (and unfortunately that code is often subtle and tricky) to deal with this. For example, in addition to writing the code that destroys all the objects and deletes the memory, arrays often also force you you to write an extra try block with a catch clause that destroys all the objects, deletes the memory, then re-throws the exception. This is a real pain in the neck, as shown here. When using container classes, things are much easier. 【數組經常需要你從堆中分配內存(見下面的例子),在這種情況下,你必須手動確保分配最終被刪除(即使有人拋出異常)。當你使用容器類時,這種內存管理是自動處理的,但當你使用數組時,你必須手動編寫一堆代碼(不幸的是,這些代碼通常是微妙和棘手的)來處理它。例如,除了編寫銷毀所有對象并刪除內存的代碼外,數組通常還迫使你編寫一個額外的 try 塊,其中包含一個 catch 子句,用于銷毀所有對象,刪除內存,然后重新拋出異常。這是一個非常棘手的問題,如描述的這樣。使用容器類時,事情會簡單得多。】
6. You can’t insert an element into the middle of the array, or even add one at the end, unless you allocate the array via the heap, and even then you must allocate a new array and copy the elements.【不能在數組中間插入元素,甚至不能在末尾添加元素, 除非通過堆分配數組,但即使這樣,也必須分配一個新數組并復制元素。】
7. Container classes give you the choice of passing them by reference or by value, but arrays do not give you that choice: they are always passed by reference. If you want to simulate pass-by-value with an array, you have to manually write code that explicitly copies the array’s elements (possibly allocating from the heap), along with code to clean up the copy when you’re done with it. All this is handled automatically for you if you use a container class. 【容器類可以選擇按引用傳遞,也可以選擇按值傳遞,但數組沒有這個選擇:它們總是按引用傳遞。如果你想模擬數組的值傳遞,就必須手動編寫代碼顯式復制數組的元素(可能從堆中分配),以及在完成復制后清理副本的代碼。如果您使用容器類,所有這些都會自動為您處理。】
8. If your function has a non-static local array (i.e., an “automatic” array), you cannot return that array, whereas the same is not true for objects of container classes.【如果函數有一個非靜態的局部數組(即“自動”數組),則不能返回該數組,而對于容器類的對象則不是這樣。】

Here are some things to think about when using containers: 【以下是使用容器時需要考慮的一些事情:】

  1. Different C++ containers have different strengths and weaknesses, but for any given job there’s usually one of them that is better — clearer, safer, easier/cheaper to maintain, and often more efficient — than an array. For instance, 【不同的C++容器有不同的優缺點,但對于任何給定的任務來說,通常有一種容器比數組更好——更清晰、更安全、更容易/更便宜維護,而且通常更高效。例如,】

    • You might consider a std::map instead of manually writing code for a lookup table. 【可以考慮使用 std::map,而不是手動編寫查找表代碼】
    • A std::map might also be used for a sparse array or sparse matrix. 【std::map也可以用于稀疏數組或稀疏矩陣。】
    • A std::array is the most array-like of the standard container classes, but it also offers various extra features such as bounds checking via the at() member function, automatic memory management even if someone throws an exception, ability to be passed both by reference and by value, etc. 【std::array 是最像數組的標準容器類,但它還提供了各種額外的特性,例如通過 at() 成員函數進行邊界檢查,即使拋出異常也能自動管理內存,能夠按引用和按值傳遞,等等。】
    • A std::vector is the second-most array-like of the standard container classes, and offers additional extra features over std::array such as insertions/removals of elements. 【 std::vector 是標準容器類中第二大類似數組的類,它提供了比 std::array 更多的特性,例如插入/刪除元素。】
    • A std::string is almost always better than an array of char (you can think of a std::string as a “container class” for the sake of this discussion). 【std::string 幾乎總是比 char 數組好(為了討論這個問題,你可以把 std::string 想象成一個“容器類”)。】
  2. Container classes aren’t best for everything, and sometimes you may need to use arrays. But that should be very rare, and if/when it happens: 【容器類并不適合所有情況,有時可能需要使用數組。但這種情況應該非常罕見,如果/當它發生時:】

    • Please design your container class’s public interface in such a way that the code that uses the container class is unaware of the fact that there is an array inside. 【請設計容器類的公共接口,令使用容器類的代碼不知道其中有一個數組。】
    • The goal is to “bury” the array inside a container class. In other words, make sure there is a very small number of lines of code that directly touch the array (just your own methods of your container class) so everyone else (the users of your container class) can write code that doesn’t depend on there being an array inside your container class. 【目標是將數組“埋葬”在容器類中。換句話說,確保有非常少的代碼行直接接觸數組(只是容器類的自己的方法),以便其他人(容器類的用戶)可以編寫不依賴于容器類中數組的代碼。】

To net this out, arrays really are evil. You may not think so if you’re new to C++. But after you write a big pile of code that uses arrays (especially if you make your code leak-proof and exception-safe), you’ll learn — the hard way. Or you’ll learn the easy way by believing those who’ve already done things like that. The choice is yours.

為了說明這一點,數組真的很邪惡。如果你剛接觸C++,可能不會這么想。但是,當你編寫了一大堆使用數組的代碼(特別是在你的代碼防泄漏和異常安全的情況下)后,你將了解這一點——這是一種艱難的方式。或者你可以通過相信那些已經做過類似事情的人來學會簡單的方法。選擇權在你。

2、How can I make a perl-like associative array in C++? (如何在C++中創建類似perl的關聯數組?)

Use the standard class template std::map<Key,Val>:

#include <string>
#include <map>
#include <iostream>int main()
{// age is a map from string to intstd::map<std::string, int, std::less<std::string>>  age;age["Fred"] = 42;                     // Fred is 42 years oldage["Barney"] = 37;                   // Barney is 37if (todayIsFredsBirthday())           // On Fred's birthday...++ age["Fred"];                     // ...increment Fred's agestd::cout << "Fred is " << age["Fred"] << " years old\n";// ...
}

Nit: the order of elements in a std::map<Key,Val> are in the sort order based on the key, so from a strict standpoint, that is different from Perl’s associative arrays which are unordered. If you want an unsorted version for a closer match, you can use std::unordered_map<Key,Val> instead.

std::map<Key,Val> 中元素的順序是基于鍵的排序順序,所以從嚴格的角度來看,這與Perl的無序關聯數組是不同的。如果你想要一個未排序的版本來進行更緊密的匹配,可以使用 std::unordered_map<Key,Val>

3、Is the storage for a std::vector or a std::array<T,N> guaranteed to be contiguous?(std::vectorstd::array<T,N>的存儲空間保證是連續的嗎?)

Yes.
當然。

This means the following technique is safe:

#include <vector>
#include <array>
#include "Foo.h"  /* get class Foo */// old-style code that wants an array
void f(Foo* array, unsigned numFoos);void g()
{std::vector<Foo> v;std::array<Foo, 10> a;// ...f(v.data(), v.size());  // Safef(a.data(), a.size());  // Safe
}

In general, it means you are guaranteed that &v[0] + n == &v[n], where v is a non-empty std::vector<T> or std::array<T,N> and n is an integer in the range 0 .. v.size()-1.

一般來說,這意味著保證 &v[0] + n == &v[n],其中 v 是非空的std::vector<T>std::array<T,N>n 是范圍為0 ~ v.size() - 1 的數。

However v.begin() is not guaranteed to be a T*, which means v.begin() is not guaranteed to be the same as &v[0]:

但是 v.begin() 不保證是 T*,這意味著 v.begin() 不保證與 &v[0] 相同:

void g()
{std::vector<Foo> v;// ...f(v.begin(), v.size());  // error, not guaranteed to be the same as &v[0]↑↑↑↑↑↑↑↑↑ // cough, choke, gag; use v.data() instead
}

Also, using &v[0] is undefined behavior if the std::vector or std::array is empty, while it is always safe to use the .data() function.

另外,如果 std::vectorstd::array為空,那么使用 ·&v[0]· 就是未定義行為,而使用 .data() 函數總是安全的。

Note: It’s possible the above code might compile for you today. If your compiler vendor happens to implement std::vector or std::array iterators as T*’s, the above may happen to work on that compiler – and at that, possibly only in release builds, because vendors often supply debug iterators that carry more information than a T*. But even if this code happens to compile for you today, it’s only by chance because of a particular implementation. It’s not portable C++ code. Use .data() for these situations.

注意:上面的代碼可能今天就能編譯成功。如果你的編譯器廠商碰巧將 std::vectorstd::array 迭代器實現為 T* 的迭代器,那么上述代碼可能在該編譯器上有效——而且可能只在發布版本中有效,因為廠商提供的調試迭代器通常攜帶的信息比 T* 更多。但即使這段代碼今天能通過編譯,這也只是偶然的,因為有特定的實現。它不是可移植的C++代碼。在這些情況下使用 .data()

4、Why doesn’t C++ provide heterogeneous containers?(為什么C++不提供異構容器?)

The C++ standard library provides a set of useful, statically type-safe, and efficient containers. Examples are vector, list, and map:

C++ 標準庫提供了一組有用的、靜態類型安全的、高效的容器。例如 vectorlistmap

vector<int> vi(10);
vector<Shape*> vs;
list<string> lst;
list<double> l2
map<string,Record*> tbl;
unordered_map<Key,vector<Record*> > t2;

These containers are described in all good C++ textbooks, and should be preferred over arrays and “home cooked” containers unless there is a good reason not to.

這些容器在所有優秀的C++教科書中都有介紹,應該優先使用,而不是數組和“家常”容器,除非有充分的理由不使用。

These containers are homogeneous; that is, they hold elements of the same type. If you want a container to hold elements of several different types, you must express that either as a union or (usually much better) as a container of pointers to a polymorphic type. The classical example is:

這些容器是同構的;也就是說,它們持有相同類型的元素。如果想在容器中保存幾種不同類型的元素,則必須表示為聯合,或者(通常更好)表示為指向多態類型的指針的容器。經典的例子是:

vector<Shape*> vi;  // vector of pointers to Shapes

Here, vi can hold elements of any type derived from Shape. That is, vi is homogeneous in that all its elements are Shapes (to be precise, pointers to Shapes) and heterogeneous in the sense that vi can hold elements of a wide variety of Shapes, such as Circles, Triangles, etc.

在這里,vi 可以保存繼承自 Shape 的任何類型的元素。也就是說,vi 是同構的,因為它的所有元素都是Shape(準確地說,是指向Shape 的指針);在某種意義上,也可以說 vi 是異構的,因為 vi 可以包含各種形狀的元素,如圓、三角形等。

So, in a sense all containers (in every language) are homogeneous because to use them there must be a common interface to all elements for users to rely on. Languages that provide containers deemed heterogeneous simply provide containers of elements that all provide a standard interface. For example, Java collections provide containers of (references to) Objects and you use the (common) Object interface to discover the real type of an element.

因此,從某種意義上說,所有容器(每種語言中的容器)都是同構的,因為要使用它們,必須為用戶依賴的所有元素提供一個公共接口。提供容器的語言被認為是異構的,只是提供了元素的容器,這些元素都提供了標準接口。例如,Java集合提供了對象的容器(引用),你可以使用(公共)Object接口來發現元素的真正類型。

The C++ standard library provides homogeneous containers because those are the easiest to use in the vast majority of cases, gives the best compile-time error message, and imposes no unnecessary run-time overheads.

C++標準庫提供了同構的容器,因為這些容器在大多數情況下都是最容易使用的,提供了最好的編譯時錯誤消息,并且沒有不必要的運行時開銷。

If you need a heterogeneous container in C++, define a common interface for all the elements and make a container of those. For example:

如果你需要在C++中使用一個異構容器,可以為所有元素定義一個公共接口,并將這些元素制成一個容器。例如:

class Io_obj { /* ... */ };    // the interface needed to take part in object I/Ovector<Io_obj*> vio;           // if you want to manage the pointers directly
vector<shared_ptr<Io_obj>> v2; // if you want a "smart pointer" to handle the objects

Don’t drop to the lowest level of implementation detail unless you have to:

除非迫不得已,否則不要把實現細節降到最低:

vector<void*> memory;          // rarely needed

A good indication that you have “gone too low level” is that your code gets littered with casts.

如果你的代碼被強制轉換弄得一團糟,就說明你的代碼級別太低了。

Using an Any class, such as boost::any, can be an alternative in some programs:

在某些程序中,使用Any類(如boost:: Any)是一種替代方案:

vector<any> v = { 5, "xyzzy", 3.14159 };

If all objects you want to store in a container are publicly derived from a common base class, you can then declare/define your container to hold pointers to the base class. You indirectly store a derived class object in a container by storing the object’s address as an element in the container. You can then access objects in the container indirectly through the pointers (enjoying polymorphic behavior). If you need to know the exact type of the object in the container you can use dynamic_cast<> or typeid(). You’ll probably need the Virtual Constructor Idiom to copy a container of disparate object types. The downside of this approach is that it makes memory management a little more problematic (who “owns” the pointed-to objects? if you delete these pointed-to objects when you destroy the container, how can you guarantee that no one else has a copy of one of these pointers? if you don’t delete these pointed-to objects when you destroy the container, how can you be sure that someone else will eventually do the deleteing?). It also makes copying the container more complex (may actually break the container’s copying functions since you don’t want to copy the pointers, at least not when the container “owns” the pointed-to objects). In that case, you can use std::shared_ptr to manage the objects, and the containers will copy correctly.

如果你想存儲在容器中的所有對象都是公共地派生自一個共同的基類,你可以聲明/定義容器來保存指向基類的指針。通過將派生類對象的地址作為元素存儲在容器中,可以間接地將該類對象存儲在容器中。然后可以通過指針間接地訪問容器中的對象(享受多態行為)。如果你想知道容器中對象的的確切類型,可以使用dynamic_cast<>typeid()。你可能需要虛構造函數慣用法來賦值不同對象類型的容器。這種方法的缺點是它使得內存管理更有問題(誰“擁有”指向的對象?如果在銷毀容器的同時刪除了這些指針指向的對象,如何保證這些指針中的任何一個都沒有被其他人擁有副本呢?如果在銷毀容器時不刪除這些指向對象,又怎么能確定最終會有其他人來刪除呢?)它還會使復制容器變得更加復雜(實際上可能會破壞容器的復制函數,因為你不想復制指針,至少在容器“擁有”指向的對象時不會)在這種情況下,可以使用 std::shared_ptr 來管理對象,容器將能夠正確地進行復制。

The second case occurs when the object types are disjoint — they do not share a common base class. The approach here is to use a handle class. The container is a container of handle objects (by value or by pointer, your choice; by value is easier). Each handle object knows how to “hold on to” (i.e., maintain a pointer to) one of the objects you want to put in the container. You can use either a single handle class with several different types of pointers as instance data, or a hierarchy of handle classes that shadow the various types you wish to contain (requires the container be of handle base class pointers). The downside of this approach is that it opens up the handle class(es) to maintenance every time you change the set of types that can be contained. The benefit is that you can use the handle class(es) to encapsulate most of the ugliness of memory management and object lifetime.

第二種情況發生在對象類型不相交的時候——它們不共享一個共同的基類。這里的方法是使用一個handle類。容器是handle對象的容器(由值或指針決定;按值更簡單)。每個handle對象都知道如何“保持”(即維護一個指向要放入容器中的對象的指針)。可以使用具有幾種不同類型指針的單個handle類作為實例數據,也可以使用handle類的層次結構來遮蔽您希望包含的各種類型(要求容器為handle基類指針)。這種方法的缺點是,每次更改可以包含的類型集時,它都會打開handle類來維護。這樣做的好處是,你可以使用handle類來封裝內存管理和對象生存期的大部分麻煩。

5、How can I build a heterogeneous <favorite container> of objects of different types? (如何構建不同類型對象的異構<最愛容器>?)

See heterogeneous containers.

6、Why can’t I assign a vector<Apple*> to a vector<Fruit*>?(為什么不能將vector<Apple*>賦值給vector<Fruit*>?)

Because that would open a hole in the type system. For example:

因為這會在類型系統中打開一個漏洞。例如:

class Apple : public Fruit { void apple_fct(); /* ... */ };
class Orange : public Fruit { /* ... */ }; // Orange doesn't have apple_fct()vector<Apple*> v;             // vector of Applesvoid f(vector<Fruit*>& vf)    // innocent Fruit manipulating function
{vf.push_back(new Orange); // add orange to vector of fruit
}void h()
{f(v);    // error: cannot pass a vector<Apple*> as a vector<Fruit*>for (int i=0; i<v.size(); ++i) v[i]->apple_fct();
}

Had the call f(v) been legal, we would have had an Orange pretending to be an Apple.

如果調用 f(v) 合法,我們就會有一個橙子假裝成蘋果。

An alternative language design decision would have been to allow the unsafe conversion, but rely on dynamic checking. That would have required a run-time check for each access to v’s members, and h() would have had to throw an exception upon encountering the last element of v.

另一種語言設計決策是允許不安全的轉換,但依賴動態檢查。這就需要每次訪問 v 的成員時都進行運行時檢查,而 h() 遇到 v 的最后一個元素時必須拋出異常。

7、How can I insert/access/change elements from a linked list/hashtable/etc?(鏈表/哈希表等中如何插入/訪問/更改元素?)

The most important thing to remember is this: don’t roll your own from scratch unless there is a compelling reason to do so. In other words, instead of creating your own list or hashtable, use one of the standard class templates such as std::vector<T> or std::list<T> or whatever.

要記住的最重要的事情是:不要自己動手,除非有令人信服的理由。換句話說,使用 std::vector<T>std::list<T> 之類的標準類模板,而不是創建自己的鏈表或哈希表。

Assuming you have a compelling reason to build your own container, here’s how to handle inserting (or accessing, changing, etc.) the elements.

假設你有充分的理由構建自己的容器,下面是如何處理插入(或訪問、更改等)元素的方法。

To make the discussion concrete, I’ll discuss how to insert an element into a linked list. This example is just complex enough that it generalizes pretty well to things like vectors, hash tables, binary trees, etc.

為了使討論具體化,我將討論如何向鏈表中插入元素。這個例子足夠復雜,可以很好地推廣到向量、散列表、二叉樹等。

A linked list makes it easy to insert an element before the first or after the last element of the list, but limiting ourselves to these would produce a library that is too weak (a weak library is almost worse than no library). This answer will be a lot to swallow for novice C++’ers, so I’ll give a couple of options. The first option is easiest; the second and third are better.

在鏈表中,可以很容易地在第一個元素之前或最后一個元素之后插入元素,但只使用這些會讓庫過于弱(弱的庫幾乎比沒有庫更糟糕)。這個答案對于C++新手來說很難理解,所以我將給出幾個選擇。第一個選擇是最簡單的;第二個和第三個比較好。

  1. Empower the List with a “current location,” and member functions such as advance(), backup(), atEnd(), atBegin(), getCurrElem(), setCurrElem(Elem), insertElem(Elem), and removeElem(). Although this works in small examples, the notion of a current position makes it difficult to access elements at two or more positions within the list (e.g., “for all pairs x,y do the following…”). 【賦予List一個“當前位置”和成員函數,如advance()、backup()、atEnd()、atBegin()、getCurrElem()、setCurrElem(Elem)、insertElem(Elem)和removeElem()。雖然這在小示例中有效,但當前位置的概念使得訪問列表中兩個或多個位置的元素變得困難(例如,“對于所有對x,y執行以下…”)。】
  2. Remove the above member functions from List itself, and move them to a separate class, ListPosition. ListPosition would act as a “current position” within a list. This allows multiple positions within the same list. ListPosition would be a friend of class List, so List can hide its innards from the outside world (else the innards of List would have to be publicized via public member functions in List). Note: ListPosition can use operator overloading for things like advance() and backup(), since operator overloading is syntactic sugar for normal member functions.【從List中刪除上述成員函數,并將它們移動到單獨的類ListPosition中。ListPosition相當于列表中的“當前位置”。這允許在同一個列表中有多個位置。ListPosition是List類的友元,因此List可以向外界隱藏它的內部結構(否則List的內部結構必須通過List中的public成員函數公開)。注意:ListPosition可以對advance()和backup()這樣的函數使用運算符重載,因為運算符重載是普通成員函數的語法糖。】
  3. Consider the entire iteration as an atomic event, and create a class template that embodies this event. This enhances performance by allowing the public access member functions (which may be virtual functions) to be avoided during the access, and this access often occurs within an inner loop. Unfortunately the class template will increase the size of your object code, since templates gain speed by duplicating code. For more, see [Koenig, “Templates as interfaces,” JOOP, 4, 5 (Sept 91)], and [Stroustrup, “The C++ Programming Language Third Edition,” under “Comparator”]. 【將整個迭代視為原子事件,并創建包含此事件的類模板。這通過在訪問期間避免公共訪問成員函數(可能是虛函數)來提高性能,而且這種訪問通常發生在內部循環中。不幸的是,類模板會增加目標代碼的大小,因為模板通過復制代碼來提高速度。更多信息,請參閱[Koenig,“Templates as interfaces”,JOOP, 4,5(9月91)]和[Stroustrup,“The c++ Programming Language Third Edition”,在“Comparator”下]。】

8、Can I have a container of smart pointers to my objects?(我可以有一個指向我的對象的智能指針的容器嗎?)

Yes, and you really want to do this, as smart pointers make your life easier and make your code more robust compared to the alternatives.

是的,你確實想這樣做,因為與替代方案相比,智能指針使你的生活更輕松,使你的代碼更健壯。

Note: forget that std::auto_ptr ever existed. Really. You don’t want to use it, ever, especially in containers. It is broken in too many ways.

注意:請忘記 std::auto_ptr 曾經存在過。真的。你永遠都不想使用它,尤其是在容器中。它在很多方面都有缺陷。

Let’s motivate this discussion with an example. This first section shows why you’d want to use smart pointers in the first place - this is what not to do:

讓我們用一個例子來激發這個討論。第一節展示了為什么你需要首先使用智能指針——這是應該做的:

#include <vector>class Foo {
public:// ...blah blah...
};void foo(std::vector<Foo*>& v)  // ← BAD FORM: a vector of dumb pointers to Foo objects
{v.push_back(new Foo());// ...delete v.back();  // you have a leak if this line is skippedv.pop_back();     // you have a "dangling pointer" if control-flow doesn't reach this line
}

If control flow doesn’t reach either of the last two lines, either because you don’t have it in your code or you do a return or something throws an exception, you will have a leak or a “dangling pointer”; bad news. The destructor of std::vector cleans up whatever allocations were made by the std::vector object itself, but it will not clean up the allocation that you made when you allocated a Foo object, even though you put a pointer to that allocated Foo object into the std::vector object.

如果控制流沒有到達最后兩行,要么是因為你的代碼中沒有它,要么是因為執行了return或拋出了異常,那么就會出現泄漏或“懸空指針”;壞消息。std::vector的析構函數會清除 std::vector 對象本身所進行的任何分配,但是它不會清除為 Foo 對象分配內存時所進行的分配,即使你將指向已分配內存的 Foo 對象的指針放在了 std::vector 對象中。

That’s why you’d want to use a smart pointer.

這就是為什么你想用智能指針.

Now let’s talk about how to use a smart pointer. There are lots of smart pointers that can be copied and still maintain shared ownership semantics, such as std::shared_ptr and many others. For this example, we will use std::shared_ptr, though you might choose another based on the semantics and performance trade-offs you desire.

現在讓我們談談如何使用智能指針。有很多智能指針可以在被復制的同時仍然保持共享的所有權語義,比如 std::shared_ptr 等。在這個例子中,我們將使用 std::shared_ptr,不過你也可以根據語義和性能權衡選擇另一種。

typedef std::shared_ptr<Foo> FooPtr;  // ← GOOD: using a smart-pointervoid foo(std::vector<FooPtr>& v)  // ← GOOD: using a container of smart-pointer
{// ...
}

This just works safely with all operations. The object is destroyed when the last shared_ptr to it is destroyed or set to point to something else.

這對所有操作都是安全的。當最后一個指向它的 shared_ptr 被銷毀或被設置為指向其他對象時,對象就會被銷毀。

Using a std::unique_ptr in a std::vector is safe, but it has some restrictions. The unique_ptr is a move-only type, it can’t be copied. This move-only restriction then applies to the std::vector containing them as well.

std::vector 中使用 std::unique_ptr 是安全的,但有一些限制。unique_ptr 是只允許移動的類型,不能被復制。這個只能移動的限制也適用于包含它們的 std::vector

void create_foo(std::vector<std::unique_ptr<Foo>> &v)
{v.emplace_back(std::make_unique<Foo>(/* ... */));
}

If you want to put an element from this vector into another vector, you must move it to the other vector, as only one unique_ptr at a time can point to the same Foo object.

如果你想把這個vector中的一個元素放到另一個vector中,就必須把它移動到另一個vector中,因為同一時刻只有一個 unique_ptr 指向同一個 Foo 對象。

There are lots of good articles on this general topic, such as Herb Sutter’s in Dr. Dobbs and many others.

關于這個主題有很多不錯的文章,比如Herb Sutter在《多布斯博士》(Dr. Dobbs)和其他很多文章。

9、Why are the standard containers so slow? (為什么標準容器這么慢?)

They aren’t, they’re among the fastest on the planet.

它們不是,它們是地球上速度最快的。

Probably “compared to what?” is a more useful question (and answer). When people complain about standard-library container performance, we usually find one of three genuine problems (or one of the many myths and red herrings):

可能是“與什么相比?”是一個更有用的問題(和答案)。當人們抱怨標準庫容器的性能時,我們通常會發現以下三個真正的問題之一(或者是許多誤解和轉移注意力的東西之一):

  • I suffer copy overhead 【承受了復制開銷】
  • I suffer slow speed for lookup tables 【查找表的速度很慢】
  • My hand-coded (intrusive) lists are much faster than std::list 【我手工編寫的(侵入式的)列表比 std::list 快得多】

Before trying to optimize, consider if you have a genuine performance problem. In most of the cases sent to me, the performance problem is theoretical or imaginary: First measure, then optimize only if needed.

在嘗試優化之前,請考慮是否真的存在性能問題。在我收到的大多數案例中,性能問題都是理論上或想象出來的:首先測量,然后只在需要時進行優化。

Let’s look at those problems in turn. Often, a vector<X> is slower than somebody’s specialized My_container<X> because My_container<X> is implemented as a container of pointers to X (brief spoiler: if you want that, you have it too: vector<X*> – more on this in a moment). A vector<X> (no *) holds copies of values, and copies a value when you put it into the container. This is essentially unbeatable for small values, but can be quite unsuitable for huge objects:

讓我們依次來看一下這些問題。通常,vector<X> 比某人專用的 My_container<X> 慢,因為 My_container<X> 被實現為指向 X 的指針的容器(簡要劇播:如果你想這樣,你也有它: ·vector<X*>·——稍后會詳細介紹)。vector<X> (沒有*)保存值的副本,并且當你將值放入容器時復制它。這對于小的值來說基本上是無敵的,但對于大型對象來說可能非常不合適:

vector<int> vi;
vector<Image> vim;
// ...
int i = 7;
Image im("portrait.jpg");    // initialize image from file
// ...
vi.push_back(i);             // put (a copy of) i into vi
vim.push_back(im);           // put (a copy of) im into vim

Now, if portrait.jpg is a couple of megabytes and Image has value semantics (i.e., copy assignment and copy construction make copies) then vim.push_back(im) will indeed be expensive. But – as the saying goes – if it hurts so much, just don’t do it.

現在,如果 portrait.jpg 有幾兆字節,并且圖像具有值語義(即,復制賦值和復制構造會產生副本),那么 vim.push_back(im) 確實會很昂貴。但是,就像俗話說的那樣,如果真的很疼,那就不要做了。

Move semantics and in-place construction can negate many of these costs if the vector is going to own the object, and you don’t need copies of it elsewhere.

如果vector 要擁有對象,且不需要在其他地方復制對象,那么移動語義和就地構造可以消除這些開銷。

vector<Image> vim;
vim.emplace_back("portrait.jpg"); // create image from file in place in the vector

Alternatively, either use a container of handles or a container of pointers. For example, if Image had reference semantics, the code above would incur only the cost of a copy constructor call, which would be trivial compared to most image manipulation operators. If some class, say Image again, does have copy semantics for good reasons, a container of pointers is often a reasonable solution:

或者,使用句柄容器或指針容器。例如,如果 Image 具有引用語義,上面的代碼只會產生復制構造函數調用的開銷,與大多數圖像操作操作符相比,這是微不足道的。如果某些類,比如 Image,有充分的理由具有復制語義,則指針容器通常是一個合理的解決方案:

vector<int> vi;
vector<Image*> vim;
// ...
Image im("portrait.jpg");    // initialize image from file
// ...
vi.push_back(7);             // put (a copy of) 7 into vi
vim.push_back(&im);          // put (a copy of) &im into vim

Naturally, if you use pointers, you have to think about resource management, but containers of pointers can themselves be effective and cheap resource handles (often, you need a container with a destructor for deleting the “owned” objects), or you can simply use a container of smart pointers.

當然,如果你使用指針,你必須考慮資源管理,但是指針容器本身可以是有效且廉價的資源句柄(通常,你需要一個帶有析構函數的容器,用于刪除“擁有的”對象),或者你可以簡單地使用智能指針的容器。

The second frequently occurring genuine performance problem is the use of a map<string,X> for a large number of (string,X) pairs. Maps are fine for relatively small containers (say a few hundred or few thousand elements – access to an element of a map of 10000 elements costs about 9 comparisons), where less-than is cheap, and where no good hash-function can be constructed. If you have lots of strings and a good hash function, use an unordered_map.

第二個經常發生的真正性能問題是用map<string, X>表示大量的 (string, X) 對。map 適用于相對較小的容器(如只有幾百或幾千個元素——訪問包含10000 個元素的 map 中的元素大概需要9次比較),這種情況下, less-than是便宜的,且無法構建良好的散列函數。如果你有很多字符串和一個好的散列函數,請使用 unordered_map

Sometimes, you can speed up things by using (const char*,X) pairs rather than (string,X) pairs, but remember that < doesn’t do lexicographical comparison for C-style strings. Also, if X is large, you may have the copy problem also (solve it in one of the usual ways).

有時,你可以使用 (const char*, X) 對而不是 (string, X) 來加快速度,但請記住 < 不會對C風格的字符串進行字典序比較。另外,如果 X 很大,你可能也會遇到復制問題(用一種常用的方法解決)。

Intrusive lists can be really fast. However, consider whether you need a list at all: A vector is more compact and is therefore smaller and faster in many cases – even when you do inserts and erases. For example, if you logically have a list of a few integer elements, a vector is significantly faster than a list (any list). Also, intrusive lists cannot hold built-in types directly (an int does not have a link member). So, assume that you really need a list and that you can supply a link field for every element type. The standard-library list by default performs an allocation followed by a copy for each operation inserting an element (and a deallocation for each operation removing an element). For std::list with the default allocator, this can be significant. For small elements where the copy overhead is not significant, consider using an optimized allocator. Use a hand-crafted intrusive lists only where a list and the last ounce of performance is needed.

侵入式鏈表的速度非常快。然而,考慮一下你是否真的需要鏈表:vector 更緊湊,因此在許多情況下更小、更快——即使在進行插入和刪除操作時也是如此。例如,如果邏輯上有一個由幾個整數元素組成的鏈表,那么 vector 明顯比鏈表(任何鏈表)快得多。此外,侵入式鏈表不能直接保存內置類型(int沒有鏈接成員)。因此,假設你確實需要一個鏈表,并且可以為每種元素類型提供一個link字段。在默認情況下,標準庫鏈表為每個插入元素的操作執行分配,并保存副本(并為每個刪除元素的操作執行回收)。對于具有默認分配器的 std::list 來說,這可能很重要。對于復制開銷不大的小元素,可以考慮使用優化的分配器。僅在需要鏈表和最后一點性能的地方使用手工制作的侵入式鏈表。

People sometimes worry about the cost of std::vector growing incrementally. Many C++ programmers used to worry about that and used reserve() to optimize the growth. After measuring their code and repeatedly having trouble finding the performance benefits of reserve() in real programs, they stopped using it except where it is needed to avoid iterator invalidation (a rare case in most code). Again: measure before you optimize.

人們有時會擔心 std::vector 的開銷會遞增。許多C++程序員過去常常擔心這個問題,并使用 reserve() 來優化增長。在測量了他們的代碼并不斷地發現reverse() 沒有性能優勢后,他們停止使用它,除非需要避免迭代器失效(大多數代碼中,這是一種很罕見的情況)。再次強調:優化之前進行測量。

The cost of std::vector growing incrementally in C++11 can be a lot less than it was in C++98/03 when you are using move-aware types, such as std::string or even std::vector<T>, as when the vector is reallocated, the objects are moved into the new storage instead of copied.

當你使用 std::string 甚至 std::vector<T> 等支持移動的類型時,在C++ 11中 std::vector 的增量增長開銷比在C++ 98/03中要小得多,因為在重新分配 vector 時,對象會被移動到新的存儲空間中,而不是復制。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/448136.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/448136.shtml
英文地址,請注明出處:http://en.pswp.cn/news/448136.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

自動擋車擋位的基本知識介紹

一般來說&#xff0c;自動檔汽車的自動變速器的檔位分為P、R、N、D、2 (或S)、L(或1)等。下面分別詳細介紹如下&#xff1a; P (Parking) 停車檔&#xff0c;或稱泊車檔&#xff1a; P用作停車之用&#xff0c;它是利用機械裝置去鎖緊汽車的轉動部分&#xff0c;使汽車不能移動…

Java 強引用、弱引用、軟引用、虛引用

1、強引用&#xff08;StrongReference&#xff09; 強引用是使用最普遍的引用。如果一個對象具有強引用&#xff0c;那垃圾回收器絕不會回收它。如下&#xff1a; Object onew Object(); // 強引用 當內存空間不足&#xff0c;Java虛擬機寧愿拋出OutOfMemoryError錯誤…

解決:(iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 8082 -j DNAT --to-destin

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1. 用 docker 部署一個前端工程&#xff0c;run 后容器有了&#xff0c;卻不是運行狀態&#xff0c;是創建狀態&#xff0c;于是我執行 …

在DOS命令行執行MYSQL語句

最近有個工作需要從MSSQL庫中取數據然后導入SQL 2005。由于之前曾經做過利用BCP導入SQL&#xff0c;因此想借助這個工具實現此功能。 在探索過程中&#xff0c;好像發現MYSQL不能想SQL那樣有OSSQL這樣的外部命令。因此想到利用MYSQL執行文件內容的功能來生成導出數據。&#xf…

無損壓縮——Huffman編碼

最近項目中涉及到人臉關鍵點中神經網絡的壓縮&#xff0c;采用了性能較好的哈夫曼編碼進行。 源碼地址&#xff1a;https://github.com/believeszw/HuffmanCompress 1 引言 哈夫曼&#xff08;Huffman&#xff09;編碼算法是基于二叉樹構建編碼壓縮結構的&#xff0c;它是數據…

26條安全開車經驗 開車20年老司機分享

總有些人&#xff0c;覺得自己開車技術比舒馬赫牛叉&#xff0c;市區高速漂移無比瀟灑。也總有些人&#xff0c;覺得路是自家的鋪的&#xff0c;愛怎么開就怎么開&#xff0c;愛停哪就停哪&#xff0c;哪個不服打開車窗就是一句國罵一個中指。其實他們都沒有意識到&#xff0c;…

解決:Request header field Content-Type is not allowed by Access-Control-Allow-Headers

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1. 前端 vue 工程 post 請求后端接口&#xff0c;報錯&#xff1a; Request header field Content-Type is not allowed by Access-Con…

書寫README的各種markdown語法

README 該文件用來測試和展示書寫README的各種markdown語法。GitHub的markdown語法在標準的markdown語法基礎上做了擴充&#xff0c;稱之為GitHub Flavored Markdown。簡稱GFM&#xff0c;GFM在GitHub上有廣泛應用&#xff0c;除了README文件外&#xff0c;issues和wiki均支持…

Apache2.4配置ssl

1》驗站 如下截圖&#xff0c;驗站就是在DNS域名商哪里&#xff0c;在對應host下面&#xff0c;添加一個TXT記錄類型&#xff0c;主機記錄&#xff0c;記錄值后&#xff0c;檢測即可。   2》SSL證書申請 阿里云&#xff0c;騰訊云有很多免費證書申請&#xff0c;免費的缺點是…

助你解決新手開車四大問題 為您支招

新手開車起步技巧涉及方方面面&#xff0c;對于新手來說&#xff0c;如何首次將車獨自開上路且不發生任何意外是眾多人熱切盼望的理想方式。但是新手上路難免會磕磕碰碰&#xff0c;發生小摩擦都是在所難免的&#xff0c;那么如何在起步階段就將發生事故的概率降到最低呢?在此…

VUE - get 、post 請求后端接口:get 、post 寫法 (Axios 中文說明文檔地址)

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 Axios 中文使用說明文檔地址&#xff1a;Axiox 中文說明文檔 我只是記錄下寫法&#xff0c;兩種請求都能正常運行&#xff1a; 1. 安裝…

C++11新特性——移動語義,右值引用

移動語義 有一些類的資源是__不可共享__的&#xff0c;這種類型的對象可以被移動但不能被拷貝&#xff0c;如&#xff1a;IO 或 unique_ptr 庫容器、string 和 shared_ptr 支持拷貝和移動&#xff0c;IO 和 unique_ptr 則只能移動不能拷貝。。 右值引用 右值引用是必須綁定到…

離合器半聯動點的判斷和技巧 為您支招

現在將離合器半聯動的使用方法揭密如下&#xff1a;將離合器抬到車開始動時你就別再抬了&#xff0c;你如果感覺到車有些快了&#xff0c;可再往下踩些&#xff0c;你如果感覺到車有些慢了&#xff0c;可再往起抬些&#xff0c;這樣可將車速控制在你想要的速度范圍之內。 ● 坡…

客戶端調用 WCF 的幾種方式

轉載網絡代碼.版權歸原作者所有.....客戶端調用&#xff37;&#xff23;&#xff26;的幾種常用的方式&#xff1a;&#xff11;普通調用var factory new DataContent.ServiceReference1.CustomerServiceClient();factory.Open();List<DataContent.ServiceReference1.Cust…

VUE:組件間相互跳轉、頁面帶參跳轉

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 只是記錄下用法&#xff1a; 從 A 頁面跳轉到 B 頁面。 如下寫法&#xff1a; A 頁面跳轉方式&#xff1a; 代碼&#xff1a; getdat…

Protocol Buffer 序列化

Protobuf使用 目錄 proto3的更新定義協議格式編譯protobufprotobuf_API 枚舉和嵌套類標準消息方法解析和序列化 寫一條消息閱讀消息編譯Protobuf擴展優化高級用法 proto3的更新 在第一行非空白非注釋行&#xff0c;必須寫&#xff1a; syntax "proto3";字段規…

如何調整反光鏡和座椅的位置 為您支招

【太平洋汽車網 學車頻道】首先要進行座椅的高度調整&#xff0c;上下調整座椅讓頭部離車頂至少還有一拳的距離。如果座椅調得太高&#xff0c;車輛在顛簸時頭部容易碰到車頂&#xff0c;調得太矮了又會影響視線。然后是前后距離的調整&#xff0c;當腳踩住制動踏板至最深處時…

關于hexo與github使用過程中的問題與筆記

快速閱讀 如何用github 和hexo 創建一個blog 1.github中要新建一個與用戶名同一樣的倉庫&#xff0c; 如:homehe.github.io - 必須是io后綴。一個帳戶 只能建立一個2. 綁定域名 &#xff0c; A記錄指向ip, cname記錄指向homehe.github.io 3. 配置sshkey - 個人設置 -> SSH a…

CSS 中 的 margin、border、padding 區別 (內邊距、外邊距)

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 圖解CSS padding、margin、border屬性 W3C組織建議把所有網頁上的對像都放在一個盒(box)中&#xff0c;設計師可以通過創建定義來控制這…

CMake 常用的預定義變量

CMake 常用的預定義變量 PROJECT_NAME : 通過 project() 指定項目名稱 PROJECT_SOURCE_DIR : 工程的根目錄 PROJECT_BINARY_DIR : 執行 cmake 命令的目錄 CMAKE_CURRENT_SOURCE_DIR : 當前 CMakeList.txt 文件所在的目錄 CMAKE_CURRENT_BINARY_DIR : 編譯目錄&#xff0c;…