指针

来源:互联网 发布:手机淘宝投诉卖家流程 编辑:程序博客网 时间: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。