C语言之指针初级

目录

指针是什么

指针的原理

计算机中的地址总线

总结

关于指针 

指针的简单操作

指针和指针类型

指针的大小

指针类型的意义

空指针

野指针

野指针的成因

如何规避野指针

指针运算

指针和数组

数组名是什么?

整个数组的地址

数组名和首元素地址关系

二级指针

指针数组

指针与const

指针是什么

指针:在计算机科学中,指针是编程语言中的一个对象,利用地址,他的值直接指向存在电脑储存器中的另一个地方的值。由于通过地址能找到所需的变量单元,可以说地址指向变量单元。因此将地址形象化的成为指针。意思是通过他能找到以他为地址的内存单元

作用:可以通过指针来间接的访问内存

前言:

  • 指针的内存编号是从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 = &num;
	*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 = &num;//在*之前加了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 = &num;//在*之前加了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

常量指针:指针的指向可以修改,但是指针指向的值不可以修改

指针常量:指针指向的值可以更改,但指针的指向不可更改

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值