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