本文共 5480 字,大约阅读时间需要 18 分钟。
1、指针也是一种数据类型
指针是一种数据类型,是指它指向的内存空间的数据类型;
*号表示 操作 指针所指向的内存空间中的值;
*p相当于通过地址(p变量的值)找到一块内存;然后操作内存; *p放在等号的左边赋值(给内存赋值); *p放在等号的右边取值(从内存获取值);不断的给指针变量赋值,就是不断的改变指针变量(和所指向内存空间没有任何关系);
指针做函数参数 形参有多级指针的时候, 站在编译器的角度 ,只需要分配4个字节的内存(32bit平台),当我们使用内存的时候,我们才关心 指针所指向的内存 是一维的还是二维的。
2、指针指向某个变量,就是把某个变量地址赋给指针;
指针变量 和 它指向的内存块变量 是两个不同的概念;释放了指针所指的内存空间,但是如果指针变量没有重置成NULL,则会出现“野指针”的情况。
避免方法:(1)定义指针的时候,初始化成NULL;(2)释放指针所指向的内存空间以后,紧接着把指针重置成NULL。3、一级指针的典型用法:数组 int buf[10]、字符串
1).C语言的字符串 以零结尾的字符串
2).在C语言中没有字符串类型, 通过 字符数组 来模拟字符串 3).字符串的内存分配,堆上、栈上、全局区 (很重要) 4).注意:buf是一个指针,是一个只读的常量,也就是说buf是一个常量指针,不能别修改指向(也就是地址)。——这是必须的,也是显而易见的,为了保护数组的首地址,析构内存的时候,保证buf所指向的内存空间安全释放p = buf; //buf 数组首元素的地址 for (i=0; i5).字符串作函数参数
//不要轻易改变形参的值, 要引入一个辅助的指针变量. 把形参给接过来int copy_str26_good(char *from , char *to){ //*(0) = 'a'; char *tmpfrom = from; char *tmpto = to; if ( from == NULL || to == NULL) { return -1; } while ( *tmpto++ = *tmpfrom++ ) ; //tmpfrom在不断变化! //空语句 printf("from:%s \n", from); //打印成功!from未变化! }int main(){ int ret = 0; char *from = "abcd"; char buf2[100]; copy_str26_good(from, buf2); printf("copy_str25_err end\n"); return 0;}
应用场景:char *p = "abcdbcd123123cdacbdabcdabcdabcdaabcd"; ,求字符串p中 abcd出现的次数,1).请自定义函数接口,完成上述需求 ,2).自定义的业务函数 和 main函数分开。
int count(char *source, char *sub){ int count = 0; char *tmp_source = source;//不轻易改变形参的值 char *tmp_sub = sub; for (; *tmp_source != '\0'; tmp_source++)//循环检测原字符串 { char *temp = tmp_source;//记录原字符串匹配位置,以便复位 while (*tmp_source != '\0')//开始一趟匹配 { if (*tmp_source != *tmp_sub) { break; } tmp_source++; tmp_sub++; if (*tmp_sub == '\0') count++; } tmp_sub = sub;//复位 tmp_source = temp;//复位 } return count;}void main(){ char *p = "abcd111122abcd3333322abcd3333322qqq"; char buf[] = "abcd"; int sub_count = count(p, sub_str); printf("sub_str(abcd) count: %d\n", sub_count);}
void get_count(char *source, char *sub, int *count){ if (source == NULL || sub == NULL || count == NULL)//增强程序的健壮性 { printf("func getCount() err (source==NULL || sub==NULL || count==NULL) \n"); return -1; } char *tmp_sub = sub; for (; *source != '\0'; source++)//循环检测原字符串 { char *temp = source;//记录原字符串匹配位置,以便复位 while (*source != '\0')//开始一趟匹配 { if (*source != *tmp_sub) { break; } source++; tmp_sub++; if (*tmp_sub == '\0') (*count)++;//注意:不是*count++!++的优先级高于*。否则将会是地址移动!这里应该是对*count } tmp_sub = sub;//复位 source = temp;//复位 //这里有的程序优化为:当查找成功时,复位到+strlen(sub)。我觉得不合适!试想一下在“aaaa”中查找“aa”! } printf("sub_str(abcd) count: %d\n", *count);}void main(){ char *p = "abcd111122abcd3333322abcd3333322qqq"; char buf[] = "abcd"; int num = 0; get_count(p, buf, &num);}
4、二级指针:指向指针变量的指针,存放地址值的地址。一级指针所关联的是其值(一个地址)名下空间里的数据,这个数据可以是任意类型并做任意用途,但二级指针所关联的数据只有一个类型一个用途,就是地址。一级指针的值虽然是地址,但这个地址做为一个值也需要空间来存放,是空间就具有地址 ,这就是存放地址这一值的空间所具有的地址,二级指针就是为了获取这个地址。
例如:如果A、B、C都是变量,即C是普通变量,B是一级指针变量,其中存放着C的地址,A是二级指针变量,其中存放着B的地址,则这3个变量分别在内存中占据各自的存储单元,它们之间的相互关系下图所示,相互之间的前后位置关系并不重要.此时,B是一级指针变量,B的值(即C的地址)是一级指针数据;A是二级指针变量,A的值(即B的地址)是二级指针数据.
关于为什么要使用二级指针?参见:http://blog.csdn.net/hmsiwtv/article/details/7413168 \ http://blog.csdn.net/anna39/article/details/6769177 \ http://blog.csdn.net/mhjcumt/article/details/7351032
对上述参见资料的一些分析:
谨记:指针存放的是某变量的地址,但是同时指针变量又有存放自己的地址。
程序是想修改p的地址,所以要把p的地址作为实参扔给形参,所以形参应该是二级指针。
如果形参是一级指针,则在被调用函数中对该指针(形参)的地址修改一万次,对主调函数的实参指针都没有任何作用!
对于多级指针,只需从右向左一级一级分析即可。*号就像一把钥匙,通过后边的地址去操作存放在地址中的内容。
间接赋值:(形参指针级别需要高)
a.用 1级 指针作形参,去间接修改了 0级 指针(实参)的值; b.用 2级 指针作形参,去间接修改了 1级 指针(实参)的值; c.用 3级 指针作形参,去间接修改了 2级 指针(实参)的值; d.用 n级 指针作形参,去间接修改了 n-1级 指针(实参)的值;对于参见资料2,用函数返回值来传递动态内存,不要用return语句返回指向“栈内存”的指针,要返回一个“常量”指针或者一个“堆内存指针”。(详见上篇内存四区专题)
对于参见资料1,当使用一级指针的被调函数中不断修改pa的指向,在此时形参结合所发生的事:array得到了数组名为str, search得到了a的值, pa得到了p的值(而非p自身的地址)!但实参p并未得到形参pa传回的值(某元素的地址)。可见尽管使用了指针,也并没实现传址,当实参形参都是指针时,它们也仅仅是传值——传了“别人的”地址,没有传回来。正如我们以前所知的,想要在被调函数中修改主调函数中的某值,可以使用(一级)指针做函数参数。
但是在参见资料1中,我们的需求是在被调函数中修改主调函数中的某值的地址值,所以要使用二级指针——指向指针的指针。这样,我们在传递变量时find2(str, a, &p); 而非find1(str, a, p);,传递的是p的地址,而不是传递指针变量p所保存的地址(这不是p的地址)。void getMem(char **p2){ *p2 = 400; //间接赋值 p2是p1的地址}void getMem2(char *p2){ //char *p2;放在形参位置和放在函数这儿一样, p2 = 800; // 所以在此修改p2对主调函数中的p1没有任何影响}void main(){ char *p1 = NULL; char **p2 = NULL; p1 = 0x11; p2 = 0x22; //直接修改p1的值 p1 = 0x111; //间接修改p1的值 p2 = &p1; *p2 = 100; //间接赋值 p2是p1的地址 printf("p1:%d \n", p1); //{ // *p2 = 200; //间接赋值 p2是p1的地址 // printf("p1:%d \n", p1); //} getMem(&p1);//传递的是p1的地址 getMem2(p1);//传递的是p1保存的地址 printf("p1:%d \n", p1); system("pause"); return ;}应用场景:
int getMem3(char **myp1, int *mylen1, char **myp2, int *mylen2){ int ret = 0; char *tmp1, *tmp2; tmp1 = (char *)malloc(100);//原来p1指向NULL,现在要让p1指向新分配的堆内存 strcpy(tmp1, "1132233"); //间接赋值 *mylen1 = strlen(tmp1); //1级指针的间接赋值 *myp1 = tmp1; //2级指针的间接赋值 tmp2 = (char *)malloc(200);//原来p2指向NULL,现在要让p2指向新分配的堆内存 strcpy(tmp2, "aaaaavbdddddddd"); //间接赋值 *mylen2 = strlen(tmp2); //1级指针的间接赋值 *myp2 = tmp2; //2级指针的间接赋值 return ret;}int main(){ int ret = 0; char *p1 = NULL; int len1 = 0; char *p2 = NULL; int len2 = 0; ret = getMem3(&p1, &len1, &p2, &len2);//测试是否调用成功 if (ret != 0) { printf("func getMem3() err:%d \n", ret);//如果不成功返回错误码 return ret; } printf("p1:%s \n", p1); printf("p2:%s \n", p2); if (p1 != NULL)//释放内存 { free(p1); p1 = NULL; } if (p2 != NULL) { free(p2); p2 = NULL; } system("pause"); return ret;}函数调用时,形参传给实参,用实参取地址,传给形参,在被调用函数里面用*p,来改变实参,把运算结果传出来。——C语言 的精华。 间接赋值成立的三个条件: 条件1 //定义1个变量(实参) //定义1个变量(形参) 条件2 //建立关联:把实参取地址传给形参,实参可能是普遍变量,也有可能是指针变量。 条件3 //*形参去间接地的修改了实参的值。 //1 2 3 这3个条件 写在有一个函数 //12 写在一块,3 单独写在另外一个函数里面 ==>函数调用 //1 23写在一块 ==> C++会有