指针是C++的一个核心概念,是程序员直接对内存进行操作的一种工具。这样的工具是一把双刃剑,一方面可以实现一些非常优化的程序,另一方面也会导致一些难以调试的错误。
一:使用指针遍历数组
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
int arr[5] = {0,1,2,3,4};
int *ptr = arr;
cout << "使用指针遍历数组: ";
for (int i = 0; i < 5; ++i)
{
cout << *ptr << " ";
ptr ++;
//也可以直接写成 cout << *(ptr++) << " ";
}
cout << endl;
return 0;
}
int *ptr = arr 该语句用数组名初始化指针。在这里数组名代表的是数组第一个元素的地址,之后在循环内程序会递增指针,以指向数组后面的几个元素。
二:指针的概念和理解
指针(Pointer),从英文字面上来理解就是一个指向某一物件的东西,在程序中就是指向数据的地址。计算机的内存可以看成一个机密排列的数据序列,每一小块数据(也就是字节)旁边都有一个编号代表数据的地址。
假设arr的地址是203,那么数组后面的几个元素的地址依次递增(这个例子中因为数组类型是int,所以真实的地址需要依次加4字节)。之前我们说过,指针实际上就是内存地址,所以arr的值就是203,而当ptr指向数组最后一个元素时,它的值是207。
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
int arr[5] = {0,1,2,3,4};
int *ptr = arr;
for (int i = 0; i < 5; ++i)
{
cout << *ptr << "指针地址" << ptr << endl;
ptr ++;
}
return 0;
}
我们可以看到每个元素之间的间距正好是int的大小 - 4字节
三:指针的创建和初始化
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
float *floatPtr = NULL;
string *strPtr;
int *intPtr1, *intPtr2;
int* intPtr3, intPtr4; //intPtr4只是一个整数
cout << "float指针地址" << floatPtr << endl;
cout << "string指针地址" << strPtr << endl;
cout << "指针地址" << intPtr2 << endl;
cout << "指针地址" << intPtr4 << endl;
return 0;
}
注意:初学者很容易在声明多个指针的时候遗漏后面变量前的星号(),就像intPtr4一样,感觉像是定义了一个指针,其实只是一个整型。*
此外,示例中只有第一行的floatPtr初始化了,但是在实际编程中我们一定要初始化所有的指针,就和变量一样。floatPtr的初始值NULL是一个宏定义,它的实际数值是0,也就是地址0x00000000。一般我们把初始化为NULL的指针叫做空指针。因此我们只要检查指针是否为NULL就知道指针是否指向有效数据。
如果指针没有初始化,他可能指向一个未知的地址,那么我们在尝试读取数据的时候就有可能造成程序崩溃。此外,在初始化的时候,不能使用0以外的整型给指针赋值。
四:指针的基本操作
对于指针来说,解引用和取地址是重要的两个操作符;
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
int num = 4;
int *intPtr = #
cout << "初始num的值" << num << endl;
cout << "num地址:" << &num << endl;
cout << "指针的值是:" << intPtr << endl;
if (intPtr) //检查指针是否为空
{
cout << "指针所指的数字是" << *intPtr << endl;
}
*intPtr = 5; //解引用操作符也可以用作为赋值语句的左值以修改数据
cout << "修改后num的值" << num << endl;
return 0;
}
- "&"表示取地址操作符,他可以获得变量的内存地址。
- "*"表示解引用操作符,他可以得到所知向地址的数据。
五:指针的算术操作
指针可以像整形那样进行一部分算术操作,还可以对地址进行修改。因为计算后的指针不一定会指向具有有效数据的地址,所以我们在进行指针的算术操作的时候需要格外小心。
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
int arr[5] = {0,10,20,30,40};
int *ptr = arr;
cout << "arr + 4: " << *(arr+4) << endl;
cout << "ptr + 4: " << *(ptr+4) << endl;
cout << "ptr: " << ptr << endl;
cout << "ptr + 2: " << ptr+2 << endl;
cout << "++ptr: " << ++ptr << endl;
cout << "ptr - 2: " << ptr-2 << endl;
cout << "--ptr: " << --ptr << endl;
return 0;
}
我们可以看到,指针与整型的算术操作不同于一般的数字加减,而是与指针的类型绑定的。由于一个int的大小是4字节,那么ptr+2会将地址加上8;在数组中就是指向第三个元素。
数组名其实可以看作指向第一个元素的指针。指针的各种操作都适用于数组名,但只有一点区别,那就是数组名不能重复赋值。这也是很容易理解的,因为数组是静态的,数组名当前作用域唯一的一个数组,不可能像指针那样指向其他地址。
指针除了与整型的算术操作之外,还可以进行指针相减。
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
int arr[5] = {0,10,20,30,40};
int *ptr1 = arr + 1;
int *ptr2 = arr + 3;
cout << "ptr1:" << ptr1 << endl;
cout << "ptr2:" << ptr2 << endl;
cout << "ptr1 - ptr2 = " << ptr1 - ptr2 << endl;
cout << "ptr2 - ptr1 = " << ptr2 - ptr1 << endl;
return 0;
}
指针相减返回的是指针地址之间的距离,并且是分正负的。这个距离也与类型绑定,单位是该类型数据的个数。指针之间不存在加法,相加会报错。
六:const指针
指向const对象的指针 ( const int *Ptr)
- 不能修改解引用后的值。
- 可以修改指向的地址
- 可以指向普通变量。
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
const int num = 3;
//普通指针不能指向const变量
// int *ptr1 = # 报错
const int *ptr2 = #
cout << "*ptr2:" << *ptr2 << endl;
//指向const对象的指针不能修改解引用后的值
// *ptr2 = 4;
//指向const对象的指针可以修改指向的地址
const int num1 = 4;
ptr2 = &num1;
cout << "*ptr2:" << *ptr2 << endl;
//指向const对象的指针也可以指向普通变量
int num2 =5;
ptr2 = &num2;
cout << "*ptr2:" << *ptr2 << endl;
//同样也不能修改解引用的值
// *ptr2 = 10;
// cout << "num2:" << num2 << endl;
return 0;
}
const指针 (int *const Ptr)
- 不能修改指向的地址
- 可以修改指向变量的值
- 只想const对象的const指针既不能修改地址,也不能修改值
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
int num1 = 3;
int num2 = 4;
int *const ptr1 = &num1;
//const指针不能修改指向地址
ptr1 = #
const int num3 =5;
const int num4 =6;
//指向const对象的指针既不能修改地址,也不能修改值;
const int *const ptr2 = num3;
ptr2 = num4;
*ptr2 = 100;
return 0;
}
上面代码报错不能执行。
七:指针的数组和数组的指针
指针作为一种变量类型,自然可以被声明成数组,而数组作为一种变量类型,也可以有指向他的指针。所以指针的数组是一种数组,而数组的指针则是一种指针。
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
int arr[5] = {0,1,2,3,4};
//数组的指针
int (*arrPtr)[5] = &arr;
//指针的数组
int *ptrArr[5] = {&arr[0],&arr[1],&arr[2],&arr[3],&arr[4]};
cout << "arrPtr:" << arrPtr << endl;
cout << "*arrPtr:" << *arrPtr << endl;
for (int i = 0; i < 5; ++i)
{
cout << (*arrPtr)[i] << " ";
cout << ptrArr[i] << " ";
cout << *(ptrArr[i]) << " ";
cout << endl;
}
return 0;
}
数组的指针和指针的数组的语法区别在于:数组的指针需要在星号和变量名外加一个括号,而指针的数组却没有。这一点其实很好理解,因为声明数组的时候元素类型名int和数组大小[5]是被变量名隔开的,在这里我们添加一个星号,并且用括号括起来,表示这个指针int(arrPtr)[5]是指向整个数组的;如果不加括号,编译器就只会将星号联系到前面的类型名int所以ptrArr就只是生命了一个数组,数组类型是int。
八:指针的指针
指针可以指向任何变量或对象,所以也可以指向指针。
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
int num = 3;
int *numPtr = #
int **numPtrPtr = &numPtr;
cout << "num: " << num << endl;
cout << "numPtr: " << numPtr << endl;
cout << "*numPtr: " << *numPtr << endl;
cout << "numPtrPtr: " << numPtrPtr << endl;
cout << "*numPtrPtr: " << *numPtrPtr << endl;
cout << "**numPtrPtr: " << **numPtrPtr << endl;
return 0;
}
我们可以看到, 指针的指针的声明就是多加了一个星号,以表示指针指向的是指针类型,因此我们将numPtr的地址赋值给它。可以由以下图解解释
这样看来指针的指针就和普通的指针没有什么区别。不过一般情况下,我们也不使用这种不直观的类型,指针的指针一般用于函数传参数时修改传入的指针。