我们口头说的指针就是地址,
指针变量是变量,是用来储存地址的。
#include
int main()
{
int a = 0;
int* p = &a;
*p = 20;
printf("%d\n",a);
printf("%d\n",*p);
return 0;
}
对于上面的代码
这里的p是指针变量——是用来存放地址的变量;
可以这样理解从*可以看出p是个指针变量,p指向的内容是int类型的。
*p = 20
,此处的 *
是解引用操作符。
&
为取地址操作符
指针的类型是根据原来值的类型来确定用什么类型的指针。如:char类型,那就用char* 。
去掉指针变量名剩下的就是指针的类型
关于指针类型的声明有人可能会问用不同的类型声明可以吗?当然可以,但是会出现一些问题。
不同的指针类型决定了指针所移动的步长
如:char类型的,那么它+1就指向下一个字节(向后走一步)
int 类型的,那么它+1就指向到第4个字节处。(向后走4步)
指针的类型代表它所能访问几个字节大小的空间
看下面这个代码
int main()
{
int a = 0x11223344;
char* p = (char*)&a;
*p=0;
printf("%x", a);
return 0;
}
此处的&a
应该是int*
类型的,但强转成char*
类型的指针之后,对p进行解引用赋值成0只能改变一个字节,所以输出的结果是11223300。
看计算机输出的结果
指针的大小由电脑的平台所决定的而不是由指针类型决定的。
如果平台上是32位的,那就是4个字节的大小;64位的平台就是8个字节的大小。
看下面这个代码可以看出来:
#include
int main()
{
printf("%d\n", sizeof(int*));
printf("%d\n", sizeof(char*));
printf("%d\n", sizeof(long*));
printf("%d\n", sizeof(float*));
return 0;
}
在我的32位平台上上面的输出结果都是4。
野指针就是指针访问了位置不可知的地方,造成非法访问
比如这样:
int* i;
这样写是不对的,指针i指向的空间不知道
可以这样写:
int* i=NULL;
看下面这个代码:
int arr[5]={1,2,3,4,5};
int* p=arr;
int i=0;
for(i=0;i<=5;i++)
{
*(p++)=i;
}
该数组里面有5个元素,当i为5时,
p
访问的空间超过了数组的范围
造成越界访问,这时p就是野指针.
int* f(int* a,int* b)
{
int c = 0;
c = *a + *b;
return &c;
}
int main()
{
int a = 10, b = 20;
int* p=f(&a, &b);
printf("%d\n", *p);
return 0;
}
在调用函数的过程中开辟的空间,出了这个了函数,则开辟的空间返还给操作系统,导致
p
指针接受的地址被释放,次数p
为野指针。
虽然在vs 上面依然可以输出,但是确实是错误的。需要注意。
还有这种,和上面的差不多:
int main()
{
int* p = (int*)malloc(4 * sizeof(int));
int i = 0;
if (p != NULL)
{
for (i = 0; i < 4; i++)
{
*(p + i) = i;
}
}
free(p);
for (i = 0; i < 4; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
在p指向的空间被释放后,它所指向的空间被返还给操作系统
再次对它解引用进行访问就造成非法访问。
通常在释放空间后加上p=NULL
指针加一个整数表示该指针向后走几个该指针所指类型字节的个数。
比如:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8 };
int p=*(arr + 4);
return 0;
}
p就是5,因为数组的每个元素都是int
类型的,所以arr+4
表示arr
向后走4个int类型
的字节数,即访问数组第5个元素。
指针减整数也是同理,注意不要越界访问
指针相减得到的是两个指针之间的元素个数,不要认为是地址相减的结果,不要问为什么,这是这么规定的。
看下面的代码帮助你理解:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8 };
int* p1 = arr;
int* p2 = arr + 4;
printf("%d\n", p2 - p1);
return 0;
}
指针也是有大小的,就比如有高地址与低地址这么一说
c语言标准规定
允许指针与指针指向数组的最后一个元素后面的那个地址进行比较,不允许和指针指向数组第一个元素前面的那个地址进行比较。
再次说明不要问为什么,规定就是规定
例子:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8 };
int* p;
for (p=arr;p<=&arr[7];p++)
{
;
}
上面这个可以,下面这个不可以,原因上面说的非常清楚
for (p = &arr[7]; p >= arr; p--)
{
;
}
return 0;
}
指针的地址怎么储存呢?——用二级指针变量进行储存
int i=0;
int* p=&i;
int** p1=&p;
指针的指针就是二级指针,用于储存一级指针的地址。要改变
i
的值p1
要解引用2次才可以改变
能够指向字符数据的指针
形如这样的char* p
这里就讲比较难的地方吧!看下面这个代码
void pd(char* s1,char* s2)
{
if (s1 == s2)
printf("相等\n");
else
printf("不相等\n");
}
int main()
{
char arr1[] = "abcef";
char arr2[] = "abcef";
pd(arr1,arr2);
char* p1 = "abcef";
char* p2 = "abcef";
pd(p1,p2);
return 0;
}
arr1与arr2不相等,p1和p2相等。在数组储存数据时,即使储存的字符串相等也开辟不同的空间。但是p1,p2可不能储存
"abcef"
这样的常量字符串,它们只是储存了首字符的地址,所以p1和p2相等.并且p1,p2所指向内存的数据不能更改,因为初始化的常量字符串是不能更改的,可以这样写
const char* p1 = "abcef";
const char* p2 = "abcef";
数组指针本质上是指针,例如:
int (*p)[5]
*说明p是一个指针,指针指向一个数组,
数组中有5个元素,每个元素是int类型
这里的p
的类型为 int (*)[5] 类型。
这样使用,例子:
void f(int (*p)[5],int n)
{
int i, j;
for (i=0;i<5;i++)
{
for (j=0;j<5;j++)
{
*(*(p + i) + j) = 1;
}
}
}
int main()
{
int arr[5][5]={1,2,3,4,5,6,7};
f(arr,5);
return 0;
}
- 这里的二维数组名表示第一行数组的地址,数组的地址要用数组指针进行接收。
指针数组本质上是数组。例如:
int *p[5]
根据运算符的结合性可知,
p先与[]结合,说明p是一个数组,数组里面有5个元素,
每个元素是int*类型,也就是指针指向int类型
给个简单的例子:
看代码:简单
int main()
{
int arr1[] = { 1,2,3 };
int arr2[] = { 4,5,6 };
int arr3[] = { 7,8,9 };
int* p[] = { arr1,arr2,arr3 };
int i = 0, j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
printf("%d ", p[i][j]);
}
printf("\n");
}
return 0;
}
- 一维数组名
一维数组名表示数组首元素地址
- 二维数组名
二维数组名表示首行数组的地址
每一行的数组都有自己的名字,对与下面的代码arr2[0]
为第一行数组的名字
sizeof(数组名)
求的是整个数组的大小。
&数组名
取出的是整个数组的地址
例子:看代码
int main()
{
int arr1[] = { 1,2,3,4,5,6 };
int arr2[][3] = { 1,2,3,4,5,6 };
printf("%d\n",sizeof(arr1));求的整个数组的大小,单位字节24
printf("%d\n", sizeof(arr2));求的整个数组的大小24
printf("%d\n\n", sizeof(arr2[0]));求的是第一行数组的大小12
printf("%p\n", arr1);首元素的地址
printf("%p\n", arr1+1);第2个元素的大小
printf("%p\n", &arr1);这个元素的大小
printf("%p\n\n", &arr1+1);越过整个数组后的地址
printf("%p\n", arr2);首行的地址
printf("%p\n", &arr2 + 1);越过这个数组后的地址
printf("%p\n", arr2[0]);首行首元素的地址
printf("%p\n", &arr2[0] + 1);第二行数组的首元素的地址
return 0;
}
f(int arr[])
{}
f(int arr[3])
{}
f(int* arr)
{}
上面这三种都可以,但是最后一种比较好,它能反应出数组名字为数组首元素地址,
像第1与第2种里面[]里面有没有数值的没有问题。
f1(int* arr1[])
{}
f1(int* arr1[3])
{}
上面这两种大家应该可以理解
f1(int** arr1)
{}
arr是数组名,表示首元素地址,每个元素是指针,即指针的地址要用二级指针接受
int main()
{
int arr[3] = { 1,2,3 };
int* arr1[3];
f(arr);
f1(arr1);
return 0;
}
对于一维数组传参,上面的几种都正确。
f(int p[2][2] )
{}
f(int p[][2] )
{}
上面这两种都可以,也都可以理解,前一个数值可以省略,
但是第二个[]的数值不能去掉.
f(int(*p)[2] )
{}
二维数组名表示首行数组的地址,要用数组指针进行接收
int main()
{
int arr[2][2];
f(arr);
return 0;
}
f(int* p)
{}
int main()
{
int a = 0;
int* p = &a;
f(p);
return 0;
}
一级指针传参用一级指针进行接收
思考:形参为一级指针的时候,实参可以为什么样的类型
1️⃣实参可以为一维数组的数组名
2️⃣可以为一级指针
f(int** p1)
{}
int main()
{
int a = 0;
int* p = &a;
int** p1 = &p;
f(p1);
return 0;
}
二级指针传参用二级指针进行接收
思考:形参为一级指针的时候,实参可以为什么样的类型
1️⃣实参可以为指针数组
2️⃣可以为二级指针
3️⃣可以为一级指针的地址
引例:
int add(int x,int y)
{
return x + y;
}
int main()
{
&add;
return 0;
}
对于函数的地址应该怎么储存呢?那就需要用函数指针类型进行接收。
对于上面的代码,我们可以这样写
int (*p)(int,int)
对于上面这个可以这么理解:
* 告诉我们P是个指针,从后面的小括号可以看出是个函数,函数的参数有两个,都是int 类型的,返回类型是int
下面这样进行使用:
int(*p)(int,int)=&add;
(*p)(1, 2);
(**p)(1, 2);
p(1, 2);
add(1, 2);
我们发现上面这3种都正确,说明p变量前面的星号没有任何用处,p就相当与add。
看下面的这个代码可以更好的理解:我们发现打印的结果相同
printf("%p\n",add);
printf("%p\n", &add);
(*(void (*)())0)()
void (*signal(int , void(*)(int)))(int)
(*(void (*)())0)();
1.void (*)()这是一个函数指针类型,无参数,返回类型为空(void)
2.(void (*)())0这个是强制类型转换,把0强转为函数指针类型
3.*解引用变成函数,调用函数,函数无参数
void (*signal(int , void(*)(int)))(int);
1.void(*)(int)这是一个函数指针类型,返回类型为空,参数为int类型的
2.signal(int , void(*)(int))这是一个函数,函数名为signal,
参数有俩个,分别为int类型和函数指针类型。
3.函数要有返回类型,去掉signal(int , void(*)(int)),就是该函数的返回类型
该函数的类型为void (*)(int),函数指针类型。
对这个函数可以进行一下好看的改变
typedef void (*v_i)(int)
然后这样写:v_i signal(int,v_i);这样看着是不是非常舒服
函数指针数组
是个数组,里面的元素是是函数指针
给个例子:
void f1()
{}
void f2()
{}
int main()
{
void (*p[2])()={f1,f2};
这个就是函数指针数组,
这样理解p先和[]结合说明p是一个数组,数组有2个元素,
每个元素的个数是void (*)()函数指针类型
return 0;
}
指向函数指针数组的指针
它是一个指针,指向一个数组,数组的每个元素是函数指针类型
看下面的这个代码:
void f1()
{}
void f2()
{}
int main()
{
void (*p[2])()={f1,f2};
void(*(*p1)[2])()=&p;
p1就是一个函数指针数组的指针
return 0;
}
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
下面有个例子:
void f1()
{}
f(int a,void (*p)())
{
p();
}
int main()
{
int a;
f(a, f1);
把f1函数作为参数传给f函数,通过函数指针来调用f1函数。
return 0;
}
#include
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int mul(int x, int y)
{
return x * y;
}
int div(int x, int y)
{
return x / y;
}
void menu()
{
printf("**** 请选择 ****\n");
printf("**** 1.加 2.减 **********\n");
printf("**** 3.乘 4.除 **********\n");
printf("************* 0退出 ***************\n");
}
f(int (*p)(int, int))
{
int a, b;
printf("请输入两个整数:>");
scanf("%d%d", &a, &b);
printf("结果为%d\n", p(a, b));
}
int main()
{
int n;
int (*gather[5])(int, int) = { 0,add,sub,mul,div };
该函数指针数组里面存的是各个函数的地址,首个元素设置为0,
方便用下标访问
do
{
menu();
该函数打印菜单,供用户选择
scanf("%d", &n);
if (n == 0)
{
printf("退出程序\n");
}
else if (n > 0 && n < 5)
{
f(gather[n]);
这就是一个回调函数
}
else
{
printf("选择错误请从新选择\n");
}
} while (n);
return 0;
}