1. 程式人生 > 其它 >《演算法筆記》C&C++快速入門指南

《演算法筆記》C&C++快速入門指南

1 基本資料型別

1.1 變數的定義

  1. 定義格式
變數型別 變數名; 
變數型別 變數名 = 初值;   //變數可以在定義的時候就賦初值 
  1. 變數名取名滿足條件:

    1. 不能是C語言識別符號,建議取有實際意義的變數名,這樣可以提高程式可讀性

    2. 變數名的第一個字元必須是字母下劃線,除第一個字元之外的其他字元必須是字母、數字或下劃線

    3. 區分大小寫

1.2 變數型別

整型

整型可以分為短整型(short)、整型(int)、長整型(long long),其中短整型(short)一般用不到,整型int也被稱為long int,長整型也被稱為long long int.

  1. 整型int

    • 一個整數佔用32bit(即32位),也即4Byte(即4位元組),取值範圍是$-2^{31}- +(2{31}-1)$,絕對值在$10
      9$範圍以內的整數都可以定義成int
  2. 長整型long long

    • 一個整數佔用64bit,也即8Byte,取值範圍是-2^{63} - +(2{63}-1),如果題目要求的整數取值範圍超過2147483647(例如10{10}),就得用long long型來儲存,如果long long型賦大於2^{31}-1的初值,則需要在初值後面加上LL,否則會編譯錯誤

    • 在整型資料前面加unsigned,以表示無符號型。例如unsigned int和unsigned long long,佔用的位數和原先相同,但是把負數範圍挪到正數上來了。unsigned int的取值範圍是$0-2^{32}-1$

浮點型

對於浮點型來說,不要使用float,碰到浮點型的資料都應該用double來儲存

  1. 單精度float

    • 浮點數的範圍是$-2{128}-+2{128}$,有效精度有6~7
  2. 雙精度double

    • 一個浮點數佔用64bit,範圍是$-2{1024}-+2{1024}$,其有效精度有15~16

字元型

  1. 字元變數和字元常量

    • 字元常量使用ASCII碼統一編碼,標準ASCII碼的範圍是0~127。

    • 小寫字母比大寫字母的ASCII碼值大32

    • 字元常量(必須是單個字元)必須用單引號標註,%c是char型的輸出格式

  2. 轉義字元

    • \n:換行,\t:Tab鍵

    • \0:代表空字元NULL,其ASCII碼為0,請注意\0不是空格

  3. 字串常量

    • 字串常量是由雙引號標記的在字符集

    • 字串常量可以作為初值賦給字元陣列,並使用%s的格式輸出

    • 不能把字串常量賦值給字元變數,因此char c = "abcd"的寫法是不允許的

布林型

  • 布林型在C++中可以直接使用,但是在C語言中必須新增stdbool.h標頭檔案才可以使用

  • “bool型變數”取值只能是true(真)或者false(假)

  • 整型常量在賦值給布林型變數時會自動轉換為true(非零)或者false(零),true和false在儲存時分別為1和0

1.3 強制型別轉換

  • 格式:(新型別名)變數

  • %.1f是指保留一位小數輸出

  • 如果將一個型別的變數賦值給另一個型別的變數,卻沒有寫強制型別轉換操作,那麼編譯器會自動轉換。不是任何時候都可以不寫型別轉換,因為如在計算時需要型別轉換,那麼久不能等它算完再在賦值的時候轉換。

1.4 符號常量和const常量

  • 用一個識別符號來替代常量,又稱"巨集定義"或者"巨集替換"
#define 識別符號 常量
#define pi 3.14   //末尾不加分號

#const 資料型別 變數名 = 常量;
#const double pi = 3.14;
//兩種寫法都為常量,一旦確定其值後就無法改變   
  • define除了可以定義常量外,其實可以定義任何語句或片段
#define 識別符號 任何語句或片段
#define ADD(a,b) ((a)+(b))//直接使用ADD(a,b)來代替a+b的功能
//為保險起見,多加括號是必須的,巨集定義是直接對應的部分替換,然後才進行編譯和執行,因為替換的部分直接原封不動替換進去
//儘量不要使用巨集定義來做除了定義常量以外的事情,除非給能加的地方都加上引號

1.5運算子

算術運算子

  • +,-,*,/,%,++,- -

  • 對於除法運算子,當被除數跟除數都是整數時,並不會得到double浮點型的數,而是直接捨去小數部分(向下取整)

  • 除數如果為0,會導致程式異常退出或是得到錯誤輸出"1.#INF00"

  • 取模運算返回被除數與除數相除得到的餘數

  • 自增運算子:i++是先使用i再將i加1,而++i則是先將i加1再使用i

關係運算符

<、>、<=、>=、==、!=

邏輯運算子

&&、!、||

條件運算子

( ? : )三目運算子

A?B:C;
//如果A為真,那麼執行並返回B的結果;如果A為假,那麼執行並返回C的結果

位運算子

  • 位運算子使用得較少,最常用的是左移運算子

  • 由於int型的上限為$2{31}-1$,因此程式中無窮大的數INF可以設定成(1<<31)-1(注意:必須加括號,因為位運算子的優先順序沒有算術運算子高),但一般更常用的是$2{30}-1$,二進位制的形式為0x3fffffff

const int INF = (1<<30)-1;
const int INF = 0x3fffffff;
  • 六種位運算子

  • <<(左移a<<x):整數a按二進位制位左移x位

  • (右移 a>>x):整數a按二進位制位右移x位

  • &(位與 a&b):整數a和b按二進位制對齊,按位進行與運算(除了11得1,其他均為0)

  • |(位或 a|b):整數a和b按二進位制對齊,按位進行或運算(除了00得0,其他均為1)

  • ^(位抑或 a^b):整數a和b按二進位制對齊,按位進行抑或運算(相同為0,不同為1)

  • ~(位取反 ~a):整數a的二進位制的每一位進行0變1、1變0的操作

2 順序結構

2.1 賦值表示式

  • 等號來表示賦值運算
int n = 5;
n = 6;
int m;
n = m = 5; 
  • 賦值運算子可以通過將其他運算子放在前面來實現賦值操作的簡,如n+=2
    n/= m+1等價於n=n/(m+1)

2.2 使用scanf和printf輸入/輸出

scanf

  • 格式:scanf("格式控制",變數地址); 例如scanf("%d",&n);

  • 為了得到變數的地址,需要在變數前加一個&(稱為取地址運算子),也就是"&變數名"的寫法

  • 常見資料型別變數的scanf格式符

資料型別 格式符
int %d
long long %lld
float %f
double %lf
char %c
字串(char陣列) %s
  • 陣列名稱本身就代表了這個陣列第一個元素的地址,所以不需要再加取地址運算scanf("%s",str)
    在scanf中,除了char陣列整個輸入的情況不加&之外,其他變數型別都需要加&

  • scanf的雙引號的內容其實就是整個輸入,只不過把資料換成它們對應的格式符並把變數的地址按次序寫在後面而已

  • 除%c外,scanf對其他格式符(如%d)的輸入是以空白符(即空格、換行等)為結束判斷標誌的,字元陣列使用%s讀入的時候以空格跟換行為讀入結束的標誌

  • 在寫scanf時不要忘記&

print

  • 格式:printf("格式控制",變數名稱);

  • 常見的printf格式表只有double型別與scanf格式不同,double型別的變數,其輸出格式變成了%f,而在scanf中卻是%lf

  • 在printf中可以使用轉義字元,如果想要輸出%或\,則需要在前面再加一個%或/。

printf("%%");
printf("\\");  
  • 三種實用輸出格式

  • %md:事不足m位的int型變數以m位進行右對齊輸出

  • %0md:當變數不足m位時,將在前面補足足夠數量的0而不是空格。

  • %.mf:讓浮點數保留m位小數輸出,精度使用“四捨六入五成雙”規則(如果是四捨五入,那麼需要用到round函式)%m.nf:m控制寬度,n表示保留位數

2.3使用getchar和putchar輸入/輸出字元

  • getchar用來輸入單個字元,putchar用來輸出單個字元,getchar可以識別換行符

2.4 註釋

  • 多行註釋:/**/

  • 單行註釋://

2.5 typedef

  • 給複雜的資料型別起一個別名
#include<cstdio>
typedef long long LL;     //給long long起個別名LL
int main()
{
   LL a = 123456789012345LL;
   printf("%lld\n",a);
   return 0;
}

2.6 常用math函式

  1. fabs(double x):用於對double型變數取絕對值

  2. floor(double x)和ceil(double x):分別用於double型變數的向下取整和向上取整,返回型別為double型

  3. pow(double r,double p):該函式用於返回$r^{p}$,要求r和p都是double型

  4. sqrt(double x):返回double型變數的算術平方根

  5. log(double x):返回double型變數以自然對數為底的對數
    注:C語言中沒有對任意底數求對數的函式,因此必須使用換底公式來將不是以自然對數為底的對數轉換為以e為底的對數

  6. sin(double x)、cos(double x)和tan(double x):三個函式分別返回double型變數的正弦值、餘弦值和正切值,引數要求是弧度制

const double pi = acos(-1.0);    //因為cos(pi)=-1
  1. asin(double x)、acos(double)和atan(double x):分別返回double型變數的反正弦值、反餘弦值和反正切值

  2. round(double x):將double型變數四捨五入,返回型別也是double型

3 選擇結構

3.1 if語句

  • if語句格式
if(條件A){
  ...
} 
  • if...else格式
if(條件A){
  ...
}else{
  ...
} 
  • if...elseif...else格式
if(條件A){
  ...
}else if(條件B){
  ...
} else{
  ...
} 
  • 如果表示式是"!= 0",則可以省略"!= 0";如果表示式為"0",則可以省略"0"
    if(n)表示if(n!=0) if(!n)表示if(n==0)

3.2 if語句的巢狀

  • if語句的巢狀是指在if或者else的執行內容中使用if語句

3.3 switch語句

  • switch語句在分支條件較多時會顯得比較精煉

  • 格式

switch(表示式){
    case 常量表達式1:
        ...
        break;
    case 常量表達式2:
        ...
        break;
    case 常量表達式n:
        ...
        break;
    default:
        ...
} 
  • case下屬語句沒有使用大括號將它們括起來,這是由於case本身預設把兩個case之間的內容全部作為上一個case的內容,因此不用加大括號。

  • break的作用在於可以結束當前switch語句,如果將其刪去,則程式將會從第一個匹配的case開始執行語句,直到其下面的所有語句都執行完畢才會退出switch

4 迴圈結構

4.1 while語句

  • 格式
while(條件A){
    ...
}

4.2 do...while語句

  • 格式
do{
    ...
}while(條件A); //注意:這裡有分號
  • do...while會先執行迴圈體一次,然後才去判斷迴圈條件是否為真,這就使得do..while語句的實用性遠不如while

4.3 for語句

  • for迴圈使用頻率最高
for(表示式A;表示式B;表示式C){
    ...
}
//1.在for迴圈開始前,首先執行表示式A
//2.判斷表示式B是否成立:若成立,執行省略號內容;否則,退出迴圈 
//3.在省略號內容執行完畢後,執行表示式C,之後回到2. 
  • 在C語言中不允許在for語句的表示式A裡定義變數(例如int i的寫法是不允許的),但是在C++中可以,因此下面這種寫法需要把檔案儲存為.cpp檔案才能通過編譯

4.4 break和continue語句

  • break在需要的場合下退出迴圈

  • continue:在需要的地方臨時結束迴圈的當前輪迴,然後進入下一個輪迴

5 陣列

5.1 一維陣列

  • 一維陣列定義格式:資料型別 陣列名[陣列大小]; 注意陣列大小必須是整數常量,不可以是變數

  • 訪問格式:陣列名稱[下標]

  • 一維陣列初始化:需要給出用逗號隔開的從第一個元素開始的若干個元素的初值,並用大括號括住。後面未被賦初值的元素將會由不同編譯器內部實現的不同而被賦不同的初值(可能是很大的隨機數),而一般情況預設初值為0。

int a[10]={5,3,2,6,8,4};
  • 給整個陣列賦初值0,只需要把第一個元素賦為0,或者只用一個括號來表示
int a[10]={0};
int a[10]={}; 
  • 遞推(分為遞推和逆推):可以不斷讓後一位的結果由前一位或前若干位計算得來。

5.2 氣泡排序

  • 氣泡排序的本質在於交換,即每次通過交換的方式把當前剩餘元素的最大值移動到一端,而當剩餘元素減少為0時,排序結束。

  • 交換兩個數

#include<stdio.h>
int main()
{
  int a=1,b=2;
  int temp = a;
  a = b;
  b = temp;
  printf("a = %d,b = %d\n",a,b);
  return 0;
}
  • 氣泡排序:當第i趟時,從a[0]到a[n-i-1]都需要與下一個數比較
#include<stdio.h>
int main()
{
    int a[10] = {3,1,4,5,2};
    for(int i=1;i<=4;i++)    //進行n-1趟
    {
        //第i趟時從a[0]到a[n-i-1]都與它們下一個數比較
        for(int j=0;j<5-i;j++)
        {
            if(a[j]>a[j+1])   //如果左邊的數更大,則交換a[j]和a[j+1]
            {
                int temp = a[j];
                a[j] = a[j+1];
                a[j+1] = temp;
            }
        }
    }
    for(int i=0;i<5;i++)
    {
        printf("%d",a[i]);
    }
    return 0;      
}

5.3 二維陣列

  • 格式:資料型別 陣列名[第一維大小][第二維大小]; 訪問:陣列名[下標1][下標2];

  • 二維陣列初始化:需要按第一維的順序依次用大括號給出二維陣列初始化情況,然後將它們用逗號分隔,並用大括號全部括住,而在這些被賦初值的元素之外的部分被預設賦值為0;

int a[5][6] = {{3,1,2},{8,4},{},{1,2,3,4,5}};

  • 如果陣列大小較大(大概$10^6$級別),則需要將其定義在主函式外面,否則會使程式異常退出,原因是函式內部申請的區域性變數來自系統棧,允許申請的空間較小;而函式外部申請的全域性變數來自靜態儲存區,允許申請的空間較大。

  • 多維陣列

5.4 memset——對陣列中每一個元素賦相同的值

  • 給陣列中每一個元素賦相同的值有兩種方法:memset函式和fill函式

  • 格式:memst(陣列名,值,sizeof(陣列名)); 新增string.h標頭檔案

  • memset按位元組賦值,只建議初學者使用memset賦0或-1,這樣不容易弄錯

  • 如果要對陣列賦其他數字(例如1),那麼請使用fill函式(但memset的執行速度快)

  • 對二維陣列或多維陣列的賦值方法也是一樣的(仍然只需要寫陣列名),不需要改變任何東西

5.5 字元陣列

  1. 字元陣列初始化:可以和普通陣列一樣,也可以直接賦值字串來初始化(僅限於初始化,程式其他位置不允許這樣直接賦值整個字串)
char str1[15]={'G','o','o','d','','S','t','o','r','y','!'};
char str2[15]="Good Story!";
  1. 字元陣列就是char陣列,一維時可以當作字串,二維時可以當作字串陣列,即若干字串

    1. scanf輸入,printf輸出

      scanf對字元型別有%c和%s兩種格式(printf同理,下同),其中%c用來輸入單個字元,%s用來輸入一個字串並存在字元數組裡。%c格式能夠識別空格跟換行並將其輸入,而%s通過空格或換行來識別一個字串的結束。

    2. getchar輸入,putchar輸出

      用來輸入和輸出單個字元,可以用getchar()函式將每行末尾的換行符吸收掉

    3. gets輸入,puts輸出

      • gets用來輸入一行字串(注意:gets識別換行符\n作為輸入結束,因此scanf完一個整數後,如果要使用gets,需要先用getchar接受整數後的換行符),並將其存放於一維陣列(或二維陣列的一維)中;

      • puts用來輸出一行字串,即將一維陣列(或二維陣列的一維)在介面上輸出,並緊跟一個換行

  2. 字元陣列的存放方式

    • 在一維字元陣列(或是二維字元陣列的第二維)的末尾都有一個空字串\0,以表示存放的字串的結尾,空字串\0在使用gets或scanf輸入字串時會自動新增在輸入的字串的結尾,並佔用一個字元位,而puts與printf就是通過識別\0作為字串的結尾來輸出的。

    • 結束符\0的ASCII碼為0,即空字元NULL,佔用一個字元位,因此字元陣列的長度要比實際儲存字串的長度至少多1。int型陣列的末尾不需要加\0,只有char型陣列需要。

    • \0跟空格不是一個東西,空格的ASCII碼是32。

    • 若不是使用scanf函式的%s格式或gets函式輸入字串(例如使用getchar),請一定要在輸入的每個字串後加入“\0”,否則printf和puts輸出字串會因為無法識別字符串末尾而輸出一大堆亂碼。

5.6 string.h標頭檔案

  • string.h標頭檔案包含了許多用於字元陣列的函式
  1. strlen():可以得到字元陣列中第一個\0前的字元的個數,格式:strlen(字元陣列);

  2. strcmp():返回兩個字串大小的比較結果,比較原則是按字典序

    • 格式:strcmp(字元陣列1,字元陣列2);

    • 字典序就是字串在字典中的順序,strcmp的返回結果如下:

      • 如果字元陣列1<字元陣列2,則返回一個負整數(不同編譯器處理不同,不一定是-1);

      • 如果字元陣列1==字元陣列2,則返回0;

      • 如果字元陣列1>字元陣列2,則返回一個正整數(不同編譯器處理不同,不一定是+1);

  3. strcpy():可以把一個字串複製給另一個字串,格式:strcpy(字元陣列1,字元陣列2),是把字元陣列2複製給字元陣列1,這裡的“複製”包括了結束符\0。

  4. strcat():把一個字串接到另一個字串後面,格式:strcat(字元陣列1,字元陣列2); 是把字元陣列2接到字元陣列1後面

5.7 sscanf與sprintf

  • sscanf(str,"%d",&n); sscanf寫法的作用是把字元陣列str中的內容以"%d"的格式寫到n中(還是從左至右),可以配合正則表示式使用。

  • sprintf(str,"%d",n); sptintf寫法的作用是把n以"%d"的格式寫到str字元陣列組中(還是從右至左)

  • 利用sscanf和sprintf進行復雜輸入和輸出

#include<stdio.h>
int main()
{
    int n;
    double db;
    char str[100]="2048:3.14,hello",str2[100];
    sscanf(str,"%d:%lf,%s",&n,&db,str2);
    printf("n=%d,db=%.2f,str2=%s\n",n,db,str2);
    return 0;
}
#include<stdio.h>
//#include<string.h>
int main()
{
    int n=12;
    double db=3.1415;
    char str[100],str2[100] = "good";
    sprintf(str,"%d:%.2f,%s",n,db,str2);
    printf("str = %s\n",str);
    return 0;
}

6 函式

6.1 函式的定義

  • 函式:是一個實現一定功能的語句和集合,並在需要時可以反覆強呼叫而不必每次都重新寫一遍。

  • 格式

返回型別 函式名稱(引數型別 引數)
{
    函式主體
}
  • 全域性變數:在定義之後的所有程式段內都有效的變數(即定義在其之後所有函式之前)

  • 區域性變數:與全域性變數相對,區域性變數定義哦在函式內部,且只在函式內部生效,函式街商戶時區域性變數銷燬

  • 函式引數傳遞是值傳遞

6.2 再談main函式

  • 一個程式只能有一個主函式,並且無論主函式寫在哪個位置,整個程式一定是從主函式的第一個語句開始執行。

6.3 以陣列作為函式引數

  • 函式的引數可以是陣列,且陣列作為引數時,引數中陣列的第一維不需要填寫長度(如果是二維陣列,那麼第二維需要填寫長度),實際呼叫時也只是需要填寫陣列名。

  • 陣列作為引數時,在函式中對陣列元素的修改就等同於是對原陣列元素的修改(這與普通的區域性變數不同)

  • 雖然陣列可以作為引數,但是卻不允許作為返回型別出現,如果想要返回陣列,則只能用上面的方法,將想要返回的陣列作為引數傳入。

6.4 函式的巢狀呼叫

6.5 函式的遞迴呼叫

  • 函式的遞迴呼叫是指一個函式呼叫該函式自身

7 指標

7.1 什麼是指標

  • 在C語言中用“指標”來表示記憶體地址,指標是一個unsigned型別的整數

  • 只要在變數前面加上&,就表示變數的地址

7.2 指標變數

  • 指標變數用來存放指標(或者可以理解成地址),在某種資料型別後加*用來表示這是一個指標變數
int* p1,p2;  //只有p1是int*型的,而p2是int型的
//正確寫法
//int *p1,*p2,*p3; 
int a;
p1 = &a;   //把變數的地址取出來,賦給對應的指標變數 
  • int*是指標變數的型別,而後面的p才是變數名,星號是型別的一部分

  • 用*p可以獲得地址中存放的元素

  • 指標可以進行加減法,減法的結果就是兩個地址偏移的距離,對於一個int*型別的指標變數p來說,p+1是指p所指的int型變數的下一個int型變數地址。這個所謂的“下一個”是跨越了整個int型(即4Byte)

  • 指標變數支援自增自減操作

  • 基型別:指標變數儲存的地址的型別稱為基型別,基型別必須和指標變數儲存的地址型別相同。

7.3 指標與陣列

  • 陣列名稱可作為陣列的首地址

  • a+i等同於&a[i],*(a+i)和a[i]等價,因此輸入陣列元素又可以寫成:

scanf("%d",a+i);
  • 列舉陣列中的元素
#include<stdio.h>
int main()
{
    int a[10] = {1,2,3,4,5,6,7,8,9,10};
    for(int *p=a;p<a+10;p++)
        printf("%d",*p);
    return 0;
}
  • 兩個int型的指標相減,等價於在求兩個指標之間相差了幾個int

7.4 使用指標變數作為函式引數

  • 指標可以作為函式引數傳參,這就是把變數的地址傳入函式。如果在函式中對這個地址中的元素進行改變,原先的資料就會確實地改變。

  • 利用指標交換兩個數

#include<stdio.h>

void swap(int *a,int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main()
{
    int a = 1,b = 2;
    int *p1 = &a , *p2 = &b;
    swap(p1,p2);
    printf("a=%d,b=%d\n",*p1,*p2);

    return 0;
}
  • 典型錯誤寫法
void swap(int *a,int *b)
{
    int *temp = a;
    a = b;
    b = temp;
}
//main函式傳給swap函式的“地址”其實是一個“無符號整數”的數
//其本身也和普通變數一樣只是“值傳遞”
//swap函式對地址本身進行修改並不能對地址指向的資料進行修改

能夠使main函式裡的資料發生變化的只能是swap函式中對地址指向的資料進行的修改

7.5 引用

  • C++中的引用不產生副本,只是給變數起了個別名,對引用變數的操作就是對原變數的操作

  • 不管是否使用引用,函式的引數名和實際傳入的引數名可以不同

  • 要把引用的&跟取地址運算子&區分開來,引用並不是取地址的意思

  • 使用引用交換兩個變數的值

void swap(int* &p1,int* &p2)
{
    int *temp = p1;
    p1 = p2;
    p2 = temp;
}
//簡單地把int*型理解成unsigned int型,而直接交換這樣的兩個整數變數是需要加引用的 
  • 引用是變數的別名,常量不可以使用引用

8 結構體(struct)的使用

8.1 結構體的定義

  • 基本格式:
struct Name
{
    //一些基本的資料結構或者自定義的資料型別
};

  • 結構體定義

  • 直接定義

struct studentInfo
{
    int id;
    char gender; //'F'or'M'
    char name[20];
    char major[20];
}Alice,Bob,stu[1000];
  • 按照基本資料型別定義
studentInfo a;
studentInfo b[1000];
  • 結構體裡面能定義除了自己本身之外的任何資料型別(迴圈定義問題),但是可以定義自身型別的指標變數
struct node
{
    //node n;  //不能定義node型變數
    node* next;   //可以定義node*型指標變數
};

8.2 訪問結構體內的元素

  • 訪問結構體內的元素有兩種方法:“.”操作“->”操作
struct studentInfo
{
    int id;
    char name[20];
    studentInfo* next;
}stu,*p;
//訪問
stu.id;
(*p).name;
p->next; 

8.3 結構體的初始化

  • 建構函式:不需要寫返回型別,且函式名與結構體名相同。結構體內部會生成預設的建構函式,但是不可見。

  • 自己重新定義建構函式

struct studentInfo
{
    int id;
    char gender;
    //下面的引數用以對結構體內部變數進行賦值
    studentInfo(int _id,char _gender)
    {
        id = _id;
        gender = _gender;
    }
    //建構函式簡化寫法
    //studentInfo(int _id,char _gender):id(_id),gender(_gender){}
};
studentInfo stu = studentInfo(10086,'M');
  • 如果自己重新定義了建構函式,則不能不經初始化就定義結構體變數,因此加上“studentInfo(){}”就可以適用不同場合。

9 補充

9.1 cin和cout

cin

  • cin=c+in,採用輸入運算子“>>”來進行輸入,不需要寫&,直接寫變數名就可以
#include<iostream>
using namespace std;
int main()
{
    int n;
    cin >> n;
    return 0;
} 
  • 讀入一整行,使用getline函式
char str[100];
cin.getline(str,100);//把一整行都讀入char型陣列str[100]中 
  • string容器,使用如下方式輸入
string str;
getline(cin,str); 

cout

  • cout=c+out,使用輸出運算子“<<”,輸出中間沒有空格,讀者可以自己加入
cout<<n<<" "<<db<<" "<<c<<" "<<str;
cout<<n<<"haha"<<db<<"heihei"<<c<<"wawa"<<str;
  • 換行:"\n"和endl
cout<<n<<"\n"<<db<<endl;
  • 控制double型精度,要加上標頭檔案#include標頭檔案
cout<<setiosflags(ios::fixed)<<setprecision(2)<<123.4567<<endl;

9.2 浮點數的比較

  • 計算機採用有限位二進位制編碼,經過大量計算後,浮點數在計算機中的儲存並不精確,因此會影響比較操作的準確性,引入極小數eps來對這種誤差進行修正。

  • eps取$10^{-8}$是一個合適的數字,因此將eps定義為常量1e-8

#include<stdio.h>
#include<math.h>
const double eps = 1e-8;
#define Equ(a,b) ((fabs((a)-(b)))<(eps))  // ==,想要使用不等於,用(!Equ(a,b))即可
#define More(a,b) ((fabs((a)-(b)))>(eps))  // > a-b大於eps
#define Less(a,b) (((a)-(b))<(-eps))  // < a-b小於eps
#define MoreEqu(a,b) (((a)-(b))>(-eps))  // >= a-b大於-eps
#define LessEqu(a,b) (((a)-(b))<(eps))  // <= a-b小於eps

  • 圓周率π
const double Pi = acos(-1.0); //cos(π)=-1
  • 由於精度問題,在經過大量運算後,可能一個變數中儲存的0是個很小的負數。這時如果開根號,就會因為不在定義域內而出錯。同樣的問題還可能出現在asin(x)當x存放+1、acos(x)當存放-1時。這種情況需要用eps使變數保證在定義域內。

  • 由於編譯環境的原因,本應為0.00的變數在輸出時會變成-0.00。這是編譯環境的bug,只能把結果存放到字串中,然後與-0.00進行比較,如果對比成功,則加上eps來修正0.00。

9.3 複雜度

  1. 時間複雜度
  • 時間複雜度是演算法需要執行基本運算的次數所處的等級,基本運算是類似加減乘除這種計算機可以直接實現的運算,時間複雜度是評判演算法時間效率的有效標準。

  • 在時間複雜度中,高等級的冪次會覆蓋低等級的冪次

  • 當時間複雜度的常數比較大時,即便時間複雜度相同,其效能也會有較大差距

  • 常數複雜度O(1)表示演算法消耗的時間不隨規模的增長而增長

  • O(1)<O(log n)<O(n)<O($n^2$)

  • 對一般的OJ系統來說,一秒能承受的運算次數大概是****$107$****~****$108$

  1. 空間複雜度
  • 如果消耗的最大資料空間是一個二維陣列,那麼該演算法空間複雜度就是O($n^2$)

  • O(1)的空間複雜度是指演算法消耗的空間不隨資料規模的增大而增大

  • 考慮到空間夠用,因此常採用以空間換時間的策略

  1. 編碼複雜度
  • 編碼複雜度是一個定性概念,並沒有量化標準,如果程式碼量巨大,其編碼複雜度就會非常大

10 黑盒測試

黑盒測試:系統後臺準備若干組輸入資料,然後讓提交的程式去執行這些資料,如果輸出的結果與正確答案完全相同(字串意義上的比較,那麼就稱通過了這道題的黑盒測試,否則會根據錯誤型別而返回不同的結果)。

10.1 單點測試

  • 單點測試:系統會判斷每組資料的輸出結果是否正確。

  • 如果輸出正確,那麼對改組資料來說就通過了測試,並獲得了這組資料的分值。在這種情況下。總得分等於通過的資料的分值之和。

  • PAT就採用了單點測試

  • 從程式碼編寫上來看,單點測試只需要按正常的邏輯執行一遍程式即可,是“一次性”的寫法。

10.2 多點測試

  • 多點測試要求程式能一次執行所有資料,並要求所有輸出結果都必須完全正確,才能算這道題正確。

  • 大部分線上評測系統都採用了這種方式,這樣可以嚴格考驗程式碼是否嚴謹

  • 三種不同的輸入方式

  1. while...EOF型:題目沒有給定輸入的結束條件,預設讀到檔案末尾
while(scanf("%d",&n) != EOF)
{
    ...
}
//scanf函式的返回值為其成功讀入的引數的個數,讀入失敗時會返回-1,使用EOF來代表-1 
- 在黑框裡手動觸發EOF,可以按<Ctrl+Z>組合鍵,這時就會顯示一個^Z,按<Enter>就可以結束當前while了

- 讀入字串,則有scanf("%s",str)與gets(str)兩種方式
while(sanf("%s",str)!=EOF)
{
    ...
}
while(gets(str)!= NULL)
{
    ...
}
  1. while...break型:while...EOF型別的延伸
    題目描述:當輸入的兩個a和b都為0時結束輸入
#include<stdio.h>
int main()
{
    int a,b;
    while(scanf("%d%d",&a,&b)!= EOF)
    {
        if(a==0 && b==0)
            break;
        printf("%d\n",a+b);
    }
    return 0;
}
//代替while...break的簡便寫法
// while(scanf("%d%d",&a,&b),a||b)
  1. while(T--)型:題目給出迴圈次數,用一個變數T來儲存,進行T次迴圈
  • 三種輸出型別

    1. 正常輸出:每兩組輸出資料之間沒有額外的空行,即輸出資料是連續的多行

    2. 每組資料輸出之後都額外加一個空行:在每組輸出結束之後額外輸出一個換行符\n即可

    3. 兩組輸出資料之間有一個空行,最後一組資料後面沒有空行:當使用while(T—)時,只需要判斷T是否已經減小到0來判斷是否應當輸出額外的行。

      例如:輸出一行N個整數,每兩個整數之間用空格隔開,最後一個整數後面不允許加上空格

for(int i=0;i<N;i++)
{
    printf("%d",a[i]);
    if(i<N-1)
        printf(" ");
    else
        print("\n");
}
  • 在多點測試中,每一次迴圈都要重置一下變數和陣列,否則在下一組資料來臨的時候變數和陣列的狀態就不是初始狀態了。例如sum=0的語句就必須放在while之內。

  • 重置陣列一般使用memset函式或fill函式。