当前位置:首页 » 《随便一记》 » 正文

C语言:大战指针“哥斯拉”(1)

25 人参与  2024年02月07日 14:41  分类 : 《随便一记》  评论

点击全文阅读


目录

一、前言

二、指针

(一)指针定义

(二)指针变量

(三)void*指针

三、指针类型和意义

(一)指针的类型

 (二)指针类型的意义

四、const修饰指针

(一)const修饰变量

(二)const修饰指针变量

五、指针的基本运算

(一)指针+-整数

(二)指针-指针   

(三)指针的关系运算

六、野指针

(一)野指针成因

1.指针未初始化

2.数组越界访问

3.指针指向空间释放

(二)规避野指针的方法

1.指针初始化

2.小心指针越界 

3.指针指向空间释放后,要及时置NULL

七、总结


一、前言

相信每个人开始学C语言的时候,指针是最令人头疼的部分,今天蜡笔小欣带大家一起来大战指针“哥斯拉”,让你对指针有初步的了解。

二、指针

(一)指针定义

指针是内存中一个最小单元的编号,也就是地址,通过它能找到以它为地址的内存单元。简单来说指针就相当于一个门牌号,里面存的的是住户的编号。

(二)指针变量

我们平时口头说的指针,通常指的是指针变量,它是用来存放内存地址的变量。当你定义一个变量的时候,实际上是向内存申请了一块空间来存放你的变量。我们都知道 int 类型占 4 个字节,在计算机中数字都是用补码表示的。

int a = 666;

例如:666在计算机中换算成补码为:0000 0010 1001 1010
这里有 4 个byte,因此需要用四个单元格来存储。

如果我们想知道这个变量一开始存储的地址,就可以通过运算符&来取得变量实际的地址,这个值就是变量所占内存块的初始地址。

printf("%x",&a);

运行之后就会发现打印出来一串数字f3cffca4,这个就是定义整型变量a的初始地址。

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>int main(){int a = 666;//在内存中开辟一块空间int* pa = &a;//对变量a,取出它的地址,使用&操作符//a变量占用4个字节的空间,将a的4个字节中的第一个字节的地址存放在pa变量中,//pa就是一个之指针变量。return 0;}

如上面代码所示,pa中存储的是a变量的内存地址,那我们该如何通过地址去获取a的值呢?

我们需要通过解引用的操作,在 C 语言中通过 * 就可以找到一个指针所指向地址的内容了。

 简单来说pa里面是用户的门牌号(地址),而 *pa是通过这个地址找到了里面的住户(内容)。

(三)void*指针

void*类型的指针我们可以理解为没有具体类型的指针,它可以用来接受任何类型的地址,但无法直接进行指针的+-整数和解引用的运算。

int main(){int a = 6;char b = "bit";void* p1 = &a;//int*void* p2 = &b;//char**p1 = 20;//err void*类型的指针不能直接进行解引用操作p1++;//err void*类型的指针也不能进行+-整数操作return 0;}

三、指针类型和意义

(一)指针的类型

我们前面学过变量有许多类型,比如整型,浮点型等,指针变量也不例外,它有许多类型。

char* pa = NULL;
int* pb = NULL;
short* pc = NULL; 
long* pd = NULL;
float* pe = NULL;
double* pf = NULL;

从上面我们可以看出,指针的定义方式是:type + *。

char*类型的指针是为了存放char类型变量的地址。

int*类型的指针是为了存放int类型变量的地址。

short*类型的指针是为了存放short类型变量的地址。

long*类型的指针是为了存放long类型变量的地址。

float*类型的指针是为了存放float类型变量的地址。

double*类型的指针是为了存放double类型变量的地址。

 (二)指针类型的意义

我们一起运行下面的代码:

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>int main(){int x = 5;int* pa = &x;char* pb = (char*)&x;//将int*强转为char*printf("%p\n", &x);printf("%p\n", pa);printf("%p\n", pa + 1);printf("%p\n", pb);printf("%p\n", pb + 1);return 0;}

运行结果: 

我们发现 &x、pa、pb所得到的地址相同,因为&x 本来就是取x的地址,而pa、pb 是指针,保存了x的地址。与此同时你们是否有疑问:上面定义的指针变量明明一个是int类型,一个是char类型,为什么地址会一样呢?

为什么pa+1和pb+1的地址不同呢?

这就和指针的类型有关了。char* 类型的指针变量+1是跳过1个字节, 而int* 类型的指针变量+1就不一样,它是跳过4个字节。因此我们可以得出一个结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。

四、const修饰指针

(一)const修饰变量

const修饰的变量是不能被修改的。

const int a = 10;//a不能被修改了,但是a的本质还是变量,const只是在语法上做了限制,我们习惯上叫a为常变量a = 20;

(二)const修饰指针变量

const修饰指针变量可以分为以下两种情况。

第一种情况:

int main(){const int a = 5;int const* p = &a;//const限制的是*p*p = 10;//errprintf("a=%d", a);return 0;}

const放在 * 的左边,限的是*p,表示不能通过指针变量p去修改p指向的空间的内容

*p = 10; //err

但是p是没有受限制的

p=&b;//ok

 第二种情况:

int main(){const int a = 5;int* const p = &a;//const限制的是p*p = 10;//okprintf("a=%d", a);return 0;}

const放在 * 的右边,限制的是p变量,它不能被修改,无法再指向其他的变量

p = &b;//err

但是*p是没有限制的,可以通过p修改p所指向的对象的内容

*p = 10;//ok 

五、指针的基本运算

(一)指针+-整数

结合前面所学的知识我们知道数组是连续存放的,地址由低到高,我们只要知道第一个元素的地址,就能找到该数组其他元素的地址,下面举个栗子:

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

数组12345678910
下标0123456789

#define _CRT_SECURE_NO_WARNINGS#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]);for (i = 0; i < sz; i++){printf("%d ", *(p + i));//p+i加的是i*sizeof(int)    //通过指针+-整数来找到数组后面的其他元素}return 0;}

 打印结果:1 2 3 4 5 6 7 8 9 10

(二)指针-指针   

指针-指针其实就是地址-地址,在两个指针指向同一块空间的前提下,指针-指针的绝对值是两个指针之间的元素个数。

我们利用指针-指针来写一个my_strlen函数来求字符串长度。

代码如下所示:

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

运行结果如下 :

(三)指针的关系运算

指针的关系运算其实就是指针比较大小,也是地址比较大小,举个栗子:打印数组的内容

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>int main(){int arr[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);//使用while循环打印arr的内容int* p = &arr[0];//arr是数组名,数组名就是数组首元素的地址,arr<==>&arr[0]while (p < arr + sz){printf("%d ", *p);p++;}return 0;}

运行结果: 1 2 3 4 5 6 7 8 9 10

通过指针的关系运算,我们可以更加方便地打印数组的内容。

六、野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。

(一)野指针成因

1.指针未初始化
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>int main(){int* p; //指针变量未初始化,系统默认为随机值*p = 10;return 0;}

局部变量p没有初始化,变量的值是随机的,无法通过p找到相应的空间地址,就变成野指针。

2.数组越界访问
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>int main(){int arr[5] = { 0 };int* p = arr;int i = 0;for (i = 0; i < 10; i++){*p = 1;p++;//指针指向的范围超出数组arr的范围,p就变成野指针}return 0;}

运行结果: 

由于数组arr定义有5个元素,对这5个元素(下标为0 到4的元素)的访问都合法,如果对这5个元素之外的访问,就是非法的,导致数组越界访问,也会造成p变为野指针。

3.指针指向空间释放
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>int test(){int n = 6;return &n;}int main(){int* p = test();printf("%d", *p);return 0;}

我们在函数中定义的变量是临时变量,只要出了这个函数的作用域就会自动销毁。销毁后系统没办法访问这个空间地址,但我们通过指针还能在内存里找到这个空间,这就会非法访问,造成野指针。

(二)规避野指针的方法

1.指针初始化
#include <stdio.h>int main(){//第一种情况int a = 6;int* p = &a;//明确p应该指向a,把a的地址初始化//第二种情况    //不知道给指针初始化谁的地址,直接用空指针初始化int* p = NULL;return 0;}
2.小心指针越界 

明确一个程序向内存申请了哪些空间,使用指针访问空间时不能超出访问范围,超出了就会造成越界访问。

3.指针指向空间释放后,要及时置NULL

我们在平时编程时,对空指针很容易检测(if(p==NULL)),但是对于非法指针p不为空,我们是无法检测到的。防止对一个已经释放的指针多次释放造成程序崩溃,但是对一个NULL指针多次释放是合法的。因此我们在指针指向空间释放后,要及时置NULL。

七、总结

通过上面对指针的初步学习,相信大家已经掌握了战胜指针“哥斯拉”的第一招,后面再和蜡笔小欣一起学习战胜指针“哥斯拉”的其他招式,我们下期再见!


点击全文阅读


本文链接:http://zhangshiyu.com/post/67713.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1