在指針篇1我們已經了解了整型指針,當然還有很多其他類型的指針,像字符指針、數組指針、函數指針等,他們都有他們的特別之處,讓我們接著學習。
1. 指針類型介紹和應用
1.1 字符指針變量
字符指針變量類型為char*,一般這樣使用:
int main()
{char ch = 'w';char *pc = &ch;*pc = 'w';return 0;
}
還有一種使用方式:
int main()
{const char* pstr = "hello bit.";//這?是把?個字符的地址傳給pstr變量printf("%s\n", pstr);return 0;
}
1.2 數組指針變量(與指針數組區分)
提到數組指針,我們會想數組指針到底是數組還是指針,指針數組又是什么,他們怎么區分?
我們可以這樣理解:
數組指針,就是一個存放內容是數組的指針,那自然是指針了
指針數組,就是一個數組元素是指針的數組,那自然是數組了
我們也可以通過后綴來理解,后綴是本質,前者都是形容詞。
知道了兩者的區別后我們看一段代碼:
int *p1[10]; //指針數組
int (*p2)[10];//數組指針
int *p1[10] 解釋:
[ ]的優先級高于*,p1先與[10]結合,表明這是一個數組,前面的int*表明數組中的元素是int*類型的,所以這是一個指針數組。
int (*p2)[10]解釋:因為有()的存在,p2先與*結合,說明p2是一個指針變量,然后指針指向的是一個大小為10個整型的數組,所以這是一個數組指針。
1.3 函數指針變量
通過前面的學習我們不難猜到函數指針是用來存放函數地址的,未來可以通過函數地址調用函數。
函數的地址與數組有相似之處,函數名就是函數的地址,也可以通過&函數名的方式獲得函數的地址。我們要將函數的地址存放起來,就需要創建函數指針變量了,函數指針變量的寫法也和數組指針非常相似,如下代碼:
void test()
{printf("hehe\n");
}void (*pf1)() = &test;
void (*pf2)()= test;int Add(int x, int y)
{return x+y;
}int(*pf3)(int, int) = Add;
int(*pf4)(int x, int y) = &Add;//x和y寫上或者省略都是可以的
上面說到可以通過函數地址調用函數,函數地址存放在函數指針變量中,我們是不是可以通過函數指針調用指針指向的函數呢?我們通過代碼實現一下。
#include <stdio.h>int Add(int x, int y){return x+y;}int main(){int(*pf3)(int, int) = Add;printf("%d\n", (*pf3)(2, 3));//輸出5printf("%d\n", pf3(3, 5)); //輸出8return 0;}
2. 二維數組的本質
有了數組指針的理解,我們就可以講一講二維數組傳參的本質了
過去二維數組要傳參給一個函數時,代碼如下:
#include <stdio.h>void test(int a[3][5], int r, int c)
{int i = 0;int j = 0;for(i=0; i<r; i++){for(j=0; j<c; j++){printf("%d ", a[i][j]);}printf("\n");}
}int main()
{int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};test(arr, 3, 5);return 0;
}
這里,形參除了寫成二維數組的形式,還可以怎么寫?
我們想想,二維數組名就是數組首行元素的地址,第一行的一維數組類型就是int [5],第一行的地址類型就行int (*) [5],那么意味著二維數組傳參的本質也是傳遞了地址,傳的是第一行這個一位數組的地址,形參也可以寫成指針形式,如下:
#include<stdio.h>void test(int (*p)[5], int r, int c)
{int i = 0;int j = 0;for(i = 0;i < r; i++){for(j = 0; j < c; j++){printf("%d ",*(*(p + i) + j));}printf("\n");}
}int main()
{int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};test(arr, 3, 5);return 0;
}
3. 函數指針數組(應用:轉移表)
3.1 函數指針數組定義
數組是一個存放相同數據類型的存儲空間,把幾個函數的地址存放到一個數組中,這個數組就叫函數指針數組。嗯,函數指針數組,聽起來好復雜,它又應該怎么定義呢?
int (*parr[num]) ();
解釋一下,變量名是parr,先與[num]結合表明這是一個數組,去掉數組部分,剩余部分是數組元素類型,剩余部分是int(*) (),很明顯類型是函數指針,所以這是一個元素類型為函數指針的數組。這樣是不是有所理解了呢。
3.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;do{printf("***********************\n");printf("*1.add 2.sub*\n");printf("* 0.exit *\n");printf("*3.mul 4.div*\n");printf("***********************\n");printf("請選擇:>\n");scanf("%d", &input);switch (input){case 1:printf("輸?操作數:>\n");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("選擇錯誤\n");break;}} while (input);return 0;
}
上面代碼沒有處理除數為0的情況,而且代碼非常冗余,下面這一段代碼重復了多次,只有選擇函數部分不一致,如果我們加減乘除函數存放在數組中,通過數組選擇加減乘除函數,是不是大大減少了代碼量呢?下面我們實現一下。
printf("輸?操作數:");scanf("%d %d", &x, &y);ret = func(x, y);printf("ret = %d\n", ret);break;
?使用函數指針數組實現:
//轉移表實現計算器加減乘除(使用函數指針數組)
#include<stdio.h>void menu()
{printf("***********************\n");printf("*1.add 2.sub*\n");printf("* 0.exit *\n");printf("*3.mul 4.div*\n");printf("***********************\n");
}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 = 0;int y = 0;int input = 1;int (*p[5])(int x, int y) = { NULL, add, sub, mul, div };do{menu();printf("請選擇:>\n");scanf("%d", &input);//判斷有效性if (input >= 1 && input <= 4){printf("請輸入操作數:>\n");scanf("%d %d", &x, &y);//判斷除數為0的情況if (input == 4 && y == 0){printf("除數不能為0\n");continue;}int ret = (*p[input])(x, y);printf("%d\n", ret);}else if (input == 0){printf("退出計算器\n");break;}else{printf("輸入有誤,請重新輸入\n");}} while (input);return 0;
}
4. 回調函數
4.1回調函數定義
回調函數就是通過函數指針調用的函數。
當我們將一個函數的指針(地址)作為參數傳遞給另一個函數時,如果該指針被用于調用其指向的函數,這種被間接調用的函數就稱為回調函數。回調函數的獨特之處在于:它不是由函數定義方直接調用,而是在特定事件或條件發生時由第三方觸發,用于響應相應的事件或條件。
4.2回調函數的應用(加減乘除計算器優化)
在上面第三節中我們實現加減乘除計算器時,可不可以用回調函數實現呢,封裝一個函數,把加減乘除函數的地址作為參數傳給函數,下面編程實現一下。
#include <stdio.h>void menu()
{printf("***********************\n");printf("*1.add 2.sub*\n");printf("* 0.exit *\n");printf("*3.mul 4.div*\n");printf("***********************\n");
}
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 func(int (*p)(int,int))
{int x = 0;int y = 0;int z = 0;printf("輸?操作數:>\n");scanf("%d %d", &x, &y);z = (*p)(x,y);
printf("%d\n", z);
}
int main()
{int input = 1;int ret = 0;do{menu();printf("請選擇:>\n");scanf("%d", &input);switch (input){case 1:func(add);break;case 2:func(sub);break;case 3:func(mul);break;case 4:func(div);break;case 0:printf("退出程序\n");break;default:printf("選擇錯誤\n");break;}} while (input);return 0;
}
5. qsort使用舉例及模擬實現(冒泡排序)
qsort是C語言中用于排序各種同類型數據的庫函數,使用者需要實現一個比較函數,傳入待比較的兩個參數,并返回一個整型數據。(返回值>0,前者>后者;返回值<0,前者<后者;返回值=0,前者=后者)
5.1 qsort使用舉例
5.1.1 使用qsort排序整型數據
//使用qsort排序整形
#include<stdio.h>
;
int int_cmp(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}int main()
{int arr[] = { 0,5,4,2,3,1,6,7,8,9 };qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}return 0;
}
5.1.2 使用qsort排序結構體
//使用qsort排序結構體
#include<stdio.h>
#include<string.h>struct Person
{char name[20];int age;
};void print(struct Person p[])
{for (int i = 0; i < 3; i++){printf("%s %d \n", p[i].name, p[i].age);}
}//按照年齡排序
int stu_cmp_by_age(const void* p1, const void* p2)
{return ((struct Person*)p1)->age - ((struct Person*)p2)->age;
}
//按照年齡排序
void test1()
{struct Person s[] = { {"zhangsan", 21}, {"lisi", 25}, {"wangwu", 29} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), stu_cmp_by_age);print(s);
}//按照名字排序
int stu_cmp_by_name(const void* p1, const void* p2)
{return strcmp(((struct Person*)p1)->name, ((struct Person*)p2)->name);
}//按照名字排序
void test2()
{struct Person s[] = { {"zhangsan", 21}, {"lisi", 25}, {"wangwu", 29} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), stu_cmp_by_name);print(s);
}int main()
{test1();test2();return 0;
}
5.2 qsort的模擬實現(冒泡排序)
在指針篇1中我們學習了冒泡排序,這次我們使用冒泡排序的方法模擬實現一下qsort庫函數。因為qsort不知道使用者要比較的是什么類型數據,所以傳入的參數地址要使用void*類型(void*類型在指針篇2.3.3中提到)
//qsort模擬實現(使用冒泡排序)
#include<stdio.h>int int_cmp(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}void Swap(char* one, char* two, size_t width)
{for (int i = 0; i < width; i++){char tmp = *one;*one = *two;*two = tmp;one++;two++;}
}void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void*,const void*))
{for (int i = 0; i < sz - 1; i++){for (int j = 0; j < sz - 1 - i; j++){//數組內部兩兩比較if (cmp((char*)base + j * width, (char*)base+(j + 1) * width) > 0){Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}
}int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;bubble_sort(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;
}
6. sizeof和strlen的對比
6.1 sizeof
sizeof是C語言中的一個單目操作符,sizeof是計算變量所占內存空間大小的,單位是字節,如果操作數是數據類型的話,計算的是使用此類型創建的變量所占內存空間的大小。代碼示例:
#inculde <stdio.h>int main()
{int a = 10;printf("%d\n", sizeof(a));printf("%d\n", sizeof a);printf("%d\n", sizeof(int));//都可以正常輸出4return 0;
}
6.2 strlen
strlen是C語言庫函數,使用需要包含頭文件string.h,功能是求字符串長度。函數原型:
size_t strlen ( const char * str );
統計的是傳入參數的地址到遇到 \0 之前字符串中字符的個數,strlen會一直向后找直到找到 \0 為止,所以可能存在越界查找。
#include <stdio.h>int main()
{char arr1[3] = {'a', 'b', 'c'};char arr2[] = "abc";printf("%d\n", strlen(arr1));//隨機值printf("%d\n", strlen(arr2));//3printf("%d\n", sizeof(arr1));//3printf("%d\n", sizeof(arr2));//4return 0;
}