关于C/C++ 一些自己遇到的问题以及解惑
文章目录
???????有些自己遇到的,有一些是通过群友的提问应发的,问题本身的价值可能并不高,但其背后的原因才是我们应该学习的,下面我们来看看。 1.数组越界造成的死循环???????有一位朋友在群里发了该代码,并说该代码导致了死循环??? ???????废话少说,上工具,我们来分析分析。 ???????Dev下的程序并无异常???????我们来看看vs2015的表现,虽然是正常输出内容,并没有造成死循环,但是弹出了一个异常~ 。这个异常是由于我们数组越界造成的,而数组越界又是一种未决行为,编译器不会做任何处理,但是vs2015还是义务的帮我提示了异常,所以Dev和vs该用哪一个编译器,心里有数了吧? ???????那么为什么会产生这样的效果呢,揭秘如下. 若是内存递减分配,对于数组和i的内存分配如下: 若是内存递增分配,对于数组和i的内存分配如下: ???????现在可能就有人问了,为什么递减分配 i和iarray[2]挨着,而递增i就和iarray[0]挨着,其实这个不难理解,*(iarray+1)一定比*(iarray)的地址高不是吗,对于递减分配,必须倒着来分配,对于递减这种分配模式,iarray[3]的地址就是i的地址,iarray[3]=0便是i=0,这样一来便导致了i的值又重新被赋值为0,导致了死循环,然后,注意,没有完,之所以i会跟在数组后面,是因为字节对齐,对于32位来说是4字节,对于64位来说是8字节,当数组内容不足以字节对齐,i就会分配在其旁边,或者说是后面,当数组正好有8个元素,i就不会跟在数组后面,也就不会造成死循环,所以造成死循环一是编译器分配内存方式,二是字节对齐的问题. ???????经测试,博主所使用的dev和vs2015,以及一些编译器会在数组和i的地址之间,用一小块内存,用来避免两者,从而一定程度上解决死循环问题,但当越界过大,还是会造成死循环.所以在使用对内存的操作上,应格外小心… @H_403_74@ 如何查看内存?如果是C,我们可以用%p来输出变量地址,若是C++,我们可以用static_cast<void *>(&a)来输出变量地址,大家若是使用vs,教大家一个小技巧,在调试模式中(F5)下依次单击调试,窗口,即使窗口,打开这个窗口后,我们可以用&变量名来获取地址 2.int main(int argc,char* argv[])里面的参数有什么作用????????首先可以告诉大家的是对于单纯的C语言,main里面的参数对于我们学习C来说,并不重要,标准形式有两种int main(int argc,char* argv[])和int main(void),在实际的学习使用中,我们使用int main(void)这种形式就可以了,当然,你要是感觉酷一点可以用int main(int argc,char* argv[]),如果你还想知道int argc,char* argv[]这种参数的作用,那么请往下看。 ???????1.大多数人应该都写过XXXX管理系统,有管理,就有数据,有数据就需要我们保存,我们可以用一个文本来保存用户输入数据,但是这个文本应该保存在什么地方呢?总不能在代码中固定一个路径吧,大家计算机名字都不一样,这样肯定行不通,于是我们在代码中开始写到cout<<“请输入数据保存的路径”; 然后开始读取用户输入的路径,那么有没有进一步提升用户体验的写法?这时我们就可以用到main的参数了,利用argv[0]获取该程序的路径,并通过算法解析,即可得到用户把exe放在哪里,那么我们在exe所在的路径下保存数据文本即可,这样就会提示用户体验。 @H_403_74@???????2.当你编写的程序需要根据提供的数据执行不同从操作,但是每次执行所需要的数据又未知,这个时候我们就可以用到main的参数,我们可以写一个脚本程序,然后让程序读取脚本中提供的参数,这样就会事半功倍。 其实相当于是调用了exe,exe里面的函数利用参数工作,而exe也同样可以利用参数工作,那么如何输入参数呢,告诉大家几张方法: 1.直接在命令行输入 start 路径 参数1 参数2 参数3 2.我们将编译好的程序,快捷方式放到桌面,右键选择属性,在其目标的文本框 exe文件后面 加入参数 3.最后一个也就是直接在我们的IDE进行设置,例如我所使用的vs2015,右键项目->属性,在调试页面可以看到一个命令参数。 好了,main的参数就说到这里,悄悄告诉大家,其实main还有第三个参数:char *envp[],如果大家有兴趣,可自行研究研究。 3.程序代码区、文字常量区、静态区(全局区)、堆区、栈区???????为什么说这个,原因在图中: ???????群里在讨论链表,一位名叫C语言信赖代考的网友讲了一句清除链表只需要释放头节点就行了,不用一个一个删,我看到了,于是好意提醒了一句,结果这位网友告诉我头节点后面连着所有节点,只需要释放头节点就行了,一看此现状,我就没再与他讲理了。刚奇葩的是这个哥们私聊我说有单子给他,这我敢给啊,再顺便讲一句,不要随随便便叫人代考啊。。。。我们进入正题。 ???????这位网友之所以会怎么说,应该是没有理解malloc/new,也就是malloc的内存申请在哪,就是栈区和堆区问题,但是因为程序代码区、文字常量区、静态区(全局区)、堆区、栈区这些东西常出现在一起,索性也就放在一起说了。 ???????我查找了大量的有关博文,大多数有关博文都有怎么一张图,如果说以前,我可能会同意,但是现在我对图中栈区的向下增长有一些疑惑,就拿我们刚开始数组死循环的内存分配来说,内存两种分配模式,递增,递减,所以我觉得这个图还有待考证。
同时我们可以右键项目,链接器,系统,可以看到堆保留大小和堆栈保留大小。 然后再说一下malloc对应free,new对应delete,new,delete涉及构造函数和析构函数,不可混用,若是C++,应使用new。 4.函数指针 指针函数 指针数组 数组指针 傻傻分不清@H_403_74@int fun();?????????????????????函数 int(*fun)();??????????????????? char * p[];?????????????? int (*p)[];?????????????? 上面出现的括号都是必要的,不可省略,说其是一种格式也不为过,指针XX和XX指针分不清主次,可以像我一样在两者之间加上(样式的),应该会方便理解。 5.return continue break return 0 exit@H_403_74@break:跳出所在的当前整个循环,到外层代码继续执行,break不仅可以结束其所在的循环,还可结束其外层循环,但一次只能结束一种循环。 @H_403_74@continue:跳出本次循环,从下一个迭代继续运行循环,内层循环执行完毕,外层代码继续运行,continue结束的是本次循环,将接着开始下一次循环。 其实这两个没什么说的,return 和 exit可能在书中不常见。 @H_403_74@return:直接返回函数,所有该函数体内的代码(包括循环体)都不会再执行,同时结束其所在的循环和其外层循环。当自定义函数中无返回值时,可以使用该写法。相当于使用了break。 @H_403_74@return 0; 当函数有返回值时,使用该写法。 @H_403_74@exit(1); 程序/进程立即结束(正常退出) 6.最大值加1等于最小值?(一图看懂)我们可以把变量的取值范围当作是汽车的里程表,一来为了好理解,二来确实是这样的,拿char来说: 7.精度问题这位网友的问题很有意思,这个案例也是很好的图示了下面我要说的话,这是众多初学者的一个理解错误,每一本语言书都会告诉你单精度类型有效范围是7位,双精度类型有效范围15位,这就给大家造成一种错觉,认为只能存15位,其实是错了,它所指的15位指的是精度问题,就是图中的样子,它可以存储30位甚至更多,但是它的精度只有前15位,就是超过了15位,一起数字都化作了0。之所以可以保存到30多位,和浮点数的存储有关,浮点数是用科学记数法存储的,有关浮点数的定义,这个就涉及到计算机组成原理了,还是比较难的,大家有兴趣可以搜索IEEE754浮点数的标准,里面有关于浮点数的存储过程。 最后来一只可爱的猫猫,创作不易,求关注,求收藏! (编辑:北几岛) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |