關於 #include <cstdio>
包含printf();和scanf();
c++中printf、scanf比cout、cin效率高很多
演算法題裡cin、cout可能超時,雖然可以優化,但是優化之後還是比不上scanf和printf。
第一章 演算法入門及簡單的順序結構
程式設計是一種控制計算機的方式,和我們平時雙擊開啟檔案、關機、重啟沒有任何區別。
1.變數的定義
變數必須先定義,才可以使用。不能重名。
變數定義的方式:
常用變數型別及範圍:
2.輸入輸出
整數的輸入輸出:
字串的輸入輸出:
輸入輸出多個不同型別的變數:
3.表示式
整數的加減乘除四則運算:
浮點數(小數)的運算:
整型變數的自增、自減:
變數的型別轉換:
4.順序語句
(1)輸出第二個整數:
(2)計算 (a + b) * c的值
(3)帶餘除法
(4)求反三位數:
(5)交換兩個整數
(6)輸出菱形
第二章 printf語句與C++中的判斷結構
學習語言最好的方式就是實踐,每當掌握一個新功能時,就要立即將這個功能應用到實踐中。
一、printf輸出格式
注意:使用printf 時最好新增標頭檔案 #include 。
1.int、float、double、char等型別的輸出格式:
(1)Int:%d
(2)Float: %f, 預設保留6位小數
(3)Double: %lf, 預設保留6位小數
(4)Char: %c, 回車也是一個字元,用’\n’表示
2.所有輸出的變數均可包含在一個字串中:
練習:輸入一個字元,用這個字元輸出一個菱形:
練習:輸入一個整數,表示時間,單位是秒。輸出一個字串,用”時:分:秒”的形式表示這個時間。
3.擴充套件功能
(1)Float, double等輸出保留若干位小數時用:%.4f, %3lf
(2)最小數字寬度
a.%8.3f, 表示這個浮點數的最小寬度為8,保留3位小數,當寬度不足時在前面補空格。
b.%-8.3f,表示最小寬度為8,保留3位小數,當寬度不足時在後面補上空格
c.%08.3f, 表示最小寬度為8,保留3位小數,當寬度不足時在前面補上0
二、if 語句
1. 基本if-else語句
當條件成立時,執行某些語句;否則執行另一些語句。
Else 語句可以省略:
當只有一條語句時,大括號可以省略:
練習:輸入一個整數,輸出這個數的絕對值。
練習:輸入兩個整數,輸出兩個數中較大的那個。
If-else語句內部也可以是if-else語句。
練習:輸入三個整數,輸出三個數中最大的那個。
2. 常用比較運算子
(1) 大於 >
(2) 小於 <
(3) 大於等於 >=
(4) 小於等於 <=
(5) 等於 ==
(6) 不等於 !=
3.If-else 連寫:
輸入一個0到100之間的分數,
如果大於等於85,輸出A;
如果大於等於70並且小於85,輸出B;
如果大於等於60並且小於70,輸出C;
如果小於60,輸出 D;
練習:
1.簡單計算器輸入兩個數,以及一個運算子+, -, *, /,輸出這兩個數運算後的結果。
當運算子是/,且除數是0時,輸出”Divided by zero!”; 當輸入的字元不是+, -, *, /時,輸出”Invalid operator!”。
2.判斷閏年。閏年有兩種情況:
(1)能被100整除時,必須能被400整除;
(2)不能被100整除時,被4整除即可。
輸入一個年份,如果是閏年輸出yes,否則輸出no。
三、條件表示式
(1) 與 &&
(2) 或 ||
(3) 非
例題:輸入三個數,輸出三個數中的最大值。
練習:用一條if語句,判斷閏年。
第三章 C++中的迴圈結構
學習程式語言語法是次要的,思維是主要的。如何把頭腦中的想法變成簡潔的程式碼,至關重要。
學習迴圈語句只需要抓住一點——程式碼執行順序!
一、while迴圈
可以簡單理解為迴圈版的if語句。If語句是判斷一次,如果條件成立,則執行後面的語句;while是每次判斷,如果成立,則執行迴圈體中的語句,否則停止。
練習:求1~100中所有數的立方和。
練習:求斐波那契數列的第n項。f(1)=1, f(2)=1, f(3)=2, f(n)=f(n-1) + f(n-2)。
死迴圈:迴圈永久執行,無法結束。我們要避免寫出死迴圈。
二、do while迴圈
do while迴圈不常用。
do while語句與while語句非常相似。唯一的區別是,do while語句限制性迴圈體後檢查條件。不管條件的值如何,我們都要至少執行一次迴圈。
三、for 迴圈
基本思想:把控制迴圈次數的變數從迴圈體中剝離。
for (init-statement ; condition; expression)
{
statement
}
init-statement可以是宣告語句、表示式、空語句,一般用來初始化迴圈變數;
condition 是條件表示式,和while中的條件表示式作用一樣;可以為空,空語句表示true
expression 一般負責修改迴圈變數,可以為空
練習:求1~100中所有數的立方和。
練習:求斐波那契數列的第n項。f(1)=1, f(2)=1, f(3)=2, f(n)=f(n-1) + f(n-2)。
init-statement可以定義多個變數,expression也可以修改多個變數。
例如求 1 * 10 + 2 * 8 + 3 * 7 + 4 * 6:
四、跳轉語句
1.break
可以提前從迴圈中退出,一般與if語句搭配。
例題:判斷一個大於1的數是否是質數:
2.continue
可以直接跳到當前迴圈體的結尾。作用與if語句類似。
例題:求1~100中所有偶數的和。
五、多層迴圈
練習:列印1~100中的所有質數
練習:輸入一個n,列印n階菱形。n是奇數。
n=9時的結果:
第四章 C++中的陣列
程式 = 邏輯 + 資料,陣列是儲存資料的強而有力的手段。
1.一維陣列
1.1陣列的定義
陣列的定義方式和變數類似。
1.2陣列的初始化
在main函式內部,未初始化的陣列中的元素是隨機的。
1.3訪問陣列元素
通過下標訪問陣列。
練習題1: 使用陣列實現求斐波那契數列的第N項。
練習題2:輸入一個n,再輸入n個整數。將這n個整數逆序輸出。
練習題3:輸入一個n,再輸入n個整數。將這個陣列順時針旋轉k(k <= n)次,最後將結果輸出。
練習題4:輸入n個數,將這n個數按從小到大的順序輸出。
練習題5:計算2的N次方。N <= 10000
2.多維陣列
多維陣列就是陣列的陣列。
Int a[3][4]; // 大小為3的陣列,每個元素是含有4個整數的陣列。
Int arr[10][20][30] = {0}; // 將所有元素初始化為0
// 大小為10的陣列,它的每個元素是含有4個整數的陣列
// 這些陣列的元素是含有30個整數的陣列
練習題:輸入一個n行m列的矩陣,從左上角開始將其按回字形的順序順時針列印出來。
第五章 C++中的字串
字串是計算機與人類溝通的重要手段。
1.字元與整數的聯絡——ASCII碼
每個常用字元都對應一個-128~127的數字,二者之間可以相互轉化:
常用ASCII值:’A’-‘Z’ 是65~90,’a’-‘z’是97-122,’0’-‘9’是48-57。
字元可以參與運算,運算時會將其當做整數:
練習:輸入一行字元,統計出其中數字字元的個數,以及字母字元的個數。
2.字元陣列
字串就是字元陣列加上結束符’\0’。
可以使用字串來初始化字元陣列,但此時要注意,每個字串結尾會暗含一個’\0’字元,因此字元陣列的長度至少要比字串的長度多1!
2.1字元陣列的輸入輸出:
讀入一行字串,包括空格:
2.2字元陣列的常用操作
下面幾個函式需要引入標頭檔案:
#include <string.h>
(1)strlen(str),求字串的長度
(2)strcmp(a, b),比較兩個字串的大小,a < b 返回-1,a == b 返回0,a > b返回1。這裡的比較方式是字典序!
(3)strcpy(a, b),將字串b複製給從a開始的字元陣列。
2.3遍歷字元陣列中的字元:
練習:給定一個只包含小寫字母的字串,請你找到第一個僅出現一次的字元。如果沒有,輸出“no”。
練習:把一個字串中特定的字元全部用給定的字元替換,得到一個新的字串。
3.標準庫型別 string
可變長的字元序列,比字元陣列更加好用。需要引入標頭檔案:
#include
3.1定義和初始化
3.2string 上的操作
(1)string的讀寫:
注意:不能用printf直接輸出string,需要寫成:printf(“%s”, s.c_str());
(2)使用getline讀取一整行
(3)string的empty和size操作(注意size是無符號整數,因此 s.size() <= -1一定成立):
(4)string 的比較:
支援 > < >= <= == !=等所有比較操作,按字典序進行比較。
(5)為string物件賦值:
string s1(10, ‘c’), s2;// s1的內容是 cccccccccc;s2是一個空字串
s1 = s2;// 賦值:用s2的副本替換s1的副本// 此時s1和s2都是空字串
(6)兩個string物件相加:
string s1 = “hello, ”, s2 = “world\n”;
string s3 = s1 + s2;// s3的內容是 hello, world\n
s1 += s2;// s1 = s1 + s2
(7)字面值和string物件相加:
做加法運算時,字面值和字元都會被轉化成string物件,因此直接相加就是將這些字面值串聯起來:
string s1 = “hello”, s2 = “world”;// 在s1和s2中都沒有標點符號
string s3 = s1 + “, “ + s2 + ‘\n’;
當把string物件和字元字面值及字串字面值混在一條語句中使用時,必須確保每個加法運算子的兩側的運算物件至少有一個是string:
string s4 = s1 + “, “; // 正確:把一個string物件和有一個字面值相加
string s5 = “hello” +”, “; // 錯誤:兩個運算物件都不是string
string s6 = s1 + “, “ + “world”; // 正確,每個加法運算都有一個運算子是string
string s7 = “hello” + “, “ + s2; // 錯誤:不能把字面值直接相加,運算是從左到右進行的
3.3 處理string物件中的字元
可以將string物件當成字元陣列來處理:
或者使用基於範圍的for語句:
練習:密碼翻譯,輸入一個只包含小寫字母的字串,將其中的每個字母替換成它的後繼字母,如果原字母是’z’,則替換成’a’。
練習:輸入兩個字串,驗證其中一個串是否為另一個串的子串。
第六章 C++中的函式
函式讓程式碼變得更加簡潔。
1.函式基礎
一個典型的函式定義包括以下部分:返回型別、函式名字、由0個或多個形參組成的列表以及函式體。
1.1編寫函式
我們來編寫一個求階乘的程式。程式如下所示:
int fact(int val)
{
int ret = 1;
while (val > 1)
ret *= val -- ;
return ret;
}
函式名字是fact,它作用於一個整型引數,返回一個整型值。return語句負責結束fact並返回ret的值。
1.2呼叫函式
int main()
{
int j = fact(5);
cout \<\< "5! is " \<\< j \<\< endl;
return 0;
}
函式的呼叫完成兩項工作:一是用實參初始化函式對應的形參,二是將控制權轉移給被呼叫函式。此時,主調函式的執行被暫時中斷,被調函式開始執行。
1.3形參和實參
實參是形參的初始值。第一個實參初始化第一個形參,第二個實參初始化第二個形參,依次類推。形參和實參的型別和個數必須匹配。
fact(“hello”); // 錯誤:實參型別不正確
fact(); // 錯誤:實引數量不足
fact(42, 10, 0); // 錯誤:實引數量過多
fact(3.14); // 正確:該實參能轉換成int型別,等價於fact(3);
形參也可以設定預設值,但所有預設值必須是最後幾個。當傳入的實參個數少於形參個數時,最後沒有被傳入值的形參會使用預設值。
1.4函式的形參列表
函式的形參列表可以為空,但是不能省略。
void f1() {/* …. */}// 隱式地定義空形參列表
void f2(void) {/* … */} // 顯式地定義空形參列表
形參列表中的形參通常用逗號隔開,其中每個形參都是含有一個宣告符的宣告。即使兩個形參的型別一樣,也必須把兩個型別都寫出來:
int f3(int v1, v2) {/* … */} // 錯誤
int f4(int v1, int v2) {/* … */} // 正確
1.5函式返回型別
大多數型別都能用作函式的返回型別。一種特殊的返回型別是void,它表示函式不返回任何值。函式的返回型別不能是陣列型別或函式型別,但可以是指向陣列或者函式的指標。
1.6區域性變數、全域性變數與靜態變數
區域性變數只可以在函式內部使用,全域性變數可以在所有函式內使用。當區域性變數與全域性變數重名時,會優先使用區域性變數。
2.引數傳遞
2.1傳值引數
當初始化一個非引用型別的變數時,初始值被拷貝給變數。此時,對變數的改動不會影響初始值。
2.2傳引用引數
當函式的形參為引用型別時,對形參的修改會影響實參的值。使用引用的作用:避免拷貝、讓函式返回額外資訊。
2.3陣列形參
在函式中對陣列中的值的修改,會影響函式外面的陣列。
一維陣列形參的寫法:
// 儘管形式不同,但這三個print函式是等價的
void print(int *a) {/* … */}
void print(int a[]) {/* … */}
void print(int a[10]) {/* … */}
多維陣列形參的寫法:
// 多維陣列中,除了第一維之外,其餘維度的大小必須指定
void print(int (*a)[10]) {/* … */}
void print(int a[][10]) {/* … */}
3.返回型別和return語句
return 語句終止當前正在執行的函式並將控制權返回到呼叫該函式的地方。return語句有兩種形式:
return;
return expression;
3.1無返回值函式
沒有返回值的return語句只能用在返回型別是void的函式中。返回void的函式不要求非得有return語句,因為在這類函式的最後一句後面會隱式地執行return。
通常情況下,void函式如果想在它的中間位置提前退出,可以使用return語句。return的這種用法有點類似於我們用break語句退出迴圈。
void swap(int \&v1, int \&v2)
{
// 如果兩個值相等,則不需要交換,直接退出
if (v1 == v2)
return;
// 如果程式執行到了這裡,說明還需要繼續完成某些功能
int tmp = v2;
v2 = v1;
v1 = tmp;
// 此處無須顯示的return語句
}
3.2有返回值的函式
只要函式的返回型別不是void,則該函式內的每條return語句必須返回一個值。return語句返回值的型別必須與函式的返回型別相同,或者能隱式地轉換函式的返回型別。
4.函式遞迴
在一個函式內部,也可以呼叫函式本身。
第七章 類、結構體、指標、引用
類可以將變數、陣列和函式完美地打包在一起。
1.類與結構體
類的定義:
類中的變數和函式被統一稱為類的成員變數。
private後面的內容是私有成員變數,在類的外部不能訪問;public後面的內容是公有成員變數,在類的外部可以訪問。
類的使用:
\#include \<iostream\>
using namespace std;
const int N = 1000010;
class Person
{
private:
int age, height;
double money;
string books[100];
public:
string name;
void say()
{
cout \<\< "I'm " \<\< name \<\< endl;
}
int set_age(int a)
{
age = a;
}
int get_age()
{
return age;
}
void add_money(double x)
{
money += x;
}
} person_a, person_b, persons[100];
int main()
{
Person c;
c.name = "yxc"; // 正確!訪問公有變數
c.age = 18; // 錯誤!訪問私有變數
c.set_age(18); // 正確!set_age()是共有成員變數
c.add_money(100);
c.say();
cout \<\< c.get_age() \<\< endl;
return 0;
}
結構體和類的作用是一樣的。不同點在於類預設是private,結構體預設是public。
2.指標和引用
指標指向存放變數的值的地址。因此我們可以通過指標來修改變數的值。
陣列名是一種特殊的指標。指標可以做運算:
引用和指標類似,相當於給變數起了個別名。
3.連結串列
第八章 C++ STL
STL是提高C++編寫效率的一個利器。
1.#include <vector>
vector是變長陣列,支援隨機訪問,不支援在任意位置O(1)插入。為了保證效率,元素的增刪一般應該在末尾進行。
宣告
#include <vector> 標頭檔案
vector<int> a;相當於一個長度動態變化的int陣列
vector<int> b[233];相當於第一維長233,第二位長度動態變化的int陣列
struct rec{…};
vector<rec> c;自定義的結構體型別也可以儲存在vector中
size/empty
size函式返回vector的實際長度(包含的元素個數),empty函式返回一個bool型別,表明vector是否為空。二者的時間複雜度都是O(1)。
所有的STL容器都支援這兩個方法,含義也相同,之後我們就不再重複給出。
clear
clear函式把vector清空。
迭代器
迭代器就像STL容器的“指標”,可以用星號“*”操作符解除引用。
一個儲存int的vector的迭代器宣告方法為:
vector<int>::iterator it;
vector的迭代器是“隨機訪問迭代器”,可以把vector的迭代器與一個整數相加減,其行為和指標的移動類似。可以把vector的兩個迭代器相減,其結果也和指標相減類似,得到兩個迭代器對應下標之間的距離。
begin/end
begin函式返回指向vector中第一個元素的迭代器。例如a是一個非空的vector,則a.begin()與a[0]的作用相同。
所有的容器都可以視作一個“前閉後開”的結構,end函式返回vector的尾部,即第n個元素再往後的“邊界”。a.end()與a[n]都是越界訪問,其中n=a.size()。
下面兩份程式碼都遍歷了vector<int>a,並輸出它的所有元素。
for (int I = 0; I \< a.size(); I ++) cout \<\< a[i] \<\< endl;
for (vector\<int\>::iterator it = a.begin(); it != a.end(); it ++) cout \<\< *it \<\< endl;
front/back
front函式返回vector的第一個元素,等價於a.begin() 和 a[0]。
back函式返回vector的最後一個元素,等價於==a.end() 和 a[a.size() – 1]。
push_back() 和 pop_back()
a.push_back(x) 把元素x插入到vector a的尾部。
b.pop_back() 刪除vector a的最後一個元素。
2.#include <queue>
標頭檔案queue主要包括迴圈佇列queue和優先佇列priority_queue兩個容器。
宣告
queue\<int\> q;
struct rec{…}; queue\<rec\> q; //結構體rec中必須定義小於號
priority_queue\<int\> q;// 大根堆
priority_queue<int, vector\<int\>, greater\<int\> q;// 小根堆
priority_queue\<pair\<int, int\>\>q;
迴圈佇列 queue
push 從隊尾插入
pop 從隊頭彈出
front 返回隊頭元素
back 返回隊尾元素
優先佇列 priority_queue
push 把元素插入堆
pop 刪除堆頂元素
top 查詢堆頂元素(最大值)
3.#include <stack>
標頭檔案stack包含棧。宣告和前面的容器類似。
push 向棧頂插入
pop 彈出棧頂元素
4.#include <deque>
雙端佇列deque是一個支援在兩端高效插入或刪除元素的連續線性儲存空間。它就像是vector和queue的結合。與vector相比,deque在頭部增刪元素僅需要O(1)的時間;與queue相比,deque像陣列一樣支援隨機訪問。
[] 隨機訪問
begin/end,返回deque的頭/尾迭代器
front/back 隊頭/隊尾元素
push_back 從隊尾入隊
push_front 從隊頭入隊
pop_back 從隊尾出隊
pop_front 從隊頭出隊
clear 清空佇列
5.#include <set>
標頭檔案set主要包括set和multiset兩個容器,分別是“有序集合”和“有序多重集合”,即前者的元素不能重複,而後者可以包含若干個相等的元素。set和multiset的內部實現是一棵紅黑樹,它們支援的函式基本相同。
宣告
set\<int\> s;
struct rec{…}; set\<rec\> s;// 結構體rec中必須定義小於號
multiset\<double\> s;
size/empty/clear
與vector類似
迭代器
set和multiset的迭代器稱為“雙向訪問迭代器”,不支援“隨機訪問”,支援星號(*)解除引用,僅支援”++”和--“兩個與算術相關的操作。
設it是一個迭代器,例如set<int>::iterator it;
若把it++,則it會指向“下一個”元素。這裡的“下一個”元素是指在元素從小到大排序的結果中,排在it下一名的元素。同理,若把it--,則it將會指向排在“上一個”的元素。
begin/end
返回集合的首、尾迭代器,時間複雜度均為O(1)。
s.begin() 是指向集合中最小元素的迭代器。
s.end() 是指向集合中最大元素的下一個位置的迭代器。換言之,就像vector一樣,是一個“前閉後開”的形式。因此--s.end()是指向集合中最大元素的迭代器。
insert
s.insert(x)把一個元素x插入到集合s中,時間複雜度為O(logn)。
在set中,若元素已存在,則不會重複插入該元素,對集合的狀態無影響。
find
s.find(x) 在集合s中查詢等於x的元素,並返回指向該元素的迭代器。若不存在,則返回s.end()。時間複雜度為O(logn)。
lower_bound/upper_bound
這兩個函式的用法與find類似,但查詢的條件略有不同,時間複雜度為 O(logn)。
s.lower_bound(x) 查詢大於等於x的元素中最小的一個,並返回指向該元素的迭代器。
s.upper_bound(x) 查詢大於x的元素中最小的一個,並返回指向該元素的迭代器。
erase
設it是一個迭代器,s.erase(it) 從s中刪除迭代器it指向的元素,時間複雜度為O(logn)
設x是一個元素,s.erase(x) 從s中刪除所有等於x的元素,時間複雜度為O(k+logn),其中k是被刪除的元素個數。
count
s.count(x) 返回集合s中等於x的元素個數,時間複雜度為 O(k +logn),其中k為元素x的個數。
6.#include <map>
map容器是一個鍵值對key-value的對映,其內部實現是一棵以key為關鍵碼的紅黑樹。Map的key和value可以是任意型別,其中key必須定義小於號運算子。
宣告
map<key_type, value_type> name;
例如:
map\<long, long, bool\> vis;
map\<string, int\> hash;
map\<pair\<int, int\>, vector\<int\>\> test;
size/empty/clear/begin/end均與set類似。
Insert/erase
與set類似,但其引數均是pair<key_type, value_type>。
find
h.find(x) 在變數名為h的map中查詢key為x的二元組。
[]操作符
h[key] 返回key對映的value的引用,時間複雜度為O(logn)。
[]操作符是map最吸引人的地方。我們可以很方便地通過h[key]來得到key對應的value,還可以對h[key]進行賦值操作,改變key對應的value。
第九章 位運算與常用庫函式
C++幫我們實現好了很多有用的函式,我們要避免重複造輪子。
1.位運算
& 與
| 或
~ 非
^ 異或
>> 右移
<< 左移
常用操作:
(1)求x的第k位數字 x >> k & 1
(2)lowbit(x) = x & -x,返回x的最後一位1
2.常用庫函式、
(1)reverse 翻轉
翻轉一個vector:
reverse(a.begin(), a.end());
翻轉一個陣列,元素存放在下標1~n:
reverse(a + 1, a + 1 + n);
(2)unique 去重
返回去重之後的尾迭代器(或指標),仍然為前閉後開,即這個迭代器是去重之後末尾元素的下一個位置。該函式常用於離散化,利用迭代器(或指標)的減法,可計算出去重後的元素個數。
把一個vector去重:
int m = unique(a.begin(), a.end()) – a.begin();
把一個陣列去重,元素存放在下標1~n:
int m = unique(a + 1, a + 1 + n) – (a + 1);
(3)random_shuffle 隨機打亂
用法與reverse相同
(4)sort
對兩個迭代器(或指標)指定的部分進行快速排序。可以在第三個引數傳入定義大小比較的函式,或者過載“小於號”運算子。
把一個int陣列(元素存放在下標1~n)從大到小排序,傳入比較函式:
int a[MAX_SIZE];
bool cmp(int a, int b) {return a > b; }
sort(a + 1, a + 1 + n, cmp);
把自定義的結構體vector排序,過載“小於號”運算子:
struct rec{ int id, x, y; }
vector\<rec\> a;
bool operator \<(const rec &a, const rec &b) {
return a.x \< b.x \|\| a.x == b.x \&\& a.y \< b.y;
}
sort(a.begin(), a.end());
(5)lower_bound/upper_bound 二分
lower_bound 的第三個引數傳入一個元素x,在兩個迭代器(指標)指定的部分上執行二分查詢,返回指向第一個大於等於x的元素的位置的迭代器(指標)。
upper_bound 的用法和lower_bound大致相同,唯一的區別是查詢第一個大於x的元素。當然,兩個迭代器(指標)指定的部分應該是提前排好序的。
在有序int陣列(元素存放在下標1~n)中查詢大於等於x的最小整數的下標:
int I = lower_bound(a + 1, a + 1 + n,. x) – a;
在有序vector<int> 中查詢小於等於x的最大整數(假設一定存在):
int y = *--upper_bound(a.begin(), a.end(), x);