20230520
盧冠綸 @ Sprout 2023
實作動態陣列
大綱
上半堂: 下半堂:
0. 動態陣列的概念 4. 實作動態陣列
1. (複習) template
2. (複習) class
3. new / delete
不定期出現:
5. 一些爛爛的冷笑話
提醒
這堂課下半堂會讓大家從無到有,從開始到放棄,動手寫出一個動態陣列。
上半堂的部分,請一定要弄清楚概念!
註:冷笑話會不定期出現,但不會放在投影片裡。
動態陣列概念介紹
今天的課程是要幹嘛?
不知道大家是否有這種經驗...
題目:測資大小在 1000 以下。
我:開個大小為 1000 的陣列。
題目:測資大小在 1000000 以下。
我:開個大小為 1000000 的陣列。
idea
題目:測資大小在 1000000000 以下。
我:開個大小為 1000000000 的陣列。
程式:segmentation fault !!!
題目:測資大小未知。
我:
idea
此時,普通的陣列可能不再能滿足你的需求。
我們需要「動態陣列」!
動態陣列
動態陣列在做的事情:
你需要多少空間,就開多少空間給你,
你不需要的空間,就把它還回去。
動態陣列的優點:
優缺點
- 有多少元素才用多少空間
- 可以超大 (1,0000,0000 以上也不會爆)
動態陣列的缺點:
- 跑得比較慢(等等告訴大家為什麼)
今天要讓大家動手,運用至今為止所學,
從零「設計」出一個動態陣列。
課程目標
- 支援不同變數型態
- 在陣列最後追加一個元素 / 移除一個元素
- 存取陣列的第 i 個元素
- 求出陣列現在有幾個元素
這個動態陣列會有以下功能:
有用過 STL 的 vector 嗎?(沒的話,下週會教)
今天就是要告訴你,vector 是怎麼實作出來的哦!!
小補充
(複習) template
讓你把一個函式套用多種型態的好東西
當講師叫你寫一個函式,來把兩個 int 相加:
idea
int add(int a, int b){
return a+b;
}
簡單!
結果講師又跟你說,請新增 double 加法的功能。
idea
double add(double a, double b){
return a+b;
}
把 int 的程式複製貼上改成 double,依舊簡單!
idea
這時台上這位超機車的講師又跟你說,
再新增 float、long long int、string ... 的加法功能。
於是你就......複製貼上,複製貼上,複製貼上,...
總算完成了講師的要求。
idea
機車的講師:
欸抱歉我搞錯了,我希望你們做的是「相減」,
不是「相加」啦!麻煩幫我全部重新修改一下!
你:
template
只要學會 template,就算遇到再機車的講師也不怕!
template <typename T>
T add(T a, T b){
return a+b;
}
用上面這個函式,就能夠應付以下所有情況:
std::cout << add<int>(9,9) << std::endl;
std::cout << add<double>(4.5,9.1) << std::endl;
std::cout << add<char>('A',' ') << std::endl;
std::string a = "Hello", b = "World";
std::cout << add<std::string>(a,b) << std::endl;
template
當然,typename 可以不只一個。
template <typename T1, typename T2>
T1 add(T2 a, T2 b){
return a+b;
}
你就可以餵很多奇怪的東西給這個函式 (X
std::cout << add<int, int>(-1,-1) << std::endl;
std::cout << add<unsigned int, int>(-1,-1) << std::endl;
std::cout << add<int, double>(2.1,2.1) << std::endl;
std::cout << add<char, int>(65,1) << std::endl;
template
甚至,struct 或 class 也可以使用 template。
#include <iostream>
template<typename T1, typename T2>
struct pair {
T1 data1;
T2 data2;
};
int main(){
pair<int, double> a;
a.data1 = 3;
a.data2 = 5.6;
pair<char, bool> b;
b.data1 = 'n';
b.data2 = false;
}
(複習)class 1
語法、public / private
何謂類別 (class)
類別 (class) 是將資料以及函數組織在一起的方法。
class 和 struct 的差別,在於 struct 比較偏「資料導向」,而 class 比較偏「物件導向」。
struct 語法
今天,如果我想用 C++ 存資芽二階每個同學的資料。
只是想記資料的話,我可能會想用 struct 來存。
struct Student{
int homework; // 作業成績
int project; // 階段考成績
int attend; // 出席成績
};
class 語法
但是,class 除了資料 (data member) 之外,
還可以放一些函數 (member function),讓我們可以對這個 class 做一些操作。
class Student{
double homework; // 作業成績
double project; // 階段考成績
double attend; // 出席成績
double FinalScore(){ // 總成績
return 0.7 * homework + 0.2 * project + 0.1 * attend;
}
void TellJoke(){ // 課堂上說笑話的話出席分加 50 分
attend += 50;
}
};
public / private
在 class 的所有 member 中,分為 public 和 private。
public:所有人、所有地方都可以存取。
private:只有這個 class 的 member function 可以存取。
重要:class 的成員都預設為 private。
public / private
所以,如果程式這樣寫的話會出錯。
#include <iostream>
class Student{
double homework; // 作業成績
double project; // 階段考成績
double attend; // 出席成績
};
int main(){
Student a;
a.homework = 100;
}
public / private
應該要這樣寫。
#include <iostream>
class Student{
public:
double homework; // 作業成績
double project; // 階段考成績
double attend; // 出席成績
};
int main(){
Student a;
a.homework = 100;
}
public / private
或者是這樣寫。
#include <iostream>
class Student{
double homework; // 作業成績
double project; // 階段考成績
double attend; // 出席成績
public:
void set_homework(int score){
homework = score;
}
};
int main(){
Student a;
a.set_homework(100);
}
小結論
- class 與 struct 語法一樣,不過 class 會預設成員為 private。
- private 和 public 要設清楚!
- struct 主要是資料導向, class 主要是物件導向。
(複習)class 2
constructor / destructor
constructor
- 名字必須與 class 名稱一模一樣。
- 必定是 public。
- 沒有 return type。
物件被宣告時會自動執行的 member function。通常用來做初始化。
#include <iostream>
class Student{
public:
double homework; // 作業成績
double project; // 階段考成績
double attend; // 出席成績
Student(){ // constructor
homework = 100;
project = 80;
attend = 90;
}
};
int main(){
Student a;
std::cout << a.homework << std::endl;
std::cout << a.project << std::endl;
std::cout << a.attend << std::endl;
}
100
80
90
例一:
#include <iostream>
class Student{
public:
double homework; // 作業成績
double project; // 階段考成績
double attend; // 出席成績
Student(int a, int b, int c){ // constructor
homework = a;
project = b;
attend = c;
}
};
int main(){
Student a(90, 85, 80);
std::cout << a.homework << std::endl;
std::cout << a.project << std::endl;
std::cout << a.attend << std::endl;
}
90
85
80
例二:
#include <iostream>
class Student{
public:
double homework; // 作業成績
double project; // 階段考成績
double attend; // 出席成績
Student(int a, int b, int c){ // constructor
homework = a;
project = b;
attend = c;
}
Student(int a){ // constructor
homework = a;
project = 0;
attend = 0;
}
};
int main(){
Student a(90, 90, 90), b(90); // 某兩位學生,使用不同的 constructor
std::cout << a.attend << std::endl; // 輸出學生 a 出席成績
std::cout << b.attend << std::endl; // 輸出學生 b 出席成績
}
constructor overloading
你可以在一個 class 裡面定義很多 constructor,
但他們的參數型態必須不同。
90
0
#include <iostream>
class Student{
public:
const double homework; // 作業成績
const double project; // 階段考成績
const double attend; // 出席成績
Student(int a, int b, int c) : homework(a), project(b), attend(c) {}
};
int main(){
Student a(90, 85, 80);
std::cout << a.homework << std::endl;
std::cout << a.project << std::endl;
std::cout << a.attend << std::endl;
}
補充:const
如果要初始化的 member data 是 const,先前的 constructor 會造成編譯錯誤。(因為改到 const)
這時候,我們的 constructor 就應該用這種寫法。
90
85
80
destructor
- 名字必須與 class 名稱一模一樣,前面加上~
- 必定是 public。
- 沒有參數。
- 沒有 return type。
當物件被釋放時 (如 out of range 的時候) ,
自動執行的 member function。通常用來清理資源。
destructor
範例:
#include <iostream>
class Student{
public:
std::string name;
Student(std::string str){ // constructor
name = str;
std::cout << "Hello, " << name << std::endl;
}
~Student(){ // destructor
std::cout << "Goodbye, " << name << std::endl;
}
};
int main(){
Student a("Benson");
}
Hello, Benson
Goodbye, Benson
(複習)class 3
迷你練習題
#include <iostream>
class Student{
private:
double homework; // 作業成績
double project; // 階段考成績
double attend; // 出席成績
public:
Student(int a, int b, int c){ // constructor
homework = a;
project = b;
attend = c;
}
void TellJoke(){ // 課堂上說笑話的話出席分加 50 分
attend += 50;
}
double FinalScore(){ // 計算總成績
return 0.7 * homework + 0.2 * project + 0.1 * attend;
}
};
int main(){
Student a(90, 90, 40); // 某個學生,初始化時成績長這樣
a.TellJoke(); // 但這個學生課堂上說了一個笑話
std::cout << a.FinalScore() << std::endl; // 輸出該學生總成績
}
小練習1
請猜猜看這個程式會輸出什麼?
#include <iostream>
class Student{
public:
std::string name;
Student(std::string str){ // constructor
name = str;
std::cout << "Hello, " << name << std::endl;
}
~Student(){ // destructor
std::cout << "Goodbye, " << name << std::endl;
}
};
void summon(){
Student b("Ruby");
return;
}
int main(){
Student a("Benson");
summon();
return 0;
}
小練習2
請猜猜看這個程式會輸出什麼?
#include <iostream>
class Student{
private:
double homework; // 作業成績
double project; // 階段考成績
double attend; // 出席成績
public:
Student(int a, int b, int c){ // constructor
homework = a;
project = b;
attend = c;
}
double FinalScore(){ // 計算總成績
return 0.7 * homework + 0.2 * project + 0.1 * attend;
}
bool pass(){ // 如果學生的總成績在60以上,就通過(return true),反之沒通過。
// 請把你的 code 插在這裡
}
};
小練習3
請試著寫寫看 pass 這個 member function,
來判斷學生是否通過資芽第二階段。
總成績 60 以上則通過,低於 60 則未通過。
#include <iostream>
class Student{
const double homework; // 作業成績
const double project; // 階段考成績
const double attend; // 出席成績
Student(int a, int b, int c){ // constructor
homework = a;
project = b;
attend = c;
}
double FinalScore(){ // 計算總成績
return 0.7 * homework + 0.2 * project + 0.1 * attend;
}
};
int main(){
Student a(100,100,100);
std::cout << a.FinalScore() << std::endl;
}
小練習4
小明寫出了如下的 code,但是卻無法編譯。
小明不知道要怎麼修改才能順利編譯,你可以幫他找找這個程式問題出在哪嗎?
註
以上這些練習題是為了確保你們夠熟悉 class 了。
等等的實作環節會大量用到 class 哦!
解答
解答如下:(防劇透所以用黑色字。請用選取文字的方式來看答案。)
小練習1:
小練習2:
90
Hello, Benson
Hello, Ruby
Goodbye, Ruby
Goodbye, Benson
解答
小練習3:
小練習4:
return (FinalScore() >= 60);
(1) 因為 FinalScore() 在這裡沒有特別
說要 public,故預設是 private,
因此不能在 main 被存取。
(2) 因為這裡的三種分數都是 const,
所以 constructor 不能這樣寫。
new / delete
動態配置記憶體
記憶體配置
先跟大家介紹程式執行時,電腦的記憶體配置方式:
stack:平常 call 函數時,會用 stack 的空間。
空間是由編譯器來自動分配與收回。
stack 和 heap
heap:在所需記憶體空間難以預測時會用到,
由寫程式的人隨時手動分配與收回。
idea
但是,stack 並不是那麼的大,
所以沒辦法直接開出太大的陣列。
平常我們所使用的,都是用 stack 的空間。
因此,當我們有可能需要大量空間的時候,
最好是去跟 heap 要。
(也就是等等要講的 new 和 delete)
當你需要空間時,用 new 可以從 heap 中,
配置變數需要的空間。
當你不再需要當初 new 得到的空間時,就使用 delete 來歸還。
new / delete
語法(例一)
#include <iostream>
int main(){
int *x = new int;
*x = 20230520;
std::cout << *x << std::endl;
delete x;
}
配置記憶體: 指標變數 = new 資料型別;
釋放記憶體: delete 變數名稱;
20230520
語法(例二)
#include <iostream>
int main(){
int *x = new int(20230520);
std::cout << *x << std::endl;
delete x;
}
配置記憶體並初始化:
20230520
上面例子在 new 之後的記憶體分配,大概如下圖:
int *x = new int(20230520);
那 delete 又是做啥用的呢?
在 delete 過後,就會把當初 new 的那塊空間還回去。
delete x;
delete 可以避免空間的浪費。
語法(例三)
#include <iostream>
int main(){
int *array = new int[10];
delete []array;
}
配置陣列記憶體 與 釋放陣列記憶體:
語法(例四)
#include <iostream>
int main(){
int *array = new int[3]{2,5,8};
std::cout << array[0] << std::endl;
std::cout << array[1] << std::endl;
std::cout << array[2] << std::endl;
delete []array;
}
配置陣列記憶體並初始化:
2
5
8
在例三、例四中,陣列可以開到很大很大。
語法(例五)
配置二維陣列 與 釋放二維陣列:
#include <iostream>
int main(){
int **array = new int*[3];
array[0] = new int[3]{0,1,2};
array[1] = new int[3]{3,4,5};
array[2] = new int[3]{6,7,8};
for (int i=0 ; i<3 ; i++){
for (int j=0 ; j<3 ; j++){
std::cout << array[i][j] << " ";
}
std::cout << std::endl;
}
delete []array[0];
delete []array[1];
delete []array[2];
delete []array;
}
0 1 2
3 4 5
6 7 8
#include <iostream>
int main(){
int **array = new int*[3];
array[0] = new int[3]{0,1,2};
array[1] = new int[3]{3,4,5};
array[2] = new int[3]{6,7,8};
std::cout << &array << " " << array << std::endl;
std::cout << &array[0] << " " << array[0] << std::endl;
std::cout << &array[1] << " " << array[1] << std::endl;
std::cout << &array[2] << " " << array[2] << std::endl;
std::cout << &array[0][0] << " " << array[0][0] << std::endl;
delete []array[0];
delete []array[1];
delete []array[2];
delete []array;
}
0x7ffee1a14940 0x7fc17e4058c0
0x7fc17e4058c0 0x7fc17e4058e0
0x7fc17e4058c8 0x7fc17e4058f0
0x7fc17e4058d0 0x7fc17e405900
0x7fc17e4058e0 0
範例程式(例五)
關於這個二維陣列的記憶體...
解釋(例五)
(array) 0x7ffee1a14940 0x7fc17e4058c0
(array[0]) 0x7fc17e4058c0 0x7fc17e4058e0
(array[1]) 0x7fc17e4058c8 0x7fc17e4058f0
(array[2]) 0x7fc17e4058d0 0x7fc17e405900
(array[0][0]) 0x7fc17e4058e0 0
解釋(例五)
為什麼上面的例子,是先 delete array[0]、[1]、[2],
而不是先 delete array?
因為如果先 delete array,
就會變成如右的狀況:
這樣再 delete array[0] 時,
就會出問題。
中場休息
實作動態陣列 1
動態陣列的架構
等等會讓大家開始實作。
請確定你已經具備以下先備知識:
先備知識
- class
- template
- new / delete
- 剛剛講過的所有笑話
我們希望做出的動態陣列,具有以下功能:
目標
- 支援不同變數型態
- 在陣列最後追加一個元素 / 移除一個元素
- 存取陣列的第 i 個元素
- 求出陣列現在有幾個元素
std::cout << arr.size(); // 印出 2
- 存取陣列的第 i 個元素
- 求出陣列現在有幾個元素
- 支援不同變數型態
- 在陣列最後追加一個元素 / 移除一個元素
DynamicArray<int> arr; // {}
DynamicArray<double> arr2; // {}
arr.push_back(0); // {0}
arr.push_back(1); // {0, 1}
arr.push_back(2); // {0, 1, 2}
arr.pop_back(); // {0, 1}
std::cout << arr[0]; // 印出 0
std::cout << arr[1]; // 印出 1
arr[0] = 3; // {3,1}
std::cout << arr[0]; // 印出 3
我們或許會用 class 來完成這個動態陣列的架構
以 int 為例,我們的 class 架構可能長這樣...
架構
class DynamicArray {
int size_; // 現在有幾個元素
int capacity_; // 有多少空間能用
int *arr_; // 陣列內容
public:
DynamicArray(); // constructor
~DynamicArray(); // destructor
void push_back(int value); // 追加元素
void pop_back(); // 移除元素
int size(); // 回傳有幾個元素
// 存取陣列第 i 個元素 (等等會教大家怎麼寫)
};
支援不同型別
template <typename T>
class DynamicArray {
int size_;
int capacity_;
T *arr_;
public:
DynamicArray();
~DynamicArray();
void push_back(T value);
void pop_back();
int size();
};
把前面 int 的版本改成使用 template 的版本:
class DynamicArray {
int size_;
int capacity_;
int *arr_;
public:
DynamicArray();
~DynamicArray();
void push_back(int value);
void pop_back();
int size();
};
修改前
修改後
動動腦
template <typename T>
class DynamicArray {
int size_;
int capacity_;
T *arr_;
public:
DynamicArray();
~DynamicArray();
void push_back(T value);
void pop_back();
int size();
};
-
如果我們不用 class,而是用 struct 呢?
這樣有什麼好處跟壞處?
- *arr_ 如果做成 public 的話有什麼影響?
常見習慣
-
member variable 通常設為 private, 並提供 public member function 讓外界操作。
-
private member variable,通常會在名字裡加上底線 _,(如這裡的 *arr_)。
有些人會加在前面,有些人會加在後面。
目前進度
template <typename T>
class DynamicArray {
int size_;
int capacity_;
T *arr_;
public:
DynamicArray(); // TODO: constructor
~DynamicArray(); // TODO: destructor
void push_back(T value); // TODO: 新增元素
void pop_back(); // TODO: 移除元素
int size(); // TODO: 回傳有幾個元素
// TODO: 存取陣列第 i 個元素
};
實作動態陣列 2
各函式的大綱
想法
在開始動手寫之前,先讓大家釐清一下,
這五個 public member function 在做什麼。
template <typename T>
class DynamicArray {
int size_;
int capacity_;
T *arr_;
public:
DynamicArray();
~DynamicArray();
void push_back(T value);
void pop_back();
int size();
};
template <typename T>
class DynamicArray {
int size_;
int capacity_;
T *arr_;
public:
DynamicArray(); // 初始化各個 private member
~DynamicArray(); // 把當初 new 下來的資源 delete 回去
void push_back(T value);
void pop_back();
int size();
};
- constructor:初始化三個 private member。
- destructor :歸還當初 new 過的資源。
(哪個 private member 是需要被歸還的?)
- push_back():把 value 加到陣列最後。
(這需要動到哪些 private member?)
(如果 new 的空間滿了怎麼辦?)
template <typename T>
class DynamicArray {
int size_;
int capacity_;
T *arr_;
public:
DynamicArray();
~DynamicArray();
void push_back(T value){
// TODO: if (陣列空間滿了) 再 new 更多空間給 arr_
// TODO: 把 value 新增到 arr_ 陣列最後
// TODO: 記得要修正 size_
}
void pop_back();
int size();
};
- pop_back():把最後一個元素移除。
(這需要動到哪些 private member?)
template <typename T>
class DynamicArray {
int size_;
int capacity_;
T *arr_;
public:
DynamicArray();
~DynamicArray();
void push_back(T value);
void pop_back(); // 記得要修正 size_
int size(); // 回傳 size_
};
- size():回傳有幾個元素。
(這跟哪個 private member 有關?)
目前進度
template <typename T>
class DynamicArray {
int size_;
int capacity_;
T *arr_;
public:
DynamicArray(){
// TODO: 初始化三個 private member
}
~DynamicArray(){
// TODO: 把當初 new 下來的資源 delete 回去
}
void push_back(T value){
// TODO: if (陣列空間滿了) 再 new 更多空間給 arr_
// TODO: 把 value 新增到 arr_ 陣列最後
// TODO: 記得要修正 size_
}
void pop_back(){
// TODO: 記得要修正 size_
}
int size(){
// TODO: return size_
}
// TODO: 存取陣列第 i 個元素
};
目前進度與代辦事項(可下拉)
實作動態陣列 3
constructor & push_back()
constructor
constructor 用來初始化 size_、capacity_、arr_。
(先別看下一頁)要初始化成多少?
DynamicArray(){
size_ = // ?
capacity_ = // ?
arr_* = // ?
}
constructor
DynamicArray(){
size_ = 0;
capacity_ = 1;
arr_ = new T[capacity_];
}
我的作法:
動動腦:
- size_ 「一定」 要是 0。為什麼?
- capacity_ 「不一定」要是 1。為什麼?
push_back
這是剛剛討論出,push_back 要做的事情。
怎麼用 size_、capacity_、arr_,來達成:
void push_back(T value){
// TODO: if (陣列空間滿了) 再 new 更多空間給 arr_
// TODO: 把 value 新增到 arr_ 陣列最後
// TODO: 記得要修正 size_
}
- 判斷空間是否已滿?
- 把 value 這個元素新增到 arr_ 最後?
- size_ 應該改成多少?
push_back
這是上面幾個小問題的解法:
void push_back(T value){
if (size_ == capacity_){ // if (陣列空間滿了)
grow(); // 再 new 更多空間給 arr_
}
arr_[size_] = value; // 把 value 新增到 arr_ 陣列最後
size_ ++; // 修正 size_
}
方便起見,我們再定義一個 grow() 函式,
那 grow() 詳細要怎麼實現呢?
grow
grow() 在做的事,是 new 更多空間給 arr_ 用。
我們可以這樣子寫:
- new 一個更大的陣列
- 把 arr_ 陣列的內容,複製到新的大陣列去
- 修改 capacity_。
grow
- new 一個更大的陣列
- 把 arr_ 陣列的內容,複製到新的大陣列去
- 修改 capacity_。
以下是程式碼:
void grow(){
int *temp = new T[capacity_+1]; // new 一個更大的陣列
for (int i=0 ; i<size_ ; i++){ // 把 arr_ 陣列的內容,
temp[i] = arr_[i]; // 複製到新的大陣列去
}
delete[] arr_; // 記得 delete 原本的陣列
arr_ = temp;
capacity_ += 1; // 修改 capacity_
}
grow
常見疑問:
void grow(){
int *temp = new T[capacity_+1]; // new 一個更大的陣列
for (int i=0 ; i<size_ ; i++){ // 把 arr_ 陣列的內容,
temp[i] = arr_[i]; // 複製到新的大陣列去
}
delete[] arr_; // 記得 delete 原本的陣列
arr_ = temp;
capacity_ += 1; // 修改 capacity_
}
- 新的大陣列的大小,一定要是 capacity_+1 嗎?
- 為什麽是先 delete[] arr_,才做 arr_ = temp?
常見疑問 回覆
其實,比起 capacity_+1,我們更常用 capacity_*2 !
void grow(){
int *temp = new T[capacity_*2];
for (int i=0 ; i<size_ ; i++){
temp[i] = arr_[i];
}
delete[] arr_;
arr_ = temp;
capacity_ *= 2;
}
為什麼?(可從效率方面去想)
常見疑問 回覆
void grow(){
int *temp = new T[capacity_*2];
for (int i=0 ; i<size_ ; i++){
temp[i] = arr_[i];
}
delete[] arr_;
arr_ = temp;
capacity_ *= 2;
}
如果你先做 arr_ = temp,才做 delete[] arr_,
那你就會把 temp 的新陣列給 delete 掉...
目前進度
template <typename T>
class DynamicArray {
int size_;
int capacity_;
T *arr_;
void grow(){
int *temp = new T[capacity_*2];
for (int i=0 ; i<size_ ; i++){
temp[i] = arr_[i];
}
delete[] arr_;
arr_ = temp;
capacity_ *= 2;
}
public:
DynamicArray(){
size_ = 0;
capacity_ = 1;
arr_ = new T[capacity_];
}
~DynamicArray(){
// TODO: 把當初 new 下來的資源 delete 回去
}
void push_back(T value){
if (size_ == capacity_){
grow();
}
arr_[size_] = value;
size_ ++;
}
void pop_back(){
// TODO: 記得要修正 size_
}
int size(){
// TODO: return size_
}
// TODO: 存取陣列第 i 個元素
};
目前進度與代辦事項(可下拉)
加油!剩下的部分就簡單很多了!
實作動態陣列 4
其餘的功能
destructor
~DynamicArray(){
delete[] arr_;
}
把當初 new 下來的資源通通 delete 掉。
pop_back()
void pop_back(){
if (size_ > 0) size_ --;
}
最重要的是把 size_ 減一。
動動腦:
- 若移除元素後,發現 size_ 遠小於 capacity_,把多餘的空間 delete 掉是值得的嗎?
size()
int size(){
return size_;
}
回傳陣列有幾個元素。即 size_。
動動腦:
- 為什麼不直接把 size_ 設成 public,而是要用一個 size() 函式來用它?
目前進度
template <typename T>
class DynamicArray {
int size_;
int capacity_;
T *arr_;
void grow(){
int *temp = new T[capacity_*2];
for (int i=0 ; i<size_ ; i++){
temp[i] = arr_[i];
}
delete[] arr_;
arr_ = temp;
capacity_ *= 2;
}
public:
DynamicArray(){
size_ = 0;
capacity_ = 1;
arr_ = new T[capacity_];
}
~DynamicArray(){
delete[] arr_;
}
void push_back(T value){
if (size_ == capacity_){
grow();
}
arr_[size_] = value;
size_ ++;
}
void pop_back(){
if (size_ > 0) size_ --;
}
int size();{
return size_;
}
// TODO: 存取陣列第 i 個元素
};
目前進度與代辦事項(可下拉)
剩最後一步了!
存取元素
目標:用 arr_[index],來求出動態陣列 arr_ 的
第 index 個元素。
方法:運算子多載 (operator overloading)。
operator overloading
舉例來說,兩個 int 可以用 + 來做相加。
兩個 double 可以用 + 來做相加。
兩個 std::string 可以用 + 來做相加。
但,兩個 struct 或 class,不能用 + 來做相加,
然而我們可以用 operator overloading 來做這件事。
operator overloading
operator overloading,可以定義 operator 在該 struct 或 class 中的定義,讓我們直接拿來用。
#include <iostream>
struct shop{
int apple; // 蘋果數量
int orange; // 橘子數量
shop operator + (shop a){
shop b;
b.apple = apple + a.apple;
b.orange = orange + a.orange;
return b;
}
};
int main() {
shop a = {10,20}, b = {30, 10};
shop c = a + b; // 可以直接用 + 來相加
std::cout << c.apple << std::endl;
std::cout << c.orange << std::endl;
}
40
30
存取元素
我們要用的 [],其實也是一種 operator。
用 operator overloading 就可以定義它。
T& operator [](int index) {
return arr_[index];
}
完成了!
template <typename T>
class DynamicArray {
int size_;
int capacity_;
T *arr_;
void grow(){
int *temp = new T[capacity_*2];
for (int i=0 ; i<size_ ; i++){
temp[i] = arr_[i];
}
delete[] arr_;
arr_ = temp;
capacity_ *= 2;
}
public:
DynamicArray(){
size_ = 0;
capacity_ = 1;
arr_ = new T[capacity_];
}
~DynamicArray(){
delete[] arr_;
}
void push_back(T value){
if (size_ == capacity_){
grow();
}
arr_[size_] = value;
size_ ++;
}
void pop_back(){
if (size_ > 0) size_ --;
}
int size(){
return size_;
}
T& operator [](int index) {
return arr_[index];
}
};
如此一來,我們就能使用這個動態陣列!
#include <iostream>
int main(){
DynamicArray<int> arr;
arr.push_back(0);
arr.push_back(1);
arr.push_back(2);
std::cout << arr[0] << std::endl;
std::cout << arr[1] << std::endl;
arr[2] = 3;
std::cout << arr[2] << std::endl;
std::cout << arr.size() << std::endl;
arr.pop_back();
std::cout << arr.size() << std::endl;
}
0
1
3
3
2
而且這個動態陣列可以開到很大很大的空間!
不會 runtime error !!
#include <iostream>
int main(){
DynamicArray<int> arr;
for (int i=0 ; i<1000000000 ; i++){
arr.push_back(i);
}
std::cout << arr.size() << std::endl;
}
1000000000
課堂練習
謝謝大家
20230520 實作動態陣列
By allen522019
20230520 實作動態陣列
- 415