標準C語言4

sleeeeeping發表於2024-05-05

一、函式

什麼是函式:function

​ 函式就是一段具有某一項功能的程式碼集合,它是C語言中管理程式碼的最小單位,把具有某項功能的若干行程式碼封裝在函式中方便管理程式碼且方便重複呼叫。

函式的分類:

標準庫函式:

​ C語言標準委員會為C語言以函式形式提供了一些基礎功能,這些函式被封裝在libc.so庫檔案中,使用時需要匯入對應的標頭檔案,它們的詳細介紹在man手冊的第3章節。

系統函式:

​ 作業系統為程式設計師提供了一些系統API,可以以函式形式呼叫,但它們不是真正的函式,講UNIX系統環境程式設計時會詳細講解,它們的詳細介紹在man手冊的第2章節。

第三方庫函式:

​ 一些公司或開源組織實現的一些常用工具供程式設計師使用。

​ MD5 、 JSON 序列化反序列化、XML配置檔案

自定義函式:

​ 為了更方便的管理、呼叫程式碼,降低開發難度,程式設計師自己封裝的一些函式。

常用標準庫函式介紹:

​ 標準庫中除上封裝了函式,還提供一些標頭檔案,裡面是對函式的說明。

stdio.h 輸入輸出相關功能的函式:
int printf(const char *format, ...);
功能:輸出資料到終端
format:提示資訊+佔位符+跳脫字元組成
...:若干個變數名或資料
返回值:成功輸出的字元個數

int scanf(const char *format, ...);
功能:從終端讀取資料
format:一般情況下,只需要佔位符即可,除了佔位符以外的資訊,在輸入資料時要原樣補出。
...:若干個變數的地址
返回值:成功讀取的變數個數

int getchar(void);
int putchar(int c);
int puts(const char *s);
stdlib.h 實用的庫函式:
int system(const char *command);
功能:呼叫系統命令,命令執行完成後,該函式才返回
返回值:成功返回0,失敗返回-1。
	system("clear");	//	清屏命令

int rand(void);
功能:從系統中獲取隨機數 0~RAND_MAX
返回值:都是正整數,如果需要負數或浮點數,需要程式設計師自已處理。

void srand(unsigned int seed);
功能:所謂的隨機數就是把所有整數打亂順序,從某個位置獲取,預設從1位置獲取,程式執行時如果"位置"不改變,獲取隨機數與上次一樣,為了保證每次執行時,提供的位置都發生變化,一般把time函式的返回值提供給srand作為隨機數的種子。
	srand(time(NULL));
    
int abs(int j);
功能:計算並返回j的絕對值
ctype.h 字元型別的判斷函式:
int isdigit( int ch );
功能:判斷是否是數字字元
int islower( int ch );
功能:判斷是否是小寫字母
int isupper( int ch );
功能:判斷是否是大寫字母
time.h 時間日期相關的函式:
time_t time(time_t *tloc);
功能:獲取當前系統的時間,返回自 1970年1月1日 00:00:00 到現在一共過了多少秒,格林時間+8小時就是北京時間。
	time(NULL)

struct tm *localtime(const time_t *timep);
功能:把秒資料時間轉換成年月日、時分秒
math.h 數學相關的函式 :
double pow(double x, double y);
功能:計算出x的y次數

double sqrt(double x);
功能:計算x的平方根

double ceil(double x);
功能:向上取整,返回大於x的最小整數

double floor(double x);
功能:向下取整,返回小於x的最大整數
注意:使用這些函式在編譯時必須有-lm引數 libm.so

自定義函式:

​ 有兩情況適合把程式碼封裝成自定義函式:
​ 1、程式碼量過多,一般程式碼量超過50行就要考慮封裝成函式,方便管理程式碼,提高程式碼的安全性(程式設計師平均每50行會出現一個BUG)。
​ 2、如果一個程式碼需要在不同位置多次執行,為了防止出現程式碼冗餘,就要把它封裝成函式,方便重複使用,也能降低可執行檔案的大小。

函式宣告:

返回值型別 函式名(引數列表);

​ 1、根據函式的功能為函式取名字,在Linux系統下函式名全部小寫,多個單詞用下劃線分隔。
​ 2、引數列表,指的是函式執行時,呼叫者需要傳遞它的資料,此時重點關注的是引數的型別,在函式宣告時可以忽略引數的名,如果函式執行時不需要呼叫者傳遞資料則寫void。

返回值型別 函式名(型別名 形參名1,型別名 形參名2,...);
返回值型別 函式名(int n1,int n2,...);
返回值型別 函式名(void);

​ 3、返回值型別,指的是函式的執行結果是什麼型別的資料,如果函式沒有返回值,則寫void。
​ 4、函式宣告就是告訴編譯器該函式的格式,方便編譯器檢查呼叫者的使用是否正確。

​ 5、一般函式宣告放在main函式之前

函式定義:
返回值型別 函式名(型別 引數名) {
    // 函式體
}
// 如果函式的定義出現在呼叫之前,函式宣告可以省略
函式呼叫:

函式名(實參);

1、呼叫者會把實參賦值給形參變數。
2、函式的返回值會放置在呼叫位置,可立即使用,也可用變數儲存。

輸入兩個日期yyyy-mm-dd,計算相差多少天,考慮封裝函式
#include <stdio.h>                                                                                                         
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
uint32_t date_to_days(uint16_t year,uint8_t month,uint8_t day) {//  計算日期天數函式
    uint32_t sum = day;
    for (int y = 1; y < year; ++y) {
        sum += 365 + is_leap(y);
    }
    char arr[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    arr[1] += is_leap(year);
    for (int m = 0; m < month - 1; ++m) {
        sum += arr[m];
    }
    return sum;
}
bool is_leap(uint16_t year) { //  判斷閏平年
    return 0 == year % 4 && 0 != year % 100 || 0 == year % 400;
}
int main() {
    uint16_t y = 0;
    uint8_t m = 0,d = 0;
    printf("請輸入一個日期(yyyy-mm-dd)");
    scanf("%hu-%hhu-%hhu",&y,&m,&d);
    uint32_t day = date_to_days(y,m,d);
    printf("請再輸入一個日期(yyyy-mm-dd)");
    scanf("%hu-%hhu-%hhu",&y,&m,&d);
    printf("間隔了%d天\n", abs((int)date_to_days(y,m,d)-(int)day));
}
自定義函式要注意的問題:

​ 1、函式的名稱空間互相獨立,函式之間傳參是單向值傳遞(實參給形參賦值),所以兩個函式之間不能透過傳參共享變數。

#include <stdio.h>
int func(int num) {
    printf("func:%d\n",num);
    num = 100;
    printf("func:%d\n",num);
    printf("func:%p\n",&num);
    return 0;
}
void swap(int n1, int n2) { //  實現一個交換兩個變數的值的函式
    printf("swap:n1:%d n2:%d\n",n1,n2);
    int temp = n1;
    n1 = n2;
    n2 = temp;
    printf("swap:n1:%d n2:%d\n",n1,n2);
}
int main() {
    int num = 10;
    printf("main:%d\n",num);
    printf("main:%p\n",&num);
    func(num);
    printf("main:%d\n",num);
    int num1 = 10,num2 = 20;
    printf("num1:%d num2:%d\n",num1,num2);
    swap(num1,num2);
    printf("num1:%d num2:%d\n",num1,num2);
}                                                                              

​ 2、C語言中如果函式的引數列表是空的,則意味著該函式提供任意型別、多個引數都可以呼叫,容易給呼叫者造成誤會,影響程式碼的可讀性,如果函式執行時不需要呼叫者傳遞資料則引數列表要寫void,不要空著。

#include <stdio.h>
int func(void) {
    printf("func\n");
}
int main() {
    func(); 
    //func(10); 
    //func(3.14,10);    
    //func(3.14,"hehe",10);                                  
}

​ 3、如果函式有返回值但沒有寫return語句,呼叫該函式時依然有返回值。
​ 當呼叫一個有返回值的函式時,系統會為呼叫者和被呼叫者約定一個空間用於儲存返回值,而return語句的作用就是把一個資料儲存到這個空間,如果沒有寫return語句,呼叫者依然會從共用空間讀取返回值,只是讀取到的資料是隨機的。

#include <stdio.h>
int func(void) {
    //return 10;
}
int main() {
    int num = func();
    printf("%d\n",num);                                      
}

​ gcc -Wall -Werror xxx.c 可以防止漏寫return語句。

xxx.c:x:x: error: control reaches end of non-void function [-Werror=return-type]

​ 4、當使用陣列作為函式的引數傳遞時,它的長度資訊就丟失了,(陣列會蛻變成指標),無法使用sizeof計算陣列的長度,需要呼叫者額外提供一個引數作為陣列的長度。

void show_arr(int arr[], size_t len) {
    for (int i = 0; i < len; ++i) {
        printf("%d%c", arr[i], " \n"[i == len - 1]);
    }
}
int main() {
    int arr[] = {1,2,3,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5};
    show_arr(arr,sizeof(arr)/sizeof(arr[0]));
}
練習4:定義一個函式,功能是對一個陣列進行升序排序

5、當函式中使用陣列作為引數傳遞是,是"址傳遞" ,是可以被函式所共享陣列

6、當使用二維陣列作為函式的引數時,C語言規則定義二維陣列時必須有列數,所以要行、列數在前,陣列在後,並且把列數設定給陣列。

void show_arr(int row, int col, int arr[][col]) {
    for (int i = 0; i < row; ++i) {
        for (int j = 0; j < col; ++j) {
            printf("%d%c", arr[i][j], " \n"[j == col - 1]);
        }
    }
}

int main() {
    int arr[2][5] = { 
        {1,2,3,4,5},
        {2,3,4,5,6}
    };  
    show_arr(2,5,arr);
}

專案;五子棋遊戲

資料分析:

​ 1、定義15*15的二維陣列,作為棋盤 char ' * ' ' @ ' ' $ '

​ 2、定義記錄落子位置的變數

​ 3、定義一個棋子角色變數 char role = '@'

業務邏輯分析:
1、定義15*15的二維陣列,作為棋盤 char ' * '  ' @ ' ' $  '
2、定義記錄落子位置的變數
3、定義一個棋子角色變數  char role = '@'
 
//	初始化棋盤
void init_board(void);
//	顯示棋盤
void show_board(void);
//	落子	如果位置非法,需繼續重新落子,直到成功後才能返回
void get_key(void);
//	判斷五子連珠
bool is_win(void);

int main() {
    for(;;) {
        1、清理螢幕、顯示棋盤
        2、落子
        3、判斷是否五子連珠
           是:提示+結束程式
        4、交換角色
    }
}
要求:每個功能實現函式來呼叫

如何透過方向鍵控制游標:

    
#include <stdio.h>
#include <getch.h>
#include <stdbool.h>
int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, 1, -1};
char g[25][25];
void print() {
	system("clear");
	for (int i = 0; i < 15; ++i) {
		for (int j = 0; j < 15; ++j) {
			printf("%c ", g[i][j]);
		}
		puts("");
	}
}
bool win(int x, int y, char c) {
	int c1 = 0, c2 = 0, c3 = 0, c4 = 0;
	for (int i = -2; i <= 2; ++i) if (x + i >= 0 && x + i < 15) {
		c1 += g[x + i][y] == c;
	}
	for (int i = -2; i <= 2; ++i) if (y + i >= 0 && y + i < 15) {
		c2 += g[x][y + i] == c;
	}
	for (int i = -2; i <= 2; ++i) if (y + i >= 0 && y + i < 15 && x + i >= 0 && x + i < 15) {
		c3 += g[x + i][y + i] == c;
	}
	for (int i = -2; i <= 2; ++i) if (x + i >= 0 && x + i < 15 && y - i <= 0 && y - i < 15) {
		c4 += g[x + i][y - i] == c;
	}
	return c1 == 5 || c2 == 5 || c3 == 5 || c4 == 5;
}
bool check() {
	for (int i = 0; i < 15; ++i) {
		for (int j = 0; j < 15; ++j) if (g[i][j] != '*') {
			if (win(i, j, g[i][j])) {
				return false;
			}
		}
	}
	return true;
}
int main() {
	memset(g, '*', sizeof g);
	print();
	int idx = 0, px = 1, py = 1;
	while (check()) {
		printf("\33[%d;%dH", px + 1, py * 2 + 2);
		while (true) {
			int get = getch() - 183;
			if (get + 183 == 10) {
				if (g[px][py] != '*') {
					print();
					printf("該位置上已經落子,請重下\n");
					continue;
				}
				g[px][py] = '0' + idx % 2;
				print();
				break;
			} else {
				px += dx[get], py += dy[get];
				if (px < 0) px = 0;
				if (py < 0) py = 0;
				if (px > 15) px = 14;
				if (py > 15) py = 14;
			}
			printf("\33[%d;%dH", px + 1, py * 2 + 2);
		}
		idx += 1;
	}
	if (idx % 2 == 1) {
		printf("黑子贏啦,共用%d步\n", idx / 2 + 1);
	} else {
		printf("白子贏啦,共用%d步\n", idx / 2);
	}
}

相關文章