這些知識(shí)點(diǎn)你都知道嗎,測(cè)試你的C++入門(mén)程度
做公眾號(hào)以來(lái),很多人都私信問(wèn)我如何入門(mén)C++,其實(shí)之前也有很多人問(wèn)過(guò)這個(gè)問(wèn)題,覺(jué)得還是有必要寫(xiě)一篇完整的文章,給大家參考。
既然要寫(xiě)一篇文章,程序喵本著對(duì)讀者負(fù)責(zé)任的態(tài)度,在寫(xiě)這篇文章前我咨詢(xún)了好多人,既有身經(jīng)百戰(zhàn)的大佬,也有初入職場(chǎng)的精英碼農(nóng),我也查閱了好多入門(mén)C++相關(guān)的資料(發(fā)現(xiàn)內(nèi)容都差不多),結(jié)合自身經(jīng)歷,整理出一篇文章分享給大家。

不同人入門(mén)C++的方式應(yīng)該都有所不同,就我自己而言,大一先學(xué)習(xí)了C語(yǔ)言,稀里糊涂一個(gè)學(xué)期過(guò)去了,用C語(yǔ)言做個(gè)控制臺(tái)界面的學(xué)生管理系統(tǒng),然后大一下學(xué)期學(xué)習(xí)C++時(shí),恰好上了一門(mén)編譯原理的課,當(dāng)時(shí)實(shí)現(xiàn)了個(gè)小編譯器,做過(guò)校園導(dǎo)航系統(tǒng),小型數(shù)據(jù)庫(kù)系統(tǒng),有點(diǎn)小成就感,原以為算是“C++入門(mén)”了吧?,F(xiàn)在想想其實(shí)很多東西學(xué)生時(shí)期還是知其然而不知其所以然。之后在工作中使用C++做項(xiàng)目,才是真正意義的“入門(mén)”了,所以對(duì)我而言,可能更多的是依靠“實(shí)戰(zhàn)”去入門(mén)的C++。
本篇文章我不會(huì)全篇介紹入門(mén)C++需要看什么書(shū),看什么視頻,因?yàn)檫@種文章太多了,相信讀者看的已經(jīng)眼花繚亂了,正文主要介紹C++的一些基礎(chǔ)的知識(shí)點(diǎn),基本上對(duì)每個(gè)知識(shí)點(diǎn)我會(huì)貼一段示例代碼,讀者如果要具體了解某個(gè)知識(shí)點(diǎn),可以去文末的書(shū)籍或視頻中針對(duì)性學(xué)習(xí),我會(huì)在文末列出比較容易入門(mén)的幾本書(shū)和視頻。老規(guī)矩,廢話停止,直接開(kāi)始:

C++是什么?
通俗的講,可以簡(jiǎn)單理解為C語(yǔ)言的延伸語(yǔ)言,這里會(huì)主要介紹C++相對(duì)于C語(yǔ)言來(lái)說(shuō),有哪些特性。
C++的一些特點(diǎn):
允許重載:函數(shù)名相同,參數(shù)類(lèi)型不同,參數(shù)個(gè)數(shù)不同,即可重載,使用見(jiàn)下面代碼:
// overload.ccusing std::cout;void func(int a) { cout << "func " << a << "\n"; }void func(int a, int b) { cout << "func " << a << " " << b << "\n"; }void func(float a) { cout << "func " << a << "\n"; }int main() {func(1);func(1, 2);func(1.1f);return 0;}
namespace:namespace是什么?其實(shí)就相當(dāng)于為變量和函數(shù)劃個(gè)范圍
如果不加namespace會(huì)有什么問(wèn)題:
// namespace.ccvoid func() {std::cout << "n1 func" << "\n";}void func() {std::cout << "n2 func" << "\n";}// redefinition of 'func'int main() {func();return 0;}
結(jié)果大家可能都知道,這樣的代碼編譯會(huì)報(bào)錯(cuò):redefinition of 'func'
一般規(guī)定一個(gè)程序內(nèi)不允許同一個(gè)強(qiáng)符號(hào)有多個(gè)定義,那怎么解決?
可使用namespace:
// namespace.ccnamespace n1 {void func() {std::cout << "n1 func"<< "\n";}} // namespace n1namespace n2 {void func() {std::cout << "n2 func"<< "\n";}} // namespace n2// redefinition of 'func'int main() {n1::func();n2::func();return 0;}
這樣編譯成功,加入namespace后其實(shí)對(duì)編譯器來(lái)說(shuō)就是兩個(gè)不同的函數(shù),因?yàn)樯傻氖莾蓚€(gè)不同的符號(hào):

注意:不要過(guò)多using namespace xxx,例如using namespace std;可能就容易引起符號(hào)沖突問(wèn)題,
可以使用using std::cout這種方式。
class & struct:
很多面試官可能都會(huì)問(wèn)class和struct的區(qū)別?具體答案我這里就不貼出來(lái)了,大家自己搜索答案就可,或者留言問(wèn)我,它倆在C++里其實(shí)沒(méi)什么區(qū)別,就是默認(rèn)權(quán)限不同而已。
這里需要掌握幾個(gè)知識(shí)點(diǎn):
如何定義一個(gè)類(lèi)
構(gòu)造函數(shù)的定義及使用
析構(gòu)函數(shù)的定義及使用
拷貝構(gòu)造函數(shù)的定義及使用
移動(dòng)構(gòu)造函數(shù)的定義及使用
賦值構(gòu)造函數(shù)的定義及使用
移動(dòng)賦值函數(shù)的定義及使用
下面貼出一段示例代碼:
// class.ccusing std::cout;class TClass {public:TClass() {cout << "構(gòu)造函數(shù)1" << "\n";}TClass(int a) : a_(a) {cout << "構(gòu)造函數(shù)2" << "\n";data_ = new int[10];}TClass(const TClass &a) : a_(a.a_) {cout << "拷貝構(gòu)造函數(shù)" << "\n";data_ = new int[10];memcpy(data_, a.data_, 10 * sizeof(int));}TClass(TClass &&a) : a_(a.a_), data_(a.data_) {a.data_ = nullptr;cout << "移動(dòng)構(gòu)造函數(shù)" << "\n";}TClass &operator=(TClass &a) {a_ = a.a_;if (data_) delete[] data_;data_ = new int[10];memcpy(data_, a.data_, 10 * sizeof(int));cout << "賦值構(gòu)造函數(shù)" << "\n";return *this;}TClass &operator=(TClass &&a) {a_ = a.a_;if (data_) delete[] data_;data_ = a.data_;a.data_ = nullptr;cout << "移動(dòng)賦值函數(shù)" << "\n";return *this;}~TClass() {cout << "析構(gòu)函數(shù)" << "\n";if (data_) delete[] data_;}void func() { cout << "a " << a_ << " \n"; }private:int a_;int *data_;};int main() {TClass a(1); //構(gòu)造函數(shù)2TClass b(a); //拷貝構(gòu)造函數(shù)TClass c(std::move(a)); //移動(dòng)構(gòu)造函數(shù)c = b; //賦值構(gòu)造函數(shù)c = std::move(b); //移動(dòng)賦值函數(shù)TClass d = c; //拷貝構(gòu)造函數(shù)TClass e = std::move(d); //移動(dòng)構(gòu)造函數(shù)a.func();b.func();return 0;}
模板
模板主要分為函數(shù)模板和類(lèi)模板,下面有示例代碼:
// template.ccusing std::cout;template <typename T>T max(T a, T b) { // 函數(shù)模板return a > b ? a : b;}template <typename T>struct Vec { // 類(lèi)模板Vec(T a) : a_(a) {}void func() { cout << "func value " << a_ << "\n"; }T a_;};int main() {cout << "max(1, 2) " << max(1, 2) << "\n";cout << "max(1.1f, 2.2f) " << max(1.1f, 2.2f) << "\n";cout << "max(1.10, 2.20) " << max(1.10, 2.20) << "\n";Vec<int> vi(1);vi.func();Vec<float> vf(1.1f);vf.func();return 0;}
引用:C++里多了個(gè)引用的概念,很多人面試應(yīng)該有被問(wèn)到過(guò)引用和指針有什么區(qū)別吧?
關(guān)于指針和引用,在這里強(qiáng)烈推薦大家閱讀《面試系列之指針和引用的使用場(chǎng)景》這篇文章,通俗易懂的純干貨型文章。
大家可以記住一個(gè)關(guān)鍵點(diǎn)就是引用追求從一而終,它的指向永遠(yuǎn)不會(huì)變,而指針是個(gè)善變的東西,它的指向隨時(shí)可以改變,一般開(kāi)發(fā)中會(huì)使用const &方式進(jìn)行參數(shù)傳遞,省去不必要的對(duì)象拷貝:
void func(const A& a) {}多態(tài):這是C++語(yǔ)言所支持的或者說(shuō)面向?qū)ο蟮囊粋€(gè)重要特性,直接看代碼:
// polymorphic.ccusing std::cout;struct Base {Base() {cout << "base construct" << "\n";}virtual ~Base() {cout << "base destruct" << "\n";}void FuncA() {}virtual void FuncB() { cout << "Base FuncB \n"; }int a;int b;};struct Derive1 : public Base {Derive1() { cout << "Derive1 construct \n"; }~Derive1() { cout << "Derive1 destruct \n"; }void FuncB() override { cout << "Derive1 FuncB \n"; }};struct Derive2 : public Base {Derive2() { cout << "Derive2 construct \n"; }~Derive2() { cout << "Derive2 destruct \n"; }void FuncB() override { cout << "Derive2 FuncB \n"; }};int main() {{Derive1 d1;d1.FuncB();}cout << "======= \n";{Derive1 d1;d1.FuncB();Base &b1 = d1;b1.FuncB();}cout << "======= \n";{Derive2 d2;d2.FuncB();Base &b2 = d2;b2.FuncB();}cout << "======= \n";{Base b;b.FuncB();}cout << "======= \n";{Base *b = new Derive1();b->FuncB();delete b;}return 0;}
這里簡(jiǎn)單介紹了實(shí)現(xiàn)多態(tài)的兩種方式:通過(guò)指針或者通過(guò)引用。
注意:
構(gòu)造函數(shù)不能是虛函數(shù)
基類(lèi)析構(gòu)函數(shù)最好是虛函數(shù)
盡量不要使用多繼承
new和delete
C++內(nèi)存申請(qǐng)和釋放會(huì)使用new和delete關(guān)鍵字,而基本不會(huì)使用C語(yǔ)言中的malloc和free,可看下面的示例代碼:
// new_delete.ccusing std::cout;struct A {int a_;};int main() {A* a1 = new A;delete a1;A* a2 = new A[10];delete[] a2;return 0;}
注意:
new和delete要配對(duì)使用
new[]和delete[]要配對(duì)使用
maloc和free要配對(duì)使用
類(lèi)型轉(zhuǎn)換:既然使用了C++語(yǔ)言,在類(lèi)型轉(zhuǎn)換方面就一定要使用C++風(fēng)格,這比C語(yǔ)言風(fēng)格的強(qiáng)制類(lèi)型轉(zhuǎn)換更安全。
static_cast
float f = 1.0f;int a = static_cast<int>(f);
const_cast
const char* cc = "hello world\n";char* c = const_cast<char*>(cc);
dynamic_cast
struct Base {};struct Derive : public Base {};void func() {Base* base = new Derive;Derive* derive = dynamic_cast<Derive*>(base);}
reinterpret_cast
A *a = new A;void* d = reinterpret_cast<void*>(a);
C++11常用新特性
C++的重大變革肯定是C++11啦,C++11標(biāo)準(zhǔn)引入了很多有用的新特性,這仿佛打開(kāi)了新世界的大門(mén),讓C++開(kāi)發(fā)者開(kāi)發(fā)效率大幅提高,下面我會(huì)列出C++11常用的新特性,并附上簡(jiǎn)單的實(shí)例代碼:
auto & decltype:用于類(lèi)型推導(dǎo)
auto用于推導(dǎo)變量類(lèi)型,decltype用于推導(dǎo)表達(dá)式返回值類(lèi)型
// auto_decltype.ccint main() {auto a = 10; // 10是int型,可以自動(dòng)推導(dǎo)出a是intint x = 0;decltype(x) y; // y是int類(lèi)型decltype(x + y) z; // z是int類(lèi)型return 0;}
std::function:用于封裝一個(gè)函數(shù),功能類(lèi)似于函數(shù)指針,但卻比函數(shù)指針?lè)奖愕亩唷?/span>
// function.ccusing std::cout;void printNum(int i) { cout << "print num " << i << "\n"; }typedef void (*FuncPtr)(int i);int main() {FuncPtr ptr = printNum;ptr(2);void (*ptr2)(int) = printNum;ptr2(3);std::function<void(int)> func = printNum;func(1);return 0;}
lambda表達(dá)式:隨時(shí)可方便定義的一個(gè)匿名函數(shù)。
// lambda.ccusing std::cout;int main() {auto func = [](int a) -> int { return a + 1; };int b = func(2);cout << b << "\n";return 0;}
注意:lambda表達(dá)式的變量捕獲方式分為值捕獲和引用捕獲
int a = 0;auto f1 = [=](){ return a; }; // 值捕獲acout << f1() << endl;auto f2 = [=]() { return a++; }; // 修改按值捕獲的外部變量,errorauto f3 = [=]() mutable { return a++; };
std::function和std::bind使得我們平時(shí)編程過(guò)程中封裝函數(shù)更加的方便,而lambda表達(dá)式將這種方便發(fā)揮到了極致,可以在需要的時(shí)間就地定義匿名函數(shù),不再需要定義類(lèi)或者函數(shù)等,在自定義STL規(guī)則時(shí)候也非常方便,讓代碼更簡(jiǎn)潔,更靈活,開(kāi)發(fā)效率也更高。
std::thread:C++11中使用std::thread創(chuàng)建線程,使用非常的方便,直接看代碼:
void func() { std::cout << "new thread \n"; }int main() {std::thread t(func);if (t.joinable()) {t.join(); // 或者t.detach();}return 0;}
注意:使用std::thread一定要記得join或者detach。
RAII:既然使用C++,那一定要理解RAII風(fēng)格(利用對(duì)象生命周期管理資源),繼續(xù)往下看:
如果不使用RAII風(fēng)格,加鎖解鎖我們?cè)趺崔k?
// 不使用RAIIvoid func() {std::mutex mutex;mutex.lock();if (xxx) {mutex.unlock();return;}if (xxxx) {mutex.unlock();return;}...mutex.unlock();}
而如果使用RAII呢:
void func() {std::unique_lock<std::mutex> lock(mutex);if (xxx) return;if (xxxx) return;...}
是不是方便了很多,這里介紹了std::unique_lock的使用,還有一種鎖是std::lock_guard,使用方式相同,至于它們之間有什么區(qū)別,我這里賣(mài)個(gè)關(guān)子,大家可以自行查找哈,鍛煉一下自己的搜索能力。
智能指針也是典型的RAII風(fēng)格,如果不使用智能指針管理內(nèi)存是這樣:
void func() {A* a = new A;if (xxx) {delete a;return;}if (xxxx) {delete a;return;}...delete a;}
而如果使用智能指針是這樣:
void func() {std::shared_ptr<A> sp = std::make_shared<A>(); // unique_ptr類(lèi)似std::shared_ptr<A> sp = std::shared_ptr<A>(new A); // 盡可能使用make_shared或者make_unique(C++14)if (xxx) return;if (xxxx) return;...}
又方便了很多吧,unique_ptr和shared_ptr的區(qū)別本文也不介紹,文章最后我會(huì)列出學(xué)習(xí)資料,在學(xué)習(xí)資料里可以找到答案。
原子操作:使用std::atomic可達(dá)到原子效果,使用方法:
std::atomic<int> ai;ai++;ai.store(100);int a = ai.load();
enum class:帶有作用域的枚舉類(lèi)型,可用于完全替代enum,為什么要使用enum class呢?我們先看一段直接使用enum的代碼:
enum AColor {kRed,kGreen,kBlue};enum BColor {kWhite,kBlack,kYellow};int main() {if (kRed == kWhite) {cout << "red == white" << endl;}return 0;}
不帶作用域的枚舉類(lèi)型可以自動(dòng)轉(zhuǎn)換成整形,且不同的枚舉可以相互比較,代碼中的kRed居然可以和kWhite比較,這都是潛在的難以調(diào)試的bug,而這種完全可以通過(guò)有作用域的枚舉來(lái)規(guī)避。
所以出現(xiàn)了enum class:
enum class AColor {kRed,kGreen,kBlue};enum class BColor {kWhite,kBlack,kYellow};int main() {if (AColor::kRed == BColor::kWhite) { // 編譯失敗cout << "red == white" << endl;}return 0;}
使用enum class可以在編譯層面就規(guī)避掉一些難以調(diào)試的bug,使代碼健壯性更高。
condition_variable:條件變量
std::condition_variable cv;std::mutex mutex_;void func1() {std::unique_lock<std::mutex> lock(mutex_);--count;if (count == 0) cv.notify_all();}void func2() {std::unique_lock<std::mutex> lock(mutex_);while (count) {cv.wait(lock);}}
注意:不要直接用wait,而要記得使用wait(mutex, cond)或者while(cond) {wait(mutex);}
nullptr:表示空指針可以使用nullptr,不要使用NULL
void func(char*) {cout << "char*";}void func(int) {cout << "int";}int main() {func(NULL); // 編譯失敗 error: call of overloaded ‘func(NULL)’ is ambiguousfunc(nullptr); // char*return 0;}
chrono:時(shí)間相關(guān)函數(shù)可以考慮使用chrono庫(kù),例如休眠某個(gè)時(shí)間段:
void customSleep() {std::this_thread::sleep_for(std::chrono::milliseconds(5));std::this_thread::sleep_for(std::chrono::seconds(5));}
使用chrono可以精確的指定時(shí)間單位,可以明確的之道休眠的是幾毫秒還是幾秒,非常方便。
chrono中包含三種時(shí)鐘:
steady_clock:?jiǎn)握{(diào)時(shí)鐘,只會(huì)增加,常用語(yǔ)記錄程序耗時(shí)
system_clock:系統(tǒng)時(shí)鐘,會(huì)隨系統(tǒng)時(shí)間改動(dòng)而變化
high_resolution_clock:當(dāng)前系統(tǒng)最高精度的時(shí)鐘,通常就是steady_clock
拿計(jì)時(shí)舉例:
void customSleep() {std::this_thread::sleep_for(std::chrono::milliseconds(5));std::this_thread::sleep_for(std::chrono::seconds(5));}int main() {std::chrono::time_point<std::chrono::high_resolution_clock> begin = std::chrono::high_resolution_clock::now();customSleep();auto end = std::chrono::high_resolution_clock::now();auto diff = end - begin;long long diffCount = std::chrono::duration_cast<std::chrono::milliseconds>(diff).count();cout << diffCount << "\n";return 0;}
STL
使用C++有幾個(gè)能不用STL標(biāo)準(zhǔn)庫(kù)的,這里列出了常用的STL,并貼出使用代碼。
std::vector:可以理解為動(dòng)態(tài)可自動(dòng)擴(kuò)容的數(shù)組,使用vector需要掌握幾個(gè)常用函數(shù)的使用
resize
reserve
capacity
clear
swap
at
靈活使用
void func() {std::vector<int> vec;std::cout << "vector size " << vec.size() << "\n"; // vector size 0std::cout << "vector capacity " << vec.capacity() << "\n"; // vector capacity 0vec.push_back(1);vec.emplace_back(2);vec.push_back(3);std::cout << "====================== \n";std::cout << "vector size " << vec.size() << "\n"; // vector size 3std::cout << "vector capacity " << vec.capacity() << "\n"; // vector capacity 4vec.reserve(200);std::cout << "====================== \n";std::cout << "vector size " << vec.size() << "\n"; // vector size 3std::cout << "vector capacity " << vec.capacity() << "\n"; // vector capacity 200vec.resize(20);std::cout << "====================== \n";std::cout << "vector size " << vec.size() << "\n"; // vector size 20std::cout << "vector capacity " << vec.capacity() << "\n"; // vector capacity 200vec.clear();std::cout << "====================== \n";std::cout << "vector size " << vec.size() << "\n"; // vector size 0std::cout << "vector capacity " << vec.capacity() << "\n"; // vector capacity 200std::vector<int>().swap(vec);std::cout << "====================== \n";std::cout << "vector size " << vec.size() << "\n"; // vector size 0std::cout << "vector capacity " << vec.capacity() << "\n"; // vector capacity 0}
注意:for循環(huán)中erase某一個(gè)節(jié)點(diǎn)時(shí)要處理好迭代器的指向問(wèn)題
void erase(std::vector<int> &vec, int a) {for (auto iter = vec.begin(); iter != vec.end();) { // 正確if (*iter == a) {iter = vec.erase(iter);} else {++iter;}}for (auto iter = vec.begin(); iter != vec.end(); ++iter) { // errorif (*iter == a) {vec.erase(iter);}}}
繼續(xù)注意:remove函數(shù)不會(huì)真正的刪除某個(gè)元素,它只會(huì)把某個(gè)要?jiǎng)h除的元素移動(dòng)到容器尾部,真正要?jiǎng)h除還需使用erase,需要remove要和erase搭配使用。
bool isOdd(int i) { return i & 1; }void print(const std::vector<int>& vec) {for (const auto& i : vec) {std::cout << i << ' ';}std::cout << std::endl;}int main() {std::vector<int> v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};print(v);std::remove(v.begin(), v.end(), 5); // errorprint(v);v.erase(std::remove(v.begin(), v.end(), 5), v.end());print(v);v.erase(std::remove_if(v.begin(), v.end(), isOdd), v.end());print(v);}
std::array:開(kāi)發(fā)時(shí)可以考慮用std::array代替普通數(shù)組,因?yàn)樗蝎@取長(zhǎng)度,遍歷,邊界檢查等功能
int main() {std::array<int, 10> array;// int array[10];array.at(10) = 20; // terminating with uncaught exception of type std::out_of_range: array::atstd::cout << "hello " << array.at(10) << "\n";return 0;}
std::map & std::unordered_map:都是key-value級(jí)別的字典,使用方式相同,一個(gè)使用樹(shù)實(shí)現(xiàn),一個(gè)使用哈希表實(shí)現(xiàn),可根據(jù)需求選擇具體使用哪種字典。
int main() {std::map<int, std::string> map;map[1] = std::string("hello");std::cout << map[1] << "\n";return 0;}
std::list:鏈表
int main() {std::list<int> list{1, 2, 3, 2};list.sort();// std::sort(list.begin(), list.end());for (auto i : list) {std::cout << i << " ";}std::cout << "\n";return 0;}
注意:list的排序需要使用list.sort(),不能使用std::sort
std::tuple:我個(gè)人經(jīng)常使用,tuple像結(jié)構(gòu)體一樣,也可以理解為pair的延伸
struct T {int a_;int b_;int c_;T(int a, int b, int c) : a_(a), b_(b), c_(c) {}};int main() {T a(1, 2, 3);std::cout << "a " << a.a_ << " b " << a.b_ << " c " << a.c_ << "\n";std::tuple<int, int, int> tuple = std::make_tuple(2, 3, 4);std::cout << "a " << std::get<0>(tuple) << " b " << std::get<1>(tuple) << " c " << std::get<2>(tuple) << "\n";return 0;}
編碼規(guī)范
每一門(mén)語(yǔ)言基本都有幾種常見(jiàn)的編碼規(guī)范,想必大多數(shù)C++開(kāi)發(fā)者都會(huì)基于google的C++編碼規(guī)范去開(kāi)發(fā),下面是格式化代碼常用的一些配置:
// .clang-formatBasedOnStyle: GoogleIndentWidth: 4ColumnLimit: 120SortIncludes: trueMaxEmptyLinesToKeep: 2
下面再列出一些常見(jiàn)的命名規(guī)則:
文件命名:文件名字要全部小寫(xiě),中間用_相連,后綴名為.cc .cpp和.h
hello_world.cchello_world.cpphello_world.h
類(lèi)型命名:類(lèi)型名稱(chēng)的每個(gè)單詞首字母均大寫(xiě):
struct MyExcitingClass;常量和枚舉命名:聲明為 constexpr 或 const 的變量, 或在程序運(yùn)行期間其值始終保持不變的, 命名時(shí)以 “k” 開(kāi)頭, 大小寫(xiě)混合
const int kDaysInAWeek = 7;函數(shù)命名:大駝峰方式
MyExcitingFunction()命名空間命名:全部小寫(xiě)
namespace abcdefg{}成員變量命名:小寫(xiě)字母加下劃線分隔,最后加個(gè)下劃線
普通變量命名:小寫(xiě)字母加下劃線分隔
struct A {int a_;}void f(int a) {int b;}
代碼:
struct A {int a_;}void f(int a) {int b;}
規(guī)范要點(diǎn)總結(jié)
下面是我查閱很多資料又結(jié)合自身工作經(jīng)驗(yàn)總結(jié)的一些要點(diǎn):
每個(gè)頭文件都要使用#ifndef #define或者#pragma once修飾,避免被重復(fù)引用
每個(gè)命名都要盡可能表達(dá)清晰其具體含義,除非特別常用,否則不要使用縮寫(xiě),寧可名字特別長(zhǎng)
鼓勵(lì)在 .cc 文件內(nèi)使用匿名命名空間或 static 聲明. 使用具名的命名空間時(shí), 其名稱(chēng)可基于項(xiàng)目名或相對(duì)路徑. 盡量不要使用 using 指示
一行盡量不要超過(guò)120個(gè)字符,一個(gè)函數(shù)盡量不要超過(guò)40行(性能優(yōu)化除外),同時(shí)一個(gè)文件盡量控制在500行內(nèi).
所有的引用形參如不做改動(dòng)一律加const,在任何可能的情況下都要使用 const或constexpr
new內(nèi)存的地方盡量使用智能指針,c++11 就盡量用std::unique_ptr
合理使用移動(dòng)語(yǔ)義或者COW,減少內(nèi)存拷貝
使用 C++ 的類(lèi)型轉(zhuǎn)換, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等轉(zhuǎn)換方式
明確使用前置++還是后置++的具體含義,如不考慮返回值,盡量使用前置++ (++i)
不要使用uint類(lèi)型,如果需要使用大整型可以考慮int64,否則類(lèi)型的隱式類(lèi)型轉(zhuǎn)換會(huì)帶來(lái)很多麻煩
如無(wú)特殊必要不要使用宏,可以考慮使用const或constexpr替代宏,宏的全局作用域很麻煩,如果非要用在馬上要使用時(shí)才進(jìn)行 #define, 使用后要立即 #undef
盡可能用 sizeof(varname) 代替 sizeof(type).使用 sizeof(varname) 是因?yàn)楫?dāng)代碼中變量類(lèi)型改變時(shí)會(huì)自動(dòng)更新. 或許會(huì)用 sizeof(type) 處理不涉及任何變量的代碼,比如處理來(lái)自外部或內(nèi)部的數(shù)據(jù)格式,這時(shí)用變量就不合適了
類(lèi)型名如果過(guò)長(zhǎng)的話可以考慮使用auto關(guān)鍵字
注釋統(tǒng)一使用 // ,不要通過(guò)注釋禁用代碼,擅用git,不要為易懂的代碼寫(xiě)注釋
寫(xiě)完代碼后記得format,每個(gè)項(xiàng)目最好都有統(tǒng)一的.clang_format文件
使用C++的string替代C語(yǔ)言風(fēng)格的char*
盡量使用STL標(biāo)準(zhǔn)庫(kù)的容器而不是C語(yǔ)言風(fēng)格的數(shù)組,數(shù)組的越界訪問(wèn)之類(lèi)當(dāng)時(shí)是不會(huì)報(bào)錯(cuò)的,反而可能弄臟堆棧信息,導(dǎo)致奇奇怪怪難以排查的bug
可以更多的使用模板元編程,盡量多的使用constexpr等編譯期計(jì)算,編譯器是我們的好搭檔,個(gè)人認(rèn)為模板元編程以后會(huì)是C++的主流技術(shù)
可以考慮更多的使用異常處理方式,而不是C語(yǔ)言風(fēng)格的errno錯(cuò)誤碼等
常用大括號(hào)控制生命周期,能提前結(jié)束的就可以提前結(jié)束
注意memcpy和memset的使用,僅適用于POD結(jié)構(gòu)
理解內(nèi)存對(duì)齊,可以使結(jié)構(gòu)體更小,也可以使訪問(wèn)速度更快
基類(lèi)析構(gòu)函數(shù)一定要加virtual修飾
謹(jǐn)慎使用多繼承,可能導(dǎo)致菱形繼承
全局變量不要有任何依賴(lài)關(guān)系
這里我只列出來(lái)兩本學(xué)習(xí)C++的書(shū),可以按順序閱讀,我相信讀完并理解這兩本書(shū)的內(nèi)容可以算是入門(mén)C++啦~
書(shū)
《C++ Primer Plus》 注意是Plus!
《Effective Modern C++》
視頻
有喜歡看書(shū)的也有喜歡看視頻的,這里再推薦兩個(gè)比較好的C++視頻教程:
①清華大學(xué)鄭莉教授的視頻,通俗易懂
https://www.bilibili.com/video/BV1QE41147RT?from=search&seid=8449657388898463710
②還有一個(gè)是技術(shù)交流群群友推薦,也是清華大學(xué)的課程
https://www.bilibili.com/video/BV1yk4y1d7ns?p=1
③喜歡英文的同學(xué)可以看看MIT或斯坦福的C++課程
具體是看書(shū)快還是看視頻快,因人而異。適合自己的才是最實(shí)在、最好的。
再推薦兩個(gè)比較好的C++學(xué)習(xí)網(wǎng)站:
最好的肯定是cppreference啦,不用多說(shuō),也可以看看cppfaq,里面的很多問(wèn)題可以增長(zhǎng)我們的見(jiàn)識(shí),讓我們對(duì)C++的理解也更加深刻。
http://www.sunistudio.com/cppfaq/
C++學(xué)習(xí)資料免費(fèi)獲取方法:關(guān)注程序喵大人,后臺(tái)回復(fù)“程序喵”即可免費(fèi)獲取40萬(wàn)字C++進(jìn)階獨(dú)家學(xué)習(xí)資料。
