c++基础入门(根据浙大翁恺老师视频整理)
把以前的筆記搬上來一下 根據浙大翁愷老師視頻整理 https://www.bilibili.com/video/BV1yQ4y1A7ts?p=2
01第一個c++程序
02什么是對象
通過操作訪問數據 數據是被保護起來的 對象其實就是變量 任何變量就是對象
通過外部操作改變data狀態 而不是直接接觸改變數據
面向過程:事情發生的流程是怎么樣,按照時間順序一步一步會發生什么,即從時間順序
面向對象:在某個場景中有什么東西,這些東西的關系是什么,比如一間教室里有燈,有學生,有老師等等,他們的關系是怎么樣的,即從存在什么樣的東西
設計就是找出思路的過程 實現就是寫代碼的過程 oo思想關注的是東西是數據而不是過程操作
03面向對象基本原理
object實體 class是種概念不是實體
oop特點
1萬物都是對象實體
2程序就是一堆對象,告訴其他對象根據發送的消息做什么,(每個對象都給其他對象發消息告訴他們做什么而不是怎么做 要不要做)
3每個對象都有自己的內存,對象又是由其他對象組成的
c語言是一堆函數 調來調去回到main函數結束
4 每個對象都有類型 現有類型才有對象
5一個特定類型的所有對象可以接受相同的消息 比如所有水果都能被你咬一口有水喝
可以接受相同消息的對象可以歸為同一類
class creator 自己造的類 可以修改類里面的數據 但是client programmers可以調用類但是不能直接接觸類中的數據 而是通過接口
三大特性:封裝 繼承 多態性
04自動售票機例子
按照過程模擬售票機 不合理 應該關注其內部有什么東西
::”類作用域操作符。“::”指明了成員函數所屬的類。如:M::f(s)就表示f(s)是類M的成員函數。
::a++ 前面沒有類名 表示是全局的
![(https://img-blog.csdnimg.cn/20201028134324929.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N1ZW9uZw==,size_16,color_FFFFFF,t_70#pic_center)
源代碼.c/cpp------>可執行文件.out/exe
預處理生成.i
預處理生成.ii
匯編文件.s
生成二進制.o
鏈接成可執行文件.out/exe // 多個.o目標文件鏈接后生成可執行文件
g++ –E main.cpp // 生成.i
g++ –S main.i main.s // 由.i生成.s
g++ -C main.cpp // 生成.o二進制目標文件 .cpp—>.o
g++ main.o -o AA // 由.o目標文件到可執行文件AA.out/exe .o—>.out/exe
g++ main.cpp // 編譯連接一起,生成可執行文件a.out/exe .cpp–>.out/exe
g++ main.cpp -o AA // 由.cpp源文件到可執行文件AA.out/exe
編譯程序時 c++只對.cpp進行 只看到一個.cpp文件 看不見其他東西
對頭文件 我們只能聲明而不能放定義
聲明有:extern 變量 函數的原型 class/struct的聲明 (只有聲明無定義)
int a//定義
extern int a//聲明
f()//函數的定義
f// 函數原型即聲明
自動生成的標準頭文件結構 為了防止在一個.cpp文件中 include一個頭文件多次而出現那個.h類的聲明被重復出現
一個頭文件只放一個類的聲明 而其對應的源代碼文件用相同的前綴 把后綴改成.cpp 里面放著它所有函數的body(definition) 頭文件要用標準頭文件給 只能include 聲明而不是定義
06時鐘例子
抽象 :看待某件事物 有意的看不見一些細節 只會在某個層次看 把進一步的層次屏蔽起來,比如看見一輛車 你就想到大小 顏色 等等 不會去考慮里面座椅皮質是什么材質 輪胎尺寸等等
07成員變量
假如在某個函數中定義了一個本地變量 和成員變量的名字相同 c語言就是按照最近原則來 所以本地變量起作用 屏蔽成員變量
fields成員變量(字段) parameter參數 localvariable本地變量
成員變量值和函數無關 只要對象在他就在
成員變量的作用范圍是類的范圍 類中的成員函數可以使用這些成員變量
本地變量只能在他的那個函數使用
08成員變量的秘密
聲明不告訴這個變量在哪里 只是告訴有這么個東西
類不是實體 只有實體才有變量 如a是實體是對象 才有i這個變量
而函數是屬于類的 不是屬于對象的 f是A的函數
B中無f函數 只能f(&b)
f是類的 i是對象的
cout<<a.i 先輸出a.i=10
進入a.f() 先輸出10 又因為f內i=20 所以輸出20
此時輸出a.i 變成20 輸出20
再申請f(&b) 輸出20
a的地址和a.i的地址和f()內i的地址一樣 所以調用a.i a.f()時地址都是a的 這個聯系建立是通過this
this可以被隱藏的 this是個指針 可以看出f()和this 的地址同
i=20 也可寫成this.i
09構造和析構
構造函數(構造器)和類的名字一樣而且沒有返回類型
這個構造函數會在對象被創建的時候就自動被調用
當你有個對象a時 即X a就會做a.X();
把對象傳給構造函數
析構函數 對象結束之前要調用析構函數 析構函數不能有參數 和類名一樣 但是前面有~ 析構意味著收回空間
幾個對象就是幾個析構函數
會在before opening brace 之后馬上調用構造函數 在構造對象之前
會在after closing brace 之前馬上調用析構函數 在結束對象之前
x1的空間進了f的{}就有 但是因為到了jump1 x1就沒有調用構造函數 只有X x1的時候構造才會做 但是跳出{}就會析構x1 但是x1并沒構造 所以編譯無法通過 會出現問題
10對象初始化
sizeof c/sizeof *c 求數組c的個數
缺省 沒有參數的構造函數是 default constructior
Y y2[2]={Y(1)} 錯誤的 因為沒給第二個數 相當于告訴編譯器去找個default constructior來初始化這個對象 編譯器就說 找不到 因為沒有
10new和delete
new 要做:1分配空間2調用構造函數 如int只要分配空間即可 類stash在分配空間完后需要調用構造函數 對象被創建的時候一定會調用構造函數 不論是堆里面的變量還是本地變量 3預算符要有結果 所以返回的是這個空間的地址
new 先分配空間 得到空間再去里面做初始化
delete是先析構 析構完再收回空間
delete [] psome是整個數組 不帶[]只會調用第一個元素的析構函數 而不是所有元素的
一張表inchtable 記錄這塊內存的地址 這個內存有多大
假設r是數組
其中delete r 表示只調用了第一個元素的析構函數 但是整個r數組的空間都被收回了
delete r[] 會調用每個元素的析構
注意要new就要delete 雖然在平時作業中進程結束就會終止不會造成影響 進程結束這片內存會被回收 但是如果不是作業就會造成內存泄漏
12訪問限制
oop對象是封裝的是被保護的 能接觸的只有函數 可以通過函數來要求做什么 但是具體怎么做對數據產生什么影響是代碼影響 使用類的程序員(client programmer)不能訪問類里面的東西 而對寫類的人(Libary designer )可以修改內部的東西而不至于影響別人
這些權限是指在編譯層面的 只在編譯時刻檢查 是真對類的限制 不是對對象
- 運行時刻如果有辦法的話可以不受限制
public 任何人可以訪問
private 可以是函數或者變量 只有自己可以訪問 即這個類的成員函數可以訪問
protected
class vs struct
class defaults to private
struct defaults to public
13 初始化列表
構造函數的初始化和賦值
賦值做了兩件事 先初始化再賦值 只是初始化的時候是用默認的構造器初始化
但是一般編程習慣是在構造函數中初始化
14對象組合 (拿已有對象去拼裝新的對象)
三大特性 封裝繼承多態性
繼承是軟件重用的回答 或者 繼承是實現軟件重用的方式
by reference表示別的對象不是身體本身的一部分 而是其他的 寶寶出生后單獨個體 by reference 成員變量是個指針
fully 表示這個對象就是本身的一部分 完全包含 寶寶在肚子里 fully
雖然類saving account里面有類 person和currency 但是初始化和調用函數時不能直接拿這兩個類的東西 要通過他們定義的對象 m_saver和m_balance 來調用
15繼承
組合是拿對象(object)拼出新的對象 實(對象是實體)
繼承是拿類改造一下 造出新的類 虛(類是概念)
superset超集 student is a superset of person student是個繼承者
manager is a emploee sub 子類 derived派生類 B繼承于A 則有A的任何一切 擁有但是不代表可以直接使用private的東西 只能通過父類的某些函數去使用這些私有成語(比如set print)
這里B中 直接令i=30 會報錯
建立某個類時 首先所有數據都是private 要給所有人要用的放在public 留一些protected的接口給子類 告訴子類可以通過protected的東西直接訪問一些private的東西 這手段只留給子類 別人不能做
16父類子類的關系
加上std::是這個類的全名 因為以前用了using namespace std;
兩個print函數構成重載關系 overload 因為參數不一樣
現在B是A的子類 B中有A的body 所以B要初始化時候,就要遵循A的規則讓A自己把自己的那部分初始化 有父類的對象 必須讓父類對象初始化 所以父類的構造會被調用
如圖 B要構造自己的析構函數時候 必須借助A的構造函數 所以先調用A的構造再調用B的 析構是先B后A
父A子B中都有print函數 且A中有重載函數 B中的f也有print函數這個print函數與A中的某個一致(指的是名字參數表一樣 )namehidding 名字隱藏 則父類的其他同名函數隱藏了 只有子類自己定義的這個 所以當要調用B.print(20)是不合法的
17函數重載與默認參數
函數重載就是 同名 參數列表不同 (類型 個數等等)
預先給一個值 缺省參數 所以stash 有兩種寫法 給一個值或給兩個值
一定要從最右邊省略過來 寫在聲明里不寫在定義里 即可以缺省可以寫在.h里面 不可以寫在.cpp里面
default argument是發生在編譯時刻 盡量不要使用default value
18內聯函數
overhead 額外的開銷
內聯函數不會像函數調用那樣經歷push pop等操作 而是相當于會把這段函數代碼嵌入這個地方 (在編譯的時候這么做) 而仍保持函數的獨立性 有函數的空間等等
左邊是函數調用 右邊是內聯函數 二者的匯編對比
inline 必須在聲明和定義中都要有。
.cpp是用來產生函數的 .h是給調用這個函數 的地方看的
左邊是c的宏 他檢查不出double的錯誤 而inline可以檢查出double的錯誤
inline不一定會給你插入 如果你有很大的循環或遞歸的話 編譯器就會拒絕
類里面的函數body放里面則自動變inline 如point() point(ii jj kk) print都是inline
所以不會真的調用 只是把代碼插到這里來
函數的body寫在類外 比較清爽
19 const (保證初始化后不能被修改、賦值)
const是變量
const位于的左側,則const就是用來修飾指針所指向的變量,即指針指向為常量;所以修改p不可以 指針所指的內容不可以改
const位于*的右側,則const就是修飾指針本身,即指針本身是常量。q++不可以
星號前是對象是const 寫在星號后指針是const
1 p 即指針所指的內容不能修改
2 對象是const
3p指針不能修改 所以p++不對 p的地址不能改
int p 一個普通指針
const intcip 一個指向const變量的指針 即他所指的變量不能通過他修改的指針
盡管 cip就是i(因為cip=&i 所以cip=&i=i) 但是永遠不能寫cip=xxx //錯誤!
cip=&ci 正確 因為保證了 ci不會通過cip這個指針所修改 因為const intcip 一個指向const變量的指針 即他所指的變量不能通過他修改的指針
hello world是常數所以放在代碼段內 char*s 實際上是const類型的 所以他不可以被就修改
所以s[0]=‘B’無效
s1在代碼里面 與main距離很近 s2在堆棧內
一個函數f的參數類型是 int* 則無論給他什么類型的int都可以 不管是不是const 所以可以保證對傳進來的參數不修改
表明在f1內不能去改i的值
值可以賦給非const 的變量 因為并不是要改變他
通常直接傳遞一個變量開銷很大因為要放堆棧中 所以最好是傳地址 但是又不希望所指的內容被修改 所以加const以const身份對象傳給你 保證他不被修改 是安全的
在整個大對象前面加了const 這個對象的值就不能被修改 整個對象就是const 一旦這么做以后 這個對象的函數就有問題了 因為成員函數可能會修改成員變量
.h只有函數原型 .cpp才有函數body main.c include了.h文件他在編譯的時候可以知道整個函數的原型但不知道body 這樣就不知道到底哪些成員函數會修改成員變量
一個函數后面有const:在成員函數后面加上const 保證他不去修改任何的成員變量 需要在原型(這樣編譯器就能檢查出來 成員變量是安全的可以用的 )和定義的地方都要加上const
20不可修飾的對象
在函數后面寫const 即this是const
void f(){...} 等價于 void f(A* this){...} void f() const{...} 等價于voide f(const A* this) const{...} 所以a.f()調用的是第二個函數 因為const A a 二者構成overload的關系 因為實際上他們的參數列表不一樣
如果成員變量 定義const int i i是const 則i必須在構造函數中初始化 否則會報錯 A() {} 不行
必須 A():i(0){}
const 不能做數組的size
21引用
c++提供了太多可以放對象的地方 1可以放在堆棧里面 2堆里面 3全局數據區里面
提供了太多可以訪問對象的方式 1直接掌握一個對象 ,變量里面是對象 2通過指針訪問一個對象 3通過引用訪問一個對象
引用時在定義需要給一個初始值 以表明這個r是c的引用 初始值必須是個變量或是一個可以做左值的東西 r就是c的reference r是c的一個別名
reference的理解 1—y就是x的另一個名字 以后出現y的地方都可以換成x
引用的初始化會建立一個聯系
引用的目標必須有個位置放他 如i3有結果 但是沒有一個有名字的變量(位置 )去放i3這個結果
因為q是在本地變量 返回q是error return q錯誤
和用指針返回本地變量的地址一樣不正確
x是全局變量可以 return
一個函數返回的類型是 &reference 就可以做左值 因為 int& h(){.. } 所以 可以h()=16成立 這使得x=16 因為h()是返回x 且是個reference
alias 別名
reference 兩種含義 1一個變量的別名 2實際是種const 指針
int&* p;//以為是星p的reference的 即p可以取到reference的地址但是實際上reference不能取 int*& p //p是reference 所捆綁著變量是個int* 指針type& rename; int* & p;22向上造型
把子類的對象當做父類的來看待
比如學生和人 把學生看出人 學生多出來的東西當做不存在
因為指針所指的地址的便宜都是一樣的 就算子類有多出來的東西也是在之后加上去的
upcast一定是正確的 (子類指向父類)downcast有風險 比如你隨便找個人不一定就是學生
類型轉換和造型的區別:
類型轉換 比如double變int 就完全喪失了double的特性
造型 比如學生還是學生 只是我們看待他的眼光變了 不在當他是學生 把他當人看 他的類型沒有變所以不是類型轉換
當通過 ep指針輸出的print是employee的print 而不是manager的 (不是之前說的那個name hiding)
23多態性
circle rectangle ellipses都有共同的成員函數和成員變量
identical 同樣的事物 render提供 渲染
父類中的virtual 使得子類和父類的同名函數產生了聯系 這樣后代繼承的render也是virtual
virtual 通過指針或者引用去調用這個函數的時候 去調用render時 不能直接寫進來去調用哪個函數 要到運行的時候才能決定 p所指的render是通用的 對任何shape的子類都適用
p是多態的 p指的是誰 就變成誰的動作
靜態綁定 調用的函數在編譯的時候就是確定的知道的
動態綁定 在運行的時候才知道調用哪個函數 根據指針所指的對象決定去調哪個函數
p->render () 因為render是virtual 所以render 可以是ellipse circle的render 所以是動態綁定
但如果是p—>move() move()不是virtual 所以move是shape的
24多態性的實現
任何類中只要有一個虛函數 只要有一個 他的對象都會比一般的大一點
sizeof(a) 為8 因為是int型
int* p =(int*)&a; //p是變量名 是指針型的(int*型的) 所以傳a的地址
p++ 后 *p是10 //*p是指針所指的內容
有virtual類的對象的的里面 頭上會加上一個隱藏的指針叫vttr 他指向一張表叫vtable vtable里面是他所有virtual的函數的地址 vtable是這個類的不是對象的 這個類的所有vttr的值都是一樣
當使用指針或引用時才會產生動態綁定,通過點去調用不會
若如上圖 理應輸出B::f() 20
但是實際輸出的B::f() 32767
圖上寫錯 應輸出B::f()
因為只是值的傳遞 所以circ即使賦給elly 調用elly.render 也是ellipse的render 不會是circle 的render 理由同上
但如果是指針的話 這樣指針的指向改變了 elly就被賦予了circ elly指向circ 所以elly->render() 就是circle的render
如果是引用的話 耶同理 如果傳給func的是circ的話 調用是的circle 的render
如果一個類里面有一個virtual函數 那么他的析構函數必須是virtual 否則會有麻煩
c++默認是靜態綁定 其他op語言默認是動態綁定的 所以c++要加上virtual
override 覆蓋或改寫 :父類子類的兩個函數名稱相同參數表也相同 稱為override
如果需要調用父類的func可以寫成 base::func()
如上 同樣的函數 子類返回了子類的指針 返回了指針的引用可以 但是不能返回對象本身自己 因為只有通過指針或對象才能發生upcasting關系
如果你在父類有兩個virtual的overload的函數 那么你在子類也必須override所有virtutal函數的overload的函數 否則另外的函數會發生namehidding 只有c++這種oop語言存在namehide 其他語言不存在
25引用再研究
如果一個類的成員是reference 必須使用構造函數的列表初始化
引用作為返回值
在一個函數內不能返回本地變量的地址作為指針或reference 必須返回全局變量
加上const 把這個對象送進函數 保證函數無法通過指針來修改這個對象 但是指針用星號不方便
所以當我們需要穿對象進一個函數的時候 我們用const reference
26拷貝構造
將currency的對象作為輸入
在c++必須分清兩種概念 定義和聲明 即definition和declaration 還要分清初始化和賦值
currency p=bucks//initialization p=bucks//assignment在聲明這個變量的時候 前面有變量類型就是定義 給變量一個值叫initialization 已經定義好變量了 你再給他值叫assignment #include <iostream> #include<string> using namespace std;static int objectCount=0; class howmany{public:howmany(){objectCount++;print("hommany()");}// howmany(int i){objectCount++;print("hommany(int)");}//如果在定義一個帶int的構造函數時,之后在main里面//howmany h2=10;和howmany h2(10)就是等價的 輸出的結果完全一樣void print(const string& msg=""){if(msg.size()!=0) cout<<msg<<":";cout<<"objectCount="<<objectCount<<endl;}~howmany(){objectCount--;print("~howmany()");}}; howmany f(howmany x){cout<<"begin of f"<<endl;x.print("x argument inside f()");cout<<"end of f"<<endl;return x;} int main(){howmany h;//進入這句產生了hommany():objectCount=1 因為創建了h就調用了析構h.print("after construction of h");//產生了第2個輸出 此時objectcount=1howmany h2=f(h);/*此時進入f的h并沒有調用構造函數 卻直接產生了析構函數 所以一定在某個時刻調用了構造函數否則不可能有析構函數 begin of f x argument inside f():objectCount=1 end of f ~howmany():objectCount=0*/h.print("after call to f()"); /*在輸出7之后 出現了兩個析構函數 分別是h和h2的析構 所以在howmany h2=f(h)中h h2創建的過程繞過了構造函數的過程 ~howmany():objectCount=-1//8 ~howmany():objectCount=-2//9*/ }輸出hommany():objectCount=1//1 after construction of h:objectCount=1//2 begin of f//3 x argument inside f():objectCount=1//4 end of f//5 ~howmany():objectCount=0//6 after call to f():objectCount=0//7 ~howmany():objectCount=-1//8 ~howmany():objectCount=-2//9howmany這個類會對構造和析構函數調用的次數維持一個計數
howmany這個函數 完全對象傳入 對象傳出 (對象返回)
拷貝構造
注意這里是member variable的拷貝 不是字節對字節的拷貝 是成員級別上的拷貝
如果有指針的拷貝指針即兩個指針指向同一片內存單元
27 拷貝構造二
左邊的拷貝構造會出現問題 如下 所以我們希望實現的拷貝構造是右邊這樣的
輸出的結果是p1p2的名字地址相同 即p1p2指向同一片內存 同時還有一個malloc錯誤 因為p1p2指向同一片內存 又free兩次因為p1p2都要析構 所以報錯
private是針對類而不是對象 所以去訪問同類對象的private的東西是完全ok的
什么時候拷貝構造會發生?
隱含場景 1調用一個函數時 函數參數是對象本身(不是指針也不是引用) 就意味調用這個函數的時候 我要重新去創建一個新的person類的對象 這時候就會發生拷貝構造 因為我要用某個對象去初始化這個player
明顯場景 person p1=p2
baby_b和baby_c用baby_a構造 等號和括號是一樣的 都是初始化 這是明顯的拷貝構造的場景
Person baby_c(baby_a)//這是初始化
Person p=f() { Person ip;return ip;}int main(){Person p=f()//拷貝構造 一定會有新的對象構造出來 因為f返回的是一個person 的對象 //可能會發生兩種情況 1 Person p=ip 2 Person@tmp=f(); Person p=@tmp;return 0;}
編譯器會把不必要的拷貝優化出去 optimaze·out
其實本質就是拿who 構造外面這個對象 所以在第二種方法中就會一步到位優化成無需拷貝構造
任何對象只能被初始化一次 做完之后再對它做其他動作就是assiment assiment可以多次
person p=p1//initialization
p1=p2//assiment
用string 可以不用拷貝構造 以為string實際上就是一個類
c++盡量用string而不是char*
copy ctor :拷貝構造
建議 :一旦寫了一個類就必須給他三個函數default constructor ;virtual constructor ;copy constructor
28 靜態對象
static 1 持久存儲(在哪) 2訪問收局限(誰能看見你)
c中static的本地變量就是全局變量 因為持久存儲
一個全局變量是static 這個全局變量就只在.c文件里面有效
deprecated 過時的
static local variables 持久存儲
static 成員變量 在所有對象內共享
static 成員函數 在所有對象內共享只能訪問靜態對象和函數
int g_global 全局變量可以通過extern在另一個單元訪問 如在 file2
static int s_global 只能file1使用 即使使用extern也不可以
static void hidden 在file2也不能被訪問
就算編譯階段可以通過 但是了link階段無法通過
空間在全局數據區 編譯或鏈接的時候分配空間
全局變量的構造函數的執行什么時候做:程序一運行的時候但是在main函數之前
所以main就不是我們第一個被調用的函數了 真正程序跑的第一條代碼根本不是main函數
只是程序員所以能看見的所能寫的第一條代碼時main函數
程序要結束的時候 對應的析構會執行
如果一個程序有多個點cpp文件 每個文件都會有一個全局變量
每個文件都有全局變量 這些全局變量誰先初始化誰后初始化 是沒有規定規矩的 設置同一個編譯器這次編譯和下次編譯都不一樣 這樣有依賴的全局變量不能被保證初始化順序(依賴指的是某個變量要由另一個變量初始化 )
所以把有依賴的全局變量放在一個文件里
29靜態成員
persistent:全局的(含有靜態的本地) malloc的東西
在c++里面static不實現hidden 只實現persistent 在一個類中一個變量不會因為對象的不同而改變 即不依賴對象 所以static member 是class-wide 整個類的東西
若寫成static int i會編譯錯誤顯示找不到i
若去掉static編譯通過 輸出b.print()的結果0 因為沒有找到i
靜態的成員變量實際在全局 但是寫在class內的都是declaration不是definition
如寫extern int i是聲明不是定義 只是告訴編譯器某個地方有個叫i的東西 但是我不知在哪里 到時候link會幫我找出來
所以如果你在class寫了某個static 的成員變量 相當于extern 的效果 所以應該在.cpp內加int A::i否則只是聲明 沒有定義 不能是static int A::i 因為類的靜態成員可能在外部被訪問 不是只在點cpp被訪問
如圖 這樣輸出結果是10 因為靜態的成員變量實際上是全局的 只有那一個 所以b.pritnt就是被a改變的那個i
報錯 因為
initialization只能對非靜態的成員初始化 不能對靜態成員初始化
所以 A():i(0){} 不行 靜態成員只能在定義的地方被初始化 即int A::i=20;
即使A::i是全局的 到那時還是不能直接cout A::i 還是會報錯
c++內所有函數都是全局的
調用靜態函數的方法 沒有建立這個類的對象之前也可以訪問調用這個函數
如
報錯 靜態的函數只能調用靜態的成員變量 因為靜態的成員函數是沒有this的 因為this表達的是調用這個函數的那個對象 this是個hidden private 因為可以通過A::say()調用,里面沒有一個對象 所以會報錯
改成this->i 也不行 雖然i是靜態的
因為this表達的是調用這個函數的那個對象 this是個hidden private 因為可以通過A::say()調用 所以這里面沒有一個對象 所以會報錯
30運算符重載基本規則
在C語言中,多個表達式可以用逗號分開,其中用逗號分開的表達式的值分別結算,但整個表達式的值是最后一個表達式的值。
假設b=2,c=7,d=5,
a1=(++b,c–,d+3);
a2=++b,c–,d+3;
對于第一行代碼,有三個表達式,用逗號分開,所以最終的值應該是最后一個表達式的值,也就是d+3,為8,所以a1=8。
對于第二行代碼,那么也是有三個表達式,這時的三個表達式為a2=++b、c–、d+3,(這是因為 賦值運算符比逗號運算符優先級高)所以最終表達式的值雖然也為8,但a2=3。
如果是成員函數的話 this是他的第一個參數 這是隱藏默認的
所以+再給一個that變量即可
x+y//編譯器發現加號左邊是個interger 所以運算符左邊的算子叫做receiver
這個加可能有Interger加 float加 所以取決于左邊的receiver的類型來覺得怎么加
因為x是interger 所以就是interger加
z=x+y//用的overload的加
z=x+3//x是integer加 所以會把3這個int型的數變interger 再來加
z=3+y//3是int 所以用整數的加來加 y是interger 所以他需要interger這個類有個手段把interger變成int 但是interger沒有 所以編譯不通過
如果作為全局函數 而不是成員函數 就需要把左邊右邊的兩個算子寫上去
兩個參數上都可以做類型轉換
以上轉換都可以 z=3+y//在全局函數內可以 因為當他發現用3 int加沒法做的時候 就會把3變成interger來做 因為全局函數兩個參數上都可以做類型轉換 而成員函數要根據左邊算子的類型來加 且看右邊算子能不能自動變成左邊算子的類如 z=3+3.5 不行因為float不能自動轉變成int 必須強制類型轉換 (int) z=3.5+3 可以 因為int可以自動變float
單目的 以及= ()[] -> ->* 31
做成成員函數
所有其他的二元運算符做成非成員的
31運算符重載-原型
對于參數傳遞
1傳進去一定是引用
2若運算符會修改算子 就不能傳const進去 不會修改算子的必須傳const
+= -= boolean 會修改算子
3如果是成員函數 this是加在后面的const 全局函數一個加或者兩個都加
返回值取決于你是對自己做了修改還是 制作了一個新的對象 制造出來的新對象是不是可以繼續做左值
比如加法就是制造一個新的東西來 a+b=6不可以 所以他一定是const的對象
賦值來說a=6 實際上是修改了自己 所以函數后面不能有const 返回的就是reference 還可以做左值
++a 返回的是當時的結果 所以返回的是對象 返回a的reference 且前面有const 防止++a做左值
a++=6是不行的
a++ 要返回的是a以前的結果 而a++執行后已經不是a以前的那個結果了
Integer old(*this)//拷貝構造 用一個對象制造一個新的對象 ++a的調用
++(*this)//第一個++函數定義第二個++函數 第一個加加是++a的調用 第二個加加是a++的調用
關系運算符 默認this是左算子
32運算符重載 賦值
第一行調用拷貝構造函數
第二行調用賦值函數 如果你沒對賦值函數重載 就調用系統默認的
Fee fee=1//Fee(int)
Fi fi //default constructor 構造出來
Fee fum=fi//Fee(Fi)
fum=fi
系統給你的=是memberwise 而不是bitwise
自己構造賦值函數時候 應該是個成員函數 對自己做賦值然后返回自己
如果自己不等于那個that 做賦值
如果自己就是那個that 就不做賦值
什么情況下拿自己等了一遍自己是有壞處的 ?如下圖
如果此時that就是this delete p后p和that是同個東西 that.p不存在 不指向原來那片空間
所以strcpy(p,that.p)無法實現
所以重載=時 一定要有this!=rhs的判斷
賦值重載 如果類里面有動態分配內存的話寫,如果里面沒有動態分配內存沒有指針全都是成員對象 可以不寫 可以用系統的
如果想防止系統做的行為不正確 可以聲明為private 但是代價很大 相當于這個類的對象不能被賦值
33運算符重載—類型轉換
我們希望一些類的作用是用來表達一些值的
value class:看起來像基本元素類型 被函數傳進來傳出去 運算符需要被重載(如分數 的加法乘法) 這個通常可以被轉化為其他類型
f想要一個two 確給了一個one 所以編譯器會先用這個one去調用two的構造函數 構造一個two給f 所以是可以發生的
explicit 顯示的 告訴編譯器我這個構造函數只能做構造函數 不能自動類型轉化
f(Two(one)) 用two的構造函數構造一個新的two的對象出來 然后交給f
一個A類里面的operator double()是用來把A類的對象變成double的
以下為自動轉換 從窄的到寬的 會自動轉 (即左邊類型的變量可以賦給右邊類型的變量)
T給T& 初始化時是綁定 賦值時是operator assiment
T&給T 就是賦值
T給void viod*就是個指針但是不特定指向任何東西 我原來那那塊東西當T看 但是變成void的我就不拿他當任何東西來看 但是那塊東西本身沒有變化
T的對象可以變C的對象的情況
1 C類有個構造函數 他拿T可以做出新的對象 就可以把T轉成C
2 T類定義了一個operator C()
但是當以上兩者都可以時 若沒有在構造函數前面加explicit時讓其不能自動轉換 這樣就會報錯 因為編譯器不知道該用哪一個轉化 如下圖
所以解決辦法是
在Orange(Apple)前加explicit
要不要用類型轉換函數呢 一般來說 不要用 因為自動做的事情你一般不知道
34模板一
把他做成函數模板 不是模板函數(sort 等等可以處理int float等等類型的排序) 模板函數(重點是函數)是函數模板(重點是模板)的實例
模板就是規定我要做一個這樣的函數 怎么做 相當于告訴編譯器 編譯器會幫你做
模板其實是declaration 不是definition
用了模板就不用類型轉換
把int變成complex只有一種方法 如果complex的構造函數是用int作為輸入的 且不是explicit
vector的函數每一個都是函數模板 類模板的每一個函數都是函數模板 所以要記得加
arr是T類型的 遇見swap的時候 是交換T類型的 但是編譯器并不會在這是造出T類型的交換 這里只是聲明不是定義
并且在arr[j]<arr[j-1]處 要求T類型要實現<這個運算符的重載 否則sort無法完成排序
所以說sort用了operator<
Vector<Vector<double*> > > >// 有個vector每個單元都是vector vector每個單元都是一個double的指針 小心兩個大于號連起來 會被識別成右移符號 所以需要有空格
Vector<int(*)(Vector&,int )>
一個vector里面的成員是函數指針(看<>部分) 返回的是int 他的參數表是兩項 1是引用 2是int
在這里插入圖片描述
不是模板的繼承 而是模板中出現了繼承的東西
36異常基本概念
讀個文件的五個步驟可能會出現各種各樣的問題 每一步都可能存在問題和危機
判斷每一步是否會異常的動作
try就是嘗試的五個步驟 如果捕捉到某個步驟異常 就做xxx
exception的好處是把業務邏輯和錯誤處理分的很清楚
如try 五句話 catch五部分
37異常的拋出和捕捉
如果越界的話 怎么辦
1 返回一個隨機的內存對象 不能接受 因為你可能會寫到別人的內存地址上
2返回一個特殊的錯誤值 表達這是一個錯誤 不是好辦法 因為錯誤值可能與后續的操作數相加減
3 exit 退出吧
4 assert 但是也不合適 因為assert表達的是代碼的錯誤
所以綜上用exception
所以一般扔出的是一個類里面的對象
在堆里:有且僅有new出來的東西
全局數據區里:全局變量 靜態本地變量 靜態成員變量
在堆棧里:本地變量 函數內部產生的對象
index這個對象在堆棧里 因為他是函數內部的變量
先看throw所在的大括號是什么 發現是if語句 所以if之后的語句不會被執行
int i=v[42] 相當于throw 然后帶著這個對象離開這個函數
一般異常發生了往下走 不會再回去了
catch里面有throw可能是因為處理的層級不夠 我可能可以做些小調整但是還需要更高的層次來幫忙
… 代表萬能捕捉器 表示所有異常
一旦有異常發現就會沿著調用的鏈條一直找到try和匹配catch 的地方 然后事情在那里得到處理
exception常用于輸入輸出
如果發生問題用throw 這個語句產生異常 這個控制就會回到能控制處理這個異常的第一個部分 回去是隨著調用的鏈和堆棧回去的 堆棧內的東西被catch后最終會被恰當的銷毀
38異常語句
… 是代表所有的異常 但是拿不到對象 最好不要用
在所有catch里面先做1 在做2 在做3
或者針對每一條catch 做1 2 3
先看第一個catch里面是不是exact 不是
然后能不能符合base device conversion 不符合
最后是不是ellipses 不是
所以看第二個match
是不是exact 不是
然后能不能符合base device conversion 符合 所以走這個
每一個catch都按順序按照那三個原則看能不能被匹配
void abc(int a):throw(MathErr){ ... //告訴別的程序說 運行時刻最多只會拋出圓括號里面的那些類的異常 異常可能發生不是一定發生 發生了也只會出現()內的異常 這是限制abc }
c中用malloc分內存,不夠是會返回null c++用new分內存 不夠會返回bad_alloc()的異常
構造函數出錯了 拋異常 這樣這個對象構造沒有完成 析構是不會被調用的 如果在構造函數拋異常 別人無法拿到這個對象
new是先分配內存 先有內存然后執行構造函數 吧這個內存地址交給構造函數 于是構造函數去構造他
但是構造函數拋了異常 構造無法形成 所以p不存在 p不知道在哪 所以這是內存垃圾
所以解決辦法:在構造函數加上 delete this
如果是new出來的 *p 一定要記得delete p
我們傾向于拋的是堆棧里的對象
39流的概念
文件二進制和文本文件
inserters 把一個對象插入到一個輸出流的運算符<<
extractors >>把一個對象插入到一個輸入流的運算符
流是一維單方向和文件不一樣 文件可以在里面任意流動可以在任意地方讀和寫
scanf printf cin cout都是對文本文件操作的 所有的輸入輸出都是在做解析
二進制字符轉換成了我們看見的ASCII碼 這叫parse 解析
文本文件都要經過 輸入其實是解析 輸出其實是格式化
二進制文件 人是看不懂的 是直接對其讀和寫
40流的運算符
自定義的>> 返回的要是 isream&
規定輸出的格式 endl是manipulators
set是變為1 reset是變為0
41stl簡述
sets 集合沒有重復的是無序的 maps映射
v.swap(v2)交換的是兩個vector的值
總結
以上是生活随笔為你收集整理的c++基础入门(根据浙大翁恺老师视频整理)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2019蓝桥杯省赛b组
- 下一篇: 【note】fill函数和memset函