C ++的一大优势是它允许我们编写更接近硬件的高级应用程序。实际上,C ++使我们能够在字节和位级别上调整应用程序的性能。了解指针和引用如何工作是编写高效的程序的一步。
指针也是一个变量,它的地址存储在内存中。int类型的变量用于表示一个整型值,指针类型的变量用于表示内存地址。
如上图所示,指针本身也保存在内存中,它是一种特殊变量,其值指向内存中某个地址。
需要注意的是,通常使用十六进制来寻址存储器位置。十六进制具有16个不同的符号,从0到9后跟A-F,十六进制通常使用前缀0x。因此,0xA是10的十六进制; 0xF是15的十六进制; 对于16,0x10是十六进制。
像变量一样,指针也需要声明,通常声明指针来指向一个特定类型的值,意思是,该指针中包含的内存地址的位置是一个该类型的值。也可以声明指向一个内存块的指针(也称为void指针)。
指针变量的声明如下:
PointedType * PointerVariableName;
除非初始化指针变量,否则它包含的是一个随即值。可以将指针变量初始化为NULL:
PointedType * PointerVariableName = NULL; // initializing value
声明一个整型的指针变量:
int *pointsToInt = NULL;
需要注意的是,如果只声明不初始化,指针会包含垃圾值,对于指针来说,这个垃圾值特别危险,因为指针的值应该包含一个地址。未初始化的指针可能导致程序访问无效的内存位置,从而导致崩溃。
变量是编程语言用来处理内存中的数据的工具,如果varName是一个变量,那么&varName表示内存中该变量的地址。
声明一个变量:
int age = 30;
&age表示内存中的一个地址,其存放的值为30。
代码示例:
#include
using namespace std;
int main()
{
int age = 30;
const double Pi = 3.1416;
// Use & to find the address in memory
cout << "Integer ange is located at: 0x" << &age << endl;
cout << "Double Pi is located at: 0x" << &Pi << endl;
return 0;
}
输出:
Integer ange is located at: 0x00B3FA78
Double Pi is located at: 0x00B3FA68
通过上面的内容我们已经学会来指针的声明以及变量地址的获取,指针存储的是变量地址,那么如何将变量的地址赋值给指针?变量的赋值方式为:
// Declaring a variable
Type VariableName = InitialValue;
参考上面的形式,指针的赋值方式为:
// Declaring a pointer to Type and initializing to address
Type* Pointer = &Variable;
指针变量赋值操作的代码示例:
#include
using namespace std;
int main()
{
int age = 30;
int* pointsToInt = &age; // pointer initialized to &age
cout << "Integer age is at: 0x" << &age << endl;
// Displaying the value of pointer
cout << "Integer age is at: 0x" << pointsToInt << endl;
return 0;
}
输出:
Integer age is at: 0x009CFD1C
Integer age is at: 0x009CFD1C
指针重新赋值,代码示例:
#include
using namespace std;
int main()
{
int age = 30;
int* pointsToInt = &age; // pointer initialized to &age
cout << "pointToInt points to age now" << endl;
// Displaying the value of pointer
cout << "pointToInt = 0x" << hex << pointsToInt << endl;
int dogsAge = 9;
pointsToInt = &dogsAge;
cout << "pointToInt points to dogsAge now" << endl;
cout << "pointToInt = 0x" << hex << pointsToInt << endl;
return 0;
}
输出:
pointToInt points to age now
pointToInt = 0x005AFAC0
pointToInt points to dogsAge now
pointToInt = 0x005AFAA8
一个指针变量指向一个地址,那么如何访问这个被指向的地址中的数据?这个时候需要用到运算符(*)。
代码示例:
#include
using namespace std;
int main()
{
int age = 30;
int dogsAge = 9;
cout << "Integer age = " << age << endl;
cout << "Integer dogsAge = " << dogsAge << endl;
int* pointsToInt = &age;
cout << "pointToInt points to age now" << endl;
// Displaying the value of pointer
cout << "pointToInt = 0x" << hex << pointsToInt << endl;
// Displaying the value at the pointed location
cout << "*pointsToInt = " << dec << *pointsToInt << endl;
pointsToInt = &dogsAge;
cout << "pointToInt points to dogsAge now" << endl;
cout << "pointToInt = 0x" << hex << pointsToInt << endl;
cout << "*pointToInt = " << dec << *pointsToInt << endl;
return 0;
}
输出:
Integer age = 30
Integer dogsAge = 9
pointToInt points to age now
pointToInt = 0x0093FB40
*pointsToInt = 30
pointToInt points to dogsAge now
pointToInt = 0x0093FB34
*pointToInt = 9
上面的代码演示的是如何从指针指向的内存地址中读取存储的值,下面代码示例演示*pointsToInt作为“左值”时的情况:
#include
using namespace std;
int main()
{
int dogsAge = 30;
cout << "Initialized dogsAge = " << dogsAge << endl;
int* pointsToAnAge = &dogsAge;
cout << "pointsToAnAge points to dogsAge" << endl;
cout << "Enter an age for your dog: ";
// store input at the memory pointed to by pointsToAnAge
cin >> *pointsToAnAge;
// Displaying the address where age is stored
cout << "Input stored at 0x" << hex << pointsToAnAge << endl;
cout << "Integer dogsAge = " << dec << dogsAge << endl;
return 0;
}
输出:
Initialized dogsAge = 30
pointsToAnAge points to dogsAge
Enter an age for your dog: 5
Input stored at 0x0100FBE0
Integer dogsAge = 5
指针是包含内存地址的一个变量,无论指向何种类型,指针的内容都是地一串表示地址的数字。地址的长度,即存储它所需的字节数,是由系统决定的,因此sizeof()函数的结果依赖于编译器和操作系统,而不依赖于指向的数据。
动态内存分配,允许在需要的时候申请更多内存,在不需要的时候释放多余的内存。C ++提供了两个运算符new和delete,帮助我们更好地管理应用程序的内存消耗。指针变量在高效的动态内存分配中起着至关重要的作用。
使用new关键字分配新的内存块,如果分配成功,会返回对应内存位置的指针,否则会抛出错误提示。根据不同情况,分配的方式有以下几种:
(1)为一个元素请求内存
Type* Pointer = new Type; // request memory for one element
(2)为多个元素请求内存(指定元素数目)
Type* Pointer = new Type[numElements]; // request memory for numElements
例如,为整数分配内存,有下面两种:
int* pointToAnInt = new int; // get a pointer to an integer int* pointToNums = new int[10]; // pointer to a block of 10 integers
使用new关键字分配的内存,可以使用delete关键字释放:
Type* Pointer = new Type; // allocate memory
delete Pointer; // release memory allocated above
或:
Type* Pointer = new Type[numElements]; // allocate a block
delete[] Pointer; // release block allocated above
如果使用完后未释放已分配的内存,则此内存将一直保留,这减少了可供应用程序使用的系统内存量,可能使应用程序的执行速度变慢。这被称为泄漏,应该避免这种情况。
动态内存分配和内存回收的示例如下:
#include
using namespace std;
int main()
{
// Request for memory space for an int
int* pointsToAnAge = new int;
// Use the allocated memory to store a number
cout << "Enter your dog's age: ";
cin >> *pointsToAnAge;
// use indirection operator* to access value
cout << "Age " << *pointsToAnAge << " is stored at 0x" << hex << pointsToAnAge << endl;
delete pointsToAnAge; // release memory
return 0;
}
输出:
Enter your dog’s age: 9
Age 9 is stored at 0x0020AEA8
需要注意的是,delete关键字只能删除使用new分配的内存。
使用new关键字分配内存块的示例:
#include
using namespace std;
int main()
{
cout << "How mamy integers shall I reserve memory for?" << endl;
int numEntries = 0;
cin >> numEntries;
int* myNumbers = new int[numEntries];
cout << "Memory allocated at: 0x" << myNumbers << hex << endl;
// de-allocate before exiting
delete[] myNumbers;
return 0;
}
输出:
How mamy integers shall I reserve memory for?
5001
Memory allocated at: 0x00C93340
指针包含内存地址,如果一个指向整数的指针包含0x002EFB34(放置整数的地址),整数本身长4个字节,因此占用内存中从0x002EFB34到0x002EFB37的四个位置。 使用运算符(++)递增此指针不会导致指针指向0x002EFB35,因为指向整数的中间位置毫无意义。
指针的自增或自减操作被编译器解释为指向内存块中的下一个值,而不是下一个字节。
因此,指针的自增操作(假设是int类型指针),会导致它增加4个字节,在此指针上使用++运算符相当于告诉编译器希望它指向下一个整数。因此,在递增之后,指针将指向0x002EFB38。类似地,向该指针添加2会导致它向前移动2个整数,即前面的8个字节。
对指针做自增或自减操作,指针中的地址按指向的类型的大小递增或递减(并且不一定是字节)。 这样,编译器确保指针永远不会指向存储器中放置的数据的中间或末尾,它只指向地址的开始位置。
如果一个指针声明成如下形式:
Type* pType = Address;
++pType意味着pType指向的地址变成Address + sizeof(Type)。
在第3章中了解到将变量声明为const意味着变量的值必能被改变,因此该变量必能作为L值。
指针也是变量,因此const关键字也可用于指针。但是,指针是一种特殊的变量,因为它们包含一个内存地址。会在以下几种情况中对指针使用const关键字:
(1)指针指中包含的(指向的)地址不能更改,但是该地址中的数据可以更改:
int daysInMonth = 30;
int* const pDaysInMonth = &daysInMonth;
*pDaysInMonth = 31; // OK! Data pointed to can be changed
int daysInLunarMonth = 28;
pDaysInMonth = &daysInLunarMonth; // Not OK! Cannot change address!
(2)指针指向的地址中的数据不能更改,但是指针的值(指向的地址)可以更改:
int hoursInDay = 24;
const int* pointsToInt = &hoursInDay;
int monthsInYear = 12;
pointsToInt = &monthsInYear; // OK!
*pointsToInt = 13; // Not OK! Cannot change data being pointed to
int* newPointer = pointsToInt; // Not OK! Cannot assign const to non-const
(3)指针指向的地址和地址中的数据都不能更改
int hoursInDay = 24;
const int* const pHoursInDay = &hoursInDay;
*pHoursInDay = 25; // Not OK! Cannot change data being pointed to
int daysInMonth = 30;
pHoursInDay = &daysInMonth; // Not OK! Cannot change address
代码示例:
#include
using namespace std;
void CalcArea(const double* const ptrPi, // const pointer to const data
const double* const ptrRadius, // i.e. no change allowed
double* const ptrArea) // can change data pointed to
{
// check pointers for validity before using!
if (ptrPi && ptrRadius && ptrArea)
*ptrArea = (*ptrPi) * (*ptrRadius) * (*ptrRadius);
}
int main()
{
const double Pi = 3.1416;
cout << "Enter radius of circle: ";
double radius = 0;
cin >> radius;
double area = 0;
CalcArea(&Pi, &radius, &area);
cout << "Area is = " << area << endl;
return 0;
}
输出:
Enter radius of circle: 10.5
Area is = 346.361
数组和指针有许多相似之处,具体如代码所示:
#include
using namespace std;
int main()
{
// Static array of 5 integers
int myNumbers[5];
// array assigned to pointer to int
int* pointToNums = myNumbers;
// Display address contained in pointer
cout << "pointToNum = 0x" << hex << pointToNums << endl;
// Addreaa of first e;ement of array
cout << "&myNumbers[0] = 0x" << hex << &myNumbers[0] << endl;
return 0;
}
输出:
pointToNum = 0x00DCFA5C
&myNumbers[0] = 0x00DCFA5C
可以看出指针指向的就是数组首位在内存中的位置,数组的第一位就是指针(可以这样想)。
访问数组的第二、三位的元素,可以通过指针加1的方式实现,代码示例如下:
#include
using namespace std;
int main()
{
const int ARRAY_LEN = 5;
// Static array of 5 integers, initialized
int myNumbers[ARRAY_LEN] = { 24, -1, 365, -999, 2011 };
// Pointer initialized to first element in array
int* pointToNums = myNumbers;
cout << "Display array using pointer syntax, operator*" << endl;
for (int index = 0; index < ARRAY_LEN; ++index)
cout << "Element " << index << "= " << *(myNumbers + index) << endl;
cout << "Display array using ptr with array syntax, operator[]" << endl;
for (int index = 0; index < ARRAY_LEN; ++index)
cout << "Element " << index << "=" << pointToNums[index] << endl;
return 0;
}
输出:
Display array using pointer syntax, operator*
Element 0= 24
Element 1= -1
Element 2= 365
Element 3= -999
Element 4= 2011
Display array using ptr with array syntax, operator[]
Element 0=24
Element 1=-1
Element 2=365
Element 3=-999
Element 4=2011
这一段似乎有点难理解,pointToNums指向的是数组首地址,pointToNums[index]表示的是数组中的值。myNumbers是数组名,也是数组首地址,*(myNumbers + index)是数组中的值。
C ++允许动态分配内存,以便优化和控制应用程序的内存消耗。与基于运行时环境的较新语言(如C#和Java)不同,C ++没有自动垃圾收集器来清理程序已分配但无法使用的内存。所以使用指针管理内存资源容易发生一些错误,在这里提出一些常见的情况。
发生这种情况可能是分配的内存没有回收造成的。以下这种情况不应该发生:
int* pointToNums = new int[5]; // initial allocation
// use pointToNums
...
// forget to release using delete[] pointToNums;
...
// make another allocation and overwrite
pointToNums = new int[10]; // leaks the previously allocated memory
当使用运算符(*)取消引用指针以访问指向的值时,需要确保指针包含有效的内存位置,否则程序将崩溃或行为异常。无效指针是应用程序崩溃的常见原因。由于一些原因,指针可能无效,主要是由于编程和内存管理不佳。
使用delete释放后,任何指针都将无效。为避免指针释放后仍被使用,可以在初始化或删除指针后为指针赋值NULL。
在代码中,假设new将返回一个指向内存块的有效指针。 实际上,除非应用程序要求异常大量的内存,或者系统处于如严重的状态以至于没有内存可供使用,否则new通常会成功。 有些应用程序需要对大块内存(例如,数据库应用程序)发出请求。此外,不要简单地假设内存分配请求将始终成功。C ++提供了两种可能的方法,以确保指针在使用之前有效。我们迄今为止使用的默认方法为使用异常,不成功的分配导致抛出std :: bad_alloc类型的异常。异常会导致应用程序的执行中断,除非编写了异常处理程序,否则应用程序会以错误消息“未处理的异常”结束。
要始终记得初始化指针变量,否则它们将包含垃圾值。 这些垃圾值可能被解释为程序无法访问的地址位置;
使用new分配内存时,用完记得使用delete删除;
使用delete删除分配的内存后不要再访问该指针;
不要在同一个内存地址上使用超过一次delete关键字;
引用是变量的别名,声明引用时,需要将其初始化为变量。因此,引用变量只是访问存储在被引用变量中的数据的不同方式。
可以使用引用运算符(&)声明引用,如以下语句所示:
VarType original = Value;
VarType& ReferenceVariable = original;
程序示例:
#include
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: 006FFE98
ref1 is at address: 006FFE98
ref2 is at address: 006FFE98
Therefore, ref2 = 30
程序的输出表明,无论是第10行所示的初始化给原始的变量,还是如13行所示的初始化给一个引用,它们都定位到变量original所在的内存地址中。一次,引用变量ref1和ref2都是变量original的别名。
代码示例:
#include
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;
}
输出:
Enter a number you wish to square: 10
Square is: 100
如上述代码所示,引用使得函数在传递参数的时候,能将函数外部的变量传递给参数,并在函数内部对其做更改。如果不使用引用的方式,要实现上述代码中的效果,需要使用return语句。
有些情况下可能不希望引用的原始值被更改,在声明此类引用时使用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
本章介绍了指针和引用,了解了指针如何用于访问和操作内存以及它们如何成为一种有助于动态内存分配工具,学习了分配内存操作符new和delete,以及他们的变体new … []和delete [],这可以为数组分配内存。
了解了指针编程和动态内存分配中的陷阱,以及了解到释放动态分配的内存对于避免内存泄漏非常重要。
另外,引用是变量的别名,并且是在向函数传递参数时使用指针的强大替代方法。在使用指针和引用时,使用const保证部分数据(信息)的不可更改性。