1. 程式人生 > >【演算法筆記】第二章: C/C++ 快速入門

【演算法筆記】第二章: C/C++ 快速入門

【演算法筆記】第二章: C/C++ 快速入門

標籤(空格分隔):【演算法筆記】


第二章:C/C++ 快速入門

2.0 引言

  • scanf 和 printf 比 cin 和 cout 要快得多。

  • C 語言的副檔名為 .c,但是考慮到 C++有很好的特性,因此一般直接使用 C++的副檔名 .cpp 。

  • 標頭檔案和主函式

    1. 標頭檔案,例如 #include<stdio.h>,其中 stdio.h 是標準輸入輸出庫,在程式中如果由輸入輸出,就必須加上這個標頭檔案。stdio 的全稱為 standard input output ,h 為 head 的縮寫,.h 就是標頭檔案的檔案格式。
      當然,還有一些其他的庫,例如,math.h 負責一些數學函式,string.h 負責字串函式。在使用過程中,將它們的標頭檔案包含到這個程式中來即可。
      此外,在 C++ 中,stdio.h 可以寫成它的等價寫法 cstdio,也就是去掉 .h 並它前面加上 c 即可。所以#include<stdio.h>#include<cstdio>是等價的。類似的有,#include<math.h>#include<cmath>是等價的,#include<string.h>#include<cstring>是等價的。
    2. 主函式:
int main(){
... 
return 0;
}

上面的程式碼就是主函式。主函式就是一個程式的入口位置,整個程式從主函式開始執行,一個程式最多隻能有一個主函式。

2.1 基本資料型別

2.1.1 變數的定義

  • 變數是在程式執行過程中其值可以改變的量,需要在定義後才可以使用,其定義格式如下:
    變數型別 變數名;
    當然,在變數定義時可以為它賦予初值:
    變數型別 變數名 = 初值;

  • 變數的命名需要滿足以下幾個條件:

    1. 不能是 C 語言的識別符號(例如 for,if,or 等,因為它們本身在 C 語言中有含義)。建議選擇具有一定實際意義的變數名,例如 Max,Sum 等,以提高程式可讀性。
    2. 變數名的第一個字元必須是字母或下劃線,除了第一個字元之外的其他字元必須是字母、數字或下劃線。例如,abc,_hvsym12 是合法的變數名,而 6abc 是非法的變數名。
    3. 區分大小寫,Max 和 max 可以作為兩個不同的變數名。

2.1.2 變數型別

-

  • 四種基本型別:

    1. 整型 :整型一般可以分為 short 、int、long long .

      • int,一個int 佔用 32 位,即 4 Byte,取值範圍從
        2 31 ~ 2 31 1 ,絕對值在 10 9 之內的正數都可以使用 int 型別
      • long long,一個 long long 佔用 64 位,即 8Byte,取值範圍從 2 63 ~ 2 63 1 ,如果題目要求的整數範圍大於 2147483647(或者 2 31 1 ),就需要使用 long long 來儲存。
        注意:對於long long 型別的變數賦予初值,需要在初值後面加上LL,否則會出現編譯錯誤。
        long long BigNum = 123456789012345LL;
      • 另外,對於整型資料,都可以在前面加上一個 unsigned,以表示無符號整型,例如 unsigned int 和unsigned long long。佔用的位數和之前相投,但是去掉了負數部分,因此,unsigned int 表示範圍   0 ~ 2 32 1   和 unsigned long long 表示 0 ~ 2 64 1
        一般而言,很少會使用 unsigned int 和unsigned long long 的情況。
    2. 浮點型:通俗而言,浮點型就是小數,分為單精度(float)和雙精度(double)。

      • float,一個單精度型別的浮點數佔用 32bit,其中 1bit作為符號位, 8bit作為指數位, 23bit作為尾數位,可以存放的數值範圍是 2 128 ~ 2 128 , 但是有效精度只有 6 ~ 7 位,這對一些對於精度要求比較高的運算是不合適的。
      • double,一個雙精度型別的浮點數佔用 64bit,其中1bit作為符號位,11bit作為指數位,52bit作為尾數位,可以存放的資料的範圍 是 2 1024 ~ 2 1024 ,其有效精度為 15 ~ 16 位,比 float 優秀的多。
      • 例如一下程式碼:

        
        #include<stdio.h>
        
        int main(){
        double a = 3.14, b = 0.12;
        double c = a + b;
        printf("%d", c);
        return 0;
        }
      • 記住以下一點,在程式設計題目中,對於浮點型而言,用 double 儲存即可。

    3. 字元型

      • 字元變數和字元常量
        定義順便賦予初值程式碼如下:char c = 'e'
        如何理解字元常量?在上述程式碼中,c 便是一個字元變數,它可以被賦值。但對於字元本身,例如 ‘e’,它是一個不能被改變,不能被賦值,故它是字元常量。字元常量可以賦值給字元變數,就好像整型常量可以被賦值給整型變數一樣。另外,字元常量必須是單個字元,同時它必須使用單引號標註。
        在 C 語言中,字元常量用 ASCII碼 統一編碼。標準的ASCII碼的範圍是 0 ~ 127,其中包含了控制字元和通訊專用字元(不可顯示) 和常用的可顯示字元。在鍵盤上,通過敲擊可以在螢幕上顯示的字元就是可顯示字元,例如:
        image.png-12.8kB
        尤其需要注意的是,小寫字母比大寫字母的ASCII碼的值大 32
        在計算機中,字元按照ASCII碼儲存。例如:
        char c = 117; printf("%c",c)
        其顯示輸出結果為 u.

      • 轉義字元
        在上文中我們提到,ASCII 碼有一部分是控制字元,是不可顯示的。例如:刪除,換行,Tab等都是控制字元。如何在程式中表示一個控制字元?在C語言中,可以通過用一個右斜線加一些特定的字母來實現。例如,換行用’\n’表示,Tab鍵用’\t’ 表示。在這種情況下,斜線後面的字元失去了本身的含義,因此稱為“轉義字元”。

        \\經常用到的轉義字元只有下面兩個:
        \n 換行
        \0 代表空字元NULL,其ASCII碼為 0,注意\0不是空格
      • 字串常量
        字串是由若干個字元組成的串,在C語言中沒有單獨一種基本資料型別可以儲存字串,而在 C++ 中有string 型別,在C語言中只可以使用字元陣列的方式。在這裡,我們現介紹字串常量。
        在上文中我們提到,字元常量就是由一個單引號標記的字元,那麼此處的字串常量則是由雙引號標記的字符集,例如”Ilovecomputer”,這就是一個字串常量。
        字串常量可以作為初值付給字元陣列,並用 %s 的格式輸出

        
        #include<stdio.h>
        
        int main(){
            char str1[25] = "I love compueter scince";
            char str2[25] = "I want to learn code";
            printf("%s %s", str1, str2);
            return 0;
        }

        在上面的例子中,str1[25] 和 str2[25] 均代表由 25 個char 字元組成的字元集合,可以稱之為字元陣列
        注意,不可以將字串常量賦值給字元變數 char c = "1afdas";是非法的。

      • 布林型
        布林型在 C++中可以直接使用,但是在C語言中必須新增 stdbool.h 的標頭檔案才可以使用。布林型變數又稱為 bool型別變數。他的取值只能是 true 和 false ,分別代表 非零 和 零。在賦值時,可以直接使用 true 或者 false 賦值,也可以使用整數常量進行賦值。對於任何的非零整數,都會轉換為 true.

2.1.3 強制型別轉換

  • 有時候需要把浮點數的小數部分切掉而只是用整數部分,或者把整數轉化為浮點數來進行除法操作。這些情況下就需要使用強制型別轉換,即把一種資料型別轉換為另外一種型別。
    (新型別名)型別名

  • 需要注意的是,如果將一個型別的變數賦值給另外一個型別的變數,卻沒有寫強制型別轉換操作,那麼編譯器將自動進行轉換。但是這並不是說任何時候都不用使用強制型別轉換。如果在計算的過程中需要轉換型別,那麼就不可以等待它算完之後再賦值時轉換。

2.1.4 符號常量和 const 常量

  • 通俗來講,符號常量就是“替換”,即用一個識別符號來替代常量,又稱為“巨集定義”,或者“巨集替換”。
    1.第一種格式
    #define 識別符號 常量
    例如:#defien PI 3.1415926

    1. 另外一種格式
      const 資料型別 變數名 = 常量;
      例如:const int PI = 3.1415926;
  • 這兩種寫法都叫常量,它們的值一旦確定後不可更改,例如 PI = PI + 1;操作不可進行,推薦使用 const 寫法。

  • 另外,define 除了可以定義常量之外,還可以定義任何語句與片段。
    格式如下:
    #define 識別符號 任何語句與片段
    例如:#define ADD(a,b) ( (a) + (b))
    或許有人疑問,直接定義成#define ADD(a,b) a + b 或者 #define ADD(a,b) ( a + b) 不可以嗎?為什麼需要那麼多括號呢?然而,實際上,必須需要這麼多括號。巨集定義是直接將對應的部分進行替換,然後才進行編譯和執行。
    例如:

#include<stdio.h>
#difine CAL(x) ( x * 2 + 1)
int main(){
    int a = 1;
    printf("%d",CAL(a+1));
return 0;}

上述程式碼的輸出結果將會是 4,而不是預想的 5 ,因為在上述程式碼中,巨集定義會將替換的部分原封不動的替換進去。CAL( a + 1 * 2 + 1),其結果為 4.

2.1.5 運算子

  • 算數運算子

    1. 加法運算子 ‘+’;
    2. 減法運算子 ‘-‘;
    3. 乘法運算子 ‘*’;
    4. 除法運算子 ‘/’;
      注意,當除數與被除數都是整數時,例如 a = 3,b = 2; c = a/b; 得到的 c 為 1.
      另外,當除數是 0 時,會導致程式異常退出或者得到錯誤輸出“1.#INF00”。
    5. 取模運算子 ‘%’;
      取模運算子返回被除數與除數相除得到的餘數,例如 a = 5,b = 3; c = a%b; 得到的 c 為 2.
      與除法運算子一樣,除數不允許為 0.
    6. 自增運算子 ‘++’;
      注意 i++; ++i; 二者的區別,i++ 是先使用i再對 i 加一, ++i是先對 i 加一再使用i.
    7. 自建運算子 ‘–’;
  • 關係運算符
    image.png-62.8kB

  • 邏輯運算子
    1.PNG-20.3kB

  • 條件運算子
    條件運算子( ? : ) 是 C語言中唯一的三目運算子,即需要三個引數的運算子。格式如下:A ? B : C
    含義:若A為真,則返回B;若A為假,則返回C。

  • 位運算子
    image.png-80kB

2.2 順序結構

2.2.1 賦值表示式

  • 使用 ‘=’等號來完成賦值操作。
    例如:int test = 5;
    或者:int n = 3 * 2 + 1;
    再或者:int m = (1>6) && (2<8);
    賦值運算子可以通過將其他運算放到前面來實現賦值操作的簡化。例如:n += 2; 它的意思是 n = n + 2;

2.2.2 使用scanf 和 printf 進行輸入/輸出

  • C語言中stdio.h 庫函式中提供了 scanf 和 printf ,分別對應輸入輸出。

    1. scanf函式:
      格式:scanf("格式控制",變數地址);
      下圖中,’&’稱作取地址運算子。
      image.png-39.6kB

      • 說明:&n的意思是得到變數n的地址,在該地址出寫入內容。而字串中str並沒有 &運算子,因為陣列名稱本身就代表了這個陣列的第一個元素的地址。在整個C語言中,除了char陣列在輸入時候不需要新增 & 外,其餘都需要加 &.
      • 另外,很重要的一點:在scanf中,雙引號內的內容其實就是整個輸出,只不過把陣列轉換成它們對應的格式符並按照變數的地址按次序寫在後面而已。例如,scanf("%d::%d",&a,&b);,就必須輸入類似於 “1::2”這般的格式。
      • 如果要輸入類似 “3 4” 這種用空格隔開的兩個數字,在scanf中的兩個 %d 之間可以不加空格,scanf("%d%d",&a,&b);
        原因在於:除了 %c 之外,scanf對於其他格式符,(如 %d),的輸入是以空白符(即空格、換行等)為結束判斷標誌的。因此除非使用%c把空格按字元讀入,其他情況下都會自動跳過空格和換行。另外,字元陣列使用%s讀入時以空格和換行作為讀入標誌的結束。
        例:以下程式碼中,若輸入 abcd efg 則輸出結果為abcd.
        char a[20];
        scanf("%s",a);
        printf("%s",a);
    2. printf函式:
      格式:printf("格式控制",變數地址);
      在printf中,不需要給出變數地址,只需要給出變數名即可。image.png-39.3kB

      • 說明:對於double型別的變數,其輸出格式變成了 %f,然而在scanf中是%lf.在某一些系統中,如果把輸出格式寫成 %lf 也沒有錯誤,不過儘量還是按照要求寫更標準。
      • 另外,不要因為float型別的scanf和printf比較好記就用float,因為float精度低。
      • 如果想要輸出 ‘%’ 或者 ‘\’,其程式碼格式為printf("%%");,printf("\\");.例如想要輸出 “%%%”,程式碼為printf("%%%%%%");
      • 三種實用的輸出格式
        %md:可以使不滿足 m 位的int型變數以 m 位進行右對齊輸出,其中高位用空格補齊;若變數本身超過 m 位,則保持原樣。例如:int a = 123; printf("%5d", a);輸出結果為__123
        %0md:%0md只是在%md中間多加了0.和%md不同點在於,當變數不足m位時,將在高位補0而不是空格。例:int a = 123; printf("%5d", a);輸出結果為00123.
        %.mf:可以讓浮點數保留 m 位小數輸出。其中“保留”適用的是精度的四捨六入五成雙規則。(不是四捨五入,四捨五入用round函式)

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

  • getchar用於輸入單個字元,putchar輸出單個字元。在某些scanf函式使用不便的情況下可以使用getchar.
    例如:
char c1,c2,c3,c4;
c1 = getchar();
getchar();
c2 = getchar();
c3 = getchar();
putchar(c1);putchar(c2);putchar(c3); 

當輸入abcd時,輸出結果為acd
當輸入ab再按下 <Enter> 鍵 ,再輸出c,再按下<Enter> 鍵.最終輸出結果為

a
c

這意味著getchar可以識別換行符,c2中儲存的是’\n’,因此輸出中會有換行符出現。

2.2.4 註釋

  • 使用 “/* */”進行若干連續行的註釋。
  • 使用 “//”進行一行之內的註釋。

2.2.5 typedef

  • typedef可以給複雜的資料型別起一個別名,這樣再使用過程中就可以用別名來代替原先的寫法。例如,當資料型別是 long long 時,可以進行替換,以節約輸入時間。
typedef long long LL;
LL a = 12345678901234567LL;
printf("%lld",a);

效果與 long long 是一樣的。

2.2.6 常用的math函式

  • C語言中,如果需要使用數學函式,需要再標頭檔案中加上 “math.h” .以下時幾個常用的數學函式。
    1. fabs(double x)
      用於對 double型別的變數取絕對值。
    2. floor(double x):對 double 型別向下取整。
      ceil(double x):對 double 型別向上取整。
    3. pow(double r, double p):返回r的p次方。
    4. sqrt(double x):返回 x 的算術平方根。
    5. log(double x):返回 x 以自然對數為底的對數。
      另外,在 C語言中,沒有對任意底數求對數的函式,因此必須使用換底公式將不是以自然對數為底的對數轉換為以 e 為底的對數。 l o g a b = l o g e b l o g e a .
    6. sin(double x),cos(double x),tan(double x):三角函式,其中要求引數必須是弧度制。例如:sin( pi * 45 / 180);
    7. asin(double x),acos(double x),atan(double x):反三角函式。
    8. round(double x):對double 型別的變數四捨五入。

2.3 選擇結構

2.3.1 if語句

  • if語句格式:

    if(條件A){
    
    }

    當條件A為真時,執行括號內內容。

  • if-else 語句,格式:

    if(條件A){
    
    } 
    else{
    }

    當條件A為真時,執行括號內內容。否則執行else的括號內內容。

  • if-else if-else 語句,格式:

    if(條件A){
    
    } 
    else if(條件B){
    }
    else{
    }

    先判斷條件A是否成立,若成立則執行if中括號內容,不成立則判斷條件B是否成立,若成立則執行else-if內語句,若不成立,則執行else內語句。

  • 注意:

    1. 如果括號內只有一個語句,則可以去掉大括號。已達到美觀的目的。
    2. 在條件判斷中,如果表示式是 “!=0”或者”==0”則可以分別進行省略,和新增非運算子的手段進行簡化。即if( a !=0)if(a)等價。if( a ==0)if(!a)等價。

2.3.2 if語句的巢狀

  • if語句的巢狀指在 if或者 else的執行內容中使用 if語句,格式如下:

    if(條件A){
       if(條件B){
    
       }
       else{
    
       }
    } 
    else{
    }

    當條件A成立,執行大括號內語句,執行期間如果條件B成立,則執行另一個if內的語句。

2.3.3 switch語句

  • switch在分支條件較多時會顯得精煉,格式如下:

    switch(表示式){
       case 常量表達式1:
          ...
          break;
       case 常量表達式2:
          ...
          break;
       default:
           ...
    
    } 

    注意,

    1. 兩個case之間的語句並沒有使用大括號括起來,因為case本身預設吧兩個case之間的內容全部作為上一個case的內容,因此不用加大括號。
    2. 程式碼中break語句作用在於結果當前switch語句,如果刪除break,那麼程式會從第一個匹配的case語句一直執行完下面所有語句才會推出switch.

2.4 迴圈語句

2.4.1 while語句

  • 格式:

    while(條件A){
    
    }

    當滿足條件A時反覆執行括號內語句。
    另外,while條件判斷的是真假,同樣有while( a !=0)while(a)等價。while( a ==0)while(!a)等價。

2.4.2 do-while語句

  • do-while 和while語句類似。但是它們的格式是上下顛倒的。

    do{
    
    }while(條件A)

    do-while語句首先執行省略號中內容一次,然後再判斷條件A是否成立。如果條件A成立,則繼續反覆執行省略號的內容,直到條件A不成立。

  • do-while語句和while語句的區別:do-while會首先執行一次迴圈體,然後再判斷迴圈條件是否為真。這使得do-while語句實用性遠不如while語句。

2.4.3 for語句

  • 格式:

    for(表示式A;表示式B;表示式C){
    
    }

    先執行表示式A,隨後判斷是否滿足表示式B,若滿足,執行括號內內容,反之則退出迴圈;括號內內容執行完畢之後,執行表示式C,重新判斷是否滿足表示式B.

2.4.4 break和continue語句

  • break直接退出迴圈。
  • continue結束迴圈的當前回合,直接進入下一回合。

2.5 陣列

2.5.1 一維陣列

  • 格式:資料型別 陣列名[陣列大小];
    其中陣列大小必須是整數常量,不可以是變數。
    訪問格式:陣列名[下標]
    注意,在定義了陣列大小為size的一維陣列之後,只能訪問下標為 0 ~ size - 1的元素。

2.5.2 氣泡排序

  • 排序:將一個無序序列按照某個規則進行有序排列。

  • 氣泡排序
    排序演算法中最基礎的一種。它的本質在於交換,演算法思想:每次通過交換的方式把當前剩餘得的最大元素移動到另一端。
    例如:現在有一個數組a其中有五個元素。分別為a[0] = 3、a[1] = 4、 a[2] = 1、 a[3] = 5、 a[4] = 2.要求從小到大排序。步驟如下:image.png-242.3kB
    實現程式碼如下:

void BubblingSort( int *A,int N){
    for(int i = 0; i < N; i++)
        for(int j = 0; j < N - i;j++)
            if( A[j+1] < A[j])
                swap( A[j+1],A[j]);
}

2.5.3 二維陣列

  • 格式:資料型別 陣列名[第一維大小][第二維大小];
    訪問格式:陣列名[下標1][下標2]
    注意,對於定義為 int a[size1][size2];的二維陣列,下標的取值範圍只可以是 0 ~ size1 - 1, 0 ~ size2 - 1.
    例如,對於二維陣列:int a[5][9] = { {3,1,2}, {8,4}, {}, {1,2,3,4,5} }; 那麼a[1][10]的值為 4 .
  • 重要的一點,如果陣列大小比較大(大概 10 6 級別),那麼需要將其定義在主函式外面,否則會使程式異常退出,原因在於函式內部申請的區域性變數來自系統棧,允許申請的空間比較小;而函式外部申請的全域性變數來自於靜態儲存區,允許申請的空間較大。

2.5.4 多維陣列

  • 多維陣列僅僅比二維陣列的維度有所差別,使用方法無異。

2.5.5 memset——對陣列每一個元素賦予同樣的值

  • 一般而言,給陣列中每一個元素賦予相同的值有兩種方法:memset函式和fill函式。
    1. memset函式
      • 首先,使用memset函式 需要在程式開頭新增 string.h 標頭檔案。
      • 格式:memset(陣列名, 值, sizeof(陣列名) );
      • 對於初學者而言,建議使用memset賦 0 或 -1.因為memset使用的是按位元組賦值,即對於每個位元組賦予同樣的值,這樣組成 int型的 4 個位元組就會被賦予相同的值,而由於 0 的二進位制補碼全為 0,-1 的二進位制補碼全為 1,不易弄錯。如果需要對陣列賦予其他值(例如 1),那麼請使用fill函式(雖然fill函式比memset慢一些)。

2.5.6 字元陣列

  • 字元陣列的初始化:和普通陣列一樣,例如:char str[10] = {'h','e','l','l','o'};
    另外,字元陣列也可以通過直接賦值字串來初始化(僅限於初始化,程式其他位置不允許這樣直接賦值整個字串)。例如:char str = "hello";
  • 字串陣列的輸入輸出:
    1. scanf輸入、printf輸出:
#include<stdio.h>
int main(){
char str[10];
scanf("%s",str);
printf("%s",str);
return 0;}

2.getchar輸入、putchar輸出:
getchar和putchar 分別用來輸入和輸出單個字元,也可以通過迴圈的方式來輸入輸出字串。

#include<stdio.h>
int main(){
char str[3][11];
for(int i = 0;i<3;i++){
    for(int j = 0;j<3;j++)
        str[i][j] = getchar();
    getchar();//吸收換行符
}

for(int i = 0;i<3;i++){
    for(int j = 0;j<3;j++)
         putchar(str[i][j]);
    putchar('\n');
}
return 0;}

3.gets輸入、puts輸出:
gets用來輸入一行字串(注意:gets識別 \n 換行符作為輸入結束,因此scanf完一個整數後,如果使用gets,就需要先用getchar接受整數後的換行符),並將其存放於一維陣列(或者二維陣列中的一維)中;
puts用來輸出一行字串,即將陣列在介面上輸出並緊接著一個換行。

  • 字元陣列的存放方式
    由於字元陣列是由若干個char型別的元素組成,因此字元陣列的每一位都是一個char字元,除此之外,在一維字數陣列(或者二維字元陣列的第二維)的末尾都一個