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

演算法筆記 第2章:C/C++快速入門 筆記

VS2015自動程式碼對齊快捷鍵:

1、ctrl+a;

2、ctrl+k;

3、ctrl+f;

按順序按下這三個快捷鍵組合。

VS2015編譯並執行快捷鍵:

ctrl+F5;

C++中的輸入和輸出函式cin和cout可以不指定輸入輸出格式,比較方便,但是消耗時間太多,故練習演算法時最好使用scanf和printf。

注:請不要在同一個程式中同時使用cout和printf,有時會出現問題。

在C++中,所有C語言中的標頭檔案XXXX.h都可以寫為CXXXX。這是等價的。

2.1基本資料型別

整型、浮點型、字元型、布林型。

對浮點型來說,一般不要使用float型,碰到浮點型可全用double儲存。因為即使使用了float型來儲存浮點數,printf這類未在原型中顯式說明引數型別的函式使用%f傳遞引數時也會將float型轉換成double型。

字元變數與字元常量:

char c;這是字元變數

char c='e';這是字元常量,必須用單引號標註

ASCII碼:

0-9為48-57;

A-Z為65-90;

a-z為97-122;

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

轉義字元:

\n換行;

\0空字元NULL,其ASCII碼值為0,注意不是空格;

字串常量:

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

不能把字串常量賦值給字元變數!!

關於布林型:

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

布林型取值只有true和false,在與整型轉換時,非零均為true,0為false。

要注意的是,轉換後計算機在儲存true和false時儲存的是1和0,再用%d輸出bool型變數只會輸出1和0。

輸出格式:

%d、%f、%c、%s。

強制型別轉換格式:

(新型別名)變數名

符號常量格式:

#define 識別符號 常量

如:

#define pi 3.14

注意末尾不加分號。

另一種定義常量的方法是使用const,格式(const 資料型別 變數名=常量;)。

注:define除了可以定義常量外,還可以定義任何語句或片段。

#define 識別符號 任何語句或片段

注:巨集定義是直接將對應部分替換,再進行編譯和執行。所以可能要多加一些括號保證結合順序不變化。

運算子:

算術運算子、關係運算符、邏輯運算子、條件運算子、位運算子。

2.2順序結構

賦值運算子+=、-=、*=、/=。

C語言的stdio.h標頭檔案提供了scanf和printf函式。

scanf函式的格式如下:

scanf(“格式控制”,變數地址);

如scanf("%d",&n);

&為取地址運算子,即找到變數n的地址。

字串(char)陣列不需要加&,因為字串陣列變數本身就是指標。

除了%c外,scanf對其他格式符的輸入以空白符(空格、tab)為結束判斷標誌。此外,字元陣列使用%s讀入時以空格和換行作為讀入結束標誌。

sacnf的%c格式可以讀入空格和換行。

printf函式的格式如下:

printf(“格式控制”,變數名稱);

printf的變數名稱不需要加&。

對double型變數,其輸出格式為%f,而在scanf中為%lf。

三種實用輸出格式:

%md:m位右對齊輸出,左邊位不足用空格補齊。超過m位保持原樣。

%0md:m位右對齊輸出,左邊位不足用0補齊。超過m位保持原樣。

%.mf:保留m位小數輸出。

getchar用來輸入單個字元,putchar用來輸出單個字元。

如:

c1=getchar();
putchar(c1);

註釋:

/*......*/可註釋若干連續行,//......只可註釋一行。

typedef用來給資料型別起別名。

math.h標頭檔案提供了許多數學函式。

注:C語言中沒有對任意底數求對數的函式,故必須用換底公式來求,即

logab=lnb/lna。

2.3選擇結構

if語句、if-else語句、if-else if-else語句,三者可以巢狀。

switch語句:遇到break語句跳出switch。

2.4迴圈結構

while語句、do-while語句(至少執行一次)、for語句。

for(表示式1;表示式2;表示式3)

注:C語言中表達式1中不允許定義變數如int =1,但是在C++中可以。

break語句:退出迴圈。

continue語句:直接開始下一次迴圈。

2.5陣列

陣列大小必須是整數常量,不可以是變數。

一維陣列: 資料型別 陣列名[陣列大小]

二維陣列: 資料型別 陣列名[第一維大小][第二維大小]

注:如果陣列較大(10的6次方級別),需要將其定義在主函式之外,原因是函式內部申請的區域性變數來自系統棧,允許申請的空間較小;而函式外部申請的全域性變數來自靜態儲存區,允許申請的空間較大。

如何對陣列中每一個元素賦相同值:

memset函式:memset(陣列名,值,sizeof(陣列名))

注意使用memset函式要新增string.h標頭檔案,且只能賦0或-1。因為memset是按位元組賦值,每個位元組賦一樣的值。

fill函式可賦其他數字的值。

fill(開始地址,結束地址,值)

字元陣列只有初始化時可以直接賦值字串。

字元陣列的輸入輸出:

scanf、printf

getchar、putchar

gets、puts(字串輸入輸出)

在一維字元陣列末尾都有一個空字元\0,表示存放的字串的結尾。\0也佔了一個字元位。\0即NULL。

注:如果不使用scanf函式%s格式或gets函式輸入字串,請一定在輸入的每個字串後加\0,否則會因無法識別字符串末尾而輸出一堆亂碼。

string.h標頭檔案:

strlen(字元陣列):得到字元陣列中字元個數(不包括\0)。

strcmp(字元陣列1,字元陣列2):返回兩個字串大小比較結果,按字典序比較,小於為負整數,等於為0,大於為正整數。

strcpy(字元陣列1,字元陣列2):把2複製給1,包括\0。

strcat(字元陣列1,字元陣列2):把2接到1後面,接的時候直接接沒有空格。

sscanf和sprintf均在stdio.h標頭檔案下。和scanf和printf類似,只是輸入源和輸出源為一個字元陣列。

如:sscanf(str,"%d",&n);sprintf(str,"%d",n)

sscanf還支援正則表示式。

2.6函式

格式:

函式返回型別 函式名稱(引數型別 引數名)

{

函式主體

}

void表示返回型別為空。

全域性變數:定義之後的所有程式段內都有效的變數。

區域性變數:定義在函式內部,且只在函式內部生效,函式結束時區域性變數銷燬。

函式定義的小括號內的引數稱為形參,呼叫函式時傳進來的值是一個副本,稱為值傳遞。實際呼叫時小括號內的引數稱為實參。

main函式對一個程式只有一個。

陣列作為引數時,引數中陣列的第一維不需要寫程度,但若是二維陣列,第二維要寫長度,實際呼叫時只需要寫陣列名。

陣列作為引數時,在函式中對陣列元素的修改等同於對原陣列元素的修改。

陣列不允許作為返回型別出現。

函式的遞迴呼叫是一個函式呼叫其自身。

2.7指標

指標儲存一個變數的地址。

在變數前加&,就表示取變數的地址。

指標實際上是一個unsigned型別的整數。

指標的形式:

int* p;
double* p;
char* p;

一般C++程式設計師習慣把星號放在資料型別之後。

給指標變數賦值的方式一般是把變數的地址取出來,然後賦給對應型別的指標變數。

指標變數支援自增和自減操作。

對指標變數來說,把其儲存的地址的型別叫做基型別。基型別必須和指標變數儲存的地址型別相同。

在C中,陣列名稱也作為陣列的首地址使用。

指標型別也可作為函式引數的型別,這時視為把變數的地址傳入函式,此時若在函式中對這個地址的元素進行改變,原先的資料也會被改變。這種方式叫做地址傳遞。

如:void change(int* p)

例子:使用指標作為引數,交換兩個數。

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

這個函式錯誤因為地址雖然交換了但是main函式傳遞的地址是值傳遞,swap函式對地址本身修改並不能對main函式裡的地址修改,能使main函式裡的資料發生變化的只能是swap函式中對地址指向的資料進行修改。

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",*p1,*p2);
}

有什麼辦法可以不使用指標,但也能達到修改傳入引數的目的?

一個很方便地做法是使用C++中的引用。引用不產生副本,而是給原變數起了個別名。舊名字和新名字都指向同一個東西,且對引用變數的操作就是對原變數的操作。由於引用是產生變數的別名,因此常量不可使用引用。

如:void change(int &x)

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

注意要把引用的&和取地址的&區分開。

上面的例子如果使用了引用來寫swap函式:

#include <stdio.h>


void swap(int* &p1, int* &p2) {
	int* temp = p1;
	p1 = p2;
	p2 = temp;
}
int main() {
	int a = 1, b = 2;
	int *pl = &a, *p2 = &b;
	swap(pl, p2);
	printf("a = %d, b = %d\n", *pl, *p2);
	return 0;
}

執行結果如下:

2.8結構體(struct)的使用

格式:

strcut Name{
......
};

如:

struct studentInfo{
    int id;
    char gender;
    char name[20];
    char major[20];
}Alice,Bob,stu[1000];

studentInfo是結構體的名字,大括號外定義了Alice和Bob兩個該結構體變數,stu[1000]是定義的一個該結構體陣列。

結構體能定義除了自己本身之外的任何資料型別,雖然不能定義自己本身,但是可以定義自身型別的指標變數。

如:

struct node{
    node n;//不能定義node型變數
    node* next;//可以定義node*型指標變數
};

訪問結構體元素有兩種方法:“.”和“->”。

結構體的初始化:

可以先定義一個結構體變數,再分別對結構體每個成員逐一賦值來初始化,但是這樣比較麻煩。

推薦使用建構函式來對結構體進行初始化。

建構函式是用來初始化結構體的一種函式,它直接定義在結構體中。建構函式不需要寫返回型別,且函式名與結構體名相同。

如對上例,studentInfo(){}就是預設生成的建構函式。

寫成:

struct studentInfo{
    int id;
    char gender;
    studentInfo(){}
};

這個建構函式存在在結構體中,才可以直接定義studentInfo型別的變數而不進行初始化。

如果我們的類沒有顯式地定義建構函式,那麼編譯器就會為我們隱式地定義一個預設建構函式。即上面的預設建構函式。

如果要手動提供id和gender的初始化引數,則寫成下面形式:

(_id和_gender都是變數名,只要不和已有變數名衝突,也可用其他名字)

struct studentInfo{
    int id;
    char gender;
    studentInfo(int _id,char _gender):id(_id),gender(_gender){}
};

或寫成下面的形式:

struct studentInfo{
    int id;
    char gender;
    studentInfo(int _id,char _gender){
    id=_id;
    gender=_gender;
    }
};

這樣就可以在需要時直接對結構體變數賦值:

studentInfostu=studentInfo(10086,'M');

如果自己重新定義了建構函式,則不能不經過初始化就定義結構體變數。因為預設的建構函式此時被覆蓋了。

為了既能不初始化話就定義結構體變數,又能享受初始化帶來的便捷,可以將studentInfo(){}手動加上,即只要引數和型別不完全相同,可以定義任意多個建構函式。

故上面結構體應該寫成下面的形式:

struct studentInfo{
    int id;
    char gender;
    studentInfo(){}
    studentInfo(int _id,char _gender){
        id=_id;
        gender=_gender;
    }
};

例項:利用建構函式錄入四個座標再打印出來。

#include <stdio.h>
struct point{
    int x,y;
    point(){}
    point(int _x,int _y):x(_x),y(_y){}
}pt[10];
int main(){
    int num=0;
    for(int i=1;i<=2;i++){
        for(int j=1;j<=2;j++){
            pt[num++]=point(i,j);
        }
    }
    for(int i=0;i<num;i++){
        printf("%d,%d\n",pt[i].x,pt[i].y);
    }
return 0;
}

建構函式使得不需要臨時變數就可以初始化一個結構體。

2.9補充

cin、cout是C++中的輸入和輸出函式,需要新增

#include <iostream>

using namespace std;

才能使用。

cin和cout不指定格式,也不需要使用取地址運算子&。

對演算法比賽來說一般不適用,因為耗費時間較多。

浮點數的比較:

浮點數經過多次計算後,其儲存可能並不精確,略多一點點或略少一點,這會對比較操作產生較大幹擾,於是要引入一個極小數eps來對這種誤差修正。一般來說eps取10的-8次方式一個比較合適的數字。

如:

const double eps=1e-8;
#define Equ(a,b) ((fabs((a)-(b)))<(eps))

這樣只要a和b差的絕對值小於eps即判斷true。

當然如果沒有經過損失精度的計算的話直接比較==也可得到true。

程式碼總結:

const double eps=1e-8;
const double Pi=acos(-1.0);圓周率π的值
#define Equ (a,b) ((fabs ((a)一(b)))< (eps)) 等於
#define More (a, b) (((a)一(b))> (eps)) 大於
#define Less (a, b) (((a)一(b))< (-eps)) 小於
#define MoreEqu (a, b) (((a) 一(b))>(-eps))大於等於
#define LessEqu (a, b) (((a)一(b))< (eps))小於等於

由於精度問題,經過大量運算後,可能一個變數中儲存的0是個很小的負數,這時若開根號sqrt,就會因為不在定義域而出錯。

對一般的OJ系統來說,一秒能承受的運算次數大概是10的7次方到8次方,故若時間複雜度o(n平方)的演算法n規模為1000時可以承受,再大於則可能無法承受。

在演算法比賽中,空間一般夠用,只要不開幾個10的7次方以上陣列就行,所以一般採用以空間換時間的策略。

2.10黑盒測試

單點測試:

系統會判斷每組資料的輸出結果是否正確。如果輸出正確,那麼對該組資料來說就通過了測試,並獲得了這組資料的分值。在這種情況下,題目的總得分等於通過的資料的分值之和。PAT就是採用了單點測試,並且對每組資料都會給出相應的測評結果。從程式碼編寫上來說,單點測試只需要按正常的邏輯執行一遍程式即可,是 “一次性” 的寫法,即程式只需要對一組資料能夠完整執行即可。對多組資料會多次執行程式。

多點測試:

要求程式能一次執行所有資料,並要求所有輸出結果都必須完全正確,才能算作這題通過;而只要有其中一組資料的輸出錯誤,本題就只能得0分。大部分線上評測系統(包括本書來自 codeup 的除了 C 語言訓練部分以外的練習題)都採用了這種方式,因為只有這種方式才能嚴格考驗做題人的程式碼是否嚴謹。

對於多點測試因為要反覆執行程式碼的核心部分,故要用到迴圈,題目一般會有3種輸入的格式,需要採用不同的輸入方式。

1、while......EOF型

如果題目沒有給定輸入的結束條件,那麼就預設讀取到檔案末尾。在實際操作中,如使用scanf函式讀取資料,scanf返回值為成功讀入的引數個數。正常的控制檯(黑框)中輸入一般不會失敗,只有在讀取檔案到達末尾無法讀取才會失敗,這時scanf函式返回-1,且C語言中使用EOF代表-1。

故一般寫法如下:

while(scanf("%d",&n)!=EOF){
......
}

windows系統中如果要在黑框中手動觸發EOF,可按ctrl+Z組合鍵再enter。

2、while......break型

當輸入資料滿足某個條件時停止輸入。有兩種寫法,一是在while......EOF型內進行判斷,二是把退出條件的判斷放到while語句中,令其與scanf用逗號隔開。

示例如下:

第一種寫法:

#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;
}

第二種寫法:

#include <stdio.h>
int main() {
    int a, b;
    while (scanf ("%d%d", &a, &b), a||b) {
        printf ("%d\n", a + b);
    }
return 0;
}

3、while(T--)型

這種型別中,題目會給出測試資料的組數,然後才給出相應數量組數的輸入資料。故用一個變數T--來讓程式執行T次迴圈。

#include <cstdio>
int main () {
    int a, b;
    scanf ("%d", &T);
    while (T--){
        scanf("%d%d",&a, &b);
        printf("%d\n", a + b);
    }
return 0;
}

注意:

在多點測試中,每次迴圈都要重置一下變數和陣列,否則在下一組資料來臨時變數和陣列狀態就不是初始狀態了。重置陣列一般用memset函式或fill函式。