
?作者簡介:人工智能專業(yè)本科在讀,喜歡計算機(jī)與編程,寫博客記錄自己的學(xué)習(xí)歷程。
??個人主頁:小嗷犬的個人主頁
??個人網(wǎng)站:小嗷犬的技術(shù)小站
??個人信條:為天地立心,為生民立命,為往圣繼絕學(xué),為萬世開太平。
程序是算法與數(shù)據(jù)結(jié)構(gòu)的載體,是計算機(jī)用以解決問題的工具。
而在程序設(shè)計比賽中,最主流的語言是 C++。
學(xué)習(xí)編程是學(xué)習(xí)程序設(shè)計最 基礎(chǔ) 的部分。
如何開始
環(huán)境配置
工欲善其事,必先利其器。
集成開發(fā)環(huán)境(IDE)
IDE 是 Integrated Development Environment 的縮寫,即集成開發(fā)環(huán)境。它包括了代碼編輯器、編譯器、調(diào)試器等程序從編寫到運(yùn)行的一系列工具。配置較為簡單,適合入門玩家。
在競賽中最常見的是 Dev-C++。Dev-C++ 的優(yōu)點(diǎn)在于界面簡潔友好,安裝便捷,支持單文件編譯,因此成為了許多入門程序設(shè)計選手以及 C++ 語言初學(xué)者的首選。
Dev-C++ 起源于 Colin Laplace 編寫的 Bloodshed Dev-C++。該版本自 2005 年 2 月 22 日停止更新。后續(xù)又有 Orwell Dev-C++、Embarcadero Dev-C++ 等衍生版本。
以上的 Dev-C++ 分發(fā)都被認(rèn)為是「官方的」。此外,在 2015 年 Orwell Dev-C++ 停止更新后,因為教學(xué)需要,一位來自中國的個人開發(fā)者 royqh1979 決定繼續(xù)開發(fā)他的 Dev-C++ 個人分支,命名為小熊貓 Dev-C++,集成了智能提示和高版本的 MinGW64,非常便于國內(nèi)的個人使用和學(xué)習(xí),項目官網(wǎng)位于 小熊貓 Dev-C++,源代碼托管于 Github。
小熊貓官網(wǎng)也提供了相應(yīng)的 小熊貓 Dev-C++ 入門教程,本文推薦使用小熊貓 Dev-C++ 來進(jìn)行 C++ 的學(xué)習(xí)。
編譯器
除了使用 IDE 之外,我們也可以使用編譯器來編譯我們的代碼。這么做的好處是可以分離程序的編寫和編譯過程,使得我們可以選擇更加適合自己的代碼編輯器;同時,也可以使用命令行更靈活地編譯我們的代碼。
常見的編譯器有 GCC、Clang、MSVC 等。
配置相較于 IDE 來說稍微復(fù)雜一些,適合已經(jīng)對 C++ 有一定理解的玩家。
第一個 C++ 程序
通過編寫一個簡單的示例程序來開始我們的 C++ 之旅吧!
#include <iostream> // 引用輸入輸出流頭文件
using namespace std; // 使用標(biāo)準(zhǔn)命名空間
int main()
{ // 定義 main 函數(shù)
cout << "Hello, SWUFE!" << endl; // 輸出 Hello, SWUFE! 并換行
return 0; // 返回 0 表示程序正常結(jié)束
}
與它相對應(yīng)的 C 語言版本如下:
#include <stdio.h> // 引用標(biāo)準(zhǔn)輸入輸出頭文件
int main()
{ // 定義 main 函數(shù)
printf("Hello, SWUFE!\n"); // 輸出 Hello, SWUFE! 并換行
return 0; // 返回 0 表示程序正常結(jié)束
}
注意:本文的 C 語言代碼僅做參考,C++ 基本上兼容 C 語言,并且擁有許多新的功能,可以讓選手在賽場上事半功倍。
C++ 語法基礎(chǔ)
代碼框架
如果你不想深究背后的原理,初學(xué)時可以直接將這個「框架」背下來:
#include <cstdio>
#include <iostream>
using namespace std;
int main()
{
// do something...
return 0;
}
什么是 include
#include
其實是一個預(yù)處理命令,意思為將一個文件放在這條語句處,被放的文件被稱為頭文件。也就是說,在編譯時,編譯器會復(fù)制頭文件 iostream
中的內(nèi)容,粘貼到 #include <iostream>
這條語句處。這樣,你就可以使用 iostream
中提供的 std::cin
、std::cout
、std::endl
等對象了。
如果你學(xué)過 C 語言,你會發(fā)現(xiàn)目前我們接觸的 C++ 中的頭文件一般都不帶 .h
后綴,而那些 C 語言中的頭文件 xx.h
都變成了 cxx
,如 stdio.h
變成了 cstdio
。因為 C++ 為了和 C 保持兼容,都直接使用了 C 語言中的頭文件,為了區(qū)分 C++ 的頭文件和 C 的頭文件,使用了 c
前綴。
通常情況下,我們只需要 #include
自己所需的頭文件,比如上文的 iostream
和 cstdio
,引入它們就可以使用 std::cin
、std::cout
、scanf
、printf
來進(jìn)行輸入輸出了。
什么是 namespace
namespace
是命名空間的意思。C++ 的命名空間機(jī)制可以用來解決復(fù)雜項目中名字沖突的問題。
舉個例子:C++ 標(biāo)準(zhǔn)庫的所有內(nèi)容均定義在 std
命名空間中,如果你定義了一個叫 cin
的變量,則可以通過 cin
來訪問你定義的 cin
變量,通過 std::cin
訪問標(biāo)準(zhǔn)庫的 cin
對象,而不用擔(dān)心產(chǎn)生沖突。
如果你不想每次都寫 std::
,可以使用 using namespace std;
來引入 std
命名空間,這樣你就可以直接使用 cin
、cout
、endl
等對象了。
什么是 main 函數(shù)
可以理解為程序運(yùn)行時就會執(zhí)行 main()
中的代碼。
實際上,main
函數(shù)是由系統(tǒng)或外部程序調(diào)用的。如,你在命令行中調(diào)用了你的程序,也就是調(diào)用了你程序中的 main
函數(shù)。
最后的 return 0;
表示程序運(yùn)行成功。默認(rèn)情況下,程序結(jié)束時返回 0 表示一切正常,否則返回值表示錯誤代碼。這個值返回給誰呢?其實就是調(diào)用你寫的程序的系統(tǒng)或外部程序,它會在你的程序結(jié)束時接收到這個返回值。如果不寫 return
語句的話,程序正常結(jié)束默認(rèn)返回值也是 0。
在 C 或 C++ 中,程序的返回值不為 0 會導(dǎo)致運(yùn)行時錯誤(RE)。
注釋
在 C++ 代碼中,注釋有兩種寫法:
-
單行注釋:
//
-
多行注釋:
/* */
程序運(yùn)行沒有影響,可以用來解釋程序的意思,還可以在讓某段代碼不執(zhí)行(但是依然保留在源文件里)。
在工程開發(fā)中,注釋可以便于日后維護(hù)、他人閱讀。
在程序設(shè)計比賽中,很少有人寫大段的注釋,但注釋可以便于在寫代碼的時候理清思路,或者便于日后復(fù)習(xí)。而且,如果要寫題解、教程的話,適量的注釋可以便于讀者閱讀,理解代碼的意圖。
因此,寫注釋是一個好習(xí)慣。
輸入與輸出
cin 與 cout
在 C++ 中,我們可以使用 cin
和 cout
來進(jìn)行輸入輸出。
#include <iostream>
using namespace std;
int main()
{
int x, y; // 定義兩個 int 類型的變量 x 和 y
cin >> x >> y; // 輸入 x 和 y
cout << y << endl // 輸出 y 換行
<< x << endl; // 輸出 x 換行
return 0;
}
上面程序中,cin
和 cout
分別是輸入流和輸出流的對象,>>
和 <<
分別是輸入運(yùn)算符和輸出運(yùn)算符。
如何理解輸入輸出流呢?我們可以把數(shù)據(jù)流想象成水流,cin
是一個水龍頭,cout
是一個排水口,變量 x
和 y
是兩個容器,>>
和 <<
是連接水龍頭和容器、排水口和容器的管道。cin >> x
的意思就是將水龍頭的水流輸入到容器 x
中,cout << y
的意思就是將容器 y
中的水流輸出到排水口,運(yùn)算符的方向總是指向數(shù)據(jù)流的方向。
事實上,上述的比喻并不嚴(yán)謹(jǐn),因為變量是有多種類型的,這意味著“容器”也有多種類型,自然數(shù)據(jù)流中就不會只有水這一種“液體”。
關(guān)于變量的知識,我們將在后面的章節(jié)中進(jìn)行介紹。
scanf 與 printf
scanf
與 printf
其實是 C 語言提供的函數(shù)。大多數(shù)情況下,它們的速度比 cin
和 cout
更快,并且能夠方便地控制輸入輸出格式。
#include <cstdio>
int main()
{
int x, y;
scanf("%d%d", &x, &y); // 輸入 x 和 y
printf("%d\n%d\n", y, x); // 輸出 y 換行再輸出 x 換行
return 0;
}
// 這段代碼與相同功能的 C 語言代碼只有頭文件的差別
其中,%d
表示讀入/輸出的變量是一個有符號整型(int
型)的變量。這樣的符號稱為格式控制符,用來控制輸入輸出的格式。
常用的控制符有:
控制符 | 說明 |
---|---|
%s |
字符串 |
%c |
字符 |
%d |
有符號十進(jìn)制整數(shù) (int ) |
%lld 或 %I64d (依系統(tǒng)而定) |
長整型 (long long ) |
%lf |
雙精度浮點(diǎn)數(shù) (double ) |
%u |
無符號十進(jìn)制整數(shù) (unsigned int ) |
%llu 或 %I64u (依系統(tǒng)而定) |
無符號長整型 (unsigned long long ) |
格式控制符除了能表示類型外,還能夠控制格式,常用的有:
控制符 | 說明 |
---|---|
%1d |
長度為 1 的整型。 在讀入時,即使沒有空格也可以逐位讀入數(shù)字。 在輸出時,若指定的長度大于數(shù)字的位數(shù),就會在數(shù)字前用空格填充。若指定的長度小于數(shù)字的位數(shù),就沒有效果。 |
%.6lf |
用于輸出,保留 6 位小數(shù)。 |
上面兩個格式控制符相應(yīng)位置填入不同數(shù)字就會有不同的效果,如 %.3lf
表示保留 3 位小數(shù)。
擴(kuò)展內(nèi)容
endl 與 ‘\n’
endl
是 C++ 中的一個 IO 操作符,它的作用是插入一個換行符,并刷新輸出緩沖區(qū)。cout << endl;
事實上等價于 cout << '\n' << flush;
。
大部分情況下 cout << '\n'
和 cout << endl
運(yùn)行起來并無明顯區(qū)別,除非題目要求你輸出之后立即刷新緩沖區(qū)(說的就是你,交互題!)。
C++ 中的空白字符
在 C++ 中,所有空白字符(空格、制表符、換行),多個或是單個,都被視作是一樣的。(當(dāng)然,引號中視作字符串的一部分的不算。)
因此,你可以自由地使用任何代碼風(fēng)格(除了行內(nèi)注釋、字符串字面量與預(yù)處理命令必須在單行內(nèi)),例如:
#include <iostream>
int
main(){
int/**/x, y; std::cin
>> x >>y;
std::cout <<
y <<std::endl
<< x << std::endl
;
return 0; }
這段程序和上文寫的程序一樣,能夠完成相同的事情,讀入兩個整數(shù),然后先輸出第二個整數(shù),再輸出第一個整數(shù),每輸出一個整數(shù)換一行。
當(dāng)然,這么做是不被推薦的。
define 命令
#define
是一種預(yù)處理命令,用于定義宏,本質(zhì)上是文本替換。例如:
#include <iostream>
#define n 666
// n 不是變量,而是編譯器會將代碼中所有 n 文本替換為 666,但是作為標(biāo)識符一部分的
// n 的就不會被替換,如 fn 不會被替換成 f666,同樣,字符串內(nèi)的也不會被替換
int main()
{
std::cout << n; // 輸出 666
return 0;
}
宏可以帶參數(shù),帶參數(shù)的宏可以像函數(shù)一樣使用:
#include <iostream>
#define sum(x, y) ((x) + (y))
#define square(x) ((x) * (x))
int main()
{
std::cout << sum(1, 2) << ' ' << 2 * sum(3, 5) << std::endl; // 輸出 3 16
}
但是帶參數(shù)的宏和函數(shù)有區(qū)別。因為宏是文本替換,所以會引發(fā)許多問題。如:
#include <iostream>
#define sum(x, y) x + y
// 這里應(yīng)當(dāng)為 #define sum(x, y) ((x) + (y))
#define square(x) ((x) * (x))
int main()
{
std::cout << sum(1, 2) << ' ' << 2 * sum(3, 5) << std::endl;
// 輸出為 3 11,因為 #define 是文本替換,后面的語句被替換為了 2* 3 + 5
int i = 1;
std::cout << square(++i) << ' ' << i;
// 輸出未定義,因為 ++i 被執(zhí)行了兩遍
// 而同一個語句中多次修改同一個變量是未定義行為(有例外)
}
因此,使用 #define
是有風(fēng)險的,應(yīng)謹(jǐn)慎使用。
變量
數(shù)據(jù)類型
C++ 的類型系統(tǒng)主要由如下幾部分組成:
- 基礎(chǔ)類型(括號內(nèi)為代表關(guān)鍵詞/代表類型)
- 無類型/
void
型 (void
) - (C++11 起)空指針類型 (
std::nullptr_t
) - 算術(shù)類型
- 整數(shù)類型 (
int
) - 布爾類型/
bool
型 (bool
) - 字符類型 (
char
) - 浮點(diǎn)類型 (
float
,double
)
- 整數(shù)類型 (
- 無類型/
- 復(fù)合類型1
布爾類型
一個 bool
類型的變量取值只可能為兩種:true
和 false
。
一般情況下,一個 bool
類型變量占有 1 字節(jié)(一般情況下,1 字節(jié) = 8 位)的空間。
整數(shù)類型
用于存儲整數(shù),最基礎(chǔ)的整數(shù)類型是 int
。
整數(shù)類型一般按位寬有 5 個梯度:char
, short
, int
, long
, long long
。
C++ 標(biāo)準(zhǔn)保證 1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
,但對于每種類型的位寬并沒有做出具體的規(guī)定。
對于 int
關(guān)鍵字,可以使用如下修飾關(guān)鍵字進(jìn)行修飾:
- 符號性
-
signed
:有符號的(默認(rèn)) -
unsigned
:無符號的
-
- 大小
-
short
:短整型 -
long
:長整型 -
long long
:長長整型
-
下表給出了各個類型的等價形式和位寬:
類型 | 等價形式 | 位寬(C++ 標(biāo)準(zhǔn)) | 位寬(常見) |
---|---|---|---|
signed char |
signed char |
8 8 8 | |
unsigned char |
unsigned char |
8 8 8 | |
short , short int , signed short , signed short int
|
short int |
≥ 16 \geq 16 ≥16 | 16 16 16 |
unsigned short , unsigned short int
|
unsigned short int |
≥ 16 \geq 16 ≥16 | 16 16 16 |
int , signed , signed int
|
int |
≥ 16 \geq 16 ≥16 | 32 32 32 |
unsigned , unsigned int
|
unsigned int |
≥ 16 \geq 16 ≥16 | 32 32 32 |
long , long int , signed long , signed long int
|
long int |
≥ 32 \geq 32 ≥32 | 32 32 32 |
unsigned long , unsigned long int
|
unsigned long int |
≥ 32 \geq 32 ≥32 | 32 32 32 |
long long , long long int , signed long long , signed long long int
|
long long int |
≥ 64 \geq 64 ≥64 | 64 64 64 |
unsigned long long , unsigned long long int
|
unsigned long long int |
≥ 64 \geq 64 ≥64 | 64 64 64 |
對于整數(shù)類型,當(dāng)位寬為 x x x 時,有符號類型的表示范圍為 ? 2 x ? 1 ~ 2 x ? 1 ? 1 -2^{x-1} \sim 2^{x-1}-1 ?2x?1~2x?1?1,無符號類型的表示范圍為 0 ~ 2 x ? 1 0 \sim 2^x-1 0~2x?1。
程序設(shè)計中最常用的兩個整數(shù)類型是 int
和 long long
。
字符類型
分為「窄字符類型」和「寬字符類型」,由于程序設(shè)計競賽幾乎不會用到寬字符類型,故此處僅介紹窄字符類型。
窄字符型位數(shù)一般為 8 位,實際上底層存儲方式仍然是整數(shù),一般通過 ASCII 編碼 實現(xiàn)字符與整數(shù)的一一對應(yīng),有如下三種:
-
signed char
:有符號字符表示的類型,表示范圍在 ? 128 ~ 127 -128 \sim 127 ?128~127 之間。 -
unsigned char
:無符號字符表示的類型,表示范圍在 0 ~ 255 0 \sim 255 0~255 之間。 -
char
:有符號或無符號字符表示的類型,具體由編譯器決定。
事實上,只有 char
類型的變量才應(yīng)該用于表示字符,signed char
和 unsigned char
類型通常被視為整數(shù)類型。
浮點(diǎn)類型
用于存儲「實數(shù)」(注意并不是嚴(yán)格意義上的實數(shù),而是實數(shù)在一定規(guī)則下的近似),包括以下三種:
-
float
:單精度浮點(diǎn)類型。如果支持就會匹配 IEEE-754 binary32 格式。 -
double
:雙精度浮點(diǎn)類型。如果支持就會匹配 IEEE-754 binary64 格式。 -
long double
:擴(kuò)展精度浮點(diǎn)類型。如果支持就會匹配 IEEE-754 binary128 格式,否則如果支持就會匹配 IEEE-754 binary64 擴(kuò)展格式,否則匹配某種精度優(yōu)于 binary64 而值域至少和 binary64 一樣好的非 IEEE-754 擴(kuò)展浮點(diǎn)格式,否則匹配 IEEE-754 binary64 格式。
浮點(diǎn)格式 | 位寬 | 最大正數(shù) | 精度位數(shù) |
---|---|---|---|
IEEE-754 binary32 格式 | 32 32 32 | 3.4 × 1 0 38 3.4 \times 10^{38} 3.4×1038 | 6 ~ 9 6 \sim 9 6~9 |
IEEE-754 binary64 格式 | 64 64 64 | 1.8 × 1 0 308 1.8 \times 10^{308} 1.8×10308 | 15 ~ 17 15 \sim 17 15~17 |
IEEE-754 binary64 擴(kuò)展格式 | ≥ 80 \geq 80 ≥80 | ≥ 1.2 × 1 0 4932 \geq 1.2 \times 10^{4932} ≥1.2×104932 | ≥ 18 ~ 21 \geq 18 \sim 21 ≥18~21 |
IEEE-754 binary128 格式 | 128 128 128 | ≥ 1.2 × 1 0 4932 \geq 1.2 \times 10^{4932} ≥1.2×104932 | 33 ~ 36 33 \sim 36 33~36 |
IEEE-754 浮點(diǎn)格式的最小負(fù)數(shù)是最大正數(shù)的相反數(shù)。
因為 float
類型表示范圍較小,且精度不高,實際應(yīng)用中常使用 double
類型表示浮點(diǎn)數(shù)。
除此之外,浮點(diǎn)類型可以支持一些特殊值:
- 正負(fù)無窮:
INFINITY
/-INFINITY
- 負(fù)零:
-0.0
,例如1.0 / 0.0 == INFINITY
,1.0 / -0.0 == -INFINITY
- 非數(shù) (
NaN
):std::nan
,NAN
, 一般可以由0.0 / 0.0
之類的運(yùn)算產(chǎn)生。它與任何值(包括自身)比較都不相等,C++11 后可以 使用std::isnan
判斷一個浮點(diǎn)數(shù)是不是NaN
無類型
void
類型為無類型,與上面幾種類型不同的是,不能將一個變量聲明為 void
類型。但是函數(shù)的返回值允許為 void
類型,表示該函數(shù)無返回值。
類型轉(zhuǎn)換
在一些時候(比如某個函數(shù)接受 int
類型的參數(shù),但傳入了 double
類型的變量),我們需要將某種類型,轉(zhuǎn)換成另外一種類型。
C++ 中類型的轉(zhuǎn)換機(jī)制較為復(fù)雜,這里主要介紹對于基礎(chǔ)數(shù)據(jù)類型的兩種轉(zhuǎn)換:數(shù)值提升和數(shù)值轉(zhuǎn)換。
數(shù)值提升
數(shù)值提升過程中,值本身保持不變,不會產(chǎn)生精度損失。
整數(shù)提升
小整數(shù)類型(如 char
)的純右值2可轉(zhuǎn)換成較大整數(shù)類型(如 int
)的純右值。
具體而言,算術(shù)運(yùn)算符不接受小于 int
的類型作為它的實參,而在左值到右值轉(zhuǎn)換后,如果適用就會自動實施整數(shù)提升。
具體地,有如下規(guī)則:
- 源類型為
signed char
、signed short
/short
時,可提升為int
。 - 源類型為
unsigned char
、unsigned short
時,若int
能保有源類型的值范圍,則可提升為int
,否則可提升為unsigned int
。 -
char
的提升規(guī)則取決于其底層類型是signed char
還是unsigned char
。 -
bool
類型可轉(zhuǎn)換到int
:false
變?yōu)?0
,true
變?yōu)?1
。 - 若目標(biāo)類型的值范圍包含源類型,且源類型的值范圍不能被
int
和unsigned int
包含,則源類型可提升為目標(biāo)類型3。
浮點(diǎn)提升
位寬較小的浮點(diǎn)數(shù)可以提升為位寬較大的浮點(diǎn)數(shù)(例如 float
類型的變量和 double
類型的變量進(jìn)行算術(shù)運(yùn)算時,會將 float
類型變量提升為 double
類型變量),其值不變。
數(shù)值轉(zhuǎn)換
數(shù)值轉(zhuǎn)換過程中,值可能會發(fā)生改變,可能會產(chǎn)生精度損失。
注意:數(shù)值提升優(yōu)先于數(shù)值轉(zhuǎn)換。如 bool->int
時是數(shù)值提升而非數(shù)值轉(zhuǎn)換。
整數(shù)轉(zhuǎn)換
- 如果目標(biāo)類型為位寬為
x
x
x 的無符號整數(shù)類型,則轉(zhuǎn)換結(jié)果是原值
?
m
o
d
?
2
x
\bmod 2^x
mod2x 后的結(jié)果。
- 若目標(biāo)類型位寬大于源類型位寬:
- 若源類型為有符號類型,一般情況下需先進(jìn)行符號位擴(kuò)展再轉(zhuǎn)換。
- 若源類型為無符號類型,則需先進(jìn)行零擴(kuò)展再轉(zhuǎn)換。
- 若目標(biāo)類型位寬不大于源類型位寬,則需先截斷再轉(zhuǎn)換。
- 若目標(biāo)類型位寬大于源類型位寬:
- 如果目標(biāo)類型為位寬為 x x x 的帶符號整數(shù)類型,則 一般情況下,轉(zhuǎn)換結(jié)果可以認(rèn)為是原值 ? m o d ? 2 x \bmod 2^x mod2x 后的結(jié)果。
- 如果目標(biāo)類型是
bool
,則是 布爾轉(zhuǎn)換。 - 如果源類型是
bool
,則false
轉(zhuǎn)為對應(yīng)類型的0
,true
轉(zhuǎn)為對應(yīng)類型的1
。
浮點(diǎn)轉(zhuǎn)換
位寬較大的浮點(diǎn)數(shù)轉(zhuǎn)換為位寬較小的浮點(diǎn)數(shù),會將該數(shù)舍入到目標(biāo)類型下最接近的值。
浮點(diǎn)整數(shù)轉(zhuǎn)換
- 浮點(diǎn)數(shù)轉(zhuǎn)換為整數(shù)時,會舍棄浮點(diǎn)數(shù)的全部小數(shù)部分。
如果目標(biāo)類型是bool
,則是 布爾轉(zhuǎn)換。 - 整數(shù)轉(zhuǎn)換為浮點(diǎn)數(shù)時,會舍入到目標(biāo)類型下最接近的值。
如果該值不能適應(yīng)到目標(biāo)類型中,那么行為未定義。
如果源類型是bool
,那么false
轉(zhuǎn)換為0
,而true
轉(zhuǎn)換為1
。
布爾轉(zhuǎn)換
將其他類型轉(zhuǎn)換為 bool
類型時,零值轉(zhuǎn)換為 false
,非零值轉(zhuǎn)換為 true
。
定義變量
簡單來說4,定義一個變量,需要包含類型說明符(指明變量的類型),以及要定義的變量名。
例如,下面這幾條語句都是變量定義語句:
int hello;
double swufe;
char acm = '6';
在目前我們所接觸到的程序段中,定義在花括號內(nèi)部的變量是局部變量,而定義在所有花括號外部的變量是全局變量。實際有例外,但是現(xiàn)在不必了解。
定義時沒有初始化值的全局變量會被初始化為 0
。而局部變量沒有這種特性,需要手動賦初始值,否則可能引起難以發(fā)現(xiàn)的 bug。
變量作用域
變量的作用域是指變量在程序中有效的范圍。
- 全局變量的作用域:自其定義之處開始,至文件結(jié)束位置為止。
- 局部變量的作用域:自其定義之處開始,至代碼塊結(jié)束位置為止。
由一對大括號括起來的若干語句構(gòu)成一個代碼塊。
int x = 6; // 定義全局變量
int main()
{
int x = 555; // 定義局部變量
printf("%d\n", x); // 輸出 x
return 0;
}
如果一個代碼塊的內(nèi)嵌塊中定義了相同變量名的變量,則內(nèi)層塊中將無法訪問外層塊中相同變量名的變量。
例如上面的代碼中,輸出的 x
的值將是 555
。因此為了防止出現(xiàn)意料之外的錯誤,請盡量避免局部變量與全局變量重名的情況。
常量
常量,又稱常變量,值為固定值,在程序執(zhí)行期間不會改變。
常量的值在定義后不能被修改。定義時加一個 const
關(guān)鍵字即可。
const double Pi = 3.1415926;
Pi = 4;
如果修改了常量的值,在編譯環(huán)節(jié)就會報錯:error: assignment of read-only variable‘Pi’
。
運(yùn)算
算術(shù)運(yùn)算符
運(yùn)算符 | 功能 |
---|---|
+ (單目) |
取正 |
- (單目) |
取負(fù) |
* |
乘法 |
/ |
除法 |
% |
取模 |
+ |
加法 |
- |
減法 |
單目運(yùn)算符(又稱一元運(yùn)算符)指被操作對象只有一個的運(yùn)算符,而雙目運(yùn)算符(又稱二元運(yùn)算符)的被操作對象有兩個。例如 1 + 2
中加號就是雙目運(yùn)算符,它有 1
和 2
兩個被操作數(shù)。
算術(shù)運(yùn)算符中有兩個單目運(yùn)算符(取正、取負(fù))以及五個雙目運(yùn)算符(乘法、除法、取模、加法、減法),其中單目運(yùn)算符的優(yōu)先級最高。
其中取模運(yùn)算符 %
意為計算兩個整數(shù)相除得到的余數(shù),即求余數(shù)。
C++ 中的算術(shù)運(yùn)算遵循數(shù)學(xué)中加減乘除的優(yōu)先規(guī)律,首先進(jìn)行優(yōu)先級高的運(yùn)算,同優(yōu)先級自左向右運(yùn)算,括號提高優(yōu)先級。
算術(shù)運(yùn)算中的類型轉(zhuǎn)換
對于雙目算術(shù)運(yùn)算符,當(dāng)參與運(yùn)算的兩個變量類型相同時,不發(fā)生類型轉(zhuǎn)換,運(yùn)算結(jié)果將會用參與運(yùn)算的變量的類型容納,否則會發(fā)生類型轉(zhuǎn)換,以使兩個變量的類型一致。
轉(zhuǎn)換的規(guī)則如下:
- 先將
char
、bool
、short
等類型提升至int
(或unsigned int
,取決于原類型的符號性)類型; - 若存在一個變量類型為
long double
,會將另一變量轉(zhuǎn)換為long double
類型; - 否則,若存在一個變量類型為
double
,會將另一變量轉(zhuǎn)換為double
類型; - 否則,若存在一個變量類型為
float
,會將另一變量轉(zhuǎn)換為float
類型; - 否則(即參與運(yùn)算的兩個變量均為整數(shù)類型):
- 若兩個變量符號性一致,則將位寬較小的類型轉(zhuǎn)換為位寬較大的類型;
- 否則,若無符號變量的位寬不小于帶符號變量的位寬,則將帶符號數(shù)轉(zhuǎn)換為無符號數(shù)對應(yīng)的類型;
- 否則,若帶符號操作數(shù)的類型能表示無符號操作數(shù)類型的所有值,則將無符號操作數(shù)轉(zhuǎn)換為帶符號操作數(shù)對應(yīng)的類型;
- 否則,將帶符號數(shù)轉(zhuǎn)換為相對應(yīng)的無符號類型。
例如,對于一個整型 int
變量 x
和另一個雙精度浮點(diǎn)型 double
類型變量 y
:
-
x/5
的結(jié)果將會是整型int
; -
x/5.0
的結(jié)果將會是雙精度浮點(diǎn)型double
; -
x/y
的結(jié)果將會是雙精度浮點(diǎn)型double
; -
x*2/5
的結(jié)果將會是整型int
; -
x*2.0/5
的結(jié)果將會是雙精度浮點(diǎn)型double
;
位運(yùn)算符
運(yùn)算符 | 功能 |
---|---|
~ (單目) |
按位非 |
& |
按位與 |
| |
按位或 |
^ |
按位異或 |
<< |
按位左移 |
>> |
按位右移 |
位運(yùn)算就是基于整數(shù)的二進(jìn)制表示進(jìn)行的運(yùn)算。由于計算機(jī)內(nèi)部就是以二進(jìn)制來存儲數(shù)據(jù),位運(yùn)算是相當(dāng)快的。
需要注意的是,位運(yùn)算符的優(yōu)先級低于普通的算數(shù)運(yùn)算符。
自增/自減 運(yùn)算符
有時我們需要讓變量進(jìn)行增加 1(自增)或者減少 1(自減),這時自增運(yùn)算符 ++
和自減運(yùn)算符 --
就派上用場了。
自增/自減運(yùn)算符可放在變量前或變量后面,在變量前稱為前綴,在變量后稱為后綴,單獨(dú)使用時前綴后綴無需特別區(qū)別,如果需要用到表達(dá)式的值則需注意,具體可看下面的例子。
i = 10;
x1 = i++; // x1 = 10。先 x1 = i,然后 i = i + 1
i = 10;
x2 = ++i; // x2 = 11。先 i = i + 1,然后賦值 x2
i = 10;
x3 = i--; // x3 = 10。先賦值 x3,然后 i = i - 1
i = 10;
x4 = --i; // x4 = 9。先 i = i - 1,然后賦值 x4
復(fù)合賦值運(yùn)算符
復(fù)合賦值運(yùn)算符實際上是表達(dá)式的縮寫形式??煞譃閺?fù)合算術(shù)運(yùn)算符 +=
、-=
、*=
、/=
、%=
和復(fù)合位運(yùn)算符 &=
、|=
、^=
、<<=
、>>=
。
例如,x = x + 2
可寫為 x += 2
,x = x - 2
可寫為 x -= 2
,x = x * 2
可寫為 x *= 2
。
條件運(yùn)算符
條件運(yùn)算符可以看作 if
語句的簡寫,a ? b : c
中如果表達(dá)式 a
成立,那么這個條件表達(dá)式的結(jié)果是 b
,否則條件表達(dá)式的結(jié)果是 c
。
條件運(yùn)算符是 C++ 中唯一的三目運(yùn)算符,所以平常說的三目運(yùn)算符就是指條件運(yùn)算符。
比較運(yùn)算符
運(yùn)算符 | 功能 |
---|---|
> |
大于 |
>= |
大于等于 |
< |
小于 |
<= |
小于等于 |
== |
等于 |
!= |
不等于 |
其中特別需要注意的是要將等于運(yùn)算符 ==
和賦值運(yùn)算符 =
區(qū)分開來,這在判斷語句中尤為重要。
if (x=1)
與 if (x==1)
看起來類似,但實際功能卻相差甚遠(yuǎn)。第一條語句是在對 x
進(jìn)行賦值,若賦值為非 0
時為真值,表達(dá)式的條件始終是滿足的,無法達(dá)到判斷的作用;而第二條語句才是對 x
的值進(jìn)行判斷。
邏輯運(yùn)算符
運(yùn)算符 | 功能 |
---|---|
&& |
邏輯與 |
|| |
邏輯或 |
! |
邏輯非 |
c = a && b; // 當(dāng) a 與 b 都為真時則 c 為真
c = a || b; // 當(dāng) a 或 b 其中一個為真時則 c 為真
c = !a; // 當(dāng) a 為假時則 c 為真
逗號運(yùn)算符
逗號運(yùn)算符可將多個表達(dá)式分隔開來,被分隔開的表達(dá)式按從左至右的順序依次計算,整個表達(dá)式的值是最后的表達(dá)式的值。逗號表達(dá)式的優(yōu)先級在所有運(yùn)算符中的優(yōu)先級是 最低 的。
x1, x2, x3; // 最后的值為 x3 的運(yùn)算結(jié)果。
x = 1 + 2, 3 + 4, 5 + 6; //得到 x 的值為 3 而不是 11
// 因為賦值運(yùn)算符 "=" 的優(yōu)先級比逗號運(yùn)算符高,先進(jìn)行了賦值運(yùn)算才進(jìn)行逗號運(yùn)算。
x = (1 + 2, 3 + 4, 5 + 6); // 得到 x 的值為 11
// 若要讓 x 的值得到逗號運(yùn)算的結(jié)果,則應(yīng)將整個表達(dá)式用括號提高優(yōu)先級。
成員訪問運(yùn)算符
運(yùn)算符 | 功能 |
---|---|
[] |
數(shù)組下標(biāo) |
. |
對象成員 |
& (單目) |
取地址/獲取引用 |
* (單目) |
間接尋址/解引用 |
-> |
指針成員 |
這些運(yùn)算符用來訪問對象的成員或者內(nèi)存。這里還省略了兩個很少用到的運(yùn)算符 .*
和 ->*
,其具體用法可以參見 C++ 語言手冊。
auto result1 = arr[1]; // 獲取 arr 中下標(biāo)為 1 的對象
auto result2 = p.q; // 獲取 p 對象的 q 成員
auto result3 = p -> q; // 獲取 p 指針指向的對象的 q 成員,等價于 (*p).q
auto result4 = &v; // 獲取指向 v 的指針
auto result5 = *v; // 獲取 v 指針指向的對象
C++ 運(yùn)算符優(yōu)先級總表
來自 C++ 運(yùn)算符優(yōu)先級 - cppreference,有修改。
優(yōu)先級 | 運(yùn)算符 | 描述 | 結(jié)合性 |
---|---|---|---|
1 | :: |
作用域解析 | 從左到右 |
2 |
++ -- type() 、type{} f() a[] . 、->
|
后綴自增 后綴自減 函數(shù)風(fēng)格轉(zhuǎn)型 函數(shù)調(diào)用 下標(biāo)訪問 成員訪問 |
從左到右 |
3 |
++a --a +a -a ! ~ (type) *a &a sizeof new new[] delete delete[]
|
前綴自增 前綴自減 取正 取負(fù) 邏輯非 按位非 C 風(fēng)格轉(zhuǎn)型 間接尋址/解引用 取地址/獲取引用 返回類型大小 動態(tài)元素類型分配 動態(tài)數(shù)組類型分配 動態(tài)析構(gòu)元素內(nèi)存 動態(tài)析構(gòu)數(shù)組內(nèi)存 |
從右到左 |
4 |
.* ->*
|
類對象成員引用 類指針成員引用 |
從左到右 |
5 |
* / %
|
乘法 除法 取模 |
從左到右 |
6 |
+ -
|
加法 減法 |
從左到右 |
7 |
<< >>
|
左移 右移 |
從左到右 |
8 | <=> |
三路比較 | 從左到右 |
9 |
< <= > >=
|
小于 小于等于 大于 大于等于 |
從左到右 |
10 |
== !=
|
等于 不等于 |
從左到右 |
11 | & |
按位與 | 從左到右 |
12 | ^ |
按位異或 | 從左到右 |
13 | | |
按位或 | 從左到右 |
14 | && |
邏輯與 | 從左到右 |
15 | || |
邏輯或 | 從左到右 |
16 |
?: throw = += -= *= /= %= <<= >>= &= ^= |=
|
條件運(yùn)算符 拋出異常 賦值 加賦值 減賦值 乘賦值 除賦值 取模賦值 左移賦值 右移賦值 按位與賦值 按位異或賦值 按位或賦值 |
從右到左 |
17 | , |
逗號 | 從左到右 |
流程控制
分支結(jié)構(gòu)
一個程序默認(rèn)是按照代碼的順序執(zhí)行下來的,有時我們需要選擇性的執(zhí)行某些語句,這時候就需要分支的功能來實現(xiàn)。選擇合適的分支語句可以提高程序的效率。
if 語句
基本 if 語句
if (條件)
{
主體;
}
if 語句通過對條件進(jìn)行求值,若結(jié)果為真(非 0),執(zhí)行語句,否則不執(zhí)行。
如果主體中只有單個語句的話,花括號可以省略。
if…else 語句
if (條件)
{
主體1;
}
else
{
主體2;
}
if…else 語句和 if 語句類似,else 不需要再寫條件。當(dāng) if 語句的條件滿足時會執(zhí)行 if 里的語句,if 語句的條件不滿足時會執(zhí)行 else 里的語句。同樣,當(dāng)主體只有一條語句時,可以省略花括號。
else if 語句
if (條件1)
{
主體1;
}
else if (條件2)
{
主體2;
}
else if (條件3)
{
主體3;
}
else
{
主體4;
}
else if 語句是 if 和 else 的組合,對多個條件進(jìn)行判斷并選擇不同的語句分支。在最后一條的 else 語句不需要再寫條件。例如,若條件 1 為真,執(zhí)行主體 1,條件 3 為真而條件 1 和條件 2 都為假,執(zhí)行主體 3,所有的條件都為假才執(zhí)行主體 4。
實際上,這一個語句相當(dāng)于第一個 if 的 else 分句只有一個 if 語句,就將花括號省略之后放在一起了。如果條件相互之間是并列關(guān)系,這樣寫可以讓代碼的邏輯更清晰。
switch 語句
switch (選擇句)
{
case 標(biāo)簽1:
主體1;
case 標(biāo)簽2:
主體2;
default:
主體3;
}
switch 語句執(zhí)行時,先求出選擇句的值,然后根據(jù)選擇句的值選擇相應(yīng)的標(biāo)簽,從標(biāo)簽處開始執(zhí)行。其中,選擇句必須是一個整數(shù)類型表達(dá)式,而標(biāo)簽都必須是整數(shù)類型的常量。例如:
int i = 1; // 這里的 i 的數(shù)據(jù)類型是整型 ,滿足整數(shù)類型的表達(dá)式的要求
switch (i)
{
case 1:
cout << "SWUFE" << endl;
}
char i = 'A';
// 這里的 i 的數(shù)據(jù)類型是字符型 ,但 char
// 也是屬于整數(shù)的類型,滿足整數(shù)類型的表達(dá)式的要求
switch (i)
{
case 'A':
cout << "SWUFE" << endl;
}
switch 語句中還要根據(jù)需求加入 break 語句進(jìn)行中斷,否則在對應(yīng)的 case 被選擇之后接下來的所有 case 里的語句和 default 里的語句都會被運(yùn)行。具體例子可看下面的示例。
char i = 'B';
switch (i)
{
case 'A':
cout << "SWUFE" << endl;
break;
case 'B':
cout << "ACM" << endl;
default:
cout << "Hello World" << endl;
}
以上代碼運(yùn)行后輸出的結(jié)果為 ACM
和 Hello World
,如果不想讓下面分支的語句被運(yùn)行就需要 break 了,具體例子可看下面的示例。
char i = 'B';
switch (i)
{
case 'A':
cout << "SWUFE" << endl;
break;
case 'B':
cout << "ACM" << endl;
break;
default:
cout << "Hello World" << endl;
}
以上代碼運(yùn)行后輸出的結(jié)果為 ACM
,因為 break 的存在,接下來的語句就不會繼續(xù)被執(zhí)行了。最后一個語句不需要 break,因為下面沒有語句了。
處理入口編號不能重復(fù),但可以顛倒。也就是說,入口編號的順序不重要。各個 case(包括 default)的出現(xiàn)次序可任意。例如:
char i = 'B';
switch (i)
{
case 'B':
cout << "ACM" << endl;
break;
default:
cout << "Hello World" << endl;
break;
case 'A':
cout << "SWUFE" << endl;
}
switch 的 case 分句中也可以選擇性的加花括號。不過要注意的是,如果需要在 switch 語句中定義變量,花括號是必須要加的。例如:
char i = 'B';
switch (i)
{
case 'A':
{
int i = 1, j = 2;
cout << "SWUFE" << endl;
ans = i + j;
break;
}
case 'B':
{
int x = 3;
cout << "ACM" << endl;
ans = x * x;
break;
}
default:
{
cout << "Hello World" << endl;
}
}
循環(huán)結(jié)構(gòu)
有時,我們需要做一件事很多遍,為了不寫過多重復(fù)的代碼,我們需要循環(huán)。
有時,循環(huán)的次數(shù)不是一個常量,那么我們無法將代碼重復(fù)多遍,必須使用循環(huán)。
for 循環(huán)
for (初始化; 判斷條件; 更新)
{
循環(huán)體;
}
流程圖:
例如:
// 讀入 n 個數(shù)
for (int i = 0; i < n; ++i)
{
cin >> a[i];
}
for 循環(huán)的三個部分中,任何一個部分都可以省略。其中,若省略了判斷條件,相當(dāng)于判斷條件永遠(yuǎn)為真。
while 循環(huán)
while (判斷條件)
{
循環(huán)體;
}
流程圖:
例如:
// 驗證科拉茨猜想
while (x > 1)
{
if (x % 2 == 1)
{
x = 3 * x + 1;
}
else
{
x = x / 2;
}
}
do…while 循環(huán)
do
{
循環(huán)體;
} while (判斷條件);
流程圖:
與 while 語句的區(qū)別在于,do…while 語句是先執(zhí)行循環(huán)體再進(jìn)行判斷的。
// 枚舉排列
do
{
// do someting...
} while (next_permutation(a + 1, a + n + 1));
三種循環(huán)的聯(lián)系
// for 循環(huán)
for (statement1; statement2; statement3)
{
statement4;
}
// while 循環(huán)
statement1;
while (statement2)
{
statement4;
statement3;
}
在 statement4 中沒有 continue
語句(見下文)的時候是等價的,但是下面一種方法很少用到。
// while 循環(huán)
statement1;
while (statement2)
{
statement1;
}
// do...while 循環(huán)
do
{
statement1;
} while (statement2);
在 statement1 中沒有 continue
語句的時候這兩種方式也也是等價的。
while (1)
{
// do something...
}
for (;;)
{
// do something...
}
do
{
// do something...
} while (1);
這三種方式都是永遠(yuǎn)循環(huán)下去,可以使用 break
(見下文)退出。
可以看出,三種循環(huán)可以彼此代替,但一般來說,循環(huán)的選用遵守以下原則:
- 循環(huán)過程中有個固定的增加步驟(最常見的是枚舉)時,使用 for 循環(huán);
- 只確定循環(huán)的終止條件時,使用 while 循環(huán);
- 使用 while 循環(huán)時,若要先執(zhí)行循環(huán)體再進(jìn)行判斷,使用 do…while 循環(huán)。一般很少用到,常用場景是用戶輸入。
break 與 continue 語句
break
語句的作用是退出循環(huán)。
continue
語句的作用是跳過循環(huán)體的余下部分。下面以 continue
語句在 do…while 語句中的使用為例:
do
{
// do something...
continue; // 等價于 goto END;
// do something...
END:;
} while (statement);
break
與 continue
語句均可在三種循環(huán)語句的循環(huán)體中使用。
一般來說,break
與 continue
語句用于讓代碼的邏輯更加清晰,例如:
// 邏輯較為不清晰,大括號層次復(fù)雜
for (int i = 1; i <= n; ++i)
{
if (i != x)
{
for (int j = 1; j <= n; ++j)
{
if (j != x)
{
// do something...
}
}
}
}
// 邏輯更加清晰,大括號層次簡單明了
for (int i = 1; i <= n; ++i)
{
if (i == x)
continue;
for (int j = 1; j <= n; ++j)
{
if (j == x)
continue;
// do something...
}
}
// for 語句判斷條件復(fù)雜,沒有體現(xiàn)「枚舉」的本質(zhì)
for (int i = l; i <= r && i % 10 != 0; ++i)
{
// do something...
}
// for 語句用于枚舉,break 用于「到何時為止」
for (int i = l; i <= r; ++i)
{
if (i % 10 == 0)
break;
// do something...
}
// 語句重復(fù),順序不自然
statement1;
while (statement3)
{
statement2;
statement1;
}
// 沒有重復(fù)語句,順序自然
while (1)
{
statement1;
if (!statement3)
break;
statement2;
}
高級數(shù)據(jù)類型
數(shù)組
數(shù)組是存放相同類型對象的容器,數(shù)組中存放的對象沒有名字,而是要通過其所在的位置訪問。數(shù)組的大小是固定的,不能隨意改變數(shù)組的長度。
定義數(shù)組
數(shù)組的聲明形如 a[n]
,其中,a
是數(shù)組的名字,n
是數(shù)組中元素的個數(shù)。在編譯時,n
應(yīng)該是已知的,也就是說,n
應(yīng)該是一個整型的常量表達(dá)式。
unsigned int n1 = 42;
const int n2 = 42;
int arr1[n1]; // 錯誤:n1 不是常量表達(dá)式
int arr2[n2]; // 正確:arr2 是一個長度為 42 的數(shù)組
不能將一個數(shù)組直接賦值給另一個數(shù)組:
int arr1[3];
int arr2 = arr1; // 錯誤
arr2 = arr1; // 錯誤
應(yīng)該盡量將較大的數(shù)組定義為全局變量。因為局部變量會被創(chuàng)建在棧區(qū)中,過大(大于棧的大?。┑臄?shù)組會爆棧,進(jìn)而導(dǎo)致 RE。如果將數(shù)組聲明在全局作用域中,就會在靜態(tài)區(qū)中創(chuàng)建數(shù)組。
訪問數(shù)組元素
可以通過下標(biāo)運(yùn)算符 []
來訪問數(shù)組內(nèi)元素,數(shù)組的索引(即方括號中的值)從 0 開始。以一個包含 10 個元素的數(shù)組為例,它的索引為 0 到 9,而非 1 到 10。但在程序設(shè)計中,為了使用方便,我們通常會將數(shù)組開大一點(diǎn),不使用數(shù)組的第一個元素,從下標(biāo) 1 開始訪問數(shù)組元素。
例1
從標(biāo)準(zhǔn)輸入中讀取一個整數(shù) n n n,再讀取 n n n 個數(shù),存入數(shù)組中。其中, n ≤ 1000 n\leq 1000 n≤1000。
#include <iostream>
using namespace std;
int arr[1001]; // 數(shù)組 arr 的下標(biāo)范圍是 [0, 1001)
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
{
cin >> arr[i];
}
}
例2
(接例 1)求和數(shù)組 arr
中的元素,并輸出和。滿足數(shù)組中所有元素的和小于等于
2
31
?
1
2^{31}-1
231?1
#include <iostream>
using namespace std;
int arr[1001];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
{
cin >> arr[i];
}
int sum = 0;
for (int i = 1; i <= n; ++i)
{
sum += arr[i];
}
printf("%d\n", sum);
return 0;
}
越界訪問下標(biāo)
數(shù)組的下標(biāo) i d x \mathit{idx} idx 應(yīng)當(dāng)滿足 0 ≤ i d x < s i z e 0\leq \mathit{idx}< \mathit{size} 0≤idx<size,如果下標(biāo)越界,則會產(chǎn)生不可預(yù)料的后果,如段錯誤(Segmentation Fault),或者修改預(yù)期以外的變量。
多維數(shù)組
多維數(shù)組的實質(zhì)是「數(shù)組的數(shù)組」,即外層數(shù)組的元素是數(shù)組。一個二維數(shù)組需要兩個維度來定義:數(shù)組的長度和數(shù)組內(nèi)元素的長度。訪問二維數(shù)組時需要寫出兩個索引:
int arr[5][4]; // 一個長度為 5 的數(shù)組,它的元素是「元素為 int 的長度為的 4 的數(shù)組」
arr[2][3] = 1; // 訪問二維數(shù)組
我們經(jīng)常使用嵌套的 for 循環(huán)來處理二維數(shù)組。
例
從標(biāo)準(zhǔn)輸入中讀取兩個數(shù) n n n 和 m m m,分別表示黑白圖片的高與寬,滿足 n , m ≤ 1000 n,m\leq 1000 n,m≤1000。對于接下來的 n n n 行數(shù)據(jù),每行有用空格分隔開的 m m m 個數(shù),代表這一位置的亮度值。現(xiàn)在我們讀取這張圖片,并將其存入二維數(shù)組中。
const int maxn = 1001;
int pic[maxn][maxn];
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
cin >> pic[i][j];
類似地,你可以定義三維、四維,以及更高維的數(shù)組。
結(jié)構(gòu)體
結(jié)構(gòu)體(struct),可以看做是一系列稱為成員元素的組合體。
可以看做是自定義的數(shù)據(jù)類型。
本節(jié)描述的 struct
不同于 C 中 struct
,在 C++ 中 struct
被擴(kuò)展為類似 class
的類說明符。
定義結(jié)構(gòu)體
struct Object
{
int weight;
int value;
} e[array_length];
const Object a;
Object b, B[array_length], tmp;
Object *c;
上例中定義了一個名為 Object
的結(jié)構(gòu)體,兩個成員元素 value
、weight
,類型都為 int
。
在 }
后,定義了數(shù)據(jù)類型為 Object
的常量 a
,變量 b
,變量 tmp
,數(shù)組 B
,指針 c
。對于某種已經(jīng)存在的類型,都可以使用這里的方法進(jìn)行定義常量、變量、指針、數(shù)組等。
訪問/修改成員元素
可以使用 變量名.成員元素名
進(jìn)行訪問。
如:輸出 var
的 v
成員:cout << var.v
。
也可以使用 指針名->成員元素名
或者 使用 (*指針名).成員元素名
進(jìn)行訪問。
如:將結(jié)構(gòu)體指針 ptr
指向的結(jié)構(gòu)體的成員元素 v
賦值為 tmp
:(*ptr).v = tmp
或者 ptr->v = tmp
。
結(jié)構(gòu)體的意義
首先,條條大路通羅馬,可以不使用結(jié)構(gòu)體達(dá)到相同的效果。結(jié)構(gòu)體的意義在于,它能夠顯式地將成員元素(在程序設(shè)計比賽中通常是變量)捆綁在一起,如本例中的 Object
結(jié)構(gòu)體,便將 value
、weight
放在了一起(定義這個結(jié)構(gòu)體的實際意義是表示一件物品的重量與價值)。這樣的好處邊是限制了成員元素的使用。
想象一下,如果不使用結(jié)構(gòu)體而使用兩個數(shù)組 value[]
、weight[]
,很容易寫混淆。但如果使用結(jié)構(gòu)體,能夠減輕出現(xiàn)使用變量錯誤的幾率。
并且不同的結(jié)構(gòu)體(結(jié)構(gòu)體類型,如 Object
這個結(jié)構(gòu)體)或者不同的結(jié)構(gòu)體變量(結(jié)構(gòu)體的實例,如上方的 e
數(shù)組)可以擁有相同名字的成員元素(如 tmp.value
、b.value
),同名的成員元素相互獨(dú)立(擁有獨(dú)自的內(nèi)存,比如說修改 tmp.value
不會影響 b.value
的值)。
這樣的好處是可以使用盡可能相同或者相近的變量去描述一個物品。比如說 Object
里有 value
這個成員變量;我們還可以定義一個 Car
結(jié)構(gòu)體,同時也擁有 value
這個成員;如果不使用結(jié)構(gòu)體,或許我們就需要定義 valueOfObject[]
,valueOfCar[]
等不同名稱的數(shù)組來區(qū)分。
如果想要更詳細(xì)的描述一種事物,還可以定義成員函數(shù)。C++ 中的類就是為此而生的。
結(jié)構(gòu)體的弊端
為了訪問內(nèi)存的效率更高,編譯器在處理結(jié)構(gòu)體中成員的實際存儲情況時,可能會將成員對齊在一定的字節(jié)位置,也就意味著結(jié)構(gòu)體中有空余的地方。因此,該結(jié)構(gòu)體所占用的空間可能大于其中所有成員所占空間的總和。
指針
變量的地址、指針
在程序中,我們的數(shù)據(jù)都有其存儲的地址。在程序每次的實際運(yùn)行過程中,變量在物理內(nèi)存中的存儲位置不盡相同。不過,我們?nèi)阅軌蛟诰幊虝r,通過一定的語句,來取得數(shù)據(jù)在內(nèi)存中的地址。
地址也是數(shù)據(jù)。存放地址所用的變量類型有一個特殊的名字,叫做「指針變量」,有時也簡稱做「指針」。
指針變量的大小與機(jī)器的位數(shù)有關(guān),32 位機(jī)器上的指針變量通常是 4 字節(jié),64 位機(jī)器上的指針變量通常是 8 字節(jié)。
地址只是一個刻度一般的數(shù)據(jù),為了針對不同類型的數(shù)據(jù),「指針變量」也有不同的類型,比如,可以有 int
類型的指針變量,其中存儲的地址(即指針變量存儲的數(shù)值)對應(yīng)一塊大小為 32 位的空間的起始地址;有 char
類型的指針變量,其中存儲的地址對應(yīng)一塊 8 位的空間的起始地址。
事實上,用戶也可以聲明指向指針變量的指針變量。
假如用戶自定義了一個結(jié)構(gòu)體:
struct TwoInt
{
int a;
int b;
};
則 TwoInt
類型的指針變量,對應(yīng)著一塊 2 × 32 = 64 bit 的空間。
指針的聲明與使用
C/C++ 中,指針變量的類型為類型名后加上一個星號 *
。比如,int
類型的指針變量的類型名即為 int*
。
我們可以使用 &
符號取得一個變量的地址。
要想訪問指針變量地址所對應(yīng)的空間(又稱指針?biāo)?指向 的空間),需要對指針變量進(jìn)行 解引用(dereference),使用 *
符號。
int main()
{
int a = 123; // a: 123
int *pa = &a;
*pa = 321; // a: 321
}
對結(jié)構(gòu)體變量也是類似。如果要訪問指針指向的結(jié)構(gòu)中的成員,需要先對指針進(jìn)行解引用,再使用 .
成員關(guān)系運(yùn)算符。不過,更推薦使用「箭頭」運(yùn)算符 ->
這一更簡便的寫法。
struct TwoInt
{
int a;
int b;
};
int main()
{
TwoInt x{1, 2}, y{6, 7};
TwoInt *px = &x;
(*px) = y; // x: {6,7}
(*px).a = 4; // x: {4,7}
px->b = 5; // x: {4,5}
}
指針的偏移
指針變量也可以和整數(shù)進(jìn)行加減操作。對于 int
型指針,每加 1(遞增 1),其指向的地址偏移 32 位(即 4 個字節(jié));若加 2,則指向的地址偏移 2 × 32 = 64 位。同理,對于 char
型指針,每次遞增,其指向的地址偏移 8 位(即 1 個字節(jié))。
使用指針偏移訪問數(shù)組
我們前面說過,數(shù)組是一塊連續(xù)的存儲空間。而在 C/C++ 中,直接使用數(shù)組名,得到的是數(shù)組的起始地址。
int main()
{
int a[3] = {0, 1, 2};
int *p = a; // p 指向 a[0]
*p = 4; // a: [4, 1, 2]
p = p + 1; // p 指向 a[1]
*p = 5; // a: [4, 5, 2]
p++; // p 指向 a[2]
*p = 6; // a: [4, 5, 6]
}
當(dāng)通過指針訪問數(shù)組中的元素時,往往需要用到「指針的偏移」,換句話說,即通過一個基地址(數(shù)組起始的地址)加上偏移量來訪問。
我們常用 []
運(yùn)算符來訪問數(shù)組中某一指定偏移量處的元素。比如 a[1]
或者 p[3]
。這種寫法和對指針進(jìn)行運(yùn)算后再引用是等價的,即 p[5]
和 *(p + 5)
是等價的兩種寫法。
空指針
在 C++11 之前,C++ 和 C 一樣使用 NULL 宏表示空指針常量,C++ 中 NULL
的實現(xiàn)一般如下:
// C++11 前
#define NULL 0
空指針和整數(shù) 0 的混用在 C++ 中會導(dǎo)致許多問題,比如:
// 函數(shù)重載
int f(int x);
int f(int* p);
在調(diào)用 f(NULL)
時,實際調(diào)用的函數(shù)的類型是 int(int)
而不是 int(int *)
。
為了解決這些問題,C++11 引入了 nullptr
關(guān)鍵字作為空指針常量。
C++ 規(guī)定 nullptr
可以隱式轉(zhuǎn)換為任何指針類型,這種轉(zhuǎn)換結(jié)果是該類型的空指針值。
nullptr
的類型為 std::nullptr_t
,稱作空指針類型,可能的實現(xiàn)如下:
namespace std
{
typedef decltype(nullptr) nullptr_t;
}
另外,C++11 起 NULL
宏的實現(xiàn)也被修改為了:
// C++11 起
#define NULL nullptr
指針的進(jìn)階使用
使用指針,使得程序編寫者可以操作程序運(yùn)行時中各處的數(shù)據(jù),而不必局限于作用域。
指針類型參數(shù)的使用
在 C/C++ 中,調(diào)用函數(shù)(過程)時使用的參數(shù),均以拷貝的形式傳入子過程中(引用除外,會在后續(xù)介紹)。默認(rèn)情況下,函數(shù)僅能通過返回值,將結(jié)果返回到調(diào)用處。但是,如果某個函數(shù)希望修改其外部的數(shù)據(jù),或者某個結(jié)構(gòu)體/類的數(shù)據(jù)量較為龐大、不宜進(jìn)行拷貝,這時,則可以通過向其傳入外部數(shù)據(jù)的地址,便得以在其中訪問甚至修改外部數(shù)據(jù)。
下面的 swap
方法,通過接收兩個 int
型的指針,在函數(shù)中使用中間變量,完成對兩個 int
型變量值的交換。
void swap(int *x, int *y)
{
int t;
t = *x;
*x = *y;
*y = t;
}
int main()
{
int x = 5, y = 4;
swap(&x, &y);
// 調(diào)用后,main 函數(shù)中 x 變量的值變?yōu)?4,y 變量的值變?yōu)?5
}
C++ 中引入了引用的概念,相對于指針來說,更易用,也更安全。
動態(tài)實例化
除此之外,程序編寫時往往會涉及到動態(tài)內(nèi)存分配,即,程序會在運(yùn)行時,向操作系統(tǒng)動態(tài)地申請或歸還存放數(shù)據(jù)所需的內(nèi)存。當(dāng)程序通過調(diào)用操作系統(tǒng)接口申請內(nèi)存時,操作系統(tǒng)將返回程序所申請空間的地址。要使用這塊空間,我們需要將這塊空間的地址存儲在指針變量中。
在 C++ 中,我們使用 new
運(yùn)算符來獲取一塊內(nèi)存,使用 delete
運(yùn)算符釋放某指針?biāo)赶虻目臻g。
int* p = new int(100);
/* ... */
delete p;
上面的語句使用 new
運(yùn)算符向操作系統(tǒng)申請了一塊 int
大小的空間,將其中的值初始化為 100,并聲明了一個 int
型的指針 p
指向這塊空間。
同理,也可以使用 new
開辟新的對象:
class A
{
int a;
public:
A(int a_) : a(a_) {}
};
int main()
{
A *p = new A(100);
/* ... */
delete p;
}
如上,「new
表達(dá)式」將嘗試開辟一塊對應(yīng)大小的空間,并嘗試在這塊空間上構(gòu)造這一對象,并返回這一空間的地址。
struct TwoInt
{
int a;
int b;
};
int main()
{
TwoInt *p = new TwoInt{3, 2};
/* ... */
delete p;
}
{}
運(yùn)算符可以用來初始化沒有構(gòu)造函數(shù)的結(jié)構(gòu)。除此之外,使用 {}
運(yùn)算符可以使得變量的初始化形式變得統(tǒng)一,詳見 列表初始化 (C++11 起) - cppreference。
需要注意,當(dāng)使用 new
申請的內(nèi)存不再使用時,需要使用 delete
釋放這塊空間。不能對一塊內(nèi)存釋放兩次或以上。而對空指針 nullptr
使用 delete
操作是合法的。
動態(tài)創(chuàng)建數(shù)組
也可以使用 new[]
運(yùn)算符創(chuàng)建數(shù)組,這時 new[]
運(yùn)算符會返回數(shù)組的首地址,也就是數(shù)組第一個元素的地址,我們可以用對應(yīng)類型的指針存儲這個地址。釋放時,則需要使用 delete[]
運(yùn)算符。
int *p = new int[50];
delete[] p;
數(shù)組中元素的存儲是連續(xù)的,即 p + 1
指向的是 p
的后繼元素。
二維數(shù)組
在存放矩陣形式的數(shù)據(jù)時,可能會用到「二維數(shù)組」這樣的數(shù)據(jù)類型。從語義上來講,二維數(shù)組是一個數(shù)組的數(shù)組。而計算機(jī)內(nèi)存可以視作一個很長的一維數(shù)組。要在計算機(jī)內(nèi)存中存放一個二維數(shù)組,便有「連續(xù)」與否的說法。
所謂「連續(xù)」,即二維數(shù)組的任意一行(row)的末尾與下一行的起始,在物理地址上是毗鄰的,換言之,整個二維數(shù)組可以視作一個一維數(shù)組;反之,則二者在物理上不一定相鄰。
對于「連續(xù)」的二維數(shù)組,可以僅使用一個循環(huán),借由一個不斷遞增的指針即可遍歷數(shù)組中的所有數(shù)據(jù)。而對于非連續(xù)的二維數(shù)組,由于每一行不連續(xù),則需要先取得某一行首的地址,再訪問這一行中的元素。
這種按照「行(row)」存儲數(shù)據(jù)的方式,稱為行優(yōu)先存儲;相對的,也可以按照列(column)存儲數(shù)據(jù)。由于計算機(jī)內(nèi)存訪問的特性,一般來說,訪問連續(xù)的數(shù)據(jù)會得到更高的效率。因此,需要按照數(shù)據(jù)可能的使用方式,選擇「行優(yōu)先」或「列優(yōu)先」的存儲方式。
動態(tài)創(chuàng)建二維數(shù)組
在 C/C++ 中,我們可以使用類似下面這樣的語句聲明一個 N 行(row)M 列(column)5的二維數(shù)組,其空間在物理上是連續(xù)的。
int a[N][M];
這種聲明方式要求 N 和 M 為在編譯期即可確定的常量表達(dá)式。
在 C/C++ 中,數(shù)組的第一個元素下標(biāo)為 0,因此 a[r][c]
這樣的式子代表二維數(shù)組 a 中第 r + 1 行的第 c + 1 個元素,我們也稱這個元素的下標(biāo)為 (r,c)
。
不過,實際使用中,(二維)數(shù)組的大小可能不是固定的,需要動態(tài)內(nèi)存分配。
常見的方式是聲明一個長度為 N × M 的 一維數(shù)組6,并通過下標(biāo) r * M + c
訪問二維數(shù)組中下標(biāo)為 (r, c)
的元素。
int* a = new int[N * M];
這種方法可以保證二維數(shù)組是 連續(xù)的。
此外,亦可以根據(jù)「數(shù)組的數(shù)組」這一概念來進(jìn)行內(nèi)存的獲取與使用。對于一個存放的若干數(shù)組的數(shù)組,實際上為一個存放的若干數(shù)組的首地址的數(shù)組,也就是一個存放若干指針變量的數(shù)組。
我們需要一個變量來存放這個「數(shù)組的數(shù)組」的首地址——也就是一個指針的地址。這個變量便是一個「指向指針的指針」,有時也稱作「二重指針」,如:
int** a = new int*[5];
接著,我們需要為每一個數(shù)組申請空間:
for (int i = 0; i < 5; i++)
{
a[i] = new int[5];
}
至此,我們便完成了內(nèi)存的獲取。而對于這樣獲得的內(nèi)存的釋放,則需要進(jìn)行一個逆向的操作:即先釋放每一個數(shù)組,再釋放存儲這些數(shù)組首地址的數(shù)組,如:
for (int i = 0; i < 5; i++)
{
delete[] a[i];
}
delete[] a;
需要注意,這樣獲得的二維數(shù)組,不能保證其空間是連續(xù)的。
還有一種方式,需要使用到「指向數(shù)組的指針」。
int main()
{
int(*a)[5] = new int[5][5];
int *p = a[2];
a[2][1] = 1;
delete[] a;
}
這種方式獲得到的也是連續(xù)的內(nèi)存,但是可以直接使用 a[n]
的形式獲得到數(shù)組的第 n + 1 行(row)的首地址,因此,使用 a[r][c]
的形式即可訪問到下標(biāo)為 (r, c)
的元素。
由于指向數(shù)組的指針也是一種確定的數(shù)據(jù)類型,因此除數(shù)組的第一維外,其他維度的長度均須為一個能在編譯器確定的常量。不然,編譯器將無法翻譯如 a[n]
這樣的表達(dá)式(a
為指向數(shù)組的指針)。
指向函數(shù)的指針
關(guān)于函數(shù)的介紹請參見本文 函數(shù) 章節(jié)。
簡單地說,要調(diào)用一個函數(shù),需要知曉該函數(shù)的參數(shù)類型、個數(shù)以及返回值類型,這些也統(tǒng)一稱作接口類型。
可以通過函數(shù)指針調(diào)用函數(shù)。有時候,若干個函數(shù)的接口類型是相同的,使用函數(shù)指針可以根據(jù)程序的運(yùn)行 動態(tài)地 選擇需要調(diào)用的函數(shù)。換句話說,可以在不修改一個函數(shù)的情況下,僅通過修改向其傳入的參數(shù)(函數(shù)指針),使得該函數(shù)的行為發(fā)生變化。
假設(shè)我們有若干針對 int
類型的二元運(yùn)算函數(shù),則函數(shù)的參數(shù)為 2 個 int
,返回值亦為 int
。下邊是一個使用了函數(shù)指針的例子:
#include <iostream>
int (*binary_int_op)(int, int);
int foo1(int a, int b) { return a * b + b; }
int foo2(int a, int b) { return (a + b) * b; }
int main()
{
int choice;
std::cin >> choice;
if (choice == 1)
{
binary_int_op = foo1;
}
else
{
binary_int_op = foo2;
}
int m, n;
std::cin >> m >> n;
std::cout << binary_int_op(m, n);
}
可以使用 typdef
關(guān)鍵字聲明函數(shù)指針的類型。
typedef int (*p_bi_int_op)(int, int);
這樣我們就可以在之后使用 p_bi_int_op
這種類型,即指向「參數(shù)為 2 個 int
,返回值亦為 int
」的函數(shù)的指針。
函數(shù)
函數(shù)的聲明
編程中的函數(shù)(function)一般是若干語句的集合。我們也可以將其稱作「子過程(subroutine)」。在編程中,如果有一些重復(fù)的過程,我們可以將其提取出來,形成一個函數(shù)。函數(shù)可以接收若干值,這叫做函數(shù)的參數(shù)。函數(shù)也可以返回某個值,這叫做函數(shù)的返回值。
聲明一個函數(shù),我們需要返回值類型、函數(shù)的名稱,以及參數(shù)列表。
// 返回值類型 int
// 函數(shù)的名稱 function
// 參數(shù)列表 int, int
int function(int, int);
如上圖,我們聲明了一個名為 function
的函數(shù),它需要接收兩個 int
類型的參數(shù),返回值類型也為 int
??梢哉J(rèn)為,這個函數(shù)將會對傳入的兩個整數(shù)進(jìn)行一些操作,并且返回一個同樣類型的結(jié)果。
實現(xiàn)函數(shù):編寫函數(shù)的定義
只有函數(shù)的聲明(declaration)還不夠,他只能讓我們在調(diào)用時能夠得知函數(shù)的 接口 類型(即接收什么數(shù)據(jù)、返回什么數(shù)據(jù)),但其缺乏具體的內(nèi)部實現(xiàn),也就是函數(shù)的 定義(definition)。我們可以在 聲明之后的其他地方 編寫代碼 實現(xiàn)(implement)這個函數(shù)(也可以在另外的文件中實現(xiàn),但是需要將分別編譯后的文件在鏈接時一并給出)。
如果函數(shù)有返回值,則需要通過 return
語句,將值返回給調(diào)用方。函數(shù)一旦執(zhí)行到 return
語句,則直接結(jié)束當(dāng)前函數(shù),不再執(zhí)行后續(xù)的語句。
int function(int, int); // 聲明
/* some other code here... */
int function(int x, int y)
{ // 定義
int result = 5 * x + y;
return result;
result = 4; // 這條語句不會被執(zhí)行
}
在定義時,我們給函數(shù)的參數(shù)列表的變量起了名字。這樣,我們便可以在函數(shù)定義中使用這些變量了。
如果是同一個文件中,我們也可以直接將 聲明和定義合并在一起,換句話說,也就是在聲明時就完成定義。
int function(int x, int y) { return 5 * x + y; }
如果函數(shù)不需要有返回值,則將函數(shù)的返回值類型標(biāo)為 void
;如果函數(shù)不需要參數(shù),則可以將參數(shù)列表置空。同樣,無返回值的函數(shù)執(zhí)行到 return;
語句也會結(jié)束執(zhí)行。
void say_happy()
{
cout << "happy!\n";
cout << "happy!\n";
cout << "happy!\n";
return;
cout << "happy!\n"; // 這條語句不會被執(zhí)行
}
函數(shù)的調(diào)用
和變量一樣,函數(shù)需要先被聲明,才能使用。使用函數(shù)的行為,叫做「調(diào)用(call)」。我們可以在任何函數(shù)內(nèi)部調(diào)用其他函數(shù),包括這個函數(shù)自身。函數(shù)調(diào)用自身的行為,稱為 遞歸(recursion)。
在大多數(shù)語言中,調(diào)用函數(shù)的寫法,是 函數(shù)名稱加上一對括號 ()
,如 f()
。如果函數(shù)需要參數(shù),則我們將其需要的參數(shù)按順序填寫在括號中,以逗號間隔,如 f(1, 2)
。函數(shù)的調(diào)用也是一個表達(dá)式,函數(shù)的返回值 就是 表達(dá)式的值。
函數(shù)聲明時候?qū)懗龅膮?shù),可以理解為在函數(shù) 當(dāng)前次調(diào)用的內(nèi)部 可以使用的變量,這些變量的值由調(diào)用處傳入的值初始化??聪旅孢@個例子:
void f(int, int);
/* ... */
void f(int x, int y)
{
x = x * 2;
y = y + 3;
}
/* ... */
a = 1;
b = 1;
// 調(diào)用前:a = 1, b = 1
f(a, b); // 調(diào)用 f
// 調(diào)用后:a = 1, b = 1
在上面的例子中,f(a, b)
是一次對 f
的調(diào)用。調(diào)用時,f
中的 x
和 y
變量,分別由調(diào)用處 a
和 b
的值初始化。因此,在 f
中對變量 x
和 y
的修改,并不會影響到調(diào)用處的變量的值。
如果我們需要在函數(shù)(子過程)中修改變量的值,則需要采用「傳引用」的方式。
void f(int &x, int &y)
{
x = x * 2;
y = y + 3;
}
/* ... */
a = 2;
b = 2;
// 調(diào)用前:a = 2, b = 2
f(a, b); // 調(diào)用 f
// 調(diào)用后:a = 4, b = 5
上述代碼中,我們看到函數(shù)參數(shù)列表中的「int
」后面添加了一個「&
(and 符號)」,這表示對于 int
類型的 引用(reference)。在調(diào)用 f
時,調(diào)用處 a
和 b
變量分別初始化了 f
中兩個對 int
類型的引用 x
和 y
。在 f
中的 x
和 y
,可以理解為調(diào)用處 a
和 b
變量的「別名」,即 f
中對 x
和 y
的操作,就是對調(diào)用處 a
和 b
的操作。
main 函數(shù)
特別的,每個 C/C++ 程序都需要有一個名為 main
的函數(shù)。任何程序都將從 main
函數(shù)開始運(yùn)行。
main
函數(shù)也可以有參數(shù),通過main
函數(shù)的參數(shù),我們可以獲得外界傳給這個程序的指令(也就是「命令行參數(shù)」),以便做出不同的反應(yīng)。
下面是一段調(diào)用了函數(shù)(子過程)的代碼:
#include <iostream>
void say_happy()
{
std::cout << "happy!\n";
std::cout << "happy!\n";
std::cout << "happy!\n";
}
int main()
{
say_happy();
say_happy();
}
文件操作
文件的概念
文件是根據(jù)特定的目的而收集在一起的有關(guān)數(shù)據(jù)的集合。C/C++ 把每一個文件都看成是一個有序的字節(jié)流,每個文件都是以 文件結(jié)束標(biāo)志(EOF)結(jié)束,如果要操作某個文件,程序應(yīng)該首先打開該文件,每當(dāng)一個文件被打開后(請記得關(guān)閉打開的文件),該文件就和一個流關(guān)聯(lián)起來,這里的流實際上是一個字節(jié)序列。
C/C++ 將文件分為文本文件和二進(jìn)制文件。文本文件就是簡單的文本文件(重點(diǎn)),另外二進(jìn)制文件就是特殊格式的文件或者可執(zhí)行代碼文件等。
文件的操作步驟
- 打開文件,將文件指針指向文件,決定打開文件類型;
- 對文件進(jìn)行讀、寫操作(比賽中主要用到的操作,其他一些操作暫時不寫);
- 在使用完文件后,關(guān)閉文件。
freopen 函數(shù)
函數(shù)簡介
函數(shù)用于將指定輸入輸出流以指定方式重定向到文件,包含于頭文件 stdio.h (cstdio)
中,該函數(shù)可以在不改變代碼原貌的情況下改變輸入輸出環(huán)境,但使用時應(yīng)當(dāng)保證流是可靠的。
函數(shù)主要有三種方式:讀、寫和附加。
函數(shù)原型
FILE* freopen(const char* filename, const char* mode, FILE* stream);
參數(shù)說明
-
filename
: 要打開的文件名 -
mode
: 文件打開的模式,表示文件訪問的權(quán)限 -
stream
: 文件指針,通常使用標(biāo)準(zhǔn)文件流 (stdin
/stdout
) 或標(biāo)準(zhǔn)錯誤輸出流 (stderr
) - 返回值:文件指針,指向被打開文件
文件打開格式
-
r
:以只讀方式打開文件,文件必須存在,只允許讀入數(shù)據(jù) (常用) -
r+
:以讀/寫方式打開文件,文件必須存在,允許讀/寫數(shù)據(jù) -
rb
:以只讀方式打開二進(jìn)制文件,文件必須存在,只允許讀入數(shù)據(jù) -
rb+
:以讀/寫方式打開二進(jìn)制文件,文件必須存在,允許讀/寫數(shù)據(jù) -
rt+
:以讀/寫方式打開文本文件,允許讀/寫數(shù)據(jù) -
w
:以只寫方式打開文件,文件不存在會新建文件,否則清空內(nèi)容,只允許寫入數(shù)據(jù) (常用) -
w+
:以讀/寫方式打開文件,文件不存在將新建文件,否則清空內(nèi)容,允許讀/寫數(shù)據(jù) -
wb
:以只寫方式打開二進(jìn)制文件,文件不存在將會新建文件,否則清空內(nèi)容,只允許寫入數(shù)據(jù) -
wb+
:以讀/寫方式打開二進(jìn)制文件,文件不存在將新建文件,否則清空內(nèi)容,允許讀/寫數(shù)據(jù) -
a
:以只寫方式打開文件,文件不存在將新建文件,寫入數(shù)據(jù)將被附加在文件末尾(保留 EOF 符) -
a+
:以讀/寫方式打開文件,文件不存在將新建文件,寫入數(shù)據(jù)將被附加在文件末尾(不保留 EOF 符) -
at+
:以讀/寫方式打開文本文件,寫入數(shù)據(jù)將被附加在文件末尾 -
ab+
:以讀/寫方式打開二進(jìn)制文件,寫入數(shù)據(jù)將被附加在文件末尾
使用方法
讀入文件內(nèi)容:
freopen("data.in", "r", stdin);
// data.in 就是讀取的文件名,要和可執(zhí)行文件放在同一目錄下
輸出文件內(nèi)容:
freopen("data.out", "w", stdout);
// data.out 就是輸出文件的文件名,和可執(zhí)行文件在同一目錄下
關(guān)閉標(biāo)準(zhǔn)輸入/輸出流:
fclose(stdin);
fclose(stdout);
注:printf
/ scanf
/ cin
/ cout
等函數(shù)默認(rèn)使用 stdin
/ stdout
,將 stdin
/ stdout
重定向后,這些函數(shù)將輸入/輸出到被定向的文件。
模板
#include <cstdio>
#include <iostream>
int main(void)
{
freopen("data.in", "r", stdin);
freopen("data.out", "w", stdout);
/*
中間的代碼不需要改變,直接使用 cin 和 cout 即可
*/
fclose(stdin);
fclose(stdout);
return 0;
}
fopen 函數(shù)(選讀)
函數(shù)大致與 freopen
相同,函數(shù)將打開指定文件并返回打開文件的指針。程序設(shè)計中不常用到。
函數(shù)原型
FILE* fopen(const char* path, const char* mode)
各項參數(shù)含義同 freopen
。
可用讀寫函數(shù)(基本)
-
fread
/fwrite
-
fgetc
/fputc
-
fscanf
/fprintf
-
fgets
/fputs
使用方式
FILE *in, *out; // 定義文件指針
in = fopen("data.in", "r");
out = fopen("data.out", "w");
/*
do what you want to do
*/
fclose(stdin);
fclose(stdout);
C++ 的 ifstream / ofstream 文件輸入輸出流
使用方法
讀入文件內(nèi)容:
ifstream fin("data.in");
// data.in 就是讀取文件的相對位置或絕對位置
輸出到文件:
ofstream fout("data.out");
// data.out 就是輸出文件的相對位置或絕對位置
關(guān)閉標(biāo)準(zhǔn)輸入/輸出流
fin.close();
fout.close();
模板
#include <fstream>
using namespace std; // 兩個類型都在 std 命名空間里
ifstream fin("data.in");
ofstream fout("data.out");
int main(void)
{
/*
中間的代碼改變 cin 為 fin ,cout 為 fout 即可
*/
fin.close();
fout.close();
return 0;
}
參考資料
- OI Wiki: https://oi-wiki.org/
- C++ Reference: https://zh.cppreference.com/
-
復(fù)合類型包括數(shù)組類型、引用類型、指針類型、類類型、函數(shù)類型等。由于本文是面向初學(xué)者的,故不在本文做具體介紹。具體請參閱 類型 - cppreference.com ??
-
詳見 值類別 - cppreference。 ??
-
不包含寬字符類型、位域和枚舉類型,詳見 整型轉(zhuǎn)換 - cppreference。 ??
-
定義一個變量時,除了類型說明符之外,還可以包含其他說明符。詳見 聲明 - cppreference。 ??
-
更通用的方式是使用第 n 維(dimension)的說法。對于「行優(yōu)先」的存儲形式,數(shù)組的第一維長度為 N,第二維長度為 M。 ??文章來源:http://www.zghlxwxcb.cn/news/detail-515017.html
-
實際上,數(shù)據(jù)在內(nèi)存中都可以視作線性存放的,因此在一定的規(guī)則下,通過動態(tài)開辟一維數(shù)組的空間,即可在其上存儲 n 維的數(shù)組。 ??文章來源地址http://www.zghlxwxcb.cn/news/detail-515017.html
到了這里,關(guān)于C++ 程序設(shè)計入門的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!