1 什么是指针
指针是存储内存地址的变量
与所有变量一样,指针也占用内存空间,其特殊之处在于,指针包含的值为内存地址,因此,指针是指向内存单元的特殊变量
内存单元通常使用十六进制表示法
1.1 声明指针
和其它变量一样,指针在使用前也需要声明。通常将指针声明为指向特定的类型,如int,这意味着指针变量包含的地址对应的内存单元存储了一个整数;也可将指针声明为指向一个内存块,这种指针被称为void指针
格式:
PointedType* PointerVariableName
可将指针初始化为NULL,NULL是一个可检查的值,且不会是内存地址
1.2 使用引用运算符&获取变量地址
1.3 使用解引用运算符 * 访问指向的数据
1.4 将sizeof()作用于指针
指针是包含内存地址的变量,所以不管其指向哪种类型的变量,内容都是一个地址。因此,将sizeof()作用于指针时,结果取决于编译程序时使用的编译器和针对的操作系统,与指针指向的变量类型无关
2 动态内存分配
2.1 使用new和delete动态分配内存和释放内存
使用new分配新的内存块,成功则返回一个指向分配内存的指针,否则触发异常。使用new时,需要指定要为哪种数据类型分配内存
Type* Pointer=new Type;
Type* Pointer=new Type[numElements]; //指定为多个元素分配内存
new表示请求分配内存,并不能保证分配总能得到满足,取决于系统状态机内存资源的可用性
释放内存使用delete
delete Pointer
delete[] Pointer
new[...]分配的内存块,需使用delete[]释放;使用new为单个元素分配的内存,需使用delete释放
不再使用分配的内存后,如果不释放它们,这些内存仍被预留并分配给应用程序。这将减少可供其它应用程序使用的系统内存量,甚至降低应用程序的执行速度,这被称为内存泄漏。
delete只能用于new返回的且未使用delete释放的指针,而不能将其用于任何包含地址的指针
2.2 对指针使用自加++自减--运算符
对指针使用自加++自减--运算符,编译器将会指向内存块相邻的值(同时假定该值类型与前一个值类型相同),而不是相邻字节
示例:
#include <iostream>
using namespace std;
int main()
{
cout << "How many integers you wish to enter? ";
int numEntries = 0;
cin >> numEntries;
int* pointsToInts = new int [numEntries];
cout << "Allocated for " << numEntries << " integers" << endl;
for(int counter = 0; counter < numEntries; ++counter)
{
cout << "Enter number "<< counter << ": ";
cin >> *(pointsToInts + counter);
}
cout << "Displaying all numbers entered: " << endl;
for(int counter = 0; counter < numEntries; ++counter)
cout << *(pointsToInts++) << " ";
cout << endl;
// return pointer to initial position
pointsToInts -= numEntries;
// done with using memory? release
delete[] pointsToInts;
return 0;
}
调用delete[]释放内存时,必须指定分配内存时new返回的指针地址。这个值存储在pointsToInts中,但后面修改了pointsToInts的值,所以在使用delete前使用-=让pointsToInts重新指向原来地址(很不方便啊。。。)
2.3 const指针
指针也是变量,因此可将关键字const用于指针。const指针有如下三种:
常量指针(顶层const)
指针变量存储的地址(所指向的变量的地址)是常量,不可修改,但可修改指针指向的变量的值
示例:
int daysInMonth=30; //一个月有多少天
int* const pDaysInMonth=&daysInMonth;
*pDaysInMonth=31; //可以修改指向的数据
int daysInLunarMonth=28;
pDaysInMonth=&daysInLunarMonth; //错误,不可让指针指向另一个地址
指向常量的指针(底层const)
指针指向的内容是常量,不能修改,但可以修改指针存储的地址,即指针可以指向其它地方
在C++ Primer中,又说可以修改指针指向的变量(p56),只是它以为它指向的是常量
虚线表示可选的指向地址
声明示例:
const int* pointsToInt;
指针包含的地址以及它指向的值都是常量,不能修改
声明示例:
const int* const pHourInDay;
函数参数应该声明为第三张const,以确保函数不会修改指针指向的值
2.4 数组与指针
数组变量是指向其第一个元素的指针
3 使用指针常犯的错误
3.1 内存泄露
确保程序释放其分配的所有内存
3.2 指针指向无效的内存单元
使用运算符 * 对指针解引用以访问指向的值时,务必确保指针指向了有效的内存单元,否则程序要么崩溃、要么发生不可预测的行为
3.3 悬浮指针
初始化指针或释放指针后都应将其设置为NULL,并在使用运算符 * 对指针解引用前检查它是否有效(将其与NULL比较)
3.4 检查使用new发出的分配请求是否得到满足
除非请求分配的内存量特别大或系统处于临界状态,可供使用的内存很少,否则new的请求一般都能成功。但还是有许多情况内存分配不一定成功,C++有两种确认指针有效的方法:
- 默认方法使用异常,即如果内存分配失败,将触发std::bad_alloc异常,这将导致程序中断,除非提供了异常处理程序,否则程序将崩溃并显示“异常未处理”的消息
- 第二张方法使用new变种new(nothrow),在内存分配失败时不引发异常,而返回NULL,让程序员在使用指针前检查其有效性
4 引用是什么
引用是变量的别名。声明引用时,需将其初始化为一个变量,因此引用只是另一种访问相应变量存储的数据的方式
引用声明需使用引用运算符&,格式:
VarType original=Value;
VarType& ReferenceVariable=original;
示例程序:
#include <iostream>
using namespace std;
int main()
{
int original = 30;
cout << "original = " << original << endl;
cout << "original is at address: " << hex << &original << endl;
int& ref1 = original;
cout << "ref1 is at address: " << hex << &ref1 << endl;
int& ref2 = ref1;
cout << "ref2 is at address: " << hex << &ref2 << endl;
cout << "Therefore, ref2 = " << dec << ref2 << endl;
return 0;
}
输出:
original = 30
original is at address: 0x7ffde6e23cb4
ref1 is at address: 0x7ffde6e23cb4
ref2 is at address: 0x7ffde6e23cb4
Therefore, ref2 = 30
输出表明,无论将引用初始化为变量还是其它引用,它都将指向相应变量所在的内存单元
4.1 引用的作用
- 函数编写
之前的函数编写传参过程,一般直接使用变量,这将产生较大的复制开销。引用可以直接使用调用者栈中的数据,从而避免复制操作。(和指针有什么区别呢??)
程序示例:
#include <iostream>
using namespace std;
void GetSquare(int& number){
number *= number;
}
int main()
{
cout << "Enter a number you wish to square: ";
int number = 0;
cin >> number;
GetSquare(number);
cout << "Square is: " << number << endl;
return 0;
}