同一個演算法,不同的人寫出來,效率上可能會差1000倍,寫程式的同時,要注意性能,
文章目錄
- 玄學
- 命名空間
- 左值非const
- 結構體 set 和 map
- STL 容器選擇
- 關于-O3的過度優化
- 記憶體上的性能優化
- 關于 set 和 map
- const_cast的妙用
- 關于函式傳遞陣列與指標
- 匿名運算式
- Debug 模式和 Release 模式
- 全域變數
- Ubuntu 修改C頭檔案(鏈接庫、環境變數)目錄
- 類中vector的初始化
- vector在使用迭代器遍歷程序中,不能使用erase (),否則出錯
- 關于型別重命令
- explicit關鍵字
- static_cast
- 關于 const 成員函式多載
- 函式模板型別推斷不能通過左值
- 關于列舉型別
- 關于傳入引數的默認值
- 關于模板
- 頭檔案定義
- for_each
- 關于 mem_fun
- std::bind2nd
- std::transform
- 類成員變數的初始化
- 自定義默認代碼
- valarry
- unique_ptr
- accumulate 自定義資料型別的處理
- dynamic_cast
- numeric_limits
- 虛函式
- mutable 關鍵字
- 大括號
- typename 關鍵字
- const
玄學
所謂的玄學,主要發生在有些情況下你寫的程式壓根就是錯的,只不過當前的編譯環境下因為某些不知名的原因沒識別出錯誤,換個環境就報錯了,不同的編譯環境對于錯誤的包容方面是不一樣的,有的環境下,這種錯誤不識別,有的環境下,那種錯誤不識別,總之,你就是寫錯了的,編譯器沒認出來,所以說,規范編程很重要,不要感覺,編譯和運行不報錯就可以亂來,
命名空間
使用命名空間可以有效地防止命名沖突
左值非const
-
在C++語言中,對于一個由類名加倆冒號再加成員名構成的東西(學名叫“quilified-id”),比如:A::x,只有當x是A類的靜態成員的時候,A::x才能表示一個左值,
-
對于函式型別到函式指標型別的默認轉換,只有當函式型別是左值的時候才行,所有對于非靜態的成員函式,就不存在這種從函式型別到函式指標型別的默認轉換,于是編譯器也就不知道這個
結構體 set 和 map
set中放入結構體或者自定義物件時,結構體的寫法,要多載小于號,注意是 bool operator<(const A & )const 的形式(map一樣要多載),關于比較函式,return false 就是交換,直到任意兩個輸入,走一遍都能return true,具體怎么比的,就很迷,
STL 容器選擇
- C++ 的 STL 容器主要分為序列式和關聯式,所謂的序列式宏觀上理解就是小鬼們按一定的順序排排坐,所謂關聯式類似于資料庫里面,有一個 key,有一個值這樣的,序列式的容器包含 array、vector、list、deque,關聯式的容器有 map、set 和 multi 以及 unordered 兩個層面的變種,除此之外,還有在學資料結構時,耳熟能詳的堆疊(stack)、佇列(queue)等,
- 很多人寫演算法,想到哪個就用哪個,這是很不對的,在選擇容器的時候,我們要考慮程式的性能,那么應該如何選擇合適的容器呢?如果你想表達的資料,stack 或者 queue 的特征已經非常明顯了,直接用他倆;如果對資料后續要有大量的查找,就用關聯式容器,其中又以無序的查找最快,但是它無序;如果有大量的添加和洗掉操作(特別是在中間),對次序有要求,選擇 list,而盡可能地避免 vector 和 array;如果對元素的次序要求比較高,而且有隨機訪問(所謂的隨機訪問,是說有按下標比如說v[3],v[5]這樣訪問的),且沒有元素在中間的插入或者洗掉,且沒有極其大量的查找,可以選擇 vector 和 array…知道或者大概知道長度的,盡量用array,
關于-O3的過度優化
O3的優化很神奇,他會自動判斷你哪些代碼是無效,比如說某個回圈不運行可能對結果沒影響,她就會給你忽略過去,
開O3優化會讓你產生一些錯覺,找熱點和除錯程式的時候盡量不要開-O3優化,主要原因是資料區用滿了,-O3優化下,不會報錯,不同的全域變數之間,相互污染了,
記憶體上的性能優化
記憶體盡量整一塊,不要東找找,西找找,任務集中,相同的任務放在一起做,
簡單地說,如果你哪個變數在全域變數開太大,導致 bss 越界了,就可以用 malloc 動態開辟記憶體,雖然速度慢點,但是沒有問題,
全域的未初始化變數存在于.bss段中,具體體現為一個占位符,全域的已初始化變數存于.data段中,
系統允許你的資料是 1G ,那么這1G = 已初始化資料 + bss,
關于 set 和 map
-
map和unordermap更適合建資料庫,以供查詢,
-
用bool向量來替代洗掉,有時候可以避免記憶體的開辟和變數的遷移,用什么資料結構記憶體占用等方面本質相同的,差距在于操作時是否有記憶體的開辟,以及資料的拷貝,
-
別一開始就定義很完整的結構體,先做主要作業,后面需要什么再補上,
-
結構體set得自己多載比較規則,
-
set map雖然查找很快,但是建立非常慢,適合只會增加出現,很少會沒的東西,
-
容器的insert其實一種拷貝,而不是參考,
-
能不用指標的,盡量別用,
-
map和set是插入的時候才排序,如果程序中有值的改變,是不會進行重排序的,
const_cast的妙用
學會用 const_cast 處理一些報錯,
關于函式傳遞陣列與指標
-
同一個變數的多次宣告,都指向一個地址,比如,你定義了 int a多次,其實都是指向一個地址,
-
一個經驗總結就是:c++能不用指標就不用指標,盡量用參考,指標我們是用不好的,尤其是多重指標,
-
存陣列,不能把臨時陣列的指標當成這個屬于存下來,否則你下次指標指向的內容改變了,陣列也就變了,同一個臨時變數的多次重新宣告和定義,地址總是不變的,并不會開辟新的記憶體,
匿名運算式
C++ 11 新特性,用多了是不是很酷,
[=] (int x, int y) -> bool {return x%10 < y%10; }
我們可以看到,匿名運算式無非就是一般的函式把函式名隱藏,然后把回傳型別用->放到后面,加了一個中括號,表示捕獲串列,即從外部傳入函式體內的一些變數,包括參考和非參考方式的傳入,= 就是非參考傳入,& 就是參考傳入,值可以被修改,也可以單獨地傳入某些個外部變數,
Debug 模式和 Release 模式
一般來說,release 模式跑起來更快,但是,有時寫記憶體訪問沖突問題,release 模式下不報錯,但是再在debug 模式下會報錯,也有的時候有問題也都不報錯,洗掉了一個數之后,for 回圈的迭代器是否會繼續往后走,是未知的,有時候會,有時候不會,C++ 的記憶體這個東西,是個玄學,保證代碼正確即可,不必深究,如:
int main()
{
vector<int> a = { 7,6,5,4,3,2,1 };
for (auto& it : a)
{
cout << "it:" << it << endl;
if (it == 6)
{
for (auto ite = a.begin(); ite != a.end(); ite++)
{
if (*ite == it)
a.erase(ite);
}
cout <<"after del:" << it << endl;
}
}
return 0;
}
全域變數
vector<int> a(10); 等價于 vector<int> a(10,0);存放于存于.data段中,
Ubuntu 修改C頭檔案(鏈接庫、環境變數)目錄
最直接的就是在/etc/profile添加,然后關機重啟,部分情況下 source 一下也可以,LD_LIBRARY_PATH 是元件,LIBRARY_PATH 是靜態鏈接庫,目錄只會在給定的路徑下查找,而不會遞回地查找子檔案夾,冒號拼接,最后不加斜桿,看清楚需要添加的路徑是哪個,不要少加了幾層,也不要多加了幾層,
export C_INCLUDE_PATH=$C_INCLUDE_PATH:/usr/include/libxml2:/home/test/Desktop/boost_1_75_0
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/include/libxml2:/home/test/Desktop/boost_1_75_0
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/test/Desktop/boost_1_75_0/stage/lib
export LIBRARY_PATH=$LIBRARY_PATH:/home/test/Desktop/boost_1_75_0/stage/lib
類中vector的初始化
在類XXX中創建一個10個元素的vector A, 初始值為0:
class XXX{
public:
vector<int> A(10, 0);
......
}
報錯expected identifier before numeric constant,意為:數字常量前應有識別符號,原因: 編譯器認為, 你正在定義一個成員函式,函式名為A ,回傳值型別為 vector ,函式有兩個輸入引數(10, 0), 但是引數的給定方式有錯誤,修改:
class XXX{
public:
vector<int> A = vector<int> (10, 0);
......
}
vector在使用迭代器遍歷程序中,不能使用erase (),否則出錯
關于型別重命令
vector帶大小不代表一種型別,所以typedef std::vector<double>(3) v3type;是不合法的,C++ 11的標準下可以使用using identifier = type;替代typedef,
explicit關鍵字
C++提供了關鍵字 explicit,使用 explicit 關鍵字,便不允許建構式的隱式轉換呼叫,如下,t3 的構造是會報錯的,因為explicit,規定了 Test2 的構造只能使用默認的構造,
#include <iostream>
using namespace std;
class Test1
{
public :
Test1(int num):n(num){}
private:
int n;
};
class Test2
{
public :
explicit Test2(int num):n(num){}
private:
int n;
};
int main()
{
Test1 t1 = 12;
Test2 t2(13);
Test2 t3 = 14;
return 0;
}
static_cast
基本型別之間的轉換,但不能用于基本型別指標之間的型別轉換(void指標和基本型別指標之間可以),
double d=0;
int i=static_cast<int>(d);
關于 const 成員函式多載
如果類實體為const,就呼叫的const函式,否則呼叫非const,直接看例子,
using namespace std;
class Test
{
protected:
int x;
public:
Test (int i):x(i) { }
void fun() const
{
cout << "fun() const called " << endl;
}
void fun()
{
cout << "fun() called " << endl;
}
};
int main()
{
Test t1 (10);
const Test t2 (20);
t1.fun(); //此處呼叫fun()
t2.fun(); //此處呼叫 fun() const
return 0;
}
函式模板型別推斷不能通過左值
在推斷函式模板的型別時,型別必須顯示地出現在函式的呼叫運算式里面,否則會出錯,如:
template <Uint _Size>
inline SVectorCL<_Size> std_basis(Uint i)
{
SVectorCL<_Size> ret(0.);
if (i>0) ret[i-1]= 1.;
return ret;
}
//正確呼叫
SVectorCL<5> myVector9 = std_basis<5>(2);
//錯誤呼叫
SVectorCL<5> myVector9 = std_basis(2);
關于列舉型別
所謂的列舉,本質就是限定一個變數只能取一些離散的整數值,為了增加可讀性,往往會找一些字母串來表示作為這些整數值得代號,列舉變數的值只能取列舉常量表中所列的值,就是整型數的一個子集,基本的寫法如下:
enum color_set1 {RED, BLUE, WHITE, BLACK}; // 定義列舉型別color_set1
enum week {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; // 定義列舉型別week
關于傳入引數的默認值
一般情況下,在函式呼叫時形參從實參那里取得值,因此實參的個數應與形參相同,有時多次呼叫同一函式時用同樣的實參,C++ 提供簡單的處理辦法,給形參一個默認值,這樣形參就不必一定要從實參取值了,這種方法比較靈活,可以簡化編程,提高運行效率,
關于模板
處理模板函式和類模板時,因為要進行實體化模板函式和類模板,要求編譯器在實體化模板時必須在背景關系中可以查看到其定義物體,因為編譯器不會會預先知道 typename 實參是什么呢,因此模板的實體化與定義體必須放到同一翻譯單元中,也就是 h 檔案和 cpp 分離式的寫法,模板函式實作最好寫在 h 檔案中,某個類的成員函式,如果是模板函式,也要和宣告定義寫在一塊,
頭檔案定義
定義頭檔案都要養成一個好習慣,
#ifndef DROPS_FEMP3_H
#define DROPS_FEMP3_H
xxx
#endif
防止重復呼叫帶來的重定義,
for_each
模板函式原型:
template<typename InputIterator, typename Function>
Function for_each(InputIterator beg, InputIterator end, Function f) {
while(beg != end)
f(*beg++);
}
for_each 的回傳值是其第三個引數,
用法示例:
# include<iostream>
# include<algorithm>
# include<vector>
using namespace std;
void Print(int val)
{
cout << val << " ";
}
/*
基本資料型別
遍歷演算法
*/
void test1()
{
vector<int> v;
v.push_back(1);
v.push_back(5);
v.push_back(4);
v.push_back(2);
v.push_back(6);
for_each(v.begin(),v.end(),Print);
}
/*
自定義資料型別
for_each遍歷
*/
class Person{
public:
Person(int a,int b):index(a),age(b){}
public:
int age;
int index;
};
void Print2(Person &p)
{
cout << "index:" << p.index << " " << "age:" << p.age << endl;
}
void test2()
{
vector<Person> vp;
Person p1(1,4),p2(2,5),p3(3,6);
vp.push_back(p1);
vp.push_back(p2);
vp.push_back(p3);
for_each(vp.begin(),vp.end(),Print2);
}
int main()
{
//test1();
test2();
return 0;
}
關于 mem_fun
mem_fun、mem_fun_ref 的作用就是將一個"成員函式指標"包裝成一個仿函式,當容器中存放的是物件物體的時候用 mem_fun_ref,當容器中存放的是物件的指標的時候用 mem_fun ,
class ClxECS
{
public :
int DoSomething()
{
// 這里以輸出一句話來代替具體的操作
cout << " Output from method DoSomething! " << endl;
return 0 ;
};
};
vector < ClxECS *> vECS;
for ( int i = 0 ; i < 13 ; i ++ )
{
ClxECS * pECS = new ClxECS;
vECS.push_back(pECS);
}
int DoSomething(ClxECS * pECS)
{
return pECS -> DoSomething();
}
for_each(vECS.begin(), vECS.end(), & DoSomething);//方式1
for_each(vECS.begin(), vECS.end(), mem_fun( & ClxECS::DoSomething));//方式2
上述方式 1 訪問需要單獨定義一個全域的仿函式,要直接呼叫 ClxECS 中對應的成員函式,才有了方式 2,
DoSomething 函式是無參的,而對于有一個引數的函式,可以使用std::bind 輔助函式,
std::bind2nd
std::bind1st 和 std::bind2nd 函式用于將一個二元算子轉換成一元算子,bind 的意思是“系結”,1st 代表 first,2nd 代表 second, 例子如下:
#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;
int main()
{
vector<int> coll = { 1,2,3,5,15,16,18 };
// 查找元素值大于10的元素的個數
// 也就是使得10 < elem成立的元素個數
int res = count_if(coll.begin(), coll.end(), bind1st(less<int>(), 10));
cout << res << endl;
// 查找元素值小于10的元素的個數
// 也就是使得elem < 10成立的元素個數
res = count_if(coll.begin(), coll.end(), bind2nd(less<int>(), 10));
cout << res << endl;
return 0;
}
std::transform
std::transform在指定的范圍內應用于給定的操作,并將結果存盤在指定的另一個范圍內,要使用std::transform函式需要包含<algorithm>頭檔案,
template <class InputIterator, class OutputIterator, class UnaryOperation>
OutputIterator transform (InputIterator first1, InputIterator last1,
OutputIterator result, UnaryOperation op);
template <class InputIterator1, class InputIterator2,
class OutputIterator, class BinaryOperation>
OutputIterator transform (InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, OutputIterator result,
BinaryOperation binary_op);
類成員變數的初始化
類成員變數的初始化,一般都放在建構式里面,類內不能直接初始化,如下寫法是錯誤的,
class Solution {
vector<int> r(10);
};
正確的寫法是通過建構式初始化,
class Solution{
Type() : vec(10) { } // 要這么初始化
vector<int> vec;
};
因為類只是定義的型別,還沒有實體化,也就是沒有定義類的物件(變數),沒法存盤,你可以在初始化串列里進行初始化,
自定義默認代碼
#include <iostream>
using namespace std;
int main()
{
return 0;
}
valarry
valarray 是一個類模板,面向數值計算,優勢是多載了各種數學函式,方便數學計算,
valarray::resize 將 valarray 中的元素數更改為指定數量,如果第二個傳入引數省略,默認賦值為 0,
unique_ptr
動態記憶體的使用很容易出問題,因為確保在正確的時間釋放記憶體是極其困難的,有時會忘記釋放記憶體,在這種情況下會產生記憶體泄露和非法指標的參考,而智能指標,當該物件被銷毀時,會在其解構式中洗掉關聯的原始指標,就不用再專門地洗掉物件了,
為了更容易(同時也更安全)地使用動態記憶體,C++11標準庫提供了兩種智能指標(smart pointer)型別來管理動態物件,智能指標的行為類似常規指標,重要的區別是它負責自動釋放所指的物件,C++11標準庫提供的這兩種智能指標的區別在于管理底層指標的方式:shared_ptr 允許多個指標指向同一個物件,unique_ptr 則"獨占"所指向的物件,
它的用法就是把指標形如 Task* 改成 std::unique_ptr<Task>,
accumulate 自定義資料型別的處理
四個形參:頭兩個形參指定要累加的元素范圍,第三個形參則是累加的初值,第四個回呼函式來實作自定義資料的處理,
#include <vector>
#include <string>
using namespace std;
struct Grade
{
string name;
int grade;
};
int main()
{
Grade subject[3] = {
{ "English", 80 },
{ "Biology", 70 },
{ "History", 90 }
};
int sum = accumulate(subject, subject + 3, 0, [](int a, Grade b){return a + b.grade; });
cout << sum << endl;
system("pause");
return 0;
}
dynamic_cast
dynamic_cast 運算子的主要用途:將基類的指標或參考安全地轉換成派生類的指標或參考,并用派生類的指標或參考呼叫非虛函式,如果是基類指標或參考呼叫的是虛函式無需轉換就能在運行時呼叫派生類的虛函式,
前提條件:當我們將 dynamic_cast 用于某種型別的指標或參考時,只有該型別至少含有虛函式時(最簡單是基類解構式為虛函式),才能進行這種轉換,否則,編譯器會報錯,
numeric_limits
提供了型別的一些數值極限,如:
cout<<"numeric_limits<int>::min()= "<<numeric_limits<int>::min()<<endl; //int的最小值
cout<<"numeric_limits<int>::max()= "<<numeric_limits<int>::max()<<endl; //int的最大值
虛函式
一言以蔽之,基型別別的指標指向子類,該指標呼叫的同名函式是子類的,
mutable 關鍵字
在C++中,mutable 是為了突破 const 的限制而設定的,可以用來修飾一個類的成員變數,被 mutable 修飾的變數,將永遠處于可變的狀態,即使是 const 函式中也可以改變這個變數的值,
大括號
在C/C++中大括號指明了變數的作用域,在大括號內宣告的區域變數其作用域自變數宣告始,到大括號之后終結,我們應該善用它,使我們的程式更加清晰明白,C++ 的大括號不是無意義,它圈定了大括號里面變數的生存周期,
typename 關鍵字
有時候,在使用模板引數,容易引起歧義的時候,我們會在前面加個 typename 關鍵字,說明后面是個型別別,而不是別的東西,
const
c++ 函式前面和后面 使用const 的作用:
- 前面使用const 表示回傳值為const
- 后面加 const表示函式不可以修改class的成員
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/384531.html
標籤:其他
下一篇:C語言初階函式講解
