{大赦客之伍}

所以我們甚麼時候要換模板

\(INDEX\)

這我

  • 20729 蔡嘉晉
  • 失蹤的世宗

 

  • 因為大家都用之前的綽號講爛梗所以就換綽號了
  • 但還是一樣爛
  • 這邊推薦大家叫我本名

 

  • 店言學術
  • 資訊校隊墊底

 

 

{指標}

要燒雞ㄌ

一個指向別的變數的東東

  • 因為我想不到要怎麼開頭 所以來看定義吧
  • wikipedia
    • 在電腦科學中,指標(英語:Pointer),是程式語言中的一類資料類型及其物件或變數_(程式設計),用來表示或儲存一個記憶體位址,這個位址的值直接指向(points to)存在該位址的對象的值。
  • 重點
    • 一個變數
    • 存的值是一個記憶體位址
    • 指向另外一個變數

先來看看電腦是怎麼存變數的

  • 每個變數都會有對應的位址 並在那個位址儲存該變數的值
  • 透過取址符號 & 可以得到一個變數的位址
    • 雖然跟位元and同個符號 但用的地方不一樣就是不同意思
var_a var_b var_c var_d
型態 int long long string char
0 1 "cjtsai" 'r'
位址 0x8071 0x8072 0x8073 0x8074
int main(){
    int var_a=0;
    cout<<var_a; //0;
    cout<<&var_a; //0x8071;
    var_a=101;
    cout<<var_a<<' '<<&var_a; // 101 0x8071
} //我只是想說變數位址跟他自己的值沒關係

每次執行的變數都會存在不同位址

不同電腦也都會有些差異喔

但格式都差不多

所以我要怎麼指別人

  • 我要知道一個變數現在的值
  • 還要知道他在被改動之後變成甚麼值
  • 怎麼做
  • 知道他的位址就好了
  • 宣告指標變數!

int* a = &b

  • b 的型態要是int   a 的型態就變成了 指向整數的指標 簡稱 int*

T* a = &b

  • b 的型態要是T   a 就變成了指向 b 的神奇指標型態 T*
  • (T的意思是template 這邊大概是要寫個標準表達式的概念

然後呢

  • 於是int*這種型態的變數存的值便會是一串代表一個記憶體位址的東西
  • 所以我指到別人了 要怎麼知道我指的那個位址所存的值?
  • 取值符號!
  • 在指標變數前面放一個* (就是宣告時用的那顆) 就會得到該記憶體位址的值囉
var_a pt_a var_c pt_c
型態 int int* string string*
0 0x8071 "cjtsai" 0x8073
位址 0x8071 0x8072 0x8073 0x8074
int main(){
    int var_a=0;
    int* pt_a = &var_a;
    cout<<var_a; //0
    cout<<pt_a; //0x8071
    cout<<*pt_a; //0
}
int main(){
    std::string var_c="cjtsai";
    std::string* pt_c = &var_c;
    cout<<var_c; //"cjtsai"
    cout<<pt_a; //0x8073
    cout<<*pt_a; //"cjtsai"
}

複習一下

  • 宣告指標變數: *
  • 符號: &
  • 符號: *

指標的指標的指標的指標的指標?

  • 再看一下剛剛這張表
  • 指標變數也是有位址的
  • 畢竟只要是變數都會有自己的位址
  • 所以可以幹嘛?
var_a pt_a var_c pt_c
型態 int int* string string*
0 0x8071 "cjtsai" 0x8073
位址 0x8071 0x8072 0x8073 0x8074
  • 拿一個指標的指標指向這個指標
  • 或是拿一個指標的指標的指標指向剛剛那個指標的指標
  • 或是拿一個指標的指標的指標的指標指向剛剛那個指標的指標的指標?
  • 沒 不要做這種事 有夠讀

{參考}

持續燒雞中

一個參考別人的值的東東

  • 可是指標我還要甚麼取址 宣告指標 取值
  • 好麻煩喔
  • 可不可以讓這個變數的值直接跟別的變數同步阿
  • 宣告 參考 ! (reference
int main(){
    int var_a=0;
    int& ref_a=var_a;
    cout<<ref_a<<' '<<var_a; //0 0
    ref_a=10;
    cout<<ref_a<<' '<<var_a; //10 10;
}

int& a = b

T& a = b

兩個變數var_a 跟 ref_a 的值同步了!

變簡單了欸

  • 再看回來這張表
var_a pt_a var_c pt_c
型態 int int* string string*
0 0x8071 "cjtsai" 0x8073
位址 0x8071 0x8072 0x8073 0x8074
  • 參考存的值不是一個位址
  • 他跟他參考的東西是同個物體
  • 下面的程式 ref_a 該放在上表哪裡?
int main(){
    int var_a=0;
    int& ref_a=var_a;
}

ref_a

變簡單了欸

  • 再看回來這張表
var_a pt_a var_c pt_c
型態 int int* string string*
0 0x8071 "cjtsai" 0x8073
位址 0x8071 0x8072 0x8073 0x8074
  • 參考存的值不是一個位址
  • 他跟他參考的東西是同個物體
  • 下面的程式 ref_a 該放在上表哪裡?
int main(){
    int var_a=0;
    int& ref_a=var_a;
    cout<<ref_a; //0
    cout<<&var_a<<' '<<&ref_a; //0x8071 0x8071 一樣欸
}//再次複習 & 是用來取得某個變數的記憶體位址喔

ref_a

他們的記憶體位址

輸出後是同個欸

再來複習一次

  • 宣告參考: &
  • 宣告指標變數: *
  • 符號: &
  • 符號: *

 

  • 對 C++就是一個這麼破的語言
  • 一個符號可以擁有很多種意思
  • 在不同地方會自己變換喔
  • 就像*還可以順便當運算子作為乘法
  • 如果在寫函式的話我們叫這個多載

奇怪小測驗

a b c d
位址 0x8071 0x8072 0x8073 0x8074
int a=10;
int *b=&a;
int *&c=b;
int **d=&b;
  • a
  • &a
  • b
  • *b
  • &b
  • 10
  • 0x8071
  • 0x8071
  • 10
  • 0x8072
  • c
  • *c
  • &c
  • *d
  • **b
  • 10
  • CE
  • 0x8073
  • 0x8071
  • 10

感覺好像不會用到阿

  • 聽完指標跟參考的介紹後
  • 會不會覺得我平常好好地寫扣為甚麼要用
  • 接下來就會知道ㄌ

{遞迴}

遞迴只應天上有 凡人應當用迴圈

--資訊之芽

資芽你甚麼意思?

  • 解答
    • 函數的呼叫會有操作 stack frame 的執行成本。此外,遞迴不會有迴圈具有的 spatial locality,對於記憶體(其實是 cache)的存取可能比較沒有效率。
  • 但這不是重點 有興趣自己去翻資訊毒芽的講義
  • 其實真的沒什麼差啦 資芽最後也補了
    • 同樣的目的,使用迴圈的效能通常會比使用遞迴來得好一點點
    • 嗯 一點點
    • 遞迴還是一項很重要的技術可以解決很多問題喔

所以甚麼是遞迴

  • 在執行一個函式的時候 呼叫函式本身
    • 有點抽象
    • 看圖 萬年不變的那張
    • 舉個例子
    • 費波納氣數列
    • \(a_i=a_{i-1}+a_{a_i-2}\)
    • 先看扣
#include <bits/stdc++.h>
using namespace std;

int fibo(int n){
   if(n==1) return 1;
   if(n==2) return 1;
   return fibo(n-1)+fibo(n-2);
}

int main(){
   cout<<fibo(5); //5
}

遞迴結構

#include <bits/stdc++.h>
using namespace std;

int fibo(int n){
   if(n==1) return 1;
   if(n==2) return 1;
   return fibo(n-1)+fibo(n-2);
}

int main(){
   cout<<fibo(5); //5
}

這裡我們在定義fibo這個函數

但我們在定義的時候使用了fibo這個函數

這就是遞迴函式特殊的寫法

我叫 邊界條件

邊界條件

如果沒有邊界條件 呼叫了 fibo(1) 會發生甚麼事?

fibo(1)會去呼叫 fibo(0)跟fibo(-1) 但這完全不合理

所以我們要設定邊界 適當的邊界能使程式執行成功並且不會吃到一些錯誤

像是maximum recursion depth exceed之類的

注意函式要宣告在main函數外面

但是好像有個東西叫做lambda

你可以輕鬆寫出比這個好很多的fibo函式

但我們先別在意這個問題

模擬一下遞迴

  • main()
    • fibo(5)
      • fibo(4)
        • fibo(3) 
          • fibo(2) return 1;
          • fibo(1) return 1;
        • fibo(2) return 1;
      • fibo(3)
        • fibo(2) return 1;
        • fibo(1) return 1;
#include <bits/stdc++.h>
using namespace std;

int fibo(int n){
   if(n==1) return 1;
   if(n==2) return 1;
   return fibo(n-1)+fibo(n-2);
}

int main(){
   cout<<fibo(5); //5
}

return 2;

return 3;

return 2;

return 5;

順帶一提

不覺得這個遞迴呼叫了很多次相同的東西嗎

如果可以記錄已經算過的項 紀錄狀態

是不是能快很多呢

但這就是以後的事了

遞迴之優劣分析

  • 空間要求很大
    • 可能遞迴太多層就吃MLE了
  • 時間可能也比較慢
    • 一直呼叫函數 執行的指標一直跳來跳去
    • 就跳成TLE了
  • 蛤那遞迴好爛喔
    • 不是欸
  • 可讀性高
    • 可以用遞迴寫的東西常也可以用迴圈寫 但會比較難理解
  • 好寫
    • 可能是你看到題目最先想到的解法
    • 也可以暴力解題目的方式

題單時間

為甚麼我複製你的扣會TLE!

  • 不覺得這個遞迴呼叫了很多次相同的東西嗎
  • 如果可以記錄已經算過的項 紀錄狀態
  • 是不是能快很多呢
  • 如果我開一個陣列紀錄已經算過的值
  • 之後是不是需要取用第x項的值 就可以直接存取而不用重算呢
Made with Slides.com