目錄
1.C++的bool關鍵字
(1)bool類型也叫邏輯類型,是個2值enum,值爲true或false(這2個也是C++關鍵字)
(2)C語言沒有bool關鍵字,不源生支持bool類型,一般用typedef int bool;這樣來自定義
(3)C++語言源生支持bool類型,一般佔1字節(平臺相關),用法沒什麼差異
(4)bool內建和自定義至少有一個差別:函數重載機制認爲bool是不同類型
#include <iostream>
using namespace std;
int main(void)
{
int a = 4;
bool b1 = !a;
cont << b1 <<endl; //輸出0
cout << boolalpha << b1 << endl; //輸出false
return 0;
}
2.C++的字符類型char
1、char
(1)字符類型,一般佔1字節,表示字符(ASCI或unicode字符)
(2)從C++14開始char默認是unsigned還是signed取決於目標平臺,如arm默認unsigned,而X64默認是signed,建議如果在意符號最好顯式使用unsigned char或signed char
(3)char類型cout輸出默認爲字符,而int類型cout輸出默認爲數字
(4)1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
2、wchar_t
(1)寬字符,用於應對一個字符編碼超過1字節的Unicode編碼
(2)wchar_t和char的數組都能存下unicode碼,區別是需要幾個單元才能存一個字
(3)wchar_t佔幾個字節取決於具體實現,可能是unsigned short也可能是int
(4)wchar_t要用wcin和wcout來輸入輸出,對應字符串爲wstring
3、指定具體字節數的字符類型
(1)char8_t (C++20 起) , char16_t (C++11 起) , char32_t (C++11 起)
(2)這三個類型一個套路,最大特徵就是明確指定佔用字節數,且都是無符號的
(3)char8_t很大程度上等同於unsigned char
(4)C++20起,新增字符串類u8string, u16string, u32string
3.C++中無變化關鍵字和運算符代用關鍵字
1、C++中無明顯變化的關鍵字
if
else
for
do
while
break
continue
switch
case
default
goto
return
unsigned
signed
float
double
short
int
long
void
sizeof
register
volatile
extern
typedef
asm
2、C++中新增的運算符代用關鍵字
(1)邏輯運算代用關鍵字
and -------> &&
or -------> ||
not -------> !
(2)位運算代用關鍵字
bitand-------> &
bitor-------> |
xor -------> ^
and_eq -------> &=
or_eq -------> |=
xor_eq -------> ^=
compl -------> ~
(3)不等判斷運算符代用關鍵字
not_eq -------> !=
(4)運算符代用關鍵字的優勢:有些人認爲這樣便於理解和閱讀
4.C++新增的引用
1、引用介紹
(1)引用的經典案例:實現swap函數實戰
#include <iostream>
using namespace std;
void swap1(int a, int b);
void swap2(int *pa, int *pb);
void swap3(int &pa, int &pb);
int main(void)
{
int x = 4, y = 6;
swap1(x, y);
swap2(&x, &y); // &在這裏是取地址符
swap3(x, y);
cout << "x = " << x << ", y = " << y << endl;
return 0;
}
// 不成功
void swap1(int a, int b)
{
int tmp;
tmp = a;
a = b;
b = tmp;
}
// 成功,用指針
void swap2(int *pa, int *pb)
{
int tmp;
tmp = *pa;
*pa = *pb;
*pb = tmp;
}
// 成功,用引用
void swap3(int &pa, int &pb) // &在這裏是引用標識符
{
int tmp;
tmp = pa;
pa = pb;
pb = tmp;
}
(2)引用定義和識別的關鍵:&符號,注意這裏和取地址一毛錢關係都沒有
(3)引用符號(注意我沒說變量)在定義時必須同時初始化,以後不能再另外賦值,只能使用
#include <iostream>
using namespace std;
int main (void)
{
int a = 2;
int &b = a;
b = 3;
cout << b << endl; //b = 3,可以通過a的引用符號b來改變原來a 的值
return 0;
}
2、引用和指針的對比
(1)指針在C和C++中都有,且使用方法和實現本質完全相同;引用只有C++可用
(2)引用可以理解爲功能弱化、安全性增強的低配版指針
(3)引用能做的事指針都能做,但指針能做的事兒引用不一定能做
(4)引用是它指向變量的“別名”,這個是從引用的使用效果角度講的,對熟悉指針的人反而不好理解“別名”這個詞
(5)引用比指針弱的地方就是一個引用定義時綁定了一個變量,後面沒法改了
(6)引用比指針強的地方也是沒法改,所以不存在"野指針"問題,更安全
(7)引用主要用在函數傳參和返回值
3、引用可以加const修飾
(1)const int &b = a; 表示b是a的const別名,無法通過b修改a了
注:int const *p 等價於const int *p ,都表示p指向的空間是const的;而int * const p 表示p本身是const的。
int x = 4;
const int &b = x; // 定義了一個引用符號叫b,關聯到x
//b = 44; // 編譯時報錯
x = 44; // 編譯可以,運行也可以
(2)主要用在函數形參中,告訴大家該函數內部不會修改實參的值。用在某些時候我們有一個非const類型的變量,但是我們在某個函數調用的過程中,不希望變量的值在函數內部被修改,這時候就可以用const引用來傳參。
int func(const char &p1, const char &p2)
char a1 = 'A', a2 = 'b';
const char &ra1 = a1;
const char &ra2 = a2;
func(ra1, ra2); // 在func內部肯定不會修改ra1和ra2(a1和a2)
a1 = 'B'; // 實際上a1和a2並不是const的,是可以修改的
4、引用的本質是const指針
(1)int &b = a; 等價於 int * const b = &a;也就是說b本身是const的,也就是爲什麼引用定義時要初始化,而且一旦初始化之後就不能另外賦值。
(2)C++標準並沒有規定引用是否佔用內存空間,但是大多數編譯器都把引用實現爲const指針,所以大部分編譯器中引用也是要佔內存空間的
(3)引用是天然const的,所以定義時必須初始化指向變量,否則就沒意義了
(4)引用本質是指針,是地址,所以才能實現傳址調用的效果
總結:引用就是指針在定義時增加了把指針變量本身const化
5、引用和sizeof運算符
(1)sizeof引用得到的不是引用本身的大小,而是引用指向的目標變量的大小
(2)在struct或class中定義一個引用,再sizeof整個struct或class就會不一樣
#include <iostream>
using namespace std;
struct ints1
{
int a; // 元素1,int類型,在我的64位Ubuntu系統中,佔8字節
int &b; // 元素2,int &類型
};
struct chars1
{
char a; // 元素1,char類型,在我的64位Ubuntu系統中,佔1字節
char &x; // 元素2,char &類型
};
int main(void)
{
cout << sizeof(struct ints1) << endl;
cout << sizeof(struct chars1) << endl;
int a = 4;
double b = 5.5;
char c = 'A';
int &ra = a;
double &rb = b;
char &rc = c;
cout << sizeof(rc) << "--" << sizeof(char) << endl;
cout << sizeof(ra) << "--" << sizeof(int) << endl;
cout << sizeof(rb) << "--" << sizeof(double) << endl;
return 0;
}
5.C++的enum枚舉
1、C++繼承C的枚舉用法
(1)典型枚舉類型定義,枚舉變量定義和使用
(2)枚舉類型中的枚舉值常量不能和其他外部常量名稱衝突:舉例1宏定義,舉例2另一個枚舉
// C和C++98等老版本里的定義和使用
enum day {MON, THU, WEN};
enum day d1; // 定義了一個day類型的變量,變量名是d1
/**********************************/
//C中習慣用typedef來重命名類型以避免每次類型使用都加enum
typedef enum {MON, xxx, xx} day;
day d1;
/*********************************/
//枚舉類型中的枚舉值常量不能和其他外部常量名稱衝突
enum day {MON, THU, WEN};
enum day2 {MON, xxxx, yyy};
#define MON "55" //會報錯
2、C++11中擴展的枚舉
(1)enum class enumType:valueType{one=xx, two, three};
(2)兩種簡寫
(3)解決2個枚舉中的重名問題,但是宏定義仍然不能重名
/*********************************/
// C++11開始enum支持新的寫法
// 完全寫法
enum class day:unsigned int{MON = 44, THU, WEN};
// 簡化寫法1
enum class day{MON, THU, WEN};
// 簡化寫法2
enum day{MON, THU, WEN};
//使用方法
day d1; // C++中定義時可以省掉前面的enum
d1 = day::MON;
/********************************/
6.C++的共用體union
1、C語言中union回顧
(1)union翻譯成共用體更合適,而不是聯合、聯合體
(2)union中所有成員是多選一的關係,這是union和struct的最大差別
(3)union的典型用法是測試大小端,面試筆試常考,必須掌握(具體可參考我的博客C語言高級專題)
2、C++中union和C中不同
(1)C++中union類型定義後使用時可以省去union(和上節enum時一樣)
(2)C++中union裏成員除了普通的,還可以是對象,但是對象不能包含自定義構造函數、析構函數,簡單說就是不能太複雜
(3)C++中經常用到匿名union,一般是內置在class內部做成員變量
(1)union在C++中沒有突出變化,主要還是沿用C中使用
union myu // union類型的定義
{
char *p;
函數指針p1;
};
union myu m1; // C中定義了一個myu類型的變量
myu m1; // C++中定義了一個myu類型的變量
(2)匿名union
union
{
char *p1;
int *p2;
}m1; // 直接定義了union變量m1
7.inline關鍵字
1、C中inline使用關鍵點強調
(1)inline是一種“用於實現的關鍵字”,而不是一種“用於聲明的關鍵字”,所以關鍵字 inline 必須與函數定義體放在一起,而不是和聲明放在一起
(2)如果希望在多個c文件中使用,則inline函數應該定義在h文件中(不需要額外聲明);如果只在一個c文件中使用,則inline函數可以定義在c文件或h文件中(若定義在c文件時可以聲明到h文件中去,聲明時可以不加inline)
(3)inline函數在項目中可以多次定義,只要函數體完全相同且在一個c文件範圍只定義一次
(4)inline只是一種對編譯器的建議而不是強制,所以inline函數不一定真被inline
(5)遞歸函數不應該被聲明爲inline,超過一定長度(通常是10行)的函數不應該被inline,內含循環的函數不建議被inline
2、C++中inline新增的特性
(1)定義在類聲明之中的成員函數將自動地成爲內聯函數,例如如下代碼:
class A
{
public:
void Foo(int x, int y) // 自動地成爲內聯函數,即使沒有inline關鍵字
{
}
}
(2)如果在類中未給出成員函數定義,而又想內聯該函數的話,那在類外要加上 inline,否則就認爲不是內聯的。值得注意的是:如果在類體外定義inline函數,則心須將類定義和成員函數的定義都放在同一個頭文件中,否則編譯時無法進行置換。
class B
{
int add(int a ,int b);
}
inline int add (int a,int b)
{
return a + b ;
}
8.C++11引入的nullptr
1、C語言中的NULL
(1)NULL用來標記野指針
int *p = NULL;
if (NULL != p)
{
// 解引用指針p
}
(2)NULL在C和C++中的定義爲什麼不同?因爲C++不允許void *隱式轉爲int *等類型
(3)C++中也可以繼續用NULL,但是因爲函數重載的引入,NULL傳參會帶來歧義
#include <iostream>
using namespace std;
// C語言中NULL就是(void *)0
// C++語言中NULL就是0
void func(int a)
{
cout << "func int a" << endl;
}
void func(int *p)
{
cout << "func int *p" << endl;
}
int main(void)
{
int a = 1;
int *p = &a ;
func(a);
func(p);
func(NULL); //編譯發生錯誤,有歧義
return 0;
}
2、nullptr如何工作
(1)nullptr傳參,表示真正的空指針
(2)nullptr的本質:
const class nullptr_t{
public:
template<class T> inline operator T*()const {return 0;}
template<class C, class T> inline operator T C::*() const {return 0;}
private:
void operator&() const;
} nullptr={};
思考:NULL和nullptr到底有什麼區別
- NULL是個宏定義,而nullptr是個關鍵字
- NULL本質是一個數字0,而nullptr本質是一個指針類型
- nullptr就是一個可以繞過C++嚴格的類型檢查的NULL,就是因爲C++不允許int *p = (void *)0這樣,所以纔有了nullptr
3、nullptr的評價
(1)C++11開始可用,注意版本要求,編譯時加編譯選項 -std = c++11
(2)實踐中在判斷野指針時很多人還是喜歡if (!p)這樣···
(3)nullptr無法解決char p和int p這樣的傳參重載問題,所以還是有點不完美
(4)nullptr不屬於任何一種對象指針,但是卻可以表示任何類型的空指針**
9.使用靜態斷言
1、C中的斷言assert
(1)直接參考:assert參考博客
(2)C的assert是運行時檢測發現錯誤,而不是編譯時
(3)C在編譯時錯誤用#error來輸出
2、C++靜態斷言
(1)C++引入static_assert(表達式, “提示字符串”)來實現編譯時的靜態斷言
(2)實例演示
#include <iostream>
using namespace std;
// 假設:我寫了一個項目只能運行在32位系統上,我要防止用戶在64位系統上運行該程序
int main(void)
{
static_assert((sizeof(void *) == 4), "not support non 32bit system");
return 0;
}
3、靜態斷言主要用途
(1)static_assert主要用於檢查模板參數是否符合期望
(2)C++20中引入了concept來進一步更好的實現模板參數的編譯時類型匹配檢查
10.C++內存對齊
1、C語言中內存對齊關鍵點
(1)#pragma pack(),和 __ attribute __((aligned(n)))
2、C++中內存對齊新增關鍵字
(1)alignof (C++11 起)
(2)alignas (C++11 起)
3、實戰演示
#include <iostream>
using namespace std;
#pragma pack(1)
struct s1
{
char a; // 1 +3 4
int b; // 4 +0 4
double c; // 8 +0 8
};
#pragma pack()
struct s2
{
char a;
short b;
short c;
}__attribute__((aligned(32)));
int main(void)
{
// alignof有點像sizeof,用來測一個類型或變量的對齊規則
cout << "alignof s1 = " << alignof(s1) << endl; //1
cout << "sizeof s1 = " << sizeof(s1) << endl; //13
cout << "alignof s2 = " << alignof(s2) << endl; //32
cout << "sizeof s2 = " << sizeof(s2) << endl; //32
return 0;
}
4、什麼情況下需要人爲改變/指定對齊方式
(1)往大去對齊。有時候會有一些硬件特殊要求,譬如MMU,cache等。用__ attribute __((aligned(n)))實測ok,用#pragma實測不ok
(2)往小去對齊。有時候需要節省內存而浪費效率,所以希望忽略內存對齊,緊密排放。用#pramgma實測ok,用
__ attribute __((aligned(n)))實測不ok
5、alignas的用法
(1)使用方法:一般在類型定義時,放在名稱前
#include <iostream>
using namespace std;
struct alignas(32) s3
{
char a; // 1 +3 4
int b; // 4 +0 4
double c; // 8 +0 8
};
int main(void)
{
cout << "alignof s3 = " << alignof(s3) << endl; //32
cout << "sizeof s3 = " << sizeof(s3) << endl; //32
return 0;
}
(2)效果:和__attribute__((aligned(n)))效果一樣
11.typeid
2、typeid
(1)typeid是一個運算符,類似於sizeof
(2)typeid定義在頭文件typeinfo中,必須包含該頭文件
(3)typeid用來返回一個變量(表達式)(對象)的類型
(4)typeid使用實戰
#include <iostream>
#include <typeinfo>
using namespace std;
int main(void)
{
// int a; // int類型的type.name是 "i"
// char a; // char類型的type.name是 "c"
// unsigned char a; // unsigned char類型的type.name是 "h"
signed char a; // signed char類型的type.name是 "a"
unsigned char b;
if (typeid(a) == typeid(b))
cout << "==" << endl;
else
cout << "!=" << endl;
cout << "a type = " << typeid(a).name() << endl;
return 0;
}
3、typeid的深層次說明
(1)一個表達式的類型分靜態類型和動態類型,分別對應編譯時和運行時類型決策系統
(2)typeid可用來返回靜態類型,也可用來返回動態類型
(3)typeid是C++語言本身的特性,由編譯器和庫函數共同支撐
(4)typeid真正大用在引入class和繼承後,並結合指針和引用後才能顯現出來
12.C++的4種cast轉換
1、static_cast
(1)源生類型之間的隱式類型轉換,可以用static_cast來明確告知編譯器,避免警告,轉換後可能丟失精度,正確性需要程序員自己保證
(2)用來將void *p轉爲具體的指針類型,取回原有的指針類型
(3)用於類層次結構中父類和子類之間指針和引用的轉換。其中上行轉換時安全的,而下行轉換時不安全的。
(4)總結:static_cast<>()是編譯時靜態類型檢查,使用static_cast可以儘量發揮編譯器的靜態類型檢查功能,但是並不能保證代碼一定“正確”(譬如可能會丟失精度導致錯誤,可能經過void *之後導致指針類型錯誤,可能下行轉換導致訪問錯誤。)
(5)評價:static_cast必須會用,見了必須認識,能理解使用static_cast的意義,但是實際上只能解決很初級的編程問題,屬於初級語法特性。
int a = 5;
int *p = &a;
void *p1 = p; // p1已經丟掉了自己的類型
int *p2 = static_cast<int *>(p1); // p2又取回了自己的類型
int *p3 = (int *)p1;
char *p4 = static_cast<char *>(p1);
// char *p5 = static_cast<char *>(p); // 編譯器報錯,編譯器知道p是int *的,現在你要轉成char *,編譯器就會報錯
char *p6 = (char *)p; // C語言中的寫法ok,可以
2、reintepret_cast
(1)用於明確告知編譯器該類型轉換在編譯時放行,正確性由程序員自己負責
(2)reintepret_cast轉換前後對象的二進制未發生任何變化,只是對這些二進制位的編譯器類型標識發生了變化,或者說是編譯器看待這些二進制位的結論不同了
(3)reintepret_cast一般用於將指針轉成int或者回轉,將A類型指針轉爲B類型指針等
(4)reintepret_cast其實就是讓C++在本次轉換中放棄嚴苛的編譯器類型檢查
int a = 5;
int *p = &a;
char *p5 = static_cast<char *>(p); // 編譯器報錯
char *p5 = reinterpret_cast<char *>(p); //明確告訴編譯器,雖然類型不匹配,但是轉換後的正確性由程序員負責,編譯時不要報錯
3、const_cast
(1)用來修改類型的const或volatile屬性
(2)格式爲:const_cast<type_id> (expression)
#include <iostream>
using namespace std;
int main(void)
{
const int a = 5;
// a = 6; // 編譯報錯,因爲a是const類型所以編譯器發現操作非法
// int *p = (int *)&a; // 老式轉換可以,但是不推薦
int *p = const_cast<int *>(&a); // 新式寫法,推薦
*p = 14;
cout << "&a = " << &a << endl; //0x7fffc57a1a0c 地址相同,說明p指向的是a的地址
cout << "p = " << p << endl; //0x7fffc57a1a0c
cout << "a = " << a << endl; //5 雖然通過指針解引用的方式去修改a的值,但是c++編譯器從定義const int a = 5;起
cout << "*p = " << *p << endl; //14 就把a和5綁定起來,相當於宏定義一樣
如果定義的時候定義成volatile const int a = 5;
則:cout << "a = " << a << endl; 不讓編譯器優化,這時就能改變a的值=14;
return 0;
}
4、dynamic_cast
(1)只用在父子class的指針和引用訪問時的轉換中,尤其是下行轉換時
(2)屬於一種運行時轉換機制,運行時才能知道轉換結果是NULL還是有效對象
(3)運行時確定對象類型RTTI(run time type indentification)是一種需求,C++有一套機制來實現
5、4種cast轉換總結
(1)C中一般都用隱式轉換或強制類型轉換解決,本質上全靠程序員自己把控
(2)C++中4種cast轉換實際上是細分了具體場景,讓程序員在具體情況下顯式的使用相應的cast來轉換,讓編譯器和運行時儘可能幫程序員把關。
13.C++的自動類型推導
1、auto關鍵字
(1)auto在C中修飾局部變量,可以省略,完全無用。C++中的auto完全是一個新關鍵字
(2)auto要求至少不低於C++11標準來支撐
(3)auto在編譯器由編譯器幫我們自動推導出變量(對象)類型,所以定義時必須初始化
(4)auto可以一次定義多個同類型的變量,但是不能一次定義多個類型不同的變量,這是auto的類型推導機制決定的。
int i = 5;
auto i = 5; // 編譯器在編譯時自動幫我們推導出i的類型是int
int a, b;
auto a = 4, b = 5; // 正確
auto a = 4, b = 5.5; // 錯誤
2、decltype關鍵字
(1)C++11新增關鍵字
(2)decltype可以讓編譯器推導目標表達式的類型作爲一種類型符使用
(3)decltype(表達式)作爲類型定義變量不要求初始化
double i = 5;
decltype(i) j; // 定義了變量j,類型是和i相同
3、auto和decltype的對比
(1)auto忽略頂層const,而decltype則保留const
(2)auto作爲類型佔用符,而decltype用法類似於sizeof運算符
(3)對引用操作,auto推斷出原有類型,decltype推斷出引用
(4)對解引用操作,auto推斷出原有類型,decltype推斷出引用
(5)auto推斷時會實際執行,decltype不會執行,只做分析。
const int i = 5;
decltype(i) j = 8;
j = 9; // 編譯器報錯,因爲j是const的
const int i = 5;
auto j = i;
j = 4; // 編譯器不報錯,auto忽略頂層const
14.C++類與面向對象
1、struct和class
(1)struct是C中用戶自定義類型,主要功能是對功能相關數據的封裝
(2)struct不能直接封裝函數,但可以通過封裝函數指針來間接封裝函數
(3)struct就是class的初級階段,class在struct基礎上做了很多擴展,便有了面向對象
2、訪問權限
(1)類是對數據(成員變量)和方法(成員函數)的封裝
(2)封裝的一個重要特徵就是訪問權限管控,本質是爲了隱藏實現細節,避免意外篡改
(3)C++支持三個訪問管控級別:private、protected、public
3、C++的對象創建和銷燬
(1)對象的本質等同於C中的變量,對象的創建和銷燬也就是變量的產生和銷燬,本質上是變量對應的內存地址的分配和釋放歸還
(2)C中全局變量和局部變量都是自動分配和回收內存的,堆內存需要用戶手工申請和釋放(malloc和free調用)
(3)C++中因爲變量升級成了對象,涉及到構造函數和析構函數,因此malloc和free升級爲了new和delete
(4)C++中仍然大量使用全局變量和局部變量,但是動態分配佔比例越來越多。這是業務特點決定的,不是C++語言決定的。語言只是提供機制,業務才決定策略。
15.C++中static和this關鍵字
1、static在C中的用法
(1)靜態全局變量和函數,限制鏈接屬性。C++中建議優先使用命名空間機制替代
(2)靜態局部變量,更改地址域和生命週期。C++中繼續沿用。
2、static在C++中新增用法
(1)用在class中,有靜態數據成員和靜態成員函數
(2)簡單理解:靜態成員和方法是屬於class的,而非靜態是屬於對象的
(3)靜態類往往用在單例模式中,實際上和麪向對象的思想有所差異
(4)要真正理解靜態類,得先學習面向對象和普通非靜態類後纔可以
3、this關鍵字
(1)本質是個指針,指向當前對象
(2)this的主要作用是讓我們在未定義對象前可以在方法中調用對象裏的成員
(3)this的深度講解在第2部分,包括具體使用和實現原理
#include <iostream>
using namespace std;
class A
{
public:
// 成員變量
int i;
// 非靜態成員函數 方法
void func1(void);
// 靜態成員變量
static int j;
// 靜態成員方法
static void func2(void); // static是一個聲明性的
};
void A::func1(void)
{
// cout << "A::func1, i = " << i << endl; // 成員函數中訪問成員變量
cout << "A::func1, i = " << this->i << endl; //實質是通過this指針找到的成員變量i
}
void A::func2(void)
{
cout << "A::func2" << endl;
}
int main(void)
{
A::func2(); //直接輸出A::func2,不用定義對象,說明靜態成員函數是屬於class的
A a; // 首先要定義一個對象,沒有對象就無法訪問成員
a.i = 34; // 在外部訪問class中普通成員變量的方法
a.func1(); // 在外部訪問class中普通成員函數的方法
//輸出 A::func1, i = 34 ,說明非靜態成員函數是屬於對象的
return 0;
}
16.C++面向對象的其他關鍵字
1、面向對象允許類的繼承機制
(1)C++中用:來表示繼承關係,有些編程語言有extends關鍵字表示繼承關係
(2)virtual修飾class的成員函數爲虛函數,一般在基類中,只有接口聲明沒有實體定義
(3)基類的virtual成員可以在派生類中override重寫,以實現面向對象的多態特性
(4)注意區分重寫override與重載overload
(5)override關鍵字是C++11引入,用來在派生類中成員函數聲明時明確表明需要派生類去重寫的那些成員方法,這樣如果程序員在成員方法實體定義中做的不對編譯器可以報錯提醒
2、繼承的終止final
(1)一個class不希望被繼承(不想做父類),可以定義時用final修飾
(2)一個成員方法不希望被子類override,可以聲明時用final修飾
(3)final是C++11引入的
(4)很多其他面嚮對象語言如java中也有final關鍵字,也是這個作用
3、using關鍵字
(1)用法1就是using namespace std;這種
(2)用法2與class的繼承和訪問權限限制有關,屬於一個聲明,能夠讓private繼承的子類去聲明並訪問父類中本來無權限訪問的成員
4、operator
(1)用於運算符重載,也就是爲一個class重定義某種運算符
5、friend
(1)讓不屬於一個class的外部函數也能訪問class內受保護的成員變量
(2)實際上是對面向對象的一種擴展或者說破壞,在面向對象深入理解之後再來學習更好
6、explicit
(1)本意爲顯式的,對應implicit隱式的
(2)用來修飾只有一個參數的構造函數,以阻值構造函數不合時宜的類型轉換
(3)很簡單一個特性,第2部分再詳解
17.C++的const關鍵字
1、C語言中const用法回顧
(1)const變量,比宏定義的優勢是帶數據類型,可以讓編譯器幫我們做類型檢查
(2)const數組,和常量變量類似
(3)const指針,三種情況:const int *p, int * const p, const int const p;(看const後面是p代表指針指向的內容是const的,const後面的是p就代表p本身是const的)
2、C++中const新增用法
(1)const引用,主要用於函數傳參,限制函數內部對實參進行修改
// C++中更傾向於使用引用而不是指針
// 調用時,int i; func2(i);
int func2(const int &a)
{
if (a > 100)
return 0;
else
return -1;
}
(2)const成員函數,限制函數內部對類的成員變量的修改
#include <iostream>
using namespace std;
class A
{
public:
int i;
int func6(void) const ; // const成員,明確告知func6內部不會修改class A的成員變量的值
};
int A::func6(void) const
{
//this->i = 5; //編譯會報錯,const不允許修改成員變量的值
cout << "A::func6, i = " << this->i << endl;
}
int main(void)
{
A a;
a.func6();
return 0;
}
18.const有關的其他幾個關鍵字
1、mutable
(1)mutable用來突破const成員函數的限制,讓其可以修改特定的成員變量
(2)案例參考:mutable案例參考博客
2、constexpr
(1)用法如下:
constexpr int multiply (int x, int y)
{
return x * y;
}
const int val = multiply( 10, 10 ); // 將在編譯時計算
const int val = 100; //編譯器自動優化成
(2)本質上是讓程序利用編譯時的計算能力,增加運行時效率
(3)由C++11引入,但是實際有一些編譯器並不支持,需實際測試
3、C++20新引入的2個
(1) constinit
(2) consteval
19.模板編程的幾個關鍵字
1、模(mu)板編程初體驗
(1)template和typename
(2)模板實際上是一種抽象,C++的高級編程特性就是不斷向抽象化發展
#include <iostream>
using namespace std;
/*
// 寫一個函數add,完成2個數字的加和
// 假如有10種數據類型要考慮,那就要寫10個add的重載函數,非常低效
int add(int a, int b)
{
return (a + b);
}
double add(double a, double b)
{
return (a + b);
}
*/
// 自定義一個抽象類型,譬如命名爲X,編程的時候用X編程,X的具體類型在調用函數時
// 由實參的類型來確定
template <typename T>
T add(T a, T b)
{
return (a + b);
}
int main(void)
{
char i = 45, j = 6;
cout << "a + b = " << add(i, j) << endl;
return 0;
}
2、export
(1)用來在cpp文件中定義一個模板類或模板函數,而它的聲明在對應的h文件中
(2)export專用於模板,類似於extern之於簡單類型
(3)實際很多環境不支持,暫不必細究,看到代碼時能認出即可
3、requires
(1)C++20引入,用於表示模板的參數約束
(2)瞭解即可,暫時不用管
20.C++的異常處理機制
1、何爲異常處理
(1)異常exception,即運行時錯誤
(2)C中沒有異常機制,所以運行時遇到錯誤只能終止程序
(3)C++中新增了異常處理機制,允許程序在運行時攔截錯誤並處理,這樣程序就不用終止
(4)異常機制的一個典型案例就是:由用戶輸入2個數字然後相除中的除0異常
2、異常處理編程實踐
(1)try, catch, throw
#include <iostream>
using namespace std;
int main(void)
{
// 讓用戶輸入2個數,然後程序返回他的相除
cout << "please input 2 numbers" << endl;
int m, n;
cin >> m >> n;
// C++中用異常處理機制來處理
try
{
// try括號裏的代碼就是有可能觸發異常的代碼
if (n == 0)
throw ('A');
cout << "m / n = " << m/n << endl;
}
catch (int e) // catch的()裏寫上要抓取的異常類型
{
dosomething(); //處理異常
}
catch (double e) // catch的()裏寫上要抓取的異常類型
{
dosomething2(); //處理異常
}
/*
// C中我們這樣處理
if (n == 0)
{
cout << "0 not good" << endl;
return -1;
}
else
{
cout << "m / n = " << m/n << endl;
}
*/
cout << "---other code---" << endl;
return 0;
}
3、異常和函數
(2)throw一個異常後如果沒有catch會層層向外傳遞直到被catch爲止
(3)函數可以用throw列表來標識自己會拋出的異常
void func(void) throw(A, B, C); // 這種聲明就是告訴調用者func有可能拋出3種異常
4、標準庫中的exception類
(1)標準庫中定義的異常類及其派生類,很多內置代碼的錯誤會拋出這些異常
(2)譬如bad_typeid,使用 typeid 運算符時,如果其操作數是一個多態類的指針,而該指針的值爲 NULL,則會拋出此異常
(3)譬如bad_cast,用 dynamic_cast 進行從多態基類對象(或引用)到派生類的引用的強制類型轉換時,如果轉換是不安全的,則會拋出此異常
5、noexcept關鍵字
(1)throw(int, double, A, B, C)表示函數可能會拋出這5種類型的exception
(2)throw() 表示函數不會拋出任何異常
(3)C++11中引入noexcept關鍵字替代throw()表示函數不會拋出任何異常,也可以使用noexcept(bool)表示有無異常
(4)沒有throw列表的函數,表示函數可能會拋出任意類型的異常
21.剩餘關鍵字和總結
1、剩餘一些關鍵字
(1)線程相關:thread_local (C++11 起)
(2)import和module (C++20)
(3)協程相關:
co_await (C++20 起)
co_return (C++20 起)
co_yield (C++20 起)
(4)併發相關:synchronized (TM TS)
(5)反射相關:reflexpr (反射 TS)
(6)其他:
transaction_safe (TM TS)
transaction_safe_dynamic (TM TS)
atomic_cancel (TM TS)
atomic_commit (TM TS)
atomic_noexcept (TM TS)
2、總結
(1)C++關鍵字和複雜度遠超過C語言,語言特性較多
(2)面向對象編程相關特性是C++的基礎核心,佔比非常大
(3)模板泛型和抽象化編程是C++的重要特徵,甚至可以說是精髓所在
(4)和java、python相比,C++的語法細節過多,這也是C++較難學習的重要原因
(5)不要試圖去記,以理解爲主,配合代碼實驗去消化吸收,形成自己對C++的認知
(6)經典C++與C++11、14、17、20的差異其實就是相應增加的關鍵字帶來的新語言特性