C语言—指针概念简述

目录

1.什么是指针

2.指针基础内容

3.通过指针引用数组

4.二维数组的地址

5.指针数组

6.数组指针

7.函数指针

8.无类型指针

9.二级指针 

10.内存泄漏

11.为什么要用指针

12.指针自测题


1.什么是指针

假设我们要去访问一个变量,有两种方式:1.通过变量名访问。2.通过地址访问。

int a = 10;

printf("a = %d\n",a);    //1.通过变量名访问
printf("a =%d\n",*(&a)); //2.通过地址访问 
                         //取值运算符:把后面内存地址中的数据取出来

有了上面的例子,我们引入指针的概念。我们用最粗暴的方法来理解什么是指针,指针就是地址。什么是指针变量?存放地址的变量就是指针变量。指针变量可以类比跟我们之前学的整形变量,字符变量等,都是属于变量的一种。

2.指针基础内容

(1)如何定义指针

我们用指针标识符*来定义,*的作用是告诉系统这是一个指针变量,用来存放别人的地址。

注意:这个*只有在定义指针变量的时候才是指针标识符,其它时候是运算符的意思。

int a;
int *p; 
*p = &a;

printf("a =%d\n",*(&a));
printf("a =%d\n",*p);

(2)指针变量对变量类型的要求

为什么会在定义指针变量的时候要特别注意数据类型,拿下面的demo为例比来讲解。定义一个int型变量a,这个变量由于是一个int型的所以大小为4个字节,也就是32位。同样,如果用char类型来定义这个指针变量的话就是只用了一个字节,就是八位。这样就会出现一个问题,就是这个1个字节内存地址放不下一个4个字节大小的变量。编译器可能会报错,即使不报错打印出来的数据也是不完整的。

#include <stdio.h>

int main()
{
   int a = 0x1234;
   int *p = &a;
   char *q = &a;  //这样定义是错误的
   
   printf("a = %x\n",*p);
   printf("a = %x\n",*q);
   
   return 0;
}

(3)练习:指针实战之交换数据大小

下面这个demo的作用是用指针的方式交换两个数据的大小,通过地址访问到变量之后再进行交换。

#include <stdio.h>

void changedata(int *p,int *q)
{
	int tmp;
	tmp = *p;
	*p = *q;
	*q = tmp;
}

int main()
{
 	int a = 10;
 	int b = 20;
 	
 	printf("交换前:a=%d,b=%d\n",a,b);
 	changedata(&a,&b);
 	printf("交换后a=%d,b=%d\n",a,b);
 	
 	return 0;
}

运行结果:

3.通过指针引用数组

(1)定义一个指针变量指向数组

如何获取一个数组的首地址,通常有两种方式:

1.数组中首个元素的地址就是数组的首地址。

2.数组名就是数组的首地址。下面用代码表示一下这两种方式。

int main()
{
   int arrary[3] = {1,2,3};
   int *p;

   p = &arrary[0];  //1.数组中首个元素的地址就是数组的首地址。
   p = arrary;      //2.数组名就是数组的首地址。

   return 0;
}

(2)指针偏移遍历数组

我们访问了首元素的地址之后便可以访问到该数组中的每一个元素。具体如何访问看下面这个demo。这里要特别注意一点 *(p+i)这里的每次+1不是地址的加一,而是偏移了一个类型的大小。比如你是int型的指针就偏移4个字节,是char类型的就偏移一个字节。

用指针的方式访问数组在效率上是远远大于用数组名访问数组的。

#include <stdio.h>

int main()
{	
	int i;
	int arr[3] = {1,2,3};
	int *p;
	p = arr;
	
	for(i=0;i<3;i++){
		printf("%d ",*(p+i));  //这里的+1不是地址的加一,而是偏移了一个类型的大小
	}	 

 	return 0;
}

运行结果:

 (3)练习:指针与数组的结合

实验效果:输入五个数,并依次打印出来

#include <stdio.h>

void InputArr(int *parr,int size)
{
	int i;
	for(i=0;i<size;i++){
		printf("请输入\n");
		scanf("%d",parr);
		parr++;
	}
}

void PrintArr(int *parr,int size)
{
	int i;
	for(i=0;i<size;i++){
		printf("%d ",*parr);
		parr++;
	}
}

int main()
{	
	int arrary[5];
	int size;
	size = sizeof(arrary)/sizeof(arrary[0]);
	
	InputArr(arrary,size);
	PrintArr(arrary,size);
 	return 0;
}

运行结果:

4.二维数组的地址

为了研究二维数组的地址,我们可以把二维数组看作成“父子数组”(C语言并没有这个概念,只是方便理解起的名字而已)。有了“父子数组”的概念,我们就可以把二维数组拆成两个一维数组来理解。“父数组”和“子数组”都可以看作是两个一维数组,只不过“父数组”比较特殊,它里面的每一项又是一个数组。我们知道数组名就是数组的首地址这个概念,如果定义一个二维数组int a[3][4],那么a就是数组名,也就是“父数组”的名字,接下来我们就要想办法得出“子数组”的数组名,获得了数组名也就获得了地址,这个也是研究二维数组地址的重中之重。

现在定义一个二维数组int a[3][4] = {{1,3,5,7},{9,11,13,15},{17,19,21,23}}。

找个数组a是一个包含了三个元素的数组(也可以看作是包含了三个“子数组”),并且每一个元素又是一个数组。于是这里的a[0],a[1],a[2]就是代表了数组的名字,而中[ ]里面的“0,1,2”代表的也不是数组的大小。a[0],a[1],a[2]分别是这三个一维数组的名字,因此子数组的名字分别为a[0],a[1],a[2],也可以叫做是列地址,a就是行地址。

所以现在可以思考这样一个问题,a+1和a[0]+1分别代表什么含义?

a+1偏移的是整个数组,也就是说从a[0]偏移到a[1],它偏移了一行

a[0]+1只是从一个数组的某个元素偏移到了下一个元素,它偏移了一列

5.指针数组

指针数组:多个指针,叫做指针数组,数组中的每一项都是一个指针变量。

int a = 2;
int b = 3;
int c = 4;

int *arrary[3];  //指针数组

arrary[1] = &a;  //数组中的每一项存放的都是指针变量
arrary[2] = &b;
arrary[3] = &c;  //三个普通没有任何关系的整形变量的地址存入指针数组

6.数组指针

数组指针相比于指针数组最大的区别就是它是一个指针,指向的是数组。而数组指针是多个指针的集合。 

数组指针,强调的是类型,数组的个数,偏移值是偏移了整个数组的大小。

int arrary[3] = {3,4,5};

int (*p)[3];  //数组指针,强调的是类型,数组的个数,偏移值是偏移了整个数组的大小。
p = arrary;

7.函数指针

我们知道普通变量有两种访问方式,一种是通过变量名访问,还有一种是通过指针访问。同样函数也用通过函数名和指针来调用。

#include <stdio.h>

void printwelcome()
{
	printf("welcome\n");
}

int main()
{	
	void (*p)();       //定义一个函数指针
	p = printwelcome;  //给指针赋值
	
	p();               //通过函数指针调用函数
	(*p)();
	
	return 0;
}

如何定义一个函数指针?

void (*p)();

1.如何表示指针:*     

2.如何指知道是函数:()   

3.函数指针是专用的,格式要求很强(参数类型,个数,返回值)。

如何给指针赋值?

p = printwelcome;

函数名就是地址名,就像数组一样,数组名就是地址。

如何通过函数指针调用函数?

1.直接通过指针名字加括号:p();

2.取内容。(*指针名字)+():(*p)();

函数指针练习:有两个整数a和b,用户输入1,2或3。如果输入1则给出a,b中的最大值,如果输入2,则给出a,b中的最小值,输入3则输出a+b的和。

#include <stdio.h>

int getmax(int data1,int data2)
{
	return data1>data2 ? data1:data2;
}

int getmin(int data1,int data2)
{
	return data1<data2 ? data1:data2;
}

int getsum(int data1,int data2)
{
	return data1+data2;
}

int datahandler(int data1,int data2,int (*pfunc)(int data1,int data2))
{
	int ret;
	ret = (*pfunc)(data1,data2);
	return ret;
}

int main()
{	
	int a = 10;
	int b = 20;
	int cmd;
	int ret;
	int (*pfunc)(int data1,int data2);
	
	printf("请输入1最大值,输入2取最小值,输入3求和\n");
	scanf("%d",&cmd);
	
	switch(cmd){
		case 1:
			pfunc = getmax;
		break;	
		case 2:
			pfunc = getmin;
		break;		
		case 3:
			pfunc = getsum;
		break;	       
		default:
			printf("输入错误\n");
		break;           
	}
	ret = datahandler(a,b,pfunc); 
	printf("输出结果为%d\n",ret);
			
	return 0;
}

8.无类型指针

函数原型:void *malloc(size_t size);

1.void作为函数返回值的时候是无类型返回值(无类型不代表没有返回值),void*在这里同样是返回的是无类型的指针。

2.size_t 类型表示C中任何对象所能达到的最大长度,它是无符号整数。(其值是大于等于0的)

3.malloc:中文叫动态内存分配,用于申请一块连续的指定大小的内存块区域以void*类型返回分配的内存区域地址。

用下面的demo加深一下理解(用malloc开辟空间遍历数组)。

#include <stdio.h>
#include <stdlib.h>

int main()
{	
	int i,n;
	int *arrary = (int *)malloc(n*sizeof(int));  //将void型指针强转成int型指针
	
	printf("请输入开辟个数\n");
	scanf("%d",&n);
	
	for(i=0;i<n;i++){
		printf("请输入第%d个数\n",i+1);
		scanf("%d",&arrary[i]);
	}
	
	for(i=0;i<n;i++){
		printf("第%d个数是:%d\n",i+1,*arrary++);
	}
	
	return 0;
}

9.二级指针

对二级指针的理解是和一级指针是一样的,一级指针存放的是普通变量的,而二级指针存放的是指针变量的地址。

#include <stdio.h>

int main()
{	
	int data = 10;
	int *p = &data;  //定义一级指针
	int **pp = &p;   //定义二级指针
	
	printf("通过一级指针访问:data=%d\n",*p);
	printf("通过二级指针访问:data=%d\n",**pp);
	printf("p的地址是%p\n",p);
	printf("pp的地址是%p\n",pp);
	
	return 0;
}

用图像理解上面的代码就如下图,多级指针是相对的概念,都是一级指向另一级层层递进的。

10.内存泄漏

什么是内存泄漏?

内存泄漏最主要的体现是,程序刚跑起来的时候很好,跑了几小时,跑了几天或者几周,程序崩溃了。

这种情况一般来说就是不断地开辟内存空间,最后超过了你计算机的内存,然后就崩溃了。用malloc开辟的空间,程序不会主动释放。Linux系统中,程序结束,系统会回收这个空间。

如何避免内存泄漏?

1.注意循环中有没有一直申请空间。

2.有没有及时合理释放。(用free释放)

11.为什么要用指针

(1)可以将变量强制保存在我想要的地址中。

平时我们定义一个变量int a = 10;这个a的地址是系统随机分配的,那么如果我们想在指定的地址去定义这个变量该如何操作,这个时候就可以用到指针。

例:在内存空间0077ff00中定义int a = 10。

int *p = (int *)0077ff00;
*p = 10;

通过(int *)的操作强制给*p这个指针变量附上一个int型的地址,然后再通过*p=10给变量附上值,这个操作就做完了。

(2)通过被调函数修改调用函数的对象。

还记得上面我们写过一个交换数值大小的demo吗?

如果我把红框中的函数改成以下这个样子,想一想还能否完成数值交换的操作。很显然是不行的,因为修改后的函数在定义参数时开辟的内存空间与主函数在调用时所应定义的变量开辟的内存空间是完全两个不一样的空间。说白了,如果把整个程序比作是一个大楼的话,被调函数为变量开辟的空间在301房间,而在主函数定义的变量开辟的空间在403房间,301房间的人根本控制不了在403房间的人,因此传参这种方式无效。

什么是有效的传参?通过指针,对变量地址进行操作,让指定地址中的变量值进行交换。

void changedata(int p,int q)
{
	int tmp;
	tmp = p;
	p = q;
	q = tmp;
}

12.指针自测题

定义p为指向整形数据的指针变量int *p
定义一个指针数组p,它由四个指向整型数据的指针元素构成 int *p[4]
p为指向包含4个元素的一维数组的指针变量int (*p)[4]
f为返回整形函数值的函数int f()

p为一个返回指针的函数,该指针指向整形数据

int* p()
p为指向函数的指针,该函数返回一个整形值int (*p)()
p是一个指针变量,它指向一个整形数据的指针变量int **p
p是一个指针变量,其类型为void,不指向具体的对象void *p

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LJX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值