本文的题目其实有点蹭Python热度的意思,指针确实很灵活,当然易用程度和Python还是没得比,想要灵活使用指针,仍然要花大工夫。但一旦你感受到了指针的真谛,你也就自然能够感受到指针的灵活了!本文通过字符串这一载体,为指针打个call。
1.数组和指针
为什么先要介绍下数组和指针,因为在C语言中,并没有字符串类型,字符串往往是搭载在字符数组,和指针中的。因此在开篇对数组和指针的异同做个简单的介绍。其实数组和指针我们都可以认为和int,float一样是一种变量类型。
1.数组类型:固定大小内存块的别名,数组名是保存该内存块首地址的变量类型。
2.指针类型:保存某一内存的地址的变量类型。
其实听上去好像数组名和指针没什么区别。确实,大部分情况下,数组名的用法和指针是一样的,如下代码所示。
void main()
{
int i = 0;
char *p = NULL;
char buf5[128] = "ylylylylyl"; // buf
for (i=0; i<strlen(buf5); i++)
{
printf("%c ", buf5[i]);
}
p = buf5; //buf 代表数组首元素的地址
for (i=0; i<strlen(buf5); i++)
{
p = p +i;
printf("%c ", *p ) ;
}
//buf
for (i=0; i<strlen(buf5); i++)
{
printf("%c ", *(buf5+i) ) ;
}
//[] *的推导过程
// buf5[i] ===> buf5[0+i]; ==> *(buf5+i);
printf("hello....\n");
system("pause");
}
如上述代码所示,我们可以发现数组名和指针有两个共同点:
1.[]的本质 和*p 是一样 ,只不过是符合程序员的阅读习惯。
2.读取数据时,指针和数组名的用法一致。
但需要注意的是数组名有一个特性,为常量指针,原因在于数组类型是局部变量,最终编译器需要自动释放内存空间,所以其首地址指向是不能变的,一旦变了就不能释放了。而指针没有这个限定。如下代码所示,若是改变数组名的指向,程序是会报错的。
{
buf5 = buf5 + 1;
buf5 = 0x11;
}
2.字符串
上述已经介绍了数组和指针的异同,接下来进入正题。我们将通过字符串,来进一步论述下数组和指针在操作时内存模型的区别。
void main61()
{
char buf[20]= "yy"; //定义并且初始化
char buf2[] = "ll";
char *p1 = "jj";
char *p2 = malloc(100);
strcpy(p2, "mm");
return ;
}
如上述代码和对应的内存四区模型所示,可以很清晰的看到数组和指针在操纵字符串时的区别。很显然的是指针操纵字符串将会更方便。
Note:在上述内存四区模型中,我们可以看到每个字符串后面有一个“/0”,该字符是每个字符串都会有的,表示字符串结束的标志。而基于此,需要对字符串的长度和对应的存放字符串的字符数组的长度作区分。如下代码所示,很显然字符数组的长度要比对应存放的字符串长度大。strlen 和 sizeof,其实sizeof求得的是计算机感知到的长度,strlen得到的是一种人类理解上的长度。
#strlen() 长度 不包括0
#sizeof() 内存块的大小
void main()
{
int size = 0;
char buf3[] = "abcd";
int len = strlen(buf3);
printf("buf3字符的长度:%d \n", len); #输出4
size = sizeof(buf3);
printf("buf3数组所占内存空间大小:%d \n", size); #输出5
return ;
}
3.指针是如何高效操纵字符串的
利用指针实现字符串的拷贝。如下述代码所示,利用指针,一句话while ( *tmpto++ = *tmpfrom++ ) ;
就能实现这个功能(其余代码都是为了代码的接口封装,和代码的稳健性所存在的。)
//不要轻易改变形参的值, 要引入一个辅助的指针变量. 把形参给接过来.....
int copy_str26_good(char *from , char *to)
{
char *tmpfrom = from;
char *tmpto = to;
if ( from == NULL || to == NULL)
{
return -1;
}
while ( *tmpto++ = *tmpfrom++ ) ; //空语句
return 0;
}
int main()
{
int ret = 0;
char *from = "abcd";
char buf2[100];
ret = copy_str26_good(from, buf2);
if (ret != 0)
{
printf("func copy_str26_good() err:%d ", ret);
return ret;
}
return ret;
}
利用指针实现从对应的长字符串中,找出短字符串出现的次数。
//求字符串p中子串 abcd出现的次数
int getCount(char *mystr /*in*/, char *sub /*in*/,int *ncount)
{
int ret = 0;
int tmpCount = 0;
char *p = mystr; //不要轻易改变形参的值,因此把形参接过来
//防止程度挂掉
if (mystr==NULL || sub==NULL ||ncount==NULL)
{
ret = -1;
printf("func getCount() err:%d (mystr==NULL || sub==NULL ||ncount==NULL) \n", ret);
return ret;
}
do
{
p = strstr(p, sub);
if (p != NULL)
{
tmpCount++;
p = p + strlen(sub); //指针达到下次查找的条件
}
else
{
break;
}
} while (*p != '\0');
*ncount = tmpCount; //间接赋值是指针存在的最大意义
return ret;
}
int main()
{
int ret = 0;
char *p = "abcd111122abcd3333322abcd3333322qqq";
int count = 0;
char sub[] = "abcd";
ret = getCount(p,sub, &count);
if (ret != 0)
{
printf("func getCount() err:%d \n", ret);
return ret;
}
//若在子函数中不对输入指针形参进行判断,则下述代码就会挂掉
ret = getCount(p,NULL, &count);
if (ret != 0)
{
printf("func getCount() err:%d \n", ret);
return ret;
}
printf("count:%d \n", count);
}
总之,如果你对指针操作数据时的内存模型有一个清晰的认知,那么你用起指针来将会溜的飞起。。。而且相较与python而言,掌控感更强。当然就笔者的使用经验来说,如果你仅仅是想开发上述例子中的对应功能,或者是leetcode刷题,建议使用python。如果你对内存指针感兴趣,未来又可能会从事面向设备的编程,建议在空闲时间深入学习下指针。(我在学校搞深度学习以及刚开始工作练模型的时候也没想到我居然会慢慢的对指针这玩意这么感兴趣。。。)