深⼊理解指针


前言

指针是C语言中最具特色的内容,也是C语言的重要概念和精华所在。

一、指针概述

1、地址和指针

指针与地址虽然有着密切的关系,但它们的概念上是有区别的,指针所标明的地址总是为保存特定的数据类型的数据而准备的,因此指针不但标明了数据的存储位置,而且标明了数据的类型,可以说指针是存储特定的数据类型的地址。指针是一种指向其他数据对象的数据类型。
每个内存单元也都有⼀个编号(这个编号就相当于宿舍房间的⻔牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。
活中我们把⻔牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起了新的名字叫:指针。
在这里插入图片描述

2、指针变量的定义

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

int *p = &a:把a的地址给指针变量p

int是指针变量p指向的对象是int类型的,
*(星号)是说明p是指针变量。

在这里插入图片描述

5、指针变量与地址

在这里插入图片描述
通过这个代码可以看出整型变量a的地址和指针变量p存的地址是一样的。
在这里插入图片描述
这里a是整型占4个字节,有4个地址,取地址只是取出较小的地址,也可以理解为取出首地址。

4、解引用操作符

在这里插入图片描述

这里的* p等价于a,所以更改 * p的值也就把a的值也更改了。

5、指针变量的大小

指针变量的⼤小取决于地址的⼤小
32位平台下地址是32个bit位(即4个字节)
64位平台下地址是64个bit位(即8个字节)

在这里插入图片描述
在这里插入图片描述

6、指针变量的意义

在这里插入图片描述

从这个代码就可以看出char*和int *访问的字节数不一样
char *访问一个字节
int *访问四个字节
pc是存放是变量a的地址
*pc是变量a

7、const修饰指针变量

在这里插入图片描述

这里const放在的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。
但是指针变量本⾝的内容可变。
在这里插入图片描述
而const放在
的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指
向的内容,可以通过指针改变。

8、指针的基本运算

指针的基本运算有三种,分别是:
• 指针± 整数
• 指针-指针
• 指针的关系运算

指针± 整数

int main()
{
	int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &a[0];
	int i = 0;
	int sz = sizeof(a) / sizeof(a[0]);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));//p+i 这⾥就是指针+整数
	}
	return 0;
}

指针-指针

int my_strlen(char* s)
{
	char* p = s;
	while (*p != '\0')
		p++;
	return p - s;
}
int main()
{
	printf("%d\n", my_strlen("abc"));
	return 0;
}

指针的关系运算

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[0];
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("%p %d %p\n", arr, sz,p);
	while (p < arr + sz) //指针的大小比较
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

假设:int i=3;int *p =&i;

i 等价于*p
&i等价于p

i=3等价于*p=3

9、野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

野指针成因
1.指针未初始化
2.指针越界访问
3.指针指向的空间释放

二、指针的使用和传址调用

void swap(int p1, int p2)
{
	int tmp = 0;
	tmp = p1;
	p1 = p2;
	p2 = tmp;
}
int main()
{
	int i = 8;
	int j = 6;
	printf("%d %d \n", i, j);
	swap(i, j);//把变量 i j传给swap函数
	printf("%d %d \n", i, j);
	return 0;
}

输出结果:
在这里插入图片描述

传值调用
实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。

void swap(int* p1, int* p2)
{
	int tmp = 0;
	tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int main()
{
	int i = 8;
	int j = 6;
	int* p1 = &i;
	int* p2 = &j;
	printf("%d %d \n", i, j);
	swap(p1, p2);//把指针变量p1 p2传给swap函数
	printf("%d %d \n", i, j);
	return 0;
}

输出结果:
在这里插入图片描述

传址调用
在这里传的指针变量是地址所以可以交换两个变量的值,叫传址调用

三、指针、数组和地址之间的关系

1、数组名的理解

在这里插入图片描述

数组名就是数组首元素地址,但是有两个例外
1.sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,
单位是字节
2.&数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素
的地址是有区别的)
在这里插入图片描述
这里可以看出&arr+1就跳过了整个数组的大小。

2、 一维数组传参的本质

在这里插入图片描述

从这组代码可以看出,数组的大小在main函数和test函数中不一样,这是因为数组传参的本质是把数组的首元素传了过去,arr的首元素地址的大小就是4,所以在test函数中数组的大小就是1

3、指针与一维数组的对照关系

在这里插入图片描述

4 、指针数组

指针数组:存放指针的数组
指针数组的每个元素都是⽤来存放地址(指针)的。

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	//数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中
	int* parr[3] = { arr1, arr2, arr3 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}

5 、数组指针

数组指针:指向数组的指针(数组指针变量存放的是数组的地址)
数组指针变量是指针变量,一般用于二维数组

void Print(int(*p)[4], int r, int c)
{
    int i = 0;
    for (int i = 0; i < r; i++)
    {
        int j = 0;
        for (int j = 0; j < c; j++)
        {
            printf("%d ",*(*(p+i)+j));
        }
    }
}
int main() {
    int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
    Print(a, 3, 4);
    return 0;
}

在这里插入图片描述

void Print(int(*p)[4], int r, int c)
{
    int i = 0;
    for (int i = 0; i < r; i++)
    {
        int j = 0;
        for (int j = 0; j < c; j++)
        {
            printf("%d ",p[i][j]);
        }
    }
}
int main() {
    int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
    Print(a, 3, 4);
    return 0;
}

在这里插入图片描述
通以上两个代码可以看出p[i][j]等于*(*(p+i)+j)
⼆维数组传参本质上也是传递了地址,传递的是第⼀行这个⼀维数组的地址,

6、指针数组和数组指针的区别

指针数组和数组指针在定义时非常相似,只是括号的位置不同:

int *(p1[5]); //指针数组,可以去掉括号直接写作 int *p1[5];
int (*p2)[5];//数组指针,不能去掉括号

7、指针与二维数组

int a [3][4]:
a=二维数组的首地址;
a+i=第i行的首地址;
a[i]=*(a+i) —第i行第0列的元素地址;
a[i]+j <----> *(a+i)+j —第i行第j列的元素地址;
*(a[i]+j) 和 *(*a+i)+j <—>a[i][j]。

int main()
{
	int a[3][2] = { {99,88},{77,66},{88,99} };
	printf("%p %p\n", a[1] , *(a + 1) );
	printf("%p %p\n", a[1] + 1, *(a + 1) + 1);
	printf("%p %p %p\n", *(a[2] + 2), *(*(a + 2) + 2),a[2][2]);
	return 0;
}

输出结果:
在这里插入图片描述

a+i=&a[i]=a[i]=*(a+i)=&a[i][0],值相等,含义不同:
a+i<—>&a[i] ,表示第i行首地址,指向行;

a[i]<—>*(a+i)<—>&a[i][0],表示第i行第0列的元素地址指向列。

int main()
{
	int a[3][2] = { {99,88},{77,66},{88,99} };
	printf("%p %p %p %p %p\n", a+1, &a[1], a[1], *(a + 1), &a[1][0]);
	printf("%p %p %p %p %p\n", (a+1)+1, (&a[1])+1, (a[1]) + 1, (*(a + 1)) + 1, (&a[1][0]) + 1);
	return 0;
}

输出结果:
在这里插入图片描述

四、字符指针变量

这⾥是把⼀个字符串的首地址放到ps指针变量

int main()
{
	const char* ps = "hello";//这⾥是把⼀个字符串的首地址放到ps指针变量
	printf("%s\n", ps);
	return 0;
}

这里的字符串“hello”可以理解为一个字符数组,但是它的值不可以修改。
字符串常量出现在表达式中时它的值是首元素地址。

五、二级指针(指向指针的指针)

int main()
{
	int a = 10;
	int* ps = &a;
	int** pps = &ps;
	printf("%d %p %p\n",a, &a,a);
	printf("%d %p %p\n",*ps, ps,* ps);
	printf("%d %p %p\n",**pps, *pps,**pps);
	return 0;
}

在这里插入图片描述
在这里插入图片描述

六、函数指针

函数指针:用来存放函数的地址
在这里插入图片描述

  • 32
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值