> 文档中心 > C语言指针(基础篇)

C语言指针(基础篇)

文章目录

  • 前言
  • 一、指针地址
    • 1. 变量与内存
    • 2. 地址与指针
    • 3. 常量、变量、内存、地址、指针的关系
  • 二、指针的基础操作
    • 1.大端模式与小端模式
    • 2. 指针的定义与初始化
      • 2.1 指针的定义与初始化
      • 2.2 一个例子
      • 2.3 指针的定义与初始化举例及图示
    • 3. 指针的运算
    • 4. 示例演示
  • 三、二级指针

前言

      指针是C语言的灵魂,C语言因为指针而在众多语言中所向披靡,也使得C语言一直盛行到现在。因为指针的存在也使得C语言的可操作性极大提升,学好指针是十分必要的,下面将为大家来细致的讲解C语言中指针的知识点也是一些易混易错知识点。

一、指针与地址

1. 变量与内存

      C语言定义变量我想大家都很熟悉了,假如说我想定义一个整型变量 a 。

int a;

      其实要明确的一点是,我们在C语言中只要是定义的变量它都是存储在内存中的,不管是普通变量,结构体变量还是指针变量,都是存储在内存中的。在32位操作系统中,一个程序的内存有4G。当然这里所指的内存是虚拟内存。

2. 地址与指针

       在C语言中每块内存都是以字节为单位,每个内存都有一个编号,每一个编号都是以32位二进制数表示, 我们把这个编号就称之为地址,如下图所示。并且通过下图我们能够确定,每个地址的值都是唯一的。那么指针是用来干嘛的呢?其实指针就是用来存储地址的
在这里插入图片描述
       其实通过上面的信息我们能够验算两个问题。
问题一: 为什么32位操作系统中,指针都是4字节?
      因为指针存储的是地址,而地址是一个32位二进制数,一个字节是8位二进制数,所以 32 / 8 = 4(字节)。
问题二: 为什么32位操作系统中,虚拟内存是4GB?
      因为每块内存的1字节,每个内存编号是32位二进制数表示,所以内存一共有 2^32 字节大小,换算下单位 2^32(Byte) = 4,294,967,296(Byte) = 4,194,304(KB) = 4,096(MB) = 4(GB)。

3. 常量、变量、内存、地址、指针的关系

       常量是存储在变量中,变量是存储在内存中的,每个内存都有一个编号,我们称这个编号为地址,指针就是用来存储地址的。 这句话大家一定要牢记。
在这里插入图片描述
      站在编译器的角度,假如定义一个字符类型的变量a,并对其赋值为10,其实编译器就会在内存中分配出一块内存,假如这块内存的地址是 0x12345678 ,那么这块内存里面存储的数值就是10, 同时这块内存就叫做a,通过 a 我们就能读取到内存里面存储的内容,通过取地址符&,我们就能得到这块内存的地址或者也可以说变量的地址,即 &a 的值为 0x12345678 。

二、指针的基础操作

1.大端模式与小端模式

      在介绍指针之前,需要先介绍以下大端模式和小端模式,大端模式和小端模式其实就是数据的存储方式不一样。像ARM,摩托罗拉,MIPS等采用的是大端模式的存储方式,像Intel等采用的就是小端模式。
大端模式:高地址存储数据的低位,低地址存储数据的高位。
小端模式:高地址存储数据的高位,低地址存储数据的低位。

在这里插入图片描述
      图解如上,假如变量a的地址是0x000000a1,那么对于大端模式,地址0x000000a1存储的是数值0x12;对于小端模式,地址0x000000a1存储的是数值0x78。

2. 指针的定义与初始化

2.1 指针的定义与初始化

指针的定义:

<存储类型>  <数据类型> *<变量名>

指针的初始化:

1.先定义,后初始化<存储类型>  <数据类型> *<变量名>;<变量名> = <地址>;2.在定义时初始化<存储类型>  <数据类型> *<变量名> = <地址>;
  1. 存储类型:C语言的存储类型有4中,分别为 auto、static、 register、 extern。在一般情况下我们通常会对存储类型进行省略不写,但其实省略不写,就相当于存储类型就是 auto。
  2. 数据类型:数据类型就是C语言的基本数据类型,像 int 、char、float 等等。
  3. *号记得一定要写,*号和后面变量名结合,代表该变量名是一个指针变量。
  4. 变量名:符合C语言标识符命名规格即可。

2.2 一个例子

int a = 10;
int *p = a;

p是一个指针变量,它里面存储的是一个地址。
*p在定义时是表示p是一个指针变量,在后面的运算中*p是一个值,存储的一个地址。
&p是一个常量,表示p所占用的内存地址。

2.3 指针的定义与初始化举例及图示

源代码:

#include int main(){    /*a是指针变量,b是int型变量*/    /*a被定义时未初始化,此时a是野指针,a里面存储的值是编译器随机分配的,直接使用野指针会造成代码的不确定性*/    int *a, b;    /*将指针变量a指向b的地址*/    a = &b;    /*对变量b的内存里面的内容写成10*/    b = 10;    /*取出指针变量里面的内容用取值运算符*号*/    /*除了在定义时的*号+变量表示该变量是一个指针以外,在之后遇到的*号+变量都是表明是取出指针变量里面的内容的*/    /*因为a是一个地址,所以用%p打印,又因为*a是一个值,是表示取出a中存储的地址的值,所以用%d打印*/    printf("&a = %p, a = %p, *a = %d, &b = %p, b = %d\n", &a, a, *a, &b, b);    return 0;}

运行结果:

&a = 0061FE18, a = 0061FE14, *a = 10, &b = 0061FE14, b = 10

图示:
在这里插入图片描述
       如图所示,定义变量 b 的内容是10,b 的地址是 0x0061FE14,a 存储的是 b 的地址,所以 a 内存里面的内容就是b的地址值,所以 a 里面存储的数值为0x0061FE14,通过取值运算符,*a 表示的是取出a中存储的地址里面的内容,a中存储的地址是0x0061FE14,地址0x0061FE14中存储的值是 10,所以 *a 的值是10。

3. 指针的运算

       指针的运算其实本质上就是地址的运算。
例如:

char *a;short *b;int *c;
  1. 算术运算:指针的算数运算的偏移是指针指向的类型的大小的偏移。
    如 +、-、++、-- 运算
    (1)a 是 char* 类型,说明指针变量 a 是指向字符变量 char , char占一个字节,所以 a+1 的地址指向a后面的第一块内存,假如 a 原本指向的地址是 0x00000010, 那么 a+1 指向的地址是 0x00000011,a-1 指向的地址是 0x0000000f。
    (2)b是 short* 类型,说明指针变量 b 是指向短整型变量 short , short占两个字节,所以 b+1 的地址指向b后面的第二块内存,假如b原本指向的地址是 0x00000010, 那么 b+1 指向的地址是 0x00000012,b-1 指向的地址是 0x0000000e。
    (2)c是 int* 类型,说明指针变量 c 是指向整型变量 int, int占四个字节,所以 c+1 的地址指向c后面的第四块内存,假如c原本指向的地址是 0x00000010, 那么 c+1 指向的地址是 0x00000014,c-1 指向的地址是 0x0000000c。
  2. 关系运算:(如 >、=、<=、==、!=)
    比如定义两个整型指针变量 int *a, *b; 假如 a > b 说明 a 的内存中存放的地址比 b 的内存中存放的地址值要大。虽然不同类型的指针变量也可以比较大小,但没有实际意义。
  3. 赋值运算:(如 =、+=、-=)
    比如定义一个 int *a; 后续不管对指针变量 a 赋值成什么,在 a 看来这个值就是一个地址,通过 *a 可以得到 a 中存储的地址中的内容。

4. 示例演示

源代码:

#include int main(){    char a = 10;    int b = 20;    char *pa = &a;    int *pb = &b;    /*通过修改指针间接修改变量a的内容,通过修改变量,指针获取的值也会发生变化*/    *pa = 11;    b = 22;    /* *(&b)通过地址运算符先取出b的内存地址,然后通过取值运算符取出地址里面的值,也就是 *(&b) 和 b 这两种取值的方式是完全等价的*/    printf("a = %d, *pb = %d, *(&b) = %d\n", a, *pb, *(&b));    /*将字符指针指向整型变量*/    /*22的二进制数是0x00000016, 如果打印结果是0x16,那么该机器是小端模式,如果打印结果是0x00,那么该机器是大端模式*/    pa = &b;    printf("*pa = %#x\n", *pa);    return 0;}

运行结果:

a = 11, *pb = 22, *(&b) = 22*pa = 0x16

三、二级指针

       我们知道一级指针是用来存储变量的地址的,那么一级指针变量其实也是个变量,那么它同样的也应该有他的地址,==存储普通变量地址的变量我们称之为一级指针变量,存储一级指针变量地址的变量我们就称之为二级指针变量。==依此类推,存储二级指针变量地址的变量我们就称之为三级指针变量… ,尽管如此,但我们平时用到最多的还是一级指针和二级指针,更高级的指针在工作中用得可以说是很少了,并且,更高级的指针也使得代码的可读性变低,所以我们就不需要对其讨论,并且当掌握了二级指针,后面更高级别的指针都是以此类推的。
       其实二级指针的一些属性和一级指针基本差不多,我们直接通过一个实例来对其进行讲解,讲解都在代码中。
源代码:

#include int main(){    int a = 10;    /*pa是一级指针变量,&pa是取出一级指针变量的地址,一级指针变量的地址要二级指针来存储*/    int *pa = &a;    /*定义二级指针变量ppa, 用来存储二级一级指针变量的地址*/    int **ppa = &pa;    /* *ppa 和 pa 是等效的, **ppa 和 *pa 是等效的,又因为 *pa 和 a 是等效的,所以 **ppa, *pa, a 是等效的  1. &ppa 是二级指针变量的地址,需要用三级指针来存储。 2. ppa 是存储了一级指针变量的地址,ppa 等价于 &pa, 所以 ppa是一个地址。 3. *ppa 是一级指针变量里面的内容,一级指针变量里面的内容是普通变量的地址,所以 *ppa 和 pa 等价,所以 *ppa 也是一个地址 4. **ppa 是一个值。    */    printf("&ppa = %p, ppa = %p, *ppa = %p, **ppa = %d, &pa = %p, pa = %p, *pa = %d, &a = %p, a = %d\n",  &ppa, ppa, *ppa, **ppa, &pa, pa, *pa, &a, a);    return 0;}

运行结果:

&ppa = 0xbfc496ac, ppa = 0xbfc496a8, *ppa = 0xbfc496a4, **ppa = 10, &pa = 0xbfc496a8, pa = 0xbfc496a4, *pa = 10, &a = 0xbfc496a4, a = 10

图示:
在这里插入图片描述