目录

一、指针是什么?

1.1 指针

1.2 指针变量

1.2.1 指针变量是什么?

1.2.2 指针变量的大小

二、指针和指针类型

2.1 指针类型的意义

2.1.1 指针类型决定了访问空间的能力

2.1.2 指针类型决定了+1时跳过几个字节

三、野指针

3.1 野指针的成因

3.1.1 指针没有初始化

3.1.2 指针越界访问

3.1.3 指针指向的空间释放

3.2 如何规避野指针

四、指针运算

4.1 指针 +- 整数

4.2 指针 - 指针

4.3 指针的关系运算

五、指针与数组

六、二级指针

七、指针数组

7.1 存储一组相同类型元素的地址

7.2 用一维数组模拟一个二维数组

一、指针是什么?

1.1 指针

指针理解的两个要点:

  1. 指针是内存中一个最小单元的编号,也就是地址。
  2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。

要理解指针,我们就要先理解内存:内存是一块存储区域,为了更大化的利用空间,我们把内存划分为一个个很小的内存单元,我们会给每一个内存单元编号,这个编号也就是地址即指针。

1.2 指针变量

1.2.1 指针变量是什么?

我们可以通过&(取地址操作符)取出变量的内存起始地址,把地址可以存放在一个变量中,这个变量就是指针变量。

创建变量的本质就是向内存中申请一块空间。

我们接下来来看一段代码:

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

创建了一个int类型的变量,在内存空间里面会开辟四个字节的空间,创建一个char类型的变量,在内存空间里面会开辟一个一个字节的空间。

总的来说:指针变量就是用来存放地址的变量,在上述代码中pa存储a的地址,pa就是一个指针变量,我们需要注意的是:存放在指针中的值都被当成地址处理。

1.2.2 指针变量的大小

要知道指针变量的大小,我们就要知道在内存中是如何编址的。

对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0)。

那么32根地址线产生的地址就会是:
00000000000000000000000000000000

00000000000000000000000000000001

......

1111111111111111111111111111111111111

这里就会有2的 32次方个地址。

每个地址标识一个字节,那么我们就可以给(2^32byte == 2^32/1024KB ==2^32/1024/1024MB == 2^32/1024/1024/1024GB == 4GB)4G的空间进行编址。

同样的方法,如果是64位的机器上,有64个地址线,也可以计算出能够给多大的空间进行编址。

  • 在32位的机器上,地址是32个0或者1组成的二进制序列,即32个bite位,需要4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
  • 在64位的机器上,地址是64个0或者1组成的二进制序列,即64个bite位,需要8个字节的空间来存储,所以一个指针变量的大小就应该是8个字节。

总结:

  1. 指针变量是用来存放地址的,地址是唯一标识一个内存单元的。
  2. 指针的大小在32位平台是4个字节,在64位平台是8个字节。

二、指针和指针类型

变量有不同的类型,整型,浮点型等,那么指针有没有不同的类型呢?答案是有。

指针的类型是怎么定义的呢?

我们想要创建一个int类型的变量a,想要存储a的地址,这时我们就需要一个指针来存储a的地址,这个指针变量是什么类型的呢?

存储int类型的变量a的地址,指针类型是int*类型。

同理,如果我们要存储一个char类型的变量b的地址,这时我们就可以写成:char* pb = &b; 

在讲解指针变量的大小的时候,我们已经说明指针变量在32位的平台上占4个字节,在64位的平台上占8个字节,既然他们在内存中所占的字节大小相同,那么设置不同指针类型的意义在哪里?有以下两个意义。

2.1 指针类型的意义

2.1.1 指针类型决定了访问空间的能力

int main()
{int a = 0x11223344;int* pa = &a;*pa = 0;
}

我们可以看出在pa(存储a的地址的变量)的类型是int*时,在对pa解引用操作时,我们会访问4个字节的空间。

int main()
{int a = 0x11223344;char* pa = &a;*pa = 0;
}

如果使用char*类型的指针pa来存储int类型的a的地址,在对pa进行解引用的时候只能访问一个字节的空间。

我们发现int*    变为    char*  类型时,指针的操作能力就变化了。

  指针类型决定了访问空间的能力,决定了指针解引用操作时,访问几个字节。

char *          类型的指针解引用访问一个字节的空间

int *             类型的指针解引用访问四个字节的空间

double *      类型的指针解引用访问八个字节的空间

2.1.2 指针类型决定了+1时跳过几个字节

指针类型决定了+1时跳过几个字节,即指针类型决定了指针的步长(向前或向后走一步都多大的距离)。

int* 类型的指针+1,意思是跳过一个整形,也就是4个字节。

char* 类型的指针+1,意思是跳过一个字符,也就是1个字节。

double* 类型的指针+1,意思是跳过一个double类型,也就是8个字节。

三、野指针

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

即指针指向的空间不是自己的。

3.1 野指针的成因

3.1.1 指针没有初始化

在c语言中,局部变量都是在栈区创建的,如果局部变量没有初始化,会默认为随机值。

下面我们来举一个野指针的例子:

int main()
{int* pa;    //局部变量指针未初始化,默认为随机值,而这个随机值会被当做地址*pa = 20;return 0;
}

在上述的代码中pa就是一个野指针,pa属于局部变量,局部变量没有初始化,就会默认为随机值,而默认的这个随机值会被当做一个地址,这个地址所维护的空间不属于我们当前的程序,会出现问题。

3.1.2 指针越界访问

int main()
{int arr[10] = { 0 };int* p = arr;int i = 0;for (i = 0; i <= 11; i++){*(p++) = i;}return 0;
}

上述代码中数组arr的下标应该是从0-9,而在访问的时候数组访问了0-11的下标,属于越界行为,当指针指向的范围超出数组arr的范围时,p就是野指针。

3.1.3 指针指向的空间释放

int* text()
{int num = 100;return &num;
}
int main()
{int* p = text();*p = 200;return 0;
}

在上述代码中,p就是野指针。

3.2 如何规避野指针

  1. 指针初始化

    int main()
    {int a = 0;int** pa = &a;   //明确初始化int b = 0;int* pb = NULL;   //当不知道pb赋什么值时,用空指针NULL初始化return 0;
    }
  2. 小心指针越界

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

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

  5. 指针使用之前检查有效性

    int main()
    {int* p = NULL;if (p != NULL){printf("%d\n", *p);  }return 0;
    }

    检查是否为空指针。

四、指针运算

4.1 指针 +- 整数

我们已经解释过指针+1的情况,同理int *类型的指针+-n就是跳过n个4个字节的空间。

int main()
{double arr[5] = { 0 };double* p = arr;int i = 0;for (i = 0; i < 5; i++){printf("%lf", *(p + i));}return 0;
}

上述代码中p+1就相当于跳过一个double类型的空间,即8个字节的空间。

4.2 指针 - 指针

指针-指针的前提条件是,指针指向的是同一块连续的空间。

指针-指针的结果是什么呢?

我们用代码来观察一下:

int main()
{int arr[10] = { 0 };printf("%d\n", &arr[9] - &arr[0]);printf("%d\n", &arr[0] - &arr[9]);return 0;
}

我们发现指针-指针得到的(绝对值)是指针与指针之间元素的个数。

在这里,我们可以想到strlen函数实现的另一种方式:

int my_strlen(char* p)
{char* str = p;while (*p != '\0'){p++;}return p - str;
}int main()
{char arr[] = "abcde";int ret = my_strlen(arr);printf("%d\n", ret);return 0;
}

4.3 指针的关系运算

#define N_VALUES 5
float values[N_VALUES];
float* vp;int main()
{for (vp = &values[N_VALUES]; vp > &values[0];){*--vp = 0;}return 0;
}

将代码简化:

#define N_VALUES 5
float values[N_VALUES];
float* vp;int main()
{for (vp = &values[N_VALUES]; vp >= &values[0];vp--){*vp = 0;}return 0;
}

在大多数的编译器中是可以完成任务的,但是我们应该避免这样写,因为标准并不保证它可行。

标准规定:

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

五、指针与数组

首先我们需要知道:

  1. 数组和指针不是一个东西,数组能够存放一组相同类型的数,连续的空间,数组的大小取决于元素的个数,指针是一个变量,是用来存放地址的变量。
  2. 数组与指针的联系:数组名就是数组首元素的地址,交给一个指针变量后,可以通过指针来访问数组。

在接下来的代码中,我们使用指针来访问数组的每一个元素:


int main()
{ int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){printf("%d ", *p++);}return 0;
}

六、二级指针

指针变量也是变量,是变量就会有地址,那指针变量的地址存放在哪里?

这就是二级指针。

int main()
{int a = 0;int* p = &a;      //p就是指针变量,一级指针变量int** pp = &p;        //pp就是二级指针return 0;
}

对于二级指针的运算有:

  • *pp通过对pp中的地址解引用,找到的是p,*pp访问的就是p。
  • **pp先通过*pp找到p,再对p解引用操作,**pp找到的就是a。

七、指针数组

指针数组是存放指针的数组。

指针数组有什么用法:

7.1 存储一组相同类型元素的地址

int main()
{int a = 0;int b = 10;int c = 20;int d = 30;int e = 38;int* arr[] = { &a,&b,&c,&d,&e };int i = 0;for (i = 0; i < 5; i++){printf("%d ", *(arr[i]));}return 0;
}

7.2 用一维数组模拟一个二维数组

#include<stdio.h>
int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };int arr4[] = { 4,5,6,7,8 };int* arr[5] = { &arr1,&arr2,&arr3,&arr4 };int i = 0;for (i = 0; i < 4; i++){int j = 0;for (j = 0; j < 5; j++){printf("%d ", arr[i][j]);//printf("%d ",*(*(arr+i)+j));}printf("\n");}return 0;
}

初阶指针(保姆级教程)相关推荐

  1. 【数据结构】顺序表实现超详解(保姆级教程)

    [数据结构] 目录 ​ 前言 顺序表 接口实现 各项功能 接口详解 顺序表初始化 顺序表释放 顺序表展示 顺序表容量检查 顺序表数据尾插 顺序表数据头插 顺序表数据前删 顺序表数据尾删 顺序表数据查找 ...

  2. Mathematica保姆级教程

    Mathematica保姆级教程 特殊符号 内置函数与自定义函数 输出精度 绘图 解方程与微积分 大杂烩 在观看此篇前可先观看:Mathematica最简教程 特殊符号 添加注释:1+2 (*输入注释 ...

  3. 数仓建设保姆级教程,离线和实时一网打尽(理论+实战)

    本文大纲: 因内容较多,带目录的PDF查看是比较方便的,点击下方链接获取完整PDF版: 数仓建设保姆级教程PDF文档 一.数仓基本概念 1. 数据仓库架构 我们在谈数仓之前,为了让大家有直观的认识,先 ...

  4. ROS入门保姆级教程:5-ROS计算图

    ROS入门往期: ROS入门保姆级教程:1-hello world初体验 ROS入门保姆级教程:2-VScode中使用ROS ROS入门保姆级教程:3-ROS文件系统 ROS入门保姆级教程:4-ROS ...

  5. 满满干货!红书运营保姆级教程

    **满满干货!红书运营保姆级教程** 1. 同类三种媒体发布平台的优劣势 今日头条: 今日头条的用户基础雄厚,活跃的用户比较多,所能带来的流量是比较大的,这也就导致了今日头条的推荐量雄厚,是不可多得的 ...

  6. 判断是不是链接 正则_Python 正则表达式 保姆级教程,小学生都看得懂!!

    ~点击 蓝字 关注,获取更多资源~ 0 前言 上一篇文章,2020,还不会正则???,和小伙伴们一起学习了 Python 中的正则表达式,读完之后,总感觉少了点什么东西,无法尽兴?就好像爱你们的心少了 ...

  7. 参考文献顺序不对_Endnote插入参考文献的保姆级教程

    作者:白鹤(baihe@iphy.ac.cn) 审核:蔡建旺 各位同学看过来,最近由于疫情严重,小伙伴们都憋在家里写论文.这倒不是什么难事,最烦人的是参考文献的插入.一篇博士论文下来,参考论文不说30 ...

  8. SpringBoot 配置 generator代码生成+knife4j接口文档(2种模板设置、逻辑删除、字段填充 含代码粘贴可用)保姆级教程(注意事项+建表SQL+代码生成类封装+测试类)

    保姆级教程,逻辑删除及字段自动填充设置,特别要说明的是本次用的是MySQL数据库,如果使用Oracle数据库是,数据库配置需要改变,数据库表一定要大写,否则无法生成代码. 数据库表 CREATE TA ...

  9. 【NLP保姆级教程】手把手带你RNN文本分类(附代码)

    写在前面 这是NLP保姆级教程的第二篇----基于RNN的文本分类实现(Text RNN) 参考的的论文是来自2016年复旦大学IJCAI上的发表的关于循环神经网络在多任务文本分类上的应用:Recur ...

  10. 图片适应窗口_毕业论文排版保姆级教程——图片和公式排版

    [小技巧]Origin作图过程中如何让图看起来更生动 2020-05-30 [小技巧]简单设置让你origin导出的图片不在有大白边 2020-05-29 [干货放送]萤火科研资源免费赠送第一期--必 ...

最新文章

  1. 深入浅出PHP(Exploring PHP)
  2. mysql 批量增加字段命令_sql使用命令批量给一个表添加字段
  3. 美团在Redis上踩过的一些坑-2.bgrewriteaof问题
  4. .NET下数据访问层+webform前台 技术大比拼
  5. Springboot05整合FastJson优化输出
  6. 一亿人民币是什么概念?
  7. python机器学习库sklearn——数据预处理
  8. 近世代数 [计算机数学专题(3)]
  9. 微信小程序 地图map的坑
  10. 转:最详细的JavaScript 教程,入门级都能看懂
  11. Android/Linux Kernel 内存管理-入门笔记
  12. 诺基亚6300手机游戏下载_回忆杀!重温那些年你一定玩过的经典手机游戏,快看你玩过几个?...
  13. 可折叠的table表格
  14. Springcloud之OAuth2
  15. 计算机无法连接富士网络打印机,网络打印机无法连接的解决方法是什么
  16. 无线串口服务器设置,怎么使用无线串口服务器
  17. matlab ncut谱聚类,NCUT 归一化分割、谱聚类之代码调试问题
  18. 普安特友情提示:这样去抱猫,可能会让它反感
  19. 学人工智能电脑主机八大件配置选择指南
  20. jqurey的选择器使用

热门文章

  1. 离开字节跳动的年轻人,不是所有人都适合“字节范儿”
  2. Python int()的有趣用法
  3. 如何用大数据管理城市?
  4. 图像分割(七) —— Transformer and CNN Hybrid Deep Neural Network
  5. ApachePOI操作Excel快速入门使用
  6. 培养人脉的100个技巧
  7. 第二证券|汽车板块现涨停潮!多只地产债再涨到临停
  8. 创建一个动物类,具有name.age.sex,从键盘输入动物的信息的同时使用io流保存这些信息到d:\\1.txt,并且将这些信息存放入动物的对象,存放入一个TreeSet集合(要求根据年龄进行排序)
  9. good things
  10. MySQL-05-锁