本文的题目其实有点蹭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
{
printf("%c ", buf5[i]);
}
p = buf5; //buf 代表数组首元素的地址
for (i=0; i
{
p = p +i;
printf("%c ", *p ) ;
}
//buf
for (i=0; 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。如果你对内存指针感兴趣,未来又可能会从事面向设备的编程,建议在空闲时间深入学习下指针