前面我们已经了解了指针的部分概念。
指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
指针的大小是固定的4/8个字节(32位平台/64位平台)。
指针有类型,指针的类型决定了指针±整数的步长,指针解引用时候的权限。
指针的运算。
今天,我们来讨论指针的高级主题。
字符指针
在指针类型中我们知道有一种指针类型为字符指针char*。
使用方式:
char ch = 'w';
char* pc = &ch;
char* str = "hello, world!";
代码char* str = “hello, world!”;特别容易让人误以为是把字符串hello, world!放到字符指针str中,但是本质是吧字符串hello, world!首字符的地址放到了str中。
代码演示
#include <stdio.h>
int main(){
char str1[] = "hello, world!";
char str2[] = "hello, world!";
char* str3 = "hello, world!";
char* str4 = "hello, world!";
if(str1 == str2){
printf("str1 and str2 are same!\n");
}
else{
printf("str1 and str2 are not same!\n");
}
if(str3 == str4){
printf("str3 and str4 are same!\n");
}
else{
printf("str3 and str4 are not same!\n");
}
return 0;
}
运行结果
[sss@aliyun ptr]$ gcc str.c -o str
[sss@aliyun ptr]$ ./str
str1 and str2 are not same!
str3 and str4 are same!
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,它们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
指针数组
前面我们学习过,指针数组就是一个存放指针的数组。
int* arr1[10];
char* arr2[10];
double* arr3[10];
数组指针
数组指针是指针。我们已经了解:整形指针:int* pointer;能够指向整型数据的指针。浮点型指针:double* pointer;能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。
下面哪个是数组指针?
int *p1[10];
int (*p2)[10];
p1是指针数组,p2是数组指针。
解释:int (*p)[10];
p先和*结合,说明p是一个指针变量,然后指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫做数组指针。
注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
数组指针的使用
代码演示
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
static int randomNubmer(){
return 11 + rand() % (99 - 11 + 1);
}
static void arrInit(int(*arr)[4], int row, int col){
int i, j;
for(i = 0; i < row; ++i){
for(j = 0; j < col; ++j){
arr[i][j] = randomNubmer();
}
}
}
static void arrDisplay(int(*arr)[4], int row, int col){
int i, j;
for(i = 0; i < row; ++i){
for(j = 0; j < col; ++j){
printf("%-5d", arr[i][j]);
}
printf("\n");
}
}
int main(){
srand((unsigned int)time(0));
int arr[3][4] = {0};
arrInit(arr, 3, 4);
printf("The array is: \n");
arrDisplay(arr, 3, 4);
return 0;
}
运行结果
[sss@aliyun ptr]$ gcc arr_ptr.c -o arr_ptr
[sss@aliyun ptr]$ ./arr_ptr
The array is:
61 36 93 86
90 73 31 65
69 99 85 20
&数组名和数组名
对于int arr[10];
arr和&arr分别是什么?
我们知道arr是数组名,数组名表示数组首元素的地址。
那**&arr**数组名到底是什么?
代码演示
#include <stdio.h>
int main(){
int arr[10] = {0};
printf("arr: %p\n", arr);
printf("&arr: %p\n", &arr);
return 0;
}
运行结果
[sss@aliyun ptr]$ ./ptr_and_arr
arr: 0x7ffc68b07ff0
&arr: 0x7ffc68b07ff0
从运行结果可以看到,数组名和&数组名打印的地址是一样的。
难道两个是一样的吗?
修改代码
#include <stdio.h>
int main(){
int arr[10] = {0};
printf("arr: %p\n", arr);
printf("&arr: %p\n", &arr);
printf("arr + 1: %p\n", arr + 1);
printf("&arr + 1: %p\n", &arr + 1);
return 0;
}
运行结果
[sss@aliyun ptr]$ !gcc
gcc ptr_and_arr.c -o ptr_and_arr
[sss@aliyun ptr]$ ./ptr_and_arr
arr: 0x7ffc02ae8240
&arr: 0x7ffc02ae8240
arr + 1: 0x7ffc02ae8244
&arr + 1: 0x7ffc02ae8268
从运行结果可以看出,&arr和arr虽然值是一样的,但是意义是不一样的。实际上:&arr代表的是数组的地址,而不是数组首元素的地址。
数组的地址+1,跳过整个数组的大小,所以&arr + 1和&arr差值为40个字节。
数组和指针作为参数传递
一维数组传参
#include <stdio.h>
#include <stdlib.h>
static void test1(int arr[]){
}
static void test1(int arr[10]){
}
static void test1(int* arr){
}
static void test2(int* arr[10]){
}
static void test2(int** arr){
}
int main(){
int arr[10] = { 0 };
int* arr2[10] = { 0 };
test1(arr);
test2(arr2);
system("pause");
return 0;
}
二维数组传参
#include <stdio.h>
#include <stdlib.h>
static void test1(int arr[3][5]){
}
static void test2(int arr[][]){
}
static void test3(int arr[][5]){
}
static void test4(int* arr){
}
static void test5(int* arr[5]){
}
static void test6(int(*arr)[5]){
}
static void test7(int** arr){
}
int main(){
int arr[3][5] = { 0 };
test1(arr);
test2(arr);
test3(arr);
test4(arr);
test5(arr);
test6(arr);
test7(arr);
system("pause");
return 0;
}
运行结果
1>d:\backup\documents\visual studio 2013\projects\project2\project2\源.c(9): error C2087: “arr”: 缺少下标
1>d:\backup\documents\visual studio 2013\projects\project2\project2\源.c(32): warning C4048: “int (*)[1]”和“int [3][5]”数组的下标不同
1>d:\backup\documents\visual studio 2013\projects\project2\project2\源.c(34): warning C4047: “函数”:“int *”与“int [3][5]”的间接级别不同
1>d:\backup\documents\visual studio 2013\projects\project2\project2\源.c(34): warning C4024: “test4”: 形参和实参 1 的类型不同
1>d:\backup\documents\visual studio 2013\projects\project2\project2\源.c(35): warning C4047: “函数”:“int **”与“int [3][5]”的间接级别不同
1>d:\backup\documents\visual studio 2013\projects\project2\project2\源.c(35): warning C4024: “test5”: 形参和实参 1 的类型不同
1>d:\backup\documents\visual studio 2013\projects\project2\project2\源.c(37): warning C4047: “函数”:“int **”与“int [3][5]”的间接级别不同
1>d:\backup\documents\visual studio 2013\projects\project2\project2\源.c(37): warning C4024: “test7”: 形参和实参 1 的类型不同
从运行结果可以看出,二维数组传参,函数参数的设计只能忽略第一个[]的数字。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。test2,test4,test5,test7都是错的。
一级指针传参
代码演示
#include <stdio.h>
#include <stdlib.h>
static void arrDisplay(int* p, int len){
int i = 0;
for (; i < len; ++i){
printf("%d ", p[i]);
}
printf("\n");
}
int main(){
int arr[10] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
int* p = arr;
arrDisplay(p, 10);
system("pause");
return 0;
}
思考:当一个函数的参数为一级指针的时候,函数能接收什么参数?
答:字符串、一级指针、一维数组、变量地址。
二级指针传参
代码演示
#include <stdio.h>
void test(int** p){
printf("a = %d\n", **p);
}
int main(){
int a = 1;
int* pa = &a;
int** ppa = &pa;
test(ppa);
test(&pa);
return 0;
}
运行结果
[sss@aliyun ptr]$ gcc d2_ptr.c -o d2_ptr
[sss@aliyun ptr]$ ./d2_ptr
a = 1
a = 1
思考:当函数的参数为二级指针的时候,可以接受什么参数?
一级指针数组、一级指针的地址。
函数指针
代码演示
#include <stdio.h>
void func(){
printf("hello, world!\n");
}
int main(){
printf("func: %p\n", func);
printf("&func: %p\n", &func);
return 0;
}
运行结果
[sss@aliyun ptr]$ gcc func_ptr.c -o func_ptr
[sss@aliyun ptr]$ ./func_ptr
func: 0x40055d
&func: 0x40055d
输出结果为两个地址,这两个地址都是函数func的地址。如果我们想把这两个地址保存起来?下面两个哪个可以用来保存func函数的地址。
void (*pfunc1)();
void *pfunc2();
首先,指针才能够存储地址,那pfunc1和pfunc2哪个是指针呢?pfunc1是,pfunc1先和*结合。pfunc2是函数。
函数指针数组
顾名思义,函数指针数组是一个数组,该数组中存放的每个元素都是一个函数指针。
函数指针数组的定义
int (*parr[10])();
parr先和[]结合,说明是一个数组,数组的内容是什么?int (*)()类型的函数指针。
函数指针数组的用途 - 转移表
不使用函数指针数组实现简单的计算器
#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 choose = 0;
int a, b, result;
printf("Please choose: \n");
printf("1.add\t2.sub\n3.mul\tdiv\n");
scanf("%d", &choose);
printf("Please input a and b: \n");
scanf("%d %d", &a, &b);
switch(choose){
case 1:
result = add(a, b);
break;
case 2:
result = sub(a, b);
break;
case 3:
result = mul(a, b);
break;
case 4:
result = div(a, b);
break;
default:
printf("input error!\n");
break;
}
printf("The result is: %d\n", result);
return 0;
}
运行结果
[sss@aliyun ptr]$ !gcc
gcc cal_without_funcarr.c -o cal_without_funcarr
[sss@aliyun ptr]$ ./cal_without_funcarr
Please choose:
1.add 2.sub
3.mul div
1
Please input a and b:
4 2
The result is: 6
[sss@aliyun ptr]$ ./cal_without_funcarr
Please choose:
1.add 2.sub
3.mul div
2
Please input a and b:
4 2
The result is: 2
[sss@aliyun ptr]$ ./cal_without_funcarr
Please choose:
1.add 2.sub
3.mul div
3
Please input a and b:
4 2
The result is: 8
[sss@aliyun ptr]$ ./cal_without_funcarr
Please choose:
1.add 2.sub
3.mul div
4
Please input a and b:
4 2
The result is: 2
使用函数指针数组实现简单计算器
#include <stdio.h>
typedef int (*Cal)(int, int);
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 a, b, result, choose;
Cal cal[4] = {
add, sub, mul, div
};
printf("Please choose: \n");
printf("1.add\t2.sub\n3.mul\t4.div\n");
scanf("%d", &choose);
printf("Please input a and b: \n");
scanf("%d %d", &a, &b);
result = cal[choose - 1](a, b);
printf("The result is: %d\n", result);
return 0;
}
运行结果
[sss@aliyun ptr]$ !gcc
gcc cal_with_funcptr.c -o cal_with_funcptr
[sss@aliyun ptr]$ ./cal_with_funcptr
Please choose:
1.add 2.sub
3.mul 4.div
1
Please input a and b:
4 2
The result is: 6
[sss@aliyun ptr]$ ./cal_with_funcptr
Please choose:
1.add 2.sub
3.mul 4.div
2
Please input a and b:
4 2
The result is: 2
[sss@aliyun ptr]$ ./cal_with_funcptr
Please choose:
1.add 2.sub
3.mul 4.div
3
Please input a and b:
4 2
The result is: 8
[sss@aliyun ptr]$ ./cal_with_funcptr
Please choose:
1.add 2.sub
3.mul 4.div
4
Please input a and b:
4 2
The result is: 2
函数指针数组的用途 - 回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行相应。简单来说,回调函数就是将函数当做另一个函数的参数。
代码演示
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define N 10
static int randomNumber(){
return 11 + rand() % (99 - 11 + 1);
}
static void arrInit(int arr[], int len){
int i = 0;
for(; i < len; ++i){
arr[i] = randomNumber();
}
}
static void arrDisplay(int arr[], int len){
int i = 0;
for(; i < len; ++i){
printf("%-5d", arr[i]);
}
printf("\n");
}
static int asc(int* a, int* b){
return *a < *b ? 1 : 0;
}
static int desc(int* a, int* b){
return *a > *b ? 1 : 0;
}
typedef int (*Cmp)(int*, int*);
static void swap(int* a, int* b){
int temp = *a;
*a = *b;
*b = temp;
}
static void bubbleSort(int arr[], int len, Cmp cmp){
int bound, cur;
for(bound = 0; bound < len; ++bound){
for(cur = len - 1; cur > bound; --cur){
if(cmp(&arr[cur], &arr[cur - 1])){
swap(&arr[cur], &arr[cur - 1]);
}
}
}
}
int main(){
srand((unsigned int)time(0));
int arr[N] = {0};
arrInit(arr, N);
printf("The origin array is: \n");
arrDisplay(arr, N);
bubbleSort(arr, N, asc);
printf("The asc array is: \n");
arrDisplay(arr, N);
bubbleSort(arr, N, desc);
printf("The desc array is: \n");
arrDisplay(arr, N);
return 0;
}
运行结果
[sss@aliyun ptr]$ !gcc
gcc call_back.c -o call_back
[sss@aliyun ptr]$ ./call_back
The origin array is:
12 89 90 92 15 82 24 71 23 37
The asc array is:
12 15 23 24 37 71 82 89 90 92
The desc array is:
92 90 89 82 71 37 24 23 15 12