深刻理解char * ,char ** ,char a[ ] ,char *a[]

1.数组的本质

数组是多个元素的集合,在内存中分布在地址相连的单元中,因此能够经过其下标访问不一样单元的元素。ubuntu

2.指针

指针也是一种变量,只不过它的内存单元中保存的是一个标识其余位置的地址。因为地址也是整数,在32位平台下,指针默认为32位。数组

3.指针的指向

指向的直接意思就是指针变量所保存的其余的地址单元中所存放的数据类型。操作系统

int *p; //p变量保存的地址所在内存单元中的数据类型为整型

float *q; // .............浮点型

不论指向的数据类型为哪一种,指针变量其自己永远为整型,由于它保存的地址。.net

4.字符数组

字面意思是数组,数组中的元素是字符。确实,这就是它的本质意义。指针

char  str[10];   //定义了一个有十个元素的数组,元素类型为字符。

C语言中定义一个变量时能够初始化。code

char  str[10] = {"hello"};

当编译器遇到这句时,会把str数组中从第一个元素把hello\0 逐个填入。blog

因为C语言中没有真正的字符串类型,能够经过字符数组表示字符串,由于它的元素地址是连续的,这就足够了。内存

C语言中规定数组表明数组所在内存位置的首地址,也是 str[0]的地址,即str = &str[0];字符串

另外get

printf("%s",str);

为何用首地址就能够输出字符串。

由于还有一个关键,在C语言中字符串常量的本质表示实际上是一个地址,这是许多初学者比较难理解的问题。

举例:

char  *s ;

s = "China";

为何能够把一个字符串赋给一个指针变量。

这不是类型不一致吗

这就是上面提到的关键 。

C语言中编译器会给字符串常量分配地址,若是 "China", 存储在内存中的 0x3000 0x3001 0x3002 0x3003 0x3004 0x3005 .

s = "China" ,意识是什么,对了,地址。

其实真正的意义是 s ="China" = 0x3000;

看清楚了吧 ,你把China 看做是字符串,可是编译器把它看做是地址 0x3000,即字符串常量的本质表现是表明它的第一个字符的地址。

s = 0x3000

这样写彷佛更符合直观的意思。

搞清楚这个问题。

那么 %s ,它的原理其实也是经过字符串首地址输出字符串,printf("%s ", s); 传给它的实际上是s所保存的字符串的地址。

好比

#include <stdio.h>  

int main()  
{  
    char *s;  
    s = "hello";  
    printf("%p\n",s);  
    return 0;  
}

结果:

00422020

能够看到 s = 0x00422020 ,这也是"hello"的首地址

因此,printf("%s",0x00422020);也是等效的。

字符数组:

char  str[10] = "hello";

前面已经说了,str = &str[0] ,也等于 "hello"的首地址。

因此printf("%s",str); 本质也是 printf("%s", 地址);

C语言中操做字符串是经过它在内存中的存储单元的首地址进行的,这是字符串的终极本质。

4.char * 与 char a[]

char  *s;

char  a[] ;

前面说到 a表明字符串的首地址,而s 这个指针也保存字符串的地址(其实首地址),即第一个字符的地址,这个地址单元中的数据是一个字符,

这也与 s 所指向的 char 一致。

所以能够 s = a;

可是不能 a = s;

C语言中数组名能够复制给指针表示地址, 可是却不能赋给给数组名,它是一个常量类型,因此不能修改。

固然也能够这样:

char  a [ ] = "hello";  
char *s =a;  
for(int i= 0; i < strlen(a) ; i++){
        printf("%c", s[i]);  //或  printf("%c",*s++);
}

字符指针能够用 间接操做符 *取其内容,也能够用数组的下标形式 [ ],数组名也能够用 *操做,由于它自己表示一个地址 。

好比

printf("%c",*a);

将会打印出 'h'

5.char * 与 char a[] 的本质区别:

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

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

sizeof(a) = 10 ;
 
sizeof(s)  = ?

固然是4了,编译器分配4个字节32位的空间,这个空间中将要保存地址。

printf("%p",s);

这个表示 s 的单元中所保存的地址。

printf("%p",&s);

这个表示变量自己所在内存单元地址,不要搞混了。

用一句话来归纳,就是 char *s 只是一个保存字符串首地址的指针变量, char a[] 是许多连续的内存单元,单元中的元素为char 。

之因此用 char *能达到char a[]的效果,仍是字符串的本质,地址。即给你一个字符串地址,即可以为所欲为的操做他,可是,char *和char a[]的本质属性是不同的。

6.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个字节了

看一下实例:

#include <stdio.h>  
  int main()  
  {  
    char *a [] = {"China","French","America","German"};  
    printf("%p %p %p %p\n",a[0],a[1],a[2],a[3]);  
    return 0;  
  }

能够看到数组中的四个元素保存了四个内存地址,这四个地址中就表明了四个字符串的首地址,而不是字符串自己。

所以sizeof(a)固然是16了。。

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

#include <stdio.h>  
  int main()  
  {  
    char *a [ ] = {"China","French","America","German"};  
    printf("%p %p %p %p\n",a[0],a[1],a[2],a[3]); //数组元素中保存的地址  
    printf("%p %p %p %p\n",&a[0],&a[1],&a[2],&a[3]);//数组元素单元自己的地址  
    return 0;  
  }

能够看到 0012FF38 0012FF3C 0012FF40 0012FF44,这四个是元素单元所在的地址,每一个地址相差四个字节,这是因为每一个元素是一个指针变量占四个字节。

char **s;

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

举例:

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

为何能把 a赋给s,由于数组名a表明数组元素内存单元的首地址,即 a = &a[0] = 0012FF38;

而 0x12FF38即 a[0]中保存的又是 00422FB8 ,这个地址, 00422FB8为字符串"China"的首地址。

*s = 00422FB8 = "China";

这样即可以经过s 操做 a 中的数据

printf("%s",*s);  
  printf("%s",a[0]);  
  printf("%s",*a);

都是同样的。

但仍是要注意,不能a = s,前面已经说到,a 是一个常量。

再看一个易错的点:

char **s = "hello world";

这样是错误的,

由于 s 的类型是 char ** 而 "hello world "的类型是 char *

虽然都是地址, 可是指向的类型不同,所以,不能这样用。

从其本质来分析,"hello world",表明一个地址,好比0x003001,这个地址中的内容是 'h',为 char 型,而 s 也保存一个地址 ,这个地址中的内容(*s) 是char * ,是一个指针类型,因此二者类型是不同的。

若是是这样呢:

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"的值。

再如:

#include  <stdio.h>  
void  buf( char **s)  
 {  
        *s = "message";  
 }  
 int main()  
 {  
     char *s ;  
     buf(&s);  
     printf("%s\n",s);  
 }

二级指针的简单用法。,说白了,二级指针保存的是一级指针的地址,它的类型是指针变量,而一级指针保存的是指向数据所在的内存单元的地址,虽然都是地址,可是类型是不同的。

最后说明,sizoof(指针)的大小根据电脑操做系统而定,通常32位操做系统所占用的内存大小是4,64位操做系统指针的大小是8。

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

在个人ubuntu上打印 printf("%s",*s),没有崩溃。

更多参考

相关文章
相关标签/搜索