先声明一下, 这文章不是我写的, 是我转载的别人的文章,写的很好, 这里只是给自己记个笔记 。 指针这一节是本书中最难的一节,尤其是二级指针和二维数组直接的关系。 本节知识点: 1.指针基础,一张图说明什么是指针: 2.跨过指针,直接去访问一块内存: 只要你能保证这个地址是有效的 ,就可以这样去访问一个地址的内存*((unsigned int *)(0x0022ff4c))=10; 但是前提是 0x0022ff4c是有效地址。对于不同的编译器这样的用法还不一样,一些严格的编译器,当你定义一个指针,把这个指针赋值为一个这样的地址的时候,当检测到地址无效,编译的时候就会报错!!!如果一些不太严格的编译器,不管地址有效无效都会编译通过,但是对于无效地址,当你访问这块地址的时候,程序就会运行停止! 3.a &a &a[0]三者的区别: 首先说三者的值是一样的,但是意义是不一样的。(这里仅仅粗略的说说,详细见文章 &a[0]:这个是数组首元素的地址 a : 的第一个意义是 数组首元素的地址,此时与&a[0]完全相同 &a :这个是数组的地址 。跟a的区别就是,a是一个 int* 的指针(在第一种意义的时候) ,而&a是一个 int (*p)[5]类型的数组指针,指针运算的结果不一样。(此处的int* 仅仅是为了举例子,具体应该视情况而定) 4.指针运算(本节最重要的知识点,但并不是最难的,所以的问题都来源于这儿): 对于指针的运算,首先要清楚的是指针类型(在C语言中,数据的类型决定数据的行为),然后对于加减其实就是对这个指针的大小加上或者减去,n*sizeof(这个指针指向的数据的类型)。即:一个类型为T的指针的移动,是以sizeof(T)为单位移动的。如:int* p; p+1就是p这个指针的值加上sizeof(int)*1,即:(unsigned int)p + sizeof(int)*1。对于什么typedef的,struct的,数组的都是一样的。 这个有一个例子,代码如下: #include #include int main(int argc, char *argv[]) { /* int a[20]={1,2,4}; printf('%d\n',sizeof(a)); printf('%p\n',a); printf('%p\n',&a); printf('%p\n',&a[0]); */ /* int a[5]={1,2,3,4,5}; int (*p)[5]=&a; printf('%d\n',*((int *)(p+1)-1)); */
int a[5]={1,2,3,4,5}; int* p=(int *)(&a+1); // int *p=&a+1; //这个条语句是 把&a这个数组指针 进行了指针运算后 的那个地址 强制类型转换成了 int *指针 printf('%d\n',*(p-1)); return 0;
} 5.访问指针和访问数组的两种方式: 分别是以下标方式访问和以指针的方式访问,我觉得没有任何区别,*(p+4)和p[4]是一样的 ,其实都可以理解成指针运算。如果非要说出区别,我觉得指针的方式会快些,但是在当前的硬件和编译器角度看,不会太明显。同样下标的方式可读性可能会高些。 6.切记数组不是指针: 但是为什么我们会经常弄混呢?第一,我们常常利用指针的方式去访问数组。第二,数组作为函数参数的时候,编译器会把它退化成为指针,因为函数的参数是拷贝,如果是一个很大的数组,拷贝是很浪费内存的,所以数组会被退化成指针(这里一定要理解好,退化的是数组成员的类型指针,不一定是数组指针的哈)。 7.弄清数组的类型: 数组类型是由数组元素类型和数组长度两个因素决定的,这一点在数组中体现的不明显,在数组指针的使用中体现的很好。 char a[5]={'a','b','c','d','e'}; char (*p)[3]=&a; 上面的代码是错误的,为什么?因为数组指针和数组不是一个类型,数组指针是指向一个数组元素为char 长度为3的类型的数组的,而这个数组的类型是数组元素是char长度是5,类型不匹配,所以是错的。 8.字符串问题: a.C语言中没有真正的字符串,是用字符数组模拟的,即:字符串就是以'\0'结束的字符数组。 b.要注意下strlen,strcmp等这个几个函数的返回值,是有符号的还是无符号的,这里很容易忽略返回值类型,造成操作错误。 c.使用一条语句实现strlen,代码如下(此处注意assert函数的使用,安全性检测很重要): #include #include int strlen(const char* s) { return ( assert(s), (*s ? (strlen(s+1) + 1) : 0) ); } int main() { printf('%d\n', strlen( NULL));
return 0; } d.自己动手实现strcpy,代码如下: #include #include char* strcpy(char* dst, const char* src) { char* ret = dst;
assert(dst && src);
while( (*dst++ = *src++) != '\0' );
return ret; } int main() { char dst[20]; printf('%s\n', strcpy(dst, 'hello!'));
return 0; } e.推荐使用strncpy、strncat、strncmp这类长度受限的函数(这些函数还能在字符串后面自动补充'\0'),不太推荐使用strcpy、strcmpy、strcat等长度不受限仅仅依赖于'\0'进行操作的一系列函数,安全性较低。 #include int main() { char* p ='phello'; char a[256] = 'aworld'; char b[25] = {'b','b','c','d'}; char (*q)[256]=&a;
printf('%p\n',a); //0022fe48 //printf('%p\n',&a); //printf('%p\n',&a[0]);
printf('tian %s\n',(0x22fe48)); printf('%s\n',q); //q就是&a printf('%s\n',*q); //q就是a
printf('%s\n',p);
printf('%s\n',a); printf('%s\n',&a); printf('%s\n',&a[0]);
printf('%s\n',b); printf('%s\n',&b); printf('%s\n',&b[0]); } 对于上面的代码:中的0x22fe48是根据打印a的值获得的。 printf('tian %s\n',(0x22fe48));这条语句,可以看出来printf真的是不区分类型啊,完全是根据%s来判断类型。后面只需要一个值,就是字符串的首地址。a、&a、&a[0]三者的值还恰巧相等,所以说三个都行,因为printf根本就不判断指针类型。虽然都行但是我觉得要写有意义的代码,所以最好使用a和*p。还有一个问题就是,char* p = 'hello'这是一个char*指针指向hello字符串。所以对于这种方式只能使用p。因为*p是hello字符串的第一个元素,即:‘h’,&p是char* 指针的地址,只有p是保存的hello字符串的首地址,所以只有p可以,其他都不可以。scanf同理,因为&a和a的值相同,且都是数组地址。 9.二维数组(本节最重要的知识点): a.对于二维数组来说,二维数组就是一个一维数组 数组,每一个数组成员还是一个数组,比如int a[3][3],可以看做3个一维数组,数组名分别是a[0] a[1] a[2] sizeof(a[0])就是一维数组的大小 ,*a[0]是一维数组首元素的值,&a[0]是 一维数组的数组指针。 b.也可以通过另一个角度看这个问题。a是二维数组的数组名,数组元素分别是数组名为a[0]、a[1]、a[2]的三个一维数组。对a[0]这个数组来说,它的数组元素分别是a[0][0] a[0][1] 、 a[0][2]三个元素。a和a[0]都是数组名,但是是两个级别的,a作为数组首元素地址的时候等价于&a[0](最容易出问题的地方在这里,这里一定要弄清此时的a[0]是什么,此时的a[0]是数组名,不是数组首元素的地址,不可以继续等价下去了,千万不能这样想 a是&a[0] a[0]是&a[0][0] a就是&&a[0][0] 然后再弄个2级指针出来,自己就蒙了!!!这是一个典型的错误,首先&&a[0][0]就没有任何意义,跟2级指针一点关系都没有,然后a[0]此时不代表数组首元素地址,所以这个等价是不成立的。Ps:一定要搞清概念,很重要!!! ),a[0]作为数组首元素地址的时候等价于&a[0][0]。但是二维数组的数组头有很多讲究,就是a(二维数组名)、&a(二维数组的数组地址)、&a[0](二维数组首元素地址 即a[0]一维数组的数组地址 a有的时候也表示这个意思)、a[0](二维数组的第一个元素 即a[0]一维数组的数组名)、&a[0][0](a[0]一维数组的数组首元素的地址 a[0]有的时候也表示这个意思),这些值都是相等,但是他们类型不相同,行为也就不相同,意义也不相同。分析他们一定要先搞清,他们分别代表什么。 下面是一个,二维数组中指针运算的练习(指针运算的规则不变,类型决定行为): #include #include #include int main(int argc, char *argv[]) { int a[3][3]={1,2,3,4,5,6,7,8,9}; printf('%d\n',sizeof(a[0])); printf('%d\n',*a[2]); printf('%d\n',*(a[0]+1));
printf('%p\n',a[0]); printf('%p\n',a[1]); printf('%p\n',&a[0]+1); //&a[0]+1 跟 a[1]不一样 指针类型不一样 &a[0]+1这个是数组指针 a[1]是&a[1][0] 是int*指针
printf('%d\n',*((int *)(&a[0]+1)));
printf('%d\n',*(a[1]+1));
printf('%p\n',a); printf('%p\n',&a); printf('%p\n',&a[0]);
printf('%d\n',sizeof(a)); //这是a当作数组名的时候
printf('%d\n',*((int *)(a+1))); //此时 a是数组首元素的地址 数组首元素是a[0] //首元素地址是&a[0] 恰巧a[0]是数组名 &a[0]就变成了数组指针 return 0; } 总结:对于a和a[0]、a[1]等这些即当作数组名,又当作数组首元素地址,有时候还当作数组元素(即使当作数组元素,也无非就是当数组名,当数组首元素地址两种),这种特殊的变量,一定要先搞清它现在是当作什么用的。 c.二维数组中一定要注意,大括号,还是小括号,意义不一样的。 10.二维数组和二级指针: 很多人看到二维数组,都回想到二级指针,首先我要说二级指针跟二维数组毫无关系,真的是一点关系都没有。通过指针类型的分析,就可以看出来两者毫无关系。不要在这个问题上纠结。二级指针只跟指针数组有关系,如果这个二维数组是一个二维的指针数组,那自然就跟二级指针有关系了,其他类型的数组则毫无关系。切记!!!还有就是二级指针与数组指针也毫无关系!! 11.二维数组的访问: 二维数组有以下的几种访问方式: int a[3][3];对于一个这样的二位数组 a.方式一:printf('%d\n',a[2][2]); b.方式二:printf('%d\n',*(a[1]+1)); c.方式三:printf('%d\n',*(*(a+1)+1)); d.方式四:其实二维数组在内存中也是连续的,这么看也是一个一维数组,所以就可以使用这个方式,利用数组成员类型的指针。 int *q; q = (int *)a; printf('%d\n',*(q+6)); e.方式五:二维数组中是由多个一维数组组成的,所以就可以利用数组指针来访问二维数组。 int (*p)[3]; p = a; printf('%d\n',*(*(p+1)+1)); 给一个整体的程序代码: #include #include #include int main() { int a[3][3]={1,2,3,4,5,6,7,8,9}; int (*p)[3]; int *q; printf('%d\n',*(*(a+1)+1)); //a *(&a[0]+1) p = a; q = (int *)a; printf('%d\n',*(*(p+1)+1)); printf('%d\n',*(a[1]+1)); printf('%d\n',a[1][1]); printf('%d\n',*(q+6)); }
总结:对于二位数组int a[3][3] 要想定义一个指针指向这个二维数组的数组元素(即a[0]等一维数组),就要使用数组指针,这个数组指针要跟数组类型相同。a[0]等数组类型是元素类型是int,长度是3,所以数组指针就要定义成int (*p)[3]。后面的这个维度一定要匹配上,不然的话类型是不相同的。 这里有一个程序,要记得在c编译器中编译,这个程序能看出类型相同的重要性: #include int main() { int a[5][5]; int(*p)[4];
p = a;
printf('%d\n', &p[4][2] - &a[4][2]); } 12.二级指针: a.因为指针同样存在传值调用和传址调用,并且还有指针数组这个东西的存在,所以二级指针还是有它的存在价值的。 b.常使用二级指针的地方: (1)函数中想要改变指针指向的情况,其实也就是函数中指针的传址调用,如:重置动态空间大小,代码如下: #include #include int reset(char**p, int size, int new_size) { int ret = 1; int i = 0; int len = 0; char* pt = NULL; char* tmp = NULL; char* pp = *p;
if( (p != NULL) && (new_size > 0) ) { pt = (char*)malloc(new_size);
tmp = pt;
len = (size < new_size) ? size : new_size;
for(i=0; i { *tmp++ = *pp++; } free(*p); *p = pt; } else { ret = 0; } return ret; } int main() { char* p = (char*)malloc(5); printf('%0X\n', p); if( reset(&p, 5, 3) ) { printf('%0X\n', p); } return 0; } (3)定义一个指针指向指针数组的元素的时候,要使用二级指针。 c.指针数组:char* p[4]={'afje','bab','ewrw'}; 这是一个指针数组,数组中有4个char*型的指针,分别保存的是'afje'、'bab'、'ewrw'3个字符串的地址。p是数组首元素的地址即保存'afje'字符串char*指针的地址。 #include #include #include int main(int argc, char *argv[]) { char* p[4]={'afje','bab','ewrw'}; char* *d=p; printf('%s\n',*(p+1)); printf('%s\n',*(d+1)); //d &p[0] p[0]是'afje'的地址,所以&p[0]是保存'afje'字符串的char*指针的地址 return 0; } d.子函数malloc,主函数free,这是可以的(有两种办法,第一种是利用return 把malloc的地址返回。第二种是利用二级指针,传递一个指针的地址,然后把malloc的地址保存出来)。记住不管函数参数是,指针还是数组, 当改变了指针的指向的时候,就会出问题,因为子函数中的指针就跟主函数的指针不一样了,他只是一个复制品,但可以改变指针指向的内容。这个知识点可以看<在某培训机构的听课笔记>这篇文章。 13.数组作为函数参数:数组作为函数的实参的时候,往往会退化成数组元素类型的指针。如:int a[5],会退化成int* ;指针数组会退化成二级指针;二维数组会退化成一维数组指针;三维数组会退化成二维数组指针(三维数组的这个是我猜得,如果说错了,希望大家帮我指出来,谢谢)。如图: 二维数组作为实参的例子: #include #include #include int fun(int (*b)[3]) //此时的b为 &a[0] { printf('%d\n',*(*(b+1)+0)); printf('%d\n',b[2][2]);// b[2][2] 就是 (*(*(b+2)+2)) printf('%d\n',*(b[1]+2)); } int main(int argc, char *argv[]) { int a[3][3]={1,2,3,4,5,6,7,8,9}; fun(a);//与下句话等价 fun(&a[0]); return 0; } 数组当作实参的时候,会退化成指针。指针当做实参的时候,就是单纯的拷贝了! 14.函数指针与指针函数: a.对于函数名来说,它是函数的入口,其实函数的入口就是一个地址,这个函数名也就是这个地址。这一点用汇编语言的思想很容易理解。下面一段代码说明函数名其实就是一个地址,代码如下: #include #include #include void abc() { printf('hello fun\n'); } int main(int argc, char *argv[]) { void (*d)(); void (*p)(); p = abc; abc(); printf('%p\n',abc); printf('%p\n',&abc);//函数abc的地址0x40138c p(); (*p)(); d = ((unsigned int*)0x40138c); //其实就算d= 0x40138c这么给赋值也没问题 d(); return 0; } 可见函数名就是一个地址,所以函数名abc与&abc没有区别,所以p和*p也没有区别。 b.我觉得函数指针最重要的是它的应用环境,如回调函数(其实就是利用函数指针,把函数当作参数进行传递)代码如下,还有中断处理函数(同理)详细见< ok6410学习笔记(16.按键中断控制led)>中的 中断注册函数,request_irq。还有就是函数指针数组,第一次见到函数指针数组是在zigbee协议栈中。 回调函数原理代码: #include typedef int(*FUNCTION)(int); int g(int n, FUNCTION f) { int i = 0; int ret = 0; for(i=1; i<=n; i++) { ret += i*f(i); } return ret; } int f1(int x) { return x + 1; } int f2(int x) { return 2*x - 1; } int f3(int x) { return -x; } int main() { printf('x * f1(x): %d\n', g(3, f1)); printf('x * f2(x): %d\n', g(3, &f2)); printf('x * f3(x): %d\n', g(3, f3)); } 注意:可以使用函数名f2,函数名取地址&f2都可以,但是不能有括号。 c.所谓指针函数其实真的没什么好说的,就是一个返回值为指针的函数而已。 15.赋值指针的阅读: a.char* (*p[3])(char* d); 这是定义一个函数指针数组,一个数组,数组元素都是指针,这个指针是指向函数的,什么样的函数参数为char* 返回值为char*的函数。 分析过程:char (*p)[3] 这是一个数组指针、char* p[3] 这是一个指针数组 char* 是数组元素类型、char* p(char* d) 这个是一个函数返回值类型是char* 、char (*p)(char* d)这个是一个 函数指针。可见char* (*p[3])(char* d)是一个数组 数组中元素类型是 指向函数的指针,char* (* )(char* d) 这是函数指针类型,char* (* )(char* d) p[3] 函数指针数组 这个不好看 就放里面了。(PS:这个看看就好了~~~当娱乐吧) b.函数指针数组的指针:char* (*(*pf)[3])(char* p) //这个就看看吧 我觉得意义也不大 因为这个逻辑要是一直下去 就递归循环了。 分析过程:char* (* )(char *p) 函数指针类型,char* (*)(char *p) (*p)[3] 函数指针 数组指针 也不好看 就放里面了 |
|