快速理解指针原理!!!

在这里插入图片描述

创作不易,希望各位大佬多多支持~

学习不易,希望大家保持乐观的心态哦~

我的主页:optimistic_chen
我的专栏:c语言


前言

经过之前一段时间的学习,我们终于来到C语言的第一座大山——指针
让我们一起来拿下它!!!
注:为了更好的连接之前的知识,欢迎大家访问:
浅浅理解C语言之数组大佬
揭示C语言操作符之神秘

一、指针是什么?

1.概念

我们知道计算上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,为了高效准确的读取到数据,把内存划分为⼀个个的内存单元,每个内存单元也都有⼀个编号(这个编号就相当
于宿舍房间的⻔牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间,从而读取数据。这些编号我们就将其称为地址或者指针
内存单元的编号 == 地址 == 指针

2.相关操作符(&、*)

&- - 取地址操作符,*- -解引用操作符,在揭示C语言操作符之神秘中都有过学习。还记得,他们属于一对“反义词”。
让我们用举例做到更好的理解:

由图可知:

int a=1;//&a为0x00000001
int b=2;//&b为0x00000002
int c=3;//&c为0x00000003

使用指针变量:

int*pa=&a;//把a的地址存起来
int*pb=&b;//把b的地址存起来
int*pc=&c;//把c的地址存起来

我们可以通过门牌号准确的找到对应同学,同理,我们也可以通过地址准确的找到对应变量。

	printf("a=%db=%dc=%d", *pa, *pb, *pc);//通过*解引用地址找到对应的值

结果:

int a=1;
int b=2;
int c=3;
int*pa=&a;
int*pb=&b;
int*pc=&c;
printf("a=%db=%dc=%d", *pa, *pb, *pc);//a=1 b=2 c=3

3.指针变量


因为数据在内存中的地址称为指针,这个地址(指针)就存放在指针变量中,也就是说如果一个变量存储了一份数据的地址(指针),我们就称它为指针变量。

如何使用指针变量?

tybe*name
变量类型 * 变量名称

例如:

int *arr;//指向一个整形的指针
float *a;//指向一个单精度浮点数的指针
double *p1;//指向一个双精度浮点数的指针

同时,我们可以根据需求改变变量的赋值

*pa=4;
*pb=5;
*pc=6;
printf("a=%db=%dc=%d", *pa, *pb, *pc);//a=4 b=5 c=6

注:大家在写代码时小心使用指针的变量类型哦,否则会出现警告!!!

4.void*指针

在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为⽆具体类型的指针(或者叫泛型指
针),这种类型的指针可以⽤来接受任意类型地址。
也就是说:*任意类型的指针可以对 void 指针赋值

void*a;
int*b;
a=b;//这是被允许的

但是不能把void*指针赋值给任意指针类型,当然也不能直接对其解引用

void*a;
int*b;
b=a;//这是不被允许的
*a;//不能直接对void*解引用

5.const修饰

5.1const修饰变量

变量是可以修改的,但是如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作⽤。

#include <stdio.h>
int main()
{
int a = 0;
a = 20; //a是可以修改的
const int b = 0;
b = 20; //b是不能被修改的
printf("%d%d",a,b);
return 0;
}


显然,加上const后,编译器报错,所以变量不可被修改。

5.2const修饰指针变量

如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。
这样就打破了const的限制,这是不合理的,那接下来怎么做呢?

测试⽆const修饰的情况

int main()
{
    int a = 10;
    int b = 20;
    int *p = &a;
    *p = 20; 
    p = &b; 
    printf("%d%d",a,b);//a=20 b=20
}

测试const放在*的左边情况

int main()
{
    int a = 10;
    int b = 20;
    const int *p = &a;
    *p = 20; 
    p = &b; 
    printf("%d%d",a,b);//*p报错
}

测试const放在*的右边情况

int main()
{
    int a = 10;
    int b = 20;
    int *const p = &a;
    *p = 20; 
    p = &b; 
    printf("%d%d",a,b);//p报错
}

测试*的左右两边都有const

int main()
{
    int a = 10;
    int b = 20;
    const int *const p = &a;
    *p = 20; 
    p = &b; 
    printf("%d%d",a,b);//p和*p都报错
}

结论:

const如果放在的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本⾝的内容可变。

const如果放在的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

二、指针运算

1.指针变量的大小

我们知道一个字节是8个bit位
那么在32位机器上,⼀个地址就是32个bit位,需要4个字节才能存储。
如果指针变量是⽤来存放地址的,那么指针变量的⼤⼩就得是4个字节的空间才可以。

同理,在64位机器,⼀个地址就是64个bit位,存储起来就需要8个字节的空间,指针变量的⼤⼩就是8个字节。

结论:

• 32位平台下地址是32个bit位,指针变量⼤⼩是4个字节
• 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节
• 注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。

2.指针±整数

代码示例:

#include <stdio.h>
int main()
{
	int a = 10;
	char arr[10] = "abcd";

	int *p1 = &a;
	char* p2 = &arr;

	printf("&a=%p\n", &a);
	printf("p1=%p\n", p1);
	printf("p1+1=%p\n", p1+1);
	printf("p2=%p\n", p2);
	printf("p2+1%p\n", p2+1);
	
	return 0;
}

代码运行结果如下:

我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
这就是指针变量的类型差异带来的变化。
结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)

例题:通过指针访问数组每个元素

首先我们知道数组在内存中是连续存储的(地址由低到高),其次一维数组的数组名就是首元素地址,所以我们只要首元素的地址就能找到数组所有元素的地址。

#include <stdio.h>

int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};

int *p = arr;//&arr[0]==arr
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);//数组元素个数

for(i=0; i<sz; i++)
{
    printf("%d ", *(p+i)); //p+i 这⾥就是指针+整数
}
return 0;
}

3.指针-指针

指针-指针其实是指在同一空间内,两个指针之间的元素个数

简单理解就是一个字符串的元素个数:

#include<stdio.h>
int my_strlen(char* a)
{
	char* p = a;//数组名就是首元素地址
	while (*p != '\0')
	{
		p++;
	}
	return p - a;
}
int main()
{
	char a[10] = "abcd";
	int len = my_strlen(a);
	printf("%d", len);
	return 0;
}

代码结果:
在这里插入图片描述

三、野指针

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

1.野指针的成因

1.1指针未初始化

int *p; //局部变量指针未初始化,默认为随机值
*p = 20;

1.2指针越界访问

    int arr[10] = { 0 };
	for (int i = 0; i < 11; i++)
	{
		//数组下标是0到9
		printf("%d ", *(arr + i));//当指针指向的范围超出数组arr的范围时,p就是野指针
	}

1.3指针指向的空间释放

首先我们要了解变量在不同位置的意义,学习函数时,我们知道在函数定义中的变量属于临时变量,离开函数定义的作用域后,系统为了提高代码效率就会销毁存放变量的空间。一旦销毁,系统就无法访问该空间,而通过指针我们还可以访问该空间,这就造成了冲突,所以出错,造成野指针。

#include <stdio.h>
int* test()
{
	int n = 100;
	return &n;//返回n的地址
}
int main()
{
	int* p = test();//用p接受n的地址
	printf("%d\n", *p);//打印出n的值
	return 0;
}

2.解决方法

针对造成野指针的问题来解决

2.1指针初始化

int *p=NULL; 
*p = 20;

2.2小心指针越界

⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是
越界访问。

2.3避免返回局部变量的地址

如造成1.3中的例子

四、assert断言

assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报
错终⽌运⾏。这个宏常常被称为“断⾔”。
assert()宏接受⼀个表达式作为参数。

如果该表达式为真(返回值⾮零), assert() 不会产⽣任何作⽤,程序继续运⾏。
如果该表达式为假(返回值为零), assert() 就会报错,在标准错误流 stderr 中写⼊⼀条错误信息,显⽰没有通过的表达式,以及包含这个表达式的⽂件名和⾏号。

代码示例:

int *p=NULL;
assert(p);//空指针是0,0为假,就会报错

如果已经确认程序没有问题,不需要再做断⾔,就在 #include <assert.h> 语句的前⾯,定义⼀个宏 NDEBUG 。

#define NDEBUG
#include <assert.h>

缺点就是因为引⼊了额外的检查,增加了程序的运⾏时间。

完结

指针学习尚未结束,尽情期待,下期更精彩哦~~~


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值