# 第二章 :進階技巧 語法進階(前半)
## 語法進階
### 巢狀迴圈
就是迴圈裡面套一個迴圈,APCS實作3級左右很愛考,其實觀念不難,自己寫一次體驗一下大概就能理解了。
```cpp=
for(int i=0;i<10;i++){
for(int j=1;j<=10;j++) {
cout<<(10*i+j)<<' ';
}
cout<<'\n';
}
```
結果大概會是這樣。
```txt=
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90
91 92 93 94 95 96 97 98 99 100
```
執行流程是
``i=0 => j=1 => cout => j++ => ...(j=11) => cout<<'\n' => i++ => j=1 => ...(直到i=10)``
### 巢狀迴圈中的break跟continue
### 二維陣列
通常搭配巢狀迴圈一起用,也是APCS第二題很喜歡考的內容,可以想像成 : 陣列是一串空間,每個空間存一個`int` ; 二維陣列則是一串陣列,二維陣列的每個空間存一個陣列,每個陣列又存一串空間。
```cpp=
int a[11][11];
for(int i=0;i<=10;i++){
for(int j=0;j<=10;j++){
a[i][j]=10*i+j;
}
}
```
這時候陣列裡面就會是
```txt=
0 1 2 3 4 5 6 7 8 9 10
10 11 12 13 14 15 16 17 18 19 20
20 21 22 23 24 25 26 27 28 29 30
30 31 32 33 34 35 36 37 38 39 40
40 41 42 43 44 45 46 47 48 49 50
50 51 52 53 54 55 56 57 58 59 60
60 61 62 63 64 65 66 67 68 69 70
70 71 72 73 74 75 76 77 78 79 80
80 81 82 83 84 85 86 87 88 89 90
90 91 92 93 94 95 96 97 98 99 100
100 101 102 103 104 105 106 107 108 109 110
```
可以試著輸出看看
```cpp=
for(int i=0;i<=10;i++){
for(int j=0;j<=10;j++){
cout<<a[i][j]<<' ';
}
cout<<'\n';
}
```
***注意*** : 小心不要超出陣列範圍,不然會RE,是APCS第二題很常出現的錯誤
> 一些練習題
> [APCS模擬賽 地震](https://apcs-simulation.com/problem/apcs0502)
> [地震題解](https://hackmd.io/@apcs-simulation-/rkgCnXJ40)
> [APCS模擬賽 天旋地轉](https://apcs-simulation.com/problem/apcs0802)
> [天旋地轉題解](https://hackmd.io/@apcs-simulation-/ByKdMix30)
### define
C++可以用`#define`某些關鍵字在編譯時換成其他字,聽起來有點抽象,舉個例子。
```cpp=
#define a b
```
這樣之後的所有a在程式裡都會被電腦當成b。
```cpp=
#define a b
cout<<a<<'\n'; //編譯器會當成 cout<<b<<'\n';
```
幾個常用的用法
1. `#define int long long`
有一些題目可能會有很多變數會超出`long long`範圍,寫的時候又要打很多`long long`很麻煩,加上這句後所有`int`都會被當成`long long`。
```cpp=
#define int long long
int a[10]; //他其實是long long 陣列
```
不過有幾點要注意,`int main()`的`int`也會被換成`long long`,編譯器就會報錯,所以要改成下面的程式。
```cpp=
signed main(){
//...
}
```
因為int的其實是`signed int`,打`signed`的話編譯器也會把他當`int`。
另外,`long long`的空間是`int`的兩倍,在有些卡比較緊的題目中,所有變數都開成l`ong long`可能會`MLE/TLE`,不過筆者認為利大於弊,以筆者的經驗來說,忘記開`long long`而overflow的次數比開`long long`被卡時間空間的次數多很多很多。
2. `#define f/s first/second`
這通常是在會用到`std::pair`的題目會加的,因為`first(second)`太長了qwq。
3. `#define ALL(x) x.begin(),x.end()`
有些人喜歡在使用`std::vector`或其他$STL$時使用。
### struct
這東西有點像自己定義變數型態,可以把很多個變數綁在一起

像是這樣,可以放int可以放float可以放字串可以放陣列甚至可以放函式,宣告方法如下
```cpp=
struct a{
int l,r;
char op;
string ans;
double point;
bool comp(int a,int b){
//...
}
};
```
這樣就宣告了一個struct,**但他還只是一個變數型態,不是一個變數**,想要有一個真正的struct只要跟一般變數宣告方法一樣
```cpp=
a b;
```
這樣b就是一個包含很多個變數的變數了,如果要使用b其中的變數,要用.
```cpp=
b.ans="hello";
cout<<b.ans<<'\n';
```
如此,其他int、char、double同理,函數的使用方法一樣
```cpp=
b.comp(x,y);
```
struct裡的變數在宣告時和一般變數相同,若在全域宣告會是0,若在函式內宣告則會是亂數,想要控制宣告時的初始值,可使用建構函式
```cpp=
struct a{
int l,r;
a(int x,int y):l(x),r(y){}
};
```
函式前面不用宣告回傳值,函式名字直接使用struct的名字,加上一個冒號接著可以初始化裡面的變數,用法 ``變數名稱(值)``,後面的大括號內可寫入程式,在宣告時會被執行
這樣宣告時傳入$x,y$就可以把變數中的$l,r$初始化為$x,y$
```cpp=
a b(1,2);
cout<<b.l<<' '<<b.r<<'\n';
```
另外,struct的運算子如果沒有特別宣告都是未定義的,像是``+``、``-``、``*``、``/``、``=``、``>``、``<``等等,要使用的話要重載運算子
```cpp=
struct a{
int l,r;
bool operator < (a o) const{
return l<o.l;
}
};
```
像是這樣,前面先宣告回傳類型,接著``operator``然後你想重載的運算子,後面就和一般函式一樣。
### 指標
這東東在開發中有非常非常多應用,但以APCS(或競賽程式)為目標的話,只需要了解指標的概念即可,不太需要理解過於深奧的指標用法。
了解指標之前,先從變數開始 ; 變數,用來儲存資料的資料結構,想當然他必須占用電腦上的記憶體,而由於記憶體空間很大,電腦為了更好的管理空間,每個記憶體空間都有被分配一個編號,類似於住址,以下稱其為**記憶體位址**。
C/C++中,有兩種運算子可以控制記憶體位址,以下為範例 :
```cpp=
int a=1; //一般變數
cout<<(&a)<<'\n'; // & 運算子可以得到 a 變數的記憶體位址
cout<<*(&a)<<'\n'; // * 運算子則是取得記憶體位址儲存的資料
```
``&``後接==變數==,功能為取得該變數的記憶體位址 ;
``*``後接==記憶體位址==,功能為取得該記憶體位址的變數。
當然,透過記憶體位址我們也可以修改該位址所儲存的資料
```cpp=
int a=1;
cout<<a<<'\n'; // 會輸出 1
*(&a)=2;
cout<<a<<'\n'; // 會輸出 2
```
在C/C++中,指標就是一種儲存記憶體位址的變數,因此,指標可以很直接地對硬體進行控制,功能強大的同時,也很容易在使用不慎時導致程式執行時發生錯誤。
以下展示指標的宣告方法 :
```cpp=
int a=1;
int *p=&a; // 透過 * 可以宣告指標變數,儲存 a 變數的記憶體位址
cout<<p<<'\n'; //會輸出a的記憶體位址
cout<<*p<<'\n'; //會輸出a的值 (即 1)
*p+=3;
cout<<*p<<'\n'; //一樣可以修改a變數 (輸出 4)
```
想要儲存哪一種類型的變數,就得宣告該類型的指標,透過``*``代表他是指標變數,賦值時賦予記憶體位址。
指標變數也是一種變數,因此,他也需要記憶體空間,當然也有屬於他的記憶體位址
```cpp=
int a;
int *p=&a; //指向一般變數的指標
int **pp=&p; //指向指標的指標
int ***ppp=&pp; //指向指向指標的指標的指標
```
- 指標的應用
陣列,是一串記憶體位址連續的變數,而C/C++中,陣列其實是儲存陣列第一個變數的記憶體位址的指標,如此,就不需要記住每個變數的實際記憶體位址,剩下的變數透過加減即可得到
```cpp=
int a[10]={0,1,2,3,4,5,6,7,8,9};
cout<<a<<' '<<&a[0]<<'\n'; //是相同的東西
cout<<*(a+3)<<' '<<a[3]<<'\n'; //也是相同的東西
```
``[]``其實就是加法後取值,因此,陣列的index不一定要為正
```cpp=
int a[10]={0,1,2,3,4,5,6,7,8,9};
int *b=a+5;
cout<<b[-3]<<' '<<a[2]<<'\n'; //還是相同的東西
```
在第三章基礎資結會說到**STL**,而STL有一個特殊的**疊代器**,剛接觸時可能不太能理解他是甚麼,畢竟他的名字聽起來也非常高大上,但他其實就是C/C++開發者特別設計的指標,以後看到時可以就把他當成記憶體位址。
---
### 題目
- 多維陣列
[ABC364B](https://atcoder.jp/contests/abc364/tasks/abc364_b)