C语言指针

指针
一、什么是指针

指针就是存放地址的变量

内存地址:

​ 字节:内存的容量单位,Byte,一个字节有8位,1Byte == 8bits

​ 地址:为了能让程序能够有效的访问这些内存,计算机为内存中的每一个字节进行编号,称内存地址,简称地址

基地址:

​ 单字节数据:对于单字节数据而言,基地址就是其所在内存的字节编号

​ 多字节数据:对于多字节数据而言,基地址就是其所有字节在内存中地址编号最小的那个,称为基地址

12

取址符:&

​ 每个变量都是一块内存,都是可以通过取址符获取其地址(基地址)

int a;
char c;
double f;

在这里插入图片描述

attention:

​ 虽然不同的变量的尺寸是不同的,但是他们的地址表示却是一样的

​ 不同的地址虽然形式上看起来是一样的,但由于他们代表的内存尺寸和类型都不同,因此它们在逻辑上是严格区分的

为什么要用指针?

​ 指针能够指向某块内存地址,在使用过程中,它是能够实际反映数据的,特别在函数调用过程中

什么时候用指针?

​ 如果我们将一个数据传入给函数,在函数体内数据的变化,我们想在函数体外也能表现这个变化,这个时候我们就传指针,地址传递(数据结构:链表,栈,队,二叉树)

​ 如果将一个数据传入给函数,在函数体内只是访问这个数据,不想修改数据本身的值(函数体外部的值),这个时候我们一般就只用传递普通变量,值传递

对于一个匿名空间来讲,我们用指针来表示。(用malloc函数申请空间)

//头文件声明
#include <stdlib.h>
//函数原型,功能:申请一段匿名空间
void *malloc(size_t size);
参数列表:
size_t size:表示申请的空间的大小
返回值:void *:
成功:申请到的空间地址
失败:返回NULL
int *p = malloc(sizeof(int));
int *p1 = malloc(4);

//释放申请空间
void free(void *ptr);
free(p);
free(p1);
指针的尺寸?

​ 指针尺寸指的是指针所在内存的字节数(取决于操作系统的位数)

​ 32位机里面,指针变量的大小4Byte,(32bit),32位机里面,指针变量的大小8Byte,(64bit),

指针所占内存,取决于地址长度,而地址长度取决于系统寻址范围,即字长

结论:指针尺寸只跟系统的字长有关,跟具体的指针的类型无关
二、指针的基本语法

指针的定义和初始化

定义:

数据类型 *指针变量名;

int *p;
初始化:

数据类型 *指针变量名 = 地址; 或者 指针变量名 = 地址

int a;
int *p = &a;    

int *p;   
p = &a;
attention:

这个地址里面的数据的类型要和前面的数据类型相同

指针如何去赋值和获取数据

int型指针为例

	int a = 100;
	int *p = &a;   //*定义指针变量
	*p = 200;     //*解引用,拿地址p里面的值
	printf("p = %d\n", *p);
	printf("a = %d\n", a);

数据获取,需要通过*解引用的方式,拿到地址对应的数据

星号 *

​ 注释:/* …xxxx…*/

​ 算术运算符:*(乘)

​ 赋值运算符:*=

​ 关于指针的应用:

​ 指针的定义:int *p;表示声明一个指针变量

​ 在其他地方:将*放在一个地址的前面,表示解引用(获取地址里 面的值)

指针的运算
指针的定义
指针的赋值
int a = 10;
int *p = &a;

int *p1;
p1 = &a;
解引用

指针定义过后,没有赋初值或者没有给一个合法地址给他的时候,不能直接使用解引用,相当于,使用解引用必须要保证该指针指向一个合法的地址

取地址 &变量名

一般用于指针的初始化

int *p = &a;

指针的加减法:
指针加减数字

​ p+3, p-1,指针加减数字表示在内存地址中,p指针指向的地址向上或者向下移动n个单位,p指向的地址没有发生改变。

​ 具体加减多少个字节?根据指针存放的数据类型

​ 如果是int类型指针,那么实际上,p±n,实际上4*n个字节

​ 如果是char类型指针,那么实际上,p±n,实际上1*n个字节

​ 如果是一整块数组,那么就是加减整块数组的n倍

指针加减指针

​ 指针相减,两个指针必须是相同类型,表示两个地址相差多少个单位长度

int a[5] = {1,2,3,4,10};
printf("a[4] - a[1] = %ld\n", &a[4] - &a[1]);
//得到相差的3个单位长度
三、指针函数、函数指针、指针数组、数组指针、函数指针数组

指针函数:返回值是一个指针的函数

函数指针:存放一个函数的地址的指针

指针数组:每个元素都是指针的数组

数组指针:存放数组的地址的指针

函数指针数组:每个元素都是函数的地址的数组

#include <stdio.h>

void func(int a)
{
	printf("%d\n",a);
	return;
}
void func2(int a)
{
	printf("%d\n",a);
	return;
}
void func3(int a)
{
	printf("%d\n",a);
	return;
}
指针函数
void * fss(void * a)	///返回值为void *的指针的函数
{
	return a;
}
int main(int argc, char const *argv[])
{
	int a=10;
	指针
	int *pint=&a;
	数组
	int arr[5]   ={1,2,3,4,5};
	int arr1[2]  [3] = {{1,2,3},{4,5,6}}; 

	pint = arr;
	// printf("%d\n",*(pint+2));
	
	指针数组
	// int *d,*b,*c;
	// int *parr[5] = {d,b,c};
	int d,b,c;
	int *parr[5] = {&d,&b,&c};
	数组指针
	int (*p1arr)  [5] = &arr;
	int (*p2arr)  [3] = arr1;
	// printf("%d\n",*p1arr[2]);
	// printf("%d\n", p2arr[0][2]);
	 
	指针函数
	// void * f(int a);	///返回值为void *的指针的函数

	函数指针
	// void (*funp)(int a);	///存放一个函数地址的指针
	void (*funp)(int a) = func;
	// funp(3);
	// funp(4);
	
	函数指针数组
	void( *funpa[5])()={func,func2,func3};
	void(*ffff[3])(int a) = {func};
	ffff[1]=func2;
	ffff[2]=func3;
	for (int i = 0; i < 3; ++i)
	{
		funpa[i](i+10);
		ffff[i](i+1000);
	}
	return 0;
}
attention:

函数指针,函数指针数组,存放函数入口地址的时候,函数的返回值类型要一致,参数列表是可以省略不写,也就说函数的参数列表个数可以不一样

四、多级指针
二级指针:一个指向指针变量的指针
int a=10;
int *p=&a;
int **p2 = &p;
指针的拆解方法:

​ 第一部分:指针的名称

​ 第二部分:指针所指向的数据类型,可以是任意类型

char **p2; ///第一部分 *p2		第二部分 char *
char ***p3;///第一部分 *p3		第二部分 char **
char (*p4)[3];///第一部分 *p4	第二部分 char [3]
五、特殊的指针
void指针、空指针、零指针、const指针、char指针
1.void指针(万能指针,泛型指针,通用指针)

​ 当不确定后面具体需要使用什么类型的时候

​ 典型应用:malloc申请匿名空间,函数传参

​ void指针,必须要确定其类型才能使用

​ void指针最大的优点:可以赋值给任意类型指针

void *p = malloc(20);
int *p1 = p;
float *p2 = p;
char *p3 = p;
2.概念:指向一块未知区域的指针,被称为野指针,野指针是危险的。

一般我们认为没有赋初值的指针变量就是野指针

int *p;      //野指针
*p = 100;   //这里会发生段错误,非法使用一块未知的,未初始化的空间
printf("*p = %d\n";, *p);

危害:

​ 引用野指针,非法访问非法的内存,常常会导致段错误(Segmentation fault (core dumped))

​ 引用野指针,可能会破坏系统的关键数据,导致系统崩溃等严重后果

产生原因:

​ 指针定义之后,未初始化

​ 指针所指向的内存,被系统回收

int *p;					//野指针
int *p1 = malloc(4);
free(p1);				
*p1 = 100;				//野指针
printf("*p1 = %d\n", *p1);

指针越界:

int a[3] = {1, 2, 3};
int *p2 = a;
printf("*(p2+3) = %d\n", *(p2+3));   //指针访问越界

如何防止:

​ 指针定义时,及时初始化

​ 绝不引用已被系统回收的内存

​ 确认申请的内存的边界,谨防越界

3、空指针和零指针

​ 很多情况下,我们不可避免会遇到野指针,比如刚定义的指针无法立即为其分配一块恰当的内存,又或者指针所指向的区域被释放了等等,一般的做法就是将这些危险的指针,指向一块确定的内存,比如零地址内存
在这里插入图片描述
值为NULL的指针就是空指针

int *p = NULL;

为了避免野指针和类似地址访问的异常错误,这个时候,我们就给指针赋一个NULL值

目的是:更安全,但是空指针也不能直接使用,必须给他一个有效的地址才能用,地址要有空间

空指针的NULL值,在很多系统中是0值,取决于操作系统

值为0的指针就是零指针

int *p = 0;

注意:在很多系统中,零指针和空指针是一样的,值都是为0,但是,不能保证所有系统都是这样的,那么,我们平时在使用的时候,一般是用NULL。

4.const关键字

const关键字的一般使用

​ 我们平时将const定义的变量称为只读变量(本质上不是常量)

基本语法:

const 数据类型 变量名 = 初始值;
const int a = 10;
数据类型 const 变量名 = 初始值;
int const a = 20;

以上两种方式都可以:

注意:由于是只读变量,一旦定义完,不能修改它的值,所以,const修饰只读变量的时候,必须在定义时,给这个变量做初始化

const指针

const和指针变量一起用,可以限制指针变量本身,也可以限制指针变量指向的数据

const指针有两种形式,①常指针 ②常目标指针

常指针:const修饰指针本身,表示指针变量本身无法修改
在这里插入图片描述
常目标指针:const修饰指针的目标,表示无法通过该指针修改其目标
在这里插入图片描述
常指针在实际引用中并不常见

常目标指针在实际应用中广泛可见,用来限制指针的读写权限

const关键字在C语中有什么作用?

为了放在传入参数在函数中被修改,一般放在函数的形参上。

5.char 型指针

在C语言中,表示一个字符串

常量“hello world”
char型数组:char ch1[] = "hello world";
char型指针:char *ch2 = "hello world";

char型数组和char型指针在表示字符串的区别:

char ch1[] = "hello world";	//char型数组
char *ch2 = "hello world";	//char型指针,字符指针常量
printf("ch1 = %s\n", ch1);
printf("ch2 = %s\n", ch2);

ch1[2] = 'b';
ch2[2] = 'b';  //不可以更改

printf("ch1 = %s\n", ch1);
printf("ch2 = %s\n", ch2);

六、指针和数组
指针与一维数组:
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int *p=NULL;
	int arr[]={0,1,2,3,4,5,6,7,8,9};
	p=arr;

	int *p2=malloc(sizeof(int)*5);
	p2[4]=5;

	printf("%d\n", *(arr+1));
	printf("%d\n",arr[1]);

	printf("%d\n",p[9]);
	printf("%d\n",*(p+9));

	printf("%d\n",p2[4]);
	printf("%d\n",*(p2+4));

	free(p2);
	return 0;	
}
指针与二维数组:
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int *p=NULL;
	int (*q)[3]=NULL;
	int arr[2][3]={{1,2,3},{4,5,6}};
	int (*s)[3]=(int (*)[])malloc(sizeof(int)*2*3);
	int *t[2]={NULL};		//指针的数组,数组存指针
	int (*f)[2][3]=NULL;
	int (*k)[2][3]=(int (*)[2][3])malloc(sizeof(int*)*2*3);

	printf("%d\n",arr[1][1]);
	printf("%d\n",*(arr[1]+1));
	printf("%d\n", (*(arr+1))[1]);
	printf("%d\n",*(*(arr+1)+1));

	p=arr[0];
	// p=&arr[0][0];
	printf("%d\n",*(p+4));

	q=arr;
	// q=&arr[0];
	printf("%d\n",q[1][1]);
	printf("%d\n",*(q[1]+1) );
	printf("%d\n", (*(q+1))[1]);	//q+1 偏移int [3]
	printf("%d\n",*(*(q+1)+1));		//*(q)+1 偏移int 


	s[1][1]=5;
	printf("%d\n",s[1][1]);


	t[0] = arr[0];
	t[1] = arr[1];
	printf("%d\n",*(t[0]+4));	//偏移int 
	printf("%d\n",*(t[1]+1));


	f=&arr;
	(*f)[1][1]=6;
	printf("%d\n",(*f)[1][1]);


	(*k)[1][1]=5;
	printf("%d\n",(*k)[1][1]);
	free(s);
	free(k);
	return 0;	
}

other

#include <stdio.h>
int main(int argc, char const *argv[])
{
	int a=2,*p=&a;
	printf("p[0] = %d\n",p[0]);//可以看作数组第0个元素

	int a[3] = {1,2,3};
	// int *p1 = &a[0];	///a == &a[0]
	// // int	*p1 = a;
	// // int **p2 = &p1;
	// // int ***p3 = &p2;
	// int **p2 = (int **)&a;

	// int (*a1)[3] = &a;		///第一部分 *a1 第二部分 int[3]
	// int *a2[3] = {a,a+1,a+2};///第一部分(数组)a2[] 第二部分 int * 
	// a2[2] = &a[2];
	// printf("%d\n",a[2]);

	int a[2][3] = {{1,2,3},{4,5,6}};
    
    // printf("a[1][2] = %d\n", *(*(a+1)+2));
	// printf("a[1][2] = %d\n", (*(a+1))[2]);
	// printf("a[1][2] = %d\n", *(a[1]+2));
    
	// int *p1 = (int *)a;
	// int *p1 = (int *)&a;
	// int *p1 = (int *)&a[0];
	// printf("%d\n",*(p1+5));

	//int **p2 = &p1;
	// printf("%d\n",*(*p2+4));	//a[1][2]

	//指针数组,存指针
	// int *p3[3] = {&a[0][0],&a[0][1]};
	// printf("%d\n", *p3[1]);


	//数组指针
	int (*parr) [3] = a;	//一个指针 *parr 指向int[3]
	printf("a[0][0] = %d\n",**parr);
	printf("a[0][1] = %d\n",*((*parr)+1));
	// printf("%d\n",parr[0][0]);
	// printf("%d\n",parr[1][2]);
	
	// printf("%d\n",*(parr[0]+2) );
	// printf("%d\n",*(parr[1]+2) );

	// printf("%d\n",(*(parr+1))[1]);
	
	// printf("%d\n",*(*parr+2) );
	// printf("%d\n",*(*(parr+1)+1));

	int (*p4)[2][3] = &a;
	// printf("%d\n",(*p4)[1][1]);

	
    return 0;
}

test:
定义一个二维数组,通过指针的方式去遍历每个元素
int a[2][3];
定义一个数组指针int (*p)[2][3]去遍历以上数组

七、指针与函数
#include <stdio.h>

int * f(int *p)	//函数返回值为 int*
{
	p[0]++;
	return p;
}

int main(int argc, char const *argv[])
{
	
	int i=0;
	int *p=&i;
	int *(*f2)(int *p); //一个指向函数的指针	返回值为指针

	p=f(p);

	printf("%d\n",p[0]);

	f2=f;

	f2(p);	//p[0]  2
	printf("%d\n",f2(p)[0]);	//p[0]  3
	return 0;
}
/*
#include <stdio.h>
print(int(*p)[5], int r, int c)//接收的是指向一维数组的数组指针
{
	for (int i = 0; i < r;i++)
	{
		for (int j = 0; j < c; j++)
		{
			//printf("%d ", p[i][j]);
			//printf("%d ", *(p[i]+j));
			printf("%d ", *(*(p+i)+j));
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,5,4,6,7,9,8,4,2,5,4,5,4,5,6 };
	print(arr, 3, 5);//传过去的是二维数组
	return 0;
}
*/
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Yengi

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

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

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

打赏作者

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

抵扣说明:

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

余额充值