指针
来源:互联网 发布:手机淘宝投诉卖家流程 编辑:程序博客网 时间:2024/06/06 07:51
C++(大多数语言的)内存结构
(1)内存结构图(2)不同的管理区域 (1)名称 (2)作用 (3)管理特点 (4)按照不同的分类划分内存区域进行理解(3)理解内存结构的重要性 (1)加深对语言的理解,提高编程质量 (2)特别是对于带有指针的C和C++语言,有助于理解段错误的产生的原因(3)内存的作用 (1)临时存放(缓存)代码和程序中用到的数据 (2)一定需要内存吗 (3)实际上内存是硬盘的缓存(4)有和没有os区别 (1)如果没有OS,直接使用物理内存 (1)好处 (1)管理直接,不会浪费额外的管理空间,特别是对于单片机类的微控制器,节约空间很重要,传递结构时,使用值传递和地址传递对内存的影响都会特别大。 (2)不好的地方 (1)内存的管理需要需要相应内存管理库(栈/静态区管理)和程序员亲历亲为,很麻烦,要求对程序编译再反汇编后的不同的段有相当理解,如果还需实现malloc的话,还需要安装malloc的库。 (2)很容易造成内存管理不善 (3)很难实现复杂内存管理,没有OS提供的内存管理,很难实现多进程多任务等机制(2)有os的情况 有os的情况下,内存管理的结构并没有发生变化,但是在物理内存上,内核架构了一层模拟的虚拟内存,这个虚拟内存会合理的管理物理内存的使用,当然物理内存的使用效率最大化。我们编写的程序在有操作系统的机器上运行时,栈/堆/静态区等程序的内存管理是基于虚拟内存上实现的。 (1)好处 (1)程序员负担轻,不需要费心内存的管理(合理安排使用) (2)可以非常容易的实现多进程多任务机制 (3)内存管理本身会消耗很多的内存 (2)虚拟内存管理需要消耗很多用于用于管理物理内存的管理空间,所以只有内存很大的计算机才能运行 操作系统。(5)高级语言提供内存开辟接口 不管语言编写的程序在不在os上运行,高级语言都提供堆空间的空间开辟接口,比如C的malloc和free函数, C++的malloc和free函数以及new和delete,java的new。 这些高级语言如果是直接运行在裸机上的话,就需要提供相应的函数库或者类库来实现内存的管理,比如对于单片机等微控制器 ,只用C语言开发的程序都是运行在裸机上的,如果你需要使用malloc函数时,就需要移植malloc对应的函数库。 对于c和c++而言,在内存的管理上,使用malloc和new进行开辟空间时,需要手动的释放空间,当然 这样做的话,灵活性就非常的高,而且内存利用率高,但是人为管理代价高,而且风险高,很容易造成内存泄漏。 对于java自带的内存管理机制的语言(还有c#),不管这个语言编写的程序是运行在os上,还是像早期一样运行在裸机上, 完全实现了内存全自动化管理,只需要开空间,释放空间的事情不需要人为管理,语言自己会自己搞定, 代价是这样的自动管理机制浪费管理空间。 像c和c++这种语言成为非托管语言,跨平台靠编译器实现,java和c#这类的称为托管语言,跨平台靠托管平台实现。所谓跨平台,有跨 软件平台和跨硬件平台之分,java的内存实现了完全自动的话管理,这些完全的自动话全部是靠托管平台实现的。(6)内存的访问 (1)读和写 (2)访问权限:程序的内存布局中,不同的管理区域规定了不同的访问权限 (3)内存的访问和和管理一定会涉及地址和指针,但是在不同的语言中,指针暴露的情况不同,C和C++指针暴露给程序员, java等的指针完全隐藏。 (2)数据存放的两种模式(1)变量:权限,可读可写,内容可以通过反复的赋值而被改变。(2)常量:权限,可读不可写,内容初始化后就不能再变。 (1)常量的实现方式 (1)常量的空间开辟于只读存储器中。 (2)常量开辟在可读可写的内存中,但是软件(OS或者语言管理平台设定了读写权限)。 (3)编译器检查实现,典型的c和C++中的const修饰变量时的实现 (3)初始化和赋值的本质区别 初始化:初始化由编译器在编译完成,程序运行时只是复制到相应内存空间即可 赋值:程序在具体运行的过程中由赋值语句临时决定具体的值,实现内存中数据的动态改变(3)指针(1)指什么? (1)指针变量 (2)指针常量 当我们说到指针的时候,根据实际语境,我们自己应该清楚指的是指针常量还是指针变量。(2)指针变量/指针常量用来存放什么 不管是常量还是变量,都是用来存放数据的,指针变量和指针常量存放的数据就是内存地址。(3)指针声明(定义),初始化,赋值 (1)定义和声明只是为了学习的需要做的区分,在编译器眼里没有区分,或者全都是声明。 (2)在编译器眼中只有强弱符号的区分,如果给了初始化的声明就是强符号,全局强符号在 程序中只能有一个。没有初始化的声明就是弱符号,弱符号可以有多个。强弱符号的 作用主要是为了实现多个.c或者多个.cpp(编译)链接成为一个可执行文件时,将 同名的弱符号和强符号统一成为一个,使得在a中定义的全局变量可以在其它文件中 也能够被使用。(4)指针使用需要注意的问题 (1)符号& *:各自的作用 (1)&:取地址符 (2)*:标准说法解引用,通俗理解,找到地址指向的空间,找到后读或者写 (2)我们说指针指向某个空间(变量或者函数块内存空间),指得是指针存放的数据(地址)指向了 某个空间。 (3)对于指针来讲,如果不涉及强制转换的情况下,进行初始化或者赋值运算时,要求左值得类型与 右值得类型要相同。 (4)所有普通变量的地址是一级地址,需要使用相同类型的类型的一级指针变量来存储,一级指针变量的 地址是二级地址,需要使用同等类型的指针变量来存储。 (5)一般情况下,3级以上的指针变量不会提高程序效率,反而会降低程序效率。 (6)对于多级指针需要关注p, &p, *p,在使用多级指针是需要注意指针断裂的问题。 (7)为什么需要指针 (1)为了高效率的使用大块内存,比如数组。 (2)利用指针的指向malloc和new开辟的空间,方便的实现内存自由管理,特别是c++中指针 指向new开辟的对象空间。 (3)高效率的使用函数(函数指针),c语言使用函数指针也可以实现以面向对象的思想来开发大型 项目。 (8)一定需要指针吗 只要有存储器,不管是内存还是外存,就一定有指针,只是对于语言来说指针完全可以被封闭起来。 但是对于开发偏向底层的软件来说,指针的是很重要的,高效利用内存。 (9)目前c++面临的尴尬地位 (1)底层程序开发中,有一个很难回避的矛盾 如果程序想要运行效率高的话,就必须使用带有可以操作指针的语言进行开发。 如果用汇编无疑效率是最高的,但是使用带有指针的语言的话,由于语言本身的复杂性 导致构建大型项目比较艰难。 如果我们只考虑开发效率的话,我们就应该选择像java/c#这类的简单易理解的语言,开发 大型项目的效率非常高,复杂的事情都留给了语言的托管平台去做了,但是这类语言的托管 平台是非常耗费管理资源的。 (2)目前的情况 目前计算机的内存非常大,我们不需要考虑内存不够用的情况,我们只需要考虑开发效率问题 ,大多数都会选择没有指针的语言。 如果需要开发非常偏底层的软件系统的话,实际上C语言已经完全能够胜任,不管是开发开发 偏上层的还是开发偏下层的,实际上C++的与语言的复杂度都要比c和java要难,而且很容易 产生内存操作的bug。 (3)未来的it开发的趋势 总之如今C++的应用面越来越窄,主要是由C++本身的难度和复杂度决定的。毕竟市场 只希望以更少的成本和最快效率进行开发,以更少的资源应付维护。除非是一些嵌入式设备的公司 ,考虑到系统能力(java也可以开发界面,但是java管理平台太吃资源,嵌入式设备玩不起), 还会坚持用C++进行项目开发。 未来的趋势应该是以JAVA、(andriod / object-C)、.net、python等开发效率高为主导的 研发市场,所以建议以C++知识为基础,积极向C/java/安卓/ object-C/.net/python方向发展。 如果守在C++上不是非常明智。 更加未来的趋势是,软件的开发会越来越模块化,越来越简单化,甚至简单到傻瓜式的操作,很多操作 很复杂的语言,在未来的地位会越来越弱,至于会不会被淘汰,就无从得知了。 (4)因此学习C++,一定要过好c语言这一关。 (10)与指针相关的强制转换 (1)强类型语言和弱类型语言 站在数据类型的强弱来分,语言分为强类型语言和弱类型语言,语言中的数据类型不是必要的, 完全可以将所有的数据类型弄成以一种类型,典型的看汇编和脚本语言,这两种那个语言中 是没有数据类型,或者说所有的数据都是一个类型。 (2)数据类型好处 让编译器做大量的严格类型检查,可以帮助我们检查大量的人为错误,提高编程效率。 (4)具体来讲,数据类型有哪些作用呢(基本类型,结构体等聚合类型,类类型等) (1)程序员编程识别,特别是在利用右值向左值赋值时,类型的识别尤为重要。 (2)给编译器识别的:规定了数据的解析方式 (1)存储格式的识别:int和double的存储格式就不同 (2)存储空间大小的确定,特别是在空间的查找时,永远找到的都是 变量的第一个字节的空间,至于后续还有多少字节空间也算做是 变量的空间,就看类型的规定了。 (5)普通变量的强制转换 (1)强制转换时,改变的是中间临时变量,原变量内容不发生改变。 (2)强制转换的目的,数据解析方式的改变 (3)强制转换的改变的是空间的存储结构和数据的大小。 (1)如果小空间向大空间强转,一般来说问题不大 (2)大空间向小空间的强制转换需要特别注意,会导致数据的丢失 (3)需要注意,强制转换可能会导致存储结构的变化。 (3)指针的强制转换 理解了普通变量的强制转换后,指针变量的强制转换就好理解了。 (1)指针变量的强制转换修改的是指针所指向空间的解释方式。 (4)指针的强制转换对于C++的意义 在c++中,需要实现多态时,有一个必不可少的步骤就是指向子类的指针需要向上转型为 一个父类型的指针,这叫做向上转型,在java中一样涉及向上转型,本质上也是指针操作。 (5)指针强制转换符号 (1)reinterpret_cast<>():const的指针不能强制转为非const的指针,但是反过来,非const转为const可以 (1)正确的情况 double b = 100; int * const pb1 = (int *)&b; const int *pb2 = NULL; pb2 = reinterpret_cast<const int *>(pb1); (2)不正确的情况 double b = 100; const int * const pb1 = (int *)&b; int *pb2 = NULL; pb2 = reinterpret_cast<int *>(pb1); (2)c中的符号():对()来说,不存在const的问题,但是会有警告。 (6)隐士强制转换和显示强制转换 (1)c和c++中都允许对普通类型(结构体联合体除外)进行隐式强制转换 (2)c允许对指针进行隐式强制转换,但可能是有警告,c++绝对不允许对指针进行隐式强制转换,必须显式转换 (7)指针类型与普通类型之间的强制转换 除了能在普通变量之间,以及指针之间进行强制转换外,很多时候还会可以在普通变量与指针之间进行转换 (8)void *类型的作用 (1)无类型:表示类型待定 (2)作用:当类型无法统一时,可以使用void *类型,等到临时使用时根据实际情况强转为需要的类型, 起到类型统一的作用。 (3)c中允许对void *类型进行隐式转换,但是c++中不允许(很严格),必须显式转换 (9)c和C++中空指针的不同定义 (1)c中的空指针NULL #define NULL (void *)0 (2)C++中的空指针0 #define NULL 0 在c++中,使用NULL和0来表示空指针是一样的 (3)空指针的意义 防止野指针 (10)指针可能导致的错误 (1)编译错误和警告 (2)运行时错误 (1)段错误 (1)大段错误 (2)小段错误 (3)导致错误的原因 (1)野指针,或者指针被人为的指定为了NULL (2)导致运行结果不对 (1)导致错误的原因:野指针 (11)如何才能安全的使用指针 (1)指针定义时,一定要初始化为NULL,防止野指针 (2)每次使用指针之前一定要先判断指针不为NULL (3)指针使用完成后,如果不在使用其指向的空间时,我们应该将其从新赋值为NULL (4)指针与数组的关系 (1)数组是指针的典型使用,如果想要深刻理解 (1)一维数组使用时如何使用指针进行访问的 (2)type p*与 type p[] (2)多维数组与只针对关系 在讲解数组时进行深入讲解 (5)C语言的字符串如何表示的(1)一维数组表示 (2)字符串指针 (1)这种表示方式的存储方式 (2)[]或者*访问字符串内容是应该注意的问题(3)字符串指针数组(6)动态分配内存(1)c方式 malloc free (1)普通变量 (2)数组空间(2)C++方式 new delete (1)普通变量 (2)数组空间 int *p = new [4]; delete [] p;(3)注意内存泄漏的问题(7)如果不考虑C++中引用方式的话,函数传参数时,使用指针的原则是什么(1)效率 数组和结构体(2)修改(3)const与指针(8)强制转换总结C风格的强制类型转换(Type Cast)很简单,不管什么类型的转换统统是: TYPE b = (TYPE)a C++风格的类型转换提供了4种类型转换操作符来应对不同场合的应用。 const_cast,字面上理解就是去const属性。 static_cast,命名上理解是静态类型转换。如int转换成char。 dynamic_cast,命名上理解是动态类型转换。如子类和父类之间的多态类型转换。 reinterpreter_cast,仅仅重新解释类型,但没有进行二进制的转换。 4种类型转换的格式,如: TYPE B = static_cast(TYPE)(a) const_cast 去掉类型的const或volatile属性。 struct SA { int i; }; const SA ra; //ra.i = 10; //直接修改const类型,编译错误 SA &rb = const_cast<SA&>(ra); rb.i = 10; static_cast 类似于C风格的强制转换。无条件转换,静态类型转换。用于: 1. 基类和子类之间转换:其中子类指针转换成父类指针是安全的;但父类指针转换成子类指针是不安全的。 (基类和子类之间的动态类型转换建议用dynamic_cast) 2. 基本数据类型转换。enum, struct, int, char, float等。static_cast不能进行无关类型(如非基类和子类)指针之间的转换。 3. 把空指针转换成目标类型的空指针。 4. 把任何类型的表达式转换成void类型。 5. static_cast不能去掉类型的const、volitale属性(用const_cast)。 int n = 6; double d = static_cast<double>(n); // 基本类型转换 int *pn = &n; double *d = static_cast<double *>(&n) //无关类型指针转换,编译错误 void *p = static_cast<void *>(pn); //任意类型转换成void类型 dynamic_cast 有条件转换,动态类型转换,运行时类型安全检查(转换失败返回NULL): 1. 安全的基类和子类之间转换。 2. 必须要有虚函数。 3. 相同基类不同子类之间的交叉转换。但结果是NULL。 class BaseClass { public: int m_iNum; virtual void foo(){}; //基类必须有虚函数。保持多态特性才能使用dynamic_cast }; class DerivedClass: public BaseClass { public: char *m_szName[100]; void bar(){}; }; BaseClass* pb = new DerivedClass(); DerivedClass *pd1 = static_cast<DerivedClass *>(pb); //子类->父类,静态类型转换,正确但不推荐 DerivedClass *pd2 = dynamic_cast<DerivedClass *>(pb); //子类->父类,动态类型转换,正确 BaseClass* pb2 = new BaseClass(); DerivedClass *pd21 = static_castDerivedClass *>(pb2); //父类->子类,静态类型转换,危险!访问子类m_szName成员越界 DerivedClass *pd22 = dynamic_cast<DerivedClass *>(pb2); //父类->子类,动态类型转换,安全的。结果是NULL reinterpreter_cast 仅仅重新解释类型,但没有进行二进制的转换: 1. 转换的类型必须是一个指针、引用、算术类型、函数指针或者成员指针。 2. 在比特位级别上进行转换。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。但不能将非32bit的实例转成指针。 3. 最普通的用途就是在函数指针类型之间进行转换。 4. 很难保证移植性。 int doSomething(){return 0;}; typedef void(*FuncPtr)(); //FuncPtr is 一个指向函数的指针,该函数没有参数,返回值类型为 void FuncPtr funcPtrArray[10]; //10个FuncPtrs指针的数组 让我们假设你希望(因为某些莫名其妙的原因)把一个指向下面函数的指针存入funcPtrArray数组: funcPtrArray[0] = &doSomething; // 编译错误!类型不匹配,reinterpret_cast可以让编译器以你的方法去看待它们:funcPtrArray funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething); //不同函数指针类型之间进行转换 总 结 去const属性用const_cast。 基本类型转换用static_cast。 多态类之间的类型转换用daynamic_cast。 不同类型的指针类型转换用reinterpreter_cast。
阅读全文
0 0
- 指针
- 指针
- 指针
- 指针
- 指针
- 指针
- 指针
- 指针
- 指针
- 指针
- 指针
- 指针
- 指针
- 指针
- 指针
- 指针
- 指针
- 指针
- 版本控制之Git相关知识及命令操作
- windows10蓝屏显示详细信息
- CAD编辑指南6:CAD导入图片和在CAD中创建表格
- 设备树解析(setup_machine_fdt,early_init_dt_scan_memory,..)
- XML 中如何输入回车换行?
- 指针
- Redis实践(四)高可用的集群+哨兵部署 下的jedis开发
- Json文件打包找不到的问题(请看标号6)
- zookeeper安装教程(windows的环境)
- 2016 年 7 个最佳的 Java 框架
- ubuntu/Linux终端shell配置代理 | git 配置代理
- thinkPHP 登录
- ubuntu查看文件和文件夹大小
- @RestController、@Controller、@ResponseBody