參考資料:
為什么學(xué)習(xí)C++
C++可以直接控制機(jī)器挟冠,效率比較搞,C++代碼經(jīng)過對應(yīng)的編譯器編譯就得到機(jī)器碼了袍睡。C#知染、java會經(jīng)過虛擬機(jī)C++怎么工作起來的
main函數(shù)可以雖然有聲明要int返回值,但是可以不返回斑胜,會默認(rèn)返回0控淡,只有main函數(shù)有這個特例。
<< 運(yùn)算符其實(shí)是函數(shù)
hpp文件不會編譯止潘,cpp文件各自都編譯為單獨(dú).obj文件掺炭,鏈接將這些.obj文件連接起來為.exe文件
函數(shù)聲明告訴鏈接器存在這個函數(shù),鏈接器后面會去找覆山,找不到會得到鏈接錯誤竹伸。編譯
預(yù)處理泥栖,比如#include將整個文件復(fù)制過來簇宽,設(shè)置好VS可以得到已經(jīng)預(yù)處理完的.i文件勋篓。鏈接
將編譯好的多個文件編譯成一個。
編譯錯誤unresolved external symbol魏割,找不到定義函數(shù)譬嚣,可能是沒有聲明或者定義函數(shù)變量
變量不同在于內(nèi)存的大小頭文件
如果一個函數(shù)要在其他cpp文件被用到,需要在其他cpp文件中聲明說钞它,有這個函數(shù)拜银。如果沒有#include 頭文件,那么每次使用都需要寫函數(shù)聲明遭垛。
hash pragma once 頭文件在cpp文件中只包含一次指針
就是一個整數(shù)尼桶,存放著變量的地址
指針的指針說法其實(shí)有問題,指針是個常量锯仪,沒有地址泵督,應(yīng)該是指針變量的指針引用
不占用空間,不是變量庶喜,不能改變小腊,指針的簡化版本,功能簡單點(diǎn)久窟,使用簡單點(diǎn)秩冈。
下面第二句只是給a變量賦值,不是改變引用的值斥扛,引用不是變量入问,不能改變。
int& ref = a;
ref = b;
類
類不是必要的犹赖,但可以讓程序更簡潔队他,像C程序也可以完成很多工作。static
在類之外峻村,限制定義全局變量麸折,只定義在一個文件之內(nèi),不會互相影響粘昨。這里注意scope和lifetime的區(qū)別垢啼,scope是在一個文件,lifetime是伴隨著整個程序张肾。如果不同文件沒有用static定義相同的變量會產(chǎn)生鏈接錯誤芭析。函數(shù)也是一樣的。
在類之內(nèi)吞瞪,只要是用類都可以用到馁启。w
在函數(shù)之內(nèi),scope是函數(shù)體內(nèi)部,lifetime是從函數(shù)開始創(chuàng)建到程序結(jié)束惯疙。local變量單例模式使用如下翠勉。
class Player {
public:
static Player& get() {
static Player p;
return p;
}
void move() {
}
};
int main()
{
Player p = Player::get();
p.move();
return 0;
}
- enum
作用:給值賦予一個名字,是一種新的類型霉颠,可以限定對應(yīng)的變量是否在定義的那幾個數(shù)的范圍之內(nèi)对碌,比如下面的Level level; level只能是Level中的其中一個值。其中引用的時(shí)候也可以用類來引用 Log::LogLevelWarn
#include <iostream>
#include <stdint.h>
using namespace std;
class Log {
public:
enum Level {
LogLevelInfo = 0,
LogLevelWarn = 1,
LogLevelError = 2
};
private:
Level mLogLevel = LogLevelInfo;
public:
void setLevel(Level level) {
mLogLevel = level;
}
void info(const char *message) {
if (mLogLevel <= LogLevelInfo) {
cout << "[INFO]: " << message << endl;
}
}
void warn(const char *message) {
if (mLogLevel <= LogLevelWarn) {
cout << "[WARN]: " << message << endl;
}
}
void error(const char *message) {
if (mLogLevel <= LogLevelError) {
cout << "[ERROR]: " << message << endl;
}
}
};
int main(int argv, char** argc) {
Log logger;
logger.setLevel(logger.LogLevelWarn);
logger.info("info");
logger.warn("warn");
logger.error("error");
system("pause");
}
構(gòu)造器
很多時(shí)候蒿偎,我們都會在某個東西創(chuàng)建完成后希望完成初始化朽们,M.init(),但有時(shí)會漏掉诉位,構(gòu)造器正是這個用處骑脱。構(gòu)造器和析構(gòu)器有點(diǎn)回調(diào)的意味,會自動在構(gòu)造和free的時(shí)候調(diào)用苍糠。析構(gòu)器的一個應(yīng)用
釋放對象相關(guān)分配的內(nèi)存繼承
繼承可以減少重復(fù)代碼虛方法
應(yīng)用場景:一個鴨子類繼承自動物類惜姐,如果重寫了move方法,鴨子對象賦給動物類型椿息,調(diào)用move會用動物.move歹袁,但實(shí)際上我們希望調(diào)用鴨子.move,C++可能為了高效寝优,所以才會有這樣的問題条舔,虛函數(shù)會建立一些數(shù)據(jù)結(jié)構(gòu),然后調(diào)用的時(shí)候會先從鴨子開始查起來乏矾,這樣才對孟抗,但性能會略微損失。給基類方法加上virtual钻心,會建立V table凄硼,繼承的信息都放在V table里面,調(diào)用的時(shí)候先從子類的方法開始捷沸。純虛方法
相當(dāng)于java中抽象方法或者接口可見性
我們需要可見性摊沉,因?yàn)閷τ陬愔獾睦^承者,使用者來說痒给,需要屏蔽掉一些東西说墨,不能在外部使用,不然程序可能會出現(xiàn)奔潰等等苍柏。比如游戲的角色有坐標(biāo)x尼斧,y,如果外面隨意修改這些值试吁,角色可能不會像我們想象的那樣子移動棺棵,類如果可以讓我們操作角色移動,那么一定提供了更好的方法。數(shù)組
int example[5];//在棧上分配內(nèi)存
int[] example = new int[5];//在堆上分配內(nèi)存烛恤,需要delete[] example
#include <array>
int main()
{
std::array<int, 10> buff;
for (int i = 0; i < buff.size(); i++) {
buff[i] = i;
}
return 0;
}
- String
const char *string = "hahaha"; //這里字符串是存放在只讀存儲器
以'\0'結(jié)尾爬橡。只是一個指針。不能修改string[2]
#include <iostream>
#include <string>
int main()
{
std::string str = "Hello,World!";//helloworld這里是const char *指針
std::cout << str << std::endl;
std::cin.get();
return 0;
}
方法后面加const不能修改類元素
void method() const
{
}
上面函數(shù)在下面中有用e.method()必須是上面的const修飾的棒动,保證不會修改到e的內(nèi)容才可以允許調(diào)用,不然會出錯宾添。
void function(const Entity& e){
e.method()
}
但是呢船惨?被method后面被const修飾還有辦法修改類的一些變量,這些變量要用mutable修飾缕陕。
- Member Initializer Lists
構(gòu)造函數(shù):m_member(value)
可以令初始化更簡潔
可以減少拷貝粱锐,下面代碼沒有使用成員初始化列表,Example產(chǎn)生了兩次扛邑,使用成員初始化列表只會產(chǎn)生一次怜浅。
#include <iostream>
class Example {
public:
int m_member;
Example() {
std::cout << "UNKNOWN" << std::endl;
}
Example(int x) {
m_member = x;
std::cout << "create with x" << std::endl;
}
};
class Entity {
public:
Example ex;
Entity() {
this->ex = Example(2);
}
};
int main()
{
Entity e;
std::cin.get();
return 0;
}
把Entity改為下面只會生成一個
class Entity {
public:
Example ex;
Entity() :
ex(Example(2))
{
}
};
實(shí)例化對象
Entity e;這個在C++中是在棧中生成一個對象,可以直接使用蔬崩,但是在java中是一個空指針恶座,需要進(jìn)一步new 對象。
也可以是Entity e("xxxx");和Entity e = Entity("xxxx");
堆中實(shí)例化 Entity *p = new Entity();
java過來的人可能會到處new沥阳,但這在C++中并不好跨琳,因?yàn)閖ava會自動回收,但C++不會桐罕,所以還是該用棧就用棧.new
分配內(nèi)存脉让,調(diào)用構(gòu)造函數(shù)
new 的時(shí)候有用[],delete也要[]運(yùn)算符重載
有時(shí)函數(shù)的調(diào)用不是很清晰功炮,比如A.add(B.mul(C))溅潜,用運(yùn)算符就是很清晰,A+B*C薪伏。但是如果人家看你代碼要去看運(yùn)算符重載的部分滚澜,說明你可能用的不合適。
class Vect2 {
public:
double x, y;
Vect2(double x, double y)
: x(x), y(y) { }
//第一個const代表不能修改進(jìn)來的引用嫁怀,第二個const表示不能修改此對象的任何值
Vect2 operator+(const Vect2& other) const{
return Vect2(x + other.x, y + other.y);
}
Vect2 operator*(const Vect2& other) const {
return Vect2(x * other.x, y * other.y);
}
};
std::ostream& operator<<(std::ostream& stream, const Vect2& other) {
stream << other.x << "," << other.y;
return stream;
}
int main(int argv, char** argc) {
Vect2 v1(1.0 , 1.0);
Vect2 v2(2.0, 2.0);
Vect2 v3(3.0, 3.0);
Vect2 v4 = v1 + (v2*v3);
cout << v4 << endl;
system("pause");
}
const
Entity* const & e = this;
Entity* const e = this;
上面兩個式子是等價(jià)的博秫,而且不允許 e 和 this 的賦值。智能指針
刪掉指針同時(shí)自動free指向的內(nèi)存眶掌,挡育。
如果使用Entity* e = new Entity();
,main中new是在堆分配的朴爬,不會被釋放即寒,內(nèi)存泄漏。
同時(shí)注意ScopedPtr e = new Entity();
采用了隱式轉(zhuǎn)化,因?yàn)橛疫吺且粋€Entity*類型母赵,左邊是ScopedPtr逸爵,那怎么還可以賦值呢?這實(shí)際上相當(dāng)于ScopedPtr e(new Entity());
凹嘲。
智能指針結(jié)合了棧和生成器师倔,析構(gòu)器的特點(diǎn),跟回調(diào)是一樣的周蹭,同樣還可以用于一段程序的時(shí)間測量趋艘,也可以用鎖鎖住一小段程序。
#include <iostream>
#include <stdint.h>
using namespace std;
class Entity {
};
class ScopedPtr {
private:
Entity* mPtr;
public:
ScopedPtr(Entity *e) {
cout << "create pointer" << endl;
}
~ScopedPtr() {
delete mPtr;
cout << "free memory" << endl;
}
};
int main(int argv, char** argc) {
{
ScopedPtr e = new Entity();
}
system("pause");
}
- unique pointer
系統(tǒng)的另外一種智能指針凶朗,這種指針不能被復(fù)制瓷胧。
unique 指針,只能有一個棚愤,因?yàn)樵撝羔槂?nèi)存被free了搓萧,其他指向同一個內(nèi)存的指針就廢了。
class Entity {
public:
Entity() {
std::cout << "construction" << std::endl;
}
~Entity() {
std::cout << "deconstruction" << std::endl;
}
};
int main()
{
{
std::unique_ptr<Entity> e0(new Entity());//right
//std::unique_ptr<Entity> e = new Entity();//wrong
std::unique_ptr<Entity> e1 = std::make_unique<Entity>();//also right
}
std::cin.get();
return 0;
}
這種指針無法賦值宛畦,因?yàn)閺?fù)制號重載被刪掉了瘸洛。
- 復(fù)制構(gòu)造器
第45集,非常精彩次和。
下面的代碼會拋出異常货矮,因?yàn)閑1是復(fù)制e0的,e1只是分配內(nèi)存后復(fù)制了e0的全部內(nèi)容斯够,故公用new int[5]內(nèi)存塊囚玫,但是跳出main的時(shí)候會free兩次,這個時(shí)候要重寫復(fù)制構(gòu)造器读规,然后進(jìn)行分配內(nèi)存和深復(fù)制抓督。
class Entity {
public:
int *m_Buff;
Entity() {
m_Buff = new int[5];
};
~Entity() {
free(m_Buff);
}
};
int main(int argv, char** argc) {
Entity e0;
Entity e1 = e0;
}
增加復(fù)制構(gòu)造器
class Entity {
public:
int m_size;
int *m_Buff;
Entity() {
m_size = 5;
m_Buff = new int[m_size];
};
~Entity() {
free(m_Buff);
}
//:之后進(jìn)行淺復(fù)制,復(fù)制構(gòu)造函數(shù)里面深復(fù)制
Entity(const Entity& other) : m_size(other.m_size) {
cout << "進(jìn)行復(fù)制ing..." << endl;
m_Buff = new int[5];
memcpy(m_Buff, other.m_Buff, m_size);
}
};
int main(int argv, char** argc) {
Entity e0;
Entity e1 = e0;
system("pause");
}
- 參數(shù)傳入用const引用 const Type&
下面的程序輸出三句進(jìn)行復(fù)制ing...
束亏,說明復(fù)制了三次铃在,原因就是調(diào)用函數(shù)的時(shí)候復(fù)制了兩次,我們不希望在這里消耗性能碍遍,因此參數(shù)傳入改用const引用定铜。
class Entity {
public:
int m_size;
int *m_Buff;
Entity() {
m_size = 5;
m_Buff = new int[m_size];
};
~Entity() {
free(m_Buff);
}
//:之后進(jìn)行淺復(fù)制,復(fù)制構(gòu)造函數(shù)里面深復(fù)制
Entity(const Entity& other) : m_size(other.m_size) {
cout << "進(jìn)行復(fù)制ing..." << endl;
m_Buff = new int[5];
memcpy(m_Buff, other.m_Buff, m_size);
}
};
void doEntity(Entity e) {
}
int main(int argv, char** argc) {
Entity e0;
Entity e1 = e0;
doEntity(e0);
doEntity(e1);
system("pause");
}
將上面的函數(shù)改為下面只會復(fù)制一次怕敬!
void doEntity(const Entity& e) {
}
棧與堆的區(qū)別
除了使用上的區(qū)別揣炕,博主主要講了效率上的區(qū)別,棧很簡單东跪,就是移動棧頂指針畸陡,而堆需要分配內(nèi)存鹰溜,這個就比較耗時(shí)了。auto
主要是在變量類型明顯丁恭,且比較長的時(shí)候可以省略變量類型曹动。
記住返回引用類型的時(shí)候要用auto&vector的使用,實(shí)際上叫ArrayList更準(zhǔn)確牲览,即可以改變長度的數(shù)組
這個例子用法很低效率墓陈,看下一個例子的優(yōu)化
class Entity {
public:
int m_size;
int *m_Buff;
Entity(int m_size) {
this->m_size = m_size;
m_Buff = new int[m_size];
};
~Entity() {
free(m_Buff);
}
//:之后進(jìn)行淺復(fù)制,復(fù)制構(gòu)造函數(shù)里面深復(fù)制
Entity(const Entity& other) : m_size(other.m_size) {
cout << "進(jìn)行復(fù)制ing..." << endl;
m_Buff = new int[5];
memcpy(m_Buff, other.m_Buff, m_size);
}
};
ostream& operator<<(ostream& stream, const Entity& e) {
stream << "m_size : " << e.m_size << endl;
return stream;
}
int main(int argv, char** argc) {
vector<Entity> entities;
entities.push_back(1);
entities.push_back(2);
entities.push_back(3);
//刪除第二個
//entities.erase(entities.begin() + 1);
//注意這里要用const Entity& e才不會復(fù)制多次
for(const Entity& e:entities)
cout << e;
system("pause");
}
- vector的優(yōu)化使用
上面例子中第献,塞進(jìn)去三個元素贡必,復(fù)制了6次!痊硕!
為什么要復(fù)制,因?yàn)槠湓硎鞘紫仍跅V袆?chuàng)建對象押框,然后復(fù)制到vector的對應(yīng)的內(nèi)存單元中——堆岔绸,entities.push_back(3);
其實(shí)是entities.push_back(Entity(3));
;為什么是6次橡伞,vector首先分配固定的空間盒揉,不夠了就再分配一個更大的空間,然后復(fù)制過去兑徘,幾次下來就復(fù)制很多次刚盈。
下面的代碼復(fù)制0次,首先我們一開始預(yù)留了足夠的3個空間挂脑,如果接下來按照前面的做法是要復(fù)制3次藕漱,但是這次我們使用emplace_back,沒有經(jīng)過棧崭闲,直接在堆上面創(chuàng)建起來肋联。
int main(int argv, char** argc) {
vector<Entity> entities;
entities.reserve(3);
entities.emplace_back(1);
entities.emplace_back(2);
entities.emplace_back(3);
}
- 返回多個值
1.返回一個結(jié)構(gòu)體
2.輸入各個類型變量的引用,然后再函數(shù)中
【注】:由下面的觀察可知刁俭,變量名就是引用橄仍;另外getTwoString中,a = string("aaaa");
是先創(chuàng)建一個字符串牍戚,然后再復(fù)制給aN攴薄!從一個棧復(fù)制到另外一個棧如孝。
void getTwoString(string& a,string& b) {
a = string("aaaa");
b = string("bbbb");
}
int main(int argv, char** argc) {
string a, b;
getTwoString(a, b);
cout << a << endl << b << endl;
system("pause");
}
3.數(shù)組宪哩,vector兩種方式。
4.tuple第晰,跟python很像斋射。
tuple<string,int> getStringAndInt() {
//猜想這里string("Hello")產(chǎn)生在棧上育勺,然后復(fù)制到make_pair內(nèi)部的從堆上分配的內(nèi)存
return make_pair(string("Hello"), 111);
}
int main(int argv, char** argc) {
auto source = getStringAndInt();
string str = get<0>(source);
int inter = get<1>(source);
cout << str << endl << inter << endl;
system("pause");
}
- 模版入門:
編譯器幫你寫程序,模板只是一個藍(lán)圖罗岖。在調(diào)用的時(shí)候根據(jù)類型再去生成涧至。
如果寫一個打印多種數(shù)據(jù)類型的函數(shù)
void print(T content)
中的T隨著調(diào)用的類型而變成特定的類型,這個函數(shù)不是我們平常的函數(shù)桑包,這是一個模版南蓬,在編譯的時(shí)候如果沒有調(diào)用函數(shù),那么不存在哑了,錯了也沒有關(guān)系赘方,如果調(diào)用,那么就會實(shí)際生成一個函數(shù)弱左,這跟我們自己寫的效果一樣窄陡,只是這樣我們省了很多工作,模版實(shí)際上在幫我們寫代碼拆火。實(shí)際上函數(shù)調(diào)用的時(shí)候也可以寫成print<int>(6);
但C++會根據(jù)輸入的類型自動生成int跳夭,所以下面不用,但是要知道print是多個函數(shù)们镜。
template <typename T>
void print(T content) {
cout << content << endl;
}
int main(int argv, char** argc) {
print(6);
print(3.1415);
print("hello!");
system("pause");
}
再來一個例子:
class Entity {
private:
int m_Num;
public:
Entity(int num) {
m_Num = num;
}
};
template <typename T,int N>
class Array {
private:
T m_Array[N];
public:
int size() { return N; };
};
模版的弊端:可能導(dǎo)致程序很復(fù)雜币叹,因?yàn)榇a都是模版自動生成出來的
- 返回多個返回值
- 為什么要使用static array而不是普通數(shù)組?
1.array在這里是一個類模狭,但由于是模版生成的颈抚,占用的內(nèi)存跟標(biāo)準(zhǔn)數(shù)組是一樣的,比如雖然有.size()嚼鹉,size并不會占用內(nèi)存空間贩汉,因?yàn)槟0嫔傻臅r(shí)候直接返回模版輸入。
2.功能更強(qiáng)大锚赤,在debug會檢測出數(shù)組越界雾鬼,可以獲取數(shù)組長度等等。
#include <array>
int main(int argv, char** argc) {
std::array<int, 5> data;
data[0] = 1;
//這里會報(bào)錯
data[5] = 1;
system("pause");
}
- 隱式轉(zhuǎn)化
40集 Implicit Conversion and the Explicit Keyword in C++
奇怪的語法
class Entity {
private:
std::string name;
int age;
public:
Entity(std::string name) : name(name),age(-1){}
Entity(int age) :age(age),name("UNKNOWN"){}
};
void printEntity(const Entity& e) {
//...
}
int main()
{
Entity e1("Mike"); //normal1
Entity e2(22); //normal2
Entity e3 = "Stuart"; //"Stuart"這里是一個字符串類型宴树,隱含轉(zhuǎn)化
Entity e4 = 22; //隱含轉(zhuǎn)化
Entity e5 = (Entity)22; //強(qiáng)制類型轉(zhuǎn)化
printEntity(22); //隱含轉(zhuǎn)化
printEntity("Ada"); //Error策菜!"Ada"是const char[] 類型,隱含轉(zhuǎn)化只能轉(zhuǎn)化一次酒贬,不從const char->string->Entity
printEntity(std::string("Ada")); //normal
printEntity(Entity("Ada")); //normal
}
explicit 放在構(gòu)造器前面又憨,說明這個構(gòu)造器就不能再進(jìn)行隱含轉(zhuǎn)化,22 -> Entity 是調(diào)用了Entity(int age)進(jìn)行生成一個Entity對象的锭吨,如果變成explicit Entity(int age)
那么Entity e4 = 22;
和printEntity(22);
會失敗蠢莺,但是強(qiáng)制類型轉(zhuǎn)化還是可以的。implicit意味著自動零如。
- cast