C语言自学完备手册(30)——指针(4)

版权声明: https://blog.csdn.net/lfdfhl/article/details/83316474

自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


探索Android软键盘的疑难杂症
深入探讨Android异步精髓Handler
详解Android主流框架不可或缺的基石
站在源码的肩膀上全解Scroller工作机制


Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南


讲给Android程序员看的前端系列教程(图文版)
讲给Android程序员看的前端系列教程(视频版)
Android程序员C语言自学完备手册


版权声明


在本篇博客中介绍字符串和指针的密切关系。

字符串的两种实现方式

字符串常见的实现方式有如下两种:

  • 方式1:利用数组实现字符串
  • 方式2:利用指针实现字符串

请看如下示例:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    //方式1:利用数组实现字符串
    char str[]="ABC";
    //方式2:利用指针实现字符串
    char *ptr="123";
    printf("str=\"%s\",sizeof(str)=%d\n",str,sizeof(str));
    printf("ptr=\"%s\",sizeof(ptr)=%d,sizeof(\"123\")=%d\n",ptr,sizeof(ptr),sizeof("123"));

    return 0;
}

在本示例中用两种方式实现了字符串。

结果如下:

str="ABC",sizeof(str)=4
ptr="123",sizeof(ptr)=4,sizeof("123")=4

图示如下:
在这里插入图片描述

在示例代码和图示之后,我们再来分别深入地学习这两种字符串的实现方式。

利用数组实现字符串

在方式中,str是char[4]型的数组,各元素从头开始依次使用’A’、‘B’、‘C’、’\0’进行初始化。在该方式中,数组str占用4个字节的内存空间。
既然是数组,那么可通过下标的方式访问元素,例如:

printf("str[0]=%c\n",str[0]);//结果为:str[0]=A

请看如下示例:请遍历字符串中的每个字符

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int i=0;
    char str[]="123";
    while(str[i]){
        printf("%c\n",str[i++]);
    }
    return 0;
}

结果如下:

1
2
3

在此思考一个问题:*p的含义是什么?或者说*p的值是多少?请看如代码:

char str[]="123";
char *p=str;
printf("*p=%c\n",*p);//*p=1

*p指向了字符数组中的第一个元素,它的值是1。所以,还有较刚才更为简单巧妙的方法:利用指针的自增或自减来访问当前元素的下一个或者上一个元素,代码修改如下:

int main()
{
    char str[]="123";
    char *p=str;
    while(*p){
        printf("%c\n",*p++);
    }
    return 0;
}

了解了元素的访问,再来看元素的修改。当利用数组实现字符串时可通过下标修改元素;请看如下示例:

int main()
{

    char str[]="123";
    str[0]='4';
    printf("str[0]=%c\n",str[0]);//结果为:str[0]=4
    return 0;
}

但是,一定要注意:在该方式中不可以再次为str赋值,例如:str="DEF";,这一点在之前我们也反复强调过:赋值表达式的左侧不可为数组名! 虽然说左侧的数组名会被解释为数组起始元素的地址,但是依然不可以改写其值。其实,也可以反向思考一下:如果可以赋值,那岂不是就改变了原数组在内存中的地址了?这是不可以的。

利用指针实现字符串

在该方式中,ptr是指向char型变量的指针,它的初始值为字符串字面量"123"并指向该字面量的第一个字符。从图示中,我们也可以看出来指针ptr和字符串字面量“123”都会占用内存空间(即sizeof(ptr)=4,sizeof("123")=4)。也就是说:对于同一个字符串而言,用指针实现的字符串比用数组实现的字符串需要更多的内存空间。

接下来,我们来看看数据的访问。
在利用指针实现字符串的方式中依然可以采用下标的方式访问字符串。

请看如下示例:请遍历字符串中的每个字符

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int i=0;
    char *ptr="123";
    while(ptr[i]){
        printf("%c\n",ptr[i++]);
    }
    return 0;
}

结果如下:

1
2
3

按照之前的思路继续优化代码,修改如下:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char *ptr="123";
    while(*ptr){
        printf("%c\n",*ptr++);
    }
    return 0;
}

在该示例中直接使用了*ptr++让指针不断指向下一个元素。

好嘞,既然之前可以使用下标的方式访问元素,那么就继续通过该方式修改元素,例如:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char *ptr="123";
    ptr[0]='9';
    printf("ptr[0]=%c\n",ptr[0]);
    return 0;
}

运行代码可以发现:程序不能够正常执行!这是为什么呢?在利用指针实现的字符串时,该字符串常量被定义为只读;如果试图通过指针修改这个字符串的值程序会出现未定义的异常行为!也就是说:在利用指针实现字符串时可通过指针访问字符串字面量中的内容,但是不能通过指针修改字符串字面量中的内容!

另外,在利用指针实现字符串时指针(例如ptr)不可进行如下声明:

char *ptr={'1','2','3','\0'};

因为数组采用的{'1','2','3','\0'}形式的初始值,它不能够被赋值给单一的变量(例如指针ptr),应该被赋值给arrayName[ ];这点是大家容易犯错的。

在利用指针实现字符串时可以再次为指针(例如指针p)赋值么?答案是肯定的。例如:

char *p="123";
p="456";

指针p原本指向字符串字面量“123”之后被再次赋值,指向了新的字符串字面量“456”,此时原来的“123”并不会消失,但变成了无用的内存垃圾;图示如下:
在这里插入图片描述

练习1:求字符串长度

代码如下:

#include <stdio.h>
#include <stdlib.h>

int getStringLength(const char *p){
    int len=0;
    while(*p++){
        len++;
    }
    return len;
}

int main()
{
    char str[128];
    printf("请您输入字符串:");
    scanf("%s",str);
    printf("字符串%s的长度是%d\n",str,getStringLength(str));
    return 0;
}

结果如下:

请您输入字符串:asdf
字符串asdf的长度是4

练习2:复制键盘输入的字符串

代码如下:

#include <stdio.h>
#include <stdlib.h>

char* copyString(char *destination,char *source){
    char *temp=destination;
    while(*destination++ = *source++){

    }
    return temp;
}

int main()
{
    char source[128];
    char destination[128];
    printf("请您输入需要复制的字符串:");
    scanf("%s",source);
    printf("需要复制的字符串source=%s\n",source);
    puts("开始复制");
    puts("复制结束");
    printf("复制得到的字符串destination=%s",copyString(destination,source));
    return 0;
}

结果如下:

请您输入需要复制的字符串:qwert
需要复制的字符串source=qwert
开始复制
复制结束
复制得到的字符串destination=qwert

在本示例中充分运用了之前所讲的知识点——利用指针的自增或自减来访问当前元素的下一个或者上一个元素。


字符串数组

之前介绍了实现字符串的两种方式,现在在该基础上学习实现字符串数组的两种方式。

字符串常见的实现方式有如下两种:

  • 方式1:利用二维字符数组实现字符串数组
  • 方式2:利用char*型指针数组实现字符串数组

请看如下示例:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int i;
    //方式1:利用二维字符数组实现字符串数组
    char a[][5]={"LISP","C","Ada"};
    //方式2:利用char*型指针数组实现字符串数组
    char *p[]={"PAUL","X","MAC"};
    printf("字符串数组a所占内存大小为=%d\n",sizeof(a));
    for(i=0;i<3;i++){
        printf("a[%d]=%s\n",i,a[i]);
    }
    printf("字符串数组p所占内存大小为=%d\n",(sizeof(p)+sizeof("PAUL")+sizeof("X")+sizeof("MAC")));
    for(i=0;i<3;i++){
        printf("p[%d]=%s\n",i,p[i]);
    }
    return 0;
}

结果如下:

字符串数组a所占内存大小为=15
a[0]=LISP
a[1]=C
a[2]=Ada
字符串数组p所占内存大小为=23
p[0]=PAUL
p[1]=X
p[2]=MAC

图示如下:
在这里插入图片描述

在示例代码和图示之后,我们再来分别深入地学习这两种字符串数组的实现方式。

利用二维字符数组实现字符串数组

在该示例中,数组a是3行5列的二维字符数组,所占内存为15字节。因为并非所有字符串的长度是一致的,所以该数组中会存在一些未使用的部分,例如a[1][2]–a[1][4]。
很多书中都会讲到二维数组,但是都缺乏一个形象的示图。庆幸的是《明解C语言(第3版)》的321页给出了一张非常形象的插图,如上所示。由此可见:在利用二维字符数组实现字符串数组时,所有元素是连续排列的! 即:

  • 字符串数组中的“LISP”里的每个字符以及‘\0’分别存放于a[0][0]、a[0][1]、a[0][2]、a[0][3]、a[0][4]
  • 字符串数组中的“C”里的每个字符以及‘\0’分别存放于a[1][0]、a[1][1]、a[1][2]、a[1][3]、a[1][4]
  • 字符串数组中的“Ada”里的每个字符以及‘\0’分别存放于a[2][0]、a[2][1]、a[2][2]、a[2][3]、a[2][4]

利用char*型指针数组实现字符串数组

在该示例中指针p是元素类型为char*、元素个数为3的数组。数组中各元素p[0]、p[1]、p[2]的初始值是分别指向各字符串字面量的首字符“P”、“X”、“M”的指针。因此,除了字符串占用的11字节的内存以外;数组还p占用即12(3*4)个字节的内存。至于,字符串数组在内存中分配请详细查看上图即可。从这里我们也能发现:利用char*型指针数组实现字符串数组时无法保证字符串排列的顺序和连续性!

猜你喜欢

转载自blog.csdn.net/lfdfhl/article/details/83316474