文章目录
1.指针概念
- 1.1概念
简单的来说,指针就是地址。我们口头上说的指针其实指的是指针变量。指针变量就是一个存放地址的变量。 - 1.2指针的大小
指针在32位机器下是4个字节,在64位机器下是8个字节。注:(指针的大小与类型无关)
- 1.3指针的类型
指针类型的作用是指针解引用访问,使用的访问权限是多少
*当一个int 的指针+1,跳过4个字节;*当一个char 的指针+1,跳过1个字节。
同理,对于(指针±整数)类的题也是如上的原理。
#include <stdio.h>
#include <stdlib.h>
int main()
{ int a[2] = {10,1};
int* p1;//定义指针变量
p1=a;//取a的地址赋值给指针变量p
char c[2] = {'a','c'};
char *p2;
p2 =c;
printf("a的地址是%p a的值为%d\n",p1,*p1);//*p表示取指针变量存放的值及地址中存放的值。
printf("c的地址是%p a的值为%c\n",p2,*p2);//*p表示取指针变量存放的值及地址中存放的值。
p1++;
p2++;
printf("a+1的地址是%p a+1的值为%d\n",p1,*p1);//*p表示取指针变量存放的值及地址中存放的值。
printf("c+1的地址是%p c+1的值为%c\n",p2,*p2);//*p表示取指针变量存放的值及地址中存放的值。
system("pause");
return 0;
}
当一个int* 的指针+1,跳过4个字节;当一个char 的指针+1,跳过1个字节。
各种类型的大小:int类型四个字节,char类型一个字节,long,double,long long类型均为八个字节,long double类型为十六个字节
2. 野指针
2.1概念:野指针顾名思义,指针指向的位置不可知。
2.2野指针产生的原因:
- 指针未被初始化
int * p;//(p就是一个野指针,及没有初始化的指针变量就是野指针)
-
指针越界访问
-
指针指向的空间释放
int* test( )
{
int a = 5;
return &a;
}
int main()
{
int* p = test();
*p = 10;
return 0;
}
变量a的地址只在test()函数内有效,当把a的地址传给指针p时,因为出了test函数,变量a的空间地址释放,导致p变成了野指针。
2.3 野指针的规避
- 小心越界
- 及时把指针赋成空指针
- 避免返回局部变量的地址
- 使用指针前检查有效性
3.通过指针引用数组及指针的运算
1、数组元素的指针就是数组元素的地址(数组元素都在内存中占用存储单元,它们都有相应的地址)
2、数组名代表数组的首元素,引用其的方法为:
(1)下标法,如a[i]形式;
(2)指针法,如*(a+i)或*(p+i).
3、指针运算
(1)指针以指向一个数组元素时,可以进行以下运算
p+1,p-1,++p,p++,p- -,- -p
(p+1是指向同一数组中的下一个元素,加一代表的是加上一个数组元素所占用的字节数,同理可知p-1)
4、*(p+i) 或 * (a+i)
是p+i 或a+i所指向的数组元素,即a[i],三者等价([ ]是变址运算符,将a[i]按a+i计算地址,然后找出此地址单元的值
#include<stdio.h>
int main()
{
int a[5]={1,2,3,4,5};
int *p;
p=a;
printf("%d\n",*(p+1));
printf("%d\n",*(a+1));
printf("%d",a[1]);
return 0;
}
5、如果两个指针指向同一个数组,它们就可以相减,其结果为两个指针之间的元素数目。
p2 - p1,指的是元素的相对距离 如下例:
计算的结果为4,即a[0]与a[4]之间的距离为4
#include<stdio.h>
int main()
{
int a[5]={1,2,3,4,5};
int *p1,*p2;
p1 = &a[0];
p2 = &a[4];
printf("p2-p1= %d\n",p2-p1);
return 0;
}
6、指针常量不可以 ++
Eg:
int arr[3] = {1,2,3};
int *p = arr;
for(int i = 0;i<3;i++){
printf("%d\n",*p++ );
}
for(int i = 0;i<3;i++){
printf("%d\n",*arr++);//编译会报错,这里arr 是指针常量
}
4.二维数组的地址认知
对于一个二维数组
int a[3][4] = {{1,3,5,7},{9,10,11,13},{17,19,21,23}};
- a 是二维数组名,a数组又包含了3行,即3个行元素:a [0],a[1],a[2]。而每一个行元素又是一个一维数组,它包含了4个元素:a[0][0],a[0][1],a[0][2],a[0][3]。
- a[0],a[1],a[2]既然是一维数组名,C语言规定数组名代表数组首元素地址,因此,a[0]代表一维数组a[0]中第0列元素的地址,即&a[0][0],也就是说a[1]的值等于 &a[1][0] a[2]的值等于 &a[2][0]
- a是父数组的地址 a[0] ,*a均表示子数组的地址
#include <stdio.h>
int main(int argc, char const *argv[])
{
int arr[3][4] = {{1,3,5,7},{9,10,11,13},{17,19,21,23}};
printf("arr 的地址为%p ,arr+1的地址为%p\n",arr,arr+1); //arr+1 与arr 的地址相比较 偏移了16个字节
printf("arr[0]是子数组的地址为 %p, arr[0]+1 的地址为%p\n",arr[0],arr[0]+1); //arr[0]+1 与arr[0]相比 偏移了4个字节
printf("*arr 相当于 arr[0] 地址为 %p, 偏移1后的地址为%p\n", *(arr+0),*(arr+0)+1);//*(a+0) 等于a[0] *(a+1) 等于a[1]
return 0;
}
遍历二维数组的三种形式:
#include <stdio.h>
int main(int argc, char const *argv[])
{
int arr[3][4] = {{1,3,5,7},{9,10,11,13},{17,19,21,23}};
int i;
int j;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("address:0x%p,data:%d\n",&(arr[i][j]),arr[i][j]);
printf("address:0x%p,data:%d\n",arr[i]+j,*(arr[i]+j));
printf("address:0x%p,data:%d\n",*(arr+i)+j,*(*(arr+i)+j));
printf("===================================================\n");
}
}
return 0;
}
二维数组a的相关指针
5.数组指针
- 定义:
int (*p)[3];
数组指针强调的的类型,数组的个数,偏移值是整个数组的大小! - 说明:
(1)数组指针,在其偏移时,偏移对应大小的数组
(2)数组指针才是真正等同于二维数组名
int main()
{
int ihang,ilie,data;
int arr[3][4] = {{1,3,2,5},{4,9,6,2},{0,6,5,1}};
int (*p) [4];//定义数组指针,指向数组的指针,这里指针偏移为 4*4 = 16个字节
p = arr;
printf("p = %p\n", p);
printf("++p = %p\n", ++p);
return 0;
}
由此可见数组指针+1之后,地址偏移了16字节
- 应用:遍历整个二维数组
int getData(int(*p)[4],int hang,int lie)//数组指针 每次偏移的列需要定义出来
{
return *(*(p+hang) +lie);
}
int main()
{
int ihang,ilie,data;
int arr[3][4] = {{1,3,2,5},{4,9,6,2},{0,6,5,1}};
//输入需要查找的行列:
printf("请输入要查找的行号和列号:");
scanf("%d%d",&ihang,&ilie);
data = getData(arr,ihang,ilie);
printf("%d行%d列的元素为%d",ihang,ilie,data);
system("pause");
return 0;
}
6.指针数组
- 定义:
一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都存放一个地址,相当于一个指针变量。下面定义一个指针数组:
int* p[3];
//定义指针数组。多用指针数组存放函数指针。
由于[]比*
优先级高,因此p先与[3]结合,形成p[3]形式,这些显然是数组的形式,表示p数组又3个元素,然后再与p前面的*
结合,表示次数组是指针类型的,每个数组元素都可指向一个整型变量
int main()
{
int a = 0;
int b = 1;
int* p1 = &a;
int* p2 = &b;
int* arr[] = { p1,p2 };//指针数组
int* arr[] = { &a,&b };//指针数组
return 0;
}
- 数组指针与指针数组的区别
int arr[5] //名为arr的数组,数组里有5个元素,每个元素是int
int* p1[5] //指针数组,数组里有5个元素,每个元素是int*
int (*p2)[5] //数组指针,一个指向数组(里面有五个元素,每个元素是int)的指针
int (*p3[5])[5] //p3[5]是一个有5个元素的数组,数组里每个元素是int(*)[5]
7.函数指针
- 定义 :
指向函数的指针int (*pf) (int,int),
pf函数返回的类型是int
如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址**(又称入口地址)称为函数的指针。**
存放函数地址的变量。 函数名即为地址! - 注意:
对于函数来说如:Add和&Add,意义和值都一样。这一定要区别于数组。 - 函数指针示例(无返回值和参数)
void print()
{
printf("1234\n");
}
int main()
{
void (*p)();
p=print;
print();
//通过函数指针访问函数方法1,直接用函数指针名字加()
p();
//方法2,用*去函数指针所指的函数内容
(*p)();
system("pause");
return 0;
}
- 带参数与返回值的函数指针:
#include <stdio.h>
#include <stdlib.h>
int test(int num)
{
return ++num;
}
int main()
{
int (*ptest) (int nun); //返回值类型为int,且带有int类型的参数
ptest = test;
printf("%d\n", (*ptest)(10));
return 0;
}
- 函数指针练习
(1)输入不同操作命令,调用不同函数:
#include <stdio.h>
#include <stdlib.h>
int getMax(int data1,int data2)
{
return data1 > data2 ? data1:data2;
}
int getMin(int data1,int data2)
{
return data1 < data2 ? data1:data2;
}
int getAdd(int data1,int data2)
{
return data1+data2;
}
int funHandler(int data1,int data2,int (*pfun)(int ,int))
{
int ret;
ret = (*pfun)(data1,data2);
return ret;
}
int main()
{
int cmd;
int data1,data2;
int ret;
int (*pfun)(int ,int);
printf("请输入要运算的数组:\n");
scanf("%d %d",&data1,&data2);
printf("请输入要执行的操作(1:求最大值,2:求最小值,3:求和):\n");
scanf("%d",&cmd);
switch(cmd){
case 1: pfun = getMax;
break;
case 2: pfun = getMin;
break;
case 3: pfun = getAdd;
break;
default:
printf("输入错误!\n");
exit(-1);
}
ret= funHandler(data1,data2,pfun);
printf("ret = %d\n",ret);
return 0;
}
(2)结合指针数组对上例改写,将输入的数据进行求最大值,最小值,求和运算
#include <stdio.h>
#include <stdlib.h>
int getMax(int data1,int data2)
{
return data1 > data2 ? data1:data2;
}
int getMin(int data1,int data2)
{
return data1 < data2 ? data1:data2;
}
int getAdd(int data1,int data2)
{
return data1+data2;
}
int main()
{
int cmd;
int data1,data2;
int ret;
int (*pfun[3])(int ,int) = {getMax,getMin,getAdd};//定义函数指针数组,并进行初始化
printf("请输入要运算的数字:\n");
scanf("%d %d",&data1,&data2);
printf("输入数字的最大值,最小值,和分别为:\n");
for (int i = 0; i < 3; i++)
{
ret = (*pfun[i])(data1,data2);
printf("%d\n",ret);
}
return 0;
}
8.指针函数
- 定义:
一个函数可以返回一个整型值、字符值、实型值等,也可以返回指针型的数据,即地址
例如:int * a(int x,int y);
在*a
两侧没有括号,在a的两侧分别为运算符和()运算符。而()优先级高于,因此a先与()结合,显然是函数形式。且函数前面有一个*,表示此函数时指针型函数。 - 指针函数练习:
(1)例有a个学生,每个学生有 b门课程的成绩。要求在用户输人学生序号以后,能输出该学生的全部成绩。用指针函数来实现。
#include <stdio.h>
int *getScore(int pos,int (*pstu)[4])
{
int *p;
/*将二维数组指针(偏移16字节) 转换为 int *类型的指针(偏移4字节)
从而找到对应行地址,并将行地址转换为一维数组地址*/
p = (int *)(pstu + pos);
return p;
}
int main(int argc, char const *argv[])
{
int scores[3][4] = { {90,91,92,99},
{89,88,89,100},
{87,85,78,100}
};
int pos;
int *ppos;
printf("请输入要查看学生的序号(0,1,2):\n");
scanf("%d",&pos);
ppos = getScore(pos,scores);
for (int i = 0; i < 4; i++)
{
printf("%d ",*ppos++);
}
return 0;
}
(2)对上例中的学生,找出其中有不及格的课程的学生及其学生号。
#include <stdio.h>
int *getScore(int pos,int (*pstu)[4])
{
int *p;
/*//将二维数组指针(偏移16字节) 转换为 int *类型的指针(偏移4字节)
从而找到对应行地址,并将行地址转换为一维数组地址*/
p = (int *)(pstu + pos);
return p;
}
int *searchFail(int (*pstu)[4])
{
int *p;
for (int i = 0; i < 4; i++)
{
//printf("%d ",*(*(pstu+0)+i));
if(*(*pstu+i) < 60){
p = pstu;
}
}
return p;
}
int main(int argc, char const *argv[])
{
int scores[3][4] = { {90,91,92,99},
{89,59,89,100},
{87,85,78,100}
};
int pos;
int *ppos;
int *failPos;
printf("请输入要查看学生的序号(0,1,2):\n");
scanf("%d",&pos);
ppos = getScore(pos,scores);
for (int i = 0; i < 4; i++)
{
printf("%d ",*ppos++);
}
putchar('\n');
for (int i = 0; i < 3; i++)
{
failPos = searchFail(scores+i);
if(failPos == *(scores+i)){
printf("第%d个学生不及格\n",i+1);
for (int j = 0; j < 4; j++)
{
printf("%d ",scores[i][j]);
}
}
}
return 0;
}
9.二级(多级)指针
- 定义:
多级指针就是保存的是指针变量的地址 - 用途:
通过函数调用来修改调用函数指针指向,与通过函数调用修改某变量的值一样 - 示例:用过二级指针修改一级指针的内容。
#include <stdio.h>
void getScore(int pos,int (*pstu)[4],int **ppos)//这里int **ppos为二级指针,接收主函数传来的指针的地址
{
*ppos = (int *)(pstu + pos); //这里修改主函数传入的指针的地址,并访问其保存的指针(通过*ppos)的方法,达到改变其指向的新地址
}
int main(int argc, char const *argv[])
{
int scores[3][4] = { {90,91,92,99},
{89,88,89,100},
{87,85,78,100}
};
int pos;
int *ppos;
printf("请输入要查看学生的序号(0,1,2):\n");
scanf("%d",&pos);
getScore(pos,scores,&ppos);//这里&ppos 传入的是指针ppos的地址 即二级指针
for (int i = 0; i < 4; i++)
{
printf("%d ",*ppos++);
}
return 0;
}
10.各种指针定义:
1.一个指向指针的指针,它指向的指针指向一个整型数 : int **a;
2.一个有10个整型数的数组 :int a[10];
3.一个有10个指针的数组,每个指针指向一个整型数: int * a[10];
4.一个指向有10个整型数的数组指针: int (*a)[10];
5.一个指向指针的指针,被指向的指针指向一个有10个整型数的数组:int (**a)[10];
6.一个指向数组的指针,该数组有10个整型指针: int *(*a)[10];
7.一个指向函数的指针,该函数有一个整型参数并返回一个整型数: int (*a)(int);
8.一个有10个指针的数组,每个指针指向一个函数,该函数有一个整型参数并返回一个整型数: int (*a[10])(int);
9.一个函数的指针,指向的函数类型是有两个整型参数并返回一个函数指针的函数,返回的函数指针指向有一个整型参数且返回整型数的函数:
指向有两个整型参数的函数指针: (*a)(int,int);
返回的函数指针 是有一个整型参数且返回整型的函数:int *() (int)
将定义的函数指针放到返回值类型(返回值类型为函数指针):int *((*a)(int,int))(int);
11.无类型指针
- 定义:
void并不是指没有返回值 而是指的无类型。
malloc(size-t);
函数:返回值为void*
(无类型指针) 参数中的size-t指的是开辟了这么多字节的空间。
定义:
int *parry =(int*) malloc(n*sizeof(int));
malloc()调用后的返回值为无指针类型 把它强制转换成(int*)类型相当于定义了一个整数型的指针变量。
int *parry =(int*) malloc(12);
相当于int parry[3]; - 用法:可动态申请数组
int n;
printf("输入数组的个数%d",n);
scanf("%d",&n);
int a[n];
//这种方式不合法可以用malloc函数定义:
int*parray=(int*)malloc(n*sizeof(int));
12.内存泄漏
- 定义:
内存泄漏:程序刚运行很好,跑一段时间后出现错误。
int *p =malloc(1024)
malloc申请空间,系统不会主动释放,若是Linux系统的话,程序结束后会回收空间。malloc可能会失败,要对返回值做判断:
int *p =malloc(1024);
if(p==NULL){
printf(“申请内存失败”);
exit(-1);//结束进程,-1代表出现异常。
}
- 如何避免
1.循环中有没有一直申请空间,避免这种情况发生
2.有没有合理释放函数 用free()
函数进行释放空间free(p);p=NULL;
(释放后注意p变为了野指针,应当给它初始化!)