初谈指针(2)
前言
上一篇(初探指针(1))从指针的概念开始入手,依次是:什么是指针>>指针传递>>多级指针>>指针函数。尽管指针远不于此,但寻常应付想来足够了。这一次想谈谈关于指针易混淆的地方。
倒不知是否翻译之故,一度出现了指针函数和函数指针,指针数组和数组指针,指针常量和常量指针,字符指针数组和二维字符数组……统统被我认为属于一个群体——不用就会忘记系列。其实抛开考试中会故意找茬,实际运用到都算不上太难。但这些术语名字嘛……想起我还小的时候,范冰冰李冰冰傻傻分不清。
指针与二维数组
首先我们需要理清指针与数组的关系:数组不等于指针,但在某些情况下二者等价。
定义一个一维数组int a[i] = {...};
,当我们使用a[i]
的时候,实际上编译器做了转换处理,变成了*(a+i)
。
一维数组是方便理解的,那么二维数组呢?
a[i][j]
与*(*(a+i)+j)
具有等价效果,对后者分析如下:
- a表示二维数组中第一行的地址,a+i则表示第i+1行的地址
- *(a+i)表示第i+1行的列地址(这个列为第一列),*(a+i)+j则表示第i+1行、第j+1列的地址
- *(*(a+i)+j)表示取出这个地址上的数据
有了这个铺垫,我们就可以愉快的开始下面的旅程了。
指针函数
前面说到在指针函数中,return的时候注意不要是局部变量的地址,这句话本身是有瑕疵的。但如果就这样做了,保准儿程序不会出错。但是等一等,我们需要这般畏首畏尾吗?
与“猥(畏)琐(缩)”说不!
一般说来,存放在栈中的变量不可以被return地址。这是由于栈中的数据由系统产生,也由系统回收,很难说我们在return栈中的某个数据的地址时,系统是不是早一步把它干掉了。
但这几个数据是可以的:全局变量,静态变量,常量数据,堆中的数据。一个运行的程序里,这些数据并不存放在栈中,具体在哪里涉及到内存分配的问题,这里不做延伸了。
函数指针
函数指针的根本是指针,它能够指向一个函数。
定义:int (*p)(int, int);
表示p是一个能够指向函数的指针,并且它指向的函数是int类型,且这个函数有两个参数,这个两个参数都是int类型。
#include <stdio.h>
int add_xy(int x, int y)
{
return x+y;
}
int main(int argc, char **agrv)
{
int (*p)(int, int);
p = &add_xy;
int result = p(1, 2);
printf("%d\n", result);
return 0;
}
// 输出
>>3
举一反三:char *(*p)(int, char *)
表示p是一个指向函数的指针,这个函数有两个参数:一个int类型,一个char*类型(char*的指针),并且这个函数返回char *的指针
一定要区分: char *p(int, char *)
,当没有小括号把指针运算符和指针变量包起来的时候,表示指针函数的声明,函数的名字为p
指针数组
指针数组的根本是数组,即:一个数组里的成员都是指针
定义:int *p[];
按照优先级顺序,这里p[]
是一个整体,所以可以看作:int *
—> p[]
示例:
#include <stdio.h>
int main(int argc, char **agrv)
{
int a=1, b=2, c=3;
int *p[3] = {&a, &b, &c}; // 定义一个指针数组,并且初始化
int i = 0;
for(i=0;i<3;i++){
printf("%d\n", *(*(p+i)));
// printf("%d\n", *p[i]); 也可以这样写
}
return 0;
}
// 输出
>>1
>>2
>>3
数组指针
数组指针的根本是指针,即:一个指向数组的指针
定义:int (*p)[];
按照优先级顺序,*p
是一个整体,所以可以看作int *p
—> []
示例:
#include <stdio.h>
int main(int argc, char **argv)
{
int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int (*p)[3] = a; // 定义一个数组指针,并且初始化
int i = 0, j = 0;
int row = 3, col = 3;
for(i=0; i<row; i++){
for(j=0; j<col; j++){
printf("%d ", *(*(p+i)+j));
// printf("%d ", (*(p+i))[j]); 也可以这样写
}
}
return 0;
}
// 输出
>>1 2 3 4 5 6 7 8 9
指针常量
指针常量表示:这个指针为“只读”(从始至终只能指向一个地址,可以通过指针运算符来修改所指向的地址上的数据,一定在定义的时候赋初值)
定义:int *const p;
指针(*)常量(const),也就是说指针运算符在const的前面;const修饰的是p,所以p不能被赋值
#include <stdio.h>
int main(int argc, char **argv)
{
int a = 1;
int *const p = &a;
printf("%d\n", a);
*p = 2;
printf("%d\n", a);
return 0;
}
// 输出
>>1
>>2
常量指针
常量指针表示:这个指针指向的地址上的数据为“只读”(该指针可以随意更改指向的地址,但不能通过取地址运算符来修改所指向的地址上的数据)
定义:int const *p;
常量(const)指针(*),也就是说,const在指针运算符的前面;并且可以看做const 修饰的是*p,所以*p不能被赋值
示例:
#include <stdio.h>
int main(int argc, char **argv)
{
int const *p = NULL;
int a = 1, b = 2;
p = &a;
printf("%d\n", *p);
p = &b;
printf("%d\n", *p);
return 0;
}
// 输出
>>1
>>2
字符指针数组和二维字符数组
字符指针数据,也就是一个数组,里边的成员都是char *的指针,即:char *str[] = {"...", "...", ...};
二维字符数组,一个二维数组,里边成员都是char类型的字符,即:char str[][n] = {"...", "...", ...};
#include <stdio.h>
int main(int argc, char **agrv)
{
char *names[] = {"Tom", "Alice", "Bob"};
int i = 0;
for(i=0; i<3; i++){
printf("%s ", *(names+i));
// printf("%s ", names[i]); 也可以这样写
}
printf("\n");
char hobbies[][10] = {"swimming", "sleeping", "reading"};
for(i = 0; i<3; i++){
printf("%s ", *(hobbies+i));
// printf("%s ", hobbies[i]); 也可以这样写
}
printf("\n");
return 0;
}
// 输出
>>Tom Alice Bob
>>swimming sleeping reading
在使用上,二者是没差的,但从内存上分析就存在区别。
字符指针数组中的成员都是一个char *的指针,也就是说,它们都是地址,真正的字符串存放在常量区;二维字符数组里边存放的都是一个个char类型的字符。建议使用前者,因为字符指针数组存储字符串要比二维字符数组开销小。
连连看
我记得有一个送命游戏叫做:韩国女星连连看。其实对我重度脸盲患者,岂止韩国女星,即便是现在的许多大陆女星,我也着实难分辨(其实现在的新兴男星我也基本分不清了)。好在能不能区分出明星还是其次,重要的是区分以下的表达式。如果能够轻易识别,指针也算入门了。
int *p[n];
int (*p)[n];
int (*p)(int, int);
int (*p[])(int, int);
int p(int, int);
int *p(int, int);
int const *p;
int *const p;
int const * const p;