系統學習C++
方便自己日后復習,錯誤的地方希望積極指正
往期文章:
C++基礎從0到1入門編程(一)
C++基礎從0到1入門編程(二)
C++基礎從0到1入門編程(三)
參考視頻:
1.黑馬程序員匠心之作|C++教程從0到1入門編程,學習編程不再難
2.系統化學習C++
1 從結構體到類
面向對象編程,一切都是對象,對象用類來描述
類:成員變量(屬性)和函數(操作方法)
class 類名
{
public:成員1的數據類型 成員名1;成員2的數據類型 成員名2;...成員n的數據類型 成員名n;
}
用類定義一個類的變量:創建(或實例化)一個對象
對象的成員變量和成員函數的作用域和生命周期與對象的作用域和生命周期相同
#include <iostream>
using namespace std;
class CGirl
{
public:string name;int age;void setvalue(string name, int age);void show(){cout << "name = " << name << endl << "age = " << age << endl;}
};
void CGirl::setvalue(string name, int age)
{this->name = name;this->age = age;
}
int main()
{CGirl girl;girl.setvalue("BigDavid", 23);girl.show();return 0;
}
2 類的訪問權限
類的成員訪問權限:public、private、protected
在類的內部,無論成員聲名為何種權限,都可以訪問
在類的外部(定義類的代碼之外的代碼),只能訪問public成員
#include <iostream>
using namespace std;
class CGirl
{
private:string name;int age;
public:void setvalue(string name, int age);
};
void CGirl::setvalue(string name, int age)
{this->name = name;this->age = age;
}
int main()
{CGirl girl;girl.setvalue("BigDavid", 23); // 可以通過return 0;
}
(1)在一個類定義中,private和public可以出現多次
(2)結構體成員缺省為public,類成員缺省為private
(3)private的意義在于隱藏類的數據和實現,把需要向外暴露的成員聲名為public
3 簡單使用類
(1)類的成員函數可以直接訪問該類其他的成員函數(可以遞歸)
#include <iostream>
using namespace std;
class CGirl
{
private:string name;int age;int times = 0;
public:void setvalue(string name, int age){this->name = name;this->age = age;show();}void show(){if (times++ > 10) return;cout << name << ' ' << age << endl;show();}
};int main()
{CGirl girl;girl.setvalue("BigDavid", 23);return 0;
}
(2)類的成員函數可以重載,可以用默認參數
(3)類的成員可以是任意數據類型(類中枚舉)
class CGirl
{
private:string name;int age;int times = 0;enum sex {girl = 1, boy = 2};
public:void setvalue(string name, int age){this->name = name;this->age = age;show();}void show(){if (times++ > 10) return;cout << name << ' ' << age << endl;show();}
};
(4)可以為類的成員指定缺省值
(5)類可以創建對象數組
(6)對象可以作為實參傳遞給函數,一般傳引用
(7)用new動態創建對象,用delete釋放對象
(8)在類的外部,一般不直接訪問(讀和寫)對象的成員,而是用成員函數。數據隱藏是面向對象編程的思想
(9)對象一般不用memset()清空成員變量,可以寫一個專用于清空成員變量的成員函數
class CGirl
{
private:string name;int age;int times = 0;enum sex {girl = 1, boy = 2};
public:void setvalue(string name, int age){this->name = name;this->age = age;show();}void initdata(){name.clear();age = 0;}void show(){if (times++ > 10) return;cout << name << ' ' << age << endl;show();}
};
(10)對類和對象用sizeof運算意義不大,一般不用
(11)用結構體描述純粹的數據,用類描述對象
(12)在類的聲明中定義的函數都將自動成為內聯函數;在類的聲明之外定義的函數如果使用了inline限定符,也是內聯函數
(14)區分類的成員變量和成員函數的形參,把成員變量名加m_前綴或_后綴,如 m_name, name_
(15)類分文件編寫
4 構造函數和析構函數
構造函數:在創建對象時,自動的進行初始化工作
語法:類名(){...}
可以有參數,可以重載,可以有默認參數
創建對象只會自動調用一次,不能手工調用
析構函數:在銷毀對象前,自動完成清理工作
語法:~類名(){...}
沒有參數,不能重載
銷毀對象前只會自動調用一次,但可以手工調用
構造函數的細節
(1)如果沒有提供構造/析構函數,編譯器將提供空實現的構造/析構函數
(2)如果提供了構造/析構,編譯器將不提供空的構造/析構函數
(3)創建對象時,如果重載了構造函數,編譯器根據實參匹配相應的構造函數。沒有參數的構造函數也叫默認構造函數
(4)創建對象的時候不要在對象名后面加空的圓括號,編譯器誤認為是聲明函數。(如果沒有構造函數、構造函數沒有參數、構造函數的參數都有默認參數)
(5)在構造函數名后面加括號和參數不是調用構造函數,是創建匿名對象
(6) 接受一個參數的構造函數允許使用賦值語法將對象初始化為一個值(可能會導致問題)
CGirl girl = 10;
(7)第一行代碼構造函數和析構函數調用一次,下面兩行調用兩次
CGirl girl = CGirl("西施"20); // 顯式創建對象。
CGirl girl; // 創建對象。
girl = CGirl("西施"20); // 創建匿名對象,然后給現有的對象賦值
(8)用new/delete
創建/銷毀對象時,也會調用構造/析構函數
(9)不建議在構造/析構函數中寫太多的代碼,可以調用成員函數。
(10)除了初始化,不建議讓構造函數做其他工作
(11)C++11支持使用統一的初始化列表
CGirl girl = {"BigDavid", 8};
CGirl girl {"BigDavid", 8};
CGirl* girl = new CGirl{"BigDavid", 8};
(12)如果類的成員也是類,創建對象的時候,先構造成員類;銷毀對象的時候,先析構自身,再析構成員類
5 拷貝構造函數
用一個已經存在的對象創建新的對象,不會調用(普通)構造函數,會調用拷貝構造函數。
如果類中沒有定義拷貝構造函數,編譯器將提供一個拷貝構造函數,它的功能是把已存在對象的成員變量賦值給新對象的成員變量
類名 新對象名(已存在的對象名)
類名 新對象名 = 已存在的對象名
拷貝構造函數的語法:
類名(const 類名& 對象名){...}
Tip:
(1)訪問權限必須是public
(2)函數名必須與類名相同
(3)如果類中定義了拷貝構造函數,編譯器將不提供默認的拷貝構造函數
(4)以值傳遞的方式調用函數時,如果實參為對象,會調用拷貝構造函數
(5)函數以值的方式返回對象時,可能會調用拷貝構造函數(VS會調用,Linux不會,g++編譯器做了優化)
(6)拷貝構造函數可以重載,可以有默認參數
類名(......,const 類名& 對象名,......){......}
(7)如果類中重載了拷貝構造函數卻沒有定義默認的拷貝構造函數,編譯器也會提供默認的拷貝構造函數
6 淺拷貝和深拷貝
(1)如果一個對象修改了內存中的數據,會影響另一個對象
(2)其中一個對象釋放了內存,另一個對象的指針就成了野指針
#include <iostream>
using namespace std;class CGirl
{
public:string m_name;int m_age;int* m_ptr;CGirl(){m_name.clear();m_age = 0;m_ptr = nullptr;cout << "gouzaohanshu\n";}CGirl(const CGirl& gg){m_name = gg.m_name;m_age = gg.m_age;m_ptr = gg.m_ptr;}~CGirl(){delete m_ptr;m_ptr = nullptr;cout << "123\n";}void show(){cout << m_name << ' ' << m_age << ' ' << *m_ptr << endl;}
};int main()
{CGirl g1;g1.m_name = "BigDavid";g1.m_age = 23;g1.m_ptr = new int(3);g1.show();CGirl g2(g1);*g2.m_ptr = 8;g1.show();g2.show();
}
指針A指向一塊內存,重新分配一塊相同大小的內存,讓指針B指向新內存。再把指針A指向的內存中的數據拷貝到新內存中
CGirl(const CGirl& gg)
{m_name = gg.m_name;m_age = gg.m_age;m_ptr = gg.m_ptr;
}
修改上面代碼
CGirl(const CGirl& gg)
{m_name = gg.m_name;m_age = gg.m_age;// 分配內存m_ptr = new int;// *m_ptr = *gg.m_ptr; // 拷貝數據memcpy(m_ptr, gg.m_ptr, sizeof(int)); // 拷貝數據
}
7 初始化列表
構造函數的執行分為兩個階段:初始化階段和計算階段
初始化階段先于計算階段執行
構造函數除了有形參列表和函數體之外,還可以有初始化列表。
初始化列表的語法:
類名(形參列表):成員1(值),成員2(值),...,成員n(值) {...}
Tip:
(1)如果成員已經在初始化列表中,則不應該在構造函數中再次賦值,會覆蓋之前初始化列表的值
(2)初始化列表中的括號可以是具體值,也可以是構造函數的形參名,還可以是表達式
(3)初始化列表與賦值有本質的區別,如果成員是類,使用初始化列表調用的是成員類的拷貝構造函數,而賦值則是先創建成員類的對象(將調用成員類的普通構造函數),然后再賦值
(4)成員是類,初始化列表對性能略有提升
(5)如果成員是常量和引用,必須使用初始列表,因為常量和引用只能在定義的時候初始化
(6)如果成員是沒有默認構造函數的類,則必須使用初始化列表
(7)拷貝構造函數也可以有初始化列表
(8)類的成員變量可以不出現在初始化列表中
(9)構造函數的形參先于成員變量初始化
#include <iostream>
using namespace std;class CBoy
{
public:string m_xm;CBoy(){m_xm.clear();cout << "CBoy()\n";}CBoy(string xm){m_xm = xm;cout << "CBoy(string xm)\n";}CBoy(const CBoy& bb){m_xm = bb.m_xm;cout << "CBoy(const CBoy &bb)\n";}
};
class CGirl
{
public:string m_name;const int m_age;CBoy m_boy;CGirl(string name, int age, CBoy &boy):m_name(name), m_age(age) , m_boy(boy){//m_boy.m_xm = boy.m_xm;cout << "CGirl(name,age,boy)\n";}void show(){cout << m_name << ' ' << m_age << ' ' << m_boy.m_xm << endl;}
};int main()
{CBoy boy("BigDavid");CGirl g1("qwe", 18, boy);g1.show();
}
8 對象和類 - const修飾成員函數
在類的成員函數后面加const
關鍵字,表示在成員函數中保證不會修改調用對象的成員變量
(1)mutable
可以突破const限制,被mutable
修飾的成員變量,將永遠處于可變的狀態
(2)非const函數可以調用const和非const函數
(3)const成員函數不能調用非const成員函數
(4)非const對象可以調用const修飾的成員函數和非const修飾的成員函數
(5)const對象只能調用const修飾的成員函數,不能調用非cosnt修飾的成員函數
Tip:
const CGirl g1("asd", 18);
g1.show();
常對象只能訪問加了const的成員函數
創建g1時,上面代碼相當于訪問了構造函數,但是構造函數沒有加const,也沒有報錯,如果給構造函數加了const,報錯了,原因是構造函數或析構函數不允許使用類型限定符
9 this指針
如果類的成員函數涉及多個對象,在這種情況下需要使用this指針
this指針存放了對象的地址,被作為隱藏參數傳遞給了成員函數,指向調用成員函數的對象(調用者對象)
每個成員函數(包括構造函數和析構函數)都有一個this指針,可以用它訪問調用者對象的成員。可以解決成員變量名和函數形參名相同的問題
int aa;
void func(int aa)
{this->aa = aa;
}
*this
可以表示對象
如果在成員函數的括號后面使用const
,那么將不能通過this
指針修改成員變量
#include <iostream>
using namespace std;class CGirl
{
public:string m_name;int m_yz;CGirl(const string &name, int yz){m_name = name;m_yz = yz;}void show() const{cout << m_name << " beautiful\n";}const CGirl& pk(const CGirl& g) const{if (g.m_yz < m_yz) return g;return *this;}
};int main()
{CGirl g1("a", 5);CGirl g2("b", 6);CGirl g3("c", 1);const CGirl& g = g1.pk(g2).pk(g3); // c beautifulg.show();
}
10 類的靜態成員
類的靜態成員:靜態成員變量和靜態成員函數
靜態成員可以實現多個對象之間的數據共享,比全局變量更安全
static
關鍵字把類的成員變量聲名為靜態,表示在程序中(不僅是對象)是共享的
靜態成員變量不會在創建對象的時候初始化,必須在程序的全局區用代碼清晰的初始化(用范圍解析運算符 ::)
靜態成員使用類名加范圍解析運算符 :: 就可以訪問,不需要創建對象。
如果把類的成員聲明為靜態的,就可以把它與類的對象獨立開來(靜態成員不屬于對象)
靜態成員變量在程序中只有一份(生命周期與程序運行期相同,存放在靜態存儲區的),不論是否創建了類的對象,也不論創建了多少個類的對象
靜態成員函數,只能訪問靜態成員,不能訪問非靜態成員
非靜態成員函數可以訪問靜態成員
靜態成員函數沒有this
指針
const靜態成員變量可以在定義類的時候初始化
#include <iostream>
using namespace std;class CGirl
{static int m_age;
public:string m_name;CGirl(const string& name, int age){m_name = name;m_age = age;}void show(){cout << "name: " << m_name << endl;}static void showage(){cout << m_age << endl;}
};int CGirl::m_age = 22;int main()
{CGirl g1("a", 21), g2("b", 23), g3("c", 24);g1.show(); g1.showage();g2.show(); g2.showage();g3.show(); g3.showage();CGirl::showage();
}
11 簡單對象模型
C語言本身沒有支持數據和函數之間的關聯性,數據和處理數據的操作是分開的。
C++用類描述抽象數據類型(ADT),在類中定義了數據和函數,把數據
和函數
關聯起來
對象維護了多個指針表,表中存放了成員和地址的對應關系
數據成員:非靜態、靜態
函數成員:非靜態、靜態、virtual
對象內存的大小:
(1)所有非靜態數據成員的大小
(2)由內存對齊而填補的內存大小
(3)支持virtual成員而產生的額外負擔
靜態成員變量屬于類,不計算在對象的大小之內
成員函數是分開存儲的,不論對象是否存在都占用存儲空間,在內存中只有一個副本,也不計算在對象大小之內
用空指針可以調用沒有用到this指針的非靜態成員函數(如果成員函數中沒有用到非靜態成員變量,就可以用空指針調用它)
在沒有創建對象的情況下,訪問非靜態成員變量就是訪問空指針
#include <iostream>
#include <cstring>
using namespace std;class CGirl
{
public:char m_name[3];int m_bh;static int m_age;CGirl(){memset(m_name, 0, sizeof(m_name)); m_age = 0;}~CGirl() {}void showname(){//if (this == nullptr) return;cout << "asd" << endl;}void showage(){cout << m_age << endl;}
};int CGirl::m_age;
int aaa;
void func() {}int main()
{CGirl g;CGirl* g1 = nullptr;g1->showname(); // asd
}
對象的地址是第一個非靜態成員變量的地址,如果類中沒有非靜態成員變量,編譯器會隱含的增加一個1字節的占位成員