C++挖掘程序本質(第三章C++其他語法-終)李明傑-M了個J 配套教材

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個之前概念

  1. 引用&: Point &point 這裏的&point編譯器可以直接將point當做對象使用,如:point.m_x
  2. 指針*: Point *point(const Point &point){ return *this} 這裏的 Point * 爲指針類型,所以返回如:return this
  3. 返回值爲對象類型:爲了不多出中間對象,在對象的返回值中使用&返回對象的引用

在+=運算符中,如下代碼不會報錯,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 總結

有些運算符不可以被重載,比如

  1. 對象成員訪問運算符:.
  2. 域運算符:::
  3. 三目運算符:?:
  4. sizeof

有些運算符只能重載爲成員函數,比如

  1. 賦值運算符:=
  2. 下標運算符:[ ]
  3. 函數運算符:( )
  4. 指針訪問成員:->

2. 模板 (template)

泛型:是一種將類型參數化以達到代碼複用的技術,C++中使用模板來實現泛型

2.1 模板使用格式如下

  1. template <typename\class T>
  2. typename 和 class 是等價的
  3. 模板沒有被使用時,是不會被實例化出來的
#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 }

  1. capture list:捕獲外部變量列表 [ ]爲Lambda標誌
  2. params list:形參列表,不能使用默認參數,不能省略參數名
  3. mutable :用來說明是否可以修改捕獲的變量
  4. exception:異常設定
  5. return type:返回值類型
  6. function body:函數體
4.5.3 有時可以省略部分結構
  1. [capture list] (params list) -> return type { function body }
  2. [capture list] (params list) {function body}
  3. [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

throw出int,catch必須也是int

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 傳統指針存在的問題

  1. 需要手動管理內存
  2. 容易發生內存泄露(忘記釋放、出現異常等)
  3. 釋放之後產生野指針(堆空間釋放之後,再調用指針)

如下代碼:由於異常不能執行 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的原理
  1. 一個shared_ptr會對一個對象產生強引用(strong reference)
  2. 每個對象都有個與之對應的強引用計數,記錄着當前對象被多少個shared_ptr強引用着,可以通過shared_ptruse_count函數獲得強引用計數
  3. 當有一個新的shared_ptr指向對象時,對象的強引用計數就會+1
  4. 當有一個shared_ptr銷燬時(比如作用域結束),對象的強引用計數就會-1
  5. 當一個對象的強引用計數爲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;
}

6.6 總結

6.6.1 如果想多個指針指向同一個對象使用 shared_ptr
6.6.2 如果想一個指針指向 一個對象使用 unique_ptr
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章