在前面,我们知道了指针的一些基本的情况:
指针是一个变量.我们在口头上说的指针指的是指针变量,它用来存放地址;
指针的大小是固定的,在32位平台上是4个字节,在64位上是8个字节;
指针有数据类型,指针的数据类型决定了指针在+-整数时的步长;
本文主要来讨论一下指针的一些进阶用法.
一,字符指针
在指针类型中,有一种指针类型被称为字符指针(char*);
我们来看这两种情况:
char ch = 'w';
char *pc = &ch;
*pc = 'w';
printf("%c", *pc);//w
这种情况直接打印出w;
char* pstr = "hello world.";//这里是把一个字符串放到pstr指针变量里了吗?
* pstr = 'w';
printf("%s\n", pstr);
这种情况时什么样的呢?
运行起来就会发现,程序崩掉了.这是因为,上面的"hello world."是存储在内存中的只读数据区
的,也就是说,这个字符串常量不能被修改.通常,我们对于常量,加上const关键字来保护常量让它不能被修改.
为了加深印象,我们来看看这样的一道题:
#include <stdio.h> //判断一下代码的结果是什么 int main() { char str1[] = "hello bit.";
char str2[] = "hello bit."; char *str3 = "hello bit."; char *str4 = "hello
bit."; 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; }
这里str3和str4指向的是一个同一个常量字符串.C/C++会把常量字符串存储到单独的一个内存区域.当几个指针指向同一个字符串的时候,他们实际会指向同一块内存.但是用相同的常量字符串去初始
化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
在定义指针变量的时候,常常有一个误区:
int* pa, pb;
本来的想法是要定义一个指针pa,指针pb,但是这样的定义是错误的.只有pa是指针,而pb只是普通变量.
在看看以下代码:
typedef int* pint;
#define PINT int*
...
PINT pa, pb;
pint pc, pd;
这种情况下,pa,pc,pd为指针变量,而pb为普通变量.define预处理只是做了简单的替换,在没有其他的做法;而typedef关键字是为一个类型起新名字.这样,pc,pd都成为了指针类型.
二,指针数组
什么是指针数组?存放指针的数组就是指针数组.举个例子,整型数组里面,存放的是一些整数,字符数组里面,存放的是字符,类比指针数组,里面存放的就是指针了.
定义一个字符指针数组:
char* arr[] = { "abcdef", "qwer", "zhangsan" }; int i = 0; int sz =
sizeof(arr) / sizeof(arr[0]); for (i = 0; i < sz; i++) { printf("%s\n",
arr[i]); }
结果如下:
三,数组指针
数组指针是指针,它表示指向数组的指针.我们定义int(*p)[10],要注意的是,[]的优先级高于*,所以要加().这个定义:p和*结合,表示这是一个指针,然后指向大小为10个整型的数组,也就是说,这个指针指向一个大小为10个int类型的数组
要注意的是,它指向数组的首地址,不是数组的首元素的地址.我们来看这样的一组代码:
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);
结果为:
我们发现, 数组名和&数组名打印的地址是一样的.但是将它们加1后,arr是跳过4个字节的大小,而&arr是跳过了4*10个字节的大小.我们发现,其实&arr和arr,虽然值是一样的,但是意义不一样. 实际上&arr
表示的是数组的地址,而不是数组首元素的地址.数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.
数组指针一般用在二维数组的传参较多.为了有所区别,我们使用一维数组和二维数组来进行比较.
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p,但是没必要这么麻烦,也不会这样写,这里只是比较
printf("%d", **p);//*p表示拿到数组的地址,在*一下就拿到了里面的内容1;
//printf("%d", *((*p + 0) + 0));//(*)
printf("%d", *(*p + 1));//2
定义一个大小为10的数组,在定义一个数组指针,指向大小为10的数组,然后&arr操作,拿到这个数组的地址.要注意的是:arr是数组首元素的地址
, 而p是一个指针,为什么不直接int(*p)[10] =
arr呢?这里的p是指向数组的地址,arr是数组首元素的地址,不是数组的地址,数组的地址是&arr.在看看二维的情况:
int arr[3][5] = { {1,2,3,4,5}, {6,7,8,9,0}, {11,12,13,14,15} };
int(*p)[5] = arr;
print(arr, 3, 5);
printf("\n");
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 5; ++j)
{
printf("%d ", *(*(p + i) +
j));//p+i表示第i行的地址,在*,就拿出了这一行,在+j,就是这一样第j个元素的地址,在*,就拿出了这个元素
}
printf("\n");
}
这里arr不见了&,但还是一样的,arr是数组的首元素的地址,
二维数组的首元素的地址是第一行,也是一个数组,那么,就没必要要加&,否则反而会报错.实质上,我们如果将上面的一维数组类比成二维数组,那么它的打印就是(*).
四,函数指针
函数指针就是指向函数的指针. 定义Void
(*pfun1)();表示pfun1是一个指针,指针指向一个函数,这个函数没有参数,返回值为void.我们来看下面的代码:
#include void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
输出的是两个地址,这两个地址是 test 函数的地址。实质上,这个&加不加都无所谓.
结果均为5,而且,编译器没有任何的报错和警告,这就说明,它们是等价的.实质上,
对于函数名来说,前面的&和*都会被忽略,前面加不加&或*都没区别.我们接下来看看这一个代码:
(*( void (*)())0)();//来自<<C语言陷阱与缺陷>>
它表示:把0强制类型转换为void (*)()类型的函数指针,解引用后再去调用0地址处这个参数为无参,返回类型为void的函数.
五,函数指针数组
把函数的地址存到一个数组中.那这个数组就叫函数指针数组.我们定义int(*p[10])()这样一个函数指针数组,它表示:[]和p结合,说明p是一个数组,加了一个*,表示它是指针数组,在加一个(),说明这是一个函数,函数的参数是无参的,同时,int表示它的返回值是int类型.
函数指针数组有一个用途是做转移表使用.比如,我们做一个有两个操作数的计算器,代码如下,switch部分的代码能不能简化呢?是可以的,用函数指针数组就可以做到.
#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( "输入操作数:" ); 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; } //函数指针数组实现 #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
}; //转移表 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( "输入操作数:" ); scanf( "%d %d", &x, &y); ret
= (*p[input])(x, y); } else printf( "输入有误\n" ); printf( "ret = %d\n", ret); }
return 0; }
六,指向函数指针数组的指针(简单了解)
指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针.它的定义如下:
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针
ppfunArr void (*(*ppfunArr)[10])(const char*) = &pfunArr;
return 0;
}