前言: 一提到指针,大家的第一反应是什么?很难理解,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;
}
传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量
如果函数内部要修改 主调函数中的变量的值,就需要传址调⽤。
标签:传址,const,变量,int,地址,内存,字节,指针 From: https://blog.csdn.net/2303_79266237/article/details/141783806