user-0 提问: 有关二级指针引发的对类型赋值的考虑

回复:
user: user-1
content: 引用 38 楼 cao_julians 的回复:a[0] = "123";  b[0] = "987";     a[1] = "456";  b[1] = "654";     a[2] = "789";  b[2] = "321"; =============================== 用"-->" 表示指针指向：   a[0] --> "123";   b[0] --> "987";     a[1] -->"456";   b[1]--> "654";     a[2] -->"789";    b[2] --> "321";        ========================            p = a; ========================   p --> a[0] --> "123"  ----------------------------  p -----*p ------**p         //如果用p定位的话 =======================     printf("%s\n",*a);//输出123     printf("%s\n",*p);//输出123              *p = *b;       //你修改的是*p，不是p.  *b是什么？是*(&b[0]),即(*&)b[0],   即b[0], 指向"789"的首字符的指针 ======================= p ---->b[0]---->"789"     //抱歉这里我打字打错了，应该是p ---->a[0]---->"789" -------------------------- p-----*p-------**p p指向的a[0]被赋成b[0]的值，即a[0]-->"789"

user: user-2
content: 引用 36 楼 u010913001 的回复:[quote=引用 33 楼 zhao4zhong1 的回复:] 计算机组成原理→DOS命令→汇编语言→C语言（不包括C++）、代码书写规范→数据结构、编译原理、操作系统→计算机网络、数据库原理、正则表达式→其它语言（包括C++）、架构……  对学习编程者的忠告： 眼过千遍不如手过一遍！ 书看千行不如手敲一行！ 手敲千行不如单步一行！ 单步源代码千行不如单步对应汇编一行！  VC调试时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化，这样过一遍不就啥都明白了吗。 对VC来说，所谓‘调试时’就是编译连接通过以后，按F10或F11键单步执行一步以后的时候，或者在某行按F9设了断点后按F5执行停在该断点处的时候。 （Turbo C或Borland C用Turbo Debugger调试,Linux或Unix下用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。）  想要从本质上理解C指针，必须学习汇编以及C和汇编的对应关系。 从汇编的角度理解和学习C语言的指针，原本看似复杂的东西就会变得非常简单！ 指针即地址。“地址又是啥？”“只能从汇编语言和计算机组成原理的角度去解释了。” 但我又不得不承认：  有那么些人喜欢或者适合用“先具体再抽象”的方法学习和理解复杂事物；  而另一些人喜欢或者适合用“先抽象再具体”的方法学习和理解复杂事物。 而我本人属前者。  不要企图依赖输出指针相关表达式的值【比如printf("%p\n",...)】来理解指针的本质， 而要依赖调试时的反汇编窗口中的C/C++代码【比如void *p=...】及其对应汇编指令以及内存窗口中的内存地址和内存值来理解指针的本质。  这辈子不看内存地址和内存值；只画链表、指针示意图，画堆栈示意图，画各种示意图，甚至自己没画过而只看过书上的图……能从本质上理解指针、理解函数参数传递吗？本人深表怀疑！ 这辈子不种麦不收麦不将麦粒拿去磨面；只吃馒头、吃面条、吃面包、……甚至从没看过别人怎么蒸馒头，压面条，烤面包，……能从本质上理解面粉、理解面食吗？本人深表怀疑！！  提醒： “学习用汇编语言写程序” 和 “VC调试(TC或BC用TD调试)时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化，这样过一遍不就啥都明白了吗。 （Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。） 想要从本质上理解C指针，必须学习C和汇编的对应关系。” 不是一回事！  不要迷信书、考题、老师、回帖； 要迷信CPU、编译器、调试器、运行结果。 并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。 任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实！  有人说一套做一套，你相信他说的还是相信他做的？ 其实严格来说这个世界上古往今来所有人都是说一套做一套，不是吗？  不要写连自己也预测不了结果的代码！  电脑内存或文件内容只是一个一维二进制字节数组及其对应的二进制地址； 人脑才将电脑内存或文件内容中的这个一维二进制字节数组及其对应的二进制地址的某些部分看成是整数、有符号数/无符号数、浮点数、复数、英文字母、阿拉伯数字、中文/韩文/法文……字符/字符串、汇编指令、函数、函数参数、堆、栈、数组、指针、数组指针、指针数组、数组的数组、指针的指针、二维数组、字符点阵、字符笔画的坐标、黑白二值图片、灰度图片、彩色图片、录音、视频、指纹信息、身份证信息……  十字链表交换任意两个节点C源代码（C指针应用终极挑战）http://download.csdn.net/detail/zhao4zhong1/5532495我以搞清楚，谢谢你写了这么长。。你告诉我要理解汇编层次的c语言，我很赞同，我理解了你之前做的理由，但仍有些不理解汇编中c的实质。这是我自行探索的结果，请您检阅  #include<stdio.h>  int main(){ 	char *a[4],*b[4]; 	char **p; 	 	a[0] = "123";  b[0] = "987"; 	a[1] = "456";  b[1] = "654"; 	a[2] = "789";  b[2] = "321"; 	 	p = a; 	printf("%s\n",*a);//输出123 	printf("%s\n",*p);//输出123	 	 	*p = *b;//神奇之处在于p的地址与a相同但p存的东西变成了b[0]的地址了	 	printf("%s\n",*p); //输出987  	return 0;						    }  [/quote] 1:    //#include<stdio.h> 2:    char *a[4]; 3:    char *b[4]; 4:    char **p; 5:    void *pv; 6:    int main(){ 00401010   push        ebp 00401011   mov         ebp,esp 00401013   sub         esp,40h 00401016   push        ebx 00401017   push        esi 00401018   push        edi 7:    //  char *a[4]; 8:    //  char *b[4]; 9:    //  char **p; 10:   //  void *pv; 11: 12:       a[0] = "123"; 00401019   mov         dword ptr [a (00414524)],offset string "123" (00413030) 13:       a[1] = "456"; 00401023   mov         dword ptr [a+4 (00414528)],offset string "456" (0041302c) 14:       a[2] = "789"; 0040102D   mov         dword ptr [a+8 (0041452c)],offset string "789" (00413028) 15:       b[0] = "987"; 00401037   mov         dword ptr [b (00414534)],offset string "987" (00413024) 16:       b[1] = "654"; 00401041   mov         dword ptr [b+4 (00414538)],offset string "654" (00413020) 17:       b[2] = "321"; 0040104B   mov         dword ptr [b+8 (0041453c)],offset string "321" (0041301c) 18:       p = a; 00401055   mov         dword ptr [p (00414544)],offset a (00414524) 19: 20:   //  printf("%s\n",*a);//输出123 21:       pv=(void *)(*a); 0040105F   mov         eax,[a (00414524)] 00401064   mov         [pv (00414548)],eax 22: 23:   //  printf("%s\n",*p);//输出123 24:       pv=(void *)(*p); 00401069   mov         ecx,dword ptr [p (00414544)] 0040106F   mov         edx,dword ptr [ecx] 00401071   mov         dword ptr [pv (00414548)],edx 25: 26:       *p = *b;//神奇之处在于p的地址与a相同但p存的东西变成了b[0]的地址了 00401077   mov         eax,[p (00414544)] 0040107C   mov         ecx,dword ptr [b (00414534)] 00401082   mov         dword ptr [eax],ecx 27:   //  printf("%s\n",*p); //输出987 28:       pv=(void *)(*p); 00401084   mov         edx,dword ptr [p (00414544)] 0040108A   mov         eax,dword ptr [edx] 0040108C   mov         [pv (00414548)],eax 29: 30:       return 0; 00401091   xor         eax,eax 31:   } 00401093   pop         edi 00401094   pop         esi 00401095   pop         ebx 00401096   mov         esp,ebp 00401098   pop         ebp 00401099   ret  1:    //#include<stdio.h> 2:    char *a[4]; 3:    char *b[4]; 4:    char **p; 5:    void *pv; 6:    int main(){ 00401010   push        ebp 00401011   mov         ebp,esp 00401013   sub         esp,40h 00401016   push        ebx 00401017   push        esi 00401018   push        edi 7:    //  char *a[4]; 8:    //  char *b[4]; 9:    //  char **p; 10:   //  void *pv; 11: 12:       a[0] = "123"; 00401019   mov         dword ptr [a (00414524)],offset string "123" (00413030) 13:       a[1] = "456"; 00401023   mov         dword ptr [a+4 (00414528)],offset string "456" (0041302c) 14:       a[2] = "789"; 0040102D   mov         dword ptr [a+8 (0041452c)],offset string "789" (00413028) 15:       b[0] = "987"; 00401037   mov         dword ptr [b (00414534)],offset string "987" (00413024) 16:       b[1] = "654"; 00401041   mov         dword ptr [b+4 (00414538)],offset string "654" (00413020) 17:       b[2] = "321"; 0040104B   mov         dword ptr [b+8 (0041453c)],offset string "321" (0041301c) 18:       p = a; 00401055   mov         dword ptr [p (00414544)],offset a (00414524) 19: 20:   //  printf("%s\n",*a);//输出123 21:       pv=(void *)(*a); 0040105F   mov         eax,[a (00414524)] 00401064   mov         [pv (00414548)],eax 22: 23:   //  printf("%s\n",*p);//输出123 24:       pv=(void *)(*p); 00401069   mov         ecx,dword ptr [p (00414544)] 0040106F   mov         edx,dword ptr [ecx] 00401071   mov         dword ptr [pv (00414548)],edx 25: 26:       *p = *b;//神奇之处在于p的地址与a相同但p存的东西变成了b[0]的地址了 00401077   mov         eax,[p (00414544)] 0040107C   mov         ecx,dword ptr [b (00414534)] 00401082   mov         dword ptr [eax],ecx 27:   //  printf("%s\n",*p); //输出987 28:       pv=(void *)(*p); 00401084   mov         edx,dword ptr [p (00414544)] 0040108A   mov         eax,dword ptr [edx] 0040108C   mov         [pv (00414548)],eax 29: 30:       return 0; 00401091   xor         eax,eax 31:   } 00401093   pop         edi 00401094   pop         esi 00401095   pop         ebx 00401096   mov         esp,ebp 00401098   pop         ebp 00401099   ret

user: user-3
content: 引用 39 楼 FrankHB1989 的回复:[quote=引用 32 楼 u010913001 的回复:] [quote=引用 28 楼 FrankHB1989 的回复:] [quote=引用 20 楼 u010913001 的回复:] [quote=引用 11 楼 zhao4zhong1 的回复:] 不要企图依赖输出指针相关表达式的值【比如printf("%p\n",...)】来理解指针的本质， 而要依赖调试时的反汇编窗口中的C/C++代码【比如void *p=...】及其对应汇编指令以及内存窗口中的内存地址和内存值来理解指针的本质。  我不喜欢不听话的孩纸！想想觉得不赞同，这个观点。理由，只有你在用（输出）的时候，才能真正感受到指针操作是否成功，（void *）只是把地址当作数值复制来复制去，而地址后续的连续地址其实才是 类型 *p所要达到的目的。不仅要操作单个内存地址，还要成片内存地址。本人拙见。。如果错的离谱不要生气[/quote] 知道婊赵老湿，有前途。  不过C语言中保证“连续”之类直觉抽象正确的语义规则和类型检查是分离的。实际上这个问题很乱……  引用 21 楼 cao_julians 的回复:[quote=引用 19 楼 u010913001 的回复:] [quote=引用 10 楼 cao_julians 的回复:] 类型是什么？许多教程说不出来，只说此种类型的数值占几个字节。 其实，类型是一类数据的编码格式，及所允许的相关操作。  如int的整型数据和double的双精度浮点数据，编码格式不同（字节数自然可能不同），double数据不允许%运算。  又如各种指针数据类型（除void*外），只允许有限的算术运算： 指针+（或-）整型数据（结果是同类型的指针）、指针-指针（同类型指针的减，结果是整型的） 指针不允许*，/,%运算我觉得你说的挺好，但是仍没有指出，如何设定类型的运算规则，对比类的运算符重载，我想类型的运算规则，或者说相关操作应该也是可以认为改变的[/quote] 先说类型默认的规则，后说操作符重载。 1.类型的规则也许要学到《编译原理》时才能清晰、完整 2.类型规则的实现却是容易。一个例子：今年是2014年，明年是2015年，两个 整数2014,2015相加如何？算术运算层面没有问题，但求出的“和”在问题层面是什么？----无数的教程上说，指针（地址）是个整数。。。，自然在数的层面是算术运算都应可以，可是指针在问题层面它应该叫“序号”，同序列中的序号（就象2014，2015年）相加是没有意义的。因此编译器用内部编码记忆了每种数据的类型，用代码段来检测表达式中各种数据成份的类型来判断后续的操作是否合法（如指针+指针就不合法----编译器就报错----自己写段代码就能看到）。编译器实现“规则”就是用if/else来判断、执行 3.操作符重载，首先是不能改变已有类型的操作符规则；其次才是对自定义类型一个个添加操作符的执行代码，编译器先接受这些操作的自定义规则，遇到表达式中自定义类型时，应用自定义的规则去判断、执行这些规则  不可能 很系统、准确。LZ可以待学习《编译原理》时深入研究[/quote] 1.类型系统本质是类型论这个数学分支在语言设计上的应用。编译原理只是皮毛。当然，C那会儿是误打误撞敲出来的，所以有很多设计上不合理的地方。 2.类型规则实现得看你实现成什么样的。C这种只有基本的类型检查当然很直接。加上多态类型推断呢？ 3.这个也就是一种ad-hoc多态+语法糖罢了。但C里面都是写死的。 引用 23 楼 lm_whales 的回复:指针是计算机，或者说编程语言，特有的数据类型，是一种实现类型。 除非问题本身就是计算机，编程语言相关的。 否则，指针不是问题域的类型。 这是你自己人为区分的。如果问题域就是指“计算机”里的呢。 类型系统应该长成什么样是语言规则定义的，但表示什么内容还是应该随便用户高兴。 所以这个意义上指针其实挺鸡肋的，远不如ptr<T>。  [/quote]挺高深。不过指针不就是为了实现ptr<T>吗。指针的类型就是对p所存地址的操作[/quote] ptr<T>就是所谓的泛型实例。这里我不满的地方在于明明指针就是一阶参数化类型的特例，然而语言的设计者就在这里开了个洞，制定了一大坨冗余规则（光是指针就算了，还有数组……）——看样子他压根就没理解到这里的普遍语义。而且要是指针真能方便地理解为直接表示地址也就罢了，偏偏各个实现开的洞大小不一样，到底还是只能一堆implementation-defined（比如某些指针比较操作），于是就是除开类型你也不方便彻底把它当作地址，要靠谱基本还是只能指望汇编；还有__far什么的一团糟（谢天谢地，现在基本没了）。说白了“表示地址”是很方便语义规则制定以及前端实现的东西，在整个语言中只是相对次要的实现机制；要是已经有参数化类型可用，这种鸡肋何德何能占着基本类型的位置不放（没错，说得就是C艹）？ 当然，C发明当时参数化类型作为特性还没出现，没先知先觉也是情有可原，但没法掩盖在现在看来就是渣设计。 [/quote] 基本类型→内建类型。 另外这样也顺便把指针/数组声明符的艹蛋中缀语法以及和类型名不一致的问题给去掉了（不会让人在T*还是T空格*纠结了），多利国利民…… 补一句，因为和体系结构相关，指针当然不怎么方便直接让用户用库实现，但是可以像标准库的intN_t一样依赖编译器提供的私有扩展__XXX_t来实现。

user: user-3
content: 引用 32 楼 u010913001 的回复:[quote=引用 28 楼 FrankHB1989 的回复:] [quote=引用 20 楼 u010913001 的回复:] [quote=引用 11 楼 zhao4zhong1 的回复:] 不要企图依赖输出指针相关表达式的值【比如printf("%p\n",...)】来理解指针的本质， 而要依赖调试时的反汇编窗口中的C/C++代码【比如void *p=...】及其对应汇编指令以及内存窗口中的内存地址和内存值来理解指针的本质。  我不喜欢不听话的孩纸！想想觉得不赞同，这个观点。理由，只有你在用（输出）的时候，才能真正感受到指针操作是否成功，（void *）只是把地址当作数值复制来复制去，而地址后续的连续地址其实才是 类型 *p所要达到的目的。不仅要操作单个内存地址，还要成片内存地址。本人拙见。。如果错的离谱不要生气[/quote] 知道婊赵老湿，有前途。  不过C语言中保证“连续”之类直觉抽象正确的语义规则和类型检查是分离的。实际上这个问题很乱……  引用 21 楼 cao_julians 的回复:[quote=引用 19 楼 u010913001 的回复:] [quote=引用 10 楼 cao_julians 的回复:] 类型是什么？许多教程说不出来，只说此种类型的数值占几个字节。 其实，类型是一类数据的编码格式，及所允许的相关操作。  如int的整型数据和double的双精度浮点数据，编码格式不同（字节数自然可能不同），double数据不允许%运算。  又如各种指针数据类型（除void*外），只允许有限的算术运算： 指针+（或-）整型数据（结果是同类型的指针）、指针-指针（同类型指针的减，结果是整型的） 指针不允许*，/,%运算我觉得你说的挺好，但是仍没有指出，如何设定类型的运算规则，对比类的运算符重载，我想类型的运算规则，或者说相关操作应该也是可以认为改变的[/quote] 先说类型默认的规则，后说操作符重载。 1.类型的规则也许要学到《编译原理》时才能清晰、完整 2.类型规则的实现却是容易。一个例子：今年是2014年，明年是2015年，两个 整数2014,2015相加如何？算术运算层面没有问题，但求出的“和”在问题层面是什么？----无数的教程上说，指针（地址）是个整数。。。，自然在数的层面是算术运算都应可以，可是指针在问题层面它应该叫“序号”，同序列中的序号（就象2014，2015年）相加是没有意义的。因此编译器用内部编码记忆了每种数据的类型，用代码段来检测表达式中各种数据成份的类型来判断后续的操作是否合法（如指针+指针就不合法----编译器就报错----自己写段代码就能看到）。编译器实现“规则”就是用if/else来判断、执行 3.操作符重载，首先是不能改变已有类型的操作符规则；其次才是对自定义类型一个个添加操作符的执行代码，编译器先接受这些操作的自定义规则，遇到表达式中自定义类型时，应用自定义的规则去判断、执行这些规则  不可能 很系统、准确。LZ可以待学习《编译原理》时深入研究[/quote] 1.类型系统本质是类型论这个数学分支在语言设计上的应用。编译原理只是皮毛。当然，C那会儿是误打误撞敲出来的，所以有很多设计上不合理的地方。 2.类型规则实现得看你实现成什么样的。C这种只有基本的类型检查当然很直接。加上多态类型推断呢？ 3.这个也就是一种ad-hoc多态+语法糖罢了。但C里面都是写死的。 引用 23 楼 lm_whales 的回复:指针是计算机，或者说编程语言，特有的数据类型，是一种实现类型。 除非问题本身就是计算机，编程语言相关的。 否则，指针不是问题域的类型。 这是你自己人为区分的。如果问题域就是指“计算机”里的呢。 类型系统应该长成什么样是语言规则定义的，但表示什么内容还是应该随便用户高兴。 所以这个意义上指针其实挺鸡肋的，远不如ptr<T>。  [/quote]挺高深。不过指针不就是为了实现ptr<T>吗。指针的类型就是对p所存地址的操作[/quote] ptr<T>就是所谓的泛型实例。这里我不满的地方在于明明指针就是一阶参数化类型的特例，然而语言的设计者就在这里开了个洞，制定了一大坨冗余规则（光是指针就算了，还有数组……）——看样子他压根就没理解到这里的普遍语义。而且要是指针真能方便地理解为直接表示地址也就罢了，偏偏各个实现开的洞大小不一样，到底还是只能一堆implementation-defined（比如某些指针比较操作），于是就是除开类型你也不方便彻底把它当作地址，要靠谱基本还是只能指望汇编；还有__far什么的一团糟（谢天谢地，现在基本没了）。说白了“表示地址”是很方便语义规则制定以及前端实现的东西，在整个语言中只是相对次要的实现机制；要是已经有参数化类型可用，这种鸡肋何德何能占着基本类型的位置不放（没错，说得就是C艹）？ 当然，C发明当时参数化类型作为特性还没出现，没先知先觉也是情有可原，但没法掩盖在现在看来就是渣设计。

user: user-1
content: a[0] = "123";  b[0] = "987";     a[1] = "456";  b[1] = "654";     a[2] = "789";  b[2] = "321"; =============================== 用"-->" 表示指针指向：   a[0] --> "123";   b[0] --> "987";     a[1] -->"456";   b[1]--> "654";     a[2] -->"789";    b[2] --> "321";        ========================            p = a; ========================   p --> a[0] --> "123"  ----------------------------  p -----*p ------**p         //如果用p定位的话 =======================     printf("%s\n",*a);//输出123     printf("%s\n",*p);//输出123              *p = *b;       //你修改的是*p，不是p.  *b是什么？是*(&b[0]),即(*&)b[0],   即b[0], 指向"789"的首字符的指针 ======================= p ---->b[0]---->"789" -------------------------- p-----*p-------**p

user: user-1
content: 引用 36 楼 u010913001 的回复:[quote=引用 33 楼 zhao4zhong1 的回复:] 计算机组成原理→DOS命令→汇编语言→C语言（不包括C++）、代码书写规范→数据结构、编译原理、操作系统→计算机网络、数据库原理、正则表达式→其它语言（包括C++）、架构……  对学习编程者的忠告： 眼过千遍不如手过一遍！ 书看千行不如手敲一行！ 手敲千行不如单步一行！ 单步源代码千行不如单步对应汇编一行！  VC调试时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化，这样过一遍不就啥都明白了吗。 对VC来说，所谓‘调试时’就是编译连接通过以后，按F10或F11键单步执行一步以后的时候，或者在某行按F9设了断点后按F5执行停在该断点处的时候。 （Turbo C或Borland C用Turbo Debugger调试,Linux或Unix下用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。）  想要从本质上理解C指针，必须学习汇编以及C和汇编的对应关系。 从汇编的角度理解和学习C语言的指针，原本看似复杂的东西就会变得非常简单！ 指针即地址。“地址又是啥？”“只能从汇编语言和计算机组成原理的角度去解释了。” 但我又不得不承认：  有那么些人喜欢或者适合用“先具体再抽象”的方法学习和理解复杂事物；  而另一些人喜欢或者适合用“先抽象再具体”的方法学习和理解复杂事物。 而我本人属前者。  不要企图依赖输出指针相关表达式的值【比如printf("%p\n",...)】来理解指针的本质， 而要依赖调试时的反汇编窗口中的C/C++代码【比如void *p=...】及其对应汇编指令以及内存窗口中的内存地址和内存值来理解指针的本质。  这辈子不看内存地址和内存值；只画链表、指针示意图，画堆栈示意图，画各种示意图，甚至自己没画过而只看过书上的图……能从本质上理解指针、理解函数参数传递吗？本人深表怀疑！ 这辈子不种麦不收麦不将麦粒拿去磨面；只吃馒头、吃面条、吃面包、……甚至从没看过别人怎么蒸馒头，压面条，烤面包，……能从本质上理解面粉、理解面食吗？本人深表怀疑！！  提醒： “学习用汇编语言写程序” 和 “VC调试(TC或BC用TD调试)时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化，这样过一遍不就啥都明白了吗。 （Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。） 想要从本质上理解C指针，必须学习C和汇编的对应关系。” 不是一回事！  不要迷信书、考题、老师、回帖； 要迷信CPU、编译器、调试器、运行结果。 并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。 任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实！  有人说一套做一套，你相信他说的还是相信他做的？ 其实严格来说这个世界上古往今来所有人都是说一套做一套，不是吗？  不要写连自己也预测不了结果的代码！  电脑内存或文件内容只是一个一维二进制字节数组及其对应的二进制地址； 人脑才将电脑内存或文件内容中的这个一维二进制字节数组及其对应的二进制地址的某些部分看成是整数、有符号数/无符号数、浮点数、复数、英文字母、阿拉伯数字、中文/韩文/法文……字符/字符串、汇编指令、函数、函数参数、堆、栈、数组、指针、数组指针、指针数组、数组的数组、指针的指针、二维数组、字符点阵、字符笔画的坐标、黑白二值图片、灰度图片、彩色图片、录音、视频、指纹信息、身份证信息……  十字链表交换任意两个节点C源代码（C指针应用终极挑战）http://download.csdn.net/detail/zhao4zhong1/5532495我以搞清楚，谢谢你写了这么长。。你告诉我要理解汇编层次的c语言，我很赞同，我理解了你之前做的理由，但仍有些不理解汇编中c的实质。这是我自行探索的结果，请您检阅  #include<stdio.h>  int main(){ 	char *a[4],*b[4]; 	char **p; 	 	a[0] = "123";  b[0] = "987"; 	a[1] = "456";  b[1] = "654"; 	a[2] = "789";  b[2] = "321"; 	 	p = a; 	printf("%s\n",*a);//输出123 	printf("%s\n",*p);//输出123	 	 	*p = *b;//神奇之处在于p的地址与a相同但p存的东西变成了b[0]的地址了	 	printf("%s\n",*p); //输出987  	return 0;						    }  [/quote]  引用 36 楼 u010913001 的回复:[quote=引用 33 楼 zhao4zhong1 的回复:] 计算机组成原理→DOS命令→汇编语言→C语言（不包括C++）、代码书写规范→数据结构、编译原理、操作系统→计算机网络、数据库原理、正则表达式→其它语言（包括C++）、架构……  对学习编程者的忠告： 眼过千遍不如手过一遍！ 书看千行不如手敲一行！ 手敲千行不如单步一行！ 单步源代码千行不如单步对应汇编一行！  VC调试时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化，这样过一遍不就啥都明白了吗。 对VC来说，所谓‘调试时’就是编译连接通过以后，按F10或F11键单步执行一步以后的时候，或者在某行按F9设了断点后按F5执行停在该断点处的时候。 （Turbo C或Borland C用Turbo Debugger调试,Linux或Unix下用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。）  想要从本质上理解C指针，必须学习汇编以及C和汇编的对应关系。 从汇编的角度理解和学习C语言的指针，原本看似复杂的东西就会变得非常简单！ 指针即地址。“地址又是啥？”“只能从汇编语言和计算机组成原理的角度去解释了。” 但我又不得不承认：  有那么些人喜欢或者适合用“先具体再抽象”的方法学习和理解复杂事物；  而另一些人喜欢或者适合用“先抽象再具体”的方法学习和理解复杂事物。 而我本人属前者。  不要企图依赖输出指针相关表达式的值【比如printf("%p\n",...)】来理解指针的本质， 而要依赖调试时的反汇编窗口中的C/C++代码【比如void *p=...】及其对应汇编指令以及内存窗口中的内存地址和内存值来理解指针的本质。  这辈子不看内存地址和内存值；只画链表、指针示意图，画堆栈示意图，画各种示意图，甚至自己没画过而只看过书上的图……能从本质上理解指针、理解函数参数传递吗？本人深表怀疑！ 这辈子不种麦不收麦不将麦粒拿去磨面；只吃馒头、吃面条、吃面包、……甚至从没看过别人怎么蒸馒头，压面条，烤面包，……能从本质上理解面粉、理解面食吗？本人深表怀疑！！  提醒： “学习用汇编语言写程序” 和 “VC调试(TC或BC用TD调试)时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化，这样过一遍不就啥都明白了吗。 （Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。） 想要从本质上理解C指针，必须学习C和汇编的对应关系。” 不是一回事！  不要迷信书、考题、老师、回帖； 要迷信CPU、编译器、调试器、运行结果。 并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。 任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实！  有人说一套做一套，你相信他说的还是相信他做的？ 其实严格来说这个世界上古往今来所有人都是说一套做一套，不是吗？  不要写连自己也预测不了结果的代码！  电脑内存或文件内容只是一个一维二进制字节数组及其对应的二进制地址； 人脑才将电脑内存或文件内容中的这个一维二进制字节数组及其对应的二进制地址的某些部分看成是整数、有符号数/无符号数、浮点数、复数、英文字母、阿拉伯数字、中文/韩文/法文……字符/字符串、汇编指令、函数、函数参数、堆、栈、数组、指针、数组指针、指针数组、数组的数组、指针的指针、二维数组、字符点阵、字符笔画的坐标、黑白二值图片、灰度图片、彩色图片、录音、视频、指纹信息、身份证信息……  十字链表交换任意两个节点C源代码（C指针应用终极挑战）http://download.csdn.net/detail/zhao4zhong1/5532495我以搞清楚，谢谢你写了这么长。。你告诉我要理解汇编层次的c语言，我很赞同，我理解了你之前做的理由，但仍有些不理解汇编中c的实质。这是我自行探索的结果，请您检阅  #include<stdio.h>  int main(){ 	char *a[4],*b[4]; 	char **p; 	 	a[0] = "123";  b[0] = "987"; 	a[1] = "456";  b[1] = "654"; 	a[2] = "789";  b[2] = "321"; 	 	p = a; 	printf("%s\n",*a);//输出123 	printf("%s\n",*p);//输出123	 	 	*p = *b;//神奇之处在于p的地址与a相同但p存的东西变成了b[0]的地址了	 	printf("%s\n",*p); //输出987  	return 0;						    }  [/quote] 很不幸，解读的有问题：	        p = a;    //这里，p存储了&a[0] 	printf("%s\n",*a);//输出123-----------没有问题 	printf("%s\n",*p);//输出123-----------没有问题	 	 	*p = *b;//神奇之处在于p的地址与a相同但p存的东西变成了b[0]的地址了	------       ----这里，是p的指向a[0]指向了“b[0]“指向的”789“----你修改的不是p，而是a[0] 	printf("%s\n",*p); //输出987----你加一句   printf("%s\n",*a);  试试看

user: user-4
content: 引用 29 楼 FrankHB1989 的回复:赋值改变的是值，值已经包含了数值大小和类型信息。只不过C是纯粹的静态类型语言，所以编译器只需要管静态检查，不用管生成和类型相关的运行时检查类型的代码。静态检查的规则由类型系统的语义规则指定。C规定int*不能随便搞成int，如此而已。只是这里不会根据运行时确定的参数内容来提供检查，所以这里类型不匹配就是自己作死了。 ++ 指针和数组的语义不同  指针，表示所指对象（数据）的内存位置 数组，表示一批连续存储的对象（数据）。  只是数组，可以通过指针使用而已。

user: user-0
content: 引用 11 楼 zhao4zhong1 的回复:不要企图依赖输出指针相关表达式的值【比如printf("%p\n",...)】来理解指针的本质， 而要依赖调试时的反汇编窗口中的C/C++代码【比如void *p=...】及其对应汇编指令以及内存窗口中的内存地址和内存值来理解指针的本质。  我不喜欢不听话的孩纸！想想觉得不赞同，这个观点。理由，只有你在用（输出）的时候，才能真正感受到指针操作是否成功，（void *）只是把地址当作数值复制来复制去，而地址后续的连续地址其实才是 类型 *p所要达到的目的。不仅要操作单个内存地址，还要成片内存地址。本人拙见。。如果错的离谱不要生气

user: user-0
content: 引用 10 楼 cao_julians 的回复:类型是什么？许多教程说不出来，只说此种类型的数值占几个字节。 其实，类型是一类数据的编码格式，及所允许的相关操作。  如int的整型数据和double的双精度浮点数据，编码格式不同（字节数自然可能不同），double数据不允许%运算。  又如各种指针数据类型（除void*外），只允许有限的算术运算： 指针+（或-）整型数据（结果是同类型的指针）、指针-指针（同类型指针的减，结果是整型的） 指针不允许*，/,%运算我觉得你说的挺好，但是仍没有指出，如何设定类型的运算规则，对比类的运算符重载，我想类型的运算规则，或者说相关操作应该也是可以认为改变的

user: user-0
content: 引用 11 楼 zhao4zhong1 的回复:不要企图依赖输出指针相关表达式的值【比如printf("%p\n",...)】来理解指针的本质， 而要依赖调试时的反汇编窗口中的C/C++代码【比如void *p=...】及其对应汇编指令以及内存窗口中的内存地址和内存值来理解指针的本质。  我不喜欢不听话的孩纸！我确实一边看着内存地址，一边输出的值。还有这个汇编代码容我在理解理解，怎么觉得还是没**p的相关运算啊。10楼的回复挺好的，就是能再深入点就好

user: user-3
content: 赋值改变的是值，值已经包含了数值大小和类型信息。只不过C是纯粹的静态类型语言，所以编译器只需要管静态检查，不用管生成和类型相关的运行时检查类型的代码。静态检查的规则由类型系统的语义规则指定。C规定int*不能随便搞成int，如此而已。只是这里不会根据运行时确定的参数内容来提供检查，所以这里类型不匹配就是自己作死了。

user: user-3
content: 引用 20 楼 u010913001 的回复:[quote=引用 11 楼 zhao4zhong1 的回复:] 不要企图依赖输出指针相关表达式的值【比如printf("%p\n",...)】来理解指针的本质， 而要依赖调试时的反汇编窗口中的C/C++代码【比如void *p=...】及其对应汇编指令以及内存窗口中的内存地址和内存值来理解指针的本质。  我不喜欢不听话的孩纸！想想觉得不赞同，这个观点。理由，只有你在用（输出）的时候，才能真正感受到指针操作是否成功，（void *）只是把地址当作数值复制来复制去，而地址后续的连续地址其实才是 类型 *p所要达到的目的。不仅要操作单个内存地址，还要成片内存地址。本人拙见。。如果错的离谱不要生气[/quote] 知道婊赵老湿，有前途。  不过C语言中保证“连续”之类直觉抽象正确的语义规则和类型检查是分离的。实际上这个问题很乱……  引用 21 楼 cao_julians 的回复:[quote=引用 19 楼 u010913001 的回复:] [quote=引用 10 楼 cao_julians 的回复:] 类型是什么？许多教程说不出来，只说此种类型的数值占几个字节。 其实，类型是一类数据的编码格式，及所允许的相关操作。  如int的整型数据和double的双精度浮点数据，编码格式不同（字节数自然可能不同），double数据不允许%运算。  又如各种指针数据类型（除void*外），只允许有限的算术运算： 指针+（或-）整型数据（结果是同类型的指针）、指针-指针（同类型指针的减，结果是整型的） 指针不允许*，/,%运算我觉得你说的挺好，但是仍没有指出，如何设定类型的运算规则，对比类的运算符重载，我想类型的运算规则，或者说相关操作应该也是可以认为改变的[/quote] 先说类型默认的规则，后说操作符重载。 1.类型的规则也许要学到《编译原理》时才能清晰、完整 2.类型规则的实现却是容易。一个例子：今年是2014年，明年是2015年，两个 整数2014,2015相加如何？算术运算层面没有问题，但求出的“和”在问题层面是什么？----无数的教程上说，指针（地址）是个整数。。。，自然在数的层面是算术运算都应可以，可是指针在问题层面它应该叫“序号”，同序列中的序号（就象2014，2015年）相加是没有意义的。因此编译器用内部编码记忆了每种数据的类型，用代码段来检测表达式中各种数据成份的类型来判断后续的操作是否合法（如指针+指针就不合法----编译器就报错----自己写段代码就能看到）。编译器实现“规则”就是用if/else来判断、执行 3.操作符重载，首先是不能改变已有类型的操作符规则；其次才是对自定义类型一个个添加操作符的执行代码，编译器先接受这些操作的自定义规则，遇到表达式中自定义类型时，应用自定义的规则去判断、执行这些规则  不可能 很系统、准确。LZ可以待学习《编译原理》时深入研究[/quote] 1.类型系统本质是类型论这个数学分支在语言设计上的应用。编译原理只是皮毛。当然，C那会儿是误打误撞敲出来的，所以有很多设计上不合理的地方。 2.类型规则实现得看你实现成什么样的。C这种只有基本的类型检查当然很直接。加上多态类型推断呢？ 3.这个也就是一种ad-hoc多态+语法糖罢了。但C里面都是写死的。 引用 23 楼 lm_whales 的回复:指针是计算机，或者说编程语言，特有的数据类型，是一种实现类型。 除非问题本身就是计算机，编程语言相关的。 否则，指针不是问题域的类型。 这是你自己人为区分的。如果问题域就是指“计算机”里的呢。 类型系统应该长成什么样是语言规则定义的，但表示什么内容还是应该随便用户高兴。 所以这个意义上指针其实挺鸡肋的，远不如ptr<T>。

user: user-5
content: int main(int argc, char* argv[]) { 	char *a[3]; 	char **p; 	 	a[0] = "bai"; 	a[1] = "abc"; 	a[2] = "123";  	p = &a[1]; 	*p = a[1]; 	*p = *&a[2];  	printf("%s",*p);  	return 0; } 这样就可以满足楼主的需求了，感觉就是野指针的问题吧，以及赋值和初始化的区别吧。

user: user-4
content: 引用 24 楼 cao_julians 的回复:[quote=引用 23 楼 lm_whales 的回复:] 指针是计算机，或者说编程语言，特有的数据类型，是一种实现类型。 除非问题本身就是计算机，编程语言相关的。 否则，指针不是问题域的类型。 问题域指研究对象所在的范围。要研究指针及所处的环境，这就是一个指针的“问题域” 是否你我两人对“问题域”的认识不同。我个人的观点如上，您的观点是不是指”现实世界的问题“，而非”抽象世界的问题“？[/quote]  抽象，是为了解决现实问题的，你可以研究得很抽象，最终这种研究会拿来解决现实问题。  对于指针来说，问题域和实现域有时候会出现重叠。。 所以此时，即是问题域问题，又是实现域问题。 例如，操作系统的实现，其中会有指针问题，这时候指针即是问题域问题，又是实现域问题。 例如，编译器的实现，同样即是问题域问题，又是实现域问题。  或者可以区分问题域，实现域，这两种不同的指针问题，但是有时候他们是互相纠缠的。  但是实现一个word,excel那问题域，基本上就很少涉及指针这种类型了。  基本上，从编程语言要解决的问题来说，指针主要是实现域问题 int ,float 则是对现实问题的抽象，和现实息息相关  C，C++中：short int ,int ,long int, long long int  这种不同整数类型的存在，基本是实现问题  C，C++中：float,double ,long double                 有三种 浮点数的存在，基本上也是实现问题  整数类型，浮点类型（实数类型）这种不同类型数据类型的存在，则是现实世界的问题。  现实世界里，不存在数据长度这一说法，但是实现必须有。 而数据长度是类型的一个重要信息。  问题域，就是你要解决的问题的领域。 比如你要实现排序，那么基本是，如何用编程语言实现排序的问题，这基本是实现域的问题。 你要实现导航系统，那么就是解决现实问题了，你要对导航系统设计的问题，对象，角色做抽象， 还需要裁剪不需要的额外信息，以便更集中解决导航问题。

user: user-6
content: p是个指针，不能存放值，只能存放地址 你的*p = *&a[1]; 相当于*p = a[1];

user: user-1
content: 引用 23 楼 lm_whales 的回复:指针是计算机，或者说编程语言，特有的数据类型，是一种实现类型。 除非问题本身就是计算机，编程语言相关的。 否则，指针不是问题域的类型。 问题域指研究对象所在的范围。要研究指针及所处的环境，这就是一个指针的“问题域” 是否你我两人对“问题域”的认识不同。我个人的观点如上，您的观点是不是指”现实世界的问题“，而非”抽象世界的问题“？

user: user-4
content: 指针是计算机，或者说编程语言，特有的数据类型，是一种实现类型。 除非问题本身就是计算机，编程语言相关的。 否则，指针不是问题域的类型。

user: user-1
content: 如何设定类型的运算规则 ===================== 这是在问题层面讨论的 比如：一个类中有你的名字name,年龄age,身份识别ID,成绩score等数据成员 你想：ID=name+age;(这个+是字符串联接)，就可以对+、=运算符重载 但score=name+age，显然在问题层面这个+、=是有问题的

user: user-1
content: 引用 19 楼 u010913001 的回复:[quote=引用 10 楼 cao_julians 的回复:] 类型是什么？许多教程说不出来，只说此种类型的数值占几个字节。 其实，类型是一类数据的编码格式，及所允许的相关操作。  如int的整型数据和double的双精度浮点数据，编码格式不同（字节数自然可能不同），double数据不允许%运算。  又如各种指针数据类型（除void*外），只允许有限的算术运算： 指针+（或-）整型数据（结果是同类型的指针）、指针-指针（同类型指针的减，结果是整型的） 指针不允许*，/,%运算我觉得你说的挺好，但是仍没有指出，如何设定类型的运算规则，对比类的运算符重载，我想类型的运算规则，或者说相关操作应该也是可以认为改变的[/quote] 先说类型默认的规则，后说操作符重载。 1.类型的规则也许要学到《编译原理》时才能清晰、完整 2.类型规则的实现却是容易。一个例子：今年是2014年，明年是2015年，两个 整数2014,2015相加如何？算术运算层面没有问题，但求出的“和”在问题层面是什么？----无数的教程上说，指针（地址）是个整数。。。，自然在数的层面是算术运算都应可以，可是指针在问题层面它应该叫“序号”，同序列中的序号（就象2014，2015年）相加是没有意义的。因此编译器用内部编码记忆了每种数据的类型，用代码段来检测表达式中各种数据成份的类型来判断后续的操作是否合法（如指针+指针就不合法----编译器就报错----自己写段代码就能看到）。编译器实现“规则”就是用if/else来判断、执行 3.操作符重载，首先是不能改变已有类型的操作符规则；其次才是对自定义类型一个个添加操作符的执行代码，编译器先接受这些操作的自定义规则，遇到表达式中自定义类型时，应用自定义的规则去判断、执行这些规则  不可能 很系统、准确。LZ可以待学习《编译原理》时深入研究

user: user-0
content: 引用 33 楼 zhao4zhong1 的回复:计算机组成原理→DOS命令→汇编语言→C语言（不包括C++）、代码书写规范→数据结构、编译原理、操作系统→计算机网络、数据库原理、正则表达式→其它语言（包括C++）、架构……  对学习编程者的忠告： 眼过千遍不如手过一遍！ 书看千行不如手敲一行！ 手敲千行不如单步一行！ 单步源代码千行不如单步对应汇编一行！  VC调试时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化，这样过一遍不就啥都明白了吗。 对VC来说，所谓‘调试时’就是编译连接通过以后，按F10或F11键单步执行一步以后的时候，或者在某行按F9设了断点后按F5执行停在该断点处的时候。 （Turbo C或Borland C用Turbo Debugger调试,Linux或Unix下用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。）  想要从本质上理解C指针，必须学习汇编以及C和汇编的对应关系。 从汇编的角度理解和学习C语言的指针，原本看似复杂的东西就会变得非常简单！ 指针即地址。“地址又是啥？”“只能从汇编语言和计算机组成原理的角度去解释了。” 但我又不得不承认：  有那么些人喜欢或者适合用“先具体再抽象”的方法学习和理解复杂事物；  而另一些人喜欢或者适合用“先抽象再具体”的方法学习和理解复杂事物。 而我本人属前者。  不要企图依赖输出指针相关表达式的值【比如printf("%p\n",...)】来理解指针的本质， 而要依赖调试时的反汇编窗口中的C/C++代码【比如void *p=...】及其对应汇编指令以及内存窗口中的内存地址和内存值来理解指针的本质。  这辈子不看内存地址和内存值；只画链表、指针示意图，画堆栈示意图，画各种示意图，甚至自己没画过而只看过书上的图……能从本质上理解指针、理解函数参数传递吗？本人深表怀疑！ 这辈子不种麦不收麦不将麦粒拿去磨面；只吃馒头、吃面条、吃面包、……甚至从没看过别人怎么蒸馒头，压面条，烤面包，……能从本质上理解面粉、理解面食吗？本人深表怀疑！！  提醒： “学习用汇编语言写程序” 和 “VC调试(TC或BC用TD调试)时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化，这样过一遍不就啥都明白了吗。 （Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。） 想要从本质上理解C指针，必须学习C和汇编的对应关系。” 不是一回事！  不要迷信书、考题、老师、回帖； 要迷信CPU、编译器、调试器、运行结果。 并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。 任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实！  有人说一套做一套，你相信他说的还是相信他做的？ 其实严格来说这个世界上古往今来所有人都是说一套做一套，不是吗？  不要写连自己也预测不了结果的代码！  电脑内存或文件内容只是一个一维二进制字节数组及其对应的二进制地址； 人脑才将电脑内存或文件内容中的这个一维二进制字节数组及其对应的二进制地址的某些部分看成是整数、有符号数/无符号数、浮点数、复数、英文字母、阿拉伯数字、中文/韩文/法文……字符/字符串、汇编指令、函数、函数参数、堆、栈、数组、指针、数组指针、指针数组、数组的数组、指针的指针、二维数组、字符点阵、字符笔画的坐标、黑白二值图片、灰度图片、彩色图片、录音、视频、指纹信息、身份证信息……  十字链表交换任意两个节点C源代码（C指针应用终极挑战）http://download.csdn.net/detail/zhao4zhong1/5532495我以搞清楚，谢谢你写了这么长。。你告诉我要理解汇编层次的c语言，我很赞同，我理解了你之前做的理由，但仍有些不理解汇编中c的实质。这是我自行探索的结果，请您检阅  #include<stdio.h>  int main(){ 	char *a[4],*b[4]; 	char **p; 	 	a[0] = "123";  b[0] = "987"; 	a[1] = "456";  b[1] = "654"; 	a[2] = "789";  b[2] = "321"; 	 	p = a; 	printf("%s\n",*a);//输出123 	printf("%s\n",*p);//输出123	 	 	*p = *b;//神奇之处在于p的地址与a相同但p存的东西变成了b[0]的地址了	 	printf("%s\n",*p); //输出987  	return 0;						    }