1. 運算符重載 (operator overload)
運算符重載(操作符重載):可以爲運算符增加一些新的功能
運算符 + 不能爲左右倆個對象類型運算,運算符重載就是增加運算符兩邊的類型
1.1 使用運算符重載
運算符重載實現代碼
注意這裏使用友元和運算符重載沒有任何關係,僅爲課程的連貫性使用(完全可以將成員變量public,不使用友元)
#include <iostream>
using namespace std;
class Point{
friend Point operator+(Point , Point);
int m_x;
int m_y;
public:
Point(int x,int y) :m_x(x), m_y(y) {}
void display() {
cout << "(" << m_x << "," << m_y << ")" << endl;
}
};
Point operator+(Point p1, Point p2) {
return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}
int main() {
Point p1(10, 20);
Point p2(20, 30);
Point p3 = p1 + p2;
int a = 1 + 2;
p3.display();
cout << a << endl;
return 0;
}
輸出:
(30,50)
3
運算符重載 本質 就是函數的調用(換了一種寫法)
Point p3 = p1 + p2;
// 等價於
Point p3 = operator+(p1, p2);
Point p4 = p1 + p2 +p3;
// 等價於
Point p4 = operator+(operator+(p1, p2),p3);
1.2 優化運算符重載的參數
改造函數
由於使用對象類型爲參數,會差生很多不必要的中間對象,所以要是用 引用 &
由於參數可能傳入的類型爲 const,所以在參數類型定義時加上 const
改造後 operator+ 函數
Point operator+(const Point &p1, const Point &p2) {
return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}
1.3 將運算符重載改爲成員函數
#include <iostream>
using namespace std;
class Point{
friend Point operator+(const Point& , const Point& );
int m_x;
int m_y;
public:
Point(int x,int y) :m_x(x), m_y(y) {}
void display() {
cout << "(" << m_x << "," << m_y << ")" << endl;
}
Point operator+(const Point& p) {
return Point(m_x + p.m_x, m_y + p.m_y);
}
};
int main() {
Point p1(10, 20);
Point p2(20, 30);
Point p3 = p1 + p2;
p3.display();
return 0;
}
寫爲成員函數,則只需要傳入一個對象即可,簡化了操作
Point p3 = p1 + p2;
// 等價於
Point p3 = p1.operator+(p2);
1.4 運算符: + 與 - (保留運算符原始性質)
將數值返回給倆個數的和(臨時變量)沒有意義,編譯器報錯
但是目前的運算符重載不具備此特性
1.4.1 將重載運算符的返回值加上 const
#include <iostream>
using namespace std;
class Point{
friend Point operator+(const Point& , const Point& );
int m_x;
int m_y;
public:
Point(int x,int y) :m_x(x), m_y(y) {}
void display() {
cout << "(" << m_x << "," << m_y << ")" << endl;
}
const Point operator+(const Point& p) {
return Point(m_x + p.m_x, m_y + p.m_y);
}
const Point operator-(const Point& p) {
return Point(m_x - p.m_x, m_y - p.m_y);
}
};
int main() {
int a = 10;
int b = 20;
(a + b) = 30; // 報錯
Point p1(10, 20);
Point p2(20, 30);
(p1 + p2) = Point(1, 2); // 報錯
return 0;
}
1.42 將重載運算符改爲 const函數
const對象 不能調用 非const函數
關於const對象相關內容,詳見 C++挖掘程序本質(第二章C++面向對象-下) 中的第5小節
5. const成員
Point p4 = p1 + p2 + p3;
//等價於
p1.operator+(p2).operator+(p3);
正確代碼如下:
#include <iostream>
using namespace std;
class Point {
int m_x;
int m_y;
public:
Point(int x, int y) :m_x(x), m_y(y) {}
void display() {
cout << "(" << m_x << "," << m_y << ")" << endl;
}
const Point operator+(const Point& p) const {
return Point(m_x + p.m_x, m_y + p.m_y);
}
const Point operator-(const Point& p) const {
return Point(m_x - p.m_x, m_y - p.m_y);
}
};
int main() {
Point p1(10, 20);
Point p2(20, 30);
Point p3(30, 40);
Point p4 = p1 + p2 - p3;
p4.display();
//等價於
//p1.operator+(p2).operator+(p3);
return 0;
}
輸出:(0,10)
1.5 運算符: +=
首先來複習3個之前概念
- 引用&: Point &point 這裏的&point編譯器可以直接將point當做對象使用,如:
point.m_x
- 指針*: Point *point(const Point &point){ return *this} 這裏的 Point * 爲指針類型,所以返回如:
return this
- 返回值爲對象類型:爲了不多出中間對象,在對象的返回值中使用&返回對象的引用
在+=運算符中,如下代碼不會報錯,a+=b等價於 a=a+b
int a = 10;
int b = 20;
(a += b) = 2;
如下完整代碼:
#include <iostream>
using namespace std;
class Point {
int m_x;
int m_y;
public:
Point(int x, int y) :m_x(x), m_y(y) {}
void display() {
cout << "(" << m_x << "," << m_y << ")" << endl;
}
const Point operator+(const Point& p) const {
return Point(m_x + p.m_x, m_y + p.m_y);
}
const Point operator-(const Point& p) const {
return Point(m_x - p.m_x, m_y - p.m_y);
}
Point &operator+=(const Point& p) {
m_x = p.m_x;
m_y = p.m_y;
return *this;
}
};
int main() {
Point p1(10, 20);
Point p2(20, 30);
(p1 += p2) = Point(50,50);
p1.display();
int a = 10;
int b = 20;
(a += b) = 2;
return 0;
}
+= 函數 是不能用const 修飾的,因爲要執行修改、賦值倆個操作
1.6 運算符: == 與 !=
#include <iostream>
using namespace std;
class Point {
int m_x;
int m_y;
public:
Point(int x, int y) :m_x(x), m_y(y) {}
void display() {
cout << "(" << m_x << "," << m_y << ")" << endl;
}
const Point operator+(const Point& p) const {
return Point(m_x + p.m_x, m_y + p.m_y);
}
const Point operator-(const Point& p) const {
return Point(m_x - p.m_x, m_y - p.m_y);
}
Point &operator+=(const Point& p) {
m_x = p.m_x;
m_y = p.m_y;
return *this;
}
bool operator==(const Point& p) const{
return (m_x == p.m_x) && (m_y == p.m_y);
}
bool operator!=(const Point& p) const{
return (m_x != p.m_x) || (m_y != p.m_y);
}
};
int main() {
Point p1(10, 20);
Point p2(20, 30);
cout << (p1 == p2) << endl;
cout << (p1 != p2) << endl;
return 0;
}
輸出:
0
1
1.7 單目運算符: - 負號
1) 返回 const 對象類型
應修改爲:
返回 const 對象類型的函數
const Point operator-() {
return Point(m_x, m_y);
}
2) 返回 const 類型的函數
#include <iostream>
using namespace std;
class Point {
int m_x;
int m_y;
public:
Point(int x, int y) :m_x(x), m_y(y) {}
void display() {
cout << "(" << m_x << "," << m_y << ")" << endl;
}
const Point operator+(const Point& p) const {
return Point(m_x + p.m_x, m_y + p.m_y);
}
const Point operator-(const Point& p) const {
return Point(m_x - p.m_x, m_y - p.m_y);
}
Point &operator+=(const Point& p) {
m_x = p.m_x;
m_y = p.m_y;
return *this;
}
bool operator==(const Point& p) const{
return (m_x == p.m_x) && (m_y == p.m_y);
}
bool operator!=(const Point& p) const{
return (m_x != p.m_x) || (m_y != p.m_y);
}
const Point operator-() const{
return Point(-m_x, -m_y);
}
};
int main() {
Point p1(10, 20);
(-(-p1));
// 等價於
//p1.operator-().operator-();
return 0;
}
輸出:
(10,20)
(10,20)
(-10,-20)
1.8 單目運算符:++ 與 –
1.8.1 首先理解一下++a 與 a++
1)前置++ :++a
int a = 10;
int b = ++a + 5;
等價於
int a = 10;
a += 1;
int b = a + 5;
++a操作返回a,所以 ++a =20; 時可以的
2)後置++:a++
int a = 10;
int c = a++ + 5;
等價於
int a = 10;
int c = a + 5;
a += 1;
全部代碼
#include <iostream>
using namespace std;
class Point {
int m_x;
int m_y;
public:
Point(int x, int y) :m_x(x), m_y(y) {}
void display() {
cout << "(" << m_x << "," << m_y << ")" << endl;
}
const Point operator+(const Point& p) const {
return Point(m_x + p.m_x, m_y + p.m_y);
}
const Point operator-(const Point& p) const {
return Point(m_x - p.m_x, m_y - p.m_y);
}
Point &operator+=(const Point& p) {
m_x = p.m_x;
m_y = p.m_y;
return *this;
}
bool operator==(const Point& p) const{
return (m_x == p.m_x) && (m_y == p.m_y);
}
bool operator!=(const Point& p) const{
return (m_x != p.m_x) || (m_y != p.m_y);
}
const Point operator-() const{
return Point(-m_x, -m_y);
}
// 前置++
Point &operator++() {
m_x++;
m_y++;
return *this;
}
// 後置++ 參數傳入int爲語法糖
const Point operator++(int) {
Point old(m_x, m_y);
m_x++;
m_y++;
return old;
}
};
int main() {
Point p1(10, 20);
Point p2 = p1++ + Point(30, 40);
p1.display();
p2.display();
return 0;
}
輸出:
(11,21)
(40,60)
1.9 運算符:<<
#include <iostream>
using namespace std;
class Point {
friend ostream& operator<<(ostream& , const Point& );
int m_x;
int m_y;
public:
Point(int x, int y) :m_x(x), m_y(y) {}
void display() {
cout << "(" << m_x << "," << m_y << ")" << endl;
}
const Point operator+(const Point& p) const {
return Point(m_x + p.m_x, m_y + p.m_y);
}
const Point operator-(const Point& p) const {
return Point(m_x - p.m_x, m_y - p.m_y);
}
Point &operator+=(const Point& p) {
m_x = p.m_x;
m_y = p.m_y;
return *this;
}
bool operator==(const Point& p) const{
return (m_x == p.m_x) && (m_y == p.m_y);
}
bool operator!=(const Point& p) const{
return (m_x != p.m_x) || (m_y != p.m_y);
}
const Point operator-() const{
return Point(-m_x, -m_y);
}
// 前置++
Point &operator++() {
m_x++;
m_y++;
return *this;
}
// 後置++
const Point operator++(int) {
Point old(m_x, m_y);
m_x++;
m_y++;
return old;
}
};
ostream& operator<<(ostream& cout, const Point& point) {
cout << "(" << point.m_x << "," << point.m_y << ")";
return cout;
}
int main() {
Point p1(10, 20);
Point p2(20, 30);
cout << p1 << endl << p2 << endl << 1 << 2 << 3;
return 0;
}
輸出:
(10,20)
cout 不接受const返回的對象(C語言函數庫定義的),所以重寫<<運算符,參數不能傳const類型
1.10 運算符:>>
#include <iostream>
using namespace std;
class Point {
friend ostream& operator<<(ostream& , const Point& );
friend istream& operator>>(istream& , Point& );
int m_x;
int m_y;
public:
Point(int x, int y) :m_x(x), m_y(y) {}
void display() {
cout << "(" << m_x << "," << m_y << ")" << endl;
}
const Point operator+(const Point& p) const {
return Point(m_x + p.m_x, m_y + p.m_y);
}
const Point operator-(const Point& p) const {
return Point(m_x - p.m_x, m_y - p.m_y);
}
Point &operator+=(const Point& p) {
m_x = p.m_x;
m_y = p.m_y;
return *this;
}
bool operator==(const Point& p) const{
return (m_x == p.m_x) && (m_y == p.m_y);
}
bool operator!=(const Point& p) const{
return (m_x != p.m_x) || (m_y != p.m_y);
}
const Point operator-() const{
return Point(-m_x, -m_y);
}
// 前置++
Point &operator++() {
m_x++;
m_y++;
return *this;
}
// 後置++
const Point operator++(int) {
Point old(m_x, m_y);
m_x++;
m_y++;
return old;
}
};
ostream& operator<<(ostream& cout, const Point& point) {
cout << "(" << point.m_x << "," << point.m_y << ")";
return cout;
}
istream& operator>>(istream& cin, Point& point) {
cin >> point.m_x;
cin >> point.m_y;
return cin;
}
int main() {
Point p1(10, 20);
Point p2(20, 30);
cin >> p1 >> p2;
cout << p1 << p2 << endl;
return 0;
}
輸入:
11 22 33 44
輸出:
(11,22)(33,44)
1.11 重寫對象賦值 =
#include <iostream>
using namespace std;
class Point {
friend ostream& operator<<(ostream& , const Point& );
friend istream& operator>>(istream& , Point& );
int m_x;
int m_y;
public:
Point(int x, int y) :m_x(x), m_y(y) {}
void display() {
cout << "(" << m_x << "," << m_y << ")" << endl;
}
const Point operator+(const Point& p) const {
return Point(m_x + p.m_x, m_y + p.m_y);
}
const Point operator-(const Point& p) const {
return Point(m_x - p.m_x, m_y - p.m_y);
}
Point &operator+=(const Point& p) {
m_x = p.m_x;
m_y = p.m_y;
return *this;
}
bool operator==(const Point& p) const{
return (m_x == p.m_x) && (m_y == p.m_y);
}
bool operator!=(const Point& p) const{
return (m_x != p.m_x) || (m_y != p.m_y);
}
const Point operator-() const{
return Point(-m_x, -m_y);
}
// 前置++
Point &operator++() {
m_x++;
m_y++;
return *this;
}
// 後置++
const Point operator++(int) {
Point old(m_x, m_y);
m_x++;
m_y++;
return old;
}
};
ostream& operator<<(ostream& cout, const Point& point) {
cout << "(" << point.m_x << "," << point.m_y << ")";
return cout;
}
istream& operator>>(istream& cin, Point& point) {
cin >> point.m_x;
cin >> point.m_y;
return cin;
}
class Person {
int m_age;
int m_height;
public:
Person(int age = 0,int height =0) :m_age(age), m_height(height) {}
//重載 =
Person &operator=(const Person & person){
m_age = person.m_age;
return *this;
}
void display() {
cout << m_age << "," << m_height << endl;
}
};
int main() {
Person p1(10, 180);
Person p2(11, 175);
(p1 = p2) = Person(50, 20);
p1.display();
return 0;
}
1.12 對象賦值私有化,無法將對象賦值對象 p1 ≠ p2
class Person {
int m_age;
int m_height;
void operator=(const Person& person) {}
public:
Person(int age = 0,int height =0) :m_age(age), m_height(height) {}
void display() {
cout << m_age << "," << m_height << endl;
}
};
Person p1(10, 180);
Person p2(11, 175);
p1 = p2 // 報錯
重寫運算符=,後不能將person的一個對象賦值給另一個變量
1.13 調用父類的運算符重載函數
如果重載了運算符,想調用父類的重載操作,需要將父類的重載函數拿過來
#include <iostream>
using namespace std;
class Person {
public:
int m_age;
//重載 =
Person& operator=(const Person& person) {
m_age = person.m_age;
}
};
class Student :public Person {
public:
int m_score;
Student& operator=(const Student& student) {
Person::operator=(student);
m_score = student.m_score;
}
};
int main() {
Student stu1;
stu1.m_age = 20;
stu1.m_score = 100;
return 0;
}
1.14 仿函數(函數對象)
仿函數:將一個對象當做一個函數一樣來使用
#include <iostream>
using namespace std;
class Sum {
public:
int m_age;
int operator()(int a, int b) {
if (m_age) {
}
return a + b;
}
};
int main() {
Sum sum;
sum.m_age = 10;
cout << sum(10, 20) << endl;
return 0;
}
輸出:30
對比普通函數,它作爲對象可以保存狀態
1.15 C++中不能重載的運算符(5個)
C++中不能重載的運算符有5個,分別爲:“?:” “.” “::” “sizeof” “.*” 。
重載:讓操作符可以有新的語義,而不是更改語法,否則會引起混亂。
重載的部分規則:運算函數的參數至少有一個必須是類的對象或者是類的對象的引用。
下面來i解釋一下爲什麼這幾個運算符不能進行重載。
(1)?:
假設可以重載,那麼我們來看下列的代碼:
exp1 ? exp2 : exp3
該運算符的含義是執行exp2和exp3中的一個,假設重載了,就不可以保證執行一個還是兩個,還是都沒執行,該運算符的跳轉性質就不復存在了。所以,“?:”不能被重載。
(2).
假設可以重載,我們可以假設一種情況,創建一個對象,調用該對象的函數。
class Y{
public:
void fun();
};
class X{
public:
Y* p;
Y& operator.(){
return *p;
}
void fun();
}
void g(X& x){
x.fun();
}
這個例子中,x.fun()就不知道是調用哪一個fun函數了。
“.”運算符的含義是引用對象成員,然而被重載後就不能保證了,導致運算符意義的混淆。
(3)::
該運算符只是在編譯的時候域解析,而沒有運算參與。根據重載的規則,如果重載該運算符,就賦予了新的語義,可能會出現混淆。
(4)sizeof
不能被重載的原因主要是內部許多指針都依賴sizeof。
(5).*
引用指向類成員的指針
1.16 總結
有些運算符不可以被重載,比如
- 對象成員訪問運算符:.
- 域運算符:::
- 三目運算符:?:
- sizeof
有些運算符只能重載爲成員函數,比如
- 賦值運算符:=
- 下標運算符:[ ]
- 函數運算符:( )
- 指針訪問成員:->
2. 模板 (template)
泛型:是一種將類型參數化以達到代碼複用的技術,C++中使用模板來實現泛型
2.1 模板使用格式如下
- template <typename\class T>
- typename 和 class 是等價的
- 模板沒有被使用時,是不會被實例化出來的
#include <iostream>
using namespace std;
class Point {
friend ostream &operator<<(ostream&, const Point&);
int m_x;
int m_y;
public:
Point(int x, int y) :m_x(x), m_y(y) {}
Point operator+(const Point& point) {
return Point(m_x + point.m_x, m_y + point.m_y);
}
};
ostream &operator<<(ostream& cout,const Point& point) {
return cout << "(" << point.m_x << ", " << point.m_y << ")" << endl;
}
template<typename T> T add(T a, T b) {
return a + b;
}
int main() {
add(10, 20);
cout << add(10, 20) << endl;
cout << add(10.1, 20.2) << endl;
cout << add(Point(10, 20), Point(20, 30)) << endl;
return 0;
}
輸出:
30
30.3
(30, 50)
編譯器會將3個不同參數的調用函數,反彙編成不同的編碼
2.2 更舒適的寫法
將模板定義與函數換行去寫,更加易讀
template<typename T>
T add(T a, T b) {
return a + b;
}
2.3 不同類型參數的定義
定義不同的泛型類型,就可以在調用時傳入不同的參數類型
template<typename T,typename R, typename P>
T add(R a, P b) {
return a + b;
}
2.4 模板的聲明和實現如果分離到.h和.cpp中,會導致鏈接錯誤
2.4.1 正常函數的聲明和實現分離到.h和.cpp中
過程如下:
流程圖如下:
add.h文件
#pragma once
int add(int, int);
double add(double, double);
add.cpp文件
#include "add.h"
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
add.cpp文件
#include <iostream>
#include "add.h"
using namespace std;
int main() {
cout << add(10, 20) << endl;
cout << add(10.1, 20.2) << endl;
return 0;
}
2.4.2 模板的聲明和實現分離到.h和.cpp中
因爲模板文件編譯後的目標文件 .obj 爲空(沒有具體的實現方法)
所以在鏈接時會報錯
2.4.3 解決模板的聲明和實現分離,使用 .hpp 文件(不在分離)
add.hpp文件
#pragma once
template <class T>
T add(T a, T b) {
return a + b;
}
main.cpp文件
#include <iostream>
#include "add.hpp"
using namespace std;
int main() {
cout << add(10, 20) << endl;
cout << add(10.1, 20.2) << endl;
return 0;
}
2.5 友元函數使用泛型
在方法中加上 <>
2.6 模板(泛型)類
Array.hpp
#pragma once
#include <iostream>
using namespace std;
template<class T>
class Array {
friend ostream& operator<<<>(ostream& cout, const Array<T>& array);
//用於指向首元素
T* m_data;
//元素個數
int m_size;
//容量
int m_capacity;
public:
Array(int capacity = 0);
~Array();
void add(T value);
T get(int index);
int size();
T operator[](int index);
};
template<class T>
Array<T>::Array(int capacity ) { //默認參數只能寫在聲明中
m_capacity = (capacity > 0) ? capacity : 10;
// 申請堆空間
m_data = new T[m_capacity];
}
template<class T>
Array<T>::~Array() {
if (m_data == NULL) return;
delete[] m_data;
}
template<class T>
void Array<T>::add(T value) {
if (m_size == m_capacity) {
cout << "空間不夠" << endl;
return;
}
m_data[m_size++] = value;
}
template<class T>
T Array<T>::get(int index) {
if (index < 0 || index >= m_size) {
throw "數組下標越界";
}
return m_data[index];
}
template<class T>
int Array<T>::size() {
return m_size;
}
template<class T>
T Array<T>::operator[](int index) {
return get(index);
}
template<class T>
ostream &operator<<<>(ostream &cout, const Array<T>& array) {
cout << "[";
for (int i = 0; i < array.m_size; i++) {
if (i != 0) {
cout << ", ";
}
cout << array.m_data[i];
}
return cout << "]";
}
main.cpp
#include <iostream>
#include "Array.hpp"
using namespace std;
int main() {
Array<int> array;
array.add(10);
array.add(20);
array.add(30);
cout << array << endl;
return 0;
}
輸出:[10, 20, 30]
3. 類型轉換
3.1 C語言風格的類型轉換
(type)expression
type(expression)
#include <iostream>
using namespace std;
int main() {
int a = 10;
//隱式轉換
double d = (double)a;
double d2 = double(a);
return 0;
}
3.2 C++中有4個類型轉換符
3.2.1 static_cast
1)對比dynamic_cast,缺乏運行時安全監測
2)不能交叉轉換(不是同一繼承體系的、沒有任何聯繫的,無法轉換)
3)常用語基本數據類型的轉換、非 const 轉成 const
int a = 10;
double d = static_cast<double>(a);
Person* p1 = new Person();
const Person* p2 = p1;
4)使用範圍廣
3.2.2 dynamic_cast ☆
一般用於多態類型轉換,有運行時安全監測
#include <iostream>
using namespace std;
class Person {
virtual void run() {}
};
class Student :public Person {
};
class Car {
};
void const_cast1() {
const Person* p1 = new Person();
Person* p2 = const_cast<Person*>(p1);
Person* p3 = (Person*)p1;
}
int main() {
Person* p1 = new Person();
Person* p2 = new Student();
cout << "p1 =" << p1 << endl;
cout << "p2 =" << p2 << endl;
Student *stu1 = dynamic_cast<Student*>(p1);// 不安全 子->父
Student* stu2 = dynamic_cast<Student*>(p2);// 安全
cout << "stu1 =" << stu1 << endl;
cout << "stu2 =" << stu2 << endl;
return 0;
}
輸出:
p1 =0138BD70
p2 =01380570
stu1 =00000000
stu2 =01380570
當監測到不安全時,會將地址置爲NULL
Student stu1 = dynamic_cast<Student>(p1);
等價於
Student *stu1 = NULL;
3.2.3 reinterpret_cast ☆
屬於比較底層的強制轉換,沒有任何類型檢查和格式轉換,僅僅是簡單的二進制數據拷貝
說明一個問題:int 轉 double 的問題
// 0A 00 00 00
int a = 10;
// 0A 00 00 00 00 00 24 40
double d = a;
// 0A 00 00 00 cc cc cc cc
double d = reinterpret_cast<double&>(a);
使用
int* p = reinterpret_cast<int*>(0x100);
int a = reinterpret_cast<int>(p);
3.2.4 const_cast
一般用於去除const屬性,將const轉換成非const
#include <iostream>
using namespace std;
class Person {
};
int main() {
const Person* p1 = new Person();
Person* p2 = const_cast<Person*>(p1);// C++寫法
Person* p3 = (Person*)p1;// C寫法
return 0;
}
2中寫法的代碼,反彙編是一樣的
4. C++11新特性
C++標準的發展
4.1 auto
可以從初始化表達式中推斷出變量的類型,大大簡化編程工作
屬於編譯器特性,不影響最終的機器碼質量,不影響運行效率
#include <iostream>
using namespace std;
class Person {
public:
void run() {
}
};
int main() {
auto a = 10; // int
auto str = "C++"; // const char *
auto p = new Person(); // Person *
p->run();
}
4.2 decltype
可以獲取變量的類型
#include <iostream>
using namespace std;
class Person {
int main() {
int a = 10;
decltype(a) b = 20; //int
return 0;
}
4.3 nullptr
可以解決NULL的二義性問題
不建議再去用NULL去聲明一個空指針
#include <iostream>
using namespace std;
void func(int v) {
cout << "func(int v) - " << v << endl;
}
void func(int *v) {
cout << "func(int *) - " << v << endl;
}
int main() {
int a = NULL;
func(a);
return 0;
}
輸出:func(int v) - 0
NULL 的底層實際爲一個宏的定義,就是:0
#include <iostream>
using namespace std;
void func(int v) {
cout << "func(int v) - " << v << endl;
}
void func(int *v) {
cout << "func(int *) - " << v << endl;
}
int main() {
int a = NULL;
func(a);
func(nullptr);
cout << (NULL == nullptr) << endl;
return 0;
}
輸出:
func(int v) - 0
func(int *) - 00000000
1
4.4 數組快速遍歷、簡潔初始化
#include <iostream>
using namespace std;
int main() {
int array[] = { 11,22,33,44,55 };
for (int item : array) {
cout << item << endl;
}
return 0;
}
輸出:
11
22
33
44
55
更加簡潔的初始化方式
int array[] { 11,22,33,44,55 };
4.5 Lambda 表達式
最簡單的 Lambda 表達式
#include <iostream>
using namespace std;
int main() {
[] {
cout << "func()" << endl;
};
return 0;
}
4.5.1 有點類似於JavaScript中的閉包、iOS中的block,本質就是函數
用Lambda來實現,將一個函數放入另外一個函數體中
如下代碼:定義完直接調用
#include <iostream>
using namespace std;
void func() {
cout << "func()" << endl;
}
int main() {
([] {
cout << "func()" << endl;
})();
return 0;
}
4.5.2 完成結構
[capture list] (params list) mutable exception-> return type { function body }
- capture list:捕獲外部變量列表 [ ]爲Lambda標誌
- params list:形參列表,不能使用默認參數,不能省略參數名
- mutable :用來說明是否可以修改捕獲的變量
- exception:異常設定
- return type:返回值類型
- function body:函數體
4.5.3 有時可以省略部分結構
- [capture list] (params list) -> return type { function body }
- [capture list] (params list) {function body}
- [capture list] {function body}
定義一個最簡單的 Lambda 表達式
#include <iostream>
using namespace std;
void func() {
cout << "func()" << endl;
}
int main() {
void (*q)() = [] {
cout << "func() q" << endl;
};
// 等價於
auto p = [] {
cout << "func() p" << endl;
};
p();
q();
return 0;
}
Lambda 定義一個加法
#include <iostream>
using namespace std;
int main() {
auto p = [](int a, int b)->int {
return a + b;
};
cout << p(10, 20) << endl;
return 0;
}
Lambda 的加、減、乘、除
#include <iostream>
using namespace std;
// int (*func)(int, int) 傳入函數指針
int exec(int v1, int v2, int (*func)(int, int)) {
return func(v1, v2);
}
int main() {
// 加
cout << exec(10, 20, [](int v1, int v2){
return v1 + v2;
}) << endl;
// 減
cout << exec(10, 20, [](int v1, int v2) {
return v1 - v2;
}) << endl;
// 乘
cout << exec(10, 20, [](int v1, int v2) {
return v1 * v2;
}) << endl;
// 除
cout << exec(10, 20, [](int v1, int v2) {
return v1 / v2;
}) << endl;
cout << p(10, 20) << endl;*/
return 0;
}
4.5.4 外部變量捕獲、匿名(隱式)捕獲 [=] [&]
外部變量捕獲
#include <iostream>
using namespace std;
int main() {
int a = 10;
int b = 20;
auto func = [a, b] {
cout << a << endl;
cout << b << endl;
};
func();
return 0;
}
匿名捕獲 [=](默認)
#include <iostream>
using namespace std;
int main() {
int a = 10;
int b = 20;
auto func = [=] {
cout << a << endl;
cout << b << endl;
};
func();
return 0;
}
匿名捕獲 [&](默認)
#include <iostream>
using namespace std;
int main() {
int a = 10;
int b = 20;
auto func = [&] {
cout << a << endl;
cout << b << endl;
};
func();
return 0;
}
4.5.5 值捕獲(默認)、地址捕獲
值捕獲(默認)
#include <iostream>
using namespace std;
int main() {
int a = 10;
auto func = [a] {
cout << a << endl;
};
a = 20;
func();
return 0;
}
輸出:10
地址捕獲
#include <iostream>
using namespace std;
int main() {
int a = 10;
auto func = [&a] {
cout << a << endl;
};
a = 20;
func();
return 0;
}
輸出:20
4.5.6 (默認)捕獲的值無法在Lambda內修改
無法修改默認捕獲的值
需要改爲地址捕獲方式
#include <iostream>
using namespace std;
int main() {
int a = 10;
auto func = [&a] {
a++;
};
func();
cout << a << endl;
return 0;
}
輸出:11
4.5.7 mutable
#include <iostream>
using namespace std;
int main() {
int a = 10;
auto func = [a] () mutable {
a++;
// 等價於
/*int b = a;
b++;*/
cout << "lambda = " << a << endl;
};
func();
cout << a << endl;
return 0;
}
輸出:
lambda = 11
10
4.6 C++ 14 新特性
4.7 C++ 17 新特性
在VS中的設置
5. 錯誤
5.1 編程過程中的常見錯誤類型
5.1.1 語法錯誤
5.1.2 邏輯錯誤
5.1.3 異常
異常是一種在程序運行過程中可能會發生的錯誤(比如內存不夠)
5.2 異常
異常沒有被處理,會導致程序終止
捕獲異常
捕獲異常,終止異常
#include <iostream>
using namespace std;
int main() {
cout << 1 << endl;
for (int i = 0; i < 999; i++) {
try {
int* p = new int[999999];
}
catch (...) {
cout << "產生異常了:內存不夠用" << endl;
break;
}
}
cout << 2 << endl;
return 0;
}
輸出:
1
產生異常了:內存不夠用
2
5.2.1 異常捕獲
如果不用 try{ … } catch(…) { … } 去捕獲異常,後面的代碼都無法執行
5.2.2 無法捕獲的異常(需要人爲控制)
下面這段代碼在一些比較老的編輯器中,無法捕獲異常,程序只是閃退(vs2019 環境中可以捕獲異常)
#include <iostream>
using namespace std;
int divide(int v1, int v2) {
return v1 / v2;
}
int main() {
cout << 1 << endl;
try {
int a = 10;
int b = 0;
cout << divide(a, b) << endl;
}
catch (...) {
cout << "產生異常" << endl;
}
cout << 2 << endl;
return 0;
}
編程人員,需要有自己預知異常、處理異常的能力
#include <iostream>
using namespace std;
int divide(int v1, int v2) {
if (v2 == 0) {
//異常-後面代碼都不會執行
throw "不能除以0";
}
return v1 / v2;
}
int main() {
cout << 1 << endl;
try {
int a = 10;
int b = 0;
cout << divide(a, b) << endl;
}
catch (const char* exception) {
cout << "產生異常:" << exception << endl;
}
cout << 2 << endl;
return 0;
}
輸出:
1
產生異常:不能除以0
2
5.2.3 throw
1) throw 的數據類型 和 catch() 捕獲的數據類型必須嚴格一致的
不能再catch中使用 auto
2) throw異常後,會在當前函數中查找匹配的catch,找不到就終止當前函數代碼,去上一層函數中查找。如果最終都找不到匹配的catch,整個程序就會終止
#include <iostream>
using namespace std;
void func1() {
cout << "func1-begin" << endl;
throw 666;
cout << "func1-end" << endl;
}
void func2() {
cout << "func2-begin" << endl;
func1();
cout << "func2-end" << endl;
}
int main() {
cout << "main-begin" << endl;
func2();
cout << "main-end" << endl;
return 0;
}
輸出:
main-begin
func2-begin
func1-begin
將 func1() 函數的異常 處理後
#include <iostream>
using namespace std;
void func1() {
cout << "func1-begin" << endl;
try {
throw 666;
}catch (int exception) {
cout << "產生異常了(int):"<< exception << endl;
}
cout << "func1-end" << endl;
}
void func2() {
cout << "func2-begin" << endl;
func1();
cout << "func2-end" << endl;
}
int main() {
cout << "main-begin" << endl;
func2();
cout << "main-end" << endl;
return 0;
}
輸出:
main-begin
func2-begin
func1-begin
產生異常了(int):666
func1-end
func2-end
main-end
5.2.4 異常的拋出聲明
爲了增強可讀性和方便團隊協作,如果函數內部可能會拋出異常,建議函數聲明一下異常類型
5.2.5 自定義異常類型
#include <iostream>
using namespace std;
class Exception {
public:
virtual const char* what() = 0;
};
class DivideException:public Exception {
public:
const char* what() {
return "不能除以0";
}
};
class AddException :public Exception {
public:
const char* what() {
return "加法有問題";
}
};
int divide(int v1, int v2) {
if (v2 == 0) {
//拋出異常
throw DivideException();
}
return v1 / v2;
}
int main() {
cout << 1 << endl;
try {
int a = 10;
int b = 0;
cout << divide(a, b) << endl;
}catch (DivideException &exception) {
cout << "產生異常了(DivideException):" << exception.what() << endl;
}
catch (AddException& exception) {
cout << "產生異常了(AddException):" << exception.what() << endl;
}
cout << 2 << endl;
return 0;
}
輸出:
1
產生異常了(DivideException):不能除以0
2
5.2.6 攔截所有類型的異常 …
使用三個點 … 攔截所有類型異常
5.2.7 攔截所有類型的異常 …
5.2.8 標準異常(std)
#include <iostream>
using namespace std;
int main() {
cout << 1 << endl;
for (int i = 0; i < 9999; i++) {
try{
int* p = new int[999999];
}catch (std::bad_alloc exception){
cout << "產生異常了:內存不夠用 - "<< exception.what() << endl;
break;
}
}
cout << 2 << endl;
return 0;
}
輸出:
1
產生異常了:內存不夠用 - bad allocation
2
6. 智能指針(Smart Pointer)
6.1 傳統指針存在的問題
- 需要手動管理內存
- 容易發生內存泄露(忘記釋放、出現異常等)
- 釋放之後產生野指針(堆空間釋放之後,再調用指針)
如下代碼:由於異常不能執行 delete p; 代碼,所以導致內存泄露
#include <iostream>
using namespace std;
void test() {
throw 66;
}
int main() {
try {
int* p = new int();
test();
delete p;
} catch (...) {
}
return 0;
}
回收堆空間,還需要清空指針
#include <iostream>
using namespace std;
int main() {
int* p = new int();
delete p;
p = nullptr;
return 0;
}
6.2 智能指針就是爲了解決傳統指針存在的問題
智能指針不能隱式去調用
6.2.1 auto_ptr
屬於C++98標準,在C++11中已經不推薦使用(有缺陷,比如不能用於數組)
auto_ptr : 不支持釋放數組
auto_ptr p(new Person[5]); 無法釋放數組的類型
1)如下代碼:
#include <iostream>
using namespace std;
class Person {
int m_age;
public:
Person(int age):m_age(age) {
cout << "Person()" << endl;
}
~Person() {
cout << "~Person()" << endl;
}
void run() {
cout << "run()" << m_age << endl;
}
};
void test() {
//可以理解爲:智能指針p指向了堆空間的Person
auto_ptr<Person> p(new Person(20));
//智能指針不允許隱式調用
//auto_ptr<Person> p = new Person();
p->run();
}
int main() {
test();
return 0;
}
輸出:
Person()
run()20
~Person()
如上代碼:如果使用傳統指針,沒有 delete p; 就不會調用析構函數,而只只能指針無需delete
2)如下代碼:
#include <iostream>
using namespace std;
class Person {
int m_age;
public:
Person(int age):m_age(age) {
cout << "Person()" << endl;
}
~Person() {
cout << "~Person()" << endl;
}
void run() {
cout << "run()" << m_age << endl;
}
};
void test() {
cout << 1 << endl;
{
Person person(20);
auto_ptr<Person> p(&person); //因爲是智能指針,所以傳入的是地址
p->run();
}
cout << 2 << endl;
}
int main() {
test();
return 0;
}
VS2019程序卡死,報錯。由於多次free釋放
6.2.2 shared_ptr
屬於C++標準
6.2.3 unique_ptr
屬於C++標準
6.3 智能指針的簡單自實現
#include <iostream>
using namespace std;
class Person {
int m_age;
public:
Person(int age):m_age(age) {
cout << "Person()" << endl;
}
~Person() {
cout << "~Person()" << endl;
}
void run() {
cout << "run()" << m_age << endl;
}
};
template<class T>
class SmartPointer {
private:
T* m_obj;
public:
SmartPointer(T* obj) :m_obj(obj) {}
~SmartPointer() {
if (m_obj == nullptr)return;
delete m_obj;
}
//重寫運算符 ->
T *operator->() {
return m_obj;
}
};
int main() {
cout << 1 << endl;
{
SmartPointer<Person> p(new Person(20));
p->run();
}
cout << 2 << endl;
return 0;
}
輸出:
1
Person()
run()20
~Person()
2
6.4 shared_ptr 智能指針 (強引用)
shared_ptr的簡單使用
#include <iostream>
using namespace std;
class Person {
int m_age;
public:
Person( ) {
cout << "Person()" << endl;
}
Person(int age) :m_age(age) {
cout << "Person()" << endl;
}
~Person() {
cout << "~Person()" << endl;
}
void run() {
cout << "run()" << m_age << endl;
}
};
int main() {
cout << 1 << endl;
{
shared_ptr<Person> p(new Person(20));
}
cout << 2 << endl;
return 0;
}
輸出:
1
Person()
~Person()
2
6.4.1 shared_ptr的原理
- 一個shared_ptr會對一個對象產生強引用(strong reference)
- 每個對象都有個與之對應的強引用計數,記錄着當前對象被多少個shared_ptr強引用着,可以通過shared_ptr的use_count函數獲得強引用計數
- 當有一個新的shared_ptr指向對象時,對象的強引用計數就會+1
- 當有一個shared_ptr銷燬時(比如作用域結束),對象的強引用計數就會-1
- 當一個對象的強引用計數爲0時(沒有任何shared_ptr指向對象時),對象就會自動銷燬(析構)
1)shared_ptr 的 use_count 用法
#include <iostream>
using namespace std;
class Person {
int m_age;
public:
Person( ) {
cout << "Person()" << endl;
}
Person(int age) :m_age(age) {
cout << "Person()" << endl;
}
~Person() {
cout << "~Person()" << endl;
}
void run() {
cout << "run()" << m_age << endl;
}
};
int main() {
{
shared_ptr<Person> p4;
{
shared_ptr<Person> p1(new Person(10));
cout << p1.use_count() << endl;
shared_ptr<Person> p2 = p1;
cout << p1.use_count() << endl;
//shared_ptr<Person> p2(p1);
shared_ptr<Person> p3 = p2;
cout << p1.use_count() << endl;
p4 = p3;
cout << p1.use_count() << endl;
}
}
return 0;
}
輸出:
Person()
1
2
3
4
~Person()
如上面第5點
#include <iostream>
using namespace std;
class Person {
int m_age;
public:
Person( ) {
cout << "Person()" << endl;
}
Person(int age) :m_age(age) {
cout << "Person()" << endl;
}
~Person() {
cout << "~Person()" << endl;
}
void run() {
cout << "run()" << m_age << endl;
}
};
int main() {
{
Person* p = new Person();
{
shared_ptr<Person>p1(p);
}
{
shared_ptr<Person>p2(p);
}
}
return 0;
}
輸出:
Person()
~Person()
~Person()
並且報錯,因爲多次析構
6.4.2 shared_ptr中數組對象,應該將泛型的類型聲明爲數組
#include <iostream>
using namespace std;
class Person {
int m_age;
public:
Person( ) {
cout << "Person()" << endl;
}
Person(int age) :m_age(age) {
cout << "Person()" << endl;
}
~Person() {
cout << "~Person()" << endl;
}
void run() {
cout << "run()" << m_age << endl;
}
};
int main() {
cout << 1 << endl;
{
shared_ptr<Person[]> p(new Person[5]);
}
cout << 2 << endl;
return 0;
}
輸出:
1
Person()
Person()
Person()
Person()
Person()
~Person()
~Person()
~Person()
~Person()
~Person()
2
6.4.3 shared_ptr 的設計理念
多個shared_ptr可以指向同一個對象,當最後一個shared_ptr在作用域範圍內結束時,對象纔會被自動釋放
#include <iostream>
using namespace std;
class Person {
int m_age;
public:
Person( ) {
cout << "Person()" << endl;
}
Person(int age) :m_age(age) {
cout << "Person()" << endl;
}
~Person() {
cout << "~Person()" << endl;
}
void run() {
cout << "run()" << m_age << endl;
}
};
int main() {
cout << 1 << endl;
{
shared_ptr<Person> p4;
{
shared_ptr<Person> p1(new Person(10));
shared_ptr<Person> p2 = p1;
shared_ptr<Person> p3 = p2;
p4 = p3;
}
cout << 2 << endl;
}
cout << 3 << endl;
return 0;
}
輸出:
1
Person()
2
~Person()
3
可以通過一個已經存在的智能指針初始化一個新的智能指針
shared_ptr<Person> p1(new Person(10));
shared_ptr<Person> p2 = p1;
// 等價於
shared_ptr<Person> p2(p1);
6.4.4 shared_ptr 的循環引用
循環應用代碼
#include <iostream>
using namespace std;
class Car;
class Person {
public:
shared_ptr<Car> m_car;
Person() {
cout << "Person()" << endl;
}
~Person() {
cout << "~Person()" << endl;
}
};
class Car {
public:
shared_ptr<Person> m_person;
Car() {
cout << "Car()" << endl;
}
~Car() {
cout << "~Car()" << endl;
}
};
int main() {
{
shared_ptr<Person> person(new Person());
shared_ptr<Car> car(new Car());
person->m_car = car;
car->m_person = person;
}
return 0;
}
輸出:
Person()
Car()
爲什麼會沒有輸出析構函數? 如下圖
6.5 weak_ptr 智能指針 (弱引用)
weak_ptr 會對一個對象產生弱引用
6.5.1 weak_ptr可以指向對象解決shared_ptr的循環引用問題
#include <iostream>
using namespace std;
class Car;
class Person {
public:
shared_ptr<Car> m_car;
Person() {
cout << "Person()" << endl;
}
~Person() {
cout << "~Person()" << endl;
}
};
class Car {
public:
weak_ptr<Person> m_person;
Car() {
cout << "Car()" << endl;
}
~Car() {
cout << "~Car()" << endl;
}
};
int main() {
{
shared_ptr<Person> person(new Person());
shared_ptr<Car> car(new Car());
person->m_car = car;
car->m_person = person;
}
return 0;
}
輸出:
Person()
Car()
~Person()
~Car()
將其中一個強引用改爲弱引用,就可以解決循環引用的問題
6.5 unique_ptr 智能指針 (強引用)
unique_ptr也會對一個對象產生強引用,它可以確保同一時間只有1個指針指向對象
當unique_ptr銷燬時(作用域結束時),其指向的對象也就自動銷燬了
可以使用std::move函數轉移unique_ptr的所有權
#include <iostream>
using namespace std;
class Car;
class Person {
public:
Person() {
cout << "Person()" << endl;
}
~Person() {
cout << "~Person()" << endl;
}
};
int main() {
unique_ptr<Person> p0(new Person());
{
unique_ptr<Person> p1(new Person());
p0 = std::move(p1);
}
return 0;
}