详细认识指针(一) --指针的概念、指针的变量和地址、const修饰指针、指针的运算、野指针的概念、传值和传址的认识

前言: 一提到指针,大家的第一反应是什么?很难理解,No,No, No。其实指针这个知识还是很贴近生活的,接下来我把我的感悟分享给大家。


指针的概念

指针的定义 

首先,我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是从内存中读取的,处理后的数据也会放回到内存中。 这个内存指的是买电脑的运行内存: 8GB/16GB/32GB。

在C语言中,内存其实被划分成了一个个的内存单元,每个内存单元的大小占一个字节。

其实,每个内存单元,相当于一个学生宿舍。而一个宿舍能放八个学生(因为一个字节等于8个比特位,每个人是一个比特位)

然后,每个内存单元都一个编号(这个编号就相当于门牌号),有了这个编号,CPU就能快速找到内存空间。

 生活中我们把门牌号叫做地址,因为当你有了门牌号你能快速的定位别人的位置。在计算中我们把内存单元的编号叫做地址。C语言中给地址取了新的名字叫:指针

 所以我们可以理解为:

内存单元的编号=地址=指针


 了解编址(硬件层面)--内存单元的编号

CPU访问内存中的某个字节空间,必须知道这个字节空间在内存中的位置。因为内存中的字节很多,所以要给它进行编址 。(就像宿舍很多,需要给宿舍编一个门牌号)

计算机中的编址,并不是把每个字节的地址记录下来(内存单元的编号不需要存起来),而是通过硬件设计(地址总线)完成的。 

地址总线: 

我们可以简单理解,32位机器有32根地址总线, 每根线只有两态,表⽰0,1【电脉冲有⽆】,那么 ⼀根线,就能表⽰2种含义,2根线就能表⽰4种含 义,依次类推。32根地址线,就能表⽰2^32种含 义,每⼀种含义都代表⼀个地址。

 简单讲一下原理:当CPU想要读取信息时,会发送一个地址信号 给 地址总线;地址总线就在内存中直接找到地址。


 指针的变量和地址

取地址操作符

在C语言中,创建变量的本质上向内存申请空间。

#include <stdio.h>
int main()
{
	int a = 0x11223344; //申请了四个字节
	
	return 0;
}

 ⽐如,上述的代码就是创建了整型变量a,内存中 申请4个字节,⽤于存放十六进制的数(一个十六进制位用4个二进制位表示),其中每个字节都 有地址


那我们怎么得到a的地址呢?

这里就要知道一个操作符&--取地址操作符

注意:  a & b 按位与操作符,因为这是有俩个操作数,这是双目操作符
            & -- 取地址操作符,而这边是单目操作符,只有一个操作数

#include <stdio.h>
int main()
{
	int a = 0x11223344; //申请了四个字节
	printf("%p\n", &a);
	return 0;
}

 当我们打印出地址时:0x009DFBFC

因为&a取出的是a所占字节中地址较小的字节地址

虽然整型变量占⽤4个字节,我们只要知道了第⼀个字节地址,顺藤摸⽠访问到4个字节的数据也是可 ⾏的


指针的变量

int n = 10;

int * pn = &n;

 首先取出n的地址,放在pn的变量中,而这种变量叫做指针变量,那么int*是什么呢?其实这指的是pn的类型(就像int a的类型是int一样)。那如何理解指针的类型呢?其实 *指的是pn是指针变量,前面的int说明pn指向的是整型类型的对象(int a)。

注意:

&n -- n的地址 --地址就是指针

pn就是用来存放地址的,也可以说用来存放指针的

pn就可以被称为指针变量(存放地址的变量)

总的来说

指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。


解引用操作符(间接访问操作符)

C语⾔中,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针) 指向的对象,这⾥必须学习⼀个操作符叫解引⽤操作符(*)。

#include <stdio.h>
int main()
{

	int n = 20;
	int* pn = &n;
	//解引用操作符,间接访问操作符
	*pn = 30; //把地址里的值改为30
	printf("%d\n", *pn); //访问pn地址里的值
	return 0;
}

 因为pn是指针变量存放了n的地址,当想要访问地址里的值时,就要*pn,这个意思是间接访问pn地址的值 或者 修改。


 指针的大小

问题与解答

问题:指针变量是多大空间呢?

解答我们可以这样想,指针变量存放的是地址,地址的存放需要多大空间呢?那么指针变量的大小就是多大。


前⾯的内容我们了解到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后 是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4 个字节才能存储。

 同理64位机器,假设有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要 8个字节的空间,指针变量的⼤⼩就是8个字节。

结论:

• 32位平台下地址是32个bit位,指针变量⼤⼩是4个字节

• 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节

• 注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。


指针类型有什么意义? 

若指针类型为int * 的指针+1,那么它将跳过4个字节的大小指向4个字节以后的内容:

 若指针类型为char * 的指针+1,那么它只会跳过1个字节的大小指向下一个字节的内容,以此类推 

结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。 


2.指针解引用

指针的类型决定了指针解引用的时候能够访问几个字节的内容。
若指针类型为int *,那么将它进行解引用操作,它将可以访问从指向位置开始向后4个字节的内容:

 结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。 ⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。


const修饰指针

变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。 但是如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作⽤。

 const修饰指针主要有俩种情况
1.const放在*的左边 : int const* p 或者 const int* p
 意思:表示指针指向的内容,不能通过指针来改变了,但是指针变量本身的值是可以改的
2.const放在*的右边: int *const p
 意思:指针变量p本身不能被修改了,但是指针指向的内容可以通过指针变量来改变
3.const int * const p

意思:指针变量p本身 和指针指向的内容,都不能被改变。

总的来说:

const的位置不同,所产生的效果不同,根据所需要的功能,选择把const放在不同的位置。 


 指针的运算

 1.指针+-整数

因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素

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

	return 0;
}

 2.指针-指针

指针-指针后的绝对值,得到的是元素的个数。

前提条件:俩个指针指向的是同一个空间。

#include <stdio.h>
size_t my_strlen(char* str)
{
	char* p = str;
	while (*str != '\0')
	{
		str++;
	}
	return str - p;
}

int main()
{
	char arr[] = "abcdef";
	//数组名就是首个元素的地址
	size_t len = my_strlen(arr);
	printf("%zd\n", len);

	return 0;
}

3.指针的关系运算 

#include <stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	//使用指针的关系运算来打印数组的内容
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	while (p < &arr[sz]) 
	{
		printf("%d ", *p);
		p++;
	}

	return 0;
}

 通过while (p < &arr[sz]) 这一行的指针关系运算去得到数组中的每一个的值。


野指针

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

野指针的成因

1. 指针未初始化 

#include<stdio.h>
int main()
{
	int* p;
	*p = 10;
	return 0;
}

局部指针变量p未初始化,默认为随机值,所以这个时候的p就是野指针。


2.指针越界访问 

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = &arr[0];
	int i = 0;
	for (i = 0; i < 11; i++)
	{
		*p++ = i;
	}
	return 0;
}

当指针指向的范围超出arr数组时,p就是野指针。 


3.指针指向的空间被释放 

#include<stdio.h>
int* test()
{
	int a = 10;
	return &a;
}
int main()
{
	int* p = test();
	return 0;
}

 当a返回的时候,内存空间返回给了操作系统,就没有了使用权限。(在自定义的test函数中,int   a 的生命都在test中,当运行好这个函数之后,这个局部变量就在栈区销毁了)

指针变量p得到地址后,地址指向的空间已经释放了,所以这个时候的p就是野指针。(局部变量出了自己的作用域就被释放了)


 如何避免野指针

1.指针初始化

当指针明确知道要存放某一变量地址时,在创建指针变量时就存放该变量地址。
当不知道指针将要用于存放哪一变量地址时,在创建指针变量时应置为空指针(NULL)。

#include<stdio.h>
int main()
{
	int a = 10;
	int* p1 = &a;//明确知道存放某一地址
	int* p2 = NULL;//不知道存放哪一地址时置为空指针
	return 0;
}

2.小心指针越界
3.指针指向的空间被释放后及时置为NULL
4.使用指针之前检查有效性
在使用指针之前需确保其不是空指针,因为空指针指向的空间是无法访问的。


 传值和传址的认识

传值

#include<stdio.h>
void swap(int x, int y)
{
	int bottle = x;
	x = y;
	y = bottle;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);
	printf("交换之前:a = %d b = %d\n", a, b);
	swap(a, b);
	printf("交换之后:a = %d b = %d\n ", a, b);

	return 0;
}

 这里我们看到,实参传给形参的值是没有交换的,这是为什么呢?其实这是因为形参又重新创建了一块内存空间,它是在自己的内存空间中活动的,不影响实参。

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


 传址

这是指通过传地址,以此来达到交换的目的

#include<stdio.h>
void swap(int* pa, int* pb)
{
	int bottle = *pa;
	*pa = *pb;
	*pb = bottle;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);
	printf("交换之前:a = %d b = %d\n", a, b);
	swap(&a, &b);
	printf("交换之后:a = %d b = %d\n ", a, b);

	return 0;
}

传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量

 如果函数内部要修改 主调函数中的变量的值,就需要传址调⽤。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值