类似于这种形式的指针,可指向一个对应类型的变量,比如
int *p; int a; p=&a;
可以让指针p指向整型变量a,int型的指针变量p里面放的是int型变量a的地址。
表示p是一个数组,数组里面有3个元素,每一个元素都是一个可指向int型的指针变量
#include<iostream> using namespace std; int main() { int* p[3]; int a = 10; int b = 20; int c = 30; p[0] = &a; p[1] = &b; p[2] = &c; cout << *p[0] << endl; cout << *p[1] << endl; cout << *p[2] << endl; return 0; }
最后输出为:
10
20
30
这表示指针p可指向一个类型为int,大小为3的数组
#include<iostream> using namespace std; int main() { int(*p)[3]; int a[3] = { 1,2,3 }; p = &a; cout << (*p)[0] << endl; cout << (*p)[1] << endl; cout << (*p)[2] << endl; return 0; }
p=&a,代表用数组a的地址给指针p赋值,至于为什么用&a,而不是a,为什么用(*p)[0]这种形式来取得数组元素,我们在后面的“数组名退化成指针”里面讨论。
这里程序会输出:
1
2
3
const关键字会优先和左边的关键字或者运算符结合,所以这两种写法都可以可以使指针p能够指向一个常量,或者一个变量;这样的指针有一个名字叫常量指针,但我更喜欢叫它指向常量的指针
const int *p; const int a = 10; int b = 20;
对于一般的指针,比如int *p1来说,就不能让他指向a这个常量,如果指向,他就会报错,所以常量指针的指向范围比一般的指针要宽一些
根据const关键字先和左边的关键字或者运算符结合的特性,这里的const是用来修饰*的,所以这里的指针本身是一个常量,并且具有常量的性质。实际上,这里的p被叫做指针常量,意为类型为指针的常量,既然是常量,那值就不能改变,所以,在我们定义这个指针的时候,一定要给他赋值,否则编译器就会报错。
并且,在没有const运算符修饰int的情况下,他只能指向一个非常量,并且一旦指向一个变量,就不能再指向另一份变量,因为这个指针本身具有常量的性质,即值不能改变;
改变指针指向对象时会报错:
指向一个常量时也会报错:
这个没什么好说的,有个名字叫指针函数,意为返回值是指针的函数,该例子返回一个int型指针
他也有个名字叫函数指针,意为指向函数的指针,比如该例子中的这个函数指针,它可以指向一个接收两个int型参数,并且返回一个double类型值的函数,也就是说,它所指向的函数,除了函数名以外(在该例子中也就是(*func))其他都要和它相同,当然,它是一个指针,没有函数体,自然不要求函数体相同;
#include<iostream> using namespace std; double(*fun)(int a, int b); double sum(int x, int y) { return x + y;//int自动向上转换为double } int main() { fun = sum; int a = 10; int b = 20; double c = fun(a, b); cout << c << endl; return 0; }
这里输出30
指针是存储地址的工具,所以他的大小应该和寻址的范围有关,32位计算机的地址需要32位二进制数才能完全表示,所以在32位操作系统下,指针的大小为4字节(32位2进制的大小),同理,64位操作系统下,指针的大小为8字节。
数组名只有在3种情况下,才代表数组本身:
#include<iostream> using namespace std; int main() { int a[] = { 1,2,3,4,5 }; cout << sizeof(a) << endl; return 0; }
这段代码输出20,代表数组的大小为20字节
#include<iostream> using namespace std; struct Te { int a[5] = { 1,2,3,4,5 }; int b = 10; }te; int main() { int(*p)[5] = &te.a; int *p2 = te.a; ++p; ++p2; cout << (*p)[0] << endl; cout << *p2 << endl; return 0; }
这段代码输出:
10
2
因为指针p的类型是int*[5],所以让他自增1的话,他就会越过整个数组(4*5=20字节),指向数组最后一个元素的后一位,在这里p就指向了b的地址;
而我们使用括号将*和p括起来的原因是[]运算符的优先级在*之上,如果不使用括号,这里的p就会先和[]结合进行运算,然后再用运算的结果来和*进行运算(当然对于(*p)[0]加不加括号并不会对结果造成影响,毕竟p[0]代表在p的地址上加上0个该类型指针的大小,比如p[1],就代表在p的基础上加上20个字节,详见下文,指针的运算);
而p2是一个普通的int类型的指针,所以他自增后,越过的地址只有一个int型变量的大小,即4字节。
int a[]={1,2,3,4,5}; char c[]={'1','2','3','4','5','\0'};
在其他情况下,数组名均会退化成普通指针,不在具有数组名的性质。
两个指针相加没有什么意义,但是两个指针相减通常可以用来表示他们之间在内存上的距离
指针自增,代表地址加上指针所指类型的大小,对指针使用[]运算符,代表在当前指针所指的地址上加上指针所指类型的大小*[]里的数字,来看个例子:
#include<iostream> using namespace std; int main() { char c[][3] = { {'a','b','c'},{'d','e','f'},{'g','h','i'},{'j','k','l'} }; char(*pc1)[3] = &c[0]; cout << (*pc1)[0] << endl; cout << (*pc1)[1] << endl; cout << (*pc1)[2] << endl; ++pc1; cout << (*pc1)[0] << endl; cout << (*pc1)[1] << endl; cout << (*pc1)[2] << endl; cout << "-----------------------------------------------" << endl; cout << pc1[0][0] << endl; cout << pc1[1][0] << endl; cout << pc1[2][0] << endl; int *pi = (int*)&c[0][0]; cout << (char)*pi << endl; ++pi; cout << (char)*pi << endl; return 0; }
这一段代码的输出如下:
首先我们用pc1指针指向了c数组的第一个一维数组,然后我们先对pc1进行解引用操作,得到c[0],这时的c[0]不满足保持数组名的三种情况之一,退化成一个普通的char型指针,指向c数组的第一维数组的第一个元素,也就是'a',所以,我们对它分别进行[0],[1],[2]操作后,分别代表,目前地址加0字节、1字节、2字节(1个char型变量占一字节),所以前三条输出语句输出a,b,c;
之后我们让pc1自增1,所谓的自增1,其实也就是让pc1加上一个单位的自己所指类型的大小,pc1的类型是char*[3],大小为3个字节,于是pc1自增1,其实就是在原有的基础上加上3个字节;
所以我们可以看到,后面再执行同样的三条语句时,输出的是def,而不是bcd;
再然后,我们尝试对pc1进行[]操作,pc1的类型是char*[3],我们进行[0],[1],[2]操作时,等同于在pc1原有值的基础上加上0*3字节,1*3字节,2*3字节,然后,分别找到了c[1],c[2],c[3],他们代表c数组里面第2、3、 4个一维数组的首地址,也可以看做是指向第2、3、 4个一维数组的首地址的类型为char的指针,所以我们最后的结果是dgj,分别是第2、3、 4个一维数组的首元素;
我们之后再用一个int*类型的指针pi指向数组c的首地址,即c[0][0]的地址,然后再让pi自增,根据前面的规则,pi的地址要加上一个int型变量的大小,也就是4字节,所以本来指向c[0][0]的指针现在指向了c数组的第5个元素,即c[1][1],即字母'e'。
暂时完结