指针和数组是C的比较难搞懂的知识点, 需要结合内存来学习, 非常感谢各位兄弟为我指点迷津.

下面总结一下 : 
首先说明一下C程序在运行时, 不同的内容或变量分别存储在什么地方?
分了几块区域分别是, code, constants, global, heap, stack; (内存地址从低到高)
其中constants存储常量(常量值不允许修改), global存储在所有函数以外定义的全局变量(全局变量允许修改), heap是一块动态内存区域(可存放持久化内容, 不会自动释放内存), stack存放函数内的本地变量(函数执行完后本地变量占用的内存将自动释放);  
如图 : 

接下来要介绍两个符号 * 和 &.

1. * 用在定义变量类型, 或者 强制类型转换时, 表示要定义一个指针 或者 把这个变量类型转换成指针(并告知这个指针指向的是什么类型的数据) .
2. * 用在一个指针变量的前面, 表示这个指针指向地址存储的内容 (内容是什么类型的就取决于这个指针定义时:  告知这个指针指向的是什么类型的数据,指针的加减运算得到的内存地址也和指针 指向的是什么类型的数据相关,int * a,a+1 得到的地址则是在a指向的地址基础上加4字节.).
3. & 用在变量名的前面, 表示这个变量所在的地址 . 
例1 : 
[root@db-172-16-3-33 zzz]# cat b.c
#include <stdio.h>
int main() {
  char * a = "hello";
  fprintf(stdout, "&a:%p, a:%p, a:%s\n", &a, a, a);
  return 0;
}
结果 : 
[root@db-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
&a:0x7fff71b22230, a:0x400618, a:hello
&a 表示a的地址 . 
a 表示a存储的内容, 因为a是指针, 所以它的内容读取出来就是一个地址 . 
*a 表示a指针存储的这个地址 存储的内容 . 
在内存中的图示 : 
这个图包含了几块内容 : 
1. 在64位的系统中 , 指针占用了8个字节. 因为指针中存储的是地址, 而且地址是64位的. 所以需要8个字节.
2. 在x86架构的机器中, 内存填充是从低位到高位的. 所以hello在内存中是这样存储的 : 
地址:  内容
0x400618 : 0x68  (ascii : h)
0x400619 : 0x65  (ascii : e)
0x40061a : 0x6c  (ascii : l)
0x40061b : 0x6c  (ascii : l)
0x40061c : 0x6f   (ascii : o)
0x40061d : 0x68  (ascii : NULL)
3. 那怎么来证明以上是正确的呢? 很简单, 按照每个字节来打印就知道了.
这里需要注意的是指针的加减法得到的地址和指针指向的地址存储的内容的类型是有关的, 反过来说, 要让指针加1刚好得到的是下一个字节那就告诉编译器, 这个指针指向的地址的内容是char类型就好了, 因为sizeof(char) = 1字节.
先来打印一下hello是不是按照上面说的这样存储的?
[root@db-172-16-3-33 zzz]# cat b.c
#include <stdio.h>
int main() {
  char * a = "hello";
  fprintf(stdout, "&a:%p, a:%p, a:%s\n", &a, a, a);
  fprintf(stdout, "sizeof(char *):%lu, sizeof(char):%lu\n", sizeof(char *), sizeof(char));
  fprintf(stdout, "a+0:%p, *(a+0):%x, *(a+0):%c\n", a+0, *(a+0), *(a+0));
  fprintf(stdout, "a+1:%p, *(a+1):%x, *(a+1):%c\n", a+1, *(a+1), *(a+1));
  fprintf(stdout, "a+2:%p, *(a+2):%x, *(a+2):%c\n", a+2, *(a+2), *(a+2));
  fprintf(stdout, "a+3:%p, *(a+3):%x, *(a+3):%c\n", a+3, *(a+3), *(a+3));
  fprintf(stdout, "a+4:%p, *(a+4):%x, *(a+4):%c\n", a+4, *(a+4), *(a+4));
  fprintf(stdout, "a+5:%p, *(a+5):%x, *(a+5):%c\n", a+5, *(a+5), *(a+5));
  return 0;
}
结果 : 
[root@db-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
&a:0x7ffffe249680, a:0x4006f8, a:hello
sizeof(char *):8, sizeof(char):1
a+0:0x4006f8, *(a+0):68, *(a+0):h
a+1:0x4006f9, *(a+1):65, *(a+1):e
a+2:0x4006fa, *(a+2):6c, *(a+2):l
a+3:0x4006fb, *(a+3):6c, *(a+3):l
a+4:0x4006fc, *(a+4):6f, *(a+4):o
a+5:0x4006fd, *(a+5):0, *(a+5):
解说 : 
a 是一个指针, 这个指针指向的内容是char类型的, 所以这个指针的加减运算a+1 表示地址加1字节.
*a 把你带到它指向的 0x4006f8, 而fprintf 以16进制和char输出的正是 0x4006f8这个地址的 这个字节上的内容(1字节刚好用两个16进制数表示, 刚好可以转成char)
  a+0这里只是为了说明指针的加减运算, 可以去掉+0.
   sizeof(char *):8 表示指针占用了8字节, sizeof(char):1 表示char占用了1字节.
4. 接下来打印 &a 这个地址是不是也是按照从低到高存储的?
为了一个字节一个字节打出,  我们需要再定义一个指针b , 用指针b来做加减运算, 但是这样能如愿吗?
[root@db-172-16-3-33 zzz]# cat b.c
#include <stdio.h>
int main() {
  char * a = "hello";
  char ** b = &a;
  fprintf(stdout, "sizeof(a):%lu, b:%p, &a:%p, b+1:%p\n", sizeof(a), b, &a, b+1);
  return 0;
}
结果 : 
[root@db-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
sizeof(a):8, b:0x7fff3692b520, &a:0x7fff3692b520, b+1:0x7fff3692b528
解说 : 
sizeof(a)=8 结果告诉我们, a这个变量占用了8字节, 因为它存储的是个内存地址, 在64位的操作系统中这个是可以理解的.
b这个指针指向a, 所以b和&a 打印出来的值转成内存地址当然是一样的.
b+1 运算得到的地址当然应该是加8个字节 . 因为地址做加减运算得到的当然还是地址, 至于得到的结果和什么有关, 当然和这个做加减运算的指针定义时所告知的它指向什么类型的数据有关. char ** b, ( *b 表示 b是一个指针, 然后char *则是告诉你b指向的是一个指针, 甭管是什么类型的指针,反正b指向的是一个指针, 一个指针占用8字节, 所以b+1就是加8个字节)
那怎么样能让b+1结果是加1个字节呢?
这里要用到强制类型转换. (char *) b 就把b强制转成char *了.  所以 ((char *) b)+1 就是加1字节. *( ((char *) b)+1) 就是取这个地址的内容.
%x表示取一个字节并按照16进制打印出来.
[root@db-172-16-3-33 zzz]# cat b.c
#include <stdio.h>
int main() {
  char * a = "hello";
  char ** b = &a;
  unsigned short i;
  unsigned short x = (unsigned short) sizeof(a);
  fprintf(stdout, "&a:%p, a:%p\n", &a, a);
  for(i=0; i<x; i++) {
    fprintf(stdout, "i:%u, ((char *) b)+i:%p, *(((char *) b)+i):%x\n", i, ((char *) b)+i, *(((char *) b)+i));
  }
  return 0;
}
结果 : 
[root@db-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
&a:0x7fff1ffff608, a:0x400728
i:0, ((char *) b)+i:0x7fff1ffff608, *(((char *) b)+i):28
i:1, ((char *) b)+i:0x7fff1ffff609, *(((char *) b)+i):7
i:2, ((char *) b)+i:0x7fff1ffff60a, *(((char *) b)+i):40
i:3, ((char *) b)+i:0x7fff1ffff60b, *(((char *) b)+i):0
i:4, ((char *) b)+i:0x7fff1ffff60c, *(((char *) b)+i):0
i:5, ((char *) b)+i:0x7fff1ffff60d, *(((char *) b)+i):0
i:6, ((char *) b)+i:0x7fff1ffff60e, *(((char *) b)+i):0
i:7, ((char *) b)+i:0x7fff1ffff60f, *(((char *) b)+i):0
解说 : 
a变量存放在内存地址 0x7fff1ffff608这里的连续8个字节中.
a变量的8个字节中存储了什么内容呢?  0x400728
它是从低位到高位存储的, 如上面的结果, 28存在 0x7fff1ffff608, 07存在 0x7fff1ffff609, 40存在 0x7fff1ffff60a, 另外5个字节存的都是0x00.
5. 如果是多字节的数据又是怎么存储的呢? 比如int 占据了4个字节, 它是怎么存储的 ?
答案也是从低位到高位.
[root@db-172-16-3-33 zzz]# cat c.c
#include <stdio.h>
int main() {
  int a = -987654;
  int * b = &a;
  unsigned short i;
  unsigned short x = (unsigned short) sizeof(a);
  fprintf(stdout, "&a:%p, a:%i, a:%x\n", &a, a, a);
  for(i=0; i<x; i++) {
    fprintf(stdout, "i:%u, ((char *) b)+i:%p, (unsigned char) (*(((char *) b)+i)):%x\n", i, ((char *) b)+i, (unsigned char) (*(((char *) b)+i)));
  }
  return 0;
}
结果 : 
[root@db-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./c.c -o c && ./c
&a:0x7fffe57051cc, a:-987654, a:fff0edfa  //负数是正数的反码加1, 所以得到的是这个结果.
i:0, ((char *) b)+i:0x7fffe57051cc, (unsigned char) (*(((char *) b)+i)):fa
i:1, ((char *) b)+i:0x7fffe57051cd, (unsigned char) (*(((char *) b)+i)):ed
i:2, ((char *) b)+i:0x7fffe57051ce, (unsigned char) (*(((char *) b)+i)):f0
i:3, ((char *) b)+i:0x7fffe57051cf, (unsigned char) (*(((char *) b)+i)):ff
注意, 这里一定要把内容再强制转换成unsigned char再输出, 就是这个 (unsigned char) (*(((char *) b)+i)):%x . 
否则编译器会输出4字节的16进制数, 不太好看. 如下 : 
#include <stdio.h>
int main() {
  int a = -987654;
  int * b = &a;
  unsigned short i;
  unsigned short x = (unsigned short) sizeof(a);
  fprintf(stdout, "&a:%p, a:%i, a:%x\n", &a, a, a);
  for(i=0; i<x; i++) {
    fprintf(stdout, "i:%u, ((char *) b)+i:%p, (*(((char *) b)+i)):%x\n", i, ((char *) b)+i, (*(((char *) b)+i)));
  }
  return 0;
}

结果 : 
[root@db-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./c.c -o c && ./c
&a:0x7ffff6fada3c, a:-987654, a:fff0edfa
i:0, ((char *) b)+i:0x7ffff6fada3c, (*(((char *) b)+i)):fffffffa
i:1, ((char *) b)+i:0x7ffff6fada3d, (*(((char *) b)+i)):ffffffed
i:2, ((char *) b)+i:0x7ffff6fada3e, (*(((char *) b)+i)):fffffff0
i:3, ((char *) b)+i:0x7ffff6fada3f, (*(((char *) b)+i)):ffffffff

6. 那么bit-field又是怎么存储的呢?
答案当然也是从低位到高位存储的, 这里要用到struct来验证.
[root@db-172-16-3-33 zzz]# cat c.c
#include <stdio.h>
int main() {
  typedef struct test{
    unsigned char f1:1;
    unsigned char f2:2;
    unsigned char f3:3;
    unsigned char f4:2;
  } test;
  test t1 = {0,1,4,3};
  test * pt1 = &t1;
  fprintf(stdout, "(unsigned char) (*((unsigned char *) pt1)):%x\n", (unsigned char) (*((unsigned char *) pt1)));
  return 0;
}
结果 : 
[root@db-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./c.c -o c && ./c
(unsigned char) (*((unsigned char *) pt1)):e2
解说 : 
test t1 = {0,1,4,3}; 转成二进制分别如下 : 
f1(0) : 0
f2(1) : 01
f3(4) : 100
f4(3) : 11
如果是按低位到高位存储的, 那么它存储的应该是 : 11100010 . 这个与0xe2 刚好相符.
这里同样用到了强制类型转换,  (unsigned char *) pt1是把指向struct test的指针转成了指向unsigned char的指针.
(unsigned char) (*((unsigned char *) pt1)) 是把  *((unsigned char *) pt1) 转成了unsigned char类型. 
如果不使用指针, 直接把struct变量转成unsigned是不能编译通过的. 错误如下 : 
[root@db-172-16-3-33 zzz]# cat c.c
#include <stdio.h>
int main() {
  typedef struct test{
    unsigned char f1:1;
    unsigned char f2:2;
    unsigned char f3:3;
    unsigned char f4:2;
  } test;
  test t1 = {0,1,4,3};
  unsigned char a = (unsigned char) t1;
  fprintf(stdout, "a:%x\n", a);
  return 0;
}
结果 : 
[root@db-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./c.c -o c && ./c
./c.c: In function ‘main’:
./c.c:11: error: aggregate value used where an integer was expected
所以指针在C里面运用真的太灵活了.
进入正题, 讲讲 char **a , char *a[] , char a[][], char * a[][] , char ** a[][] , char * a [][][]
看起来很复杂, 其实理解了就不复杂了.
1. 
char **a : 
表示a是一个指针, 这个指针指向的地址存储的是 char * 类型的数据.  指针的加减运算在这里的体现 :  a + 1 表示地址加8字节 .  
char * 也是一个指针,  用(*a)表示 ,  指向的地址存储的是 char 类型的数据。 指针的加减运算在这里的体现 : (* a) + 1 表示地址加1字节 .  
[root@db-172-16-3-33 zzz]# cat b.c
#include <stdio.h>
int main() {
  char * a = "hello";
  char ** b = &a;
  fprintf(stdout, "&b:%p, b:%p, &a:%p, a:%p, *a:%c, a:%s\n", &b, b, &a, a, *a, a);
  return 0;
}
结果 : 
[root@db-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
&b:0x7fff5319c1d8, b:0x7fff5319c1e0, &a:0x7fff5319c1e0, a:0x400628, *a:h, a:hello
图示 : 
2.  char *a[]
表示 a是数组, 数组中的元素是指针, 指向char类型. (数组里面所有的元素是连续的内存存放的).
需要特别注意 : 
数组名在C里面做了特殊处理 , 数组名用数组所占用内存区域的第一个字节的内存地址替代了。并且数组名a也表示指针.
如数组占用的内存区域是0x7fff5da3f550到0x7fff5da3f5a0, 那么a就被替换成0x7fff5da3f550.
所以a 并不表示a地址存储的内容, 而是a地址本身(这个从 a = &a 就能够体现出来). 这个一定要理解, 否则会无法进行下去. 
a+1 表示a的第二个元素的内存地址, 所以是加8字节.( 因为a的元素是char 指针, 所需要的空间为8字节(64位内存地址). )
*(a+1) 则表示a这个数组的第二个元素的内容 (是个char 类型的指针. 本例表示为world字符串的地址).
*(*(a+1)) 则表示a这个数组的第二个元素的内容(char指针)所指向的内容(w字符).
char * a[10] 表示限定这个数组最多可存放10个元素(char指针), 也就是说这个数组占用10*8 = 80字节.
如果存储超出数组的限额编译警告如下 : 
[root@db-172-16-3-33 zzz]# cat b.c
#include <stdio.h>
int main() {
  char *a[1] = {"abc","def"};
  fprintf(stdout, "a[1]:%s\n", a[1]);
  return 0;
}
结果 : 
[root@db-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
cc1: warnings being treated as errors
./b.c: In function ‘main’:
./b.c:4: warning: excess elements in array initializer  // 超出数组长度. 因为赋值时给了2个元素, 而限定只有1个元素.
./b.c:4: warning: (near initialization for ‘a’)
例子 : 
[root@db-172-16-3-33 zzz]# cat b.c
#include <stdio.h>
int main() {
  char *a[10] = {"hello", "world"};
  fprintf(stdout, "a:%p, a+1:%p, &a:%p, (&a)+1:%p, *a:%p, *(a+1):%p\n", a, a+1, &a, (&a)+1, *a, *(a+1));
  fprintf(stdout, "*a:%s, *(a+1):%s, **a:%c, *(*(a+1)):%c\n", *a, *(a+1), **a, *(*(a+1)));
  return 0;
}

结果 : 
[root@db-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
a:0x7fff9d0fb180, a+1:0x7fff9d0fb188, &a:0x7fff9d0fb180, (&a)+1:0x7fff9d0fb1d0, *a:0x4006b8, *(a+1):0x4006be
*a:hello, *(a+1):world, **a:h, *(*(a+1)):w

解说 : 
a:%p 打印   0x7fff9d0fb180  表示a的内存地址 , 也就是说数组a的第一个元素存储在这个地址里面, 取出第一个元素的值(char指针)使用*a  .
a+1:%p 打印的是a这个数组的第二个元素 的内存地址   0x7fff5da3f558 (因为a数组里面存储的是char指针, 指针在64位机器里面占用8字节, 所以第二个元素所在的内存地址相比第一个元素所在的内存地址刚好大8字节) , 取出第二个元素的值(char指针)使用*(a+1)  .
&a:%p 打印出来的结果和a:%p 0x7fff5da3f550 一致, 因为编译器把数组名替换成了数组所在内存的位置 . 
(a:%p 打印的是第一个元素所在的内存地址, &a:%p打印的则是a数组所在的内存地址. 所以结果是一样的)
但是 a+1 不等于 (&a)+1. 原因是 a 和 &a 含义不一样, a指向的是数组里面的第一个元素, 所以a+1 指的是第二个元素. &a指向的是a这个数组, 所以(&a)+1 表示整个a数组要用到的内存空间的下一个地址. ( 如果&a不好理解的话, 想象把&a 赋予给另一个指针变量c, 那么c+1 等于啥? )
3. char [][]
char a[2][10] 表示一个二维数组, 存储在最底层的元素是char. 1维最多允许存储2个元素(char []), 2维最多允许存储10个元素(char).
超出将告警 : 
[root@db-172-16-3-33 zzz]# cat b.c
#include <stdio.h>
int main() {
  char a[2][10] = {"hello", "world", "linux"};
  return 0;
}
结果 : 
[root@db-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
cc1: warnings being treated as errors
./b.c: In function ‘main’:
./b.c:4: warning: excess elements in array initializer  // 1维在赋值时给出了3个元素"hello" , "world" , "linux" 因此告警了, 可能导致内存溢出.
./b.c:4: warning: (near initialization for ‘a’)
./b.c:4: warning: unused variable ‘a’
2维超出长度同样告警 : 
[root@db-172-16-3-33 zzz]# cat b.c
#include <stdio.h>
int main() {
  char a[2][10] = {"helloxxxxxxxx", "world"};
  return 0;
}
[root@db-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
cc1: warnings being treated as errors
./b.c: In function ‘main’:
./b.c:4: warning: initializer-string for array of chars is too long
./b.c:4: warning: (near initialization for ‘a[0]’)
./b.c:4: warning: unused variable ‘a’
char a[2][10] 中, a+$n 或者 & a[n] 表示 指向1维的第$n个元素的指针, *(a+$n)+$m 或 & a[n][m] 表示指向1维的第n个元素中的第m个元素的指针. 
*( *(a+$n)+$m) 或者 a[n][m] 表示  1维的第n个元素中的第m个元素 的内容 . 

解说 :

以 char a[2][10]为例子 : 
&a 是一个指针 , 加1 将加整个二维数组所占空间. 相当于加20字节.
a 是一个指针 , 加1 将加加1个1维元素所占空间.  相当于加10字节.
*a 是一个指针    ,  加1 将加加1个2维元素所占空间.  相当于加1字节.
**a 不是指针, 是char. 
a+1与*(a+1)相等 是因为a+1 表示1维的第2个元素的首地址, *(a+1)表示 1维的第2个元素中的第1个元素的地址. 指向同一个地址. 但是含义不一样.  (a+1) + 1 和 (*(a+1)) + 1就不一样了.
(a+1) + 1  表示1维的第3个元素的首地址;  (*(a+1)) + 1  表示 1维的第2个元素中的第2个元素的地址. 
以char a[2][6]为例子 : 
[root@db-172-16-3-150 zzz]# cat a.c
#include <stdio.h>
int main() {
  char a[2][6] = {"hello", "world"};
  fprintf(stdout, "sizeof(a):%lu\n", sizeof(a)); // a数组占用空间
  fprintf(stdout, "&a:%p, (&a)+1:%p\n", &a, (&a)+1); // a数组首地址, a数组首地址加1
  fprintf(stdout, "a:%p, a+1:%p\n", a, a+1); // a数组1维度第1个元素首地址, 加1
  fprintf(stdout, "*a:%p, *(a)+1:%p\n", *a, *(a)+1); // a数组1维度第1个元素中的第1个元素首地址, 加1
  return 0;
}

结果 : 
[root@db-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
sizeof(a):12
&a:0x7ffffe5cf8b0, (&a)+1:0x7ffffe5cf8bc  // 加12个字节
a:0x7ffffe5cf8b0, a+1:0x7ffffe5cf8b6  // 加6个字节
*a:0x7ffffe5cf8b0, *(a)+1:0x7ffffe5cf8b1    // 加1个字节

验证图示正确性 : 
[root@db-172-16-3-150 zzz]# cat a.c
#include <stdio.h>
int main() {
  char a[2][6] = {"hello", "world"};
  fprintf(stdout, "&a:%p, a:%p, *a:%p\n", &a, a, *a);
  fprintf(stdout, "(*(a+0))+1:%p, &a[0][1]:%p\n", (*(a+0))+1, &a[0][1]);
  fprintf(stdout, "a+1:%p, &a[1]:%p, *(a+1):%p, a[1]:%p\n", a+1, &a[1], *(a+1), a[1]);
  fprintf(stdout, "(*(a+1))+1:%p, &a[1][1]:%p\n", (*(a+1))+1, &a[1][1]);
  fprintf(stdout, "(&a)+1:%p\n", (&a)+1);
  return 0;
}
结果 : 
[root@db-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
&a:0x7fff5c94e670, a:0x7fff5c94e670, *a:0x7fff5c94e670
(*(a+0))+1:0x7fff5c94e671, &a[0][1]:0x7fff5c94e671
a+1:0x7fff5c94e676, &a[1]:0x7fff5c94e676, *(a+1):0x7fff5c94e676, a[1]:0x7fff5c94e676
(*(a+1))+1:0x7fff5c94e677, &a[1][1]:0x7fff5c94e677
(&a)+1:0x7fff5c94e67c 

指针运用 :
fprintf 打印 %s 需要传入char *指针 . %c 需要传入的是值. 
下面试试使用 a+1 和 *(a+1) 来打印%s, a+1 会报错, 因为它是一个指向指针的指针. 不是char *. 
[root@db-172-16-3-150 zzz]# cat a.c
#include <stdio.h>
int main() {
  char a[2][6] = {"hello", "world"};
  fprintf(stdout, "a+1:%s\n", a+1);
  return 0;
}
[root@db-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
cc1: warnings being treated as errors
./a.c: In function ‘main’:
./a.c:5: warning: format ‘%s’ expects type ‘char *’, but argument 3 has type ‘char (*)[6]’
将 a+1 指针强制转换成char * 就可以了 (char *) (a+1) . 如下 : 
[root@db-172-16-3-150 zzz]# cat a.c
#include <stdio.h>
int main() {
  char a[2][6] = {"hello", "world"};
  fprintf(stdout, "a+1:%s\n", (char *) (a+1));
  return 0;
}
[root@db-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
a+1:world
你可能觉得不对劲,  (char *) (a+1) 到底是强制转换还是去a+1这个地址的内容? 那我们就用一个三维数组来验证一下, 如果是取这个地址的值, 那么得到的应该是一个指向指针的指针. 也是不能打印的. 如下 : 
[root@db-172-16-3-150 zzz]# cat a.c
#include <stdio.h>
int main() {
  char a[2][6][6] = {{"hello", "world"},{"linux", "digoal"}};
  fprintf(stdout, "a+1:%s\n", (a+1));
  return 0;
}
结果
[root@db-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
cc1: warnings being treated as errors
./a.c: In function ‘main’:
./a.c:5: warning: format ‘%s’ expects type ‘char *’, but argument 3 has type ‘char (*)[6][6]’

[root@db-172-16-3-150 zzz]# cat a.c
#include <stdio.h>
int main() {
  char a[2][6][6] = {{"hello", "world"},{"linux", "digoal"}};
  fprintf(stdout, "a+1:%s\n", *(a+1));
  return 0;
}
结果
[root@db-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
cc1: warnings being treated as errors
./a.c: In function ‘main’:
./a.c:5: warning: format ‘%s’ expects type ‘char *’, but argument 3 has type ‘char (*)[6]’

[root@db-172-16-3-150 zzz]# cat a.c
#include <stdio.h>
int main() {
  char a[2][6][6] = {{"hello", "world"},{"linux", "digoal"}};
  fprintf(stdout, "a+1:%s\n", (char *) (a+1));
  return 0;
}
结果
[root@db-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
a+1:linux

从上面得到一个结论, 只要是数组名, 就会被替换成地址, 不管是1维还是2维. 想象一下a[2][6], a是1维的数组名, *a是2维的数组名. 都是替换成地址了. 多维也是如此.
【其他】
1. 当char []作为函数的参数时, 表示 char *. 当作为函数的参数传入时, 实际上是拷贝了数组的第一个元素的地址 . 
所以 void test (char a[]) 等同于 void test ( char * a )
char x[10] ; 然后调用 test(x) 则等同于把 x 的第一个元素的地址赋予给参数 a . 
2. char * a 和 char a[] 
相同点 : a都是指针,  指向char类型.
不同点 : char a[] 把内容存在stack . 
char *a 则把指针存在stack,把内容存在constants. 
3. char * a[10] 和 char a[10][20]
相同点 : a 都是2级指针, *a 表示一级指针, **a 表示内存中存储的内容.
不同点 :  char * a[10], 数组由char * 类型的指针组成; 
char a [10][20] 表示一位放10个元素, 二维放20个元素, 值存放地是一块连续的内存区域, 没有指针.
4. 小窍门 :  []和*的数量对应, 如 char a[][]的指针层数是2, 相当于char **a; char *a[]也是如此, 两层指针. 迷糊的时候数数到底有几个*几个[], 就知道什么情况下存储的是内容还是地址了? 如char a[][] 的情况里面: &a, a, *a 都是地址, **a 是内容.
转载: http://blog.163.com/digoal@126/blog/static/163877040201271195312138/

char* char[] char** char*[] char[][]详解相关推荐

  1. Java中Character(类型char)类及类型详解

    1. java中char类型占2个字节.16位可以存放汉子,字母和数字占一个字节,一个字节8位,中文占2个字节,16位: 2. char类型赋值 char a='a'; //任意单个字符,加单引号. ...

  2. 判断数字字符——Character.isDigit(char ch)实现源码详解

    目录 函数的使用 使用场景 实现结果 源码分析 Character.isDigit(char ch)源码解析 Character.getType(int codePoint)源码解析 Characte ...

  3. c语言char数组和short数组的区别,详解C语言中Char型指针数组与字符数组的区别

    详解C语言中Char型指针数组与字符数组的区别 详解C语言中Char型指针数组与字符数组的区别 1.char 类型的指针数组:每个元素都指向一个字符串,指向可以改变 char *name[3] = { ...

  4. c语言中argc的作用,C语言中 int main(int argc,char *argv[])的两个参数详解

    C语言中 int main(int argc,char *argv[])的两个参数详解 argc是命令行总的参数个数: argv[]是argc个参数,其中第0个参数是程序的全名,以后的参数.命令行后面 ...

  5. char数组反转java_java 字符串反转的实例详解

    java 字符串反转的实例详解 1.new StringBuffer("abcde").reverse().toString(); 2.通过char数组进行转换, 代码如下 pac ...

  6. java 怎么输出地址,Java中char[]输出不是内存地址的原因详解

    前言 Java中共有八种基本数据类型:byte,int,short,long,float,double,char,boolean. 计算机中的基础数据单位是bit, 1byte=8bit. 数据类型 ...

  7. java char 打印_Java中char[]输出不是内存地址的原因详解

    前言 Java中共有八种基本数据类型:byte,int,short,long,float,double,char,boolean. 计算机中的基础数据单位是bit, 1byte=8bit. 数据类型 ...

  8. 【转载】cocos2d-x类型转换(CCstring int string char UTF-8互转)以及字符串详解

    cocos2d-x中的字符串: <1>:使用 const char* 和 std::string const char* 是C风格的字符串  ,std::string 是C++风格的字符串 ...

  9. char 赋值 加入变量_王牌编程语言Java常量、变量、数据类型详解

    IT技术研习社,专注互联网技术研究与分享,喜欢的朋友可以点击[关注]:把经验传递给有梦想的人: 什么是常量 常量指程序里持续不变的值,在整个程序运行过程中它是不可改变的常见的字面量类型有 整型字面常量 ...

  10. C++中char类型详解

    # 1char与字符的关系##1.1char类型到底代表什么提到char类型,我相信学过C/C++的并不会陌生,char类型代表一个字节,在内存中有8位,所以signed char的范围为-128~1 ...

最新文章

  1. Nvidia、Intel、AMD技术人员薪资大揭秘:平均薪酬超20万$,英伟达最高近35万美元...
  2. 设计模式学习 之 单例模式
  3. Oracle数据库基础知识点汇总
  4. CTFshow 文件包含 web80
  5. Linux操作系统下iptables+nat实现ADSL共享上网!
  6. cnn图像二分类 python_TensorFlow2基础:CNN图像分类
  7. 设定Word段落的背景色
  8. springboot创建parent_理解spring-boot-starter-parent
  9. Android 撸起袖子,自己封装 DialogFragment
  10. 运营是一个产品价值传递的过程,互联网营销
  11. matlab 读取视频出现的问题
  12. Flex父子窗口传值
  13. Win32 Application和Win32 Console Application区别
  14. c语言自定义sum函数,c语言自定义函数
  15. 互联网赚钱骚操作,真的太骚了
  16. python计算算术平方根sqrt()
  17. 细胞色素--训练13
  18. 汇编语言学习-DOSBox-MASM-安装及使用教程
  19. 由于其配置信息(注册表中的)不完整或已损坏,Windows 无法启动这个硬件设备。 (代码 19)怎么办?
  20. leetcode java 大厂基础面试题附答案(二)

热门文章

  1. 硬件:详细讲解台式电脑上的常用的几个接口!
  2. 影像组学训练营 第三天(共三天)
  3. 神州信息“六合上甲”获2022 DAMA中国数据治理大奖
  4. JavaScript词汇表
  5. 小米3的卡槽,卡住了
  6. Java中开根号,你还记得吗
  7. 有哪些电容笔值得推荐?值得买的电容笔测评
  8. 小写转大写输出,并统计数字、大写字母、小写字母的个数
  9. 大话赛宁云 | 演系列-超仿真网络空间“演武场”
  10. 如何设置BIOS实现远程开机