青浦手機網(wǎng)站建設(shè)網(wǎng)站推廣排名公司
1、字符指針
(1)字符指針的普通用法
char a = 'A';
char* pa = &a;
但是一般來說字符指針很少這么用……更多是拿來存儲一個字符串
(2)字符串的兩種存儲以及區(qū)別
- 現(xiàn)在有了兩種存儲數(shù)組的方法
- ①一個是使用char類型數(shù)組存儲
- ②另外一個是使用指向char類型的指針存儲數(shù)組的首元素地址,將其當(dāng)成字符串的“標(biāo)記”
#include <stdio.h>
int main()
{char str1[] = "hello word.";char str2[] = "hello word.";const char *str3 = "hello word.";//將字符串的首字母h的地址存儲在str3里面const char *str4 = "hello word.";if(str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if(str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}
- 兩個數(shù)組存儲不同的字符串,盡管他們的內(nèi)容是相同的
- 兩個字符指針都指向內(nèi)容相同的字符串
這意味著str3和str4指向的是同一個常量字符串,C/C++會把常量字符串存儲到一個單獨的內(nèi)存區(qū)域,當(dāng)幾個指針指向同一串字符串時,他們實際是指向同一塊內(nèi)存。但是用相同的常量字符串去初始化不同數(shù)組時,數(shù)組會對常量字符串進行拷貝,開辟出不同的內(nèi)存塊,所以str1!=str2,但是str3==str4
(3)存儲多個字符串----字符指針數(shù)組
int main()
{char* arr[3] = { "abcd", "cdf", "jiuh" };int i = 0;for(i = 0; i < 3; i++){printf("%s\n", arr[i]);}return 0;
}
2、指針數(shù)組
比較簡單,直接寫下code就可以
char* arr[4];//存儲了4個char*指針
char** arr[10];//存儲了10個char**指針
3、數(shù)組指針
(1)數(shù)組的基礎(chǔ)概念
- 數(shù)組名字有兩種情況如下code
//復(fù)習(xí)數(shù)組的名字含義
#include <stdio.h>
int main()
{int arr[10] = { 0 };printf("%p\n", arr);//數(shù)組名字就是首元素地址printf("%p\n", &arr[0]);//取出了首元素地址printf("%p\n, &arr);//1、取出來整個數(shù)組的地址,盡管&arr和&arr[0]的值一樣,但是意義不一樣(類型不一樣),前者是數(shù)組類型int[10],后者是整型類型intprintf("%zd\n", sizeof(arr));//2、這里的arr依舊是整個數(shù)組return 0;
}
①sizeof(數(shù)組名),這里的數(shù)組名表示整個數(shù)組,計算的是整個數(shù)組的大小
②&數(shù)組名,這里取出來的是整個數(shù)組地址,盡管它的值和數(shù)組的首元素地址相同,兩者是有區(qū)別的
最大的區(qū)別就在于int arr[10]聲明后,arr是首元素地址,指針類型是int*,這是個數(shù)組指針。而能存放&arr這個地址的指針類型是int(*)[10]。這點在對兩種指針進行+/-整數(shù)的時候會更加明顯,因為指針會根據(jù)指向的類型對地址值進行增加 (數(shù)組指針后面會講)
(2)數(shù)組指針的定義
數(shù)組指針也是指針
int (*p)[10];//p是一個指向一維整型數(shù)組的指針,該數(shù)組內(nèi)含10個int類型
注意[]和()具有相同的優(yōu)先級,結(jié)合性是從左向右結(jié)合。并且優(yōu)先級都比*高。
(3)數(shù)組指針的使用
//第一種使用方法
int main()
{int arr[10] = {1, 2, 3, 4, 5};int (*p)[10] = &arr;return 0;
}語法邏輯沒毛病,可以,但是一般不會這么使用
//第二種使用方法
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{int i = 0;for(i=0; i<row; i++){for(j=0; j<col; j++){printf("%d ", arr[i][j]);}printf("\n");}
}void print_arr2(int (*arr)[5], int row, int col)
{int i = 0;for(i=0; i<row; i++){for(j=0; j<col; j++){printf("%d ", arr[i][j]);}printf("\n");}
}int main()
{int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};//數(shù)組名arr,表示首元素的地址//但是二維數(shù)組的首元素是二維數(shù)組的第一行,指向這一行的指針類型可以寫成int [][5]或者int (*)[5]//所以這里傳遞的arr,其實相當(dāng)于第一行的地址,是一維數(shù)組的地址print_arr1(arr, 3, 5);print_arr2(arr, 3, 5);return 0;
}
(4)更多數(shù)組指針的解讀
int arr[5];//單純是個整型數(shù)組int* parr1[10];//單純是一個指針數(shù)組,每一個指針都指向一個intint (*parr2)[10];//數(shù)組指針,該指針指向一個包含10個int元素的數(shù)組int (*parr3[10])[5];//指針數(shù)組,可以思考成“int(*)[5] parr3[10]”(與int arr[5]是類似的)很明顯,這是一個包含10個元素的數(shù)組,每個元素都int(*)[5]這種類型的指針,這種指針指向一包含5個元素的數(shù)組
4、數(shù)組傳參和指針傳參
(1)一維數(shù)組傳參
#include <stdio.h>
void test1(int arr[])//可以這么寫
{//code
}
void test1(int arr[10])//10寫與不寫都行,無所謂,C會將它忽略
{//code
}
void test1(int *arr)//可以的,傳過來的arr1的拷貝是一個int元素地址
{//code
}void test2(int *arr[20])//20寫與不寫都行,C依舊會將它忽略
{//code
}
void test2(int **arr)//也可以,傳過來的arr2的拷貝是一個int*元素的地址
{//code
}int main()
{int arr[10] = {0};int *arr2[20] = {0};test(arr1);test2(arr2);
}
(2)二維數(shù)組傳參
void test(int arr[3][5])//可以使用,不過3會被忽略
{//code
}
void test(int arr[][])//不可以使用,5必須留下來
{//code
}
void test(int arr[][5])//可以使用
{//code
}
//二維數(shù)組傳參,函數(shù)形參的設(shè)計只能省略第一個[]的數(shù)字。
//對一個二維數(shù)組,可以不知道有多少行,但是必須知道一行多少元素。void test(int *arr)//不行,類型不匹配,拷貝過來的參數(shù)是一個指向一維數(shù)組的指針,而這個參數(shù)僅僅是一個一維指針
{//code
}
void test(int* arr[5])//不行,這是一個指針數(shù)組,根本沒有關(guān)系
{//code
}
void test(int (*arr)[5])//可以這么寫,指針類型匹配了
{//code
}
void test(int **arr)//不行,指針類型不匹配
{//code
}int main()
{int arr[3][5] = {0};test(arr);
}
//使用二維數(shù)組指針(深刻理解)
void print(int(*p)[20], int x, int y)
{//二維數(shù)組的名字就是首元素的地址,其地址就是第一行數(shù)組的地址,也就是指向一維數(shù)組類型的指針,因此不能寫形參為int**//又因為一維數(shù)組的整體地址&arr和其首元素地址&arr[0]起始位置相同,故從值來看是一樣的,但是兩者的指針類型完全不同for (int i = 0; i < x; i++){for (int j = 0; j < y; j++){printf("%d", *(* (p + i) + j));//得到數(shù)組名,利用i得到每一行的數(shù)組名,利用j得到某一行的每一列的元素地址}}
}
int main()
{int arr[10][20] = { {1, 2, 3}, {2, 3, 4} };print(arr, 10, 20);return 0;
}
(3)一級指針傳參
#include <stdio.h>
void print(int *p, int sz)
{int i = 0;for(i=0; i<sz; i++){printf("%d\n", *(p+i));}
}
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9};int *p = arr;int sz = sizeof(arr)/sizeof(arr[0]);//一級指針p,傳給函數(shù)print(p, sz);return 0;
}
(4)二級指針傳參
#include <stdio.h>
void test(int** ptr)
{printf("num = %d\n", **ptr);
}
int main()
{int n = 10;int*p = &n;int **pp = &p;test(pp);test(&p);return 0;
}
(5)總結(jié)
指針的類型一定要匹配好,盡管類型不匹配的時候依舊可以進行傳參(因為所有地址在同一個平臺都是一樣大小的,只是存儲一個地址,理論上用什么類型的指針都可以存儲),但是在后續(xù)使用指針的時候(解引用指針)就會發(fā)生錯誤,指針會根據(jù)指針類型訪問不同大小的內(nèi)存
5. 函數(shù)指針
- 整型指針int*
- 字符指針char*
- 指向數(shù)組int arr[10]的數(shù)組指針int (*p)[10]
- 指向函數(shù)function()的函數(shù)指針int (*pf)(int, int) = function(或者int (*pf)(int, int) = &function)其中函數(shù)function()是一個有兩個int參數(shù),返回值為int的函數(shù)
(1)函數(shù)指針
-
對于函數(shù)function(),其函數(shù)指針類型為【返回值 (*指針名) (參數(shù)類型的列表)】
-
若想使用這個函數(shù)就要進行解引用,使用【(* pf)(參數(shù)列表)】或者【pf(參數(shù)列表)】都可以。編譯器在處理的時候,沒有 * 也行,但是要用 * 就一定要加括號
- 盡管這樣很矛盾,但是調(diào)用函數(shù)指針時,使用*pf和pf是一樣的,即:函數(shù)名==函數(shù)的地址,而(&函數(shù)名)==函數(shù)的地址
- 若是理解pf為指針,則*pf變成函數(shù)名字,*pf()就相當(dāng)于function()
- 若是理解pf為函數(shù)名,則pf本身就是函數(shù)的名字,pf()就相當(dāng)于function()
- C允許這兩種寫法,認(rèn)為兩種都合理
//例子
char* test(int c, float* pf)
{//某些代碼
}
int main()
{char* (*pt)(int, float*)pf = test;test(參數(shù)列表)return 0;
}
(2)有關(guān)函數(shù)指針的一些有趣的代碼
//代碼1
(*( void (*)() )0)();//從最里面開始理解void(*)()是一個指向“返回值為空,參數(shù)列表為空”函數(shù)的函數(shù)指針
//然后將0的int類型強制轉(zhuǎn)化為函數(shù)指針類型,于是0成了一個函數(shù)指針類型的地址
//再解引用0這個地址,得到0地址處的函數(shù),然后使用這個函數(shù)
//代碼2
void (* signal(int , void(*)(int)) )(int);//等價代碼如下
//typedef void(*pfun_t)(int);//注意pfun_t是一個和void(*)(int)同類型名,將pfun_t放在*旁邊是為了指明pfun_t是一個指針而已,這只是語法形式要求
//pfun_t signal(int, pfun_t);//因此有一個簡化代碼的技巧就是使用typedef
這兩段代碼來自于《C陷阱與缺陷》,是本出名的C語言書籍
6. 函數(shù)指針數(shù)組
(1)使用例子
#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a*b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf("*************************\n");printf("請選擇:");scanf("%d", &input);switch (input){case 1:printf("輸入操作數(shù):");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("輸入操作數(shù):");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("輸入操作數(shù):");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("輸入操作數(shù):");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");breark;default:printf("選擇錯誤\n");break;}} while (input);return 0
}
(2)改良后
#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a*b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //轉(zhuǎn)移表,即函數(shù)指針數(shù)組,這里的0(或者寫NULL)起到占位的作用,理論上放什么都行,只要后續(xù)處理好就行…while (input){printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf("*************************\n");printf("請選擇:");scanf("%d", &input);if ((input <= 4) && (input >= 1)){printf("輸入操作數(shù):");scanf("%d %d", &x, &y);ret = (*p[input])(x, y);}else{printf("輸入有誤\n");}printf("ret = %d\n", ret);}return 0;
}
//當(dāng)計算器后續(xù)需要加入更多的運算函數(shù)時,那么使用開關(guān)語句就會顯得冗長,但是使用函數(shù)指針數(shù)組就不會有這個問題,這將會大大縮減代碼。但是使用函數(shù)指針也具有有缺點,它只能存放同樣函數(shù)簽名(函數(shù)簽名定義了函數(shù)的輸入和輸出,即:函數(shù)簽名==參數(shù)+返回值)的函數(shù)
(3)轉(zhuǎn)移表的概念
像類似上面使用函數(shù)指針數(shù)組的方法就叫做轉(zhuǎn)移表,具有一種跳轉(zhuǎn)使用函數(shù)的效果。即“函數(shù)指針數(shù)組”==“轉(zhuǎn)移表”
7. 指向函數(shù)指針數(shù)組的指針
void test(const char* str)
{printf("%s\n", str);
}
int main()
{//聲明函數(shù)指針pfun,并且進行初始化void (*pfun)(const char*) = test;//聲明一個函數(shù)指針的數(shù)組pfunArrvoid (*pfunArr[5])(const char* str) = { pfun };pfunArr[0] = test;//指向函數(shù)指針數(shù)組pfunArr的指針ppfunArrvoid (*(*ppfunArr)[5])(const char*) = &pfunArr;return 0;
}
//*代表ppfunArr是指針,[]代表這個指針指向一個內(nèi)含5元素的數(shù)組,而每個元素的類型都是void (*)(const char*)
//因此按照運算符的順序來解讀是比較快的
8. 回調(diào)函數(shù)
- 回調(diào)函數(shù)就是一個通過函數(shù)指針調(diào)用的函數(shù)。
- 如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另外一個函數(shù),當(dāng)這個指針被用來調(diào)用其所指向的函數(shù)時,我們就說這是回調(diào)函數(shù)。
- 回調(diào)函數(shù)不是由該函數(shù)的實現(xiàn)方式直接調(diào)用,而是在特定的事件或條件發(fā)生時由另外一方調(diào)用的,用于對該事件或條件進行響應(yīng)
(1)例子一:使用qsort
①前要:C庫函數(shù)qsort能對數(shù)組進行排序
void qsort(void *base,//指向了待排序數(shù)組的第一個元素 size_t nitems,//排序的元素個數(shù)size_t size,//每個元素的大小,單位是字節(jié)int (*compar)(const void*, const void*)//指向一個函數(shù),這個函數(shù)可以比較兩個元素的大小);//其底層是使用快速排序的方法來排序的,依靠compar指向的不同函數(shù)內(nèi)部不同的比較,可以解決不同類型的數(shù)據(jù)快速排序
②使用qsort:對數(shù)組進行排序
#include <stdio.h>
#include <stdlib.h>
//qosrt函數(shù)的使用者得實現(xiàn)一個比較函數(shù)
int int_cmp(const void * p1, const void * p2)
{return ( *(int*)p1 - *(int*)p2 );//注意不能直接解引用void指針,另外如果倒過來就是逆序輸出了
}
int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++){printf( "%d ", arr[i]);}printf("\n");return 0;
}
(2)例子二:模擬實現(xiàn)類似qsort函數(shù)的bubble函數(shù)(底層采用冒泡函數(shù))
#include <stdio.h>
int int_cmp(const void * p1, const void * p2)//其中一個比較函數(shù),這個是整型比較,是用戶決定這個函數(shù)應(yīng)該如何編寫
{return (*(int*)p1 - *(int*)p2);//不過注意void不能直接解引用!!!
}void _swap(void *p1, void * p2, int size)//其中一個排序方法,這個是排序是冒泡排序,可以由開發(fā)者決定底層排序的方法,在qsort中使用的底層函數(shù)是快排
{int i = 0;for (i = 0; i< size; i++)//之所以這么做,是因為沒有辦法預(yù)測有多少個字節(jié),只能通過一個一個字節(jié)進行交換,最后所有字節(jié)進行交換,即兩個數(shù)據(jù)進行了交換{char tmp = *((char*)p1 + i);*((char*)p1 + i) = *((char*)p2 + i);*((char*)p2 + i) = tmp;}
}void bubble(void *base, int count, int size, int(*cmp )(void*, void*))//相當(dāng)于qsort,但是實現(xiàn)邏輯不僅僅是快速排序,內(nèi)部的_swap函數(shù)也可能是其他的排序算法,cmp函數(shù)可能比較不同類型的數(shù)據(jù)。 注意base是void*類型,寫int*會寫死的,只能限定于整型
{int i = 0;int j = 0;for (i = 0; i < count - 1; i++){int flag = 1;//①優(yōu)化代碼,若是沒有交換就說明不需要經(jīng)過排序就是有序的了for (j = 0; j < count - i - 1; j++){if (cmp ((char*) base + j * size, (char*)base + (j + 1) * size) > 0)//比較函數(shù),這里改成(char*)就可以利用size適應(yīng)不同的字節(jié){flag = 0;_swap((char*)base + j * size, (char*)base + (j + 1) * size, size);//排序函數(shù),這里改成(char*)就可以利用size適應(yīng)不同的字節(jié)}if(flag == 1){break;}}}
}int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };//char *arr[] = {"aaaa","dddd","cccc","bbbb"};int i = 0;bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++){printf( "%d ", arr[i]);}printf("\n");return 0;
}
注意我們不是在模擬qsort函數(shù),而是作一個類似qsort函數(shù)的“冒泡排序通用的bubble函數(shù)”