指针变量(指针)和变量
变量是对变量地址的符号化或者叫做操作数。
操作数分成立即数、寄存器和内存引用,操作数可以通过多种寻址方式找到对应值。
赋值操作 就是各操作数的传送 mov 操作,即寻址取值复制操作。
打印操作 是寻址取值并显示操作。
假设 a 为普通 int 型变量,ptr1 为一级指针,ptr2 为二级指针
也可以将变量等价于某个寄存器或内存空间,其中存储的是数值(立即数)或者内存地址。
所以与其说指针 ptr1 指向了a的地址,不如说指针变量 ptr1 存储的是变量 a 的地址更通俗易懂。
取地址符/取地址操作符 &
众所周知,操作符 & 是一个取 a 的地址的单操作数操作符,但更重要的是,它也创建了一个指向 a 的指针。所以&a 是一个地址的同时也是一个指向 a 的无名指针。
一级指针的声明与赋值
//第一种
int *ptr1 = &a;
//第二种
int *ptr1;
ptr1 = &a;
实际上,声明操作中的间接引用操作符 * 没有间接引用的含义,只是为了说明变量 ptr1 是一个指针。 * 为了表示 ptr1 是 int* 类型,所以单个变量声明 * 也可以紧挨着 int ,多个指针的声明就应该紧挨着变量名 ptr1 了。但是该语句的意思是将变量 a 的地址赋给 ptr1 ,也可以说给 &a 这个无名指针起个名 :D。
要注意的是,正确的声明语法应该确保等号两边类型一致。(特指指针和普通变量的正确匹配)
所以下面的语法明显错误,因为&a是一个指针(或者亦可称作地址)等号两边的变量类型一个是 int 一个是 int 类型的指针(int*)。
int ptr1 = &a;
二级指针的声明与赋值
int **ptr2 = &ptr1;
根据上面一级指针声明的思想我们可以很简单的得出二级指针的声明。
同理操作符 ** 也是为了声明 ptr2 是一个二级指针,没有间接引用的含义,但是该语句的意思是将指针 ptr1 的地址赋给 ptr2 。
两种赋值操作的区别与注意点
ptr1 = &a ,实质是将指针所指地址变成 a 的地址,或者说将变量 a 的地址赋给 ptr1,ptr1 的值是变量 a 的地址。
*ptr1 = a,实质是直接间接引用指针得到其所指地址的值更改成变量 a 的值,跳过了连接地址阶段,所以这种赋值方式,若没有事先指定 ptr1 所指地址,直接使用 *ptr1 会出错,此时修改 ptr1 不会影响 a 的值,若事先已指定地址则可以使用,并且修改 ptr1 也会影响 a 的真实值。
即下面的语句会通过编译但会出错不输出 ptr1 的值。
int *ptr1; *ptr1 = a; cout << ptr1;
所以声明赋值时刻要注意指针是否有指向的地址。
指针的间接引用
int a = 10; int *ptr1 = &a; cout << "*ptr1 = " << *ptr1; //*ptr1 = 10
第一个 * 只起到声明指针的作用,第二个 * 就是指针的间接引用,间接引用可以得到指针所指地址对应的值,实质上是间接寻址操作。
再次实验得到结论,下列各组等价操作表示三个内存空间。
int a = 10;
int *ptr1 = &a;
int **ptr2 = &ptr1;
cout << "a = " << a << endl //a = 10
<< "*ptr1 = " << *ptr1 << endl //*ptr1 = 10
<< "**ptr2 = " << **ptr2 << endl; //**ptr2 = 10
cout << "&a = " << &a << endl //&a = 0x70fdf4
<< "*&ptr1 = " << *&ptr1 << endl //*&ptr1 = 0x70fdf4
<< "ptr1 = " << ptr1 << endl //ptr1 = 0x70fdf4
<< "*ptr2 = " << *ptr2 << endl; //*ptr2 = 0x70fdf4
cout <<"&ptrl = " << &ptr1 << endl //&ptrl = 0x70fde8
<< "ptr2 = " << ptr2 << endl; //ptr2 = 0x70fde8
用三个方框表示三个空间画个图更好理解。
关于数组的指针问题
char ptr1[3] = {'h','h','h'};
cout << ptr1 << ' ' << *ptr1 << ' ' << &ptr1 << endl;
int a2[4] = {1, 2, 3, 4};
cout << a2 << ' ' << *a2 << ' ' << &a2 << endl;
int *ptr = &val;
cout << ptr << ' ' << *ptr <<' ' << &ptr;
//结果
//hhh h 0x70fe00
//0x70fdf0 1 0x70fdf0
//0x70fdec 10 0x70fde0
从下往上依次解释,最后一行根据上面的内容可以简单解释。
上面一行会发现 a2 与 &a2 竟然是相同的!!!!!
这根据上面的内容难以解释,猜测可能是由于C语言的特性,优化编译器简化数组索引的地址计算的影响。
理解为:
a2 指的是这个数组的 第一个元素 的首地址。 &a2 指的是这 整个数组 的首地址。
注意第一行,字符数组竟然不输出地址!!!输出的是字符串,字符!!!
可能由于特性,在调用字符数组的首指针(首地址)得到的是字符串内容,要想的到首地址,类比上面黑字的 &ptr1 就派上用场了,奇妙无比。
指针数组和数组指针
指针数组的声明与赋值
int *a[4];
指针数组是指一个指针类型的数组,其中的元素都是指针因为方括号的优先级大于 * 所以这是一个指针数组。
一些简单的用法根据上面的内容可以解释了。注意好像上述的数组的指针问题又在指针数组中不满足了??
int a2[4] = {1, 2, 3, 4};
cout << a2 << ' ' << &a2 << endl;
char *b[4] = {"bob", "amy", "black", "ming"};
int *c[4] = {a2, a2, a2, a2};
cout << *(c + 0) << ' ' << &*(c + 0) << ' ' << c[0] << endl;
cout << **(c + 0) << ' ' << **&*(c + 0) << ' ' << *c[0] << endl;
cout << *(b + 1) << ' ' << &*(b + 1) << ' ' << b[1] << endl;
cout << **(b + 1) << ' ' << **&*(b + 1) << ' ' << *b[1] << endl;
//0x70fe00 0x70fe00
//0x70fe00 0x70fde0 0x70fe00
//1 1 1
//amy 0x70fdc8 amy
//a a a
数组指针的声明与赋值
int (*ptr)[4] = &a2;
数组指针是一个指针,指针存储的是整个数组的首地址,它虽然与数组首元素的地址相同,但是数组指针指向数组的首地址是与整个数组有关联的,指向数组的首地址与指向数组首元素的地址要注意区分。
简单的使用:
int a3[4] = {1, 2, 3, 4};
int (*p)[4] = &a3;
cout << *a3 << ' ' << **p << endl;
cout << *(a3 + 1) << ' ' << *(*p + 1) << endl;,
cout << a3 << ' ' << *p << endl;
cout << a3 + 1 << ' ' << *(p + 1) << ' ' << *(*(p + 1) +1) << endl;
//1 1
//2 2
//0x70fdf0 0x70fdf0
//0x70fdf4 0x70fe00 0