初识指针1


前言

指针是C语言中是非常重要的一部分,而且内容还是挺多的,所以今天小编就来分享一下关于的指针的一小部分知识。
在这里插入图片描述


一、指针是什么?

在C语⾔中给地址起了新的名字叫:指针。意思就是说地址就是指针:地址 = 指针

二、指针变量和地址

1.取地址操作符(&)

在C语言中创建变量其实就是向内存申请空间,就像下面这串代码:

#include<stdio.h>
int main ()
{
   int a;
   return 0;
}

上述的代码表示的意思是:先创建了整形变量a,然后再向内存申请4个字节用于存放10,其中每个字节都有地址的。
那我们这里又要这样才能拿到这个地址呢?这里呀,咋们就要用到一操作符(&)——取地址操作符。那具体是怎样操作的呢?咋们往下看:

int main  ()
{
int a=10;
printf ("%p\n",&a);
return 0;
}

在这里插入图片描述
从这里可以看出直观的看出a的地址,是:0073FECC不过&a取出的是a所占4个字节中地址较⼩的字节的地址。虽然整型变量占⽤4个字节,我们只要知道了第⼀个字节地址,顺藤摸⽠访问到4个字节的数据也是可⾏的。

2.指针变量和 解引用操作符(*)

1.指针变量

我们通过取地址(&)可以得到一个数值,那为了后期的使用,这个数值也要储存起来,那储存在哪里呢?这里就要提到咋们的指针变量。

int main()
{
	int a = 10;
	int* p = &a;//把取出来的地址储存到指针变量p中
	return 0;
}

指针变量也是一种变量,这种变量是用来储存的地址的,存放在指针变量中的数值会理解成地址

了解完指针变量之后,咋们再来看看如何理解指针类型的吧:

int a=10;
int *p=&a;

这里p的左边写的是:int *, “ * ”是在说明p是指针变量,儿前面的 int 是在说明p指向的是整形(int)类型的对象。

在这里插入图片描述

2.解引用操作符(*)

我们讲数据储存起来是为了后期的使用的,那我们又是怎么样找到这个数据呢?这里又要引入一个操作符(*)——“解引用操作符
通过解引用操作符呢,当我们拿到一个地址(指针)时,就可以通过这个地址(指针)找到这个地址(指针)所指向的数值,从而达到取出数值的所用。
在这里插入图片描述

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	int a = 10; 
	int* p = &a;
	*p = 20;//这里是把a的值赋值20
	printf("%d\n", *p);
	return 0;
}

这个时候 * p已经是10了,那 * p=20就是一个赋值的过程,重新给*p赋值
在这里插入图片描述

三、指针变量类型的意义

指针变量的⼤⼩和类型⽆关,只要是指针变量,在同⼀个平台下⼤⼩都是⼀样的,为什么还要有各种各样的指针类型呢?
其实指针类型是有特殊意义的,我们接下来继续学习

//代码1
#include <stdio.h>
int main()
{
 int n = 0x11223344;
 int *p = &n; 
 *p = 0; 
 return 0;
}
//代码2
#include <stdio.h>
int main()
{
 int n = 0x11223344;
 char *p = (char *)&n;
 *p = 0;
 return 0;
}

调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第⼀个字节改为0。

结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)

⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字
2.void*指针
在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为 ⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进⾏指针的±整数和解引⽤的运算
比如看看这个:

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

这串代码将⼀个int类型的变量的地址赋值给⼀个char类型的指针变量。编译器给出了⼀个警告(如下图),是因为类型不兼容⽽使⽤void类型就不会有这样的问题

在这里插入图片描述

当使用void*类型的指针接收地址:

int main()
{
	int a = 10;
	void* p = &a;
	void* pc = &a;
	*p = 10;
	*pc = 0;
	return 0;
	return 0;
}

vs的编译结果:
在这里插入图片描述
这⾥我们可以看到,void* 类型的指针可以接收不同类型的地址,但是⽆法直接进⾏指针运算

那么 void* 类型的指针到底有什么⽤呢?
⼀般 void* 类型的指针是使⽤在函数参数的部分⽤来接收不同类型数据的地址这样的设计可以实现泛型编程的效果使得⼀个函数来处理多种类型的数据

四、const修饰指针

1.const修饰变量

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

#include <stdio.h>
int main()
{
 int m = 0;
 m = 20;//m是可以修改的
 const int n = 0;
 n = 20;//n是不能被修改的
 return 0;
}

上述代码中n是不能被修改的,其实n本质是变量,只不过被const修饰后,在语法上加了限制,只要我们在代码中对n就⾏修改,就不符合语法规则,就报错,致使没法直接修改n。

但是如果我们绕过n,使⽤n的地址,去修改n就能做到了,虽然这样做是在打破语法规则。

#include <stdio.h>
int main()
{
 const int n = 0;
 printf("n = %d\n", n);
 int*p = &n;
 *p = 20;
 printf("n = %d\n", n);
 return 0;
 }

在这里插入图片描述
我们可以看到这⾥⼀个确实修改了,但是我们还是要思考⼀下,为什么n要被const修饰呢?就是为了不能被修改,如果p拿到n的地址就能修改n,这样就打破了const的限制,这是不合理的,所以应该让p拿到n的地址也不能修改n,那接下来怎么做呢?

先让我们来看看这三种情况:
第一种当const放在 “ * ” 的左边时;

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

如果在vs编译器实现的话他会有下图这样的错误:
在这里插入图片描述
**第二种情况:**当const在 “ * ” 的右边时。

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

这个时侯呢他也会出现报错:
在这里插入图片描述
第三种当 “ * ” 两边都有const时

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

那他又有声明错误呢?
在这里插入图片描述
结论:
•const如果放在的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本⾝的内容可变。
• const如果放在
的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。
• const如果放在*的左右两边,这个时候指针指向的内容和指针变量的内容不能修改

五、指针的运算

1.指针±整数

比如用数组来演示一遍吧,因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素。所以

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

像这样我们就很容易的访问每一个元素了。

2.指针 - 指针

我们来算一下字符串的长度:

#include<stdio.h>
int my_strlen(char*p)
{
	char* str =p ;
	while (*str != '\0')
	{
		str++;
	}
	return str-p;//指针-指针
}
int main()
{
	int len = my_strlen("abcdef");
	printf("%d\n", len);
	return 0;
}

向这样我们就可以计算出字符串的长度了

3.指针的大小比较

#include <stdio.h>
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];
 int i = 0;
 int sz = sizeof(arr)/sizeof(arr[0]);
 while(p<arr+sz) //指针的⼤⼩⽐较
 {
 printf("%d ", *p);
 p++;
 }
 return 0;
}

当我们运行这串代码时,也可以发现可以访问数组的每个元素的。

六、野指针

1.野指针的成因

#include<stdio.h>
int main()
{
	//代码1(指针未初始化)
	int* p;
	*p = 20;//局部指针未初始化,默认为是随机值
	return 0;
}
	int main ()
{
	//代码2(指针越界访问)
	int arr[10] = { 0 };
	int* p = &arr[0];
	for (int i = 0; i <= 11; i++)
	{
		*p = i;
		p++;
	}
	return 0;
}
//代码3(指针指向的空间释放)
int test()
{
	int n = 100;
	return &n;
}
int main()
{
	int* p = test();
	printf("%d\n", *p);
	return 0;
}

2.如何避免野指针

了解完指针的成因之后,我们在来了解了解任何避免野指针吧。

1.指针初始化
如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL.
NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错。

就像这样:

#include <stdio.h>
int main()
{
 int num = 10;
 int*p1 = &num;
 int*p2 = NULL;
 
 return 0;
}

2.⼩⼼指针越界
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。
3.指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性

当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问,同时使⽤指针之前可以判断指针是否为NULL

我们可以把野指针想象成野狗,野狗放任不管是⾮常危险的,所以我们可以找⼀棵树把野狗拴起来,就相对安全了,给指针变量及时赋值为NULL,其实就类似把野狗栓前来,就是把野指针暂时管理起来。

不过野狗即使拴起来我们也要绕着⾛,不能去挑逗野狗,有点危险;对于指针也是,在使⽤之前,我们也要判断是否为NULL,看看是不是被拴起来起来的野狗,如果是不能直接使⽤,如果不是我们再去使⽤

int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];
 for(i=0; i<=11; i++)
 {
 *(p++) = i;
 }
 //此时p已经越界了,可以把p置为NULL
 p = NULL;
 //下次使⽤的时候,判断p不为NULL的时候再使⽤
 //...
 p = &arr[0];//重新让p获得地址
 if(p != NULL) //判断
 {
 //...
 }
 return 0;
}

4.避免返回局部变量的地址

这里说的是指针的成因的代3的形式,不要返回局部变量的地址


总结

努力就会有收获,加油!向美好人生进军!

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值