日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C語言指針和數組深入探究使用方法_C 語言

作者:程序猿教你打籃球 ? 更新時間: 2022-10-04 編程語言

1、數組參數和指針參數

1.1 一維數組傳參

這里在前幾期我們已經初略的見識過了,但是這里我們要提一個概念,數組給函數傳參是會發生降維的,降維成什么呢?我們看代碼:

這里通過打印形參的大小,發現是 4,其實也不奇怪,目前我們是 32 位操作環境,所以一個指針也就是 4 個字節,所以從這里我們可以看出,數組傳參的時候,是發生降維的,數組名除了 &數組名 和 sizeof(數組名) 其他所有情況都是首元素地址,所以本質上我們是降維成指向其數組內部元素類型的指針,為什么呢,因為他是數組首元素的地址,首元素是int 類型,所以傳過去的也是對應的 int 類型的指針,同理我們需要拿同類型指針變量來接收,所以本質上我們 p 變量中保存的就是 arr[0] 的地址!

我們在看一段代碼:

void printSize(int arr[100], int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    printSize(arr, 10);
    return 0;
}

如上這段代碼有問題嗎?其實是沒有問題的,實際傳遞數組大小與函數形參指定的數組大小沒有關系,因為他已經是指針了,只是訪問方式被打通了,第二期我們有講過,那么既然如此,我們也可以不要里面的元素個數直接成 printSize(int arr[], int n) 這樣也是可以的,至少不會讓閱讀者感到誤會。

1.2 一級指針傳參

void print(int* p, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", *(p + i));
	}
    printf("\n");
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//一級指針p,傳給函數
	print(p, sz);
	return 0;
}

這里我們需要討論一個問題,指針作為參數需要發生拷貝嗎?

答案是需要的,因為指針變量也是變量,在傳參上得符合變量的要求,也就是在棧上開辟空間,同時我們也知道,main 函數中的 p 是一個局部變量,它只在 main 函數內有效,所以只能對實參做一份拷貝,并傳遞給被調用的函數。

1.3 二維數組參數和二級指針參數

這個例子我們發現,二維數組傳參的時候也會發生降維,如何理解呢?上一期我們用了數組指針來接收了二級指針傳參,這里我們就來做一個總結:

任何維度的數組,傳參的時候都要發生降維,降維成指向其內部元素類型的指針,那么,二維數組內部元素我們可以看成是多個一維數組,所以,二維數組傳參其實是降維成指向一維數組的指針,而這里的 arr 也就代表著首元素地址,也就是第一行一維數組的地址!這也就是我們之前可以拿指針數組來接收的原因了。

這里我們還是可以省略第一個下標的值:char arr[][4] ,但是為什么不能省略第二個下標值呢?我們可以想一下,之前寫用數組指針接收是這樣寫的 char (*p)[4] ,上面我們提到過,int arr[] 用來接收實參,它本質上就是個指針,所以 char arr[][4] 本質上是個數組指針,從他的角度看,他指向了一個存放 4 個 char 類型元素的數組,所以如果省略了第二個下標則指針類型不明確!

1.4 野指針的問題

這個問題其實很多書中都會有寫,我們這里就簡單提一下:

  • 指針未初始化,默認是隨機值,如果直接訪問會非法訪問內存
  • 指針越界訪問,當指針指向不屬于我們的內存,p就是野指針
  • 指針指向的空間被釋放,如果動態開辟的內存被釋放但是指針沒置NULL,就會形成野指針,他仍然記錄者已經不屬于他的內存
  • 返回局部變量的地址,如果我們一個函數被銷毀后但是仍然返回函數內局部變量的地址也會造成也會造成野指針

2、函數指針

指針變量是用來保存地址的,那么函數有地址嗎?有!函數是由我們自己寫的一些語句構成的,程序運行的時候就會把定義好的函數中的語句調用到內存中去,那么函數代碼在內存中開始的那個內存空間的地址也就是函數的地址!

這里我們也能發現,函數是有地址的,而且 &函數名 和 單獨的函數名 都能表示函數的地址。

那么我們如果想把函數的地址存起來該如何做呢?有了上面學習指針數組和數組指針的經驗,其實函數指針也很好理解:

void (*pfun) () 其實這么寫可以了,我們來解讀下這句代碼:pfun 先和 * 結合,正如我們之前所說,就能說明他是一個指針,指向的是一個無參數并且無返回類型的函數。

那我們如果要指向一個 int add (int x, int y) 這樣的一個函數,我們應該如何定義函數指針呢?

int (*p) (int, int) 如同上面一樣,首先要保證 p 是指針,所以帶上括號,指向的是一個返回值為 int 參數為 int int 的函數。

接下來我們來使用函數指針,使用方法跟函數一樣,直接把指針變量名當函數名使用即可:

讓我們來看一道有意思的題:

int main()
{
	(*(void (*)())0)();
	return 0;
}

首先這道題的解法肯定先從 0 下手,我們先分析,0 前面的 (void (*) ()) 是什么?這很明顯是一個函數指針類型,所以可以理解成把 0 強轉成函數指針,也就是把 0 當成了一個函數的地址,然后再 * 引用這個地址,也就是找到 0 地址處的函數進行調用。所以此代碼就是一次函數調用,被調函數無參,返回類型是void。

3、函數指針數組

有了上面的學習就很好理解了,無非就是保存函數地址的數組,那么它的語法格式是什么呢?

int (*arr[10]) (int, int)

這里我們可以分析到:首先 arr 跟 [ ] 先結合,所以它是個數組,這個數組的每個元素是 int (*) (int int) 類型的函數指針,它的作用主要是轉移表,那我們這里就簡單用一下即可

假設我們需要兩個整數的 + - * / 我們寫完了四個函數是不是可以放到一個數組里,然后通過訪問數組下標就能調用我們想用的函數了:

int add(int x, int y)
{
	return x + y;
}
int sub(int x, int y)
{
	return x - y;
}
int mul(int x, int y)
{
	return x * y;
}
int div(int x, int y)
{
	return x / y;
}
int main()
{
	int (*arr[4]) (int, int) = { add, sub, mul, div };
	printf("加法:%d\n", arr[0](1, 2));
	printf("減法:%d\n", arr[1](5, 2));
	printf("乘法:%d\n", arr[2](3, 3));
	printf("除法:%d\n", arr[3](6, 2));
	return 0;
}

4、指向函數數組的指針

看到這可能有的小伙伴覺得越來越套娃了,但其實這個也很好理解,無非就是一個指針指向了一個數組,數組每個元素是函數指針,這里我們簡單了解下概念即可,用的其實也不是很多,當別人如果寫了這種代碼我們能看懂就行:

函數指針如何定義:

int test(char* str)
{
	if (str == NULL) {
		return 0;
	}
	else
	{
		printf("%s\n", str);
		return 1;
	}
}
int main()
{
	//函數指針pfun
	int (*pfun)(char*) = test;
	//函數指針的數組pfunArr
	int (*pfunArr[5])(char* str);
	pfunArr[0] = test;
	//指向函數指針數組pfunArr的指針ppfunArr
	int (*(*ppfunArr)[5])(char*) = &pfunArr;
	return 0;
}

我們來分析一下這個:int(*(*ppfunArr)[5])(char*),首先看到 (*ppfunArr) 這括號括起來先跟 * 結合證明它是一個指針,指向的類型是什么呢?把它去掉剩下的就是它的類型,int(*[5])(char*),通過這個可以發現,是一個帶有5個元素的數組,每個元素的類型是一個函數指針,而函數的返回值為int,參數為 char*

這里我們能看懂即可。

5、回調函數

回調函數指的就是一個通過函數指針調用的函數,如果你把函數的指針(地址),作為參數傳遞給另一個函數的話,當這個指針被用來調用其指向的函數,這里就被稱為回調函數。其實 qsort 函數就是很典型使用了回調函數的例子,感興趣的可以自行下來了解一下,這里我們就簡單的演示下如何使用,用回調函數實現三個數比較大小:

int max(int x, int y, int z, int(*pfun)(int, int))
{
	if (x > pfun(y, z)) {
		return x;
	}
	else
	{
		return pfun(y, z);
	}
}
int tmp(int x, int y) 
{
	return x > y ? x : y;
}
int main()
{
	int ret = max(10, 20, 30, tmp);
	printf("%d\n", ret);
	return 0;
}

比較三個數的最大值是有更優的解決方案的,我們這里只是演示一下回調函數的簡單使用,跟上面一樣,會用即可,其實不用研究的特別深入

6、一道筆試題

int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
	printf("%s\n", **++cpp);
	printf("%s\n", *-- * ++cpp + 3);
	printf("%s\n", *cpp[-2] + 3);
	printf("%s\n", cpp[-1][-1] + 1);
	return 0;
}

這道題我就不講解了,學習一定得有自己研究的一個過程,包括后續 Java 的文章,每一期基本上都會留一個小疑問讓大家自己下去解答,其實這道題很簡單,耐心畫畫圖就能理解了,如果你能自己解決這道題,說明你的指針的數組這兩章的內容已經通關了,實在是難以解決的話,可以問一下博主。

后續其實還有動態內存管理,但是這個知識點無非就是掌握對 malloc calloc realloc free 的使用,如果你是以后 C++ 方向可學習一下,如果你是 Java 方向其實有個基本認識就行,畢竟 Java接觸底層不多,有了前面學習的鋪墊,去網上看看內存管理的文章是很輕松學會的,學習最主要是培養學習的能力,

最后來個大總結:從剛開始我們一共講解了32個關鍵字,在關鍵字中也穿插了很多內容,比如大小端,結構體,往后就是符號的理解了,包括我們平常用的注釋,以及各種運算符但是除法和取模我們沒有放進去,這個在JavaSE系列中會介紹,再往后就是對預處理的深入理解了,最終我們以數組和指針結尾,C語言系列就到此結束了。

原文鏈接:https://blog.csdn.net/m0_61784621/article/details/126129515

欄目分類
最近更新