C语言-指针学习

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变为了野指针,应当给它初始化!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值