C++指针变量的定义和使用(非常详细)
C++ 指针作为一种表示内存地址的特殊变量,其定义形式也有一定的特殊性:
数据类型之后的
请看下面的例子:
这两种形式没有对错之分,可根据个人编程风格来选择。笔者推荐第一种形式,因为它把指针视为一种数据类型,使得定义指针变量的语句更加清晰明了,可读性更强。
需要注意的是,当我们用第一种形式在一条语句中定义多个指针变量时,可能会产生混淆,例如:
如果确实需要定义多个相同类型的指针变量,也可以使用 typedef 关键字将指针类型定义为新的数据类型,然后用这个新的数据类型来定义多个指针变量:
对指针变量进行赋值的语法格式如下:
数据通常是用变量来表示的,因此获得变量的内存地址相当于获得数据所在的内存地址,从而可以用这个地址给指针变量赋值。在 C++ 中,我们可以利用

图:指针和指针所指向的数据
指针的初始化赋值最好是在定义指针时同时进行。例如,在上面的例子中,我们可以在定义指针 pN 的同时获取变量 N 的内存地址并赋值给该指针,从而使得指针在一开始就有一个合理的初始值,避免未初始化的指针被错误地使用。
如果在定义指针时确实没有合理的初始值,我们可以将其赋值为 nullptr(C++ 11标准中引入的关键字),表示这个指针没有指向任何内存地址,是一个空指针(null pointer),此时指针还不能使用。例如:
我们可以用
特别地,如果一个指针指向的是一个结构体类型的变量,与结构体变量使用
需要特别说明的是,虽然两个指针指向同一个变量在语法上是合法的,但在实际开发中应当尽量避免。因为稍有不慎,这种代码就会给人带来困扰。继续上面的例子:
结合前面的代码就会明白,pa 和 pb 指向的是同一个变量 a。当我们通过指针 pb 修改变量 a 后,再通过 pa 来获得变量 a 的数据,自然就是更新过后的数据了。表面上看,似乎没有通过 pa 对变量 a 进行修改,但实际上,pb 早已暗渡陈仓,将变量 a 的数据修改了。
在程序中,最忌讳这种“偷偷摸摸”的行为,因为一旦这种行为导致程序运行错误,将很难被发现。因此,应尽量避免两个指针指向同一个变量,就如同一个人最好不要取两个名字一样。
数据类型* 指针变量名;
其中,数据类型
指的是指针所表示的地址上存储的数据的类型。例如,如果我们要定义一个指针来表示某个 int 类型数据的地址,那么指针定义中的数据类型就是 int。这个数据类型由指针所指向的数据决定,可以是 int、string 和 double 等基本数据类型,也可以是自定义的结构体等复杂数据类型。简而言之,指针所指向的数据是什么类型,就用这种类型作为指针变量定义时的数据类型。数据类型之后的
*
符号表示定义的是一个指针变量,而指针变量名
则是给这个指针指定的名字。请看下面的例子:
// 定义指针变量p,它可以记录某个int类型数据的地址 int* p; // 定义指针变量pEmp,它可以记录某个Employee类型数据的地址 Employee* pEmp
选择合适的定义指针变量的方式
实际上,下面两种定义指针变量的形式都是合乎 C++ 语法的:int* p; int *p;这两种形式都可以通过编译,并表示相同的语法含义。然而,这两种形式所反映的编程风格和所强调的意义稍有不同。
int* p
强调的是“p 为一个指向 int 类型整数的指针”,这里可以把int*
视为一种特殊的数据类型,而整个语句强调的是 p 是这种数据类型(int*)的一个变量。int *p
是把*p
当作一个整体,强调的是“这个指针指向的是一个 int 类型的整数”,而 p 就是指向这个整数的指针。这两种形式没有对错之分,可根据个人编程风格来选择。笔者推荐第一种形式,因为它把指针视为一种数据类型,使得定义指针变量的语句更加清晰明了,可读性更强。
需要注意的是,当我们用第一种形式在一条语句中定义多个指针变量时,可能会产生混淆,例如:
// p是一个int类型的指针变量,而q实际上是一个int类型的变量 // 可能会让人误认为p和q都是int类型的指针 int* p, q; // 以下形式更清楚一些:*p是一个整数,p是指向这个整数的指针,q也是一个整数 int *p, q; // 定义两个指向int类型数据的指针p和q int *p, *q;在开发实践中,有一条编码规范:一条语句只完成一件事情。按照这条规范,我们可以通过分开定义 p 和 q 来很好地避免上述问题。
如果确实需要定义多个相同类型的指针变量,也可以使用 typedef 关键字将指针类型定义为新的数据类型,然后用这个新的数据类型来定义多个指针变量:
// 将Employee指针类型定义成新的数据类型EMPointer typedef Employee* EMPointer; // 用EMPointer类型定义多个指针变量,这些变量都是Employee*类型 EMPointer pCAO,pCBO,pCCO,pCDO;
指针的赋值和使用
在定义一个指针变量之后,指针变量的初始值通常是一个随机值。它可能所指向某个无关紧要的数据,也可能指向重要的数据或者程序代码。如果直接使用这些未初始化的指针,后果可能是不可预期的。虽然有时可能啥事儿都没有,但也可能因此引发严重的问题。因此,在使用指针之前,务必对其赋值以进行初始化,将其指向某个有意义且合法内存位置。对指针变量进行赋值的语法格式如下:
指针变量 = 内存地址;
可以看到,对指针变量的赋值实际上是将这个指针指向某一内存地址,而该内存地址上存放的就是指针想要指向的数据。数据通常是用变量来表示的,因此获得变量的内存地址相当于获得数据所在的内存地址,从而可以用这个地址给指针变量赋值。在 C++ 中,我们可以利用
&
取地址运算符,将该运算符放在某个变量的前面,以获得该变量的内存地址。例如:
//定义一个整型变量,用以表示整数1003 int N = 1003; //定义整型指针变量pN,用&符号取得整型变量N的地址 //并将其赋值给整型指针变量 int* pN = &N;这里,我们用
&
符号取得整型变量 N 的内存地址,即存储 1003 这个整数所在的内存地址,然后将这个地址赋值给整型指针变量 pN,也就是将指针 pN 指向存储 1003 这个数据的内存地址,如下图所示。
图:指针和指针所指向的数据
指针的初始化赋值最好是在定义指针时同时进行。例如,在上面的例子中,我们可以在定义指针 pN 的同时获取变量 N 的内存地址并赋值给该指针,从而使得指针在一开始就有一个合理的初始值,避免未初始化的指针被错误地使用。
如果在定义指针时确实没有合理的初始值,我们可以将其赋值为 nullptr(C++ 11标准中引入的关键字),表示这个指针没有指向任何内存地址,是一个空指针(null pointer),此时指针还不能使用。例如:
//定义一个指针变量 pN,赋值为 nullptr,表示它没有指向任何内存位置 //这里只是定义变量,后面才会对其进行赋值 int* pN = nullptr; //... //判断pN是否指向某个数据 //如果pN的值不是以 nullptr 作为初始值,就表示它被重新赋值指向某个数据 if(nullptr != pN) { //使用pN指针访问它所指向的数据 cout<<"指针pN所指向的数据是:"<<*pN<<endl; }
我们可以用
&
运算符获得一个变量(数据)的内存地址,反过来,也可以用*
运算符表示一个内存地址上的变量(数据)。*
被称为指针运算符,或称为解析运算符。它所执行的是与&
运算符完全相反的操作。如果把*
放在一个指针变量的前面,就可以取得这个指针所指向的内存地址中的数据。例如:
//输出pN指向的内存地址0x0016FA38 cout<<pN<<endl; //通过*运算符获取pN所指向的内存地址中的数据1003并输出 //等同于 cout<<N<<endl; cout<<*pN<<endl; //通过指针修改它所指向地址中存储的数据 //等同于 N=1982; *pN = 1982; //输出修改后的数据1982 cout<<*pN<<endl;通过
*
运算符可以取得 pN 这个指针所指向的数据变量 N,虽然 N 和 *pN 的形式不同,但是它们都代表内存中的同一份数据,都可以对这个数据进行读写操作,并且是等效的。特别地,如果一个指针指向的是一个结构体类型的变量,与结构体变量使用
.
符号引用成员变量不同的是,如果是指向结构体的指针,则应该使用->
符号引用成员变量。这个符号很像一个指针。例如:
//定义一个结构体变量 Employee Zengmei; //定义一个指针变量,用 & 运算符取得变量Zengmei的地址,并对赋值给结构指针变量 //也就是将指针pZengmei指向这个结构体变量Zengmei Employee* pZengmei = &Zengmei; //用->运算符引用这个结构体指针变量的成员变量 pZengmei->m_strName = "Zengmei"; pZengmei->m_nAge = 28;
尽量避免把两个指针指向同一个变量
当指针变量被正确赋值指向某个变量后,它就会成为一个有效的内存地址,也可以用它对另一个指针赋值。这样,两个指针指向相同的内存地址,指向同一份数据。例如:// 定义一个整型变量 int a = 1982; // 得到变量a的内存地址并赋值给指针pa int* pa = &a; // 使用pa对另一个指针pb赋值 int* pb = pa;在这里,我们用已经指向变量 a 的指针 pa 对指针 pb 赋值,这样 pa 和 pb 的值相同,都是变量 a 的地址,也就是说,两个指针指向了同一个变量。
需要特别说明的是,虽然两个指针指向同一个变量在语法上是合法的,但在实际开发中应当尽量避免。因为稍有不慎,这种代码就会给人带来困扰。继续上面的例子:
// 输出pa指向的数据,当前为1982 cout<<*pa<<endl; // 通过pb修改它所指向的数据,修改为1003 *pb = 1003; // 再次输出pa指向的数据,已经变为1003 cout<<*pa<<endl;如果我们只看这段程序的输出,一定会感到奇怪:为什么没有通过 pa 进行任何修改,前后两次输出的内容却不同?
结合前面的代码就会明白,pa 和 pb 指向的是同一个变量 a。当我们通过指针 pb 修改变量 a 后,再通过 pa 来获得变量 a 的数据,自然就是更新过后的数据了。表面上看,似乎没有通过 pa 对变量 a 进行修改,但实际上,pb 早已暗渡陈仓,将变量 a 的数据修改了。
在程序中,最忌讳这种“偷偷摸摸”的行为,因为一旦这种行为导致程序运行错误,将很难被发现。因此,应尽量避免两个指针指向同一个变量,就如同一个人最好不要取两个名字一样。