⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 第9章 数 组.txt

📁 我自己整理的c语言教程 来自 c语言之家
💻 TXT
📖 第 1 页 / 共 2 页
字号:
for (p = a; p<&a[MAX];  ++p   ) 
{ 
      x=*p; 
      /* do aomething with x * / 
}
    这两种方式有什么区别呢?两种方式中的初始情况和递增运算是相同的,作为循环条件的比较表达式也是相同的(下文中将进一步讨论这一点)。区别在于“x=a[]”和“x=*p”,前者要确定a[i]的地址,因此需要将i和类型x的大小相乘后再与数组a中第一个元素的地址相加;
后者只需间接引用指针p。间接引用是快速的,而乘法运算却比较慢。
    这是一种“微效率”现象,它可能对程序的总体效率有影响,也可能没有影响。对方式a来说,如果循环体中的操作是将数组中的元素相加,或者只是移动数组中的元素,那么每次循环中大部分时间就消耗在使用数组下标上;如果循环体中的操作是某种I/O操作,或者是函数调用,那么使用数组下标所消耗的时间是微不足道的。
    在有些情况下,乘法运算的开销会降低。例如,当类型x的大小为1时,经过优化就可以将乘法运算省去(一个值乘以1仍然等于这个值);当类型x的大小是2的幂时(此时类型x通常是系统固有类型),乘法运算就可以被优化为左移位运算(就象一个十进制的数乘以10一样)。
    在方式b中,每次循环都要计算&a[MAX],这需要多大代价呢?这和每次计算a[i]的代价相同吗?答案是不同,因为在循环过程中&a[MAX]是不变的。任何一种合格的编译程序都只会在循环开始时计算一次&a[MAX],而在以后的每次循环中重复使用这次计算所得的值。
在编译程序确认在循环过程中a和MAX都不变的前提下,方式b和以下代码的效果是相同的:
/* how the compiler implements version (b) */ 
X      *temp =  &a[MAX];     /* optimization */ 
for (p = a; p< temp; ++p  ) 
{
    x =*p;
    /*do something with x * / 
}

    遍历数组元素还可以有另外两种方式,即以递减而不是递增的顺序遍历数组元素。对按顺序打印数组元素这样的任务来说,后两种方式没有什么优势,但是对数组元素相加这样的任务来说,后两种方式比前两种方式更好。通过下标并且以递减顺序遍历数组元素的方式(方式c)如下所示(人们通常认为将一个值和。比较的代价要比将一个值和一个非零值比较的代价小:

/* version (c) */ 
for (i = MAX - 1; i>=0; --i)

{ 
      x=a[i]; 
      /* do aomcthing with x * / 
}

    通过指针并以递减顺序遍历数组元素的方式(方式d)如下所示,其中作为循环条件的比较表达式显得很简洁:

/* version (d) */ 
for (p = &a[MAX - 1]; p>=a;  --p  ) 
{ 
      x =*P; 
      /*do something with x * / 
}

    与方式d类似的代码是很常见的,但不是绝对正确的,因为循环结束的条件是p小于a,而这有时是不可能的(见9.3)。
    通常人们会认为“任何合格的能优化代码的编译程序都会为这4种方式产生相同的代码”,但实际上许多编译程序都没能做到这一点。笔者曾编写过一个测试程序(其中类型x的大小不是2的幂,循环体中的操作是一些无关紧要的操作),并用4种差别很大的编译程序编译这个程序,结果发现方式b总是比方式a快得多,有时要快两倍,可见使用指针和使用下标的效果是有很大差别的(有一点是一致的,即4种编译程序都对&a[MAX]进行了前文提到过的优化)。
    那么在遍历数组元素时,以递减顺序进行和以递增顺序进行有什么不同呢?对于其中的两种编译程序,方式c和方式d的速度基本上和方式a相同,而方式b明显是最快的(可能是因为其比较操作的代价较小,但是否可以认为以递减顺序进行要比以递增顺序进行慢一些呢?);
  对于其中的另外两种编译程序,方式c的速度和方式a基本相同(使用下标要慢一些),但方式d的速度比方式b要稍快一些。
    总而言之,在编写一个可移植性好、效率高的程序时,为了遍历数组元素,使用指针比使用下标能使程序获得更快的速度;在使用指针时,应该采用方式b,尽管方式d一般也能工作,但编译程序为方式d产生的代码可能会慢一些。  
    需要补充的是,上述技巧只是一种细微的优化,因为通常都是循环体中的操作消耗了大部分运行时间,许多C程序员往往会舍本求末,忽视这种实际情况,希望你不要犯相同的错误。

请参见:    
    9.2可以使用数组后第一个元素的地址吗?    
    9.3为什么要小心对待位于数组后面的那些元素的地址呢?

    9.6  可以把另外一个地址赋给一个数组名吗?
    不可以,尽管在一个很常见的特例中好象可以这样做。
    数组名不能被放在赋值运算符的左边(它不是一个左值,更不是一个可修改的左值)。一个数组是一个对象,而它的数组名就是指向这个对象的第一个元素的指针。    
    如果一个数组是用extern或static说明-的,则它的数组名是在连接时可知的一个常量,你不能修改这样一个数组名的值,就象你不能修改7的值一样。
    给数组名赋值是毫无根据的。一个指针的含义是“这里有一个元素,它的前后可能还有其它元素”,一个数组名的含义是“这里是一个数组中的第一个元素,它的前面没有数组元素,并且只有通过数组下标才能引用它后面的数组元素”。因此,如果需要使用指针,就应该使用指针。
    有一个很常见的特例,在这个特例中,好象可以修改一个数组名的值:
    void f(chara[12])
    {
        ++a;  /*legal!*/
    }
    秘密在于函数的数组参数并不是真正的数组,而是实实在在的指针,因此,上例和下例是等价的:
    void f(char *a)
    {
        ++a;  /*certainlylegal*/
    }
    如果你希望上述函数中的数组名不能被修改,你可以将上述函数写成下面这样,但为此你必须使用指针句法:
    void{(char *const a)
    {
        ++a;  /*illegal*/
    }
    在上例中,参数a是一个左值,但它前面的const关键字说明了它是不能被修改的。

    请参见:
    9. 4在把数组作为参数传递给函数时,可以通过sizeof运算符告诉函数数组的大小吗?
    
    9.7 array_name和&array_name有什么不同?
    前者是指向数组中第一个元素的指针,后者是指向整个数组的指针。
    注意;笔者建议读者读到这里时暂时放下本书,写一下指向一个含MAX个元素的字符数组的指针变量的说明。提示:使用括号。希望你不要敷衍了事,因为只有这样你才能真正了解C语言表示复杂指针的句法的奥秘。下文将介绍如何获得指向整个数组的指针。
    数组是一种类型,它有三个要素,即基本类型(数组元素的类型),大小(当数组被说明为不完整类型时除外),数组的值(整个数组的值)。你可以用一个指针指向整个数组的值:
    char  a[MAX];    /*arrayOfMAXcharacters*/
    char    *p;      /*pointer to one character*/
    /*pa is declared below*/
    pa=&al
    p=a;             /* =&a[0] */
    在运行了上述这段代码后,你就会发现p和pa的打印结果是一个相同的值,即p和pa指向同一个地址。但是,p和pa指向的对象是不同的。
    以下这种定义并不能获得一个指向整个数组的值的指针:
    char *(ap[MAX]);
    上述定义和以下定义是相同的,它们的含义都是“ap是一个含MAX个字符指针的数组”;
    char *ap[MAX];

    9.8  为什么用const说明的常量不能用来定义一个数组的初始大小?
    并不是所有的常量都可以用来定义一个数组的初始大小,在C程序中,只有C语言的常量表达式才能用来定义一个数组的初始大小。然而,在C++中,情况有所不同。
    一个常量表达式的值在程序运行期间是不变的,并且是编译程序能计算出来的一个值。在定义数组的大小时,你必须使用常量表达式,例如,你可以使用数字:
    char    a[512];
    或者使用一个预定义的常量标识符:
    #define MAX    512
    /*...  */
    char    a[MAX];
    或者使用一个sizeof表达式:
    char  a[sizeof(structcacheObject)];
    或者使用一个由常量表达式组成的表达式:
    char    buf[sizeof(struct cacheObject) *MAX];
    或者使用枚举常量。
    在C中,一个初始化了的constint变量并不是一个常量表达式:
    int    max=512;    /* not a constant expression in C */
    char  buffer[max];  /* notvalid C */
然而,在C++中,用const int变量定义数组的大小是完全合法的,并且是C++所推荐的。尽管这会增加C++编译程序的负担(即跟踪const int变量的值),而C编译程序没有这种负担,但这也使C++程序摆脱了对C预处理程序的依赖。

    请参见;
    9.1数组的下标总是从0开始吗?
    9.2可以使用数组后面第一个元素的地址吗?

    9.9  字符串和数组有什么不同?
    数组的元素可以是任意一种类型,而字符串是一种特殊的数组,它使用了一种众所周知的确定其长度的规则。
    有两种类型的语言,一种简单地将字符串看作是一个字符数组,另一种将字符串看作是一种特殊的类型。C属于前一种,但有一点补充,即C字符串是以一个NUL字符结束的。数组的值和数组中第一个元素的地址(或指向该元素的指针)是相同的,因此通常一个C字符串和一个字符指针是等价的。
    一个数组的长度可以是任意的。当数组名用作函数的参数时,函数无法通过数组名本身知道数组的大小,因此必须引入某种规则。对字符串来说,这种规则就是字符串的最后一个字符是ASCII字符NUL('\0')。
    在C中,int类型值的字面值可以是42这样的值,字符的字面值可以是‘*’这样的值,浮点型值的字面值可以是4.2el这样的单精度值或双精度值。
    注意:实际上,一个char类型字面值是一个int类型字面值的另一种表示方式,只不过使用了一种有趣的句法,例如当42和'*'都表示char类型的值时,它们是两个完全相同的值。然而,在C++中情况有所不同,C++有真正的char类型字面值和char类型函数参数,并且通常会更仔细地区分char类型和int类型。
  ,整数数组和字符数组没有字面值。然而,如果没有字符串字面值,程序编写起来就会很困难,因此C提供了字符串字面值。需要注意的是,按照惯例C字符串总是以NUL字符结束,因此C字符串的字面值也以NUL字符结束,例如,“six times nine”的长度是15个字符(包括NUL终止符),而不是你看得见的14个字符。
    关于字符串字面值还有一条鲜为人知但非常有用的规则,如果程序中有两条紧挨着的字符串字面值,编译程序会将它们当作一条长的字符串字面值来对待,并且只使用一个NUL终止符。也就是说,“Hello,”world”和“Hello,world”是相同的,而以下这段代码中的几条字符串字面值也可以任意分割组合:
    char    message[]=
    ”This is an extremely long prompt\n”
    ”How long is it?\n”
    ”It's so long,\n”
    ”It wouldn't fit On one line\n”;
    在定义一个字符串变量时,你需要有一个足以容纳该字符串的数组或者指针,并且要保证为NUL终止符留出空间,例如,以下这段代码中就有一个问题:
    char greeting[12];
    strcpy(greeting,”Hello,world”);  /*trouble*/
    在上例中,greeting只有容纳12个字符的空间,而“Hello,world”的长度为13个字符(包括NUL终止符),因此NUL字符会被拷贝到greeting以外的某个位置,这可能会毁掉greetlng附近内存空间中的某些数据。再请看下例:
    char  greeting[12]=”Hello,world”;/*notastring*/
    上例是没有问题的,但此时greeting是一个字符数组,而不是一个字符串。因为上例没有为NUL终止符留出空间,所以greeting不包含NUL字符。更好一些的方法是这样写:
    char  greeting[]=”Hello,world”;
    这样编译程序就会计算出需要多少空间来容纳所有内容,包括NUL字符。
    字符串字面值是字符(char类型)数组,而不是字符常量(const char类型)数组。尽管ANSIC委员会可以将字符串字面值重新定义为字符常量数组,但这会使已有的数百万行代码突然无法通过编译,从而引起巨大的混乱。如果你试图修改字符串字面值中的内容,编译程序是
    不会阻止你的,但你不应该这样做。编译程序可能会选择禁止修改的内存区域来存放字符串字面值,例如ROM或者由内存映射寄存器禁止写操作的内存区域。但是,即使字符串字面值被存放在允许修改的内存区域中,编译程序还可能会使它们被共享。例如,如果你写了以下代码(并且字符串字面值是允许修改的):
    char    *p="message";
    char    *q="message";
    p[4]='\0'; /* p now points to”mess”*/
    编译程序就会作出两种可能的反应,一种是为p和q创建两个独立的字符串,在这种情况下,q仍然是“message”;一种是只创建一个字符串(p和q都指向它),在这种情况下,q将变成“mess”。
    注意:有人称这种现象为“C的幽默”,正是因为这种幽默,绝大多数C程序员才会整天被自己编写的程序所困扰,难得忙里偷闲一次。

    请参见:
    9.1 数组的下标总是从0开始吗?

 
 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -