阐述指针和引用
C++最大的优点之一是,您既可使用它来编写不依赖于机器的高级应用程序,又可使用它来编写与硬件紧密协作的应用程序。事实上,C++让您能够在字节和比特级调整应用程序的性能。要编写高效地利用系统资源的程序,理解指针和引用是必不可少的一步。
在本章中,您将学习:
• 什么是指针;
• 什么是自由存储区;
• 如何使用运算符 new 和 delete 分配和释放内存;
• 如何使用指针和动态分配编写稳定的应用程序;
• 什么是引用;
• 指针和引用的区别;
• 什么情况下使用指针,什么情况下使用引用。
第八章主要讨论了C++中的指针和引用,这两个概念是C++中非常重要的部分,它们允许程序员更直接地操作内存。以下是第八章的学习笔记概述:
8.1 什么是指针
- 指针定义:指针是存储另一个变量内存地址的变量。指针是一个变量,与所有变量一样,指针也占用内存空间。
- 声明指针:使用
PointedType * PointerVariableName;
格式声明。 - 初始化指针:通常初始化为
NULL
或nullptr
,防止未定义行为。
8.1.1 声明指针
- 指针需要明确指向的类型,如
int *pInt;
表示指向int
类型的指针。 - 与大多数变量一样,除非对指针进行初始化,否则它包含的值将是随机的。您不希望访问随机的 内存地址,因此将指针初始化为 NULL。NULL 是一个可以检查的值,且不会是内存地址
- 与您学过的所有数据类型一样,除非对指针进行初始化,否则它包含的将是垃圾值。对 指针来说,这种垃圾值非常危险,因为指针包含的值被视为地址。未初始化的指针可能 导致程序访问非法内存单元,进而导致程序崩溃。
8.1.2 使用引用运算符(&)获取地址
- 使用
&
运算符获取变量的内存地址。 - 前面的输出表明, int 变量 age 的地址为 0x0045FE00,而 sizeof(int)为 4,因此 0x0045FE00-0x0045FE04 的 4 字节内存由 int 变量 age 占用。
8.1.3 使用指针存储地址
- 声明指针并使用
&variable
初始化,使其存储变量的地址。
8.1.4 使用解除引用运算符(*)访问指向的数据
- 使用
*pointer
来访问或修改指针指向的数据。
8.1.5 将 sizeof() 用于指针的结果
sizeof
运算符对指针使用时,返回指针本身的大小,与指针所指向的类型无关。- 您知道,指针是包含内存地址的变量。因此无论指针指向哪种类型的变量,其内容都是一个地址 —一个数字。在特定的系统中,存储地址所需的字节数是固定的。因此,将 sizeof( )用于指针时,结 果取决于编译程序时使用的编译器和针对的操作系统,与指针指向的变量类型无关
- 程序清单 8.6 的输出表明,将 sizeof 用于指针的结果为 4 字节,但在您的系统上结果可 能不同。这里的输出是使用 32 位编译器编译代码时得到的,如果您使用的是 64 位编 译器,并在 64 位系统上运行该程序,可能发现将 sizeof 用于指针的结果为 64 位,即 8 字节。
8.2 动态内存分配
- 使用
new
和delete
:动态分配和释放内存。 - 内存泄露:忘记使用
delete
释放内存导致的问题。 -
如果在程序中使用下面这样的数组声明:
int myNums[100]; // a static array of 100 integers
程序将存在两个问题。 1.这限制了程序的容量,无法存储 100 个以上的数字。 2.如果只需存储 1 个数字,却为 100 个数字预留存储空间,这将降低系统的性能。
导致这些问题的原因是,数组的内存分配是静态和固定的。 要编写根据用户需要使用内存资源的应用程序,需要使用动态内存分配。这让您能够根据需要分 配更多内存,并释放多余的内存。为帮助您更好地管理应用程序占用的内存,C++提供了两个运算符 —new 和 delete。指针是包含内存地址的变量,在高效地动态分配内存方面扮演了重要角色。
8.2.1 使用 new 和 delete 动态地分配和释放内存
new Type
分配单个元素的内存。new Type[numElements]
分配数组的内存。delete
释放单个元素的内存。delete[]
释放数组的内存。- 不再使用分配的内存后,如果不释放它们,这些内存仍被预留并分配给您的应用程序。这将减少 可供其他应用程序使用的系统内存量,甚至降低您的应用程序的执行速度。这被称为内存泄露,应不 惜一切代价避免这种情况发生。
- 运算符 new 和 delete 分配和释放自由存储区中的内存。自由存储区是一种内存抽象,表 现为一个内存池,应用程序可分配(预留)和释放其中的内存。
8.2.2 将递增和递减运算符(++ 和 --)用于指针的结果
- 指针递增
++
移动到下一个元素的地址。 - 指针递减
--
移动到上一个元素的地址。 - 将指针递增或递减的结果 将指针递增或递减时,其包含的地址将增加或减少指向的数据类型的 sizeof(并不一定是 1 字节)。 这样,编译器将确保指针不会指向数据的中间或末尾,而只会指向数据的开头。 如果声明了如下指针:Type* pType = Address; 则执行++pType 后,pType 将包含(指向)Address + sizeof(Type)。
8.2.3 将关键字 const 用于指针
const
指针的不同类型和使用场景。- 通过将变量声明为 const 的,可确保变量的取值在整个生命周期内都固定为初始值。 这种变量的值不能修改,因此不能将其用作左值。 指针也是变量,因此也可将关键字 const 用于指针。然而,指针是特殊的变量,包含内存地址,还 可用于修改内存中的数据块。因此,const 指针有如下三种。
8.2.4 将指针传递给函数
-
如何通过指针向函数传递数据,并控制函数对数据的修改能力。
-
指针是一种将内存空间传递给函数的有效方式,其中可包含函数完成其工作所需的数据,也可包 含操作结果。将指针作为函数参数时,确保函数只能修改您希望它修改的参数很重要。例如,如果函 数根据以指针方式传入的半径计算圆的面积,就不应允许它修改半径。为控制函数可修改哪些参数以 及不能修改哪些参数,可使用关键字 const
8.2.5 数组和指针的类似之处
- 数组名本身就是一个指向数组首元素的指针。
- 换句话说,数组类似于在 固定内存范围内发挥作用的指针。可将数组赋给指针,如第 11 行所示,但不能将指针赋给数组,因为 数组是静态的,不能用作左值。myNumbers 是不能修改的。
- 使用运算符 new 动态分配的指针仍需使用运算符 delete 来释放,虽然其使用语法与静态 数组类似。牢记这一点很重要。 如果忘记这样做,应用程序将泄露内存,这很糟糕。
8.3 使用指针时常犯的编程错误
-
内存泄露:忘记释放动态分配的内存。这可能是 C++应用程序最常见的问题之一:运行时间越长,占用的内存越多,系统越慢。如果在 使用 new 动态分配的内存不再需要后,程序员没有使用配套的 delete 释放,通常就会出现这种情况。
-
指针指向无效的内存单元:未初始化或错误的内存访问。使用运算符*对指针解除引用,以访问指向的值时,务必确保指针指向了有效的内存单元,否则程 序要么崩溃,要么行为不端。这看起来合乎逻辑,但一个非常常见的导致应用程序崩溃的原因就是无 效指针。指针无效的原因很多,但主要归结于糟糕的内存管理。
-
悬浮指针:使用已经释放的内存地址。悬浮指针(也叫迷途或失控指针) 使用 delete 释放后,任何有效指针都将无效。换言之,在程序清单 8.13 中,即便在第 22 行前指针 isSunny 是有效的,但第 22 行调用 delete 后,它也变成无效的了,不应再使用。 为避免这种问题,很多程序员在初始化指针或释放指针后将其设置为 NULL,并在使用运算符*对 指针解除引用前检查它是否有效(将其与 NULL 比较)。
8.3.4 检查使用 new 发出的分配请求是否得到满足
- 在前面的代码中,我们都假定 new 将返回一个指向内存块的有效指针。事实上,除非请求分配的 内存量特大,或系统处于临界状态,可供使用的内存很少,否则 new 一般都能成功
- 有些应用程序需 要请求分配大块的内存(如数据库应用程序),因此最好不要假定内存分配能够成功。C++提供了两种 确认指针有效的方法,默认方法是使用异常(这也是前面一直使用的方法),即如果内存分配失败,将 引发 std::bad_alloc 异常。这导致应用程序中断执行,除非您提供了异常处理程序,否则应用程序将崩 溃,并显示一条类似于“异常未处理”的消息。
8.4 指针编程最佳实践
- 初始化指针。
- 仅在指针有效时使用。
- 使用
new
分配的内存,必须使用delete
释放。 - 释放内存后,不再访问该内存。
8.5 引用是什么
- 引用定义:引用是另一个变量的别名。
- 使用
&
声明引用,如Type& reference = variable;
。
8.5.1 是什么让引用很有用
- 引用用于函数参数,避免复制,提高效率。
8.5.2 将关键字 const 用于引用
-
const
引用不能修改其指向的变量的值。 -
//可能需要禁止通过引用修改它指向的变量的值,为此可在声明引用时使用关键字 const: int original = 30; const int& constRef = original; constRef = 40; // Not allowed: constRef can’t change value in original int& ref2 = constRef; // Not allowed: ref2 is not const const int& constRef2 = constRef; // OK
8.5.3 按引用向函数传递参数
-
使用引用传递可以避免复制大对象,提高性能。
-
引用的优点之一是,可避免将形参复制给形参,从而极大地提高性能。然而,让被调用的函数直 接使用调用函数栈时,确保被调用函数不能修改调用函数中的变量很重要。为此,可将引用声明为 const 的,如程序清单 8.19 所示。const 引用参数不能用作左值,因此试图给它们赋值将无法通过编译。
-
-
在前一个程序中,使用同一个参数来接受输入和存储结果,但这里使用了两个参数,一个用于接 受输入,另一个用于存储运算结果。为禁止修改传入的值,必须使用关键字 const 将其声明为 const 引 用,如第 3 行所示。这让 number 自动变为输入参数—其值不能修改的参数。 您可以尝试修改第 5 行,使其像程序清单 8.18 那样返回平方值: number *= number; 这将导致编译错误,指出 const 值不能修改。这说明 const 引用将参数标识为输入参数,并禁止对 其进行修改。乍一看,这可能微不足道,但在多名程序员合作编程时,编写第一个版本的人和改进的 人可能不同,通过使用 const 引用可提高编程质量。
ber; 这将导致编译错误,指出 const 值不能修改。这说明 const 引用将参数标识为输入参数,并禁止对 其进行修改。乍一看,这可能微不足道,但在多名程序员合作编程时,编写第一个版本的人和改进的 人可能不同,通过使用 const 引用可提高编程质量。
在Microsoft Word中插入目录,可以通过以下步骤实现: