C++知識點總結(其他語法2-模板, 類型轉換, C++11新特性)

模板(template)

泛型, 是一種將類型參數化以達到代碼複用的技術, C++中使用模板來實現泛型
模板的使用格式如下:
template <typename\class T>
typename和class是等價的
模板沒有被使用時, 是不會被實例化出來的.
例如:

template <class T>
T add(T a, T b) {
    return a + b;
}

template <class T, class A, class C>
C add(T a, A b) {
    return a + b;
}

int main() {
    add<int>(10, 20);
    add<double>(10.1, 2.2);
}

模版2-編譯細節

模板的聲明和實現如果分離到.h和.cpp中, 會導致鏈接錯誤.
編譯, 鏈接原理
1.頭文件不會參與編譯, 因爲頭文件是拿來被包含的
2.編譯器在編譯時, 會對每一個cpp文件單獨編譯, 有一個add函數時, 通常把函數聲明寫在頭文件add.h, 把函數實現
寫在add.cpp文件(要#include “add.h”), 在main.cpp裏面(#include “add.h”)即可, 這樣的話在main.cpp裏面有函數的聲明, 沒有函數的實現,
那麼當使用add函數時, 因爲有函數的聲明所以不會報錯, 但因爲是單獨編譯, 所以main.cpp找不到函數的定義,
所以彙編代碼是call <假的函數地址>, 在鏈接時, 才把假的函數地址修正成真的函數地址.
而如果有模板時, 因爲編譯add.cpp時, 不會生成具體的函數實現, 因爲是單獨編譯, 不知道main裏面的參數類型(沒有被使用, 就不會生成函數實現),
所以不會生成具體的實現.所以在鏈接時, 就無法修正call 的函數地址.所以會導致鏈接錯誤
所以在寫模板時, 不要把模版的聲明和實現分離, 要放在同一個.h文件中
一般將模板的聲明和實現統一放到一個.hpp文件(仍是頭文件, 只是語義好一點)中, 在直接#include “add.hpp”

類型轉換

C語言風格的類型轉換符
(type)expression
int a = 10;
double d = a; // 隱式轉換
C++中有4個類型轉換符
static_cast
dynamic_cast
reinterpret_cast
const_cast
cast是轉換的意思
使用格式:xx_cast(expression)

1.const_cast

一般用於去除const屬性, 將const轉換成非const
例如:

const Person *p1 = new Person();
// C++風格
Person *p2 = const_cast<Person *>(p1);
// C風格
Person *p3 = (Person *)p1;
// 這兩種寫法沒有任何區別, 只是不同語言的寫法而已

很多強制類型轉換隻是騙一下編譯器, 本質其實就是賦值
相當於Person *p2 = p1;

2.dynamic_cast

一般用於多態類型的轉換, 有運行時安全檢測.
多態類型: 能完成多態功能的那幾個類.
用法:

class Person {
    virtual void run() {}
};

class Student : public Person {

};
int main() {
    Person *p1 = new Person();
    Person *p2 = new Student();
    
    Student *stu1 = dynamic_cast<Student *>(p1);
    // 不安全, 因爲相當於
    // Studnet *stu1 = new Person();就成了用子類指針
    // 指向父類對象, 不安全, 因爲子類指針可以訪問的
    // 範圍超過父類對象所佔的內存.
    Student *stu2 = dynamic_cast<Student *>(p2);
    // 安全, 相當於Student *stu2 = new Student();
}

dynamic_cast 可以檢測到是否安全, 一旦檢測到不安全, 直接讓指針清空 = NULL, 變成空指針.

3.static_cast(瞭解, 開發中很少用)

1.對比dynamic_cast, 缺乏運行時安全檢測
2.不能交叉轉換(不是同一繼承體系的, 無法轉換)
3.常用於基本數據類型的轉換, 非const轉成const

int main() {
    int a = 10;
    double b = static_cast<double>(a);
    // 完全等價於double b = a;和double b = (double)a;
    
    Person *p1 = new Person();
    const Person *p2 = static_cast<const Person *>(p1);
    // 等價於    const Person *p2 = p1;
}

4.reinterpret_cast

1.屬於比較底層的強制轉換, 沒有任何類型檢查和格式轉換, 僅僅是簡單的二進制數據拷貝
2.語法限制:如果是不同類型之間的轉換, 需要用引用, 僅僅是語法糖
3.可以交叉轉換

int main() {
    int a = 10;
    double d = a;
    // 不是簡單的二進制數據拷貝, 而是轉換成浮點數的存儲方式
    double d = reinterpret_cast<double&>(a);
    // 沒有任何類型檢查, 僅僅是簡單的二進制數據拷貝.
    // 但double有8個字節, int有4個字節, 所以僅僅是
    // 將a的4個字節覆蓋掉double的4個字節, double剩下
    // 的4個字節不管. 如果在棧空間默認是cc
}

C++11新特性

1.auto
可以從初始化表達式中推斷出變量的類型, 大大簡化編程工作

int a = 10;
auto a = 10; // 發現右邊是整型, 所以a是int類型
auto str = "C++"; // const char *
auto p = new Person(); // Person*

屬於編譯器特性, 不影響最終的機器碼質量, 不影響運行效率
2.decltype
decl = declear type 聲明類型
可以獲取變量的類型
int a = 10;
decltype(a) b = 20; // 相當於int b = 20;
3.nullptr
nullptr == null pointer 空指針
以後凡是清空指針都用nullptr不用NULL, 因爲不專業
int *p = NULL
int *p1 = nullptr;
可以解決NULL二義性的問題
func(0);
func(nullptr);
func(NULL);
NULL -> #define NULL 0
4.快速遍歷

int array[] = {1, 2, 3, 4};
for (int item : array) {
    cout << item << endl;
}
// 將array裏面的元素挨個取出來賦值給item
// 等價於
for (int i = 0; i < 4; i++) {
    int item = array[i];
    cout << item << endl;
}

5.更加簡潔的初始化方式
int array[]{1, 2, 3 , 4};
完全等價於
int array[] = {1, 2, 3, 4};

6.Lambda表達式

Lambda表達式(未完)


其他C++系列文章:

C++知識點總結(基礎語法1-函數重載, 默認參數)
C++知識點總結(基礎語法2-內聯函數, const, 引用)
C++知識點總結(面向對象1-類和對象, this指針, 內存佈局)
C++知識點總結(面向對象2-構造函數, 初始化列表)

C++知識點總結(面向對象3-多態)

C++知識點總結(面向對象4-多繼承, 靜態成員static)
C++知識點總結(面向對象5-const成員, 拷貝構造函數)
C++知識點總結(面向對象6-隱式構造, 友元, 內部類, 局部類)
C++知識點總結(其他語法1-運算符重載)
C++知識點總結(其他語法2-模板, 類型轉換, C++11新特性)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章