文章目录
前言
指针是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 = #
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的形式,不要返回局部变量的地址
总结
努力就会有收获,加油!向美好人生进军!