C语言第六课,指针初阶

前面操作符详解的博客中,单目操作符解引用那里,就已经比较详细地讲解了指针是什么。
有的小伙伴们应该初步理解了指针,没有理解也没有关系。接下来会有更详细的讲解。

内容有:指针是什么?大小有多大?不同类型的指针可以访问多少字节?“实现逆序数组中的元素”的题目中,引出野指针的概念。二级指针、指针数组等问题。

1. 指针是什么

指针理解的2个要点:
1 指针是内存中一个最小单元的编号,也就是地址。
2 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。

问题1:内存单元编号是什么?一个小的单元有多大?

对于32位的机器,假设有32根物理的地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000(编号0x00000000)
00000000 00000000 00000000 00000001(编号0x00000001)

11111111 11111111 11111111 11111111(编号0xFFFFFFFF)

这里就有2的32次方个地址,编号采用的是16进制的编号。
每个地址标识一个字节,那我们就可以给 4G的空闲进行编址,这些编号是硬件电路产生的,没有必要把它存起来。

注意:字节(byte)是最小的存储单元,等于8bit。
对于32位机器而存放地址编号需要4个字节,也就是32bit,4GB;对于64位机器而存放地址编号需要8个字节,也就是64bit,8GB。

问题2:如何理解指针就是地址

内存单元都有一个编号,而这个编号被我们称为地址,其实我们平时说的指针都是“存放地址的变量”,可以通过这个变量找到这个地址。

所以从某种意义上来说内存单元=编号=地址=指针。(参考前面:32根物理的地址线)

2. 指针和指针类型

这里我们在讨论一下:指针的类型。
我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?当然有啊。

int main()
{
	char* pc;
	int* pa;
	double* pd;
	printf("%d\n", sizeof(pc));
	printf("%d\n", sizeof(pa));
	printf("%d\n", sizeof(pd));
	return 0;
}

x86环境下(32位)运行的结果都是4;x64环境下(64位)运行的结果都是8。
可以得知指针的大小和指针的类型无关

指针的解引用

下面来看这个样的一个代码,进一步理解一下指针:

int main()
{
	int a = 10;
	int* p = &a;
	*p=20;//解引用操作
	printf("%d", a);//通过地址解引用,来操作a
	return 0;
}

大致过程是这样的:首先定义一个变量a,这个变量有一个地址(假设是0x0012ff40),还有一个初始值10。&a就相当于获取了地址0x0012ff40,赋值给指针变量p。使用*对p进行解引用操作,可以修改指针指向的变量。

打个比方:变量a是宝藏的位置,变量的值是宝藏的内容,p就是这个宝藏的藏宝图,有了这个图,你可以解引用操作,找到宝藏然后把它拿走(更改值)!

下面再来讨论一下指针的访问权限的问题:

int main()
{
	int a = 0x11223344;
	//int* pa = &a;
	//*pa = 0;

	char* pc = &a;//理论上是可以存放的
	*pc = 0;
	//指针类型决定了在解引用时一次能访问几个字节?
	return 0;
}

按下F10进行调试,在调试->窗口->内存->内存1 中点开这个窗口,可以看到我们使用char类型的指针,成功低更改了a的内容,只有1字节。
char*
使用同样的步骤,int
类型一次性更改了4字节的值:
int*

指针±整数

int main()
{
	int a = 10;
	int* pa = &a;
	char* pc = &a;
	printf("%p\n", pa);
	printf("%p\n", pa+1);

	printf("%p\n", pc);
	printf("%p\n", pc+1);

	return 0;
}

运行结果如下:
运行结果
总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。
int型的指针一次操作四个字节,char型的指针一次操作一个字节。
不难得出double*型的一次访问8字节。

3. 野指针

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

3.1野指针的形成原因

原因一:指针未初始化

int main()
{ 
 int *p;//局部变量指针未初始化,默认为随机值
    *p = 20;
 return 0;
}

原因二:指针越界访问
笔者在自己写程序的时候,也写出过野指针。
例如需要实现逆序数组中的元素,注意这个是错误的代码,出现了野指针:

#include<stdio.h>

//用来打印数组中的数
void print(int* arr,int x)
{
	for (int i = 0; i<x; i++)
	{
		printf("%d ", *(arr+i));
	}
	printf("\n");
}

//逆序数组中的元素
void maker_reverse(int* arr,int x)
{
	int tmp = 0;
	for (int i = 0; i < x/2; i++)
	{
		tmp = *(arr+i);
		*(arr + i) = *(arr - i-1);
		*(arr - i - 1) = tmp;
	}
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("交换后:\n");
	maker_reverse(arr,sz);
	print(arr,sz);
	return 0;
}

我的思路是:第一个元素和最后一个元素交换,第二个和倒数第二个交换……
如果该数组的长度是奇数,那么中间那个元素其实可以不用参与交换。
如果该数组的长度是x偶数,那么刚好循环(x/2)次,刚好完成交换。

而我的代码中:
错误代码
当i等0的时候arr - i - 1 相当于 arr - 1, 这个不是相当于越界访问了
思考一下,然后再来看看正确的代码:

void maker_reverse(int* arr,int x)
{
	int tmp = 0;
	for (int i = 0; i < x/2; i++)
	{
		tmp = *(arr+i);
		*(arr + i) = *(arr +x - i-1);
		*(arr +x - i -1) = tmp;
	}
}

3.2 如何避免野指针

  1. 指针初始化
  2. 小心指针越界
  3. 指针指向空间释放即使置NULL
  4. 避免返回局部变量的地址
  5. 指针使用之前检查有效性

4. 指针运算

  1. 指针± 整数
  2. 指针-指针
  3. 指针的关系运算

4.1指针± 整数

int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

自己编写一个求字符串长度的函数。

int my_strlen(char*s)
{
	int count = 0;
	while (*s != '\0')
	{
		count++;
		s++;
	}
	return count;
}
int main()
{
	char arr[] = "abc";
	int len = my_strlen(arr);
	printf("%d\n", len);

	return 0;
}

4.2指针-指针

指针减去指针的前提是,两个指针指向同一个连续空间。

int main()
{
	int a[10] = { 0 };
	
	printf("%d\n", &a[9] - &a[0]);//地址从低往高
	printf("%d\n", &a[0] - &a[9]);
	
	return 0;
}

在这里插入图片描述
随着数组下标的增加,地址增加。

用这种方法编写字符串长度的函数:

int my_strlen(char* s)
{
	int* start = s;//先把这个地址保存一下
	while (*s != '\0')
	{
		s++;
	}
	return s-start;
}
int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);
	printf("%d\n", len);

	return 0;
}

5. 指针和数组

见数组名和数组首元素的地址是一样的。

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    //arr  是数组首元素地址
    int* p = arr;
    int i = 0, sz = sizeof(arr) / sizeof(arr[0]);
    for (i = 0; i < sz; i++)
    {
        printf("%p==%p\n", p + i, &arr[i]);//都是相连通的
    }

    printf("%d\n", sz);
    printf("%p\n", arr);
    printf("%p\n", &arr[0]);
    return 0;
}

在这里插入图片描述
结论:数组名表示的是数组首元素的地址。

6. 二级指针

指针变量也是变量,是变量就有地址,存放指针变量的地址就是 二级指针

int main()
{
	int a = 10;
	//int* p1, * p2;//注意不同的写法

	int* pa = &a;//*靠近哪都可以
	int** ppa = &pa;//ppa就是一个二级指针
	int*** pppa = &ppa;
	**ppa = 20;//对于二级指针,使用两次解引用访问
	printf("%d ", a);

	return 0;
}

三级指针

7. 指针数组

指针数组是数组。是存放指针的数组。
数组我们已经知道整形数组,字符数组。

int main()
{
	int arr[10];//整型数组
	char ch[5];//字符数组

	//指针数组
	int a = 0;
	int b = 9;
	int c = 8;
	int* arr2[5] = {&a,&b,&c};//存放整型指针的数组,不完全初始化
	//通过arr2打印abc
	for (int i = 0; i < 3; i++)
	{
		printf("%d ", *(arr2[i]));
	}

	return 0;
}

指针数组和整型数组的区别:
指针数组
整型数组
这些只是指针的初级阶段,要想成为“江大指针王”,还需努力!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值