指針那些事之進階篇

再看此文章之前,如果有任何關於指針概念不清楚的,可以戳這裏喲:點擊打開鏈接

這篇博客是我自己總結了我所知道的所有和指針扯得上關係的知識點,便於我們所有人回顧和了解

數組與指針

一、一維數組,一級指針;二維數組,二級指針

首先需明白一下幾點:

1)指針和數組在使用上有類似性(比如a[5]可以訪問數組中第六個元素,p+5同樣也可以訪問)

2)指針和數組是沒有關係的,這是兩種不同的類型(比如float, int是沒有辦法比較的,這是兩種不同的類型)

3)指針可以做左值也可以做右值(可表示空間,可表示內容),數組只能做右值(可以將數組賦值給別的變量,但不能將變量直接賦值給數組)

4)指針做右值表示它的內容,數組做右值表示的是數組首元素的地址

5)只有sizeof(數組名),&數組名,這兩種情況數組名代表整個數組,其他情況都代表首元素的地址

這裏有關數組和指針的練習題,涉及到上面所有概念,有興趣可以戳一下:點擊打開鏈接

二、指針數組、數組指針

指針數組是數組,是一個存放指針的數組,int *a[10]; --> a是一個數組,是一個存放指針的數組,是一個存放的指向整型的指針的數組

數組指針是指針,是一個指向數組的指針,int (*p)[10]; --> p是一個指針,是一個指向數組的指針,是一個指向含10個整型元素的數組的指針

從上可以看出,[ ]的優先級大於*,究竟是指針還是數組由優先級決定。

還有,中括號中的數也屬於類型的一部分(就像int a[3]和int a[4]是不同的類型)

我們看一下下面的代碼:

int arr[10];
int *p1 = &arr;
int (*p2) [10] = &arr;

實際上p1的表示是不正確的,應該使用p2(p2是數組指針,用來存放數組的地址很合適)

讓我們來分辨下面這幾個麻煩的概念:

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];
int *(*a[3])[10];

arr --> 是一個存放了5個整型元素的數組

parr1 --> 是一個存放了10個指向整型變量的指針的數組

parr2 --> 是一個指向存放了10個整型元素的數組的指針

parr3 --> 是一個存放了5個數組的數組,這5個數組每一個都是存放10個指向整型的指針的數組

a --> 是一個存放10個指針元素的數組,這10個指針都指向數組,指向的數組都是存放了3個指針的數組,這3個指針都指向的是整型元素 --> 是一個存放10個指向存放3個指向整型元素的指針的數組的指針的數組

三、指針、數組的定義與聲明

定義就是空間(+初始化),定義變量的時候一定會開闢空間

聲明只是說明有這個東西,聲明是不需要開闢空間的

我們需要明白,聲明成什麼類型就要定義什麼類型,否則會出現意想不到的後果。例如:

char arr[] = "abcdef";

//main.c
extern char *arr;
int main()
{
     printf("%s\n", arr);
     return 0;
}
在main函數中,將arr看做是指針。那麼在去使用的時候,就會將四字節的abcd(對應的ASCII碼,計算機存放的所有數據都是二進制序列)取出來當做地址去訪問,而abcd對應的地址不一定是允許被訪問的,程序可能會崩掉

四、數組指針參數

數組在傳參的過程中會發生降級現象(一維數組會將爲一級指針)

只要是傳參就一定會產生臨時變量

1)一維數組傳參

void test(int arr[])
{
}
void test(int arr[10])
{
}
void test(int *arr)
{
}

void test2(int *arr[20])
{
}
void test2(int **arr)
{
}

int main()
{
     int arr[10];
     int *arr2[20];
     test(arr);    
     test2(arr2);
}

上面的傳參的方式都是正確的哈^-^

2)二維數組傳參

函數形參的設計只能省略第一個[ ]的數字,因爲計算機可以不知道有多少行,但必須要知道一行有多少個元素

void test(int arr[3][5])
{
}
void test(int arr[][5])
{
}
void test(int arr[][])//Error,除了第一個[],其他都不能省略
{
}

void test(int *arr)//按理說是不行的,但非如此勉強也可以,大不了就將數組看成線性一維
{
}
void test(int *arr[5])//Error,指向整型的指針的數組
{
}
void test(int **arr)//Error,指向整型的指針的指針,和上面那個Error錯的一樣
{
}
void test(int (*arr)[5])//正確,指針存放5個整型元素數組的指針
{
}

int main()
{
     int arr[3][5];//存放數組的數組
     test(arr);
}

3)一級指針傳參

void test1(int *p) --> 此時p可以接收:整型數組、整型元素的地址

void test2(char *p) --> 此時p可以接收:字符數組、字符的地址、字符串

4)二級指針傳參

void test(char **p) --> 此時p可以接收:二級指針、指針數組、一級指針的地址

函數與指針

一、函數指針

函數指針-->函數的地址-->函數的入口地址-->函數的第一條語句的地址-->函數名可以代表函數的地址

//例如:

void test()

{

}

//test和&test,兩種方式都可以求函數的地址,將函數的地址保存起來的變量就是函數指針

區分以下兩個表示的不同:(可以證明()的優先級大於*)

void (*pfun1)(); --> 指向函數的指針

void *pfun2(); --> 函數的聲明

c語言在c庫中爲我們提供了一個qsort函數,就是用了函數指針的概念

只要你給出判斷大小的方法就可以排序任意類型的數組

int char_cmp(void *p, void *q)
{
	if (*(char *)p > *(char *)q)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

int main()
{
	char arr[] = "beautiful girl";
	int size = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, size, sizeof(char), char_cmp);
	system("plause");
	return 0;
}
我會在文章的最後,利用冒泡排序模擬qsort的排序功能

二、函數指針數組、函數指針數組的指針

1)函數指針數組

存放指向函數的指針的數組 --> int (*parr[10])();

可以方便我們對函數的使用

在這裏幾個例子:

//利用轉移表實現計算器功能

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 divi(int x, int y)
{
	return x / y;
}

int main()
{
	int(*parr[5])(int x, int y) = { 0, add, sub, mul, divi};//轉移表
	int a = 0;
	int b = 0;
	int value = 0;
	int input = 1;
	while (input)
	{
		printf("########################################\n");
		printf("#########1. Add          2. Sub#########\n");
		printf("#########3. Mul          4. Div#########\n");
		printf("###############  0. Exit  ##############\n");
		printf("########################################\n");
		scanf("%d", &input);
		if (input > 0 && input < 5)
		{
			printf("Please enter two numbers:");
			scanf("%d %d", &a, &b);
			value = (*parr[input])(a, b);
			printf("%d is the result\n", value);
		}
		else if (input != 0)
		{
			printf("Error,please try again!\n");
		}
	}
	system("pause");
	return 0;
}

2)函數指針數組的指針

指向函數指針數組的指針

void (*(*ppfunArr)[10])(const char *) --> ppfunArr指向一個數組的指針,數組裏存放了10個元素,這10個元素是指針,這些指針都是指向函數的指針,這些函數都是返回值爲void參數爲const char *的函數

3)回調函數

回調函數就是通過函數指針調用函數

回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應

模擬qsort函數的實現過程,完美的呈現了回調函數的作用

void swap(char *p, char *q, int size)
{
	assert(p);
	assert(q);
	while (size--)
	{
		*p = *p ^ *q;
		*q = *p ^ *q;
		*p = *p ^ *q;
		p++, q++;
	}
}

void bobble_qsort(void *base, int count, int size, int (*cmp)(void *, void *))
{
	assert(base);
	assert(cmp);
	int i = 0;
	int j = 0;
	int flag = 1;
	for (i = 0; i < count - 1; i++)
	{
		for (j = 1; j < count - i; j++)
		{
			if (cmp((char *)base + (j - 1)*size, (char *)base + (j)*size) > 0)
			{
				flag = 0;
				swap((char *)base + (j - 1)*size, (char *)base + (j)*size, size);
			}
		}
		if (flag)
		{
			break;
		}
	}
}

int int_cmp(void *p, void *q)
{
	return (*(int *)p - *(int *)q);
}

int main()
{
	int arr[] = { 23, 54, 346, 35, 745, 2, 56, 67, 454, 722, 5562, 25, 6 };
	int size = sizeof(arr) / sizeof(arr[0]);
	bobble_qsort(arr, size, sizeof(int), int_cmp);
	system("plause");
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章