暑假資讀[2]
陳仲肯
自我介紹
-
209
-
資讀真正最弱的
-
kennyfs
指標
資料的路標
可以幹嘛?
- 傳參數給函數,還能修改
- 加快效率
- 動態資料結構
基本操作
-
* 取值:指標變成值
-
& 取址:值變成指標
-
宣告:在變數名字前加一個*
-
指標可以加減喔
int *pointer;
int* a,b;//a是指標,b是值
int *a,*b;//ab都是指標
int a;
int *pa=&a;
cout<<&a<<'\n';
cout<<pa<<'\n';
0x7ffdbddd9e3c
0x7ffdbddd9e3c
int a[10]={1,2,3,4,5,6,7,8,9,10};
int *p=a;//=&a[0]
cout<<a[5]<<'\n';
cout<<*(p+5)<<'\n';
6
6
指標和陣列的關係
int a[10]={1,2,3,4,5,6,7,8,9,10};
for(int i=0;i<10;++i){
cout<<a[i]<<'\n';
}
for(int *p=a;p!=a+10;++p){
cout<<*p<<'\n';
}
加快效率
int a[10]={1,2,3,4,5,6,7,8,9,10};
int *p=a;
cout<<a[1000]<<'\n';
cout<<*(p+1000)<<'\n';
cout<<*(p+7122)<<'\n';
cout<<*(p+8787)<<'\n';
Segmentation fault
或讀到沒用的值
參考
別名
變數的綽號
可以幹嘛?
- 傳參數給函數,還能修改
- 加快效率
- 比起指標可以不用*和&
基本操作
- 宣告:在變數名字前加一個&
- 當成一般的變數使用
int a=5;
int &b=a;
a=3;
cout<<a<<' '<<b<<'\n';
b=7122;
cout<<a<<' '<<b<<'\n';
3 3
7122 7122
不要搞混
-
用指標時的&:取址
-
用參考時的&:宣告時要用
指標的指標?
指標的參考?
你可能會看到這樣的東西
int a=5;
int *p=&a;
int *&b=p;
int c,d;
p=&c;
cout<<p<<' '<<b<<'\n';
b=&d;
cout<<p<<' '<<b<<'\n';
莫急莫慌莫害怕
int a=5;
int *p=&a;//指標
int *&b=p;//指標的參考
int c=7,d=8;
p=&c;
cout<<*p<<' '<<*b<<'\n';
b=&d;
cout<<*p<<' '<<*b<<'\n';
7 7
8 8
函數
函數?函式?
都可以
用途
- 取代重複出現很多次的程式
- 遞迴
int a[10]={1,2,3,4,5,6,7,8,9,10};
int b;
b=a[0]*a[5];
b=a[6]*a[3];
b=a[7]*a[1];
b=a[2]*a[2];
b=a[8]*a[7];
b=a[9]*a[5];
當你一直做重複的事你就會覺得很煩
一些名詞
- 呼叫:執行函數
- 回傳:函數執行完,回到呼叫那個函數的地方
(有可能伴隨著值)
函數的宣告
回傳值型態 函數名字(參數型態 參數名字,參數型態 參數名字...){
做事情
}
回傳值型態
- int,float,double:回傳這些型態的值,一定要回傳
- void:不回傳值,要回傳的話就:
return;
return 變數;
void hello(){
cout<<"Hello\n";
}
int one(){
cout<<"1\n";
return 1;
}
void hello(){
cout<<"Hello\n";
}
int main(){
hello();
}
void num(int n){
cout<<n<<'\n';
}
int main(){
int a=3;
num(5);
num(a);
}
呼叫函數
處理回傳值
int one(){
return 1;
}
int main(){
int a;
a=one();
cout<<a<<'\n';
cout<<one()<<'\n';
}
int a[10]={1,2,3,4,5,6,7,8,9,10};
int multiply(int first,int second){
return a[first]*a[second];
}
int main(){
int b;
b=multiply(0,5);
b=multiply(6,3);
b=multiply(7,1);
b=multiply(2,2);
b=multiply(8,7);
b=multiply(9,5);
}
就可以把原本的程式寫成這樣
雖然現在沒有比較短
但是當函數裡的程式有十幾行就會短很多了
全域變數、區域變數?
#include <iostream>
using namespace std;
int hi;
int main(){
}
#include <iostream>
using namespace std;
int main(){
int sum=0;
for(int i=0;i<5;++i){
sum+=i;
cout<<sum<<'\n';
}
}
全域變數、區域變數?
#include <iostream>
using namespace std;
void printsum(){
cout<<sum<<'\n';
}
int main(){
int sum=0;
for(int i=0;i<5;++i){
sum+=i;
printsum();
}
}
全域變數、區域變數?
#include <iostream>
using namespace std;
int sum=0;
void printsum(){
cout<<sum<<'\n';
}
int main(){
for(int i=0;i<5;++i){
sum+=i;
printsum();
}
}
全域變數、區域變數?
#include <iostream>
using namespace std;
int sum=0;
void printsum(){
cout<<sum<<'\n';
}
int main(){
for(int i=0;i<5;++i){
char a='a';
sum+=i;
printsum();
}
cout<<"i="<<i<<'\n';
cout<<"a="<<a<<'\n';
}
全域變數、區域變數?
#include <iostream>
using namespace std;
int sum=0;
void printsum(){
cout<<sum<<'\n';
}
int main(){
int i;char a;
for(i=0;i<5;++i){
a='a';
sum+=i;
printsum();
}
cout<<"i="<<i<<'\n';
cout<<"a="<<a<<'\n';
}
全域變數、區域變數?
傳給函數的參數也是區域變數
陣列可以開多大?
以int為例
-
全域變數:536867927
-
區域變數:2095884
2GB
8MB
參數型態差異
int f(int a) //pass by value //傳值
int f(int* a) //pass by pointer //傳指標
int f(int& a) //pass by reference //傳參考
傳值 | 傳指標 | 傳參考 | |
---|---|---|---|
作法 | 呼叫時複製一份 | 直接丟指標 | 直接丟參考 |
在函數中修改 會不會影響 原本的值 |
不會 | 會 | 會 |
f(b);
void f(int* a){
*a=5;
}
int main(){
int a=87;
cout<<a<<'\n';
f(&a);
cout<<a<<'\n';
}
87
5
void f(int& a){
a=5;
}
int main(){
int a=87;
cout<<a<<'\n';
f(a);
cout<<a<<'\n';
}
87
5
做的事一模一樣但參考比較好寫
多個名字一樣的函數?
int j(){
return 3;
}
int j(int a){
return a+5;
}
int j(char c){
return c-'0';
}
int main(){
cout<<j()<<'\n';
cout<<j(8)<<'\n';
cout<<j('3')<<'\n';
}
會自動配對!
傳入陣列
int sum(int list[],int len){
int ans=0;
for(int i=0;i<len;++i)ans+=list[i];
return ans;
}
int main(){
int a[10]={1,2,3,4,5,6,7,8,9,10};
cout<<sum(a,10);
}
傳入陣列
int sum(int* list,int len){
int ans=0;
for(int i=0;i<len;++i)ans+=list[i];
return ans;
}
int main(){
int a[10]={1,2,3,4,5,6,7,8,9,10};
cout<<sum(a,10);
}
其實相當於傳入第一格的指標,不是複製一份陣列
但是通常陣列都會宣告為全域變數,所以很少用
遞迴
recursion
自己呼叫自己
int pow(int a,int b){
//return a^b
//b>=0
if(b==0)return 1;
return a*pow(a,b-1);
}
飯粒
遞迴的要素
- 終止條件
- 終止時要幹嘛
- 要怎麼遞迴
b=0
回傳a^0=1
分析複雜度
- 每次呼叫O(1)
- 總共呼叫O(b)次
- 答:O(b)
pow(a,b)
例題
優點
-
寫起來很直觀
-
輕鬆處理複雜問題
缺點
-
比直接用迴圈寫慢,因為要呼叫函數
-
佔記憶體,因為要儲存目前函數的狀態(參數、區域變數)
BTW
兩個函數互相呼叫也算遞迴
比如暴力枚舉下棋的所有可能:
黑棋函數:枚舉黑棋所有可能
白棋函數:枚舉白棋所有可能
例題,有點難
struct、class
結構、類別
通常解題用不到class
因為解題不需要用物件導向的概念
struct是什麼
自訂型態
物件、實體?
-
物件:你自訂的型態
-
實體:用這個型態產生的變數
struct a{
...
};
a b;
struct由什麼組成?
屬性(變數)
方法(函數)
(包含初始化、回收)
語法
struct 結構名字{
變數型態 變數名字;//(屬性)
變數型態 變數名字,變數名字;
函數
}實體名字;//兩種宣告都可以,沒有要宣告就直接分號
結構名字 實體名字;
-
結構中的函數存取自己的變數、函數可以直接用
-
在外面存取實體的變數、函數要用"."
-
另外,物件指標存取變數、函數要用"->"
dot *a;
cout<<a->x;
例子
struct dot{
int x,y;
dot(){
x=y=0;
}
void move(int dx,int dy){
x+=dx;
y+=dy;
cout<<"In dot.move:"<<x<<' '<<y<<'\n';
}
};
int main(){
dot a;
a.move(1,1);
cout<<"In main:"<<a.x<<' '<<a.y<<'\n';
}
In dot.move:1 1
In main:1 1
初始化的酷酷寫法+
初始化時給參數
struct dot{
int x,y;
dot():x(0),y(0){}
dot(int a,int b):x(a),y(b){}
}
運算子重載
自己定義struct的
-
+ - * / %
-
+= -=...
-
輸入輸出(>> <<)
假如你要重載...
a+b
a.operator+(b)
呼叫
假如你要重載...
a+=b
a.operator+=(b)
呼叫
假如你要重載...
a+b
a+=b
a+=3
a++
(a的型態)的operator+(b的型態)
(a的型態)的operator+=(b的型態)
(a的型態)的operator++()
(a的型態)的operator+=(int)
如何重載dot+dot?
//在struct定義的裡面
struct dot{
dot operator+(dot other){
//處理自己+other
}
};
//在struct定義的外面
dot operator+(dot a,dot b){
//處理a+b
}
型態 | 可不可以 |
int+int | X |
int+dot | X |
dot+int | O |
dot+dot | O |
如何重載輸入輸出?
先了解cin cout如何輸入輸出:
- ostream的實體<<a
- istream的實體>>a
ostream的實體<<a的回傳值是一個ostream的參考
cout<<a<<b => (cout<<a)<<b
如何做到一長串輸入輸出?
這跟a=b=3一樣:
a=(b=3)
其實指定運算子都會回傳東西,所以可以
a= b+=5 之類的
( )
int a=1,b=2;
a=b++;
//a=2,b=3
int a=1,b=2;
a=++b;
//a=3,b=3
考考你,這會輸出什麼?
int a=1,b=2;
(a=b)+=5;
cout<<a<<' '<<b<<'\n';
7 2
如何重載輸入輸出?
ostream& operator<<(ostream& s,dot a){
s<<a.x<<' '<<a.y;
return s;
}
istream& operator>>(istream& s,dot& a){
s>>a.x>>a.y;
return s;
}
dot a;
cin>>a;
cout<<a<<'\n';
不常用,底爸(debug)時才會自訂輸出,輸入也不必多此一舉
今天的總結
寫一個鏈結串列
鏈結串列是什麼
記下每個元素的值外
再多記下一個元素是哪個
可以幹嘛?
1
13
8
5
3
2
1
a[7]={
}
普通的陣列要刪掉一個元素時:
可以幹嘛?
1
13
8
5
2
1
a[6]={
}
普通的陣列要刪掉一個元素時:
O(N)!!!!
可以幹嘛?
1
1
鏈結串列要刪掉一個元素時:
3
2
5
8
13
可以幹嘛?
1
1
2
5
8
13
鏈結串列要刪掉一個元素時:
可以幹嘛?
1
1
2
5
8
13
鏈結串列要刪掉一個元素時:
可以幹嘛?
1
1
2
5
8
13
鏈結串列要刪掉一個元素時:
插入元素
可以幹嘛?
1
1
鏈結串列要加一個元素時:
2
8
13
5
可以幹嘛?
1
1
2
8
13
5
3
鏈結串列要加一個元素時:
struct node{
int val;
node *next=NULL;
};
用重載運算子可以幹嘛?
struct node{
int val;
node *next=NULL;
int& operator[](int step){
if(step==0){
return val;
}
if(next==NULL){
cout<<"ERROR\n";
return val;
}
return (*next)[step-1];
}
};
node a,b,c;
a.next=&b;
b.next=&c;
a.val=1;
b.val=2;
c.val=3;
cout<<a[0]<<' '<<a[2]<<'\n';
如果你還嫌太麻煩的話
struct node{
int val;
node *next=NULL;
node(int a):val(a){}
int& operator[](int step){
if(step==0){
return val;
}
if(next==NULL){
cout<<"ERROR\n";
return val;
}
return (*next)[step-1];
}
node& operator+=(node& n){
next=&n;
return *this;
}
};
node a(1),b(2),c(3);
a+=b+=c;
cout<<a[0]<<' '<<a[1]<<' '<<b[1]<<'\n';
例題
#include <iostream>
using namespace std;
struct node{
int val;
bool dead=0;
node *next=NULL;
node& operator+=(node& n){
next=&n;
return *this;
}
};
int const N=1e6+10;
int n,m;
node d[N];
int main(){
cin>>n>>m;
for(int i=1;i<=n-1;++i){
d[i].val=i;
d[i]+=d[i+1];
}
d[n].val=n;
int killer;
for(int i=0;i<m;++i){
cin>>killer;
if(d[killer].dead||d[killer].next==NULL){
cout<<"0u0 ...... ?\n";
continue;
}
cout<<d[killer].next->val<<'\n';
d[killer].next->dead=1;
d[killer]+=*(d[killer].next->next);
}
}
指標、參考、函數、遞迴、運算子重載、結構(暑假資讀)
By kennyfs
指標、參考、函數、遞迴、運算子重載、結構(暑假資讀)
沒過初選QAQ
- 868