目录
指针是什么
指针:在计算机科学中,指针是编程语言中的一个对象,利用地址,他的值直接指向存在电脑储存器中的另一个地方的值。由于通过地址能找到所需的变量单元,可以说地址指向变量单元。因此将地址形象化的成为指针。意思是通过他能找到以他为地址的内存单元
作用:可以通过指针来间接的访问内存
前言:
- 指针的内存编号是从0开始记录的,一般用16进制的数字表示
- 可以利用指针变量来保存地址
指针的原理
内存:内存是电脑上特别重要的存储器,计算机中所有程序的运行都在内存中进行。
所以为了有效地使用内存,就把内存分成了一个个细小的内存单元,每个内存单元的大小都是一字节。为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
计算机中的地址总线
32位——32根地址线——通电——每根线的通电状态(0/1)
64位——64根地址线——通电——每根线的通电状态(0/1)
电信号转化成数字信号就变成了1,0组成的2进制序列
以32位为例
00000000000000000000000000000000
……
11111111111111111111111111111111
总共可以表示的地址数为2^32个,这些序列都可以作为内存的编号,每个序列都可以作为内存的一个地址,然而表示地址的二进制数为32位最大为2^32,即4GB,这也是计算机内存的大小
对于32位系统,其数据总线为32位,因此ALU一次可处理4字节数据,对于64位系统,其数据总线为64位,因此ALU一次可处理8字节数据(操作系统的位数主要取决于数据总线的位数)
既然内存有地址,那么指针就可以拿出内存单元的地址
在C语言中指针用来存放变量的地址,指针本身也是个地址,只不过是存放地址的地址。
总结
- 指针是用来存放地址的,地址是唯一标识一块地址空间的
- 指针大小在32位平台上是4字节,在64位平台上是8字节
关于指针
当一个变量所占空间大于一个地址单元时,那么他会往下面继续排列所占用的地址连续,如果取址这个变量,那么拿到的是这个变量第一个地址单元的地址。(取址a——&a)
经过仔细地计算和权衡,我们发现一个字节给一个对应的地址是比较合适的。
#include <stdio.h>
void main() {
int a = 10;//占用4个字节,假设地址1字节
int* pa=&a;//拿到的是a的4个字节中第一个字节的地址
printf("%d\n", *pa);//通过*pa可以很好的找到a
}
指针的简单操作
int main() {
int a = 56;
printf("%p\n",&a);
int* pa = &a;
*pa = 20;
printf("%d\n", a);
return 0;
}
这里*号代表pa是指针变量
int代表pa执行的对象是int类型
这里存放a的地址是为了找到a,并对其进行控制
*pa就代表了a,这里的*号是解引用操作符目的是找到a
指针和指针类型
指针的大小
指针类型的大小都是相同的,指针有多大,取决于地址编号有多大(不是管理的内存空间)。
printf("%d\n", sizeof(char*));
printf("%d\n", sizeof(int*));
printf("%d\n", sizeof(long*));
printf("%d\n", sizeof(long long*));
指针类型的意义
#include <stdio.h>
void main() {
int a = 0x11223344;//16进制,一个位表示4个2进制位,由此观之a总共占了4字节
int* pa = &a;
*pa = 0;
//解引用后监测a的地址可以看到a地址所对应的数(4字节【16进制的11223344】)全部变成0
}
#include <stdio.h>
void main() {
int a = 0x11223344;//16进制,一个位表示4个2进制位,由此观之a总共占了4字节
char* pa = &a;
*pa = 0;
//解引用后监测a的地址可以看到a地址所对应的数(仅有开始的1字节【16进制的44】)变成00
}
由此观之:指针类型决定了解引用的权限有多大
#include <stdio.h>
void main() {
int arr[10] = { 0 };
int* pa = arr;
char* pc = arr;
printf("%p\n", pa);
printf("%p\n", pa+1);
printf("%p\n", pc);
printf("%p\n", pc+1);
//pa和pc两个地址的首位置相同,但是步长不同pa跳过4字节,pc跳过1字节
}
由此观之:指针类型决定了指针走一步能走多远(int*四字节,char*一字节)
注意:
- 浮点型指针解引用是向内存中拿一个浮点类型的数
- 整型指针解引用是向内存中拿一个整型的数
空指针
含义:指针变量指向内存编号为0的空间
作用:用于初始化指针变量
#include <stdio.h>
void main() {
int* p = NULL;
//p指向的地址为:00000000
printf("p指向的地址为:%p",p);
}
注意:
- 空指针指向的内存是不可以访问的
- 内存编号为0到255之间为系统占用内存,不允许用户访问(通过指针解引用的方式)
野指针
概念:野指针就是指针所指向的位置是不可知的(随机的,不明确的,没有明确限制的)
野指针的成因
1.指针未初始化
#include <stdio.h>
void main() {
//这里的p就是一个野指针
int* p;//p是一个局部的指针变量,局部变量不初始化的话,默认是随机值
*p = 20;//非法访问内存
}
2.指针越界访问
#include <stdio.h>
void main() {
int arr[10] = { 0 };
int* p = arr;
//指针指向的范围超出数组arr的范围,之后的地址所储存的东西我们不可预知(p+10)为野指针
*(p + 10) = 20;
}
3.指针指向的空间释放
理解:当申请一块特定的空间时(知道本空间地址),后来此空间是释放掉了还给了操作系统就不属于自己了。
#include <stdio.h>
int* test() {
int a = 10;
return &a;
}
void main() {
int* p = test();//出了作用域变量a销毁
*p = 20;
}
如何规避野指针
- 指针初始化(int* p=null)
- 小心指针越界(C语言本身不会检测数组越界行为)
- 指针指向的空间释放即时设置null
- 指针使用之前检查有效性(判断指针是否为null如果不为null则直接用)
指针运算
- 指针+-整数
- 指针-指针
- 指针的关系运算
1.指针+-整数
2.指针的关系运算
标准规定
#include <stdio.h>
void main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int* pend = arr + 9;
while (p<=pend)//指针关系运算
{
printf("%d\n", *p);
p++;//指针加减运算
}
}
3.指针-指针
#include <stdio.h>
void main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", &arr[9] - &arr[0]);
//结果为9
}
指针与指针相减前提:两个指针指向同一块空间
指针-指针得到的时指针之间的元素个数
理解:指针+9-指针=9
注意:指针+指针没什么用,别纠结了
指针和数组
数组名是什么?
#include <stdio.h>
void main() {
int arr[10] = { 0 };
printf("%p\n",arr);
printf("%p\n",&arr[0]);
//最终得到的结果一样,由此观之,数组名是首元素的地址
}
结果:数组名是数组首元素的地址
#include <stdio.h>
void main() {
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;//验证数组名是首元素地址
printf("%d\n", arr[2]);
printf("%d\n", p[2]);
printf("%d\n", 2[arr]);
printf("%d\n", 2[p]);
//arr[2] 等价于*(arr + 2),最后又有*(2+arr)等价于2[arr]
//最终结果都为3
}
arr[2]等价于2[arr]的理解
arr始终是个地址,2是个常数;arr[2] 等价于*(arr + 2)那么*(2+arr)等价于2[arr];其中只是交换了一下位置
整个数组的地址
#include <stdio.h>
void main() {
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%p\n", arr);
printf("%p\n", arr+1);
printf("%p\n", &arr);
printf("%p\n", &arr+1);
//最终得到的结果2行和4行不同——>得出结论arr为数组首元素的地址,&arr为整个数组的地址
}
结果:&arr为整个数组的地址
数组名和首元素地址关系
//1
#include <stdio.h>
#include <string.h>
void main() {
//首元素地址为数组名
char c[] = "hello";
printf("%d\n", strlen(c));//5
printf("%d\n", strlen(&c));//5——与上面不同这是首元素地址在传到strlen函数里面时被强转为char*类型
}
//2
#include <stdio.h>
#include <string.h>
void main() {
//首元素地址不为数组名
char* p = "hello";
printf("%d\n", strlen(p));//5
printf("%d\n", strlen(&p));//随机值
//由此观之,数组名是首元素的地址,但首元素的地址不一定是数组名
}
结果:数组名是首元素的地址,但首元素的地址不一定是数组名
二级指针
指针变量也是变量,变量就有地址,那么一级指针变量的地址存放的地方就称为二级指针
#include <stdio.h>
void main() {
int a = 10;
int* pa = &a;//pa是指针变量,一级指针
//这里的类型依据pa所对的类型int*——所以才出现后面的int**
int** ppa = &pa;//pa也是个变量,是变量就有地址,用ppa存放,ppa为二级指针
printf("%d\n", **ppa);//*ppa=pa,*pa=a,推出**ppa=a;
}
指针数组
定义:存放指针的数组
整形指针的数组:int* parr[5];
指针与const
const修饰变量,这个变量就称为常变量,不可更改,但是他本质还是一个变量
#include <stdio.h>
void main() {
const int num = 10;//加了const之后num不可更改
int* p = #
*p = 20;
printf("%d\n", num);
//但是会发现却可以以解引用的方式修改num为20
}
#include <stdio.h>
void main() {
const int num = 10;//加了const之后num不可更改
int a = 9;
int const * p = #//在*之前加了const则*p不可更改,但是p指向的内容却可更改
p = &a;
printf("%d\n", num);
}
#include <stdio.h>
void main() {
const int num = 10;//加了const之后num不可更改
int a = 9;
int const * const p = #//在*之前加了const则*p不可更改,在p前加const表示p的指向内容也不可更改
printf("%d\n", num);
}
总结:
const修饰指针变量的时候
- const如果放在*左边,则修饰的是*p,则*p不可更改(常量指针)
- const如果放在p的左边*的右边则修饰的是p,则p不可更改(指针常量)
- const如果既放*左边也放p的左边*右边,则修饰的是p与*p,则两者均不可更改
- 二级指针亦复如是const *pa与const **pa与const pa
常量指针:指针的指向可以修改,但是指针指向的值不可以修改
指针常量:指针指向的值可以更改,但指针的指向不可更改