1. 程式人生 > >C++ 語言總結

C++ 語言總結

pub 類型轉換 改變 歷史 ace 字符串 UC peid 提前

蔣貴良
課程時間:
標準C++(11天)
QT(8天)
=======================
《C++程序設計原理與實現》
《C++ Primer》
=======================
聯系:
[email protected]
QQ:280089088
=======================
一 C++概述
1 C++歷史背景
1)C++的江湖地位
jave C C++ C# python

2)C++之父:Bjarne Stroustrup(1950--)
1979,Cpre,為C語言增加類的機制
1983,Bjarne發布了全新的編程語言C with Class
1985,CFront1.0《The C++ programming Language》

3)C++發展過程
1987,GNU C++
1990,Borland C++(BC編譯器)
1992,Microsoft C++(VC)
1998,ISO C++98
2003,對C++98進行修訂,C++03
2011,ISO C++11/C++0x
2014, ISO對C++標準做了部分擴展,C++14
*2017,C++17(未知)

2 應用領域
1)遊戲
2)科學計算
3)網絡通信(ACE)
4)操作系統和設備驅動
5)其它...

3 C和C++
1)都是編譯型語言
2)都是強類型語言,但是C++更強
3)C++兼容C語言,但是去除了C中不好的特性
4)C++增加了很多了好的特性,比C語言更適合大型軟件的開發

二 第一個C++程序
1 編譯方式
1)gcc xx.cpp -lstdc++
2)g++ xx.cpp //good

2 文件擴展名
1)xx.cpp//good
2)xx.cc
3)xx.cxx
4)xx.C

3 頭文件
//包含標準C++中所有和I/O有關的類型、對象、函數
#include <iostream>

//在C++中依然可以使用C庫的函數,但需要寫上對應的有文件,C++中提供了一套和C庫對應的頭文件
#include <stdio.h> ==> #include <cstdio>
#include <stdlib.h> ==> #include <cstdlib>
#include <string.h> ==> #include <cstring>
...

4 標準輸入和輸出
1)用cin對象表示標準輸入//類似scanf
eg:
//從標準輸入設備獲取一個整形數放到變量a中
int a;
scanf("%d",&a);
cin >> a;
">>" 稱為提取運算符
eg:
int a;
double d;
scanf("%d%lf",&a,&d);
cin >> a >> d;

2)用cout對象表示標準輸出//類似printf
eg:
int a = 100;
printf("%d\n",a);
cout << a << endl;
"<<":稱為插入運算符
註:endl表示換行,和“\n”等價
eg:
int a = 10;
double d = 3.14;
printf("%d,%lf\n",a,d);
cout << a << ‘,‘ << d << endl;

5 "std::"表示標準名字空間

三 名字空間(namespace)
1 名字空間的作用
1)避免名字沖突
2)劃分邏輯單元

2 定義名字空間
namespace 名字空間名{
名字空間成員1;
名字空間成員2;
...
}
名字空間成員可以是全局變量、全局函數、類型、名字空間。
eg:
namespace ns{
int i;//全局變量
void func(void){...}//全局函數
struct Stduent{...};//結構體類型
namespace ns2{...}
}

3 名字空間成員使用
1)通過作用域限定運算符“::”
名字空間名::要訪問的成員;
eg:
namespace ns{
int i;//全局變量
}
int main(void){
i=100;//error,名字空間裏面成員不能直接訪問
ns::i=100;//ok,通過作用域限定符可以訪問
}
2)名字空間指令
using namespace 名字空間名;
在該條指令以後的代碼,指定名字空間中的成員都可見,訪問其中的成員可以省略作用域限定。

3)名字空間聲明
using 名字空間名::名字空間成員;
將名字空間中的某個成員引入當前作用域,在該作用域訪問這個成員如果訪問局部變量一樣,可以省略作用域限定.
eg:
namespace ns{
int i1=10;
int i2=20;
}
int main(void){
using namespace ns;//名字空間指令
cout << i1 << endl;//ok
cout << i2 << endl;//ok
---------------------------
using ns::i1;//名字空間聲明
cout << i1 << endl;//ok
cout << i2 << endl;//error
}
4 無名名字空間
不屬於任何名字空間的標識符,將被編譯器劃分到無名名字空間中,顯式訪問裏面的成員:"::成員"。

5 嵌套名字空間//了解
eg:
namespace china{
namespace beijing{
namespace chaoyang{
char* name;
}
}
}
china::beijing::chaoyang::name = "老王";

四 C++結構體、聯合體和枚舉
1 C++結構體
1)定義結構體類型變量時可以省略“struct”關鍵字
eg:
struct A{...};//聲明
A a;//定義結構體類型的變量a
2)在C++結構體裏面可以直接定義函數,稱為成員函數,而且在成員函數中可以直接訪問成員變量;
eg:
struct A{
int a;//成員變量
void foo(void){...}//成員函數
};

2 聯合體 //了解
1)定義聯合體類型變量時可以省略“union”關鍵字
2)支持匿名聯合

3 枚舉
1)定義枚舉類型變量時可以省略“enum”關鍵字
2)C++枚舉是一種獨立的數據類型,而C中枚舉本質就是整型數。
eg:
enum COLOR{RED,GREEN,BLUE};
/*enum*/COLOR c;
c = 100;//C:ok C++:error

五 C++的字符串
1 C++兼容C中字符串表示方式
1)字面值常量字符串 "hello"
2)字符指針 char*
3)字符數組 char[]
eg:
const char* p = "hello";
//strcpy(p,"world");//段錯誤
p = "world";//ok

char arr[5]={0};
strcpy(arr,"jiangguliang");//越界使用內存危險
cout << arr << endl;

//arr-->char* const arr
//arr = "hello";//error
2 C++中增加string類型,專門表示字符串
1)定義字符串
string s;//定義空字符串
string s1 = "hello";//定義同時初始化
string s2("hello");
string s3 = string("hello");
註:s1 s2 s3三種寫法完全等價
2)字符串的基本操作
--》字符串拷貝"="
--》字符串的連接"+" "+="
--》字符串比較: > < == !=
--》獲取字符串中某個字符:[]
--》獲取字符串的長度:size()/length()
--》將string轉換成char*: c_str()
eg:
string s1 = "hello";
s1 = "abcdefg";//拷貝字符串
cout << s1 << endl;//abcdefg
------------------------------
string s1 = "hello";
s1 += " world";//把world連接到s1後面
cout << s1 << endl;//"hello world"
------------------------------
string s1 = "hello";
string s2 = s1 + " world";
cout << s2 << endl;//"hello world"
------------------------------
string s1 = "hello";
string s2 = "world";
if(s1 > s2){//字符串比較
cout << "s1>s2" << endl;
}
else{
cout << "s1<s2" << endl;
}
------------------------------
string s1 = "hello";
//獲取字符串中某個字符
cout << s1[0] << endl;//h
s1[0] = ‘H‘;
cout << s1 << endl;//Hello
------------------------------
string s1 = "hello world!";
cout << s1.size() << endl;//12
cout << s1.length() << end;//12
-----------------------------
string s1 = "hello";
char* s2 = s1;//error
char* s2 = s1.c_str();//ok


=====================
練習:使用string表示字符串,從鍵盤讀取一個字符串,統計裏面包含字母A/a的個數.
string s;
cin >> s;//註:會被空白字符截斷
getline(cin,s);//它可以讀走空格


--day02--
回顧:
1 C++標準
C++98、C++11/C++0x
2 第一個C++程序
.cpp g++
iostream
stdio.h-->cstdio
3 名字空間
1)定義
namespace 名字空間名{成員...}
2)使用
名字空間名::成員;
名字空間指令,using namespace std;
名字空間聲明,using 名字空間名::成員;
3)無名名字空間
4)名字空間嵌套

4 C++的結構體、聯合體、枚舉

5 C++的字符串:string
================================
今天

六 C++的布爾類型
1 bool類型是C++中基本類型,專門表示邏輯值:true/false
2 bool在內存上占一個字節:1表示true,0表示false
3 bool類型可以接收任意類型和表達式的結果,其值非0則為true,值為0則為false

七 操作符別名(了解)
&& --》 and
|| --》 or
{ --》 <%
} --》 %>
...

八 C++函數
1 函數重載
strcpy(char*,const char*)
strcpy(char*,const char*,int)
1)定義
在相同的作用域,定義同名的函數,但是它們的參數表必須有所區分,這樣的函數構成重載關系。
註:重載和返回返回類型無關
2)函數重載匹配
調用重載關系函數函數,編譯器根據實參與形參匹配程度,自動選擇最優的重載版本。
當前編譯器的匹配原則 g++ V4.8.1:
完全匹配>常量轉換>升級轉換>降級轉換>省略號匹配
3)函數重載原理
C++編譯器通過函數換名,將參數表的信息整合到新的函數名中,實現解決函數重載與名字沖突的矛盾
eg:
代碼裏面寫的函數:
void func(int i,double d);
void func(int i);
編譯之後函數名將改變:
func(int,double)-->_Z4funcid
func(int)-->_Z4funci

筆試題:函數聲明中加入extern "C"作用?
要求C++編譯器不會函數做換名,便於C程序調用該函數。
語法:
extern "C" void func(..){..}
----------------------------
extern "C"{
void func1(){}
void func2(){}
}
-------
2 函數的缺省參數(默認實參)
1)可以為函數的部分參數和全部參數指定缺省值,調用該函數,如果不給實參,就取缺省值作為相應的形參值。
eg:
void func(int a,int b,int flag=0){..}
int main(void){
func(10,20,1);
func(10,20);
}
2)缺省參數必須靠右,如果一個參數有缺省值,那麽這個參數的右側所有參數都必須帶有缺省值。

3)如果函數的定義和聲明分開,缺省參數應該寫在函數的聲明部分,而定義部分不寫。
void func(){..} -->函數的定義
void func(); -->函數的聲明

3 函數的啞元參數
1)定義:只有類型而沒有變量名的形參稱為啞元
eg:
void func(int a,int/*啞元*/){}
2)使用啞元的場景
--》為了兼容舊代碼
算法庫:
void math_func(int a,int b){...}
使用者:
int main(void){
math_func(10,20);
...
math_func(20,30);
}
------------------------------------
升級算法庫:
void math_func(int a,int=0){...}

--》操作符重載時,區分前後++/--(後面講)

4 內聯函數(inline)
//筆試題:inline關鍵字的作用
1)定義
使用inline關鍵修飾的函數,表示這個函數是內聯函數,編譯器將嘗試做內聯優化,避免函數調用的開銷,提高代碼的執行的效率。
2)適用場景
--》多次調用的小而簡單的函數適合內聯
--》調用次數極少獲取大而復雜的函數不適合內聯
--》遞歸函數不適合內聯

註:內聯只是一種建議而不是強制要求,能否內聯主要取決於編譯器,有些函數不加inline關鍵字修改也會被默認處理為內聯,有些函數即便加了inline關鍵字也會被編譯器忽略。
---------------------
筆試題:
1)代碼片段1
for(int i=0;i<100000;i++)
for(int j=0;j<100;j++)
...
2)代碼片段2
for(int i=0;i<100;i++)
for(int j=0;j<100000;j++)
...
---------------------
九 C++的動態內存分配
//筆試題:C++中new/delete和C中malloc和free區別
1 回顧C中動態分配
1)分配:malloc()
2)釋放:free()
3)錯誤:返回值
eg:
int* p = (int*)malloc(sizeof(int));
*p = 100;
free(p);//避免泄露
p = NULL;//避免使用野指針

2 C++使用運算符分配動態內存
1)分配:new、new[]
2)釋放:delete、delete[]
3)錯誤處理:異常(後面講)
eg:
//int* p = new int;//分配不初始化
//*p = 100;
int* p = new int(100);//分配同時初始化
delete p;
p = NULL;
-----------------
int* parr = new int[10];//分配數組
parr[0]=10;
parr[1]=20;
...
delete[] parr;
parr = NULL;
--------------------------
十 C++的引用(Reference)
1 定義
1)引用就是某個變量的別名,對引用的操作與對該變量的操作完全相同。
2)語法規則
類型& 引用名 = 變量名;
註:引用在定義時必須初始化,而且初始化以後不能修改引用的目標。
註:引用類型和它所綁定的目標變量類型要一致
eg:
int a = 10;
int& b = a;//b就是a的別名
b++;
cout << a << endl;//11
int c = 20;
b = c;//將c的值賦值給b(a)
cout << a << endl;//20

2 常引用
1)定義引用時加const修飾,即為常引用,不能通過常引用修改引用的目標。
const 類型& 引用名 = 變量名;
eg:
int a = 10;
const int& b = a;//b就是a的常引用
b = 200;//error
2)普通的引用只能引用左值,而常引用也叫做萬能引用,既能引用左值,也能引用右值。
==================
註:關於左值和右值
1)左值:可以放在賦值運算符的左側
-->普通變量都是左值
-->前++/--表達式結果是左值
-->賦值表達式的結果是左值
eg:
int a = 1;
++a = 20;//ok
cout << a << endl;//20
++++++a;
cout << a << endl;//23
eg:
int a = 3,b = 5;
int c = 0;
(c = a) = b;//ok
cout << c << endl;//5

2)右值:只能放在賦值運算符右側
--》字面值常量
--》大多數表達式的值
eg:
int a = 3,b = 5;
(a + b) = 10;//error,a+b的結果是右值
--》函數返回值
eg:
int foo(void){
int a = 100;
return a;//分配臨時變量=a
}
int main(void)
{
int res = foo();//函數調用結果是臨時變量
cout << res << endl;//100
int& r = foo();//error
const int& cr = foo();
return 0;
}



--day03--
回顧:
1 bool類型
2 操作符別名
3 C++的函數
1)函數重載//extern "C"
2)缺省參數 void func(int b,int a = 10)
3)啞元參數 void func(int)
4)內聯函數 inline

4 動態內存分配 new/delete new[]/delete[]

5 C++引用
1)引用即別名: 類型& 引用名 = 變量名;
2)常引用(萬能引用)
3)關註左值和右值
===============================
今天
十 C++的引用(Reference)
1
2
3 引用型函數參數
1)將引用用於函數的參數,可以修改實參變量的值,同時也能減小函數調用的開銷。
2)引用參數有可能意外修飾實參的值,如果不希望修改實參變量本身,可以將其定義為常引用,提高傳參效率的同時還可以接收常量型的實參。

4 引用型函數返回值
1)可以將函數返回類型聲明為引用,避免函數返回值所帶來的開銷。
2)一個函數返回類型被聲明為引用,那麽該函數返回值可以是一個左值。
3)為了避免在函數外部修改引用的目標,可以為該引用附加常屬性。
eg:
int& foo(void){
static int a = 100;
return a;
}
int main(void)
{
foo() = 200;//ok
}
註:不要返回局部變量的引用,因為所引用的目標內存會在函數返回以後被釋放,危險!
但是可以返回成員變量、靜態變量、全局變量的引用

//筆試題:引用和指針的區別...
5 引用和指針
1)從C語言角度,引用的本質就是指針,但是在C++中推薦使用引用而不是指針。
eg:
double d = 3.14;
double& rd = d;
double* const pd = &d;
rd <=等價=>*pd
2)指針定義可以不做初始化,其目標可以修改(指針常量除外),而引用必須做初始化,而且一旦初始化所引用的目標能再改變。
eg:
int a=3,b=5;
int* p;//ok,可以不初始化
//int& r;//error
p = &a;
p = &b;
int& r = a;
r = b;//不是修改引用的目標,而是對r(a)進行賦值

//後面了解
3)可以定義指針的指針(二級指針),但是不能定義引用的指針
eg:
int a = 100;
int* p = &a;
int** pp = &p;

int& r = a;
int& * pr = &r;//error
int* pr = &r;//ok,是一個普通的指針
4)可以定義指針的引用,但是不能定義引用的引用
eg:
int a = 100;
int* p = &a;
int* & rp = p;//ok,指針的引用
-----------
int& r = a;
int&& rr = r;//error,在C++11中稱為右值引用
int& r2 = r;//ok,不能稱為引用的引用,只是一個普通的引用,相當於給a再起一個別名。

5)可以指針數組,但是不能引用數組
eg:
int a=1,b=2,c=3;
int* parr[3] = {&a,&b,&c};//指針數組
int& rarr[3] = {a,b,c};//error
6)可以定義數組引用
eg:
int arr[3] = {1,2,3};
//rarr稱為數組引用,給數組起一個別名
int (&rarr)[3] = arr;//ok
arr[0] <-等價-> rarr[0]
7)和函數指針一樣,可以定義函數引用,語法和函數指針一致。
eg:
void func(int a,int b){...}
int main(void){
//定義和使用函數指針
void (*pfunc)(int,int) = func;
pfunc(10,20);
//定義和使用函數引用
void (&rfunc)(int,int) = func;
rfunc(10,20);
}
======================================
十一 類型轉換
1 隱式類型轉換
eg:
char c = ‘A‘;
int n = c;//隱式類型轉換
-----------
void foo(int n){..}
foo(c);//隱式類型轉換
-----------
int foo(void){
char c = ‘A‘;
return c;//隱式類型轉換
}
2 強制類型轉換
eg:
char c = ‘A‘;
int n = (int)c;//C風格的強制轉換
int n = int(c);//C++風格的強制轉換

3 C++增加了四種操作符形式的顯式類型轉換
1)靜態類型轉換
語法:
目標類型變量 =
static_cast<目標類型>(源類型變量);
適用場景:
用於將void*轉換為其它類型指針。
eg:
int a = 100;
void* pv = &a;//ok
int* pi = pv; //error
int* pi = static_cast<int*>(pv);//ok

2)動態類型轉換(後面講)
語法:
目標類型變量 =
dynamic_cast<目標類型>(源類型變量);
3)常類型轉換
語法:
目標類型變量 =
const_cast<目標類型>(源類型變量);
適用場景:用於去除一個指針或引用的常屬性
eg:
int a = 100;
const int* pa = &a;
*pa = 200;//error
int* pa2 = const_cast<int*>(pa);
*pa2 = 200;//ok
-------------------
const int& r = a;
r = 300;//error
int& r2 = const_cast<int&>(r);
r2 = 300;//ok

4)重解釋類型轉換
語法:
目標類型變量 =
reinterpret_cast<目標類型>(源類型變量);
適用場景:
-->任意類型的指針或引用之間的轉換
-->在指針和整型數之間的轉換
eg:
int addr = 0x12345678;
int* p =
reinterpret_cast<int*>(0x12345678);
*p = 100;
======================
小結:
1 慎用宏,用const、enum、inline替換
#define PAI 3.14 --》const double PAI = 3.14

#define STATE_SLEEP 0
#define STATE_RUN 1
#define STATE_STOP 2
--》enum STATE{SLEEP,RUN,STOP};

#define max(a,b) ((a)>(b)?(a):(b))
--> inline int max(int a,int b){
return a > b ? a : b;
}
2 變量隨用隨聲明同時初始化

3 盡量使用new/delete分配,取代malloc/free

4 少用void*、指針計算、聯合體、強制轉換

5 盡量使用string表示字符串,少用C中的char*表示的字符串

====================================
十二 類和對象//了解
1 什麽是對象?
萬物皆對象,任何一種事物都可以看做是對象。

2 如何描述對象?
通過對象的屬性(名詞、數量詞、形容詞)和行為(動詞)描述和表達對象。

3 面向對象的程序設計
對自然世界中對象觀察引入到編程實踐的一種理念和方法。這種方法稱為"數據抽象",即在描述對象時把細節的東西剝離出去,只考慮一般性的,有規律性的和統一性的東西。

4 什麽是類?
類是將多個對象的共性提取出來定義的一種新的數據類型,是對 對象的屬性和行為的抽象描述。

練習:
復習前面內容(重點看第八、十章節)
預習 十三章 類的定義和實例化

--day04--
回顧:
1 引用型函數參數
2 引用型函數返回值
3 引用和指針
4 類型轉換
1)靜態類型轉換:將void*--》其它類型指針
2)常類型轉換:去除指針或引用的常屬性
3)重解釋類型轉換
5 類和對象
===========================================
今天
十三 類的定義與實例化
1 類的一般形式
class/struct 類名:繼承表{
訪問控制限定符:
類名(形參表):初始化表{}//構造函數
~類名(void){}//析構函數
返回類型 函數名(形參表){}//成員函數
數據類型 變量名;//成員變量
};
2 訪問控制限定符
1)public
公有成員,類內部和外部都可以訪問的成員
2)private
私有成員,只能在類的內部訪問的成員
3)protected
保護成員(後面講)
註:class定義類,默認訪問控制屬性是private,而struct定義的類默認的訪問控制屬性是public。
eg:
class/struct A{
public:
member1;//公有成員
private:
member2;//私有成員
public:
member3;//公有成員
void func(){
member1 = 200;//ok
member2 = 100;//ok,類內部可以訪問私有成員
}
};
int main(){
A a;
a.member1 //ok,類的外部可以訪問公有成員
a.member2 //error,類的外部不能訪問私有成員
}

3 構造函數(Constructor)
class 類名{
類名(構造形參表){
//構造函數體
}
};
1)函數名與類名相同,並且沒有返回類型
2)構造函數在創建對象時自動被調用,不能像普通的成員函數一樣直接去調用。
3)構造函數主要負責初始化對象,即初始化成員變量。

4 對象的創建和銷毀
1)在棧區創建單個對象//重點掌握
類名 對象名(構造實參表);
類名 對象名 = 類名(構造實參表);
註:兩種寫法完全等價
2)在棧區創建多個對象(對象數組)//了解
類名 對象數組[元素個數] =
{類名(構造實參表),類名(構造實參表)..}

3)在堆區創建/銷毀單個對象//重點掌握
創建:類名* 對象指針 = new 類名(構造實參表);
銷毀:delete 對象指針;

4)在堆區創建/銷毀多個對象//了解
創建:
類名* 對象指針 =
new 類名[元素個數]{類名(構造實參表),...};
銷毀:
delete[] 對象指針;
===============================
練習:實現電子時鐘類,讓其構造接收當前系統時間,以秒為單位運行。
class Clock{
public:
Clock(time_t t){
tm* local = localtime(&t);
m_hour = local->tm_hour;
m_min = local->tm_min;
m_sec = lcoal->tm_sec;
}
void run(void){
while(1){
計時:+1秒
打印當前時間;
sleep(1);
}
}
private:
int m_hour;//小時
int m_min;//分鐘
int m_sec;//秒
};
int main(){
Clock c(time(NULL));
c.run();
return 0;
}
=============
5 多文件編程
1)類的聲明部分放在xx.h頭文件中
2)類的實現部分放在xx.cpp源程序中
3)類的使用代碼一般會放在其它文件中

練習:使用多文件編程方法重構電子時鐘類

=============
十四 構造函數和初始化表
1 構造函數可以重載、可以帶有缺省參數,也可以定義啞元.
2 缺省構造函數(無參構造函數)
1)如果類中沒有定義任何構造函數,那麽編譯器會提供一個缺省(無參)構造函數。
--》對於基本類型的成員變量不做初始化
--》對類 類型的成員變量,會自動調用相應類型的無參構造函數來初始化
eg:
class B{
public:
B(void){
m_j = 0;
}
int m_j;
};
class A{
public:
int m_i;//基本類型的成員變量
B m_b;//類 類型的成員變量(成員子對象)
};
int main(void){
A a;
cout << a.m_i << endl;//不確定
cout << a.m_b.m_j << endl;//0
}
2)如果類中定義了構造函數,無論是否有參數,那麽編譯都不會再提供無參構造函數

3 類型轉換構造函數(單參構造函數)
class 目標類型{
public:
[explicit] 目標類型(源類型 src){...}
};
可以實現源類型到目標類型的隱式轉換

註:使用explicit關鍵字,可以強制這種轉換必須顯式的完成。

4 拷貝(復制)構造函數
1)用一個已定義的對象構造同類型的副本對象時,會調用該類的拷貝構造函數.
class 類名{
類名(const 類名& that){...}
};
eg:
class A{...};
A a1;
A a2(a1);//拷貝構造函數
A a2 = a1;//和上面完全等價

2)如果一個類沒有定義拷貝構造函數,那麽編譯器會為其提供一個缺省拷貝構造函數
--》對基本類型的成員變量,按字節復制
--》對類 類型成員變量,調用相應類拷貝構造函數

註:在大部分情況下,不需要自己定義拷貝構造函數,因為編譯器所提供的缺省拷貝構造函數已經很好用了。

3)拷貝構造函數的調用時機
--》用已定義的對象作為類型對象的構造函數
--》以對象形式向函數傳遞參數
--》從函數中返回對象(有可能會被編譯器優化掉)

--day05--
回顧:
1 類的定義和實例化
1)class/struct
2)訪問控制屬性:public private
3)構造函數
4)對象的創建和銷毀
類名 對象名(...);
類名* 對象指針名 = new 類名(...);
5)多文件編程

2 構造函數和初始化表
1)可以重載、缺省參數、啞元
2)缺省(無參)構造函數
3)類型轉換構造函數(explicit)
4)拷貝構造函數
類名(const 類名& that){..}
==================================
十四 構造函數和初始化表
...
5 初始化表
1)語法形式
class 類名{
類名(形參表):成員變量1(初值),...{}
};
2)必須要使用初始化表的場景
--》如果有類 類型的成員變量,而該類又沒有無參構造函數,則必須通過初始化表來初始化該成員變量。
--》類中包含"const"和“引用”成員變量,必須在初始化表中顯式的初始化。
註:成員變量的初始化順序由聲明順序決定,而與初始化表的順序無關。

練習:使用初始化表為電子時鐘類增加計時器功能。
如果使用日歷時間初始化時鐘對象,表現為時鐘功能。
如果以無參的方式構造時鐘對象,通過構造函數的初始化表將時分秒初始化為0:0:0,表現為計時器功能。


十五 this指針與常函數
1 this指針
1)類的成員函數和構造函數中都有一個隱藏的類 類型指針參數,名為this.
--》對於普通的成員函數,this指針就是指向調用該函數的對象。
--》對於構造函數,this指針指向正在被創建的對象

2)需要顯式使用this的場景
--》區分作用域
--》從成員函數返回調用對象的自身(返回自引用)
--》從類的內部銷毀對象自身

2 常成員函數(常函數)
1)在一個普通成員函數後面加上const 關鍵字,這個成員函數就稱為常函數。
返回類型 函數名(形參表) const {函數體}
2)常函數中的this指針是一個常指針,不能在常函數中修改成員變量的值。
註:被mutable關鍵字修飾的成員變量可以在常函數中被修改。
3)非 常對象既可以調用常函數也可以調用非 常函數,但是常對象只能調用常函數,不能調用非 常函數。
4)函數名和形參表相同的成員函數,其常版本和非常版本可以構成重載關系,常對象調用常版本,非常對象調用非常版本。

-------------
十六 析構函數(Destructor)
1 語法形式
class 類名{
public:
~類名(void){//清理對象在創建分配的動態資源}
};
1)函數名必須是"~類名"
2)沒有返回類型,也沒有參數,不能被重載。
3)主要負責清理對象在創建分配的動態資源。

2 當對象被銷毀時,析構函數將自動被執行
1)棧對象當其離開作用域時,其析構函數被作用域終止的右花括號"{"調用。
2)堆對象的析構函數被delete運算符調用。

3 如果一個類沒有顯式定義析構函數,那麽編譯器會為該類提供一個缺省的析構函數;
-->對基本類型的成員變量什麽也不做
-->對類類型的成員變量,會調用相應的析構函數

4 對象的創建和銷毀過程
1)對象的創建
--》分配內存
--》構造成員子對象
--》執行構造函數代碼
2)對象的銷毀
--》執行析構函數代碼
--》析構成員子對象
--》釋放內存
================================
十七 拷貝構造和拷貝賦值
1 淺拷貝和深拷貝
1)如果一個類中包含指針形式的成員變量,缺省的拷貝構造函數只是復制了指針變量的本身,而沒有復制指針所指向的內容,這種拷貝方式稱為淺拷貝。
2)淺拷貝將導致不同對象之間的數據共享,如果數據存放在堆區,可能會在析構時引發"double free"異常,因此就需要自己定義一個支持復制指針指向的內容的拷貝構造函數,即深拷貝。

練習:自定實現String類,支持深拷貝構造
class String{
public:
//構造函數
String(const char* str = ""):
m_str(
strcpy(new char[strlen(str)+1],str)){
/*
m_str = new char[strlen(str)+1];
strcpy(m_str,str);
*/
}
//析構函數
~String(void){
delete[] m_str;
m_str = NULL;
}
//深拷貝構造

//提供訪問接口函數
const char* c_str(void)const{
return m_str;
}
private:
char* m_str;
};
int main(void)
{
String s1("Hello");
String s2 = s1;//拷貝構造
cout << s1.c_str() << endl;//Hello
cout << s2.c_str() << endl;//Hello
return 0;
}

--day06--
回顧:
1 構造函數初始化表
類名(形參表):初始化表{函數體}
2 this指針
1)區分作用域
2)返回自引用
3)自銷毀

3 常成員函數//mutable
返回類型 函數名(形參表)const{函數}

4 析構函數
~類名(void){清理對象創建時分配的動態資源}

5 對象的創建和銷毀
創建:
-》分配內存
-》構造成員子對象
-》執行構造函數代碼
銷毀:
-》執行析構函數代碼
-》析構成員子對象
-》釋放內存
===============================
十七 拷貝構造和拷貝賦值
1 淺拷貝和深拷貝
1)如果一個類中包含指針形式的成員變量,缺省的拷貝構造函數只是復制了指針變量的本身,而沒有復制指針所指向的內容,這種拷貝方式稱為淺拷貝。
2)淺拷貝將導致不同對象之間的數據共享,如果數據存放在堆區,可能會在析構時引發"double free"異常,因此就需要自己定義一個支持復制指針指向的內容的拷貝構造函數,即深拷貝。

2 在C++類中會提供一個缺省的拷貝賦值運算符函數,完成兩個對象直接的賦值操作。但是它和缺省拷貝構造函數類似,也是淺拷貝,為了得到深拷貝賦值的效果,必須自己定義拷貝賦值運算符函數

s2 = s3;//s2.operator=(s3)

類名& operator=(const 類名& that){
if(this != &that){//1)防止自賦值
2)釋放舊資源
3)分配新資源
4)拷貝新數據
}
return *this;//5)返回自引用
}

十八 靜態成員(static)
1 靜態成員變量
class 類名{
static 數據類型 變量名;//聲明靜態成員變量
};
數據類型 類名::變量名 = 初值;//定義和初始化
1)靜態成員變量不屬於對象,但是可以通過對象去訪問它。
2)不能在構造函數中定義和初始化,需要在類的外部單獨的定義和初始化
3)靜態成員變量內存在全局區;
4)靜態成員變量可以通過類名直接訪問
類名::靜態成員變量
5)靜態成員變量在類所創建多個對象之間是共享的

2 靜態成員函數
class 類名{
static 返回類型 函數名(形參表){函數體}
};
1)靜態成員函數沒有this指針,也沒有const屬性
2)可通過"類名::"直接訪問,也可以通過對象訪問
註:靜態成員函數只能訪問靜態成員

3 單例模式:一個類只允許創建唯一的對象
1)禁止在類的外部創建對象:私有化構造函數
2)類的內部維護唯一對象:靜態成員變量
3)提供訪問單例對象的方法:靜態成員函數
4)創建方式:
--》餓漢式:單例對象無論用或不用,程序啟動即創建。
--》懶漢式:單例對象在用的時候再創建,不用即銷毀。
eg:
class A{
public:
static A& get(void){
return m_a;
}
private:
A(void);
A(const A&);
static A m_a;
};
===================
十九 成員指針(了解)
1 成員變量指針
1)定義
類型 類名::*成員指針變量名 = &類名::成員變量;
2)使用
對象.*成員指針變量名;
對象指針->*成員指針變量名;
".*":成員指針解引用運算符
"->*":間接成員指針解引用運算符
註:成員變量指針的本質是類中特定成員在對象中的相對地址。

2 成員函數指針
1)定義
返回類型 (*類名::成員函數指針)(形參表)
= &類名::成員函數名;
2)使用
(對象.*成員函數指針)(實參表);
(對象指針->*成員函數指針)(實參表);

==================
二十 操作符重載
eg:復數x+yi 3+4i
(1+2i) + (3+4i) = 4+6i
1 雙目操作符
1.1 運算類的雙目操作符(L # R):+ - * /
-->左右操作數可以是左值也可以是右值
-->表達式結果是右值
1)成員函數形式
L#R的表達式會被編譯器自動處理為L.operator#(R)的成員函數調用,該函數的返回值即為表達式的值。

2)全局函數形式

1.2 賦值類的雙目操作符

練習:復習String類和單例模式實現

--day07--
回顧:
1 拷貝構造和拷貝賦值(String)
1)深拷貝和淺拷貝
2)拷貝賦值操作符函數

2 靜態成員
1)靜態成員變量(全局區)
2)靜態成語函數(沒有this指針)
3)靜態成員函數只能訪問靜態成員

3 單例模式
1)私有化構造函數,禁止在類的外部創建對象
2)靜態成員變量,在類內部維護一個唯一對象
3)靜態成員函數獲取單例對象
4)創建方式:餓漢式/懶漢式

4 成員指針(了解)
=======================
今天
二十 操作符重載
eg:復數x+yi 3+4i
(1+2i) + (3+4i) = 4+6i
1 雙目操作符(L # R)
1.1 運算類的雙目操作符:+ - * /
-->左右操作數可以是左值也可以是右值
-->表達式結果是右值
1)成員函數形式
L#R的表達式會被編譯器自動處理為L.operator#(R)的成員函數調用,該函數的返回值即為表達式的值。

2)全局函數形式
L#R的表達式會被編譯器自動處理為operator#(L,R)的全局函數調用,該函數的返回值即為表達式的值。

註:通過friend關鍵字,可以把一個全局函數聲明為某個類的友元,友元函數可以訪問類中的任何成員。

1.2 賦值類的雙目操作符
-->左操作數是左值(不能是常量),右操作數可以是左值也可以是右值
-->表達式結果是左值,就是左操作的自身
1)成員函數形式:
L # R --》L.operator#(R);
2)全局函數形式:
L # R --》operator#(L,R);
--------------------
2 單目操作符重載 #O
2.1 計算類單目操作符:-(取負) ~ !
-->操作數可以左值也可以值右值
-->表達式結果是一個右值
1)成員函數形式
#O --》O.operator#();
2)全局函數形式
#O --》operator#(O);

2.2 自增減單目操作符: ++/--
1)前綴自增減
--》操作數是左值
--》表達式結果就是操作數自身,也是左值
成員函數形式:
#O--> O.operator#()
全局函數形式:
#O--> operator#(O)

2)後綴自增減
--》操作數是左值
--》表達式結果是自增減之前的副本(右值)
成員函數形式:
O#--> O.operator#(int/*啞元*/)
全局函數形式:
O#--> operator#(O,int/*啞元*/)

---------------------------
3 插入和提取操作符: << >>
功能:實現自定義類型的輸入和輸出的功能
註:只能用全局函數形式
#include <iostream>
ostream cout;
istream cin;
cout << a;//operator<<(cout,a);
friend ostream& operator<<(
ostream& os,const RIGHT& right){...}

cin >> a;//operator>>(cin,a);
friend istream& operator>>(
istream& is,RIGHT& right);
==========================================
練習:實現3x3矩陣類,支持如下操作符重載
<< + - += -= -(取負) ++(前後) --(前後)
要求:除了<<,都使用成員函數形式
class M33{
public:
M33(void){
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
m_a[i][j] = 0;
}
M33(int a[][3]){
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
m_a[i][j] = a[i][j];
}
const M33 operator+(const M33& m)const{
int a[3][3]={0};
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
a[i][j]= m_a[i][j] + m.m_a[i][j];
M33 res(a);
return res;
}

private:
int m_a[3][3];
};
int main(void)
{
int a1[3][3] = {1,2,3,4,5,6,7,8,9};
int a2[3][3] = {9,8,7,6,5,4,3,2,1};
M33 m1(a1);
M33 m2(a2);
}
m1 + m2
1 2 3 9 8 7 10 10 10
4 5 6 + 6 5 4 = 10 10 10
7 8 9 3 2 1 10 10 10
-------
m1 - m2
1 2 3 9 8 7 -8 -6 -4
4 5 6 - 6 5 4 = 2 0 2
7 8 9 3 2 1 4 6 8

4 new/delete 操作符
static void* operator new(size_t size){...}
static void operator delete(void* p){...}

5 函數操作符"()"

6 下標操作符"[]"


--day08--
二十 操作符重載
5 函數操作符"()"
功能:讓對象當做函數來使用
註:對參數的個數、返回類型沒有限制
eg:
class A{...};
A a;
//a.operator()(100,200)
a(100,200);


6 下標操作符"[]"
功能:讓對象可以當做數組一樣去使用
註:非常對象返回左值,常對象返回右值
eg:
string s("hello");
//s.operator[](0)
s[0] = ‘H‘;//ok
cout << s << endl;//Hello
-------------
const string s("hello");
s[0] = ‘H‘;//error

7 類型轉換操作符
功能:實現自定義類型的轉換
class 源類型{
operator 目標類型(void)const{...}
};

8 操作符重載的限制
1)不是所有的操作符都能重載,下面幾個不能重載
--》作用域限定操作符 "::"
--》直接成員訪問操作符 "."
--》直接成員指針解引用操作符 ".*"
--》條件操作符 "?:"
--》字節長度操作符 "sizeof"//獲取類型大小
--》類型信息操作符 "typeid"//獲取類型信息(後面講)
2)如果一個操作符的所有操作數都是基本類型,則無法重載。
int operator+(int a,int b){//error
return a - b;
}
3)操作符重載不會改變編譯器預定義優先級
4)操作符重載無法改變操作數的個數(函數操作符例外)
5)無法通過操作符重載機制發明新的的操作符
operator@()//error
6)只能使用成員形式不能使用全局函數形式的操作符
=、()、[]、->

===================
二十一 繼承(Inheritance)
1 繼承的概念
通過一種機制表達類與類之間共性和特性的方式,利用已有的類定義新的類,這種機制就是繼承。

eg:描述人類、學生類、教師類
人類:姓名、年齡、吃飯、睡覺
學生類:姓名、年齡、吃飯、睡覺、學號、學習
教師類:姓名、年齡、吃飯、睡覺、工資、講課
-------------
人類:姓名、年齡、吃飯、睡覺
學生類繼承人類:學號、學習
教師類繼承人類:工資、講課

人類(父類/基類)
/ \
學生類 教師類(子類/派生類)

2 繼承的語法
1)定義
class 子類名
:繼承方式1 基類1,繼承方式2 基類2,...{};
2)繼承方式
public:公有繼承
protected:保護繼承
private:私有繼承

eg:
class A{...};
//A類派生B類,B類繼承A類
//A類稱為基類(父類),B稱為子類
class B:public A{
//B類中會存在一份A類中的成員
};

3 公有繼承特性(Public)
3.1 子類對象可以繼承基類的屬性和行為,通過子類訪問基類中的成員,如果是基類對象在訪問它們一樣。
註:子類對象中包含基類的部分稱為"基類子對象"

3.2 向上和向下造型
1)向上造型//重點掌握
將子類類型的指針或引用轉換為基類類型的指針或引用。這種操作範圍縮小的類型轉換,在編譯器看來是安全的,所以可以隱式轉換。
eg:
class Base{};
class Derived:public Base{};
void func(Base& b){}
int main(void){
Derived d;
Base* pb = &d;//向上造型
Base& rb = d;//向上造型
func(d);//向上造型
}
2)向下造型//了解
將基類類型指針或引用轉換為子類類型的指針或引用。這種操作範圍放大的類型轉換,在編譯器看來是危險的,因此必須顯式轉換。

3.3 子類繼承的基類成員
1)在子類中,可以直接訪問基類中公有成員和保護成員,就如同它們是子類自己的成員一樣。
2)在子類中,所繼承過來的私有成員雖然存在(占據內存),但是不可見,所以無法直接訪問,但是可以提供保護或公有的接口函數來間接訪問。
3)基類的構造函數和析構函數,子類無法繼承,但是可以在子類自己的構造函數中通過初始化表,顯式的指明基類部分(基類子對象)的初始化方式。
eg:
class Base{
public:
Base(int data):m_data(data){}
int m_data;
};
class Derived:public Base{
public:
Derived(int data1,int data2)
:Base(data1),m_data2(data2){}
int m_data2;
};

3.4 子類隱藏基類中的成員
1)子類和基類中定義同名的成員函數,因為作用域不同,不會構成重載關系,而是一種隱藏關系。如果需要在子類中訪問所隱藏的基類成員,可以使用作用域限定操作符來顯式指明。
2)通過using聲明可以將基類的成員函數引入子類的作用域的,形成重載。//不推薦

4 繼承方式和訪問控制屬性
1)三種訪問控制限定符:影響訪問該類成員的位置
訪問控制 訪問控制 內部 子類 外部 友元
限定符 屬性 訪問 訪問 訪問 訪問
public 公有成員 ok ok ok ok
protected 保護成員 ok ok no ok
private 私有成員 ok no no ok
---------------------------------------------
2)三種繼承方式:影響通過子類訪問基類中的成員的可訪問性。
---------------------------------------------
基類中的 在公有子 在保護子 在私有子
類中變成 類中變成 類中變成
公有成員 公有成員 保護成員 私有成員
保護成員 保護成員 保護成員 私有成員
私有成員 私有成員 私有成員 私有成員
----------------------------------------------
註:私有子類和保護子類的指針或引用,不能轉換為其基類類型的指針或引用(不能向上造型).

練習:復習公有繼承的語法特性

--day09--
回顧:
1 操作符重載:() [] 類型轉換操作符
2 操作符限制
-------------------
3 繼承
1)概念
2)語法
class 子類:繼承方式 基類{}
基類方式:public、protected、private
3)公有繼承的特性
--》可以把一個子類對象看做是基類對象
--》向上造型:子類指針/引用--》基類指針/引用
--》向下造型:基類指針/引用--》子類指針/引用
--》子類繼承基類的成員:公有、保護、私有
--》子類隱藏基類的成員:解決--》"類名::"
4)繼承方式和訪問控制屬性
=============================
今天:
二十一 繼承(Inheritance)
...
5 子類的構造函數和析構函數
5.1 子類的構造函數
1)如果子類構造函數沒有顯式指明基類子對象的初始化方式,那麽該子對象將以無參方式被初始化。
2)如果希望基類子對象以有參的方式被初始化,必須在子類構造函數的初始化表中顯式指明。
class 子類:public 基類{
子類(...):基類(基類子對象構造實參表){}
};
3)子類對象的構造過程
--》分配內存
--》構造基類子對象(按繼承表順序)
--》構造成員子對象(按聲明順序)
--》執行子類的構造代碼

5.2 子類的析構函數
1)子類的析構函數,會自動調用基類的析構函數,析構基類子對象。
2)子類對象的銷毀過程
--》執行子類的析構代碼
--》析構成員子對象(按聲明逆序)
--》析構基類子對象(按繼承表逆序)
--》釋放內存
3)基類析構函數不能調用子類的析構函數,對一個指向子類對象的基類指針使用delete運算符,實際被執行的僅是基類的析構函數,所釋放的僅是基類子對象構造時的分配的動態資源,而子類特有的動態資源將會形成內存泄露。
eg:
class A{
A(void){動態資源分配}
~A(void){動態資源銷毀}
};
class B:class A{
B(void){動態資源分配}
~B(void){動態資源銷毀}
};
A* pa = new B;//pa:指向子類對象的基類指針
delete pa;//子類析構函數執行不到,內存泄露

解決:明天講..
-------------------------
6 子類的拷貝構造和拷貝賦值
6.1 子類的拷貝構造
1)子類沒有定義拷貝構造函數,編譯器會為子類提供缺省拷貝構造函數,該函數會自動調用基類的拷貝構造函數,初始化基類子對象。
2)子類定義拷貝構造函數,需要使用初始化表,顯式指明基類子對象也以拷貝方式進行初始化。
6.2 子類的拷貝賦值
1)子類沒有定義拷貝賦值操作符函數,編譯器會提供缺省拷貝賦值函數,該函數會自動調用基類的拷貝賦值函數,復制基類子對象。
2)子類定義拷貝賦值操作符函數,需要顯式調用基類的拷貝賦值函數,完成對基類子對象的復制。

7 多重繼承
1)一個子類繼承多個基類,這樣繼承方式稱為多重繼承。
eg:
技術員 經理
\ /
技術主管
eg:
電話 播放器 計算機
\ | /
智能手機

2)向上造型時,編譯器會根據各個基類子對象在子類對象中內存布局,進行適當的偏移計算,保證指針的類型和其所指向的目標對象類型一致。

3)名字沖突問題
一個子類的多個基類存在相同的名字,當通過子類訪問這些名字時,編譯器會報歧義錯誤--名字沖突。
解決名字沖突的通用做法就是顯式地通過作用域限定,指明所訪問的名字屬於哪一個基類。
如果產生沖突的名字是成員函數,並且參數不同,也可以通過using聲明,讓其在子類中形成重載,通過函數重載匹配解決沖突問題。

8 鉆石繼承
1)一個子類的多個基類源自共同的祖先基類,這樣繼承結構稱為鉆石繼承。
A(m_data)
/ \
B C
\ /
D
2)公共基類(A)子對象,在匯聚子類(D)對象中,存在多個實例。在匯聚子類中,或者通過匯聚子類對象,去訪問公共基類的成員,會因為繼承路徑不同而導致結果不一致。
3)通過虛繼承可以讓公共基類(A)子對象,在匯聚子類(D)對象中的實例唯一,並且為所有子類共享,這樣即使沿著不同的繼承路徑去訪問公有基類的成員,結果也是一致的。

9 虛繼承的語法
A(m_data)
/ \
B C(virtual)
\ /
D(負責構造虛基類子對象)
1)在繼承表中使用virutal關鍵字
2)由匯聚子類的構造函數負責構造虛基類子對象
3)公共基類的所有子類,都必須在構造函數的初始化表中顯式指明其初始化方式,否則編譯器將會選擇以無參方式初始化。

----------------------------
練習:薪資計算
員工
/ | \
技術員 經理 銷售員
\ / \ /
技術主管 銷售主管
所有員工:姓名、工號、職位等級、出勤率
經理:績效獎金(元/月)
技術員:研發津貼(元/小時)
銷售員:提成比率(x%)

薪資=基本工資+績效工資
基本工資計算=職位等級的固定額度*出勤率(輸入);
績效工資根據具體的職位而定:
普通員工:基本工資一半
經理:績效獎金*績效因數(輸入)
技術員:研發津貼*工作小時數*進度因數(輸入)
銷售員:提成比率*銷售額度(輸入)

技術主管:(技術員績效工資+經理績效工資)/2
銷售主管:(銷售員績效工資+經理績效工資)/2

結果:打印員工信息,輸入必要數,計算和打印工資

--day10--
回顧:
1 子類的構造函數和析構函數
2 子類對象創建和銷毀過程
創建:分配內存->構造基類子對象->構造成員子對象->執行子類的構造函數代碼

銷毀:執行子類的析構函數代碼->析構成員子對象->
基類子對象->釋放內存
3 子類的拷貝構造和拷貝賦值
4 多重繼承
1)向上造型//自動偏移
2)名字沖突問題
5 鉆石繼承
A
/ \
B C
\ /
D
解決:虛繼承
1)在繼承表中使用virtual
2)末端的匯聚子類負責構造公共基類子對象
=============================================
今天:
eg:
圖形(位置/繪制)
/ \
矩形(長和寬/繪制) 圓形(半徑/繪制)

二十二 多態(Polymorphic)
1 函數重寫(虛函數覆蓋)、多態概念
如果將基類中的某個成員函數聲明為虛函數,那麽子類與其具有相同原型的成員函數就也將是虛函數,並且對基類中的版本形成覆蓋。
這時,通過指向子類對象的基類指針,或者引用子類對象的基類引用,調用該虛函數,實際被執行的將是子類中覆蓋版本,而不是基類中的原始版本,這種語法現象稱為多態.
eg:
class Base{
public:
virtual void foo(void){
cout << "Base::foo" << endl;
}
};
class Derived:public Base{
public:
void foo(void){
cout << "Derived::foo" << endl;
}
};
int main(void){
Derived d;
Base* pb=&d;//pb指向子類對象的基類指針
Base& rb=d;//rb引用子類對象的基類引用
pb->foo();//Derived::foo
rb->foo();//Derived::foo
}

2 函數重寫要求(虛函數覆蓋條件)
1)類中普通的成員函數可以聲明為虛函數,而全局函數、類中的靜態成員函數、構造函數都不能聲明為虛函數。
註:析構函數可以為虛函數(後面講)
2)只有在基類中以virtual關鍵字修飾的函數,才能作為虛函數被子類覆蓋,而與子類中virtual關鍵字無關。
3)虛函數在子類中的覆蓋版本和改函數在基類中原始版本要擁有相同的函數簽名,即函數名、形參表、常屬性必須嚴格一致.
4)如果基類中的虛函數返回基本類型的數據,那麽子類的覆蓋版本必須返回相同的類型。
5)如果基類的虛函數返回類類型指針(A*)或引用(A&),那麽允許子類返回其子類的指針(B*)或引用(B&)。--類型協變
class A{};
class B:public A{};

3 多態的條件
1)在滿足虛函數覆蓋前提下,必須要通過指針或引用調用該虛函數,才能表現出來。
2)調用虛函數的指針也可以是this指針,只要它是一個指針子類對象的基類指針,調用虛函數時,同樣可以表現多態的特性。//重點掌握

4 純虛函數、抽象類和純抽象類
1)純虛函數
virtual 返回類型 函數名(形參表)[const]=0;
2)抽象類
如果一個類中包含純虛函數,那麽這個類就是抽象類,抽象類不能創建對象。
3)純抽象類(有名接口類)
如果一個抽象類除了構造函數和析構函數以外的所有成員函數都是純虛函數,那麽該類就是純抽象類。

註:如果子類沒有覆蓋抽象基類的純虛函數,那麽該子類就也是一個抽象類,類的抽象屬性可以被繼承.

eg:PDF文檔閱讀器

5 多態實現原理(了解)
通過虛函數表和動態綁定,參考poly.jpg
1)動態綁定會增加內存開銷
2)虛函數調用會增加時間開銷
3)虛函數不能被內聯優化
結論:如果沒有多態的語法要求,最好不要使用虛函數。

6 虛析構函數
1)基類析構函數不能調用子類的析構函數,對一個指向子類對象的基類指針使用delete運算符,實際被執行的僅是基類的析構函數,所釋放的僅是基類子對象構造時的分配的動態資源,而子類特有的動態資源將會形成內存泄露。
2)將基類的析構函數聲明為虛函數,那麽子類的析構函數就也是一個虛函數,並且可以對基類的虛析構函數形成有效的覆蓋,可以表現多態的特性。
3)這時delete一個指向子類對象的基類指針,實際被調用的將是子類的析構函數,而子類的析構函數在執行後又會自動調用基類的析構函數,避免內存泄露。
//筆試題:虛析構函數的作用?
=========================================
練習:薪資計算
員工
/ | \
技術員 經理 銷售員
\ / \ /
技術主管 銷售主管
所有員工:姓名、工號、職位等級、出勤率
經理:績效獎金(元/月)
技術員:研發津貼(元/小時)
銷售員:提成比率(x%)

薪資=基本工資+績效工資
基本工資計算=職位等級的固定額度*出勤率(輸入);
績效工資根據具體的職位而定:
普通員工:基本工資一半
經理:績效獎金*績效因數(輸入)
技術員:研發津貼*工作小時數*進度因數(輸入)
銷售員:提成比率*銷售額度(輸入)

技術主管:(技術員績效工資+經理績效工資)/2
銷售主管:(銷售員績效工資+經理績效工資)/2

結果:打印員工信息,輸入必要數,計算和打印工資
class 員工{
double 計算工資(){
return 基本工資() + 績效工資();
}
virutal void 績效工資(){}
};
class 技術員:public 員工{
void 績效工資(){}
}
int main(){
員工對象.計算工資();
技術員對象.計算工資();
}

--day11--
1 多態概念
2 虛函數覆蓋
3 多態條件(this)
4 純虛函數、抽象類、純抽象類
5 多態實現原理
6 虛析構函數
-----------------------
二十三 運行時的類型信息
1 typeid運算符
#include <typeinfo>
typeid(類型/對象)
1)返回typeinfo的對象,用於描述類型信息。
2)在typeinfo類中包含了一個name()成員函數返回字符串形式類型信息。
3)typeinfo類支持"=="、"!="的操作符重載,可以直接進行類型之間的比較,如果類型之間存在多態的繼承關系,還可以利用多態的特性確定實際的對象類型。
eg:
cout << typeid(int).name() << endl;//i

2 動態類型轉換dynamic_cast
語法:
目標類型變量 =
dynamic_cast<目標類型>(源類型變量);
場景:用於具有多態特性的父子類指針和引用之間進行顯示的轉換(向下造型)。
註:在轉換過程中,會檢查目標對象和期望轉換的對象類型是否一致,如果一致轉換成功,不一致轉換失敗。
如果轉換的是指針,返回NULL表示失敗,如果轉換的是引用拋出異常"bad_cast"表示失敗。

二十四 異常(Exception)
1 常見的錯誤
1)語法錯誤
2)邏輯錯誤
3)功能錯誤
4)設計缺陷
5)需求不符
6)環境異常
7)操作不當

2 傳統C中錯誤處理機制
1)通過返回值表示錯誤
優點:函數調用路徑中所有的棧對象,都能正確的被析構,不會內存泄露
缺點:錯誤處理流程比較復雜,逐層判斷,代碼臃腫

2)通過遠跳機制處理錯誤
優點:不需要逐層判斷,一步到位錯誤處理,代碼精煉
缺點:函數調用路徑中的棧對象失去被析構的機會,有內存泄露的風險

3 C++異常機制
結合兩種傳統錯誤處理的優點,同時避免它們的缺點,在形式上實現一步到位的錯誤處理,同時保證所有棧對象能正確的被析構。

4 異常語法
1)異常拋出
throw 異常對象;
eg:
throw -1;
throw "File Error";

class FileError{};
throw FileError(...);

2)異常捕獲
try{
可能發生異常的語句;
}
catch(異常類型1){
針對異常類型1的處理
}
catch(異常類型2){
針對異常類型2的處理
}
...
catch(...){
針對其它異常的處理
}

註:catch子句根據異常對象類型自上而下順序匹配,因此對類類型的異常捕獲要寫到對基類類型的異常捕獲的前面,否則子類的異常將被提前截獲。

5 函數異常說明
1)可以任何函數中增加異常說明,說明該函數所可能拋出的異常類型。
返回類型 函數名(形參表) throw(異常類型表){}
2)函數的異常說明是一種承諾,表示該函數所拋出的異常不會超出說明的範圍。如果函數拋出了異常說明以外的異常,則無法正常捕獲,導致進程終止。
3)異常說明極端形式
--》不寫異常說明,表示可以拋出任何異常
--》空異常說明,throw(),表示不會拋出任何異常
4)如果函數聲明和定義分開,要保證異常說明的類型一致。

補充:函數重寫要求
如果基類中的虛函數帶有異常說明,那麽該函數在子類中覆蓋版本不能說明比基類拋出更多的異常,否則將因為"放松throw限定"而編譯報錯.

6 標準異常類
class exception{
public:
exception()throw(){}
virtual ~exception()throw(){}
virtual const char* what() const throw();
};
eg:
class A:public exception{
public:
const char* what() const throw(){
//...
return "Error A";
}
};
try{
throw A();
}
catch(exception& ex){
ex.what();//Error A
}

7 構造函數和析構函數中的異常
1)構造函數拋出異常,該對象將會被不完整構造,這樣對象的析構函數永遠不會被自動執行。因此在構造函數拋出異常之前,需要手動銷毀之前分配的動態資源。
2)析構函數最好不要拋出異常

---------------------------------
二十五 I/O流 //了解
1 主要的I/O流類
ios
/ \
istream ostream
/ | \ / | \
istrstream ifstream iostream ofstream ostrstream
/ \
strstream fstream
2 格式化I/O
1)格式化函數
eg:
cout << 10/3.0 << endl;//3.33333
cout.precision(10);
cout << 10/3.0 << endl;//3.333333333
2)流控制符
eg:
cout << 10/3.0 << endl;//3.33333
cout << setprecision(10) <<
10/3.0 << endl;//3.333333333

3 字符串流
#include <strtstream>//過時
istrstream ostrstream strstream

#include <sstream>//當前用的比較多
istringstream//讀取內存,sscanf()
ostringstream//寫入內存,sprintf()
stringstream //讀寫內存

4 文件流
#include <fstream>
ifstream //讀取文件,fscanf
ofstream //寫入文件,fprintf
fstream //讀寫文件

5 二進制I/O //fread、fwrite
ostream& ostream::write(
const char* buffer,size_t num);

istream& istream::read(
char* buffer,streamsize num);

C++ 語言總結