** 指向指针的指针,那个指针又指向常量

*是取值运算符,对地址使用可以获得地址中储存的数值;对于指针a,*a表示取a中的值
&是地址运算符,对变量使用可以获得该变量的地址。    对于变量b,*b表示取b的地址


在定义时,* 是一个标识符,声明该变量是一个指针,比如说int *p; 那p就是一个指向int型的指针;
在调用时,*p是指指针p指向的那个变量,比如说之前有int a=5;int *p=a;那么p的值是a的地址,也就是指针p指向a,*p则等于a的值,即*p=5。
而&,则是引用,比如说有定义int a=5;再定义int b=&a;那么这里的b则引用a的值,即b=5
,而再给b赋值:b=10,a的值也会变为10。
我想楼主会问*和&的区别,应该是针对函数定义里的参数而言吧,因为这里的这两者比较相似:
举几个简单例子:
先定义有int x=0;和int *p=x;
1、若定义函数: void fun_1(int a){ a=5;} , 则调用:fun_1(x); 之后,x还等于0;因为fun_1函数只改变了形参a的值,a只是fun_1函数里的局部变量,
调用fun_1(x)相当于是“a=x;a=5;”,x没变;
2、若定义函数:void fun_2(int &a){ a=5;} , 则调用:fun_2(x); 之后,x等于5;因为这里的a引用了x的值;
3、若定义函数:void fun_3(int *a){ *a=5;} , 则调用:fun_3(p); 之后,x也等于5;因为fun_3函数的参数a是一个指针,相当于a=p;*a则与*p指向同一地址,
改变*a即改变*p即x







char *command=NULL;

char prompt[MAX_PROMPT]
char **parameters = malloc(sizeof(char *)*(MAXARG+2));
read_command(&command,parameters,prompt)
builtin_command(command,parameters)

int read_command(char **command,char **parameters,char *prompt){}
int builtin_command(char *command, char **parameters){}


int *p;

p = (int*)malloc(sizeof(int) * 128



char *command=NULL;
char prompt[MAX_PROMPT]
char **parameters = malloc(sizeof(char *)*(MAXARG+2));
read_command(&command,parameters,prompt)
builtin_command(command,parameters)

int read_command(char **command,char **parameters,char *prompt)
int builtin_command(char *command, char **parameters)


int *p;
p = (int*)malloc(sizeof(int) * 128





=========<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>==============

进入正题, 讲讲 char **a , char *a[] , char a[][], char * a[][] , char ** a[][] , char * a [][][]
看起来很复杂, 其实理解了就不复杂了.
1.
char **a :
表示a是一个指针, 这个指针指向的地址存储


的是 char * 类型的数据. 指针的加减运算在


这里的体现 : a + 1 表示地址加8字节 .



八位字节(octet)

由八个二进制数位组成的字节,通常可表示

一个字符。

指针变量是保存一个变量地址的变量,它里面保存的是一个特殊的东西:地址,即内存单元。一个内存单元要用32个状态来表示,(32个0和1),一个0或1占用1个位,8个位是一个字节,所以当然要占用32/8=4个字节的空间。





char * 也是一个指针, 用(*a)表示, 指向的地址存储的是 char 类型的数据。指针的加减运算在这里的体现 :(*a) + 1 表示地址加1字节 .  


[root@db-172-16-3-33 zzz]# cat b.c
#include

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





[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[

1.*是定义一个变量是指针型,例如,
int * p,p是整型指针,指向一块内存,
而定
义int a,&a表示取这个整型变量的存储
地址,具体什么时候用这两个符号,
要看函数的参数类型和返回类型,如果是
指针型则用p和&a,如果不是就用*p和a





2.y应该是个二维数组

C语言允许把一个二维数组分解为多个一维数组来处理。因此数组a可分解为三个一维数组,即a[0],a[1],a[2]。每一个一维数组又含有四个元素。例如a[0]数组,含有a[0][0],a[0][1],a[0][2],a[0][3]四个元素。 数组及数组元素的地址表示如下:a是二维数组名,也是二维数组0行的首地址,等于1000。a[0]是第一个一维数组的数组名和首地址,因此也为1000。*(a+0)或*a是与a[0]等效的, 它表示一维数组a[0]0 号元素的首地址。 也为1000。&a[0][0]是二维数组a的0行0列元素首地址,同样是1000。因此,a,a[0],*(a+0),*a,&a[0][0]是相等的。同理,a+1是二维数组1行的首地址,等于1008。a[1]是第二个一维数组的数组名和首地址,因此也为1008。 &a[1][0]是二维数组a的1行0列元素地址,也是1008。因此a+1,a[1],*(a+1),&a[1][0]是等同的。 由此可得出:a+i,a[i],*(a+i),&a[i][0]是等同的。

C语言规定,它是一种地址计算方法,表示数组a第i行首地址。由此,我们得出:a[i],&a[i],*(a+i)和a+i也都是等同的。另外,a[0]也
可以看成是a[0]+0是一维数组a[0]的0号元素的首地址, 而a[0]+1则是a[0]的1号元素首地址,由此可得出a[i]+j则是一维数组a[i]的j号元素首地址,它等于&a[i][j]。由a[i]=*(a+i)得a[i]+j=*(a+i)+j,由于*(a+i)+j是二维数组a的i行j列元素的首地址。该元素的值等于*(*(a+i)+j)。

3.*是取值,p是地址 ,*p表示p指向内存块中存储的值,看了上面的解释也会有所了解


]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]







图示 :
char * a, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on in memory - 德哥@Digoal - The Heart,The World.
 
char * a, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on in memory - 德哥@Digoal - The Heart,The World.
 

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

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’)



例子 :
char * a, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on in memory - 德哥@Digoal - The Heart,The World.
 


[root@db-172-16-3-33 zzz]# cat b.c
#include

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

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

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, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on in memory - 德哥@Digoal - The Heart,The World.
 
解说 :
以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

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

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

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

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

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

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

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 是内容.



++++++++++<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>++++++++++++++++






=====================================+++++++++++++++++++++++++++++++++++++++++++++++++++++++++===========================================

字和字节和位的关系

1、位(bit) 来自英文 bit, 音译为“比特”, 表示二进制位。

位是计算机内部数据储存的最小单位, 11010100 是一个 8 位二进制数。

一个二进制位只可以表示 0 和 1 两种状态(21);两个二进制位可 以表示 00、01、10、11 四种(22)状态;三位二进制数可表示八种状态(23)……。

2、字节(byte) 字节来自英文 Byte,音译为“拜特”,习惯上用大写的“B”表示。

字节是计算机中数据处理的基本单位。

计算机中以字节为单位存储和解释信息, 规定一个字 节由八个二进制位构成,即 1 个字节等于 8 个比特(1Byte=8bit)。

八位二进制数最小为 00000000,最大为 11111111;通常 1 个字节可以存入一个 ASCII 码,2 个字节可以存放 一个汉字国标码。

3、字 计算机进行数据处理时,一次存取、加工和传送的数据长度称为字(word)。

一个字通常 由一个或多个(一般是字节的整数位)字节构成。

例如 286 微机的字由 2 个字节组成,它 的字长为 16;486 微机的字由 4 个字节组成,它的字长为 32 位机。

计算机的字长决定了其 CPU 一次操作处理实际位数的多少,由此可见计算机的字长越大, 其性能越优越。

另一种说法: 字 在计算机中,一串数码作为一个整体来处理或运算的,称为一个计算机字,简称宇。

字通常 分为若干个字节(每个字节一般是 8 位)。

在存储器中,通常每个单元存储一个字,因此每个 字都是可以寻址的。

字的长度用位数来表示。

在计算机的运算器、控制器中,通常都是以字为单位进行传送的。

宇出现在不问的地址其含 义是不相同。

例如,送往控制器去的字是指令,而送往运算器去的字就是一个数。

在计算机中作为一个整体被存取、传送、处理的二进制数字符串叫做一个字或单元,每个字 中二进制位数的长度,称为字长。

一个字由若干个字节组成,不同的计算机系统的字长是不 同的,常见的有 8 位、16 位、32 位、64 位等,字长越长,计算机一次处理的信息位就越 多,精度就越高,字长是计算机性能的一个重要指标。

目前主流微机都是 32 位机。

注意字与字长的区别,字是单位,而字长是指标,指标需要用单位去衡量。

正象生活中重量 与公斤的关系,公斤是单位,重量是指标,重量需要用公斤加以衡量。

字长 计算机的每个字所包含的位数称为字长。

根据计算机的不同,字长有固定的和可变的两种。

固定字长,即字长度不论什么情况都是固定不变的;可变字长,则在一定范围内,其长度是 可变的。

计算的字长是指它一次可处理的二进创数字的数目。

计算机处理数据的速率, 自然和它一次 能加工的位数以及进行运算的快慢有关。

如果一台计算机的字长是另一台计算机的两倍, 即 使两台计算机的速度相同,在相同的时间内,前者能做的工作是后者的两倍。

图1

一般地,大型计算机的字长为 32―64 位,小型计算机为 12―32 位,而微型计算机为 4 一 16 位。

字长是衡量计算机性能的一个重要因素。

字节 字节是指一小组相邻的二进制数码。

通常是 8 位作为一个字节。

它是构成信息的一个小单 位,并作为一个整体来参加操作,比字小,是构成字的单位。

在微型计算机中,通常用多少字节来表示存储器的存储容量。

字块 在信息处理中,一群字作为一个单元来处理的称为“字块”.也称“字组”。

例如,储存于滋鼓 的一个磁道上的字群就称为一个字块。

在磁带上通常每 120 个字符就间隔一个字块际志, 也称为一个字块。

块与块之间一般留 1.27―2.54 厘米(1/2 一 1 英寸)的间隔。

在大容量存储 中,信息都是以字块为单位而存入的,因此只有字块才是可选址的。

目前,在高速绥冲技术 中也引入了“字块”的概念。

祝你好运8 个位(bit)称为一个字节(byte),两个字节称为一个字(Word),两个自称为一个双字 (dword),两个双字称为一个四字(qword)位(bit)只能是 0,1 代码,因为四个二进制数是一个 16 进制数,所以,两个 16 进制的基数 表示一个字节。

1、附加几种类型的位(bit)和字节(Byte)的问题(以下的内容均是在 MSDN 中的 Windows Data Types 中查找的) (带有”/”的类型可以在 VC 中互用) 长度为一个字节(8 位)的数据类型) bool CHAR/char BYTE/byte BOOLEAN/boolean TCHAR 长度为两个字节(16 位)的数据类型 short/SHORT WORD



======================================++++++++++++++++++++++++++++++++++++++++++++++++++++===============================================

int a[10],a+1为什么是地址

1.

   int a[10];则下列项为地址的是_A___。



A   a+1

 

B   *(a+1)

 

C   a[1]+1

 

D   a[1]





2.如下说法正确的是_B___。



A   全局变量的有效范围从文件开始到文件末尾

 

B   全局变量不属于任何函数

 

C   在函数之内定义的静态变量实际就是全局变量

 

D   main函数内定义的变量是全局变量 





1.选择A
因为:a为数组名,是指指针即地址,指向a[0];a+1是a[1]的地址
其他选项都是数组元素:*(a+1)就是a[1],其他很明显了
2。选择B
因为:全局变量的作用域从他的定义位置开始到文件末尾;
静态变量是从变量的生存期角度讨论变量的存储性质,而全局变量是从变量的作用域讨论,两者不等价
main函数和其他函数一样,在内部定义的变量一定是局部变量。





++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++




首先a是一个数组名,当看到这个a&a时,一般我们的理解都是这个数组的首地址。没错,如果加上打印的话,确实两个值是一样的。

不过&a是整个数组的首地址,a则是数组首元素的地址虽然值一样,但是意义却不相同。

在此之前有必要先看下c程序在内存中的分布图。



下面来看一个例子吧还是。

示例代码:

  1. #include <stdio.h>  
  2. int main(int argc, char *argv[])  
  3. {  
  4.     int i;  
  5.     int a[]={1, 2, 3, 4, 5};  
  6.     int s;  
  7.     int *p = (int *)(&a+1);  
  8.     printf(" a = %p\n&a = %p\n", a, &a);  
  9.     for(i = 0; i < 5; i++)  
  10.         printf("a[%d] = %p\n", i, &a[i]);  
  11.     printf(" p = %p\n&p = %p\n&s = %p\n", p, &p, &s);  
  12.     return 0;  
  13. }  

运行结果:

 

  1. a = 0022FF40  
  2. &a = 0022FF40  
  3. a[0] = 0022FF40  
  4. a[1] = 0022FF44  
  5. a[2] = 0022FF48  
  6. a[3] = 0022FF4C  
  7. a[4] = 0022FF50  
  8.  p = 0022FF54  
  9. &p = 0022FF38  
  10. &s = 0022FF3C  

由上面的运行结果,我们可以知道a&a的区别了。可得下图:



因为是运行在main函数体内的变量,所以这些都是在栈中运行的,所以p指针是指向了a[4]后的那个地址,而p本身的地址是在栈中分配的。

综上可知:

  1. &a+i = a + i*sizeof(a);  
  2. a+i = a +i*sizeof(a[0]);  
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++






指针是个地址。
char *a: a的值包含字符型数据,取消对a的引用(*a)得到一个字符,
不取消引用得到一个字符串,printf("%c",*a)输出一个字符或者printf("%s",a)输出字符串;
char **a: a的值包含一个地址,该地址包含字符字符型数据,取消对a的引用(*a),得到该地址,再取消对该地址的引用(**a),得到一个字符,printf("%c",**a)输出一个字符printf("%s",*a)输出字符串;
char *a[]: a是一个数组,数组的元素是地址(地址其实就是指针),地址包含的值是字符型数据,printf("%c",*a[i])输出一个字符,printf("%s",a[i]))输出字符串;
char *a或char **a可以改变a的值,char a*[]不能改变a的值。
=========================================================>>>>>>>>


file 1:
char *name[] 意思是, 有个数组叫做name, 其中的每个元素都是指针(地址); 作为元素的每个指针是指向一个 char 的(存放着某个char所在的地址).
name[0]中实际上放的是'a'所在的地址, name[1]中实际上放的是'e'所在的地址, name[2]中实际上放的是'g'所在的地址.
char **p 意思是, p 是指针, 指向一个地址(指

针), 而这个被p指向的地址又指向一个 char

p = name 即是 p = &name[0], p = name + i

即是 p = &name[i], &name[i]都是数组元素

所在的地址(元素本身又是地址)

file 2:
int (*p)[4] 意思是, 有个指针p, 指向了某个东西, 这个东西是由连续的4个int组合成的东西.
p = a 的意思就是 p = &a[0], 是取a第一个元素的地址; 而a中每个元素都是int, &a[0]当然就是int类型的地址
刚才说了 p 指向的是4个int组合成东西的地址, 而不是int类型的地址, 它不应指向 int 类型的地址.


作者:匿名用户
链接:https://www.zhihu.com/question/37703728/answer/87717132
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。




========================================================>>>>>>>>>>>>>>>>>>




二级指针的分类

编辑

指向指针变量的指针

在如上的A指向B、B指向C的指向关系中,如果A、B、C都是变量,即C是普通变量,B是一级指针变量,其中存放着C的地址,A是二级指针变量,其中存放着B的地址,则这3个变量分别在内存中占据各自的存储单元,它们之间的相互关系下图所示,相互之间的前后位置关系并不重要.此时,B是一级指针变量,B的值(即C的地址)是一级指针数据;A是二级指针变量,A的值(即B的地址)是二级指针数据.
指针变量和指向对象指针变量和指向对象

指向数组的指针

在C语言中,数组与其它变量在使用上有很大的不同.无论是字符型、整型、实型变量,还是结构体类型或者指针类型的变量,语句中出现变量名都代表对该变量所在内存单元的访问,变量名代表整个变量在内存中的存储单元,可以向该变量赋值,也可以从中取出数据使用.但是定义一个数组之后,数组名并不代表整个数组所占据的内存单元,而是代表数组首元素的地址.


++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

深入 char * ,char ** ,char a[ ] ,char *a[] 内核

原创 2013年02月23日 15:34:13

   C语言中由于指针的灵活性,导致指针能代替数组使用,或者混合使用,这些导致了许多指针和数组的迷惑,因此,刻意再次深入探究了指针和数组这玩意儿,其他类型的数组比较简单,容易混淆的是字符数组和字符指针这两个。。。下面就开始剖析一下这两位的恩怨情仇。。。

 1 数组的本质

   数组是多个元素的集合,在内存中分布在地址相连的单元中,所以可以通过其下标访问不同单元的元素。。

 2 指针。

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

 3 指针的指向?

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

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

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

           不论指向的数据类型为那种,指针变量其本身永远为整型,因为它保存的地址。

    4  字符数组。。。

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

         char  str[10]; 

         定义了一个有十个元素的数组,元素类型为字符。

         C语言中定义一个变量时可以初始化。

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

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

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

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

         而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所保存的字符串的地址。。。

          比如

        

  1. #include <stdio.h>  
  2.  int main()  
  3.  {  
  4.     char *s;  
  5.     s = "hello";  
  6.     printf("%p\n",s);  
  7.     return 0;  
  8.  }  

                          

          

 

      可以看到 s = 0x00422020 ,这也是"China"的首地址

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

     

       字符数组:

       char  str[10] = "hello";

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

       所以printf("%s",str); 本质也是 printf("%s", 地址");

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

    5  char *  与 char  a[ ];

       char  *s;

       char  a[ ] ;

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

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

      因此可以 s = a;

       但是不能 a = s;

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

       当然也可以这样:
       

  1. char  a [ ] = "hello";  
  2. char *s =a;  
  3. for(int i= 0; i < strlen(a) ; i++)  
  4.       printf("%c", s[i]);  
  5. 或  printf("%c",*s++);  

        字符指针可以用 间接操作符 *取其内容,也可以用数组的下标形式 [ ],数组名也可以用 *操作,因为它本身表示一个地址 。。

       比如 printf("%c",*a);  将会打印出 'h'

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

            看一下实例:

        

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

    

      可以看到数组中的四个元素保存了四个内存地址,这四个地址中就代表了四个字符串的首地址,而不是字符串本身。。。

      因此sizeof(a)当然是16了。。

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

       

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

          

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

       char **s;

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

       举例:

       

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

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

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

       即 *s = 00422FB8 = "China";

         这样便可以通过s 操作 a 中的数据

      

  1. printf("%s",*s);  
  2. printf("%s",a[0]);  
  3. printf("%s",*a);  

      都是一样的。。。

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

      再看一个易错的点:

      

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

      这样是错误的,

       因为  s 的类型是 char **  而 "hello world "的类型是 char *

       虽然都是地址, 但是指向的类型不一样,因此,不能这样用。,从其本质来分析,"hello world",代表一个地址,比如0x003001,这个地址中的内容是 'h'

  ,为 char 型,而 s 也保存一个地址 ,这个地址中的内容(*s) 是char * ,是一个指针类型, 所以两者类型是不一样的。 。。

  如果是这样呢?
    

  1. char  **s;  
  2. *s = "hello world";  

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

       why??

      咱来慢慢推敲一下。。

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

      举例:

      

  1. s = 0x1000;  

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

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

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

      如果直接 char  **s;

      

  1. *s = "hello world";  

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

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

      

  1. char  **s ;  
  2. s = (char **) malloc(sizeof(char**));  
  3. *s =  "hello world";  

      这样 s 给分配了了一个可用的地址,比如 s = 0x412f;

      然后在 0x412f所在的内存中的位置,保存 "hello world"的值。。

    再如:

   

  1. #include  <stdio.h>  
  2. void  buf( char **s)  
  3.  {  
  4.         *s = "message";  
  5.  }  
  6.  int main()  
  7.  {  
  8.      char *s ;  
  9.      buf(&s);  
  10.      printf("%s\n",s);  
  11.  }  

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






+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++



C++二级指针char **的用法

char **p和char *p[]基本一样,
区别讲不出来,写个例子体会吧。
复制代码
#include <iostream>
using  namespace std;
void main()
{
     //  第一种初始化方法
     char **p =  new  char *[ 10];
     //  赋值后正常使用
    p[ 0] =  " aaa ";
    cout<<p[ 0]<<endl;
     //  值可以改变
    p[ 0] =  " bbb ";
     //  未赋值使用会崩。编译能过。
    
// cout<<p[1]<<endl;
    
//  越界赋值,编译能过,运行能过,输出时崩。
    
// p[100] = "ccc";
    
// cout<<p[100]<<endl;

    
//  第二种初始化方法
    unsigned  int i =  0;
     char** pP = NULL;
    pP = ( char**)calloc( 128sizeof( char*));
     for (i =  0; i <  128; ++i)
    {
        pP[i] = ( char*)calloc( 128sizeof( char));
    }
     //  这种初始化方法,好像不存在越界。
    pP[ 1000] =  " ddd ";
    cout<<pP[ 1000]<<endl;
}
复制代码
char *c与char c[]的区别是前者是常量。
说点题外话,学习编程应该是先学会用,在使用的过程中慢慢领悟。
对于有点难度的知识点,一下子可能没有办法完全搞清楚。
新手需要源码,留下邮箱索取。
url: http://greatverve.cnblogs.com/archive/2012/11/23/cpp-char-string.html 
int main(int argc, char **argv)中两个参考的用法 
argc,argv 用命令行编译程序时有用。 

  主函数main中变量(int argc,char *argv[ ])的含义 

  有些编译器允许将main()的返回类型声明为void,这已不再是合法的C++ 

  main(int argc, char *argv[ ], char *env[ ])才是UNIX和Linux中的标准写法。 

  argc: 整数,用来统计你运行程序时送给main函数的命令行参数的个数 

  * argv: 字符串数组,用来存放指向你的字符串参数的指针数组,每一个元素指向一个参数 

  argv[0] 指向程序运行的全路径名 

  argv[1] 指向在DOS命令行中执行程序名后的第一个字符串 

  argv[2] 指向执行程序名后的第二个字符串 

 

比如我们编译好了的程序叫hello,你运行./hello   ni   hao
那么 argc 就是3, 
argv[0]   --->   ./hello
argv[1]  ----->  ni
argv[2]  -----> hao

也就是说他们是运行程序时所传的参数个数,和参数。
记住了,argv[0] 是程序路径名,就是程序本身,从 argv[1]开始才是参数

const char*, char const*, char*const的区别 

 const char*, char const*, char*const的区别问题几乎是C++面试中每次都会有的题目。 这个知识易混点之前是看过了,今天做Linux上写GTK程序时又出现个Warning,发散一下又想到这个问题,于是翻起来重嚼一下。

事实上这个概念谁都有只是三种声明方式非常相似:

Bjarne在他的The C++ Programming Language里面给出过一个助记的方法:

把一个声明从右向左读。

char * const cp; ( * 读成 pointer to ) cp is a const pointer to char

const char * p; p is a pointer to const char;

char const * p; 同上因为C++里面没有const*的运算符,所以const只能属于前面的类型。

C++标准规定,const关键字放在类型或变量名之前等价的。

const int n=5; //same as below

int const m=10

结论:

char * const cp     : 定义一个指向字符的指针常数,即const指针

const char* p       : 定义一个指向字符常数的指针

char const* p       : 等同于const char* p

 

const   char   **是一个指向指针的指针,那个指针又指向一个字符串常量。   
       char   **也是一个指向指针的指针,那个指针又指向一个字符串变量。

CString,string,char*的综合比较及相互转换

string和CString均是字符串模板类,string为标准模板类(STL)定义的字符串类,已经纳入C++标准之中;

CString(typedef CStringT<TCHAR, StrTraitMFC<TCHAR>> CString)为Visual C++中最常用的字符串类,继承自CSimpleStringT类,主要应用在MFC和ATL编程中,主要数据类型有char(应用于ANSI),wchar_t(unicode),TCHAR(ANSI与unicode均可);

char*为C编程中最常用的字符串指针,一般以’/0’为结束标志;

(二) 构造

²        string是方便的,可以从几乎所有的字符串构造而来,包括CString和char*;

²        CString次之,可以从基本的一些字符串变量构造而来,包括char*等;

²        char*没有构造函数,仅可以赋值;

²        举例:

char* psz = “joise”;

CString cstr( psz );

string str( cstr );

(三) 运算符重载

a)       operator=

²        string是最方便的,几乎可以直接用所有的字符串赋值,包括CString和char*;

²        CString次之,可以直接用些基本的字符串赋值,包括char*等;

²        char*只能由指针赋值,并且是极危险的操作,建议使用strcpy或者memcpy,而且char*在声明的时候如未赋初值建议先设为NULL,以避免野指针,令你抓狂;

²        举例:

char *psz = NULL;

psz = new char[10]; //当然,以上的直接写成char *psz = new char[10];也是一样

memset( psz, 0, 10 );

strcpy( psz, “joise” );

CString cstr;

cstr = psz;

string str;

str = psz;

str = cstr;

delete []psz;

b)          operator+

²        string与CString差不多,可以直接与char*进行加法,但不可以相互使用+运算符,即string str = str + cstr是非法的,须转换成char*;

²        char*没有+运算,只能使用strcat把两个指针连在一起;

²        举例:

char* psz = “joise”;

CString cstr = psz;

cstr = cstr + psz;

string str = psz;

str = str + str + psz;

strcat( psz, psz );

strcat( psz, cstr );//合法

strcat( psz, str );//非法,由此可见,CString可自动转换为const char*,而string不行

c)      operator +=

²        string是最强大的,几乎可以与所有的字符串变量+=,包括CString和char*;

²        CString次之,可以与基本的一些字符串变量进行+=而来,包括char*等;

²        char*没有+=运算符,只能使用strcat把两个指针连在一起;

d)      operator[]

²    CString最好,当越界时会抛出断言异常;

²    string与char*下标越界结果未定义;

²        举例:

char* psz = “joise”;

CString cstr = psz;

cout << cstr[8];

string str = psz;

cout << str[8];

cout << psz[8];

e)       operator== 、operator!=、operator> 、operator< 、operator>= 、perator<=

²        CString与string之间不可以进行比较,但均可以与char*进行比较,并且比较的是值,而不是地址;

       cout << ( psz == cstr );

       cout << ( psz == str );

       cout << ( str == psz );

       cout << ( cstr == psz );//以上代码返回均为1 

1 string 使用 
1.1 充分使用string 操作符 
1.2 眼花缭乱的string find 函数 
1.3 string insert, replace, erase 2 string 和 C风格字符串 
3 string 和 Charactor Traits 
4 string 建议 
5 小结 
6 附录前言: string 的角色

C++ 语言是个十分优秀的语言,但优秀并不表示完美。还是有许多人不愿意使用C或者C++,为什么?原因众多,其中之一就是C/C++的文本处理功能太麻烦,用起来很不方便。以前没有接触过其他语言时,每当别人这么说,我总是不屑一顾,认为他们根本就没有领会C++的精华,或者不太懂C++,现在我接触perl, php, 和Shell脚本以后,开始理解了以前为什么有人说C++文本处理不方便了。

举例来说,如果文本格式是:用户名 电话号码,文件名name.txt 
Tom 23245332
Jenny 22231231
Heny 22183942
Tom 23245332
...


现在我们需要对用户名排序,且只输出不同的姓名。

那么在shell 编程中,可以这样用: 
awk '{print $1}' name.txt | sort | uniq


简单吧?

如果使用C/C++ 就麻烦了,他需要做以下工作: 
先打开文件,检测文件是否打开,如果失败,则退出。 
声明一个足够大得二维字符数组或者一个字符指针数组 
读入一行到字符空间 
然后分析一行的结构,找到空格,存入字符数组中。 
关闭文件 
写一个排序函数,或者使用写一个比较函数,使用qsort排序 
遍历数组,比较是否有相同的,如果有,则要删除,copy... 
输出信息


你可以用C++或者C语言去实现这个流程。如果一个人的主要工作就是处理这种类似的文本(例如做apache的日志统计和分析),你说他会喜欢C/C++么?


当然,有了STL,这些处理会得到很大的简化。我们可以使用 fstream来代替麻烦的fopen fread fclose, 用vector 来代替数组。最重要的是用 string来代替char * 数组,使用sort排序算法来排序,用unique 函数来去重。听起来好像很不错 。看看下面代码(例程1): 
#i nclude <string>
#i nclude <iostream>
#i nclude <algorithm>
#i nclude <vector>
#i nclude <fstream>
using namespace std;
int main(){
             ifstream in("name.txt");
             string strtmp;
             vector<string> vect;
             while(getline(in, strtmp, '/n'))
             vect.push_back(strtmp.substr(0, strtmp.find(' ')));
             sort(vect.begin(), vect.end());
             vector<string>::iterator it=unique(vect.begin(), vect.end());
             copy(vect.begin(), it, ostream_iterator<string>(cout, "/n"));
             return 0;
}


也还不错吧,至少会比想象得要简单得多!(代码里面没有对错误进行处理,只是为了说明问题,不要效仿).

当然,在这个文本格式中,不用vector而使用map会更有扩充性,例如,还可通过人名找电话号码等等,但是使用了map就不那么好用sort了。你可以用map试一试。


这里string的作用不只是可以存储字符串,还可以提供字符串的比较,查找等。在sort和unique函数中就默认使用了less 和equal_to函数, 上面的一段代码,其实使用了string的以下功能: 
存储功能,在getline() 函数中 
查找功能,在find() 函数中 
子串功能,在substr() 函数中 
string operator < , 默认在sort() 函数中调用 
string operator == , 默认在unique() 函数中调用


总之,有了string 后,C++的字符文本处理功能总算得到了一定补充,加上配合STL其他容器使用,其在文本处理上的功能已经与perl, shell, php的距离缩小很多了。 因此掌握string 会让你的工作事半功倍。


1 string 使用


其实,string并不是一个单独的容器,只是basic_string 模板类的一个typedef 而已,相对应的还有wstring, 你在string 头文件中你会发现下面的代码:

extern "C++" {
             typedef basic_string <char> string;
             typedef basic_string <wchar_t> wstring;
} // extern "C++"

由于只是解释string的用法,如果没有特殊的说明,本文并不区分string 和 basic_string的区别。

string 其实相当于一个保存字符的序列容器,因此除了有字符串的一些常用操作以外,还有包含了所有的序列容器的操作。字符串的常用操作包括:增加、删除、修改、查找比较、链接、输入、输出等。详细函数列表参看附录。不要害怕这么多函数,其实有许多是序列容器带有的,平时不一定用的上。


如果你要想了解所有函数的详细用法,你需要查看basic_string,或者下载STL编程手册。这里通过实例介绍一些常用函数。 
1.1 充分使用string 操作符


string 重载了许多操作符,包括 +, +=, <, =, , [], <<, >>等,正式这些操作符,对字符串操作非常方便。先看看下面这个例子:tt.cpp(例程2)

#i nclude <string>
#i nclude <iostream>
using namespace std;
int main(){
             string strinfo="Please input your name:";
             cout << strinfo ;
             cin >> strinfo;
             if( strinfo == "winter" )
             cout << "you are winter!"<<endl;
             else if( strinfo != "wende" )
             cout << "you are not wende!"<<endl;
             else if( strinfo < "winter")
             cout << "your name should be ahead of winter"<<endl;
             else 
             cout << "your name should be after of winter"<<endl;
             strinfo += " , Welcome to China!";
             cout << strinfo<<endl;
             cout <<"Your name is :"<<endl;
             string strtmp = "How are you? " + strinfo;
             for(int i = 0 ; i < strtmp.size(); i ++)
             cout<<strtmp[i];
             return 0;
}

下面是程序的输出 
-bash-2.05b$ make tt
c++       -O -pipe -march=pentiumpro       tt.cpp       -o tt
-bash-2.05b$ ./tt
Please input your name:Hero
you are not wende!
Hero , Welcome to China!
How are you? Hero , Welcome to China!


有了这些操作符,在STL中仿函数都可以直接使用string作为参数,例如 less, great, equal_to 等,因此在把string作为参数传递的时候,它的使用和int 或者float等已经没有什么区别了。例如,你可以使用: 
map<string, int> mymap;
//以上默认使用了 less<string>


有了 operator + 以后,你可以直接连加,例如:

string strinfo="Winter";
string strlast="Hello " + strinfo + "!";
//你还可以这样:
string strtest="Hello " + strinfo + " Welcome" + " to China" + " !";

看见其中的特点了吗?只要你的等式里面有一个 string 对象,你就可以一直连续"+",但有一点需要保证的是,在开始的两项中,必须有一项是 string 对象。其原理很简单:

系统遇到"+"号,发现有一项是string 对象。 
系统把另一项转化为一个临时 string 对象。 
执行 operator + 操作,返回新的临时string 对象。 
如果又发现"+"号,继续第一步操作。

由于这个等式是由左到右开始检测执行,如果开始两项都是const char* ,程序自己并没有定义两个const char* 的加法,编译的时候肯定就有问题了。

有了操作符以后,assign(), append(), compare(), at()等函数,除非有一些特殊的需求时,一般是用不上。当然at()函数还有一个功能,那就是检查下标是否合法,如果是使用: 
string str="winter";
//下面一行有可能会引起程序中断错误
str[100]='!';
//下面会抛出异常:throws: out_of_range
cout<<str.at(100)<<endl;


了解了吗?如果你希望效率高,还是使用[]来访问,如果你希望稳定性好,最好使用at()来访问。

1.2 眼花缭乱的string find 函数

由于查找是使用最为频繁的功能之一,string 提供了非常丰富的查找函数。其列表如下: 
函数名 描述 find 查找 rfind 反向查找 find_first_of 查找包含子串中的任何字符,返回第一个位置 find_first_not_of 查找不包含子串中的任何字符,返回第一个位置 find_last_of 查找包含子串中的任何字符,返回最后一个位置 find_last_not_of 查找不包含子串中的任何字符,返回最后一个位置 以上函数都是被重载了4次,以下是以find_first_of 函数为例说明他们的参数,其他函数和其参数一样,也就是说总共有24个函数 :

size_type find_first_of(const basic_string& s, size_type pos = 0)
size_type find_first_of(const charT* s, size_type pos, size_type n)
size_type find_first_of(const charT* s, size_type pos = 0)
size_type find_first_of(charT c, size_type pos = 0)

所有的查找函数都返回一个size_type类型,这个返回值一般都是所找到字符串的位置,如果没有找到,则返回string::npos。有一点需要特别注意,所有和string::npos的比较一定要用string::size_type来使用,不要直接使用int 或者unsigned int等类型。其实string::npos表示的是-1, 看看头文件:

template <class _CharT, class _Traits, class _Alloc> 
const basic_string<_CharT,_Traits,_Alloc>::size_type 
basic_string<_CharT,_Traits,_Alloc>::npos 
= basic_string<_CharT,_Traits,_Alloc>::size_type) -1;

find 和 rfind 都还比较容易理解,一个是正向匹配,一个是逆向匹配,后面的参数pos都是用来指定起始查找位置。对于find_first_of 和find_last_of 就不是那么好理解。


find_first_of 是给定一个要查找的字符集,找到这个字符集中任何一个字符所在字符串中第一个位置。或许看一个例子更容易明白。


有这样一个需求:过滤一行开头和结尾的所有非英文字符。看看用string 如何实现: 
#i nclude <string>
#i nclude <iostream>
using namespace std;
int main(){
             string strinfo="        //*---Hello Word!......------";
             string strset="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
             int first = strinfo.find_first_of(strset);
             if(first == string::npos) { 
                     cout<<"not find any characters"<<endl;
                     return -1;
             } 
             int last = strinfo.find_last_of(strset);
             if(last == string::npos) { 
                     cout<<"not find any characters"<<endl;
                     return -1;
             } 
             cout << strinfo.substr(first, last - first + 1)<<endl;
             return 0;
}


这里把所有的英文字母大小写作为了需要查找的字符集,先查找第一个英文字母的位置,然后查找最后一个英文字母的位置,然后用substr 来的到中间的一部分,用于输出结果。下面就是其结果:

Hello Word

前面的符号和后面的符号都没有了。像这种用法可以用来查找分隔符,从而把一个连续的字符串分割成为几部分,达到 shell 命令中的 awk 的用法。特别是当分隔符有多个的时候,可以一次指定。例如有这样的需求:

张三|3456123, 湖南
李四,4564234| 湖北
王小二, 4433253|北京
...

我们需要以 "|" ","为分隔符,同时又要过滤空格,把每行分成相应的字段。可以作为你的一个家庭作业来试试,要求代码简洁。 
1.3 string insert, replace, erase 
了解了string 的操作符,查找函数和substr,其实就已经了解了string的80%的操作了。insert函数, replace函数和erase函数在使用起来相对简单。下面以一个例子来说明其应用。 
string只是提供了按照位置和区间的replace函数,而不能用一个string字串来替换指定string中的另一个字串。这里写一个函数来实现这个功能:

void string_replace(string & strBig, const string & strsrc, const string &strdst) {
             string::size_type pos=0;
             string::size_type srclen=strsrc.size();
             string::size_type dstlen=strdst.size();
             while( (pos=strBig.find(strsrc, pos)) != string::npos){
                     strBig.replace(pos, srclen, strdst);
                     pos += dstlen;
             }
}看看如何调用: 
#i nclude <string>
#i nclude <iostream>
using namespace std;
int main() {
             string strinfo="This is Winter, Winter is a programmer. Do you know Winter?";
             cout<<"Orign string is :/n"<<strinfo<<endl;
             string_replace(strinfo, "Winter", "wende");
             cout<<"After replace Winter with wende, the string is :/n"<<strinfo<<endl;
             return 0;
}其输出结果: 
Orign string is :
This is Winter, Winter is a programmer. Do you know Winter?
After replace Winter with wende, the string is :
This is wende, wende is a programmer. Do you know wende?如果不用replace函数,则可以使用erase和insert来替换,也能实现string_replace函数的功能: 
void string_replace(string & strBig, const string & strsrc, const string &strdst) {
             string::size_type pos=0;
             string::size_type srclen=strsrc.size();
             string::size_type dstlen=strdst.size();
             while( (pos=strBig.find(strsrc, pos)) != string::npos){
                     strBig.erase(pos, srclen);
                     strBig.insert(pos, strdst);
                     pos += dstlen;
             }
}当然,这种方法没有使用replace来得直接。 
2 string 和 C风格字符串 
现在看了这么多例子,发现const char* 可以和string 直接转换,例如我们在上面的例子中,使用 
string_replace(strinfo, "Winter", "wende");来代用 
void string_replace(string & strBig, const string & strsrc, const string &strdst) 在C语言中只有char* 和 const char*,为了使用起来方便,string提供了三个函数满足其要求: 
const charT* c_str() const 
const charT* data() const 
size_type copy(charT* buf, size_type n, size_type pos = 0) const 其中: 
c_str 直接返回一个以/0结尾的字符串。 
data 直接以数组方式返回string的内容,其大小为size()的返回值,结尾并没有/0字符。 
copy 把string的内容拷贝到buf空间中。 
你或许会问,c_str()的功能包含data(),那还需要data()函数干什么?看看源码: 
const charT* c_str () const
{ if (length () == 0) return ""; terminate (); return data (); }原来c_str()的流程是:先调用terminate(),然后在返回data()。因此如果你对效率要求比较高,而且你的处理又不一定需要以/0的方式结束,你最好选择data()。但是对于一般的C函数中,需要以const char*为输入参数,你就要使用c_str()函数。 
对于c_str() data()函数,返回的数组都是由string本身拥有,千万不可修改其内容。其原因是许多string实现的时候采用了引用机制,也就是说,有可能几个string使用同一个字符存储空间。而且你不能使用sizeof(string)来查看其大小。详细的解释和实现查看Effective STL的条款15:小心string实现的多样性。

另外在你的程序中,只在需要时才使用c_str()或者data()得到字符串,每调用一次,下次再使用就会失效,如:

string strinfo("this is Winter");
...
//最好的方式是:
foo(strinfo.c_str());
//也可以这么用:
const char* pstr=strinfo.c_str();
foo(pstr);
//不要再使用了pstr了, 下面的操作已经使pstr无效了。
strinfo += " Hello!";
foo(pstr);//错误!会遇到什么错误?当你幸运的时候pstr可能只是指向"this is Winter Hello!"的字符串,如果不幸运,就会导致程序出现其他问题,总会有一些不可遇见的错误。总之不会是你预期的那个结果。

3 string 和 Charactor Traits 
了解了string的用法,该详细看看string的真相了。前面提到string 只是basic_string的一个typedef。看看basic_string 的参数: 
template <class charT, class traits = char_traits<charT>,
class Allocator = allocator<charT> >
class basic_string
{
             //...
}char_traits不仅是在basic_string 中有用,在basic_istream 和 basic_ostream中也需要用到。 
就像Steve Donovan在过度使用C++模板中提到的,这些确实有些过头了,要不是系统自己定义了相关的一些属性,而且用了个typedef,否则还真不知道如何使用。

但复杂总有复杂道理。有了char_traits,你可以定义自己的字符串类型。当然,有了char_traits < char > 和char_traits < wchar_t > 你的需求使用已经足够了,为了更好的理解string ,咱们来看看char_traits都有哪些要求。

如果你希望使用你自己定义的字符,你必须定义包含下列成员的结构: 表达式       描述  
char_type       字符类型  
int_type       int 类型  
pos_type       位置类型  
off_type       表示位置之间距离的类型  
state_type       表示状态的类型  
assign(c1,c2)       把字符c2赋值给c1  
eq(c1,c2)       判断c1,c2 是否相等  
lt(c1,c2)       判断c1是否小于c2  
length(str)       判断str的长度  
compare(s1,s2,n)       比较s1和s2的前n个字符  
copy(s1,s2, n)       把s2的前n个字符拷贝到s1中  
move(s1,s2, n)       把s2中的前n个字符移动到s1中  
assign(s,n,c)       把s中的前n个字符赋值为c  
find(s,n,c)       在s的前n个字符内查找c  
eof()       返回end-of-file  
to_int_type(c)       将c转换成int_type  
to_char_type(i)       将i转换成char_type  
not_eof(i)       判断i是否为EOF  
eq_int_type(i1,i2)       判断i1和i2是否相等  
想看看实际的例子,你可以看看sgi STL的char_traits结构源码.

现在默认的string版本中,并不支持忽略大小写的比较函数和查找函数,如果你想练练手,你可以试试改写一个char_traits , 然后生成一个case_string类, 也可以在string 上做继承,然后派生一个新的类,例如:ext_string,提供一些常用的功能,例如:

定义分隔符。给定分隔符,把string分为几个字段。 
提供替换功能。例如,用winter, 替换字符串中的wende 
大小写处理。例如,忽略大小写比较,转换等 
整形转换。例如把"123"字符串转换为123数字。 
这些都是常用的功能,如果你有兴趣可以试试。其实有人已经实现了,看看Extended STL string。如果你想偷懒,下载一个头文件就可以用,有了它确实方便了很多。要是有人能提供一个支持正则表达式的string,我会非常乐意用。

4 string 建议 
使用string 的方便性就不用再说了,这里要重点强调的是string的安全性。 
string并不是万能的,如果你在一个大工程中需要频繁处理字符串,而且有可能是多线程,那么你一定要慎重(当然,在多线程下你使用任何STL容器都要慎重)。 
string的实现和效率并不一定是你想象的那样,如果你对大量的字符串操作,而且特别关心其效率,那么你有两个选择,首先,你可以看看你使用的STL版本中string实现的源码;另一选择是你自己写一个只提供你需要的功能的类。 
string的c_str()函数是用来得到C语言风格的字符串,其返回的指针不能修改其空间。而且在下一次使用时重新调用获得新的指针。 
string的data()函数返回的字符串指针不会以'/0'结束,千万不可忽视。 
尽量去使用操作符,这样可以让程序更加易懂(特别是那些脚本程序员也可以看懂) 
5 小结 
难怪有人说:
string 使用方便功能强,我们一直用它!

6 附录 
string 函数列表 函数名       描述  
begin       得到指向字符串开头的Iterator  
end       得到指向字符串结尾的Iterator  
rbegin       得到指向反向字符串开头的Iterator  
rend       得到指向反向字符串结尾的Iterator  
size       得到字符串的大小  
length       和size函数功能相同  
max_size       字符串可能的最大大小  
capacity       在不重新分配内存的情况下,字符串可能的大小  
empty       判断是否为空  
operator[]       取第几个元素,相当于数组  
c_str       取得C风格的const char* 字符串  
data       取得字符串内容地址  
operator=       赋值操作符  
reserve       预留空间  
swap       交换函数  
insert       插入字符  
append       追加字符  
push_back       追加字符  
operator+=       += 操作符  
erase       删除字符串  
clear       清空字符容器中所有内容  
resize       重新分配空间  
assign       和赋值操作符一样  
replace       替代  
copy       字符串到空间  
find       查找  
rfind       反向查找  
find_first_of       查找包含子串中的任何字符,返回第一个位置  
find_first_not_of       查找不包含子串中的任何字符,返回第一个位置  
find_last_of       查找包含子串中的任何字符,返回最后一个位置  
find_last_not_of       查找不包含子串中的任何字符,返回最后一个位置  
substr       得到字串  
compare       比较字符串  
operator+       字符串链接  
operator==       判断是否相等  
operator!=       判断是否不等于  
operator<       判断是否小于  
operator>>       从输入流中读入字符串  
operator<<       字符串写入输出流  
getline       从输入流中读入一行 

CString,int,string,char*之间的转换2007年01月06日 星期六 11:11 A.M.
string 转 CString  
CString.format("%s", string.c_str()); 

char 转 CString  
CString.format("%s", char*); 

char 转 string  
string s(char *); 

string 转 char *  
char *p = string.c_str(); 

CString 转 string  
string s(CString.GetBuffer()); 

1,string -> CString  
CString.format("%s", string.c_str());  
用c_str()确实比data()要好.  
2,char -> string  
string s(char *);  
你的只能初始化,在不是初始化的地方最好还是用assign().  
3,CString -> string  
string s(CString.GetBuffer());  
GetBuffer()后一定要ReleaseBuffer(),否则就没有释放缓冲区所占的空间. 


《C++标准函数库》中说的  
有三个函数可以将字符串的内容转换为字符数组和C—string  
1.data(),返回没有”/0“的字符串数组  
2,c_str(),返回有”/0“的字符串数组  
3,copy() 

--------------------------------------------------------------- 

CString与int、char*、char[100]之间的转换- - 


CString与int、char*、char[100]之间的转换- - 

 

CString互转int 

将字符转换为整数,可以使用atoi、_atoi64或atol。  
而将数字转换为CString变量,可以使用CString的Format函数。如  
CString s;  
int i = 64;  
s.Format("%d", i)  
Format函数的功能很强,值得你研究一下。 

void CStrDlg::OnButton1()  
{  
// TODO: Add your control notification handler code here  
CString  
ss="1212.12";  
int temp=atoi(ss);  
CString aa;  
aa.Format("%d",temp);  
AfxMessageBox("var is " + aa);  

sart.Format("%s",buf); 

CString互转char* 

///char * TO cstring  
CString strtest;  
char * charpoint;  
charpoint="give string a value";  
strtest=charpoint; 


///cstring TO char *  
charpoint=strtest.GetBuffer(strtest.GetLength()); 

标准C里没有string,char *==char []==string 

可以用CString.Format("%s",char *)这个方法来将char *转成CString。要把CString转成char *,用操作符(LPCSTR)CString就可以了。 


CString转换 char[100] 

char a[100];  
CString str("aaaaaa");  
strncpy(a,(LPCTSTR)str,sizeof(a));


///


      //string 转换为 char 型
      char* str = strdup ( SendData.strSql.c_str() );
      cout << str << endl;

      char 转换为 string 型
      char* str = "char 转换为 string 型";
      SendData.strSql = str;

 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++                                                                                                                             ++

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


C++ const用法 尽可能使用const

  C++ const 允许指定一个语义约束,编译器会强制实施这个约束,允许程序员告诉编译器某值是保持不变的。如果在编程中确实有某个值保持不变,就应该明确使用const,这样可以获得编译器的帮助。

1.const 修饰成员变量 

复制代码
 1 #include<iostream>
 2 using namespace std;
 3 int main(){
 4     int a1=3;   ///non-const data
 5     const int a2=a1;    ///const data
 6 
 7     int * a3 = &a1;   ///non-const data,non-const pointer
 8     const int * a4 = &a1;   ///const data,non-const pointer
 9     int * const a5 = &a1;   ///non-const data,const pointer
10     int const * const a6 = &a1;   ///const data,const pointer
11     const int * const a7 = &a1;   ///const data,const pointer
12 
13     return 0;
14 }
复制代码

const修饰指针变量时:

  (1)只有一个const,如果const位于*左侧,表示指针所指数据是常量,不能通过解引用修改该数据;指针本身是变量,可以指向其他的内存单元。

  (2)只有一个const,如果const位于*右侧,表示指针本身是常量,不能指向其他内存地址;指针所指的数据可以通过解引用修改。

  (3)两个const,*左右各一个,表示指针和指针所指数据都不能修改。

2.const修饰函数参数

  传递过来的参数在函数内不可以改变,与上面修饰变量时的性质一样。

void testModifyConst(const int _x) {
     _x=5;   ///编译出错
}

3.const修饰成员函数

(1)const修饰的成员函数不能修改任何的成员变量(mutable修饰的变量除外)

(2)const成员函数不能调用非onst成员函数,因为非const成员函数可以会修改成员变量

复制代码
 1 #include <iostream>
 2 using namespace std;
 3 class Point{
 4     public :
 5     Point(int _x):x(_x){}
 6 
 7     void testConstFunction(int _x) const{
 8 
 9         ///错误,在const成员函数中,不能修改任何类成员变量
10         x=_x;
11 
12         ///错误,const成员函数不能调用非onst成员函数,因为非const成员函数可以会修改成员变量
13         modify_x(_x);
14     }
15 
16     void modify_x(int _x){
17         x=_x;
18     }
19 
20     int x;
21 };
复制代码

 4.const修饰函数返回值

(1)指针传递

如果返回const data,non-const pointer,返回值也必须赋给const data,non-const pointer。因为指针指向的数据是常量不能修改。

复制代码
 1 const int * mallocA(){  ///const data,non-const pointer
 2     int *a=new int(2);
 3     return a;
 4 }
 5 
 6 int main()
 7 {
 8     const int *a = mallocA();
 9     ///int *b = mallocA();  ///编译错误
10     return 0;
11 }
复制代码

(2)值传递

 如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。所以,对于值传递来说,加const没有太多意义。

所以:

  不要把函数int GetInt(void) 写成const int GetInt(void)。
  不要把函数A GetA(void) 写成const A GetA(void),其中A 为用户自定义的数据类型。

 

  在编程中要尽可能多的使用const,这样可以获得编译器的帮助,以便写出健壮性的代码。


++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++



char * a, char ** a, char * a[], char a[][],&n

(2013-02-03 03:34:54)
 分类:技术
进入正题, 讲讲 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

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




图示 : 
char * a, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on in memory - 德哥@Digoal - The Heart,The World.
   
char * a, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on in memory - 德哥@Digoal - The Heart,The World.
 

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

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’)

例子 : 
char * a, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on in memory - 德哥@Digoal - The Heart,The World.
 
 [root@db-172-16-3-33 zzz]# cat b.c#includeint 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 && ./ba: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:%p0x7fff5da3f550 一致, 因为编译器把数组名替换成了数组所在内存的位置. 
(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

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

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, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on in memory - 德哥@Digoal - The Heart,The World.
 
解说 :
以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

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

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

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

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

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

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

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 是内容.
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值