1指针的大小
对于任何指针来说,当我们使用操作符sizeof时,得到的是4字节(32位系统)或8字节(64位)。但会有一些特殊情况
#include<iostream>
void test(int *arr)
{
std::cout<<sizeof(arr)<<std::endl;
return;
}
int main()
{
int arr[10] = {0};
std::cout<<sizeof(arr)<<std::endl;
test(arr);
return 0;
}
在函数test中,我们传过去的实际上是数组的首元素地址,所以里面的sizeof获得的是首元素的地址,因此计算出来是4/8;而在main中,sizeof(arr),这里的arr传过去的是一个数组名,因此计算的是整个数组大小,因为是int类型,有10个元素,所以大小就是40字节。
2字符指针
字符指针需要注意的点有3个:
- char str[]=“abcde”,字符str是以"\0"为结尾的。
- char*p = str;得到的是首元素地址。
- char*p = “abcde”;这个会被编译器视为常量,为了不给自己犯浑,建议直接用
const char*p = “abcde”;常量指针来定义(后面细说)
3指针与数组
3.1指针的数组,指针数组
接下来我将会以很长的篇幅介绍这种类似的套娃。
对于一个指针数组,指针是定语,数组才是主语,为了保证主语是完整的,我们先写一个数组,比如 int arr[10]={0};然后这个数组要保存指针,所以加个指针,变成int* arr[10];
这玩意儿是干吗的呢?对于int arr[10]来说,他保存的是10个int的数据;而对于int*arr[10]则是保存了10个int*的数据,比如:
int main()
{
int a = 1;
int b = 2;
int c = 3;
int *arr[3] = {&a,&b,&c};
return 0;
}
实际上,这玩意儿可能在处理二维数组时更有用,对于一个二维数组arr[i][j]来说,实际上它是由i个含有j元素的数组组成的一个数组,而这i个数组实际上又是以他们首元素地址来定义的,因此可以这么来定义二维数组:
#include<iostream>
using namespace std;
int main()
{
//int 4bits;int * 8bits;
//sizeof(arr)提取整个数组的字节
int arr1[] = {1, 2, 3};
int arr2[] = {2, 3, 4};
int arr3[] = {3, 4, 5};
int *parr[] = {arr1, arr2, arr3};//数组名就是首元素地址,所以这里得用指针数组
int col = sizeof(arr1) / sizeof(int);//col
int row = sizeof(parr)/sizeof(int*);
for (int i = 0; i < col; i++)
{
for (int j = 0; j < row; j++)
cout << *(parr[i]+j) << ' ';
cout << endl;
}
system("pause");
return 0;
}
3.2数组的指针,数组指针
这次主语是指针所以我们先定义一个指针比如*parr,然后我们定义数组 int *parr[10];诶!这不是跟指针数组一样了吗!确实,因为搞错了,这实际上是一些运算符的优先级问题,*运算好像比[]要低,所以为了确保是指针应该是 (*parr),然后才是定义数组int (*parr)[10]。
int main()
{
int (*parr)[10];
return 0;
}
对于这玩意儿需要注意的点是,他是数组的指针不是数组元素的指针! 请参考以下情况。
int main()
{
int arr[5] = {0};
int (*parr)[5] = arr;//这句是会报错的
int(*parr)[5] = &arr;
return 0;
}
由于arr是首元素地址,他不能给int (*parr)[5];赋值;而我们&arr后,获得的就是整个数组的地址,这个才能赋值给数组指针。
下面是一些使用的例子。
//打印一维数组
#include <iostream>
using namespace std;
int main()
{
int arr[5] = {33, 32, 11, 52, 1};
int(*parr)[5] = &arr;
for (int i = 0; i < 5; i++)
{
//实际上(*parr)[i]等价于arr[i]
//由于*的优先级比[]低,所以为了确保解引用需要用()
cout << (*parr)[i] << endl;
}
system("pause");
return 0;
}
//打印二维数组
#include <iostream>
using namespace std;
int main()
{
int arr[5][3] = {{1, 2, 3},
{2, 3, 4},
{3, 4, 5},
{4, 5, 6},
{5, 6, 7}};
//获取二维数组首元素地址,即{1,2,3}这个数组名的地址(不是1的地址)
//你可以想象说arr[5][3]是一个指针数组
int(*parr)[3] = &arr[0];
//计算行列,也可以直接写5,3
int col = sizeof(arr[0]) / sizeof(int);
int row = sizeof(arr) / sizeof(arr[0]);
for (int r = 0; r < row; r++)
{
for (int c = 0; c < col; c++)
{
//(parr+r)找的是数组1,数组2...的位置,第一次解引用后就是数组1,数组2...的首元素地址,
//然后在各自的数组内部找他们元素的地址,最后第二次解引用得到值。
//实际上可以直接写成parr[i][j]
cout << *(*(parr + r) + c) << ' ';
}
cout << endl;
}
system("pause");
return 0;
}
3.3 数组,指针与函数参数
3.3.1 一维数组
我们定义一个一维数组 int arr[10]={0}; ,他可以这样传到函数中
void test(int arr[]);//ok
void test(int arr[10]);//ok,10写不写无所谓
void test(int *arr);//ok
void test2(int *arr[]);//ok
void test2(int *arr[20]);//ok
void test2(int **arr);//ok
int main()
{
int arr[10]={0};
int *arr2[20] = {0};//指针数组,内部放的都是地址
test(arr);
test2(arr2);
return 0;
}
注意!传入的其实只是首元素地址,arr中传入的只是&arr[0],arr2中,传入的也只是&arr2[0],你可以在这些函数的内部加入sizeof去验证。
3.3.2 二维数组
二维数组的情况可能会麻烦点
void test(int arr[][]);//error
void test(int arr[3][]);//error
void test(int arr[][5]);//ok
void test(int arr[3][5]);//ok
//main中arr传过来是一个首元素地址,那么用一个指针接受,
//但是该首元素地址指向的是一个数组名,而不是一维数组那种首元素,
//即实际上这里传了个数组地址过去
void test1(int *arr);//error
//二级指针存放的是一级指针,数组地址都放不进上面的一级指针,这里就造不出二级指针
void test1(int **arr)//error
//结合第一个分析,这是对的,我们要传的就是一个数组指针
void test1(int (*arr)[5]);
int main()
{
int arr[3][5] = {0};
test(arr);
test1(arr);
return 0;
}
4函数和指针
4.1指针的函数,指针函数
他首先是个函数,我们随便写个声明 void test(int a,int b);然后加上指针,变成
void* test(int a,int b)。这就是指针函数。与指针数组类似,指针数组说的是数组里面放的是指针,而这里说的是函数里面的返回值是一个指针。
4.2 函数的指针,函数指针
他首先得是一个指针,(*pf),然后才是一个函数,void(*pf)(int a,int b);比如:
int Add(int x,int y)
{
int z =0;
z = x+y;
return z;
}
int main()
{
int a =10;
int b =20;
cout<<Add(a,b);
if(Add==&Add)
{
cout<<"都是函数的地址";
}
int (*pa)(int,int)=&Add//这就是函数指针
cout<<(*pa)(a,b);
//实际上直接用pa(a,b)是可以的,因为Add==&Add,但是不易于阅读
return 0;
}
4.3 究极套娃,函数指针数组
他首先是个数组pfarr[5],然后是指针(*pfarr[5]),最后是函数 void(*pfarr[5])(int a,int b);在示例代码前,我简单分析一下,他本质就是个数组,但是里面的内容是一些指针,里面的指针存放的是函数的地址。然后因为数组内部是同类型元素,所以你可以预计到这些函数也是同类型的。而同类型的函数意思是你的输入类型,输出类型是一样的,比如下面这样:
#include <iostream>
using namespace std;
int ADD(int a, int b)
{
return 0;
}
int SUB(int a, int b)
{
return 1;
}
int MUL(int a, int b)
{
return 2;
}
int main()
{
int *arr[5];
int (*pfun[3])(int, int) = {ADD, SUB, MUL};
// ADD,SUB要写完整不能只有原型,只有输入和输出类型一致时才能这么操作
for (int i = 0; i < 3; i++)
{
cout << (*pfun[i])(1, 2);
}
system("pause");
return 0;
}
4.4究极娃中娃,函数指针数组指针
- 首先他是指针,(*ppfarr)
- 其次是数组,(*ppfarr)[5]//这里还不能放类型,因为要考虑函数的返回
- 然后又是指针, (*(*ppfarr)[5])
- 最后才是函数, int (*(*ppfarr)[5])(int a, int b);
所以首先他是一个指针,里面存放的是一个函数指针数组(4.3),回看4.3的代码,假设我们需要保存 int (*pfun[3])(int, int) = {ADD, SUB, MUL};这个数组的地址,我们就需要这样的指针
//请配合4.3食用
int main()
{
int *arr[5];
int (*pfun[3])(int, int) = {ADD, SUB, MUL};
int (*(*ppfun)[3])(int, int) = &pfun;
system("pause");
return 0;
}
实际上,当你需要用到这种指针时,你可以在 int (*pfun[3])(int, int)的基础上修改即可,首先你得保证他是个指针int ((*pfun)[3])(int, int),然后在加个指针就可以了int (*(*pfun)[3])(int, int)。
5 常量和指针
5.1 指针是一个常量,指针常量
首先他是一个常量const a; 然后是个指针 int* const a;他的意思是:
- a所对应的地址不能修改;即,int b =20; a = &b;是不允许的
- a所对应的值可以修改;如*a =10;
5.1.1 指针常量和引用
引用,都指向同一块内存空间,里面的值可以修改,这和我们描述的指针常量很像,C++的引用本质上是指针常量的使用
int main()
{
int a =10;
//这一句实际上是:int* const ref = &a;
//实际上要的是a所对应的内存空间,而不是把10赋给了&ref
//比如int&ref = 10;是编译不了的
int &ref = a;
//当编译器知道ref是一个a的引用后,这里其实转变成*ref=20;
ref = 20;
return 0;
}
5.2 常量是一个指针,常量指针
首先他是指针 int * a,然后是个常量 const int*a
- 指针的指向是可以更改的
- 但是指针所指向的值是不可以被更改的
5.3 套娃,常量指针常量
首先是个常量 const a,然后是指针 int* const a,最后是常量,const int* const a=&tmp;
结合了指针常量和常量指针,他直接锁死了地址和值,啥都不能改。
6指针引用
额。。。体会以下代码吧。。。暂时没有啥想说的
int main()
{
int a = 100;
int *p = &a;
int * &rp = p;
cout << a << endl;
cout << *p << endl;
cout << *rp << endl;
cout << p << endl;
cout << rp << endl;
getchar();
return 0;
}
7练习
1.请说出他们的各自类型
//这个代码是跑不动的
int add(int x,int y);
int sub(int x,int y);
int main()
{
int arr[10];
int* arr[10];
int (*pa)[10]=&arr;
int (*pf)(int,int) = &add;
int (*pfArr[])(int,int) = {add,sub};
int(*(*ppfArr[]))(int,int) = &pfArr;
return 0;
}
void* blabla(int a, int b);//这个是什么函数
数组,指针数组,数组指针,函数指针,函数指针数组,函数指针数组指针,指针函数
2.请尝试自己解读以下两段莫名其妙代码(画图会比较好理解)
(*(void(*)())0)();
void (*signal(int,void(*)(int)))(int);
(由于文字很长,我不用删除线了)第一段中,void(*)(),这就是一个函数指针,他外面套的那个括号加上0,(void(*)())0,称之为强类型转换,他把0转化成一个函数指针,然后外面的*是解引用,使用0所在地址的函数;
第二段中,先从最里面的void(*)(int)开始,他是一个函数指针,他作为参数传入(关于这方面的应用我会在回调函数里面详细介绍),signal那里的*是解引用,引用的函数的输入是(int),返回是void。
8一些其他指针
8.1空指针
- 空指针用NULL来表示(C++也有NULL,但是建议用nullptr),int *p = NULL;
- 对于那些我们不知道应该赋什么值来初始化的指针,我们应该用NULL给他赋值,但是NULL所指向的空间,我们是没办法访问的!
- 空指针在概念上不同于未初始化的指针。空指针可以确保不指向任何对象或函数;而未初始化的指针则可能指向任何地方(这是野指针)
- 在内存管理中,我们在使用malloc和free后,必须把管理的指针置空!否则将会非法访问,造成野指针问题。
8.2野指针
这种指针指向的是我们不知道的内存空间,他可能会乱值,也可能指向一片已经被系统回收的地址,所以你得不到改地址原来的信息,具体成因:
- 指针变量未初始化,应该要int*p=NULL;
int *p;
- 指针释放后不置空,应该加上一句p=NULL;
int* p = (int*)malloc(sizeof(int)*10);
if(p==NULL)
{
cout<<perror("Error:")<<endl;
return 1;
}
free(p);
- 指针越界访问,通常报segment blabla就是这个问题
class A {
public:
void Func(void){ cout << “Func of class A” << endl; }
};
class B {
public:
A *p;
void Test(void)
{
A a;
p = &a; // 注意a的生命期 ,只在这个函数Test中,而不是整个class B
}
void Test1() {
p->Func(); // p 是“野指针”
}
};