指针知识点总结

Posted on 2018-07-22 17:15  黑企鹅  阅读(226)  评论(0编辑  收藏  举报

最近接触不少C程序,对于指针的使用有很多,相关知识点的认识也有误区,网上搜了一下,综合一下备用。

char *s1 = "hello";
char s2[] = "hello";

【区别所在】

char *s1 的s1是指针变量,而指针是指向一块内存区域,它指向的内存区域的大小可以随时改变,而且当指针指向常量字符串时,它的内容是不可以被修改的,否则在运行时会报错。
char s2[]的s2 是数组对应着一块内存区域,其地址和容量在生命期里不会改变,只有数组的内容可以改变

 

定义 char a[10 ]  时,编译器会给数组分配十个单元,每个单元的数据类型为字符。

定义 char *s 时,  这是个指针变量,只占四个字节,32位,用来保存一个地址。。

【内存模型】
       +-----+     +---+---+---+---+---+---+
   s1: |  *======> | h | e | l | l | o |\0 |
       +-----+     +---+---+---+---+---+---+
       +---+---+---+---+---+---+
   s2: | h | e | l | l | o |\0 |
       +---+---+---+---+---+---+

场景一)
char *s1 = "hello";
char s2[] = "hello";
s2=s1;  //编译ERROR
s1=s2;  //OK

分析:s2其地址和容量在生命期里不能改变

 

场景二)
char s2[] = "hello";
char *s1 = s2;  //编译器做了隐式的转换 实际为&s2

char *s1 = &s2;

分析:以上两个指针复值完全等价,由于编译器会做这个隐式转换也容易导致初学者误认为 char *s 与char s[]是一回事。
      另用第二种在一些编译器甚至会报警告信息。

 

场景三)
char *s1 = "hello";
char s2[] = "hello";
s1[0]='a';  //×运行ERROR( 这一句好像在一些的编译器不会出错,原因待查)
s2[0]='a';  //OK

分析:运行时会报错,原因在于企图改变s1的内容,由于s1指向的是常量字符串,其内容是不可修改的,因此在运行时不会通过。而s2指向的是变量区字符串,可以修改。

 

场景四)
让我们来给一个指针的指针赋值,在使用某些含char**参数的函数时会用到,场景二的增强版。
    char *s1="hello";
    char s2[]="hello";
    char *s3=s2;       //★注意这句必须要★
    char **s4=&s3;   //s2(char[])要用两步才能完成赋值
    char **s5=&s1;   //s1(char*) 只需一步
    printf("s4=[%s]\n",*s4);//打印结果:s4=[hello]
    printf("s5=[%s]\n",*s5);//打印结果:s5=[hello]

分析:这个例子应当说最能反映出char *与char []的差异,但是由于使用场合不多,新人尤其需要注意。

 

数组指针:首先它是一个指针,它指向一个数组,即指向数组的指针;在32 位系统下永远是占4 个字节,至于它指向的数组占多少字节,不知道。数组指针指向的是数组中的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关。 
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称,即每个元素都是指针。 
二级指针 : 如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。

int *p1[10];

int (*p2)[10];

p1是一个数组,其包含10 个指向int 类型数据的指针,即指针数组

p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针。数组在这里并没有名字,是个匿名数组。

 

引入数组指针后,我们就有两种方案来访问数组元素了,一种是使用下标,另外一种是使用指针。 
1. 使用下标 
也就是采用 arr[i] 的形式访问数组元素。如果 p 是指向数组 arr 的指针,那么也可以使用 p[i] 来访问数组元素,它等价于 arr[i]。 
2. 使用指针 
也就是使用 (p+i) 的形式访问数组元素。另外数组名本身也是指针,也可以使用 (arr+i) 来访问数组元素,它等价于 *(p+i)。 
不管是数组名还是数组指针,都可以使用上面的两种方式来访问数组元素。不同的是,数组名是常量,它的值不能改变,而数组指针是变量(除非特别指明它是常量),它的值可以任意改变。也就是说,数组名只能指向数组的开头,而数组指针可以先指向数组开头,再指向其他元素。

char **  与char  *a[ ] 

 先看 char  *a[ ] ;

 由于[ ] 的优先级高于* 所以a先和 [ ]结合,他还是一个数组,数组中的元素才是char * ,前面讲到char * 是一个变量,保存的地址。。

 所以 char *a[ ] = {"China","French","America","German"};

 同过这句可以看到, 数组中的元素是字符串,那么sizeof(a) 是多少呢,有人会想到是五个单词的占内存中的全部字节数 6+7+8+7 = 28;

但是其实sizeof(a) = 16;

字符串常量的本质是地址,a 数组中的元素为char * 指针,指针变量占四个字节,那么四个元素就是16个字节了

注意这四个地址是不连续的,它是编译器为"China","French","America","German" 分配的内存空间的地址, 所以,四个地址没有关联。

 

 char **s;
  char **为二级指针, s保存一级指针 char *的地址,关于二级指针就在这里不详细讨论了 ,简单的说一下二级指针的易错点。  

  举例:
  char *a[] = {"China","French","America","German"};  
  char **s = a;  

  为什么能把 a赋给s,因为数组名a代表数组元素内存单元的首地址,即 a = &a[0] ;

而 a[0]中保存的又是字符串"China"的首地址。

 *s  = "China";

 printf("%s",*s);  
 printf("%s",a[0]);  
 printf("%s",*a);  
 都是一样的

 

char  **s;  
 *s = "hello world";  

 貌似是合理的,编译也没有问题,但是 printf("%s",*s),就会崩溃

 printf("%s",*s); 时,首先得有s 保存的地址,再在这个地址中找到 char *  的地址,即*s;

 举例:

 s = 0x1000;  

 在0x1000所在的内存单元中保存了"hello world"的地址 0x003001 , *s = 0x003001;

这样printf("%s",*s);

这样会先找到 0x1000,然后找到0x003001;

如果直接 char  **s;

*s = "hello world";  

s 变量中保存的是一个无效随机不可用的地址, 谁也不知道它指向哪里。。。。,*s 操作会崩溃。。

所以用 char **s 时,要给它分配一个内存地址。

char  **s ;  
s = (char **) malloc(sizeof(char**));  
*s =  "hello world"; 

这样 s 给分配了了一个可用的地址,比如 s = 0x412f;
然后在 0x412f所在的内存中的位置,保存 "hello world"的值。。