最全C語言筆記篇(中)

2020-08-12 19:33:11

BACK:最全C語言筆記篇(上)

Seventh Week 函數


1. 函數的定義和使用

沒有返回值的函數:

  • void 函數名(參數表)
  • 不能使用帶值的return
    • 可以沒有return
  • 呼叫的時候不能做返回值的賦值
void sum(int begin, int end) {
	int i;
	int sum = 0;
	for (i=begin; i<=end; i++) {
		sum += i;
	}
	printf("%d到%d的和是%d\n", begin, end, sum);
}

2. 函數的參數和變數

函數的先後 先後關係:

  • 像這樣把sum()寫在上面,是因爲:
  • C的編譯器自上而下順序分析你的程式碼
  • 在看到sum(1,10)的時候,它需要知道sum()的樣子
  • 也就是sum()要幾個參數,每個參數的型別如何,返回什麼型別
  • 這樣它才能 纔能檢查你對sun()的呼叫是否正確
void sum(int begin, int end) {
	int i;
	int sum = 0;
	for (i=begin; i<=end; i++) {
		sum += i;
	}
	printf("%d到%d的和是%d\n", begin, end, sum);
}

int main() {
	sum(1, 10);
	sum(20, 30);
	sum(35, 45);
    
	return 0;
}

函數原型:

  • 函數頭,以分號「;」結尾,就構成了函數的原型
  • 函數原型的目的是告訴編譯器這個函數長什麼樣
    • 名稱
    • 參數(數量及型別)
    • 返回型別
  • 舊標準習慣把函數原型寫在呼叫它的函數裏面
  • 現在一般寫在呼叫它的函數前面
  • 原型裡可以不寫參數的名字,但是一般仍然寫上
double max(double a, double b);  // 函數原型

int main() {
	int a, b, c;
	a = 5;
	b = 6;
	c = max(10, 12);
	printf("%d\n", c);
	max(12, 13);  // 根據原型判斷
	
	return 0;
}

double max(double a, double b) {  // 實際的函數頭

}

型別不匹配?

  • 呼叫函數時給的值與參數的型別不匹配時c語言系統上最大的漏洞
  • 編譯器總是悄悄替你把型別轉換好,但是這很可能不是你所期望的
  • 後續的語言,C++/Java在這方面很嚴格

傳過去的是什麼?

  • C語言在呼叫函數時,永遠只能傳值給函數
  • 這樣的程式碼能交換a和b的值嗎?(不能)
void swap(int a, int b);

int main() {
	int a = 5;
	int b = 6;
	
	swap(a, b);
	printf("a=%d b=%d\n", a, b);
	
	return 0;
}

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

傳值:

  • 每個函數都有自己的變數空間,參數也位於這個獨立的空間中,和其他函數沒有關係
  • 過去,對於函數參數表中的參數,叫做「形式參數」,呼叫函數時給的值,叫做「實際參數」
  • 由於容易讓初學者誤會實際參數就是實際在函數中進行計算的參數,誤會呼叫函數的時候把變數而不是值傳進去了,所以我們不建議繼續用這種古老的方式來稱呼他們
  • 我們認爲,它們是參數和值的關係

本地變數:

  • 函數的每次執行,就產生一個獨立的變數空間,在這個空間中的變數,是函數的這次執行所獨有的,稱作本地變數
  • 定義在函數內部的變數就是本地變數
  • 參數也是本地變數

變數的生存期和作用域

  • 生存期:什麼時候這個變數開始出現了,到什麼時候它消亡了
  • 作用域:在(程式碼的)什麼範圍內可以存取這個變數(這個變數可以起作用)
  • 對於本地變數,這兩個問題的答案是統一的:大括號內 ——> 塊

本地變數的規則:

  • 本地變數是定義在塊內的
    • 它可以是定義在函數的塊內
    • 也可以定義在語句的塊內
    • 甚至可以隨便拉一對大括號來定義變數
  • 程式執行進入這個塊之前,其中的變數不存在,離開這個塊,其中的變數就消失了
  • 塊外面定義的變數在裏面仍然有效
  • 塊裏面定義了和外面同名的變數則掩蓋了外面的
  • 不能在一個塊內定義同名的變數
  • 本地變數不會被預設初始化
  • 參數在進入函數的時候被初始化了

沒有參數時:

  • void f(void);
    
  • 還是

  • void f();
    
    • 在傳統C中,他表示f函數的參數表未知,並不表示沒有參數

關於main:

  • int main()也是一個函數
  • 要不要寫成int main(void)?
  • return的0有人看嗎?
    • windows(批次檔中):if error level 1…
    • unix bash:echo $?
    • csh:echo $status

可以在一個函數裡放另一個函數的宣告,但是不能定義另一個函數。


Eighth Week 陣列


1. 陣列

定義陣列:

  • <型別> 變數名稱[元素數量];

    • int grades[100];
      
    • double weight[20];
      
  • 元素數量必須是整數

  • C99之前:元素數量必須是編譯時刻確定的字面量

陣列:

  • 是一種容器(放東西的東西),特點是:
    • 其中所有的元素具有相同的數據型別;
    • 一旦建立,不能改變大小
    • *(陣列中的元素在記憶體中是連續依次排列的)

有效的下標範圍:

  • 編譯器和執行環境都不會檢查陣列下標是否越界,無論是對陣列單元做讀還是寫
  • 一旦程式執行,越界的陣列存取可能造成問題,導致程式崩潰
    • segmentation fault
  • 但是也可能運氣好,沒造成嚴重的後果
  • 所以這是程式設計師的責任來保證程式只使用有效的下標值:[0,陣列的大小-1]

  • int a[0];  // 可以存在,但是無用
    

統計個數範例:

const int number = 10;  // 陣列的大小,c99
int x;
int count[number];  // 定義陣列
int i;

for (i=0; i<number; i++) {  // 初始化陣列
    count[i] = 0;
}
scanf("%d", &x);
while (x!=-1) {
    if (x>=0 && x<=9) {
        count[x]++;  // 陣列參與運算
    }
    scanf("%d", &x);
}
for (i=0; i<number; i++) {  // 遍歷陣列輸出
    printf("%d:%d\n", i, count[i]);
}

2. 陣列運算

整合初始化時的定位:

int a[10] = {  // c99 ONLY
	[0] = 2, [2] = 3, 6,
};
  • 用[n]在初始化數據中給出定位
  • 沒有定位的數據接在前面的位置後面
  • 其他位置的值補零
  • 也可以不給出陣列大小,讓編譯器算
  • 特別適合初始數據稀疏的陣列

陣列的大小:

  • sizeof給出整個陣列所佔據的內容的大小,單位是位元組

    sizeof(a)/sizeof(a[0])
    
  • sizeof(a[0])給出陣列中單個元素的大小,於是相除就得到了陣列的單元個數

  • 這樣的程式碼,一旦修改陣列中初始的數據,不需要修改遍歷的程式碼

陣列的賦值:

int a[] = {2, 4, 6, 7, 1, 3};
int b[] = a;
  • 陣列變數本身不能被賦值
  • 要把一個數組的所有元素交給另一個數組,必須採用遍歷
for (i=0; i<length; i++) {
	b[i] = a[i];
}

  • 陣列作爲函數參數時,往往必須再用另一個參數來傳入陣列的大小
  • 陣列作爲函數的參數時:
    • 不能在[]中給出陣列的大小
    • 不能再利用sizeof來計算陣列的元素個數!
/*
找出key在陣列a中的位置
@param key 要尋找的數位
@param a 要尋找的陣列
@param length 陣列a的長度
@return 如果找到,返回其在a中的位置;如果找不到則返回-1
*/
int search(int key, int a[], int length);

int main(void) {
	int a[] = {2, 4, 6, 7, 1, 3, 5, 9};
	int x;
	int loc;
	printf("請輸入一個數字:");
	scanf("%d", &x);
	loc = search(x, a, sizeof(a)/sizeof(a[0]));
	if (loc != -1) {
		printf("%d在第%d個位置上\n", x, loc);
	} else {
		printf("%d不存在\n", x);
	}
	
	return 0;
}

int search(int key, int a[], int length) {
    int ret = -1;
    int i;
    for (i=0; i<length; i++) {
        if (a[i] == key) {
            ret = i;
            break;
        }
    }
    return ret;
}

2.1 素數

判斷是否能被已知的且<x的素數整除:

int isPrime(int x, int knownPrimes[], int numberOfKnownPrimes) {
	int ret = 1;
	int i;
	for (i=0; i<numberOfKnownPrimes; i++) {
		if (x%knownPrimes[i] == 0) {
			ret = 0;
			break;
		}
	}
	return ret;
}

int main(void) {
	const int number = 100;
	int prime[number] = {2};
	int count = 1;
	int i = 3;
	while (count < number) {
		if (isPrime(i, prime, count)) {
			prime[count++] = 1;
		}
		i++;
	}
	for (i=0; i<number; i++) {
		printf("%d", prime[i]);
		if ((i+1)%5) printf("\t");
		else printf("\n");
	}
    
	return 0;
}

構造素數表:

  • 預構造n以內的素數表
    1. 令x爲2
    2. 將2x、3x、4x直至ax<n的數標記爲非素數
    3. 令x爲下一個沒有被標記爲非素數的數,重複2;直到所有的數都已經嘗試完畢

  • 構造n以內(不含)的素數表
    1. 開闢prime[n],初始化其所有元素爲1,prime[x]爲1表示x是素數
    2. 令x=2
    3. 如果x是素數,則對於 (i=2; x*i<n; i++) 令 prime[i*x]=0
    4. 令x++,如果x<n,重複3,否則結束
#include <stdio.h>

int main() {
	const int maxNumber = 25;
	int isPrime[maxNumber];
	int i;
	int x;
	for (i=0; i<maxNumberl i++) {
		isPrime[i] = 1;
	}
	for (x=2; x<maxNumber; i++) {
		if (isPrime[x]) {
			for (i=2; i*x<maxNumber; i++) {
				isPrime[i*x] = 0;
			}
		}
	}
	for (i=2; i<maxNumber; i++) {
		if (isPrime[i]) {
			printf("%d\t", i);
		}
	}
	printf("\n");
	
	return 0;
}

2.2 二維陣列

二維陣列的初始化:

int a[][5] = {
	{0, 1, 2, 3, 4},
	{2, 3, 4, 5, 6},
};
  • 列數是必須給出的,行數可以由編譯器來數
  • 每行一個{},逗號分隔
  • 最後的逗號可以存在,有古老的傳統
  • 如果省略,表示補零
  • 也可以用定位(*C99 ONLY)


Ninth Week 指針


1. 指針

只能變數取地址

  • 就是儲存地址的變數
int i;
int* p = &i;
int* p, q;
int *p, q;  // 與上一行所表達一樣,p指針,q是int型別變數

存取那個地址上的變數*

  • *是一個單目運算子,用來存取指針的值所表示的地址上的變數
  • 可以做右值也可以做左值
    • int k = *p;
      
    • *p = k+1;
      

*左值之所以叫左值

  • 是因爲出現在賦值號左邊的不是變數,而是值,是表達式計算的結果;
    • a[0] = 2;
      
    • *p = 3;  // 賦值時,p代表的是地址,\*p代表的是地址所指的值。
      
  • 是特殊的值,所以叫做左值

2. 指針應用

指針應用場景一:

  • 交換兩個變數的值
void swap(int *pa, int *pb) {
	int t = *pa;
	*pa = *pb;
	*pb = t;
}

指針應用場景二a:

  • 函數返回多個值,某些值就只能通過指針返回
    • 傳入的參數實際上是需要儲存帶回的結果的變數

指針應用場景二b:

  • 函數返回運算的狀態,結果通過指針返回
  • 常用的套路是讓函數返回特殊的不屬於有效範圍內的值來表示出錯:
    • -1或0(在檔案操作會看到大量的例子)
  • 但是當任何數值都是有效的可能結果時,就得分開返回了
    • 後續的語言(C++,Java)採用了異常機制 機製來解決這個問題

3. 常見錯誤

定義了指針變數,還沒有指向任何變數,就開始使用指針

4. 陣列和指針

傳入函數的陣列成了什麼?

  • 函數參數表中的陣列實際上是指針
    • sizeof(a) == sizeof(int*)
      
    • 但是可以用陣列的運算子[]進行運算

int isPrime(int x, int knownPrimes[], int numberOfKnownPrimes) {
	int ret = 1;
	int i;
	for (i=0; i<numberOfKnownPrimes; i++) {
		if (x%knownPrimes[i] == 0) {
			ret = 0;
			break;
		}
	}
	return ret;
}

陣列變數是特殊的指針:

  • 陣列變數本身表達地址,所以

    • int a[10]; int *p=a;  // 無需用&取地址
      
    • 但是陣列的單元表達的是變數,需要用&取地址

    • a == &a[0]
      
  • []運算子可以對陣列做,也可以對指針做:

    • p[0] <==> a[0]
  • *運算子可以對指針做,也可以對陣列做:

    • *a = 25;
      
  • 陣列變數是const的指針,所以不能被賦值

    • int a[] <==> int *cosnt a=…

5. 指針和const

指針是const:

  • 表示一旦得到了某個變數的地址,不能再指向其他變數(可以改所指地址變數的值,不可以改所指地址)
    • int* const q = &i;  // q是const
      
    • *q = 26;  // OK
      
    • q++;  // ERROR
      

所指是const:

  • 表示不能通過這個指針去修改那個變數(並不能使得那個變數成爲const。變數本身是可以修改的,但是不可以通過那個指針去修改)
    • const int *p = &i;
      
    • *p = 26;  // ERROR!(\*p)是const
      
    • i = 26;  // OK
      
    • p = &j;  // OK
      

轉換:

  • 總是可以把一個非const的值轉換成const的
void f(const int *x);  // 表示接受的值,我不會去更改它,你放心!

int a = 15;
f(&a);  // ok
const int b = a;  // 無論傳入f的值是不是const都可以
f(&b);  // ok

b = a + 1;  // Error
  • 當要傳遞的參數的型別比地址大的時候,這是常用的手段:既能用比較少的位元組數傳遞值給參數,又能避免函數對外面的變數的修改

6. 指針運算

1+1=2?

  • 給一個指針加1表示要讓指針指向下一個變數
int a[10];
int *p = a;
// *(p+1)——>a[1]
  • 如果指針不是指向一片連續分配的空間,如陣列就是連續分配的空間,則這種運算沒有意義

指針計算:

  • 這些算術運算可以對指針做:
    • 給指針加、減一個整數(+,+=,-,-=)
    • 遞增遞減(++/–)

指針相減是指相隔了多少個數

*p++

  • 取出p所指的那個數據來,完事之後順便把p移到下一個位置去
  • *的優先順序雖然高,但是沒有++高
  • 常用於陣列類的連續空間操作
  • 在某些CPU上,這可以直接被翻譯成一條彙編指令

指針比較:

  • <,<=,==,>,>=,!=都可以對指針做(地址大小的比較)
  • 比較它們在記憶體中的地址
  • 陣列中的單元的地址肯定是線性遞增的

0地址

  • 當然你的記憶體中有0地址,但是0地址通常是個不能隨便碰的地址
  • 操作系統會給每個進程一片虛擬的地址空間,都含有虛擬的從0地址開始的連續空間
  • 所以你的指針不應該具有0值
  • 因此可以用0地址來表示特殊的事情:
    • 返回的指針是無效的
    • 指針沒有被真正初始化(可以先初始化爲0,但是並沒有意義的值。可以當你使用這個變數的時候系統會崩潰,表明這個值爲0或沒有初始化)
  • NULL是一個預定定義的符號,表示0地址
    • 有的編譯器不願意你用0來表示0地址

指針的型別:

  • 無論指向什麼型別,所有的指針的大小都是一樣的,因爲都是地址
  • 但是指向不同類型的指針是不能直接相互賦值的
  • 這是爲了避免用錯指針

指針的型別轉換:

  • void*表示不知道指向什麼東西的指針
    • 計算時與char*相同(但不相通)
  • 指針也可以轉換型別
int *p = &i;
void *q = (void*)p;
  • 這並沒有改變p所指的變數的型別,而是讓後人用不同的眼光通過p看它所指的變數
    • 我不再當你是int啦,我認爲你就是個void!

  1. 指針的型別就是指針
    指針大小和記憶體的編址方式有關,只是恰好與無符號整形大小相同
    他的大小是4位元組(32位元)就是類似0012ff78(16進位制 32位元)
    注:如果你的電腦是64位元電腦那麼他的大小就是8位元組!
  2. 指針是用來儲存記憶體地址的
    記憶體有按32位元編制 編製和按64位元編制 編製之分
  3. 爲什麼要給指針定義型別呢?
    只有爲指針定義型別
    才能 纔能知道指針所指向的變數的大小
    例如: int*p;和 double*q;
    那麼讀取*p時 就要從地址p開始讀取4位元組
    讀取*q時就要從地址q開始讀取8位元組

用指針來做什麼:

  • 需要傳入較大的數據時用作參數
  • 傳入陣列後對陣列做操作
  • 函數返回不止一個結果
    • 需要用函數來修改不止一個變數
  • 動態申請的記憶體

7. 動態分配記憶體

malloc:

#include <stdlib.h>
void* malloc(size_t size);
  • 向malloc申請的空間的大小是以位元組爲單位的

  • 返回的結果是void*,需要型別轉換爲自己需要的型別

    • (int*)malloc(n*sizeof(int))
      

沒空間了?

  • 如果申請失敗則返回0,或者叫做NULL
  • 你的系統能給你多大的空間?
#include <stdio.h>
#include <stdlib.h>

int main(void) {
	void *p;
	int cnt = 0;
	while (p=malloc(100*1024*1024)) {
		cnt++;
	}
	printf("分配了%d00MB的空間\n", cnt);
	
	return 0;
}

free():

  • 把申請得來的空間還給「系統」
  • 申請過的空間,最終都應該要還
    • 出來混,遲早都是要還的
  • 只能還申請來的空間的首地址

常見問題:

  • 申請了每free ——> 長時間執行記憶體逐漸下降
    • 新手:忘了
    • 老手:找不到合適的free的時機
  • free過了再free
  • 地址變過了,直接去free


Tenth Week 字串


1. 字串

  • 以0(整數0)結尾的一串字元
    • 0或’\0’是一樣的,但是和’0’不同
  • 0標誌字串的結束,但它不是字串的一部分
    • 計算字串長度的時候不包含這個0
  • 字串以陣列的形式存在,以陣列或指針的形式存取
    • 更多的是以指針的形式
  • string.h裡有很多處理字串的函數

字串變數:

char *str = "Hello";
char word[] = "Hello";
char line[10] = "Hello";

字串常數1:

  • 「Hello」
  • "Hello"會被編譯器變成一個字元陣列放在某處,這個陣列的長度是6,結尾還有表示結束的0
  • 兩個相鄰的字串常數會被自動連線起來(可以用反斜槓\來分割字串爲上下兩行!但是第二行的兩個tab也會列印出來,所以第二行需要頂到最前面!)

字串:

  • C語言的字串是以字元陣列的形態存在的
    • 不能用運算子對字串做運算
    • 通過陣列的方式可以遍歷字串
  • 唯一特殊的地方是字串字面量""可以用來初始化字元陣列
  • 以及標準庫提供了一系列字串函數

字串常數2:

char *s = "Hello, world!";
  • s是一個指針,初始化爲指向一個字串常數
    • 由於這個常數所在的地方,所以實際上s是const char *s,但是由於歷史的原因,編譯器接受不帶const的寫法
    • 但是試圖對s所指的字串做寫入會導致嚴重的後果
  • 如果需要修改字串,應該用陣列:
char s[] = "Hello, world!";

指針還是陣列?

char *str = "Hello";
char word[] = "Hello";
  • 陣列:這個字串在這裏,在一個固定的位置

    • 作爲本地變數空間自動被回收
  • 指針:這個字串不知道在哪裏

    • 處理參數
    • 動態分配空間
  • 如果要構造一個字串 ——> 陣列方式

  • 如果要處理一個字串 ——> 指針方式

char*是字串?

  • 字串可以表達爲char*的形式
  • char*不一定是字串
    • 本意是指向字元的指針,可能指向的是字元的陣列(就像int*一樣)
    • 只有它所指的字元陣列有結尾的0,才能 纔能說它所指的是字串

字串輸入輸出:

  • char string[8];
    scanf("%s", string);
    printf("%s", string);
    
  • scanf讀入一個單詞(到空格、tab或回車爲止)

  • scanf是不安全的,因爲不知道要讀入的內容的長度

安全的輸入:

  • char string[8];
    scanf("%7s", string);
    
  • 在%和s之間的數位表示最多允許讀入的字元的數量,這個數位應該比陣列的大小小一

    • 下一次scanf從哪裏開始?(交給下一個%或者scanf去閱讀!)

常見錯誤:

  • char *string;
    scanf("%s", string);
    
  • 誤以爲char*是字串型別,定義了一個字串型別的變數string就可以直接使用了,

    • 其實由於沒有對string初始化爲0,可能指向的地址無害,則所以不一定每次執行都出錯

空字串:

  • char buffer[100] = "";
    
    • 這是一個空的字串,buffer[0] = ‘\0’
  • char buffer[] = "";
    
    • 這個陣列的長度只有1!

字串陣列:寫一個數組表達很多個字串

  • char **a;  // 非字串陣列
    
    • a是一個指針,指向另一個指針,那個指針指向一個字元(串)
  • char a[][10]; 
    char *a[];  // 字串陣列的兩種表達形式:第一種每個字串有固定的長度,用陣列來表示;而第二種沒有固定的長度,因爲每個字串是使用指針來表示的。
    

程式參數:

  • int main(int argc, char const *argv[])
    
  • argc是argv的個數,argv[0]是命令本身,argv[0]後面的是命令列輸入的參數

    • 當使用Unix的符號鏈接時,反應符號鏈接的名字
#include <stdio.h>

int main(int argc, char const *argv[]) {
	int i; 
	for (i=0; i<argc; i++) {
		printf("%d:%s\n", i, argv[i]);
	}
	
	return 0;
}

2. 字串函數

putchar:

  • int putcahr(int c);
    
  • 向標準輸出寫一個字元

  • 返回寫了幾個字元,EOF(-1)表示寫失敗

getchar:

  • int getchar(void);
    
  • 從標準輸入讀入一個字元

  • 返回型別是int是爲了返回EOF(-1)

    • Windows ——> Ctrl-Z
    • Unix ——> Ctrl-D
#include <stdio.h>

int main(int argc, char const *argv[]) {
	int ch;
	
	while ((ch=getcahr()) != EOF) {
		putchar(ch);
	}
	
	return 0;
}

Crtl+C強制結束

Crtl+Z返回EOF(windows)

string.h:

  • strlen:返回字串長度
  • strcmp:比較兩個字串
  • strcpy:字串拷貝
  • strcat:字串相接
  • strchr:字串中找字元
  • strstr:字串中找字串

strlen:

  • size_t strlen(cosnt char *s);
    
  • 返回s的字串長度(不包括結尾的0)

#include <stdio.h>
#include <string.h>

size_t mylen(const char *s) {
	int idx = 0;
	while (s[idx] != '\0') {
		idx++;
	}
	return idx;
}

int main(int argc, char const *argv[]) {
	char line[] = "Hello";
    printf("strlen=%lu\n", mylen(line));
    printf("sizeof=%lu\n", sizeof(line));
    
    return 0;
}

strcmp:

  • int strcmp(const char *s1, const char *s2);
    
  • 比較兩個字串,根據ascii碼從左到右依次比較,返回:

    • 0:s1 == s2
    • 1:s1 > s2
    • -1:s1 < s2
#include <stdio.h>
#include <string.h>

int mycmp(const char *s1, const char *s2) {
	// int idx = 0;
	// while (s1[idx]==s2[idx] && s1[idx]!='\0') {
    // 	 idx++;
	// }
	while (*s1==*s2 && s1!='\0') {
		s1++;
		s2++;
	}
	return *s1 - *s2
}

int main(int argc, char const *argv[]) {
	char s1[] = "abc";
	char s2[] = "abc";
	printf("%d\n", mycmp(s1, s2));
	printf("%d\n", 'a'-'A');
	
	return 0;
}

strcpy:

  • char* strcpy(char *restrict dst, const char *restrict src);
    
  • 把src的字串拷貝到dst

    • restrict表明src和dst不重疊,爲了多核效能(c99纔有)
  • 返回dst

    • 爲了能鏈起程式碼來

複製一個字串:

char *dst = (char*)malloc(strlen(src)+1);
strcpy(dst, src);
#include <stdio.h>
#include <string.h>

char* mycpy(char *dst, const cahr *src) {
    // int idx = 0;
    // while (src[idx]) {
    //     dst[idx] = src[idx];
    //     idx++;
    // }
    // dst[idx] = '\0';
    char *ret = dst;
    while (*dst++ == *src++);
    *dst = '\0';
    
    return 0;
}

int main(int argc, char const *argv[]) {
    char s1[] = "abc";
    char s1[] = "abc";
    strcpy(s1, s2);
    
    return 0;
}

strcat:

  • char* strcat(char *restrict s1, const char *restrict s2);
    
  • 把s2拷貝到s1的後面,接成一個長的字串,會把s1最後的’\0’給覆蓋掉

  • 返回s1

  • s1必須具有足夠的空間

安全版本:目的地沒有足夠的空間

  • char* strncpy(char *restrict dst, const char *restrict src, size_t n);
    
  • char* strncat(char *restrict s1, const char *restrict s2, size_t n);
    
  • int strncpm(const char *s1, const char *s2, size_t n);  // 此函數的作用不是爲了安全,是爲了決定比較前n個的字元
    

字串中找字元:

  • char* strchr(const char *s, int c);  // 返回找到的字元及其後面的字串
    
  • char* strrchr(const char *s, int c);  // 從右開始找
    
  • 返回NULL表示沒有找到

  • 如何尋找第二個?

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char const *argv[]) {
	char s[] = "hello";
    char *p = strchr(s, 'l');
    char c = *p;
    *p = '\0';  // 返回字元l之前的字串
    char *t = (char*)malloc(strlen(s)+1);
    strcpy(t, s);  // 把選定的字元的字串複製到另一個變數
    printf("%s\n", t);
    free(t);
    
    return 0;
}

字串中找字串:

  • char* strstr(const char *s1, const char *s2);
    
  • char* strcasestr(const char *s1, const char *s2);  // 忽略大小寫
    

NEXT:最全C語言筆記篇(下)