C++里,一直让我头疼的就是指针了,指针真的就是个p
所以花点时间研究了一下。
一、指针的“ * ”操作
指针的定义方式如下:
int *p = new int;
需要在名称前加上“*”,一直比较纠结的是,在使用的时候,什么情况情况下加“*”,什么情况下不加。
“*”就是取出指针所指向的东西。那么指针该如何赋值呢?
int *p = new int;
p = 10;
//以上会报错:不能将 int类型的值赋值给 int*类型的实体
有以上可以知道,在声明指针的时候,*p并不是指针,指针的本体是p,加上“*”即指针所指向的int型存储区域。
正确的赋值方式应该是:
*p = 10;
那么,不同指针有该如何赋值呢:
void main()
{
int *p = new int;
*p = 10;
cout << "p=" << p << endl;
int *q = new int;
cout << "q=" << q << endl;
q = p;
cout << "q=" << q << endl;
cout << "*q=" << *q << endl;
cin.get();
}
/*-------------输出结果如下:
p=000001E22F8F5E50
q=000001E22F8F4C10
q=000001E22F8F5E50
*q=10
----------------------*/
声明指针p指向int型数据10,打印p,再声明指针q,并打印q,将p赋值给q,打印出q和*q。
从代码中看出,指针之间的赋值不用加“*”,指针之间的赋值,只是将等号右侧指针所存储的的地址,复制给等号左侧的指针变量。
总结:定义指针 *p,其中p是指针变量本体,理解为一个容器,这个容器里面是用来存放内存地址的,在使用该指针时,加上“*”,即是按照这个容器里的地址,去内存中找其对应的位置。
二、多级指针
在以上指针的理解上,开始研究多级指针,比如int *******************p(有点夸张了啊喂!)
不多说,先看代码:
void main()
{
int ***ppp = new int**; //声明三级指针
int **pp = new int*; //声明二级指针
int *p = new int; //声明一级指针
*p = 300; //一级指针指向int型数据
*pp = p; //二级指针指向一级
*ppp = pp; //三级指针指向二级
cout << "***ppp=" << ***ppp << endl;
if (ppp != NULL){ delete(ppp); }
if (pp != NULL) { delete(pp); }
if (p != NULL) { delete(p); }
cin.get();
}
/*-------------输出结果如下:
***ppp=300
----------------------*/
在上面的代码中,定义了一级、二级、三级指针,
既然已经说过“*”是按照zhi指针存储的地址找到对应的位置,那么是否可以进行如下操作:
int ***ppp = new int**;
int *p = new int;
*p = 300;
**ppp = p;
答案自然是不行的,这样写,不会有语法错误,也是可以编译通过的,但运行时就会抛异常。
指针ppp是三级指针,它只能指向二级指针变量。这里直接进行“**”这样的操作,逻辑上是取出了一级指针,但中间一级是没有定义的,不明确,所以在运行时,就会弹出指针越界的错误。
总结:可以把指针的“*”理解为一种“扒衣服”操作,有一个“*”即扒一层衣服;指针等赋值时,等号两边必须同等级。
即如:
int ***ppp = new int**;//ppp是三级指针
int **pp = new int*;//pp是二级指针
int *p = new int;//p是一级指针
*p = 300;//p加“*”即脱一层衣服,就与int型变量同等级,就可以进行赋值操作
*pp = p;//pp脱一层衣服,就是一级指针,就可以把一级指针p赋值给它
*ppp = pp;//ppp本身是三级指针,脱一层衣服就是二级,与pp同级
cout << "***ppp=" << ***ppp << endl;//给三级指针脱三层衣服,输出的结果就是指向的数据300
三、“ * ”与“ [ ] ”
当指针与数组同时出现的时候,那简直无比酸爽
先看一段代码:
int main()
{
int a[3] = { 2,3,4 };
int b[3] = { 8,7,6 };
int **ab = new int*[2];
ab[0] = a;
ab[1] = b;
//int *ab[2] = { pa,pb };//与上面的三行等效
cout << "ab=" << ab << endl;
cout << "&ab[0]=" << &(ab[0]) << endl;
cout << "ab[0]=" << ab[0] << endl;
cout << "&ab[1]=" << &(ab[1]) << endl;
cout << "ab[1]=" << ab[1] << endl;
cout << "*ab[0]=" << *ab[0] << endl;
cout << "*ab[1]=" << *ab[1] << endl;
cout << "(*ab)[1]=" << (*ab)[1] << endl;
cout << "*(ab[1])=" << *(ab[1]) << endl;
cout << "ab[0][0]=" << ab[0][0] << endl;
cout << "ab[0][1]=" << ab[0][1] << endl;
cout << "ab[1][1]=" << ab[1][1] << endl;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
cout << "a[1]=" << a[1] << endl;
cout << "b[1]=" << b[1] << endl;
return 0;
}
你能知道输出的结果各是什么吗?
----------------------揭晓答案:
ab =000002249FB50220
&ab[0] =000002249FB50220
ab[0] =00000066E6AFF728
&ab[1] =000002249FB50228
ab[1] =00000066E6AFF758
*ab[0] =2
*ab[1] =8
(*ab)[1] =3
*(ab[1]) =8
ab[0][0] =2
ab[0][1] =3
ab[1][1] =7
a =00000066E6AFF728
b =00000066E6AFF758
a[1] =3
b[1] =7
1、ab是一个一维数组,数组元素均为指针,所以他是指向指针的二级指针。
输出ab,即ab这个一维数组的地址,也就是第一个元素的地址,也就是ab[0]的地址
但ab[0]是ab中的第一个元素,也是一个指针,直接输出得到的是这个一级指针存储的地址
所以:ab=&(ab[0]),
2、由于有ab[0] = a; 和ab[1] = b;,所以ab[0]和a是指向同一块内存的,所以这两个指针变量中存放的地址就相同
因此输出时ab[0] = a,ab[1] = b。
3、*ab[1]即先得到 ab[1]这个一级指针,这个指针变量中的地址与指针b中的地址一样,在“脱衣服”后,
*ab[1]等效于*(ab[1]),其指向的内容,同*b,也就是b[0],也就是数字8
4、(*ab)[1],时先得到*ab,也就是ab[0],再加上“[1]”,也就是ab[0][1];
其他的就不需要解释了,从上面的代码和运行结果,总结出一个很有意思的规律
就是:“ * ”和“ [ ] ”,有着相同的作用——给指针脱衣服,如果定义 int p[3]={1, 2, 3};则p[0]和*p是相同的
也就是说这里的的“p[x]”,就会使得p成为一个指针。
之前有总结,等号两别必须同级别,那么这里注意到一个问题,看如下两个指针的声明:
int *p = new int;
int *a = new int[3];
都是编译没有问题的,刚才发现规律时“*”和“ [ ] ”作用相同,那么可以理解int[x]是一级指针,可以和int *p画等号;
但第一行似乎不太对,
其实,第一行如果写成
int *p = new int[1]
这样以上的总结就都能说通了。
看一段复杂的代码:
int main()
{
const char *str1[2] = { "abcdef", "123456" };
const char *str2[2] = { "ABXDEF","zyxwvu" };
const char ***pcharp = new const char**[2];
pcharp[0] = str1;
pcharp[1] = str2;
cout << "str1=" << str1 << endl;
cout << "str2=" << str2 << endl;
cout << "str1[0]=" << str1[0] << endl;
cout << "str2[1]=" << str1[1] << endl;
cout << "*str1[0]=" << *str1[0] << endl;
cout << "str2[0][2]=" << str1[0][2] << endl;
cout << "pcharp=" << pcharp << endl;
cout << "pcharp[0]=" << pcharp[0] << endl;
cout << "*pcharp[0]=" << *pcharp[0] << endl;
cout << "pcharp[0][1]=" << pcharp[0][1] << endl;
cout << "*++pcharp=" << *++pcharp << endl;
cout << "*++*pcharp=" << *++*pcharp << endl;
cout << "***pcharp=" << ***pcharp << endl;
cout << "pcharp[0][0]=" << pcharp[0][0] << endl;
cout << "pcharp[0][0][0]=" << pcharp[0][0][0] << endl;
cin.get();
return 0;
}
以上代码,输出结果又是什么呢
按照前面总结的,我先给出一部分答案吧:
str1 =0000008E047BF838
str2 =0000008E047BF868
str1[0] =abcdef
str2[1] =123456
*str1[0] =a
str2[0][2] =c
pcharp =00000192AEAB08C0
pcharp[0] =0000008E047BF838
*pcharp[0] =abcdef
pcharp[0][1] =123456
ps:要注意,字符串本身就是char* 类型,char只能是单一字符
三、“*++p”和“p[1]”
指针是可以加减操作的,比如如下代码:
int main()
{
int *p = new int[3];
p[0] = 1; p[1] = 3; p[2] = 4;
cout << "*p=" << *p << endl;
cout << "*(p+1)=" << *(p+1) << endl;
cout << "p[1]=" << p[1] << endl;
cout << "p[+1]=" << p[+1] << endl;
cout << "*++p=" << *++p << endl;
cin.get();
return 0;
}
其输出的结果是:
*p =1
*(p+1) =3
p[1] =3
p[+1] =3
*++p =3
显然可以看出*(p+1) 、p[1] 、p[+1] 、*++p 是等价的。但真的是这样吗?将上面的代码中,数组声明定义改写为如下形式:
int p[3] = {1, 2, 3};
则此时程序是无法编译通过的,语法报错在“cout << "*++p=" << *++p << endl;”这一句,提示“++” “表达式必须是可修改的左值。
因为这种定义方式后,p虽然是指针,但不可以修改,它不能再指向其他内存,所以不能进行p=p+1的运算。
所以按照后一种定义方式,*(p+1) 、p[1] 、p[+1] 均能正常输出,且结果一致,但不能使用*++p。
如何理解可修改与不可修改呢,请注意下面两组代码:
const char* str = "123456";
cout << "*++str=" << *++str << endl;
str 虽然被const限制,但其指向谁依然是可修改的,只是指向的字符串不可修改,而下面的写法就会报错
const char str[7] = "123456";
cout << "*++str=" << *++str << endl;//str会报错,“必须是可修改的左值”
总结:“++”操作,只能对可修改的值进行操作,这里要正确理解“可修改的左值”
到这里还没结束,现在来考虑以下 在第二节中,没有输出完的的结果,请看如下:
int main()
{
const char *str1[2] = { "abcdef", "123456" };
const char *str2[2] = { "ABXDEF","zyxwvu" };
const char ***pcharp = new const char**[2];
pcharp[0] = str1;
pcharp[1] = str2;
cout << "*++pcharp=" << *++pcharp << endl;
cout << "*++*pcharp=" << *++*pcharp << endl;
cout << "***pcharp=" << ***pcharp << endl;
cout << "pcharp[0][0]=" << pcharp[0][0] << endl;
cout << "pcharp[0][0][0]=" << pcharp[0][0][0] << endl;
cin.get();
return 0;
}
(我又把源代码插入了一次,有所缩减,方便查看)
str1 =0000008E047BF838
str2 =0000008E047BF868
str1[0] =abcdef
str2[1] =123456
*str1[0] =a
str2[0][2] =c
pcharp =00000192AEAB08C0
pcharp[0] =0000008E047BF838
*pcharp[0] =abcdef
pcharp[0][1] =123456
--------------------------------------
*++pcharp =0000008E047BF868
*++*pcharp =zyxwvu
***pcharp =z
pcharp[0][0] =zyxwvu
pcharp[0][0][0] =z
*pcharp应该是指针变量str1的地址,*++pcharp就应该是指针变量str2的地址,由结果看,显然是正确的
++*pcharp,应该是指向“123456”的指针变量的地址,加上“*”,*++*pcharp就应该指向“123456”,但输出的结果显然不对
***pcharp本应该是‘a’这里也成了‘z’。
这里就涉及到“++”的另一个坑,就是“++”运算的本质会覆盖变量原有的值
即: ++pcharp,其意义就是pcharp=pcharp+1,pcharp已经不是原来的指针了,它已经后移了。
总结:指针的“++”运算虽然很多情况下和“[+1]”能取得相同结果,但一定要注意“++”运算的本质,是会改变指针变量的!
先到这里,下一篇整理一下指针作为函数参数以及函数指针的东西吧。