一、字符指針變量
我們知道有種指針類型為字符指針(char*)。
#include <stdio.h>
int main()
{char ch = 'w';char* pch = &ch;printf("%c\n", *pch);return 0;
}
其實它還有一種使用方式。
#include <stdio.h>
int main()
{char* pstr = "hello world";printf("%s\n", pstr);return 0;
}
在代碼?char* pstr = "hello world";
?中,指針?pstr
?存放的正是字符串?"hello world"
?首字符?'h'
?的地址。在 C 語言里,字符串是以字符數組的形式存儲在內存中的,并且以?'\0'
(空字符)作為字符串結束的標志。當定義一個指針指向一個字符串常量時,該指針的值就是這個字符串存儲在內存中起始位置(也就是首字符所在位置)的地址。當在printf
函數中使用%s
格式化符并且參數是一個字符指針(如char *pstr
)時,printf
函數會把這個指針當作字符串的起始地址。它會從這個地址開始讀取字符,一直讀取到遇到'\0'
(字符串結束標志)為止。例如,在代碼char* pstr="hello world"; printf("%s\n", pstr);
中,pstr
指向字符串"hello world"
的首字符'h'
。printf
會從這個地址開始,逐個字符地輸出,直到遇到'\0'才停止。
現在我們來看一看《劍指offer》中的一道題。
#include <stdio.h>
int main()
{char str1[] = "hello";char str2[] = "hello";const char* str3 = "hello";const char* str4 = "hello";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;
}
我們來分析一下結果為什么是這樣的。當我們使用char數組存放字符串時,就會為數組分配獨立的內存空間來存放字符串,所以str1和str2都指的是這兩個數組的首字符的地址,即使字符串相同,所對應的地址也不相同。在 C 語言中,像?"hello"
?這樣的常量字符串在程序中通常只存儲一份在只讀的內存區域。當定義?str3
?和?str4
?這兩個指針并讓它們指向同一個常量字符串"hello"
?時,它們實際上都指向了這個字符串的起始地址。所以當進行?str3 == str4
?比較時,比較的是兩個指針所存儲的地址值,因為它們都指向同一個?"hello"
?字符串所在的內存位置,所以地址是相同的。其實造成差異的本質原因是前兩個都是在定義字符串變量,也就是字符數組,它本身是沒有地址的,而后兩個常量字符串本身就是有地址的,這里只是將地址取出來存放在指針中。
二、數組指針變量
int(*p)[10];
這就是數組指針的形式。p是這個變量的名字,*說明這個變量是指針,還剩下int [10]就說明它指向的對象是大小為10的整型數組類型的。
注意:[]的優先級要高于*號的,所以必須加上()來保證p先和*結合。否則這個變量就不是數組指針了,而是指針數組。
那么數組指針變量怎么初始化呢?其實和我們之前初始化指針變量一樣。
#include<stdio.h>
void test(int arr[2][3],int a,int b)
{for (int i = 0; i < a; i++){for (int j = 0; j < b; j++){printf("%d ", arr[i][j]);}printf("\n");}
}
int main()
{int arr[2][3] = { {1,2,3},{2,3,4} };test(arr,2,3);return 0;
}
之前我們使用二維數組傳參都是如上圖的代碼這樣去進行操作的。其實二維數組就是一維數組的數組,也就是二維數組的每個元素是一個一維數組。所以二維數組的數組名表示的就是第一行的地址,是一個一維數組的地址。根據上面的例子,第一行的一維數組的類型就是 int [3] ,所以第一行的地址的類型就是數組指針類型 int(*)[3] 。那就意味著二維數組傳參本質上也是傳遞了地址,傳遞的是第一行這個一維數組的地址,那么形參也是可以寫成指針形式的。如下:
#include<stdio.h>
void test(int(*arr)[3], int a, int b)
{for (int i = 0; i < a; i++){for (int j = 0; j < b; j++){printf("%d ", arr[i][j]);}printf("\n");}
}
int main()
{int arr[2][3] = { {1,2,3},{2,3,4} };test(arr,2,3);return 0;
}
四、函數指針變量

#include<stdio.h>
void test(int(*arr)[3], int a, int b)
{for (int i = 0; i < a; i++){for (int j = 0; j < b; j++){printf("%d ", arr[i][j]);}printf("\n");}
}
int main()
{int arr[2][3] = { {1,2,3},{2,3,4} };test(arr,2,3);void (*p)(int(*arr)[3], int a, int b) = test;return 0;
}
p是變量名,*表示這個變量是指針,前面的void是函數的返回值,后面加的是函數的參數。而且在參數中可以省略具體的變量名,只留下參數的數據類型。在這里就是將a,b去掉,只留下(int,int)。那我們怎么去使用這個函數指針變量呢?
#include<stdio.h>
int add(int a, int b)
{return a + b;
}
int main()
{int (*p)(int, int) = add;printf("%d", p(1, 2));return 0;
}
使用函數指針名加參數就能實現,當然將函數指針名解引用后加參數也能實現,但是直接使用函數指針名編譯器就可以理解這是對函數指針所指向的函數的調用,使用起來會更加方便,一般不推薦顯式解引用來使用函數。
接下來,我們來看兩段c陷阱與缺陷中的代碼。當然,這些代碼只是為了讓我們更加理解指針等知識,其實某些寫法并不推薦。
1.(*(void (*)())0)();
來分析一下代碼,我們能看到中間的void (*)(),知道這是一個函數指針,參數為空,返回值為void,后面還跟著一個0,那我們就可以知道,這里是將0強制類型轉換了,將它變成了函數指針類型,然后將整體解引用,后面再加上空參,就是在調用函數。
2.void (*signal(int , void(*)(int)))(int);
看到signal(int , void(*)(int))應該就能知道這是一個函數名加上參數,一個是整型類型的,一個是函數指針類型的。其實,剩下的void(*)(int)是這個函數的返回值,這種寫法確實比較奇怪,我們在這里也不需要多糾結,這個代碼整體就是一個函數的聲明。
五、typedef 關鍵字
typedef unsigned int uint;
//將unsigned int重命名為uint
一般類型都可以這樣進行操作,但是對于數組指針和函數指針稍微有點區別,新的類型名必須在*的右邊:
#include <stdio.h>
int add(int x, int y)
{return x + y;
}
int main()
{int arr[5] = { 0 };typedef int(*parr_t)[5];//int(*)[5]->parr_ttypedef void(*pf_t)(int);//void(*)(int)->pf_tparr_t p1 = arr;pf_t p2 = add;return 0;
}
六、函數指針數組
int (*parr1[3])();
那么函數指針數組有什么用呢,接下來我們來看轉移表。舉例:計算器的?般實現:
#include<stdio.h>
void menu()
{printf("***0.exit***\n");printf("***1.add****\n");printf("***2.sub****\n");printf("***3.mul****\n");printf("***4.div****\n");printf("請輸入...\n");
}
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 n;int(*p[5])(int, int) = { 0,add,sub,mul,div };while (1){menu();scanf("%d", &n);if (n > 0 && n < 5){int a, b;printf("請輸入兩個操作數...\n");scanf("%d%d", &a, &b);printf("結果是:%d\n", p[n](a, b));}else if (n == 0){printf("退出計算器\n");break;}elseprintf("輸入有誤\n");}return 0;
}
如上圖的代碼,我們就能實現簡易的加減乘除計算器,這就使用了函數指針數組。那么轉移表又是什么呢?其實它指的就是運用函數指針數組以數組方式去調用里面的函數,使代碼變得更加簡潔。
七、回調函數
#include<stdio.h>
void menu()
{printf("***0.exit***\n");printf("***1.add****\n");printf("***2.sub****\n");printf("***3.mul****\n");printf("***4.div****\n");printf("請輸入...\n");
}
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;
}
void is(int(*p)(int, int))
{int a, b;printf("請輸入兩個操作數...\n");scanf("%d%d", &a, &b);printf("結果是:%d\n", p(a, b));
}
int main()
{int n;int(*p[5])(int, int) = { 0,add,sub,mul,div };while (1){menu();scanf("%d", &n);if (n > 0 && n < 5){switch (n){case 1:{is(add);break;}case 2:{is(sub);break;}case 3:{is(mul);break;}case 4:{is(div);break;}}}else if (n == 0){printf("退出計算器\n");break;}elseprintf("輸入有誤\n");}return 0;
}
八、qsort 使用舉例
qsort的作用是對數組的元素進行排序,使用?compar?函數確定順序,將base?指向的數組的?num?個元素進行排序,每個元素大小為size。該函數不返回任何值,但通過對?compar?定義的元素進行基數重新排序來修改指向的數組的內容。我們能發現參數中的指針都是void*類型的,這是因為我們進行操作的數據類型不是固定的。
我們先來看一下compar?函數,它可以比較元素對,并將指向它們的指針作為參數。其返回值如下圖,一般可以簡化成小于返回-1,等于返回0,大于返回1。
1.使用qsort函數排序整型數據
#include<stdio.h>
#include<stdlib.h>
int int_compar(const void* p1, const void* p2)
{return *((int*)p1) - *((int*)p2);
}
int main()
{int arr[] = { 4,3,6,12,8,2,9,7,1,5 };qsort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(int), int_compar);for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}return 0;
}
2.使用qsort排序結構數據
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{char name[20];int age;
};
int cmp_stu_by_age(const void* e1, const void* e2)
{return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int cmp_stu_by_name(const void* e1, const void* e2)
{return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void test1()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
void test2()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{test1();test2();return 0;
}
通過調試我們可以看出結構體中的數據確實被排序了。
3.qsort函數的模擬實現
#include <stdio.h>
int int_cmp(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);
}
void _swap(void* p1, void* p2, int size)
{int i = 0;for (i = 0; i < size; i++){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*))
{int i = 0;int j = 0;for (i = 0; i < count - 1; i++){for (j = 0; j < count - i - 1; j++){if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0){_swap((char*)base + j * size, (char*)base + (j + 1) * size,size);}}}
}
int main()
{int arr[5] = { 2,3,4,1,5 };bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), int_cmp);for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}return 0;
}
九、sizeof和strlen的對比
1.sizeof
#include <stdio.h>
int main()
{int a = 10;printf("%d\n", sizeof(a));printf("%d\n", sizeof a );printf("%d\n", sizeof(int));return 0;
}
2.strlen
size_t strlen(const char* str);
