8.1 指针用法初识

一、指针是什么

(一) 含义

地址是内存单元的编号 指针就是地址

(二) 通过一个简单程序来认识指针

# include <stdio.h>

int main(void)
{
    int * p; 
    // 定义了一个变量叫P,  
    //int * 是变量类型 ,p是变量名 
    //所谓 int * 数据类型,就是存放整型变量地址的数据类型,
    //其中 int 也叫基类型,指定此指针变量可以指向的变量的类型。
    //表示p只能存放 int型 变量的地址。
    //为什么要指定基类型? 因为不同类型数据所占用的内存单元大小和存放方式不一致。
    int i =3;
    p = &i; //正确
    p=i; //Error 类型不一致,P 只能存放整形变量的 “地址” ;而 i 是一个整型变量。
    p = 55; //Error 同上
    return 0;
}

深层理解 p = &i;

1. P 保存了i 的地址,因此P指向了i 2. P不是i , i 也不是P。 3. 更准确的说,修改P的值不影响i的值,修改 i 的值,也不影响P的值 4. P是指针变量(能存放其他变量的地址) i 是一个普通变量(只能存放值) 5. 如果一个指针指向了某个普通变量,则 *指针变量 就完全等同于 普通变量 例子: 如果 P 是一个指针变量,并且P存放了普通变量 i 的地址,
则P指向了普通变量 i ; * P 就完全等同于普通变量i
或者说: 在所有出现 * P 的地方都可以替换成i; 在所有出现 i 的地方都可以替换成 * P 6. * P就是以P的内容(P的内容是 i 的地址)为地址的变量

指针与指针变量

  1. 指针就是地址,地址就是指针
  2. 地址就是内存单元的编号
  3. 指针变量就是存放地址(指针)的变量
  4. 指针和指针变量是两个不同的概念
  5. 但是要注意
    1. 通常我们叙述时,会把指针变量简称为指针
    2. 实际他们含义并不一样

二、指针的重要性

  1. 通过指针可以一些复杂的数据结构(树、链表、图)
  2. 快速的传递数据,减少内存耗用
  3. 是函数可以返回一个以上的值
  4. 能直接访问硬件
  5. 能方便地处理字符串
  6. 是理解面向对象语言中引用的基础

总结:指针是C语言的灵魂

三、指针的定义与分类

概况

一、地址 ① 内存单元的编号 ② 从0开始的非负整数 ③ 范围: 和内存大小、地址总线的条数(CPU)、操作系统等因素有关。[这段写得可能有错,因为没学过组成原理!!!] 例如:
4G 内存,32位CPU
物理地址:0——(2^32-1)

二、指针 ① 指针就是地址,地址就是指针 ② 指针变量就是存放内存单元的编号,或者说指针变量就是存放地址的变量。 ③ 指针和指针变量是两个不同的概念,但是要注意,通常我们叙述时,会把指针变量简称为指针,实际他们含义并不一样。 ④ 指针的本质就是一个操作首先的非负整数。(指针不能相加,相乘,相除;只能进行想减)

(一) 基本类型指针

初识

int * P;
int i=3;
p = & i;

基本类型指针的常见错误

读写修改未分配权限空间的值
一、
int main (void)
{
    int *P; 
    /*
        定义P,没初始化,给P分配了一块空间,里边是垃圾值
        但是垃圾值也是有指向的,指向一处未知空间,暂且把这个记为X
    */
    int i =5;
    *P=i; 
    /*
        * P 以 P 的内容为地址的值。 
        现在P的内容为一个垃圾地址,这个地址指向X,
        *P 等同于X,但是系统没给分配修改X的权限,这句却要修改人家,必然是错的。
    */
    printf("%d\n",*P);
    return 0;
}
二、
int main (void)
{
    int *P;
    int *q;
    int i =5;
    P=&i;
    *P=*q;//语法上没错,但是没实际意义,输出结果,每次运行基本都不一样
    printf("%d\n",*P);
    return 0;
}
三、
int main (void)
{
    int *P;
    int *q;
    int i =5;
    P=&i;
    P=q; // q 是垃圾值,q 赋给P,P也变成垃圾值
    printf("%d\n",*P); //error
    /*
        q的空间是属于本程序的,所以本程序可以读写q的内容,
        但是如果q的内部是垃圾值,则本程序不能读写*q的内容
        因为此时*q所代表的内存单元的控制权限并没有分配给本程序
    */
    return 0;
}
野指针

见动态内存部分

基本类型指针案例——互换两个数字

1. 不用函数
int main(void)
{
    int a = 5;
    int b = 3;
    int c;
    c = a;
    a = b;
    b = c;
    printf("a=%d;b=%d\n",a,b);
    return 0;
}
2. 不用指针的函数能否实现数字互换呢?
void huhuan (int a ,int b)
{
    int t;
    t=a;
    a=b;
    b=t;  
    printf("函数内:a=%d;b=%d\n",a,b); 
    return;
}
int main(void)
{
    int a=5;
    int b=3;
    huhuan(a,b);
    printf("函数调用完:a=%d;b=%d\n",a,b); 
    return 0;
}
//输出
函数内:a=3;b=5
函数调用完:a=5;b=3

可见,这样的函数,互换操作的是形参,对真正的实参不起作用。

程序运行,从主函数main开始,当遇到huhuan这个函数时,转到函数huhuan执行,
主函数调用huhuan,将两个实参a,b具体的值传到huhuan函数的形参部分,huhuan拿到形参具体的值(5,3),此时,系统自动为形参a,b分配内存空间,现在函数形参a=5,b=3;
函数体语句是对形参a,b进行的操作。
一旦huhuan函数执行完毕,系统为形参开辟的内存空间将被自动释放,对实参不起任何作用。

3. 用指针写函数互换两个变量的值

①.错误写法

void change(int *p,int*q)
{
    int *t; // 必须是 int * 类型,否则类型不一致报错
    t=p;
    p=q;
    q=t;
    /*
        此函数交换的是p,q的内容,
        a,b 的值并没有换。
        p,q是代表a,b地址的值。
        注意 a,b的地址是固定的,不能说交换a,b的地址。
        因为a,b是静态局部变量,一旦分配好空间,就一直是在一个地方,一个地址。
    */
    return;
}
int main(void)
{
    int a=3;
    int b=5;
    change(&a,&b);
    printf("函数调用完:a=%d;b=%d\n",a,b);
    return 0;
}
//输出
change函数调用完:a=3;b=5

改变的是p,q里面存放的地址,并不能改变a,b的地址;任何语言都不可能改变静态变量已经分配好的地址。

② 正确写法

    void ZZhuhuan(int *p,int *q)
    {
        int t;
        t=*p;
        *p=*q;
        *q=t; 
        return;
    }

    int main(void)
    {
        int a = 3;
        int b = 5;
        ZZhuhuan(&a,&b);
        printf("ZZhuhuan函数调用完:a=%d;b=%d\n",a,b);
        return 0;
    }
    //输出:
    ZZhuhuan函数调用完:a=5;b=3

附注

星号*的含义:

  1. 乘法
  2. 定义指针变量 int * P
  3. 指针运算符(取地址的逆运算符),放在已经定义好的指针变量的前面。如果P是一个已经定义好的指针变量,则*P表示以P的内容的为地址的变量

不用指针,函数只能返回一个值。 通过指针,可以使函数返回一个以上的值。

如何通过被调函数修改主调函数普通变量的值?

  1. 实参必须为该普通变量的地址
  2. 形参必须是指针变量
  3. 被调函数体内,通过* 形参名=…… 的方式就可以修改主调函数相关变量的值。

(二) 指针和数组

见 8.2 指针和数组

(三) 指针和函数

见 8.3 指针和函数


本文章使用limfx的vsocde插件快速发布