• 【C语言】学习笔记5——指针(1)


    1. 指针:一种以符号形式使用地址的方法。

      因为计算机的硬件指令非常依赖地址, 所以使用指针的程序更有效率。尤其是, 指针能有效地处理数组,数组地表示法其实是在变相地使用指针。

      例子:数组名是数组首元素的地址。 也就是说,如果 flizny 是一个数组,下面的语句成立 

    flizny == &flizny[0]

      flizny 和 &flizny 都表示数组首元素的地址(&是地址运算符)。两者都是常量,在程序运行中,不会改变,但是,可以把他们赋值给指针变量, 然后可以修改指针变量。

    //指针地址
    
    #include <stdio.h>
    #define SIZE 4
    
    int main()
    {
        short dates[SIZE];
        //声明一个short类型的指针变量 
        short * pti;    
        short index;
    
        double bills[SIZE];
        double * ptf;
        
        pti = dates;
        ptf = bills;
        
        printf("%23s %15s
    ", "short", "double");
        for(index = 0; index < SIZE; index++)
            printf("pointers + %d: %10p %10p
    ", index, pti + index, ptf + index); 
        return 0;
     } 
     /*
     Output:
                       short          double
    pointers + 0: 000000000062FE30 000000000062FE10
    pointers + 1: 000000000062FE32 000000000062FE18
    pointers + 2: 000000000062FE34 000000000062FE20
    pointers + 3: 000000000062FE36 000000000062FE28
     */

    不知道为什么, 我以前上学的时候看指针跟看天书一样,现在再看指针突然很简单了。可能现在代码写多了,很容易分清楚变量,值, 变量名, 地址的关系。

    所以现在看来学校开C语言来作为计算机语言的入门科目确实是有待商榷的,诚然C语言是最早的高级语言,学会了C语言会很容易学习其他计算机语言。但是先学习像java / python这样简单粗暴的计算机语言,可以让学生很轻松地理解写程序到底是怎么一回事。然后再回头深究它底层的原理会轻松地多。

    我觉得可以把指针理解为一个变量。这个变量的大小取决于计算机的地址大小(我的电脑是64位的,所以一个指针大小就是8 bytes)。比如上面程序中的例子

    //声明一个short类型的指针, 指针增量为sizeof(short)=2, 也就是说pti + 1相当于 pti 所表示的地址 + 2
    short * pti;
    
    //声明一个double类型的指针, 指针的增量为sizeof(double)=4, 也就是说ptl + 1 表示 pti 所指向的地址 + 4
    double * ptf;

    看上面的程序结果也很容易看出来, 再看书上的图也是符合的

    2. 函数、数组和指针

      因为数组名是该数组首元素地地址,作为实际参数地数组名要求形式参数是一个与之匹配的指针。只有在这种情况下,C才会把 int ar[] 和 int * ar 解释成一样的。 也就是说, ar是指向int的指针。

      由于函数原型可以省略参数名, 所以下面4中原型都是等价的。  

    int sum(int * ar, int n);
    
    int sum(int *, int);
    
    int sum(int ar[], int n)
    
    int sum(int [], int);

      但是, 在函数定义中不能省略参数名, 下面两种形式的函数定义等价:

    int sum(int *ar, int n) 
    {
      //函数内容省略  
    }
    
    int sum(int ar[], int n)
    {
     //函数内容省略
    }

      看下面示例程序, marbles 的大小为40 bytes, 说明marbles表示的是数组, 而 ar的大小是8 bytes, 说明函数接收的是一个地址();

    //数组元素之和
    
    #include <stdio.h>
    #define SIZE 10
    
    //声明函数原型 
    int sum(int ar[], int n);
    
    int main()
    {
        int marbles[SIZE] = {15, 23, 24, 69, 14, 12, 15, 18, 16, 35};
        long answer;
        
        answer = sum(marbles, SIZE);
        
        printf("marbles 所有元素之和为 %1d.
    ", answer);
        
        printf("marbles 的大小为%u bytes.
    ", sizeof marbles);
        
        return 0;
     }
     
     int sum(int ar[], int n)
     {
         int i;
         int total = 0;
         
         for(i = 0; i < n; i++)
         {
             total += ar[i];
         }
         printf("ar 的大小为 %u bytes.
    ", sizeof ar);
         return total;
     }
     
     
     /*
     Output:
     
    ar 的大小为 8 bytes.
    marbles 所有元素之和为 241.
    marbles 的大小为40 bytes.
    
     */

     3. 使用指针形参

    /*使用指针形参*/
    
    #include <stdio.h>
    #define SIZE 10
    
    int sump(int * start, int * end);
    
    int main()
    {
        int marbles[SIZE] = {15, 23, 24, 69, 14, 12, 15, 18, 16, 35};
        
        long answer;
        
        answer = sump(marbles, marbles + SIZE);
        
        printf("the total number of marbles is %1d.
    ", answer);
        
        return 0;
     }
     
     //使用指针算法 
     int sump(int * start, int * end)
     {
         int total;
         while (start < end)     // 越界判断 
         {
             total += * start;  //累加数组元素的值 
             start++;           // 让指针指向下一个元素 
         }
         
         return total;
     }
     
     
     /*
     Output:
     
     the total number of marbles is 241.
    
     */

    4. 指针操作

      a. 赋值:可以把地址赋值给指针。

      b. 解引用:* 运算符给出 指针所指地址上 存储的值。

      c. 取址:和所有变量一样,指针也有自己的地址和值,也可以用取址运算符 & 来获取指针本身的地址

      d. 指针与整数相加: 可以运用 + 运算符把指针与整数相加, 或整数与指针相加。 无论哪种情况,整数都会和指针所指向类型的大小(byte为单位)相乘, 然后把结果与初始地址相加。

      e. 指针递增(递减): 递增(递减)指向数组的指针可以让该指针指向该数组的下(上)一个元素。

      f. 指针减去一个整数:用初始地址减去 整数和指针所指向类型的大小(byte为单位)相乘 的结果。

      g. 指针求差:可以计算两个指针的差值。 两个指针所指向的地址的差值 / 指针所指向类型的大小

      h. 比较: 使用关系运算符可以比较两个指针的值,前提是两个指针都指向相同类型的对象

    // 指针操作
    
    #include <stdio.h>
    
    int main()
    {
        int urn[5] = {100, 200, 300, 400, 500};
        
        int * ptr1, * ptr2, * ptr3;
        
        ptr1 = urn;
        ptr2 = &urn[2];
        
        printf("pointer value, dereferenced pointer, pointer address:
    ");
        printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p
    ", ptr1, *ptr1, &ptr1);
        
        ptr3 = ptr1 + 4;         //指针加法
        printf("
     adding an int to a pointer:
    ");
        printf("ptr1 + 4 = %p, *(ptr1 + 4) = %d
    ", ptr1 + 4, *(ptr1 + 4));
        
        ptr1++;             //递增指针
        printf("
     values after ptr1++:
    ");
        printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p
    ", ptr1, *ptr1, &ptr1);
        
        ptr2--;             //递减指针 
        printf("
     values after ptr2--:
    ");
        printf("ptr2 = %p, *ptr2 = %d, &ptr2 = %p
    ", ptr2, *ptr2, &ptr2);
        
        //恢复 
        --ptr1;
        ++ptr2;
        printf("
     pointers reset to original values:
    ");
        printf("ptr1 = %p, ptr2 = %p
    ", ptr1, ptr2);
         
    
        
        //一个指针减去另一个指针
        printf("
     subtracting one pointer from another:
    ");
        printf("ptr2 = %p, ptr1 = %p, ptr2 - ptr1 = %1d
    ", ptr2, ptr1, ptr2 - ptr1);
        
        //一个指针减去一个整数
        printf("
     subtracting an int from a pointer:
    ");
        printf("ptr3 = %p, ptr3 - 2 = %p
    ", ptr3, ptr3 - 2);
        
        return 0; 
         
     } 
     
    /*
    Output:
    pointer value, dereferenced pointer, pointer address:
    ptr1 = 000000000062FE30, *ptr1 = 100, &ptr1 = 000000000062FE28
    
     adding an int to a pointer:
    ptr1 + 4 = 000000000062FE40, *(ptr1 + 4) = 500
    
     values after ptr1++:
    ptr1 = 000000000062FE34, *ptr1 = 200, &ptr1 = 000000000062FE28
    
     values after ptr2--:
    ptr2 = 000000000062FE34, *ptr2 = 200, &ptr2 = 000000000062FE20
    
     pointers reset to original values:
    ptr1 = 000000000062FE30, ptr2 = 000000000062FE38
    
     subtracting one pointer from another:
    ptr2 = 000000000062FE38, ptr1 = 000000000062FE30, ptr2 - ptr1 = 2
    
     subtracting an int from a pointer:
    ptr3 = 000000000062FE40, ptr3 - 2 = 000000000062FE38
    
     
    */ 

    5. 不要解引用未初始化的指针:

      

    int * pt;   //未初始化的指针
    
    *pt = 5;   //严重的错误。 因为pt还没有初始化,我们不知道pt指向谁,解引用不知道把5赋值给谁

    6. 保护数组中的数据(按值传递还是按地址传递)

      a. 函数中的值传递和地址传递:编写一个处理基本类型(如 int)的函数时, 要选择时传递int类型的值还是传递指向int类型的指针。 通常都是直接传递数值, 只有程序需要在函数中改变该数值时,才会传递指针。对于数组别无选择,必须传递指针,因为这样做效率高。如果按一个函数按值传递数组, 则必须分配足够的空间来存储原数组的副本,然后把数组所有的数据拷贝至新的数组中。 如果把数组的地址传递给函数,让函数直接处理原数组则效率要高。

      b. 传递地址会导致一些问题(可能会对数值进行意外的修改)。C通常都是按值传递数据,因为这样可以保证数据的完整性。

    7. 对形参使用const : 如果函数的意图不是修改数组中的数据内容, 那么在函数原型和函数定义中声明形式参数时应使用关键字const 

       

      

  • 相关阅读:
    c# 02-18 值类型 引用类型 字符串的不可变性 字符串的处理方法
    c#0218-命名空间
    c#学习0217
    BootStrap学习
    c#学习0216
    AngularJS学习
    java面试题之什么是死锁、活锁、饿死和竞态条件?
    java面试题之能创建volatile数组吗?
    java面试题之sleep()和wait()方法的区别
    java面试题之Thread的run()和start()方法有什么区别
  • 原文地址:https://www.cnblogs.com/yeyeck/p/9502392.html
Copyright © 2020-2023  润新知