Chinaunix首页 | 论坛 | 博客
  • 博客访问: 996168
  • 博文数量: 186
  • 博客积分: 10020
  • 博客等级: 上将
  • 技术积分: 1676
  • 用 户 组: 普通用户
  • 注册时间: 2006-08-14 17:08
文章存档

2011年(5)

2009年(11)

2008年(2)

2007年(111)

2006年(57)

我的朋友

分类: LINUX

2007-03-20 02:24:53

五.指针与数组
1.指针与地址
1)机器的一个字节可以存放一个char类型的数据,两个相邻的字节存储单元可存储一个short(短整型)类型的数据,而4个相邻的字节存储单元可存储一个long(长整型)类型的数据。
2)指针是能够存放一个地址的一组存储单元(通常是两个或4个字节)
3)如果c的类型是char,并且p是指向c的指针.  char *p;  char c;
  p = &c;  将把c的地址赋值给变量p,我们称p为"指向"c的指针
4)地址运算符&只能应用于内存中的对象,即变量与数组元素。它不能作用于表达式,常量或register类型的变量。它不能作用于表达式,常量或register类型的变量。
5)int *ip;  该声明语句表明表达式*ip的结果是int类型
6)我们应该注意,指针只能指向某种特定类型的对象,也就是说,每个指针都必须指向某种特定的数据类型。
   (一个例外情况是指向void类型的指针可以存放指向任何类型的指针,但它不能间接引用其自身)
7)最后说明一点,因为指针也是变量,所以在程序中可以直接使用,而不必通过间接引用的方法使用。
  例如,如果iq是另一个指向整型的指针,那么语句  iq = ip
 
2.指针与函数参数
1)由于C语言是以传值的方式将参数值传递给被调用函数,因此,被调用函数不能直接修改主调用函数中变量的值(由于参数传递采用传值方式,函数仅仅交换了参数的副本的值)
2)如何解决以上那个问题呢??采取传地址的方式,把所有的参数都声明为指针,并且通过这些指针来间接访问它们指向的操作数(指针参数使得被调用函数能够访问和修改主调函数中对象的值)
 
3.指针与数组
1)用指针编写的程序比用数组下标编写的程序执行速度快,但另一方面,用指针实现的程序理解起来稍微困难一些
 
2)例如:int a[10];      int *pa;
pa = &a[0];则可以将指针pa指向数组a的第0个元素,也就是说,pa的值为数组元素a[0]的地址
x = *pa;   将把数组元素a[0]中的内容复制到变量x中
pa + 1; 如果pa指向数组中的某个特定元素,那么,根据指针运算的定义,pa+1将指向下一个元素
pa + i; 将指向pa所指向数组元素之后的第i个元素
pa - i; 将指向pa所指向数组元素之前的第i个元素
因此,如果指针pa指向a[0],那么*(pa+1)引用的是数组元素a[1]的内容,pa+i是数组元素a[i]的地址,*(pa+i)引用的是数组元素a[i]的内容
pa = &a[0]; <=> pa =a; pa和a具有相同的值,因为数组名所代表的就是该数组最开始的一个元素的地址
对数组元素a[i]的引用也可以写成*(a+i)这种形式
在计算数组元素a[i]的值时,C语言实际上先将其转换为*(a+i)的形式,然后再进行求值,因此在程序中这两种形式是等价的
&a[i] <=> a+i;  两者等价,a+i是a之后第i个元素的地址
pa[i] <=> *(pa+i) 如果pa是一个指针,那么,在表达式中也可以在它的后面加下标
 
3)数组名和指针之间有一个不同之处。指针是一个变量,因此,在C语言中,语句pa=a和pa++都是合法的。但数组名不是变量,因此类似于a=pa和a++形式的语句是非法的。
4)当把数组名传递给一个函数时,实际上传递的是该数组第一个元素的地址。在被调用函数中,该参数是一个局部变量,因此,数组名参数必须是一个指针,也就是一个存储地址值的变量。
 
5)在函数定义中,形式参数char s[];和char *s;是等价的。如果将数组名传递给函数,函数可以根据情况判定是按照数组处理还是按照指针处理,在函数中甚至可以同时使用数组和指针这两种表示方法。
在函数f(int arr[]){...} 和 f(int *arr){...}中
f(&a[2])与f(a+2)都将把起始于a[2]的子数组的地址传递给函数f
 
6)如果确信相应的元素存在,也可以通过下标访问数组第一个元素之前的元素。类似于p[-1],p[-2]这样的表达式在语法上都是合法的,它们分别引用位于p[0]之前的两个元素。当然,引用数组边界之外的对象是非法的。
 
4.地址算术运算
1)一般情况下,同其他类型的变量一样,指针也可以初始化;通常,对指针有意义的初始化值只能是0或者是表示地址的表达式,对后者来说,表达式所代表的地址必须是在此前已定义的具有适当类型的数据的地址。
2)0永远不是有效的数据地址,因此,返回值0可用来表示发生了异常事件;
3)指针与整数之间不能相互转换,但0是惟一的例外:常量0可以赋值给指针,指针也可以和常量0进行比较。程序中经常用符号常量NULL代替常量0(符号常量NULL定义在标准头文件中)
4)任何指针与0进行相等或不等的比较运算都有意义。但是,指向不同数组的元素的指针之间的算术或比较运算没有意义
p+n; 表示指针p当前指向的对象之后第n个对象的地址。无论p指向的对象是何种类型,上述结论都成立。例如:如果int类型占4个字节的存储空间,那么在int类型的计算中,对应的n将按4的倍数来计算
p
5)size_t是由运算符sizeof返回的无符号整型。
6)有效的指针运算包括相同类型指针之间的赋值运算;指针同整数之间的加法或减法运算;指向相同数组中元素的两个指针间减法或比较运算;将指针赋值为0或指针与0之间的比较运算。
 
5.字符指针与函数
1)字符串常量是一个字符数组,例如:"I am a string"在字符串的内部表示中,字符数组以空字符'\0'结尾,所以,程序可以通过检查空字符找到字符数组的结尾。字符串常量占据的存储单元数也因此比双引号内的字符数大1。
2)printf("hello,world");实际上printf接受的是一个指向字符数组第一个字符的指针。也就是说,字符串常量可通过一个指向其第一个元素的指针访问。
3)pmessage="now is the time"将把一个指向该字符数组的指针赋值给pmessage.该过程并没有进行字符串的复制,而只是涉及到指针的操作。C语言没有提供将整个字符串作为一个整体进行处理的运算符。
4)复制函数,最后的结果是依次将t指向的字符复制到s指向的位置,直到遇到结束符'\0'为止(同时也复制该结束符)
  void strcpy(char *s, char *t)
  {
    while((s[i] = t[i]) != '\0') --------------------- 版本1
    while((*s = *t) != '\0')     --------------------- 版本2
    while((*s++ = *t++) != '\0') --------------------- 版本3
    {
       s++;
       t++;
    }
  }
 
6.指针数组以及指向指针的指针
#include
#include
#define MAXLINES 5000
char *lineptr[MAXLINES];
int readlines(char *lineptr[], int nlines);
void writelines(char *lineptr[], int nlines);
void qsort(char *lineptr[], int left, int right);
/* 对输入的文本行进行排序 */
main()
{
   int nlines;
   if ((nlines = readlines(lineptr, MAXLINES)) >= 0)
   {
  
   }
}
#define MAXLEN 1000
int getline(char *, int);
char *alloc(int);
/* readlines函数:读取输入行 */
int readlines(char *lineptr[], int maxlines)
{
   int len, nlines;
   char *p, line[MAXLEN];
   nlines = 0;
   while ((len = getline(line, MAXLEN)) > 0)
       if(nlines >= maxlines || (p = alloc(len)) == NULL)
           return -1;
       else {
          line[len-1] = '\0'; /* 删除换行符 */
          strcpy(p, line);
          lineptr[nlines++] = p;
       }
    return nlines;
}
/* writelines函数:写输出行 */
void writelines(char *lineptr[], int nlines)
{
    int i;
    for (i=0; i < nlines; i++)
        printf("%s\n", lineptr[i]);
}
void writelines(char *lineptr[], int nlines)
{
     while (nlines-- > 0)
          printf("%s\n", *lineptr++);
}
char *lineptr[MAXLINES]:lineptr是一个具有MAXLINES个元素的一个维数组,其中数组的每个元素是
                        一个指向字符类型对象的指针。也就是说,lineptr[i]是一个字符指针,
                        而*lineptr[i]是该指针指向的第i个文本行的首字符
 
7.多维数组
1)C语言中二维数组的使用方式和其他语言一样,数组元素按行存储,因此,当按存储顺序访问数组时,最右边的数组下标(即列)变化得最快。
2)数组可以用花括号括起来的初值表进行初始化,二维数组的每一行由相应的子列表进行初始化。
3)如果将二维数组作为参数传递给函数,那么在函数的参数声明中必须指明数组的列数;而数组的行数没有多大关系。
  比如f(int day[2][13]) {...} 或 f(int day[][13]) {...}
  因为数组的行数无关紧要,所以,该声明还可以写成 f(int (*day)[13]) {...}
4)假如去掉括号,声明成int *day[13]:则相当于声明了一个数组,该数组有13个元素,其中每个元素都是一个指向整型对象的指针。
5)一般来说,除数组的第一维(下标)可以不指定大小,其余各维都必须明确指定大小。
 
8.指针数组的初始化
char *month_name(int n)
{
  static char *name[] = {
        "Illegal month","January","February","March","April","May","June",
        "July","August","September","October","November","December" 
  };
  return (n < 1 || n > 12) ? name[0] : name[n];
}
1)name数组的初始化通过一个字符串列表实现,列表中的每个字符串赋值给数组相应位置的元素。第i个字符串的所有字符存储在存储器中的某个位置,指向它的指针存储在name[i]中
2)由于上述声明中没有指明数组name的长度,因此,编译器编译时将对初值个数进行统计,并将这一准确数字填入数组的长度。
 
9.指针与多维数组
int a[10][20]; 已分配200个int类型长度的存储空间
int *b[10];    分配了10个指针,但未初始化
1)假定b的每个元素都指向一个具有20个元素的数组,那么编译器就要为它分配200个int类型长度的存储空间以及10个指针的存储空间。
2)指针数组的一个重要优点在于,数组的每一行长度可以不同,也就是说,b的每个元素不必都指向一个具有20个元素的向量。
char aname[][15] = { "Illegal month", "Jan", "Feb", "Mar" };
aname:
    Illegal month\0  Jan\0                   Feb\0        Mar\0
    0                15                      30           45
   
10.命令行参数
main(int argc, char *argv[])
1)因此*++argv是一个指向参数字符串的指针
2)因此(*++argv)[0] <=> **++argv是它的第一个字符
3)实际上,表达式*++argv[0]其目的是遍历一个特定的参数串。在内层循环中,表达式*++argv[0]对指针argv[0]进行了自增运算。
 
11.指向函数的指针
1)函数本身不是变量,但可以定义指向函数的指针。
  这种类型的指针可以被赋值,存放在数组中,传递给函数以及作为函数的返回值等等
#include
#include
#define MAXLINES 5000
char *lineptr[MAXLINES];
int readlines(char *lineptr[], int nlines);
void writelines(char *lineptr[], int nlines);
int numcmp(char *,char *);
void qsort( void *lineptr[],int left, int right, int (*comp)(void *,void *) );
int main(int argc, char *argv[])
{
     int nlines;             /* 读入的输入行数 */
     int numeric = 0;        /* 若进行数值排序,则numeric的值为1 */
     if ( argc > 1 && strcmp(argv[1],"-n") == 0 )
          numeric = 1;
     if ((nlines = readlines(lineptr, MAXLINES)) >= 0)
     {
         qsort((void **)lineptr, 0, nlines-1, (int (*)(void *,void *))(numeric ? numcmp : strcmp))
         writelines(lineptr, nlines);
         return 0;
     }
     else
     {
         printf("input too big to sort\n");
         return 1;
     }
}
在调用函数qsort的语句中,strcmp和numcmp是函数的地址;因为它们是函数,所以前面不需要加上取地址运算符&,同样的原因,数组名前面也不需要&运算符。
qsort包含一个指针数组,两个整数和一个有两个指针参数的函数;其中,指针数组参数的类型为通用指针类型void *.
由于任何类型的指针都可以转换为void *类型,并且在将它转换回原来的类型时不会丢失信息
void qsort(void *v[],int left,int right,int (*comp)(void *,void *))
{}
int (*comp)(void *,void *)它表明comp是一个指向函数的指针,该函数具有两个void*类型的参数,其返回值类型为int.
例如:if( (*comp)(v[i],v[left]) < 0 )
int *comp(void *,void *)表明comp是一个函数,该函数返回一个指向int类型的指针,这同我们的本意显然有很大的差别
#include
int numcmp(char *s1,char *s2)
{
   double v1, v2;
  
   v1 = atof(s1);
   v2 = atof(s2);
   if (v1 < v2)
       return -1;
   else if (v1 > v2)
       return 1;
   else
       return 0;
}
 
12.复杂声明
1) int   *f();    /*  f:是一个函数,它返回一个指向int类型的指针 */
   int (*pf)();   /* pf:是一个指向函数的指针,该函数返回一个int类型的对象 */
2)char **argv --- argv : pointer to pointer to char
  int (*daytab)[13] --- daytab : pointer to array[13] of int
  int  *daytab[13] --- daytab : array[13] of pointer to int
 
  void *comp() --- comp : function returning pointer to void
  void (*comp)() --- comp : pointer to function returning void
 
  char (*(*x())[])() --- x : function return pointer to array[] of pointer to function return char     
  char (*(*x[3])())[5] --- x : array[3] of pointer to function returning pointer to array[5] of char
阅读(1776) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~