免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 4589 | 回复: 7
打印 上一主题 下一主题

[C] C语言指针和数组宝典(0) 目录和序言 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-12-06 11:03 |只看该作者 |倒序浏览
A TUTORIAL ON POINTERS AND ARRAYS IN C
C语言指针和数组宝典
                     by Ted Jensen
                      Version 0.1
      This material is hereby placed in the public domain.
翻译:针尖骑士
BLOG: chinacpp.tianya.cn
日期:2004.10.10
完成日期:
                    TABLE OF CONTENTS
=========================================
目录:
----------------------------------------------------------------------
序言
介绍
第一章: 什么是指针?
第二章: 指针和数组
第三章: 指针和STRINGS
第四章: 更多STRINGS
第五章: 指针和结构体
第六章: 字符串和字符串数组
第七章: 多维数组进阶
第八章: 指向数组的指针
第九章: 指针和动态分配内存
第十章: 指向函数的指针
结语
=========================================
序言
----------------------------------------------------------------------

介绍
    如果一个人精通C语言编程的话,那么他一定已经对如何使用指针和指针的工作过程有很透彻的了解.
不幸的是,对于初学者来说,C语言中的指针与其它计算机语言(比如Fortran,Pascal和Basic)相比更加的难以理解和驾驭.
我写的以下材料就是帮助初学者更好的理解指针的.要从这个材料中学到最多的东西的话,我觉得应该把本材料中的
代码自己在计算机上运行调试一下.因此,我已经尽量的按照ANSI标准写示例代码,以便读者在所有的符合ANSI标准的编译器上都
可以编译和运行该代码.我已经尽量的使我的代码块清晰,以便于读者用ASCII编辑器拷贝该代码块到你自己的系统上,并在你的
系统上编辑和编译该代码.我建议读者这样做,因为这样对你理解该材料很有帮助.
----------------------------------------------------------------------chinacpp.tianya.cn

论坛徽章:
0
2 [报告]
发表于 2006-12-06 11:06 |只看该作者

C语言指针和数组宝典(1)第一章: 什么是指针?

作者BLOG地址:http://chinacpp.tianya.cn
第一章: 什么是指针?
    C语言初学者常常发现学习过程中最难的一件事就是正确理解指针的概念。写本文的目的就是向初学者介绍指针的概念和使用方法。

我发现初学者常常会碰到关于指针问题,主要的原因就在于他们对C语言里的变量的概念理解的不够深入和透彻。因此,我们就先来
讨论C语言中的变量。

    程序里的变量就是有自己名字的一个容器,它的值可以被程序改变。编译器和连接器处理这个变量的方式是:编译器在计算机里分配
一个特定的内存块去存放这个变量的值。这个内存块的大小取决于这个变量可变化的范围。例如,在个人电脑上,整形变量占2个字节,
长整形变量占4个字节。在C语言里,一个变量的大小并不是相同的,比如整形,不同的机型可能会有不同的大小。

    当我们要声明一个变量时,我们要告诉编译器两件事:变量名和变量类型。例如,我们声明一个变量:类型为整形,变量名为k,
那我们在程序里就要这样写:
        int k;

    当编译器看到这句话的“int”部分时,它就会留出2个字节的内存空间,用来存放这个整形变量的值。同时,它也会建立一张符合表,
并在这张符合表里加入这个符号“k”和在内存中留出的两个字节单元的相对应的地址。

因此,后来如果我们写如下的语句:
        k = 2;

在这个语句被执行时,我们的预期是原来为变量k所留出的那块内存单元被放入了值“2”。在C语言里,我们把象k这样的整型变量看作
一个“对象”。

从某种意义上讲,与对象k相关的有两种值:一种是被存入内存单元的整数值(就是上例中的“2”),另一种是整数值被存在内存中的
位置(也就是k的地址)。有些文章把这两个值分别叫做:右值(are value)和左值(el value)。

在有些语言里,左值是允许出现在赋值操作符左边的值(也就是右值的地址)。右值是出现在赋值语句右边的值(就是上例中的“2”)。
右值并不能被用在赋值操作符的左边,因此:2 = k; 是非法的。

实际上,在C语言里,对上面“左值”的定义已经有所修改。依据K&R-2(197页):[1]
一个对象就是一个被命名了的存储区域;一个“左值”是引用对象的表达式。

但是,就这点而言,原来引用上面的定义是充分的。当我们更加的熟悉“指针”的时候,我们将更加详细的讨论这个知识点。

好,现在让我们考虑下面的例子:
int j, k;
k = 2;
j = 7; <-- line 1
k = j; <-- line 2

在这个例子中,编译器把第一行的j解释成变量j的地址。但第二行j被解释成“右值”(因为它在赋值操作符‘=’的右边)。即在这里j被
看成是内存中存放j这个变量的值,在这个例子中就等于7,因此,7被赋值给左值k。

在上面的例子中,我们使用了2个字节长的INT,以便赋值操作都是从内存中的一块内存区拷贝2个字节到另外一块内存区。如果我们使用
LONG型变量,赋值操作就会拷贝4个字节。

现在我们可以这样认为,一个变量被存放在一个LVALUE(一个地址)中,需要存放一个变量的大小依赖于使用的系统。一个旧式的桌面
系统,带有64K内存的。一个指针的地址在内存中可以用两个字节来表示。带有更大内存的计算机系统将需要更多的字节来表示一个地址。
有些计算机,比如IBM PC 在特定的环境下,可能需要一个段地址&偏移地址共同来表示一个内存地址。一个指针在内存中实际的大小并
不是最重要的,只要我们能够告诉编译器我们想去存一个变量到一个地址就可以了。

像上面说的变量就被称为“指针变量”(希望在稍后你会更加清楚的理解这个名词)。在C 语言中,要定义一个指针变量,我们需要在
变量的名字前面加上个‘*’号。在此时,我们也给我们的定义的指针赋予一种类型,这个类型就是我们将存入我们定义的指针变量中的
地址所存放的数据的类型。
例如,下面这个变量的声明:
  int *ptr;
    ptr 是我们定义的这个变量的名字(正像'k'是我们定义的整型变量的名字一样)。'*'告知编译器我们想要的是一个指针变量,
即在内存中留出多少个字节去存贮一个地址变量。'int'是说我们想要用定义的指针变量去存贮一个整型的地址。也可以说是定义了
一个指向一个整型的指针。但是,请记住当我们写"int k;"这句话时,我们并没有给'k'赋值。如果这句话被定义在一个函数的外面,
那么很多编译器将把该变量初始化为零。同样,ptr也没有被赋值,就是说我们没有存储一个地址进我们上面声明的指针变量里。此时,
如果变量声明在函数的外面,则编译器同样会将该变量初始化为一个被编译器定义为'NULL'的值。'NULL'被称为一个空指针。在大多数
情况下,无需做任何动作,NULL都被自动定义为零。但不同的编译器也会有些不同。尽管0是一个整型,而NULL却不是。对程序员来说,
NULL是一个非常小的数列,因此,在写程序时,无论NULL的值是多少,"NULL==0" 的逻辑值永远都是'真'。

    下面我们来讲如何使用我们新定义的指针变量'ptr'。假设我们现在想把整型变量'k'的地址存储到ptr中。我们将会使用一元操作符
'&',写法如下:
    ptr = &k;
   
    '&'的意思是把变量'k'的左值(即地址值)取出(注:尽管k在赋值操作符'='的右手边),将此值拷贝给指针ptr的内容。 现在,
ptr就被称为“指向k”。请大家不要着急,这里只是讲了指针的一个应用。下面还有更精彩的内容等着你呀!

    下面来讲讲'解引用操作符*','*'被称为"解引用操作符",用法如下:
    *ptr = 7;
    这句话将执行如下操作:拷贝'7'到ptr这个指针所指向的地址。因此,如果ptr指向k,上面的语句将设置k的值为7。那就是说,当
我们这样使用'*'的时候,表示我们引用的是ptr指针指向的变量的值,而不是ptr指针本身的值。
   
    同样的,我们可以这样写:
    printf( "%d\n", *ptr );
打印在屏幕上的将是被ptr所指向地址中存贮整型值。

    现在我们将上面的几个语句合成一个小程序,运行并详细查看输出结果。

-------------------------------------------------
#include <stdio.h>

int j, k;
int *ptr;


int main(void)
{
   j = 1;
   k = 2;
   ptr = &k;
   printf("\n");
   printf("j has the value %d and is stored at %p\n",j,&j);
   printf("k has the value %d and is stored at %p\n",k,&k);
   printf("ptr has the value %p and is stored at %p\n",ptr,&ptr);
   printf("The value of the integer pointed to by ptr is %d\n",
           *ptr);
   return 0;
}
---------------------------------------        
           
复习:
                1, 声明一个变量是通过给出一个类型和名称(例如:int k;)
                2, 声明一个指针变量通过给处一个类型和名称(例如:int *ptr;),在此这个'*'告诉编译器ptr是一个指针型变量,这个类型'int'
                   告诉编译器这个指针是指向什么类型的变量的(在这里是指整型)
                3,一旦我们声明了一个变量后,我们就可以通过在他的名字前面加上一个一元操作符'&'来得到这个变量的地址,比如:&k。
                4,我们可以'解引用'一个指针,即通过在变量名前面加一元操作符'*'来引用这个指针所指向变量的值。  
          5,一个变量左值是指这个变量的地址值。即在内存中被存储的位置。一个变量的右值是存储在这个变量中的值。
          
第一章参考数目:
           [1]  "The C Programming Language"   2nd Edition
          B. Kernighan and D. Ritchie
          Prentice Hall
          ISBN 0-13-110362-8

论坛徽章:
0
3 [报告]
发表于 2006-12-06 11:08 |只看该作者

C语言指针和数组宝典(2)第二章: 指针类型和数组

作者BLOG:chinacpp.tianya.cn
->==================================================================
第二章: 指针类型和数组

    好的,让我们继续前进。我们为什么要定义一个指针所指向变量的类型呢?例如在: int *ptr;
定义变量类型是在后来的语句:*ptr = 2;时,编译器知道往指针所指向的位置拷贝多少个字节。如果ptr被声明成指向一个整型变量,
将被拷贝2个字节,如果是一个LONG型变量,将被拷贝4个字节。同样,当定义的是FLOAT,DOUBLE型时,将有合适数目的字节被拷贝。
同样,也有一些其它的方法来定义和使用指针变量以使编译器可以解释这些写法的代码。例如:假设有一块内存,连续存放了10个整型,
即有20个字节被留出以存放10个整型变量。
    现在,让我们将我们的指针变量ptr指向这块内存中的第一个整型变量的地址上。进一步,我们假设这个整型变量被存放在内存地址
为100(十进制)的地址单元上。当我们写下面的语句时将表示什么含义:
    ptr + 1;
    因为编译器知道这是一个指针(即它的值是一个地址),并且该指针指向的是一个整型变量(它当前的地址-100就是这个整型变量
所存放的地址)。ptr上加2(整型在内存中存放需要两个字节)就表示指针指向了下一个整型变量,内存位置变为了102。同样,当ptr
被声明成LONG型指针(即指向LONG型变量地址的指针),ptr上要加4(LONG型在内存中存放需要4个字节)才表示指针指向了下一个LONG
型变量。这同样使用与其它数据类型的指针,包括DOUBLE,FLOAT和用户自定义的结构类型变量。这里的'+'并不是我们一般意义上的加法。
在C语言中,它有个专门的术语:指针算术。在后面的课程中我们还会提到。
    同样的,++ptr和ptr++都等同于ptr + 1,增加一个指针使用一元操作符'++',无论是前缀还是后缀,在内存中地址将增加该地址单元
所存放的变量类型的长度(字节数),比如:整型是2个字节,LONG型是4个字节等。
    因此,一块存放了10个连续整型的内存单元被定义时,既是定义了一个整型数组,这样就在数组和指针之间建立了一种有趣的关系。

    假设如下:
    int my_array[] = {1,23,17,4,-5,100};   
   
    在此我们定义了一个有6个元素的数组,我们使用该数组的下标来引用数组中的每个元素。也就是用my_array[0]..my_array[5]来表示
。但是我们也可以通过指针来引用数组中的每个元素。语句如下:
    int *ptr;
    ptr = &my_array[0]; /*将我们的指针指向数组的第一个整型元素上*/
   
    我们可以用数组符号或者通过对指针解引用来打印出我们的数组。
    下面的代码就说明了这个:
------------------------------------------------------
#include <stdio.h>

int my_array[] = {1,23,17,4,-5,100};
int *ptr;

int main(void)
{
    int i;
    ptr = &my_array[0];     /* point our pointer to the first
                                      element of the array */
    printf("\n\n");
    for(i = 0; i < 6; i++)
    {
      printf("my_array[%d] = %d   ",i,my_array);   /*<-- A */
      printf("ptr + %d = %d\n",i, *(ptr + i));        /*<-- B */
    }
    return 0;
}
----------------------------------------------------

    编译并运行上面的程序代码,注意第A行和第B行,在两种情况下程序打印出了相同的结果。
    也观察我们是如何对指针进行解引用操作的,我们首先将ptr加i,然后对得到的新指针进行解引用。将第B行修改成下面的语句
    printf("ptr + %d = %d\n",i, *ptr++);
    重新运行该程序,然后再改变为:
    printf("ptr + %d = %d\n",i, *(++ptr));并重新编译运行。每次尝试预言输出结果并与真正的输出结果进行比较。
   
    在C语言中,我们即可以使用ptr = &my_array[0];也可以使用 ptr = my_array; 这两个语句是等同的。这也使很多文章中说:
一个数组名就是一个指针。这种说法是欠妥的,正确的说法应该是:数组名是该数组中第一个元素的地址。不能把数组和指针混为一谈。
例如,我可以写:ptr = my_array;  但是不能写:my_array = ptr;   
    原因是ptr 是一个变量,而my_array是一个常量。
    即是说:一旦 my_array[]被声明后, 数组my_array的第一个元素在内存中的地址位置将不能被改变。
   
    一个对象(_object_)是一块被命名的存储区。
    一个左值(_lvalue_)是引用一个对象(_object_)的表达式。
   
    这就引出了一个有趣的问题,因为my_array是一个被命名的存储区,那为什么my_array在上面的赋值语句中不能成为左值呢?
要解决这个问题,有人就把my_array看作为一个"不能被修改的左值"。
   
    改变上面的程序段:
    ptr = &my_array[0];     to     ptr = my_array; 然后再次运行它并去验证是否结果相同。
   
    现在让我们去研究一下"ptr"和"my_array"在上面的使用中有什么不同?
    有些程序员将一个数组名看作为一个常量(_constant_)指针。我们如何理解这个呢?在此,要理解术语"constant",让我们回头去
看看当时对术语"variable"的定义吧。当我们声明一个变量时,编译器将在内存中留出一块存储区,以便去存放适当类型的变量值。
一个变量的名字可以按照两种方式被编译器解释。一,当这个变量名被用在赋值操作符(=)的左边时,编译器就解释为:将赋值操作符
(=)右边的值拷贝到左边变量名所代表的内存位置。二,当这个变量名被用在赋值操作符(=)的右边时,编译器就将该变量名解释为
存放那个变量的值的内存地址值。

例如:int i, k;
                        i = 2;
                       
                        在此,i 是一个变量,占去了计算机数据段的一段内存空间。2 是一个常量,它不是被存在计算机的数据段,而是直接被存放在
计算机的程序段。既是说,当我们写语句:k = i; 时,就是告诉编译器在运行时去创建机器代码,此机器代码将去寻找内存地址&i,
并将该地址值赋值给k;而代码:i = 2; 则表示编译器只是简单地放2到代码段,而不会引用数据段。即:k和i是个对象,但2并不是
一个对象。
    同样,在上面,因为'my_array'是一个常量,编译器知道该数组被存储的位置,即知道array[0]的地址。
   
    ptr = my_array;
    在上面这个代码中,我们简单的用一个地址作为一个常量,并没有引用到数据段。
    好,有很多内容需要消化吸收,我并不期望一个初学者在第一次读到这里时都能理解所有的内容,希望你在以后的学习和实践中能
回来重读第二章的内容。现在,让我们继续学习下面的内容。

论坛徽章:
0
4 [报告]
发表于 2006-12-06 11:11 |只看该作者
系列文章最好放一起,代码加上code标签,谢谢合作!

论坛徽章:
0
5 [报告]
发表于 2006-12-06 12:35 |只看该作者
原帖由 hjzheng 于 2006-12-6 11:08 发表
作者BLOG:chinacpp.tianya.cn
->==================================================================
第二章: 指针类型和数组

    好的,让我们继续前进。我们 ...


斜了!能把Discuz!代码禁用吗?

论坛徽章:
0
6 [报告]
发表于 2006-12-06 12:41 |只看该作者
整个附件放上来,多好.

论坛徽章:
0
7 [报告]
发表于 2006-12-06 20:57 |只看该作者
>> 一个数组名就是一个指针。这种说法是欠妥的,正确的说法应该是:数组名是该数组中第一个元素的地址。
>> 不能把数组和指针混为一谈。

不能把数组和指针混为一谈,指针和数组是有区别的,这是正确的。

虽然数组不是指针,但是在程序中却通常作为指针来使用。在这种情况下,数组(数组名)等同于指针。“第一个元素的地址”其实就是指向第一个元素的指针。

>> int my_array[] = {1,23,17,4,-5,100};
>>
>> 因为'my_array'是一个常量,编译器知道该数组被存储的位置,即知道array[0]的地址。

如果在编译期间能够确定数组的位置的话,那么数组名(如上面的 my_array) 或者 &my_array[0] 确实是一个常量,一个地址常量。然而,只有静态数组才能在编译期间确定其地址,而非静态数组只有在运行期间其地址才能得到确定,因此非静态数组(包括其数组名)不是常量。

在下面的帖子中我也驳斥了“数组名是一个常量”这种不正确的说法:
http://bbs.chinaunix.net/viewthr ... p;extra=&page=2

论坛徽章:
0
8 [报告]
发表于 2006-12-15 10:41 |只看该作者
我也想把这个文章的附件附上来,但是现在还不行,这篇文章我是在以前的一个地方找到的,是英文的,当时看了,觉得写的很不错,一直想把它翻译出来给国内的朋友看,但是一直没有时间,到现在才翻译了3章,我会抓紧时间翻译出来。谢谢大家的关心。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP