• 2018-2019-1 20189221 《从问题到程序》第 6 周学习总结


    2018-2019-1 20189221 《从问题到程序》第 6 周学习总结

    第七章 指针

    7.1 地址与指针

    许多高级语言把程序对象(如变量)的地址作为一种可处理数据,称为地址值或指针值,以地址为值的变量称为指针变量,简称指针(pointer)。我们知道,机器语言层对各种对象的操作都要通过地址。指针变量里保存程序对象的地址,通过它们就可以访问和处理有关对象。高级语言里的指针是访问程序对象的手段,以便能更灵活方便地实施操作。
    对指针变量的操作包括:

    • 指针赋值

      将程序对象的地址(如变量地址,还有其他情况。为简单起见,下面以变量为例)存入指针变量。
      当一个指针变量保存了某个变量的地址时,也说该指针指向了那个变量。

    • 通过指针访问被指对象(变量),称为间接访问。

    7.2 指针变量的定义和使用

    指向整型变量的指针也简称为整型指针。

    取变量地址的操作用一元运算符&;间接访问操作用一元运算符*,也称间接操作。这两个运算符与其他一元运算符的优先级相同,自右向左结合。

    利用指针解决问题的方案包括三方面:函数定义时用指针参数;函数里通过指针参数间接访问被指变量;函数调用时把变量地址传给函数。

    函数调用时形参与实参的关系:

    空指针

    空指针是个特殊指针值,也是唯一对任何指针类型都合法的指针值。一个指针变量具有空指针值,表示它当时没指向有意义的东西,处于闲置状态。空指针值用 0 表示,这个值绝不会是任何程序对象的地址。给一个指针赋值 0 就表示要它不指向任何有意义的东西。为了提高程序的可读性,标准库定义了一个与 0 等价的符号常量NULL。

    7.3 指针与数组

    
    int *p1, *p2, *p3, *p4; 
    int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    p1 = &a[0]; p2 = p1; p3 = &a[5]; p4 = &a[10]; 
    

    指针运算:间接访问
    由指针值出发进行的运算称为指针运算。

    可以通过下面各种方式遍历数组a,打印出a中各个元素:

    for (p1 = a, p2 = a+10; p1 < p2; ++p1)
         printf("%d
    ", *p1); 
    for (p1 = a; p1 < a+10; ++p1)
         printf("%d
    ", *p1); 
    for (p1 = p2 = a; p1 - p2 < 10; ++p1)
         printf("%d
    ", *p1); 
    for (p1 = a; p1 - a < 10; ++p1)
         printf("%d
    ", *p1);
    

    用指针方式实现计算字符串长度的函数。第一种实现方式是:

    
    int strLength (const char *s) {     
    	int n = 0;     
    	while (*s != '') { 
    	    ++s; 
    	    ++n; 
    	}
        return n; 
    } 
    
    
    int strLength (const char *s) {     
    	char *p = s;     
    	while (*p != '') ++p;     
    	return p - s; 
    } 
    

    例4,考虑用指针参数的方式写出上一章的数组元素划分函数。
    为函数定义一对界定数组范围(或数组中一段)的指针参数和一个划分值;
    让函数返回划分后的分界位置,这里用指向数组元素的指针值,令它指向大于等于划分值的第一个元素。
    如果不存在这种元素,返回指向(数组)序列后面一个位置的指针值。
    第一个指针参数指向序列的第一个元素,第二个指针
    参数指向序列最后元素之后一个位置。
    可以给出下面的定义:

    
    double* partition(double *begin, double *end, double cut) {     while (begin < end) { 
            while (begin < end && *begin < cut) ++begin;         while (begin < end && *(--end) >= cut) ;          if (begin < end) {             double x = *begin; 
                *begin = *end; 
                *end = x; 
                ++begin; 
            }     } 
        return begin; 
    } 
     
    

    这里的大循环内部还是用了两个while循环,其中的一个更新向右移的指针,另一个更新向左移的指针。在条件语句内部定义了一个用于交换元素的临时变量。第二个内部的while 语句值得注意。该循环以空语句为体,循环条件中还包括对变量end 的更新。当然也可以用更普通的形式写这个循环。但目前写法很紧凑,熟悉 C 语言的人们经常采用这类简洁写法,因此,在学习 C 语言和程序设计中也应逐渐习惯这类写法。
    图 7.6 展示了上述函数对一个数组的处理过程,
    假定函数调用时以 5 作为数据的分界值。

    • (1) 函数开始时,两个参数指针分别指向序列两端(半闭半开);
    • (2) 两个内部循环第一次执行之后,两个指针分别指向一对需要交换的元素;
    • (3) 交换元素并更新指针之后的现场;
    • (4) 两个内部循环执行之后,begin指针没有动,end指针向左移了一个位置;
    • (5) 交换并移动指针之后,循环条件失败,循环终止。返回的指针值指向划分之后大于等于划分值的那一段的第一个元素。

    7.4 指针数组

    指针也是数据,自然就可以定义指针的数组。指针数组在复杂的程序里使用广泛。

    7.5 多维数组作为参数的通用函数

    按语言规定,当函数参数是两维或更多维数组时,参数说明必须给出除第一维外其他各维的大小。
    面讨论可以写出下面函数定义,函数由两个整型参数得到数组两个维的大小:

    void prtMatrix (int m, int n, int *mp) {    
    int i, j;     
    	for (i = 0; i < m; ++i) {         
    	for (j = 0; j < n; ++j)             
    		printf("%d ", *(mp + i * n + j));         
    	putchar('
    '); 
        }
    } 
    

    如果要打印上面定义的数组a,正确的函数调用形式是:
    prtMatrix(10, 8, &a[0][0]);
    这里&a[0][0]在类型上是指向整型的指针,这正是函数参数mp所要求的类型。

    7.6 动态存储管理

    程序中需要用变量(各种简单类型变量、数组变量等)保存被处理数据和各种状态信息,变量在使用前必须安排好存储:放在哪里、占据多少存储单元等等,这个工作被称作存储分配。用机器语言写程序时,所有存储分配问题都需要人处理,这个工作琐碎繁杂、容易出错。在用高级语言写程序时,人通常不需要考虑存储分配细节,主要工作由编译程序在加工程序时自动完成。这也是用高级语言工作效率较高的一个重要原因。
    C 程序里的变量分为几种。外部变量、局部静态变量的存储问题在编译时确定,其存储空间的实际分配在程序开始执行前完成。程序执行中访问这些变量,就是直接访问它们的固定存储位置。对于局部自动变量,在执行进入变量定义所在的复合语句时为它们分配存储。应该看到,这种变量的大小也是静态确定的。例如,局部自动数组的元素个数必须用静态可求值的表达式描述。这样,一个函数在调用时所需的存储量(用于安放其中的所有自动变量)在编译时就完全确定了。函数定义里描述了所需要的自动变量和参数,定义了数组的规模,这些就决定了该函数在执行时实际需要的存储空间大小。

    使用动态存储管理
    1) 注意检查分配的成功与否。人们常用的写法是:
    if ((p = (... )malloc(...)) == NULL) {
    .. ... /
    对分配未成功情况的处理 */
    2) 系统对所分配存储块的使用完全不进行检查。写程序的人需要保证这种使用的正确性,切不可超出实际存储块的范围进行访问。
    3) 动态分配存储块的存在期不依赖于分配该块的位置。如果在某函数里分配了一个块,这个块的存在期与该函数的执行期无关。只有通过free释放这个块,才使其存在期结束。注意,变量存在期的结束时刻就是它所占存储被收回的时刻。
    4) 如果在某函数里分配了动态存储,并通过局部指针访问这种存储块,那么在函数退出前就必须考虑如何处理这些存储块:或是将它们释放;或是将它们的地址赋给存在期更长的指针变量(如全局变量)。否则,在函数退出时局部指针变量撤销,它们所指的尚未释放的存储块就再也找不到了(流失了)。
    5) 其他情况也可能造成存储块丢失,例如一个指向动态块的指针赋了其他值,如果原被指存储块没有其他访问路径,那么就再也无法找到它了。如果存储块丢失,在本程序随后的运行中将永远不能再用这个存储块所占的存储。
    6) 请注意计算机系统里存储管理的关系。一个程序运行时将从操作系统取得一部分存储空间,用于保存其代码和数据。用于数据存储的空间里包括一部分动态存储区,由程序里的动态存储管理系统管理。在这个程序的运行期间,所有动态存储申请都由这块空间里分配。程序代码中释放存储,就是将不用的存储块交还程序的动态存储管理系统。一旦该程序结束,操作系统将收回这个程序所获得的所有存储区域。所以,我们所说“存储流失”是程序内部的问题,而不是整个系统的问题。

  • 相关阅读:
    漫谈LiteOS-端云互通组件-MQTT开发指南(下)
    漫谈LiteOS-端云互通组件-MQTT开发指南(上)
    漫谈LiteOS之开发板-LiteOS移植(基于GD32450i-EVAL)
    漫谈LiteOS-Huawei_IoT_Link_SDK_OTA 开发指导
    SpringBoot 2.3 整合最新版 ShardingJdbc + Druid + MyBatis 实现分库分表
    从零开始实现放置游戏(十五)——实现战斗挂机(6)在线打怪练级
    Windows系统安装最新版本RabbitMQ3.8.3报错解决
    从零开始实现放置游戏(十四)——实现战斗挂机(5)地图移动和聊天
    从零开始实现放置游戏(十二)——实现战斗挂机(3)数据字典和缓存改造
    从零开始实现放置游戏(十一)——实现战斗挂机(2)注册登陆和游戏主界面
  • 原文地址:https://www.cnblogs.com/gdman/p/10003729.html
Copyright © 2020-2023  润新知