C++指针总结(不包括智能指针)
一、指针的类型
1.int* p;
类似于这种形式的指针,可指向一个对应类型的变量,比如
int *p;
int a;
p=&a;
可以让指针p指向整型变量a,int型的指针变量p里面放的是int型变量a的地址。
2.int* p[3];
表示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
3.int (*p)[3];
这表示指针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
4.const int *p、int const *p以及其他const用以修饰参数类型的情况;
const关键字会优先和左边的关键字或者运算符结合,所以这两种写法都可以可以使指针p能够指向一个常量,或者一个变量;这样的指针有一个名字叫常量指针,但我更喜欢叫它指向常量的指针
const int *p;
const int a = 10;
int b = 20;
对于一般的指针,比如int *p1来说,就不能让他指向a这个常量,如果指向,他就会报错,所以常量指针的指向范围比一般的指针要宽一些
5.int* const p以及其他const用来修饰*的情况;
根据const关键字先和左边的关键字或者运算符结合的特性,这里的const是用来修饰*的,所以这里的指针本身是一个常量,并且具有常量的性质。实际上,这里的p被叫做指针常量,意为类型为指针的常量,既然是常量,那值就不能改变,所以,在我们定义这个指针的时候,一定要给他赋值,否则编译器就会报错。
并且,在没有const运算符修饰int的情况下,他只能指向一个非常量,并且一旦指向一个变量,就不能再指向另一份变量,因为这个指针本身具有常量的性质,即值不能改变;
改变指针指向对象时会报错:
指向一个常量时也会报错:
6.int* func();
这个没什么好说的,有个名字叫指针函数,意为返回值是指针的函数,该例子返回一个int型指针
7.double (*func)(int a,int b);
他也有个名字叫函数指针,意为指向函数的指针,比如该例子中的这个函数指针,它可以指向一个接收两个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种情况下,才代表数组本身:
1.对数组名使用sizeof关键字的时候
#include<iostream>
using namespace std;
int main()
{
int a[] = { 1,2,3,4,5 };
cout << sizeof(a) << endl;
return 0;
}
这段代码输出20,代表数组的大小为20字节
2.对数组名取地址的时候
#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字节。
3.给整个数组初始化的时候
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'。
暂时完结