IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 初入C语言_7 -> 正文阅读

[C++知识库]初入C语言_7

  1. 指针

今天我们会进入许多学习者C语言最重要的一个板块——指针,许多老师在学生懂了一些基础知识后才会引入这个概念,因为它必须要有前面的一些铺垫,就好像音乐颁奖典礼上总是压轴登场的一些歌星。它涉及的方面很广,在C语言许多方面都有它的身影......好吧我不想扯太多没用的,一会儿我还要有个会要开,最近一直在日更没发现吗忠实的粉丝们。

1.什么是指针?

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向 (points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以 说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址 的内存单元。

可以这样理解:

?指针也个变量,同整形浮点型字符型一样,指针也可以用这些类型来定义,但是指针变量所存储的是地址,并不是某些数据或者字符,用十六进制表示形式表示出来的地址就是指针存入的玩意,我们知道每个内存空间都有地址,就像是居民身份证一样,出生的孩子如果想在这个社会生存,父母必须要给孩子办身份证,身份证号码就是你的编号,也可以理解为地址,指针存入的就是这个编号,通过这个编号可以找到任何一个人,那指针如何定义呢?

#include <stdio.h>
int main()
{
 int a = 10;//在内存中开辟一块空间
 int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
   //将a的地址存放在p变量中,p就是一个之指针变量。
 return 0;
}

总结:指针就是变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。

那这里有两个问题:1.一个小的单元到底是多大?2. 如何编地址?

经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。 对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号正电/负电(1或 者0)

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

00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000001

...

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

每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB == 2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB) 4G的空闲进行编址。 同样的方法,那64位机器,如果给64根地址线,那能编址多大空间,自己计算。

所以我们就明白:1.在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所 以一个指针变量的大小就应该是4个字节。 2.那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

总结:

1.指针是用来存放地址的,地址是唯一标示一块地址空间的。

2.指针的大小在32位平台是4个字节,在64位平台是8个字节。

2.指针和指针类型

前面说过指针和变量的定义是一样,说白了你用什么类型定义的变量,我就必须那什么指针来接受这个变量的地址,所以指针类型也就必须和变量一样,

char  *pc = NULL;
int   *pi = NULL;
short *ps = NULL;
long  *pl = NULL;
float *pf = NULL;
double *pd = NULL;
//NULL是代表空指针,和int num=0;的0是差不多的,这代表指针哪里都不指,先原地待命。

这里可以看到,指针的定义方式是: type + * 。 其实: char* 类型的指针是为了存放 char 类型变 量的地址。 short* 类型的指针是为了存放 short 类型变量的地址。 int* 类型的指针是为了存放 int 类型变量的地址。

3.指针类型的意义

指针+-整数:

#include <stdio.h>
//演示实例
int main()
{
 int n = 10;
 char *pc = (char*)&n;
 int *pi = &n;
 
 printf("%p\n", &n);
 printf("%p\n", pc);
 printf("%p\n", pc+1);
 printf("%p\n", pi);
 printf("%p\n", pi+1);
 return  0;
}

?我们可以发现当用char和int共同指向一块空间时他们的地址是相同的,共同维护一块空间,但是当不同类型指针进行+1-1操作时不同就显现出来了,char类型为一个字节,所以+1后向后移动一个字节,int类型四个字节,+1向后移动四个字节,所以也可以这么理解:

?总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。

4.指针的解引用

我们已经知道指针指向一块空间,里面存入的是地址,那么当我们工作需要时,如何顺着地址找到指针维护的空格呢?解引用 *?就是C语言给我们提供的工具,相信在前面的操作符篇大家已经见过了这个符号,它专为指针而生。

#include <stdio.h>
int main()
{
 int n = 0x11223344;
 char *pc = (char *)&n;
 int *pi = &n;
 *pc = 0;   //重点在调试的过程中观察内存的变化。
 *pi = 0;   //重点在调试的过程中观察内存的变化。
 return 0;
}

?我们调试一下:

?此时pi和pc共同维护0x0077FE8C这块空间,但是当我都把它们置成0时

pc指针只是改变了低地址处的44,而int则改变了剩下的112233,?全部将其变成0。

总结: 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。 比如: char* 的 指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

5.野指针

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

说白了就是创建指针后并没有为其分配地址,哪里都不指向,甚至是NULL,就好像偷渡客,在这个国家里没有身份,没有保障,所以负责起见我们在创建指针时还是为其初始化,哪怕是NULL。

#include <stdio.h>
int main()
{ 
 int *p;//局部变量指针未初始化,默认为随机值(野指针)
    *p = 20;
 return 0;
}

大家有没有想过这样一个问题,一个指针在维护一块数组,这个数组之有十个元素,但是这个指针并不听话,它要跟随地址一直访问到这个数组第十一个元素,会发生什么?

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    int *p = arr;
    int i = 0;
    for(i=0; i<=11; i++)
   {
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
   }
    return 0;
}

这种现象就是我们常说的指针越界,因为除了这块空间外其他的空间要么是没有被使用,要么是别的变量在使用,反正不是你的,你执意要访问肯定会发生指针越界,访问到的肯定是随机值,所以我们在平时写程序时一定要心里有数自己的指针到底在维护什么变量,会不会产生指针越界,如果发生了你并不知情,对你的程序产生的将是毁灭打击(认真脸)。

当然,大家会问指针随意度这么高的东西我怎么管它啊?在使用完指针后我们可以使用空间释放来“消灭”这个指针,在内存角度就是这个指针被释放,爱去哪去哪,只要不再对我的代码产生影响就可以。

int n = 10;
int* p = &n;
*p = 20;
free(p);
p = NULL;//指针释放

6.如何规避野指针

注意一下几点就好:1. 指针初始化 2. 小心指针越界 3. 指针指向空间释放即使置NULL 4. 指针使用之前检查有效性

#include <stdio.h>
int main()
{
    int *p = NULL;
    //....
    int a = 10;
    p = &a;
    if(p != NULL)
   {
        *p = 20;
   }
    return 0;
}
//这就是针对指针的安全维护

7.指针运算

通常我们说指针变量指针变量,指针也算是变量之一,变量应该就可以进行运算,那么指针可以运算吗,如何运算呢?现在引出:①指针+-整数

刚刚已经提到了指针+-整数,即向后跳指针类型倍的倍数,指向一块新的空间,为了加深理解我们再来一段代码:

#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
     *vp++ = 0;
}

这样就可以实现初始化数组的元素全为0。

②指针-指针

int my_strlen(char *s)
{
       char *p = s;
       while(*p != '\0' )
              p++;
       return p-s;
}

我们知道指针是指向一块空间,现在又有一个指针指向另一块空间,如果让它们相减的话会产生什么呢?现在来实验,指针-指针得到的就是两个指针之间的元素个数,比如一个指针指向数组的首元素,另一个指针指向数组的最后一个元素,它们相减得到的就是数组首元素和最后一个元素之间的个数。

③指针的关系运算

for(vp = &values[N_VALUES]; vp > &values[0];)
{
    *--vp = 0;
}
//代码简化, 这将代码修改如下:
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
    *vp = 0;
}

实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证 它可行。

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

8.指针和数组

我们之前说过,除两个例外,数组名代表的是首元素地址,既然是地址,我们学过指针后自然会联想到指针,那指针和&数组名有什么区别和联系呢?

#include <stdio.h>
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    printf("%p\n", arr);
    printf("%p\n", &arr[0]);
    return 0;
}

由此可见数组的地址和首元素的地址是一样的,只不过当进行+-运算时跳过的元素个数不同,数组名+1跳过的是整个数组,首元素+1跳过的只是一个元素而已。那么数组名作为首元素地址,这样的代码也就可行:

int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址

我们讲过指针可以通过地址来找到被指向空间的元素,以便于来进行改动操作,那么现在指针指向了数组的首元素地址,是否可以通过指针来找到数组的元素甚至改变它的元素的值呢?

#include <stdio.h>
int main()
{
    int arr[] = {1,2,3,4,5,6,7,8,9,0};
    int *p = arr; //指针存放数组首元素的地址
    int sz = sizeof(arr)/sizeof(arr[0]);
    for(i=0; i<sz; i++)
   {
        printf("&arr[%d] = %p   <====> p+%d = %p\n", i, &arr[i], i, p+i);
   }
    return 0;
}

答案是肯定的:

所以p+i就是访问到了数组的地址,我们也就可以通过指针来改变数组各元素的值,现在体会到指针的强大了吧, 但是要合理运用,避免出现野指针和越界的情况,有利有弊,不过弊我们一般可以避免。

int main()
{
 int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
 int *p = arr; //指针存放数组首元素的地址
 int sz = sizeof(arr) / sizeof(arr[0]);
 int i = 0;
 for (i = 0; i<sz; i++)
 {
 printf("%d ", *(p + i));
 }
 return 0;
}

?9.二级指针

内存中的每一个内存都是有地址的,而指针是用来存放地址的,那指针有没有地址呢?指针的地址又由谁来存放呢?我们引出概念:二级指针。如果二级指针是用来存放一级指针的地址,以此类推,三级指针就用来存放二级指针的地址,四级指针就用来存放三级指针的地址。。。无限套娃下去,但是我们知道套娃一般是禁止的对吧,所以我们只研究到二级指针就可。

我们知道如果想通过指针找到其指向的那块空间,可以解引用操作,但是解引用只是找了一次地址,现在的二级指针是地址套地址,所以我们在通过二级指针找到内存块时就需要解引用两次,也就是用两次*。

int b = 20;
*ppa = &b;//等价于 pa = &b;
//*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
//**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .

10.指针数组

注意,这并不是两个知识的名字写一起了,这就是一个新的知识,我们知道数组是用来存放一组数,指针是用来存放地址,那指针数组是什么呢?数组指针又是什么呢?区分他们的方法就是看后缀,指针数组后缀是数组,那它就是数组,是用来存放指针的数组;数组指针后缀是指针,那它就是指针,用来存放数组的指针。

那数组指针在内存中是指什么样的呢?

这就是整形指针数组在内存中的样子,arr3中存入的数据不再是整形变量,而是整形类型的指针,这就是指针数组,那它该如何定义呢?

int* arr3[5];//这就是指针数组的定义方式

?好了,本章到此先告一段落,想必打击也都看乏了,稍作休息,我们下次再见。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-04 12:40:48  更:2021-10-04 12:43:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:11:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码