scanf函數、冒泡排序和不定長度數組的使用 —— malloc


環境:win10 + VS2019

1 scanf函數理解

可以簡單的認爲系統I/O調用是unbuffered I/O,而C語言標準庫I/O函數(即stdio函數)是buffered I/O

標準I/O提供了三種類型的stdio緩衝:

全緩衝(fully buffered):在這種緩衝模式下,只有在填滿stdio緩衝區後纔會進行實際的I/O操作(即調用read()或者write()系統調用),也就是說單次讀、寫數據的大小與stdio緩衝區大小相同。通常打開的文件流是全緩衝的(文件位於磁盤上,而磁盤是塊設備)。

行緩衝(line buffered):在這種緩衝模式下,當在輸入和輸出流遇到換行符時,標準I/O庫執行I/O操作(即調用read()或者write()系統調用)。通常情況下,stdin和stdout都是涉及的鍵盤顯示器這些字符設備,所以是行緩衝的。

無緩衝(unbuffered):這種緩衝模式很好理解了,就是不存在stdio緩衝區,每次I/O操作就直接調用read()或者write()系統調用。stderr通常是不帶緩衝的,這就使得出錯信息可以儘快顯示出來,而不管它們是否含有一個換行符。

1.1 VS中scanf函數報錯

使用scanf函數會報錯

error C4996: ‘scanf’: This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details

報錯原因:

這種微軟的警告,主要因爲那些C庫的函數,很多函數內部是不進行參數檢測的(包括越界類的),微軟擔心使用這些會造成內存異常,所以就改寫了同樣功能的函數,改寫了的函數進行了參數的檢測,使用這些新的函數會更安全和便捷。關於這些改寫的函數你不用專門去記憶,因爲編譯器對於每個函數在給出警告時,都會告訴你相應的安全函數,查看警告信息就可以獲知,在使用時也再查看一下MSDN詳細瞭解

解決方法之一:在引用頭文件前使用以下宏定義

#define _CRT_SECURE_NO_WARNINGS

1.2 scanf函數介紹

1.2.1 簡介

與printf函數一樣,都被聲明在頭文件stdio.h裏

  • 函數聲明:int scanf( format string , arg1 , arg2 , …);
  • 函數返回:成功格式化解析的個數
  • 調用格式:scanf("<格式化字符串>", <參量表>);

由指示讀取動作的格式化字符串( format string )和相應的地址參數 arg1…argn 組成,scanf 函數從標準輸入緩衝區 stdin 中讀入,並將它們以格式化字符串中指定的格式存儲到額外的參數 arg1…arg2 等指定的內存空間中

1.2.2 轉換說明

轉換說明符 意義
%c 把輸入解釋稱一個字符
%d 把輸入解釋稱一個有符號十進制整數
%e,%f,%g,%a 把輸入解釋稱一個浮點數(%a是c99標準)
%E,%F,%G,%A 把輸入解釋稱一個浮點數(%A是c99標準)
%i 把輸入解釋稱一個有符號十進制整數
%o 把輸入解釋稱一個有符號八進制整數
%p 把輸入解釋稱一個指針(一個地址)
%s 把輸入解釋稱一個字符串,輸入內容以第一個非空白字符作爲開始,並且包含直到下一個空白字符的全部字符
%u 把輸入解釋稱一個無符號十進制整數
%x,%X 把輸入解釋稱一個無符號十六進制整數
[] 字符集合

1.2.3 讀取緩衝區數據

以 % 開頭的用於指定輸入數據格式的字符爲例。如 %d 指定需要讀取一個整形,%s 需要讀取一個字符串

scanf 等函數首先根據格式說明符嘗試去解析 stdin 中的數據,如對於 %d ,scanf 會嘗試對 stdin 中已有數據以整型的格式進行解析。若解析成功,則將上述解析結果存放到指定的內存中,若解析失敗,如 stdin 中僅存在一個字符 ‘a’,scanf 會退出並返回

但是上述不匹配的數據並不會從緩衝區中清除,後續的 scanf 調用仍從上述輸入開始讀取

scanf("%s,%d",&a,&b);
//scanf需先讀取一個字符串,再讀取一個 ',' ,最後讀取一個整數
scanf("%d\t%d",&a,&b);
//scanf需先讀取一個整數,再將格式化字符串中的 '\t' (空白字符)與緩衝區中0個或多個空白字符匹配並清除,最後讀取一個整數
scanf("%d%d",&a,&b);  //scanf需要先讀取一個整數,之後再讀取一個整數,兩個整數之間的空白字符會被忽略


2 動態數組實現

C語言是不能直接定義動態數組的,數組必須在初始化時確定長度

如果要在程序運行時才確定數組的長度,就需要在運行的時候,自己去向系統申請一塊內存用動態內存分配實現動態數組(申請內存在堆區)

2.1 堆和棧

① 棧一般是存放什麼數據的呢?

一般來講,棧主要是爲局部變量(一般是定義在函數裏面)、函數參數分配內存大小,但是當他們離開這個"本職崗位"範圍之後,就會被操作系統強行給咔嚓掉,最終被釋放了出來,歸還了給操作系統

這就好比,你去飯店吃飯,你吃飯的時候非常舒服(使用內存),但是當你發現你沒錢支付飯錢的時候,搞不好你會被別人強行毒打一頓,然後又給"歸還了"出去

② 棧的特點:

  • 運行時自動分配和自動回收性:棧是自動管理的,程序員不需要手工干預。方便簡單
  • 反覆使用性:棧內存在程序中其實就是那一塊空間,程序反覆使用這一塊空間
  • 遺留性:棧內存由於反覆使用,每次使用後程序不會去清理,因此在使用棧時還是上次棧中遺留下的數值
  • 臨時性:函數不能返回棧變量的指針,因爲這個空間是臨時的
  • 溢出性:因爲操作系統事先給定了棧的大小,如果在函數中無窮盡的分配棧內存總能用完

③ 堆的作用:

對於堆來講,它是由我們程序員來自由分配內存大小的,不過你在給一個指針變量分配內存大小的時候,在主程序return 0 語句之前記得要給它釋放,否則會出現不好的影響 —— 內存泄漏

在c語言中,我們經常使用malloc來分配內存大小,而使用free函數釋放之前分配的內存大小

④ 關於堆的申請:

malloc返回的是一個void *類型的指針,實質上malloc返回的是堆管理器分配給我本次申請的那段內存空間的首地址(malloc返回的值其實是一個數字,這個數字表示一個內存地址)

void類型不表示沒有類型,而表示萬能類型。void的意思就是說這個數據的類型當前是不確定的,在需要的時候可以再去指定它的具體類型

void *類型是一個指針類型,這個指針本身佔4個字節,但是指針指向的類型是不確定的,換句話說這個指針在需要的時候可以被強制轉化成其他任何一種確定類型的指針,也就是說這個指針可以指向任何類型的元素

  • malloc的返回值:成功申請空間後返回這個內存空間的指針,申請失敗時返回NULL。所以malloc獲取的內存指針使用前一定要先檢驗是否爲NULL
  • free( p);會告訴堆管理器這段內存我用完了你可以回收了。堆管理器回收了這段內存後這段內存當前進程就不應該再使用了

2.2 動態內存分配函數

2.2.1 malloc()函數

void *malloc(unsigned int size)

分配size個字節的內存空間,返回地址的指針,如果內存不夠分,就返回空指針NULL

注意:返回的指針是沒有類型的,所以要使用得強制類型轉換

如果在子函數中申請堆區內存,在子函數調用結束後,編譯器不會理會堆區中的內容,也就是說,子函數調用結束,堆區申請的內存不會自動釋放,其生命週期爲整個程序運行期間,如不手動釋放,則會產生垃圾,內存泄漏等問題

2.2.2 calloc()函數

void *calloc(unsigned int num, unsigned int size)

這個也是申請動態內存空間,不過就是分開了而已。 一共申請num個長度爲size字節的內存空間

2.2.3 free()函數

void free(void *p)

釋放指針p內存空間。

這個很重要!!!!很重要!!!重要!!!

2.2.4 realloc()函數

void *realloc(void *p, unsigned int size)

給指針p申請的存儲空間改爲size個字節,返回的是存儲空間首地址(指針)


3 冒泡排序動圖演示

若對n個人進行排序,我們需要n-1次比較,所以第k次比較需要進行n-k次比較。排序算法通過以數據對象的兩兩比較作爲關鍵,所以可以得出,冒泡排序需要進行的

比較次數爲:(n-1) + (n-2) + … + 1 = n*(n-1) / 2,因此冒泡排序的時間複雜度爲O(n^2)。

算法思路:

  • 比較相鄰的元素,前一個比後一個大(或者前一個比後一個小)調換位置
  • 每一對相鄰的元素進行重複的工作,從開始對一直到結尾對,這步完成後,結尾爲做大或最小的數.
  • 針對除了最後一個元素重複進行上面的步驟。
  • 重複1-3步驟直到完成排序


4 固定長度數組和冒泡排序

  • 需要排序的整數個數是固定的
  • 通過輸入固定個數的數據,實現從大到小排序
#define _CRT_SECURE_NO_WARNINGS //scanf函數

#include <stdio.h>

/*
	函數名:max_mid_min
	返回值:空
	形參:整形指針(指向數組) + 數組長度
	功能:將數組的數據從大到小排序(通過指針實現在形參中修改實參)
*/
void max_mid_min(int *arr, int len) 
{
	int temp, i, j;
	for (i=0; i<len-1; ++i)  //比較len-1輪
	{
		for (j=0; j<len-1-i; ++j)  //每輪比較len-1-i次
 		{
			if (arr[j] < arr[j+1]) //從大到小排序
			{
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

int main()
{
	int arr_temp[3] = {0}; //定義一個固定長度數組
	while (1) 
	{
		printf("請輸入三個整數,用空格分隔:\n");
		scanf("%d %d %d", &arr_temp[0], &arr_temp[1], &arr_temp[2]);
		max_mid_min(arr_temp,3);
		printf("從小到大排序爲:%d,%d,%d\n\n", arr_temp[2], arr_temp[1], arr_temp[0]);
	}

	system("pause");
	return 0;
}


5 獲取不定長度數組數據

  • 定義一個足夠大的數組
  • 所使用到的有效數組長度由輸入的所有數據決定,但內存佔用不變
  • 一定不能超過該“足夠大”的數組長度
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

/*
	函數名:max_mid_min
	返回值:空
	形參:整形指針(指向數組) + 數組長度
	功能:將數組的數據從大到小排序(通過指針實現在形參中修改實參)
*/
void max_mid_min(int *arr, int len) 
{
	int temp, i, j;
	for (i=0; i<len-1; ++i)  //比較len-1輪
	{
		for (j=0; j<len-1-i; ++j)  //每輪比較len-1-i次
 		{
			if (arr[j] < arr[j+1]) //從大到小排序
			{
				temp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = temp;
			}
		}
	}
}

/*
	函數名:input_data
	返回值:整形
	形參:整形指針(指向數組)
	功能:獲取輸入的數據(不定長度的數組數據),以空格分開,存在形參中指針所指向的數組
*/
int input_data(int *arr)
{
	int i = 0;
	char ch;
	printf("請輸入若干個整數,用空格分隔:\n");
	do //先執行函數體,再判斷條件
	{
		scanf("%d", &arr[i++]);
	} while ((ch = getchar()) != 10);// 這裏用來判斷是否輸入了回車
}

int main()
{
	int arr_temp[100] = {0};
	int len;

	while (1) 
	{
		len = input_data(arr_temp); //獲取到輸入的數據
		max_mid_min(arr_temp, len); //將數組中的數據排序
		printf("從大到小排序爲:");
		for (int j = 0; j < len; j++)
		{ 
			printf("%d, ", arr_temp[j]);
		}
		printf("\n\n");
	}

	system("pause");
	return 0;
}


6 使用不定長度的動態數組

  • 先設定一個很小的動態數組初始長度值
  • 當輸入的數據大於初始長度值,再增加堆區內存的申請
//#define _CRT_SECURE_NO_WARNINGS

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

/*
	函數名:max_mid_min
	返回值:空
	形參:整形指針(指向數組) + 數組長度
	功能:將數組的數據從大到小排序(通過指針實現在形參中修改實參)
*/
void max_mid_min(int *arr, int len) {
	int temp;
	for (int i = 0; i < len - 1; ++i) ////比較n-1輪
	{
		for (int j = 0; j < len - 1 - i; ++j)  //每輪比較n-1-i次
		{
			if (arr[j] < arr[j + 1]) //從大到小排序
			{
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
	printf("從大到小排序爲:");
	for (int k = 0; k < len; k++)
	{
		printf("%d, ", arr[k]);
	}
	printf("\n\n");
}

/*
	函數名:input_and_sort
	返回值:空
	形參:空
	功能:接受輸入數據,存入不定長度的數組並排序
*/
void input_and_sort(void)
{
	int i = 0;
	char ch;
	int initial_len = 3;
	int* arr_data = (int*)malloc(initial_len * sizeof(int));

	printf("請輸入若干個整數,用空格分隔:\n");
	do
	{
		if (i >= initial_len)
		{
			arr_data = (int*)realloc(arr_data, (i + 1) * sizeof(int));
		}
		scanf("%d", &arr_data[i++]);
	} while ((ch = getchar()) != 10);// 這裏用來判斷是否輸入了回車
	
	max_mid_min(arr_data, i);

	free(arr_data);
	arr_data = NULL; 
}

int main()
{
	while (1) {
		input_and_sort();
	}

	system("pause");
	return 0;
}


7 結束

如有理解錯誤,還望指正🤞



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章