技术交流QQ群:1027579432,欢迎你的加入!
1.Cpp中的指针
- 每个变量都有一个内存位置,每一个内存位置都定义了可使用&地址运算符访问的地址,它表示了变量在内存中的一个地址。
int var1; int var2[10]; cout << "变量var1的地址:" << &var1 << endl; // 访问变量var1的在内存中的位置,使用&地址运算符,获得变量在内存中的地址 cout << "变量var2的地址:" << &var2 << endl;
2.什么是指针?
-
指针也是一个变量,它存放的值是另一个变量的地址,即变量在内存中的位置,即内存中的直接地址。与其他变量或常量一样,在使用指针存储其他变量地址之前,必须对指针进行声明,指针变量声明的一般形式是:
数据类型 *指针变量名;
- 在上述指针的指针变量声明过程中,数据类型必须是一个有效的C++数据类型,用来声明指针变量的星号*与乘法中使用的星号是相同的。但是,在上面的声明语句中,星号是用来指定一个变量是指针,有效的指针变量的声明是:
int *p; float *p1; double *ptr; char *p2;
- 由于所有指针变量中存放的都是另一个变量在内存中的地址,所以不管指针指向的是int,float,char还是其他数据类型,指针变量的值实际数据类型都是一样的!都是一个代表内存地址的十六进制数。不同数据类型的指针变量之间唯一的区别是:指针所指向的变量或常量的数据类型不同。32位机系统中,指针变量所占的字节数是sizeof(*p) = 4个字节。
3.C++中使用指针
- 指针变量使用时,会频繁使用以下几个操作:定义一个指针变量,把另外一个变量的地址赋值个指针,访问指针变量中可用地址的值,这些操作都是通过*运算符来返回位于操作数所指定地址的变量的值。
int var = 20; // 普通变量的初始化 int *ip; // 指针变量的声明 ip = &var; // 用指针变量存储普通变量var的地址 cout << "var的值是:" << var << endl; cout << "普通变量var在内存中的地址: " << &var << endl; cout << "指针变量ip在内存中的地址: " << &ip << endl; cout << "通过访问指针变量ip中存放的普通变量的地址来得到普通变量var的值: " << *ip << endl;
4.Cpp中指针详解
-
C++中与指针有关的概念如下表:
4.1 C++中的NULL指针
-
在指针变量声明时,如果没有明确的地址可以赋值时,为指针变量赋值一个NULL值是一个很好的习惯。NULL指针是一个定义在标准库中的值为0的常量。如下面的程序:
int *ptr = NULL; cout << "指针变量ptr在内存中的地址:: " << &ptr << endl; cout << "ptr的值是: " << ptr << endl;
- 在大多数的操作系统上,程序不允许访问地址为0的内存,因为该内存是操作系统保留的。然而,内存地址0有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。如果检查一个空指针,需要使用if语句:
if(ptr) // 如果ptr非空,则完成 if(!ptr) // 如果ptr为空,则完成
- 因此,如果所有未使用的指针都被赋予空值,同时避免使用空指针,就可以防止误用一个未初始化的指针。很多时候,未初始化的变量存有一些垃圾值,导致程序难以调试。
4.2 指针的算术运算
- 指针变量中存放的是一个用数值表示另一个变量的地址,可以对指针变量执行算术运算。对指针进行四个算术运算:++ -- - +,假设ptr是一个指向地址是1000的整型指针变量,是一个32位整数,对该指针执行算术运算ptr++后,ptr的指向位置是1004。因为ptr每增加一次,它都指向下一个整数位置,即当前位置往后移动4个字节。这个运算在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。如果ptr执向的是一个地址是1000的char型字符,上面的运算会导致指针指向位置1001,因为下一个字符位置是1001。
- 递增一个指针:在程序中使用指针代替数组,因为指针变量可以递增,而数组不能递增,因为数组名是一个指针常量,如下面的例子:
int a[MAX] = {10, 100, 200}; int *pp; pp = a; for (int i = 0; i<MAX; i++){ cout << "变量a[" << i << "]的地址是: " << &a[i] << endl; cout << "变量a[" << i << "]的地址是: " << pp << endl; cout << "变量a[" << i << "]的值是: " << a[i] << endl; cout << "通过访问指针变量pp中存放的普通变量的地址来得到数组元素的值: " << *pp << endl; cout << "------------------------------------------------\n"; pp++; // 移动到下一个位置 }
- 递减一个指针:对指针进行递减运算,即把值减去其指向的数据类型的字节数,如下所示:
pt = &a[MAX - 1]; for (int i = MAX - 1; i >= 0; i--){ cout << "变量a[" << i << "]的地址是: " << &a[i] << endl; cout << "变量a[" << i << "]的地址是: " << pt << endl; cout << "变量a[" << i << "]的值是: " << a[i] << endl; cout << "通过访问指针变量pt中存放的普通变量的地址来得到数组元素的值: " << *pt << endl; cout << "------------------------------------------------\n"; pt--; // 移动到下一个位置 }
- 指针的比较:指针可以用关系运算符进行比较,如果p1和p2指向两个相关的变量,如同一个数组中的不同元素,则可以对p1和p2进行大小比较。
tt = a; int j = 0; while (tt <= &a[MAX - 1]){ cout << "变量a[" << j << "]的地址是: " << tt << endl; cout << "变量a[" << j << "]的值是: " << a[j] << endl; tt++; j++; }
4.3 指针vs数组
- 指针与数组是密切相关的,在很多情况下两个是可以互换的。例如,通过一个指向数组开头的指针,可以通过使用指针的算术运算或数组的索引来访问数组,如下面例子:
int a[MAX] = {10, 100, 200}; int *pp; pp = a; for (int i = 0; i<MAX; i++){ cout << "变量a[" << i << "]的地址是: " << &a[i] << endl; cout << "变量a[" << i << "]的地址是: " << pp << endl; cout << "变量a[" << i << "]的值是: " << a[i] << endl; cout << "通过访问指针变量pp中存放的普通变量的地址来得到数组元素的值: " << *pp << endl; cout << "------------------------------------------------\n"; pp++; // 移动到下一个位置 }
- 然而,指针与数组并不是完全可以互换的,如下面的例子:
int b[MAX] = {1, 11, 111}; cout << "b = " << b << endl; cout << "*b = " << *b << endl; for (int i = 0; i < MAX; i++){ *b = i; // 改变的是数组第一个元素的值 // b++; 数组名是一个指针常量,不能进行修改 *(b + 2) = 555; } // 不是说数组内的值不能修改,而是只要b的值(地址)不变,那么相应位置的数值改变了,也不会影响该数组的首地址。
4.4 指针数组
- 引入:一个由3个整数组成的数组
int c[MAX] = {6, 66, 666}; for (int i = 0; i < MAX; i++){ cout << "c[" << i << "]的值是: " << c[i] << endl; }
- 可能有一种情况是:希望数组存储的是指向int/char或者其他数据类型的指针,下面是一个指向整数的指针数组的声明:
int * ptr[3];
- 把ptr声明为一个数组,由3个整数指针组成。因此,ptr中的每个元素,都是指向int类型的指针,见下面的例子:
int cc[MAX] = {6, 66, 666}; int *pc[MAX]; // 定义一个指针数组 for (int i = 0; i < MAX; i++){ pc[i] = &cc[i]; // 由于pc数组中的每个元素都是指向int类型的指针,所以这里赋值为cc[i]的每个元素的地址 } for (int i = 0; i < MAX; i++){ cout << "cc[" << i << "]的地址是: " << pc[i] << endl; cout << "cc[" << i << "] = " << *pc[i] << endl; }
- 也可以用一个指向字符的指针数组来存储一个字符串列表,如下面例子:
const char *names[MAX] = { "Curry", "Harden", "Durant", }; // const char * names[MAX]是指针数组,它的本质是存储指针的数组,存储的元素是char类型的指针 for (int i = 0; i < MAX; i++){ cout << "names[" << i << "] = "<< names[i] << endl; // 输出字符串的值 cout << "*names[" << i << "]的值是: " << *names[i] << endl; // 输出指针所指向的字符串的首地址的值 cout << "names[" << i << "]的地址是: " << &names[i] << endl; // 输出指针所指向的字符串的首地址 }
4.5 指向指针的指针(多级间接寻址)
-
指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针中存储的是另一个变量的地址,当再定义一个指向指针的指针时,第一个指针变量就包含了第二个指针变量的地址,第二个指针变量存储的是另一个变量的地址。
- 一个指向指针的指针变量必须进行声明,即在变量名前加上两个**,下面是一个指向int型的指针的指针:
int val = 3000; int *p1 = &val; int **p2 = &p1; // 指向int类型的指针的指针 int ***p3 = &p2; cout << "val的地址是: " << &val << endl; cout << "指针p1的地址是: " << &p1 << endl; cout << "指向指针p1的指针p2的地址是: " << &p2 << endl; cout << "指针指针p2的指针p3的地址是: " << &p3 << endl; cout << "--------------------------------------------------------\n"; cout << "指针p3中存放的是指针p2的地址: " << p3 << endl; cout << "指针p2中存放的是指针p1的地址: " << p2 << endl; cout << "指针p1中存放的是变量val的地址: " << p1 << endl; cout << "*p3得到的是p2的值即p1的地址: " << *p3 << endl; cout << "*(*p3)得到的是p1的值即a的地址: " << *(*p3) << endl; cout << "*(*(*p3))得到的是a的值: " << *(*(*p3)) << endl;
4.6 传递指针给函数
- C++中允许传递指针给函数,只需要简单地声明函数参数为指针类即可,下面的实例是传递一个long型指针给函数,并在函数内改变这个值:
void getSeconds(unsigned long *par){ *par = time(NULL); // 获取当前秒数 } // 传递指针给函数 unsigned long sec; getSeconds(&sec); // 输出实际值 cout << "number of seconds: " << sec << endl;
- 能接收指针作为参数的函数,也可以接收数组作为参数,如下所示:
double getAverage(int *arr, int size){ int i, sum = 0; double avg; for (i = 0; i < size; i++) sum += arr[i]; avg = double(sum) / size; return avg; } int balance[5] = {1, 2, 3, 4, 5}; double avg; // 传递一个指向数组的指针作为参数 avg = getAverage(balance, 5); cout << "平均值: " << avg << endl;
4.7 从函数返回指针
- 从函数返回指针,需要声明一个返回指针的函数,如下所示:
int * myFunction(){ 语句; }
- 此外,C++不支持在函数外部返回局部变量的地址,除非定义的是局部变量static变量。
- 返回从函数返回指针的实例如下:
// 要生成和返回随机数的函数 int * getRandom(){ static int r[10]; // 设置种子 srand((unsigned)time(NULL)); for (int i=0; i<10;i++){ r[i] = rand(); cout << "r[" << i << "]=" << r[i] << endl; } return r; } // 从函数返回一个指针 int *ppg; ppg = getRandom(); // 调用子函数,从子函数中返回一个指针 for (int i = 0; i < 10; i++) cout << "*(ppg + " << i << ") = " << *(ppg + i) << endl;
5.指针小结
- 指针的本质是变量,可以是各种数据类型,定义一个指针"*ip",其中"ip"需要赋于一个地址(可以用&符号获取其他变量的地址再赋值给 ip),而"*ip"是一个具体的值,即读取地址后获得的值;&符号的意思是取地址,也就是返回一个对象在内存中的地址, *符号的意思是取得一个指针所指向的对象,也就是如果一个指针保存着一个内存地址,那么它就返回在那个地址的对象。
#include <iostream> using namespace std; int main() { int var = 20; int *ip; ip = &var; cout << "var的值:"; cout << var << endl; cout << "变量ip的储存地址:"; cout << ip << endl; cout << "指针*ip的值:"; cout << *ip << endl; return 0; }
-
C++中允许声明指向函数的指针,被称为函数指针。函数指针的声明类似于函数的声明,只不过将函数名变成了(*指针名),定义方式: int (*fp)(int a); // 定义了一个指向函数(这个函数参数仅仅为一个int类型,函数返回值是int类型)的指针fp
int func(int b); { cout << b; return ++b; } // 定义一个函数指针 int(*p)(int); p = func; // 通过函数指针来调用函数 (*p)(5);