🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ## 概述 指针的使用是现代编程中最容易出错的领域之一,以至于像Java、C#和Visual Basic这些现代语言都没有提供指针数据类型。 ## 用来理解指针的范例 从概念上看,每一个指针都包含两个部分:内存中的某处位置,以及如何解释该位置中的内容。 ### 内存中的位置 1. 内存中的一个位置就是一个地址,常用16进制形式表示。 2. 32位处理器中的一个地址用一个32位的值表示,比如0x0001EA40。 3. 指针本身只包含这个地址。为了使用该指针所指向的数据,就必须访问该地址,解释该位置的内存内容。 4. 如果去查看该地址的内存,可以发现它只是**一组二进制位**。必须经过解释才能使它变得有意义 ### 如何解释指针所指向的内容 1. 如何解释内存中某个位置的内容,是由指针的基类型( base type)决定的。 2. 如果某指针指向整数,编译器就会把该指针所指向内存位置的数据解释为整数 3. **你可以让一个整数指针、一个字符串指针和一个浮点数指针都指向同一个内存位置。但是其中(至多)只有一个指针能正确地解释该位置的内容** #### 示例 在理解指针的时候,应该记住内存并不包含任何与之相关联的内在的解释。只有通过使用一个特定类型的指针,一个特殊位置的比特才能解释为有意义的数据 * 原始内存空间 ``` [0A 61 62 63 64 65 67 68 69 6A] ``` 1. 解释方式: String[10](以 Visual Basic的格式表示,第一个字节存储长度) 意义:abcdefghij 2. 解释方式:双字节的整数 意义:24842 3. 解释方式:双字节的整数 意义:24842 4. 解释方式:四字节的浮点数 意义:4.17595656202980E+0021 5. 解释方式:四字节的整数 意义:1667391754 6.解释方式:字符 意义:换行符(ASCI码16进制的0A或者10进制的10) ## 使用指针的一般技巧 ### 把指针操作限制在子程序或者类里面 假设你在程序中多次使用了一个链表。每次使用它的时候,不要通过手动操作指针去遍历该链表,应该编写一组诸如 NextLink()、 PreviousLink()、 InsertIn()和 DeleteLink()这样的访问 ### 同时声明和定义指针 在声明处附近赋值 bad ``` Employee *empoyeePtr; // lots of code employeePtr = new Employee; ``` good ``` Employee *empoyeePtr = new Employee; ``` ### 在与指针分配相同的作用域中删除指针 如果你需要在一个单一作用域内使用指针,那么就应该在此作用域范围内用new分配指针,用 delete释放指针。 如果你在一个子程序内分配了一个指针,那么就在同一个子程序里释放它。 如果你在一个对象的构造函数里面分配了一个指针,就要在析构函数中释放它 ### 用狗牌字段来检测损坏的内存 是指你加入结构体内的一个仅仅用于检测错误的字段。 在分配一个变量的时候把一个应该保持不变的数值放在它的标记字段里。当你使用该结构的时候---特别是当你释放其内存的时---检测这个标记字段的取值。 如果这个标记字段的取值与预期不相符,那么这一数据就被破坏了。 ### 按照正确的顺序删除链表中的指针 在使用动态分配链表时,经常遇到的个问题是,如果先释放了链表中的第一个指针,就会致使表中的下一个指针无法访问。为了避免这一问题,在释放当前指针之前,要确保已经有指向链表中下个元素的指针。 ### 分配一片保留的内存后备区域 如果在你的程序中使用了动态内存,就需要避免发生程序忽然用尽了内存、把你的用户和用户的数据丢在RAM空间里的尴尬场景。 使你的程序对这类错误留出缓冲地带的一种方法是预先分配一片内存后备区域。**设法确定程序为了“保存所做的工作,执行清理并体面地退出”需要用多少内存**。 在程序初始化阶段把这部分内存分配出来作为后备,然后就可以不再管它。一旦你真的用光了内存,就释放保留下来的这片后备区,执行清理工作然后退出 ### 粉碎垃圾数据 在C语言中,在释放内存区域之前用垃圾数据来覆盖这些内存区域,可以让与使用已释放的指针有关的错误的表现方式更一致。 在C++里面,你可以在每次删除指针的时候使用类似于下面的代码: ``` //强制让释放的内存包含垃圾数据 memset( pointer, GARBAGE_DATA, MemoryBlocksize( pointer )); delete pointer; ``` 对c++对象不奏效 ### 在删除或释放指针之后把他们设定为空值 可有效的防止"悬空指针" 尽管在删除指针后再将其设为null并不能阻止你去读取一个空悬指针所指向的数据,但这的确可以保证当你向一个空悬指针写入数据时会产生错误。这一错误可能引发一场巨大的灾难。但至少可以由你首先发现这一错误,而不是留给其他人。 example ``` //C++示例:在删除指针之后将其设为NUL memset( pointer, GARBAGE_DATA, MemoryBlocksize( pointer )); delete pointer; pointer= NULL; ``` ### 在删除变量之前检测非法指针 通过断言 ``` // C++示例:在删除指针之前断言其不为NULL ASSERT( pointer != NULL,"Atting to delete null pointer."); memset( pointer, GARBAGE_DATA, MemoryBlocksize( pointer )); delete pointer; pointer= NULL; ``` ### 跟踪指针分配情况 维护一份你已经分配的指针的列表 ``` //G++示例:检查是否已经分配了某个指针 ASSERT( pointer != NULL,"Atting to delete null pointer."); if (IsPointerInList(pointer)){ memset( pointer, GARBAGE_DATA, MemoryBlocksize( pointer )); RemovePointerPromList(pointer) delete pointer; pointer= NULL; }else{ ASSERT( FALSE, 'Attempt ing to delete unallocated pointer.): } ``` ### 通过子程序,集中实现上述所有策略 可以实现两个子程序`SAFE_NEW`和`SAFE_DELETE` 1. SAFE_NEW 这个子程序调用new来分配指针 把这一新的指针加入已分配指针列表中, 然后将这一新分配的指针返回给调用方子程序。 它还可以在该子程序内检查new操作的返回值是否为空或是否抛出异常(也就是是否发生“内存不足”错误),从而简化了程序其他部分的错误处理。 2. SAFE_DELETE 这个子程序检查传递给它的指针是否在已分配指针的列表里。 如果它在列表里,就把该指针所指向的内存设置为垃圾数值,把该指针从列表中移除,再调用C++的 delete运算符释放该指针,并且把该指针设为空值。如果该指针不在列表里,那么SAFE_ DELETE将显示一条诊断信息,并且终止程序运行。 ``` //C++示例:在删除指针的代码外加一层包裹 define SAFE_DELETE( pointer ){\ ASSERT( pointer ! NULL, "Attempting to delete null pointer. "):\ if (IsPointerInList( pointer )){ \ memset( pointer, GARBAGE_DATA, MemoryBlocksize( pointer )); \ RemovePointerPromList(pointer); \ delete pointer; \ pointer= NULL; \ else{\ ASSERT( FALSE, .Attempting to delete unallocated pointer .);\ } ``` ### 采用非指针的技术 采用非指针的技术指针比较难理解,容易用错,也容易导致依赖于具体机器的不可移植代码。如果你能想到任何替代指针的方案,而它又能工作得很合理,那么就应该去用它,避免这些令人头痛的问题 ## C++ 指针 ### 理解指针和引用之间的区别 在C++中,指针(*)和引用(&)都能够间接地引用对象。但事实上,最重要的区别是,引用必须总是引用一个对象,而指针则可以指向空值,还有,引用所指向的对象在该引用初始化之后不能改变 ### 把指针用于"按引用传递"参数,把 const引用用于"按值传递"参数 C++向子程序传递参数的默认方式是传递值( pass by value)而不是传递引用( pass byreference)。 当你以传递值的方式向一个子程序传递一个对象的时候,C++创建了该对象的一份拷贝,当该对象传递回调用方子程序的时候,又有创建了一份拷贝。 对于大对象而言,这种复制可能**耗费大量时间和其他的资源**。 因此,当你向一个子程序传递对象的时候,通常会希望避免复制该对象,这就意味着你希望按照引用来传递它而不是按值来传递。 ### 使用 auto_ptr 通过在离开作用域的时候自动释放内存,auto_ptr(现在推荐使用 shared_ptr)能避免很多与常规指针相关的内存泄漏问题。 ### 灵活运用只能指针 灵活运用智能指针智能指针( smart pointers)是常规指针或者“哑(dumb)指针的一种替代品( Meyers1996)。它用起来与常规指针十分相像,但是针对资源管理、拷贝操作、赋值操作、对象构造和对象析构提供了更多的控制。 ## C指针 ### 使用显式指针类型而不是默认类型 使用显式指针类型而不是默认类型C允许你对任何类型的变量使用char或者void指针。C语言只关心这类指针有所指向,不会真正去关心它所指向的是什么。 然而,如果你使用了显式的指针类型,编译器就会针对不相符的指针类型和不合适的解除引用( dereferences)发出警告。 ### 避免强制类型转换 强制类型转换关闭了编译器检查类型不符的功能因此在你的防御式编程的铠甲上挖了一个洞。 ### 遵循参数传递的星号规则 只有当你在赋值语句的参数前面加了星号(*),才能把该参数从子程序中传回去。 ``` //C示例: 不奏效的参数传递 void TryToPassBackAvalue(int parameter){ paramter =SOME_VALUE; } //C示例:奏效的参数传递 void TryToPassBackAValue(int *paramter){ *paremter =SOME_VALUE; } ``` ### 在内存分配中使用 sizeof确定变量的大小 在内存分配中使用 确定变量的大小使用 sizeof()要比到手册里去查找大小容易得多。 它是可以移植的—在不同的环境下重新编译,将自动修改 sizeof()计算出来的值。