19_1.21指针

指针

概念

  1. 所谓指针,也就是内存的地址;所谓指针变量,也就是保存了内存地址的变量。
  2. 数据和代码都以二进制的形式存储在内存中,计算机无法从格式上区分某块内存到底存储的是数据还是代码。当程序被加载到内存后,操作系统会给不同的内存块指定不同的权限,拥有读取和执行权限的内存块就是代码,而拥有读取和写入权限(也可能只有读取权限)的内存块就是数据。
    CPU 只能通过地址来取得内存中的代码和数据,程序在执行过程中会告知 CPU 要执行的代码以及要读写的数据的地址。如果程序不小心出错,或者开发者有意为之,在 CPU 要写入数据时给它一个代码区域的地址,就会发生内存访问错误。这种内存访问错误会被硬件和操作系统拦截,强制程序崩溃,程序员没有挽救的机会。
    CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。
    虽然变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符,但在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的首地址。
  3. 数据在内存中的地址也称为指针,如果一个变量存储了一份数据的指针,我们就称它为指针变量。
  4. 定义指针变量
    datatype *name; int a = 100; int *p_a = &a,*p1; p1=&a;
    是一个特殊符号,表明一个变量是指针变量,定义 p1、p2 时必须带。而给 p1、p2 赋值时,因为已经知道了它是一个指针变量,就没必要多此一举再带上 ,后边可以像使用普通变量一样来使用指针变量。也就是说,定义指针变量时必须带,给指针变量赋值时不能带*。
  5. 通过指针变量取得数据
    指针变量存储了数据的地址,通过指针变量能够获得该地址上的数据,格式为:*pointer;这里的*称为指针运算符,用来取得某个地址上的数据,请看下面的例子:
    #include <stdio.h>
    int main(){
        int a = 15;
        int *p = &a;
        printf("%d, %d\n", a, *p);  //两种方式都可以输出a的值
        return 0;
    }
*在不同的场景下有不同的作用:*可以用在指针变量的定义中,表明这是一个指针变量,以和普通变量区分开;使用指针变量时在前面加*表示获取指针指向的数据,或者说表示的是指针指向的数据本身。
  1. 关于 * 和 & 的谜题
    假设有一个 int 类型的变量 a,pa 是指向它的指针,那么*&a和&pa分别是什么意思呢?
    &a可以理解为(&a),&a表示取变量 a 的地址(等价于 pa),
    (&a)表示取这个地址上的数据(等价于 pa),绕来绕去,又回到了原点,&a仍然等价于 a。
    &*pa可以理解为&(*pa),*pa表示取得 pa 指向的数据(等价于 a),&(*pa)表示数据的地址(等价于 &a),所以&*pa等价于 pa。
  2. 对星号的总结
    在我们目前所学到的语法中,星号
    主要有三种用途:
    表示乘法,例如int a = 3, b = 5, c; c = a * b;,这是最容易理解的。
    表示定义一个指针变量,以和普通变量区分开,例如int a = 100; int *p = &a;。
    表示获取指针指向的数据,是一种间接操作,例如int a, b, *p = &a; *p = 100; b = *p;。
  3. 数组指针(指向数组的指针)
  4. 指针数组(数组每个元素都是指针)
    如果一个数组中的所有元素保存的都是指针,那么我们就称它为指针数组。指针数组的定义形式一般为:
dataType *arrayName[length];//[ ]的优先级高于*可以理解为dataType *(arrayName[length]);

除了每个元素的数据类型不同,指针数组和普通数组在其他方面都是一样的,下面是一个简单的例子:

    #include <stdio.h>
    int main(){
        int a = 16, b = 932, c = 100;
        //定义一个指针数组
        int *arr[3] = {&a, &b, &c};//也可以不指定长度,直接写作 int *parr[]
        //定义一个指向指针数组的指针
        int **parr = arr;
        printf("%d, %d, %d\n", *arr[0], *arr[1], *arr[2]);
        printf("%d, %d, %d\n", **(parr+0), **(parr+1), **(parr+2));
        return 0;
    }

运行结果:
16, 932, 100
16, 932, 100

arr 是一个指针数组,它包含了 3 个元素,每个元素都是一个指针,在定义 arr 的同时,我们使用变量 a、b、c 的地址对它进行了初始化,这和普通数组是多么地类似。

parr 是指向数组 arr 的指针,确切地说是指向 arr 第 0 个元素的指针,它的定义形式应该理解为int *(parr),括号中的表示 parr 是一个指针,括号外面的int *表示 parr 指向的数据的类型。arr 第 0 个元素的类型为 int *,所以在定义 parr 时要加两个 *。

第一个 printf() 语句中,arr[i] 表示获取第 i 个元素的值,该元素是一个指针,还需要在前面增加一个 * 才能取得它指向的数据,也即 *arr[i] 的形式。

第二个 printf() 语句中,parr+i 表示第 i 个元素的地址,*(parr+i) 表示获取第 i 个元素的值(该元素是一个指针),**(parr+i) 表示获取第 i 个元素指向的数据。

    #include <stdio.h>
    int main(){
        char *str[3] = {
            "c.biancheng.net",
            "C语言中文网",
            "C Language"
        };
        printf("%s\n%s\n%s\n", str[0], str[1], str[2]);
        return 0;
    }

运行结果:
c.biancheng.net
C语言中文网
C Language

需要注意的是,字符数组 str 中存放的是字符串的首地址,不是字符串本身,字符串本身位于其他的内存区域,和字符数组是分开的。

也只有当指针数组中每个元素的类型都是char *时,才能像上面那样给指针数组赋值,其他类型不行。

  1. 定义字符串:字符数组。直接使用一个指针指向字符串。
    char *str;
    str = "http://c.biancheng.net";

字符串中的所有字符在内存中是连续排列的,str 指向的是字符串的第 0 个字符;我们通常将第 0 个字符的地址称为字符串的首地址。字符串中每个字符的类型都是char,所以 str 的类型也必须是char *。

  1. 输出字符串
    #include <stdio.h>
    #include <string.h>
    int main(){
        char *str = "http://c.biancheng.net";
        int len = strlen(str), i;
        //直接输出字符串
        printf("%s\n", str);
        //使用*(str+i)
        for(i=0; i<len; i++){
            printf("%c", *(str+i));
        }
        printf("\n");
        //使用str[i]
        for(i=0; i<len; i++){
            printf("%c", str[i]);
        }
        printf("\n");
        return 0;
    }

运行结果:
http://c.biancheng.net
http://c.biancheng.net
http://c.biancheng.net
区别:它们最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,第二种形式的字符串存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。

    #include <stdio.h>
    int main(){
        char *str = "Hello World!";
        str = "I love C!";  //正确
        str[3] = 'P';  //错误
        return 0;
    }
  1. 指针变量作为函数参数
    用指针变量作函数参数可以将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,并且这些数据不会随着函数的结束而被销毁。
    像数组、字符串、动态分配的内存等都是一系列数据的集合,没有办法通过一个参数全部传入函数内部,只能传递它们的指针,在函数内部通过指针来影响这些数据集合。
    比较两个代码区别:
    #include <stdio.h>
    void swap(int a, int b){
        int temp;  //临时变量
        temp = a;
        a = b;
        b = temp;
    }
    int main(){
        int a = 66, b = 99;
        swap(a, b);
        printf("a = %d, b = %d\n", a, b);
        return 0;
    }

运行结果:
a = 66, b = 99

    #include <stdio.h>
    void swap(int *p1, int *p2){
        int temp;  //临时变量
        temp = *p1;
        *p1 = *p2;
        *p2 = temp;
    }
    int main(){
        int a = 66, b = 99;
        swap(&a, &b);
        printf("a = %d, b = %d\n", a, b);
        return 0;
    }

运行结果:a = 99, b = 66
*** 用数组作函数参数***

    #include <stdio.h>
    int max(int *intArr, int len){ //参数 intArr 仅仅是一个数组指针,在函数内部无法通过这个指针获得数组长度,必须将数组长度作为函数参数传递到函数内部。不管使用哪种方式传递数组,都不能在函数内部求得数组长度,因为 intArr 仅仅是一个指针,而不是真正的数组,所以必须要额外增加一个参数来传递数组长度。
        int i, maxValue = intArr[0];  //假设第0个元素是最大值
        for(i=1; i<len; i++){
            if(maxValue < intArr[i]){
                maxValue = intArr[i];
            }
        }
       
        return maxValue;
    }
    int main(){
        int nums[6], i;
        int len = sizeof(nums)/sizeof(int);
        //读取用户输入的数据并赋值给数组元素
        for(i=0; i<len; i++){
            scanf("%d", nums+i);
        }
        printf("Max value is %d!\n", max(nums, len));
        return 0;
    }

***指针作为函数返回值 ***
用指针作为函数返回值时需要注意的一点是,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针请尽量不要指向这些数据,C语言没有任何机制来保证这些数据会一直有效,它们在后续使用过程中可能会引发运行时错误。

  1. 指针的总结
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值