正文

C语言笔记 32005-02-15 19:35:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/book/216.html

分享到:


2.3 指针类型
指针变量中存放的是内存地址,若该地址处存放的是变量,则称该指针为指向变量的指针;若该地址对应某函数的入口,则称该指针为指向函数的指针,简称函数指针。

2.3.1 指向变量的指针
在变量名之前添加“*”号,即可实现指针的定义。注意,这里说“在变量名之前”,而不是“在类型说明之后”,因为后一种做法容易引起误解。后一种做法看似是顺理成章的:类型说明之后加“*”号,构造了一种新的数据类型——指向该类型的指针类型。
但是,如果程序中出现这样的定义:
ArcNode* p, q;
那么,q是指针吗?回想一下数组的定义,如果程序中出现:
int zlen[DCT_SIZE*DCT_SIZE], cnt;
没有人会认为cnt也是数组。“*”和“[ ]”的作用对象都是紧邻它们的变量。那么为什么要将“*”紧靠在类型说明之后,故意造成误解?一个折中的办法是:在涉及指针的定义时,一行定义一个变量,于是上面的定义也就成了:
ArcNode* p;
ArcNode q;

还有两个容易混淆的概念,指针数组和指向数组的指针:
int *ptr;
ptr是指向整型的指针变量,在变量名后加“[ ]”,变为:
int *(ptr[MAX_LENGTH]); 或 int *ptr[MAX_LENGTH];
这时,ptr就成了长度为MAX_LENGTH指针数组,它的元素是指向整型的指针变量。
类似地:
int val[MAX_LENGTH];
val是整型数组,在变量名前后加“*”,变为:
int (*val)[MAX_LENGTH];
由于默认运算符结合优先级的关系,必须加上括号,以强调结合次序。
这时,val成了指向整型数组的指针。
这与前面所述“变量名后加‘[ ]’,原变量类型简单聚合,成数组;变量名前加‘*’,成指针,指向原变量类型。”的规则相符。

2.3.2 函数指针
函数指针是将函数参数化的工具,许多C库函数使用函数指针作为参数,如bsearch、qsort:
qsort((void *)list, length, sizeof(int), Comp);
函数名Comp标识了函数的入口地址,将它作为实际参数,传递给qsort的函数指针形式参数,该形参被定义为:
int (*fcmp)(const void *, const void *)
对比一下Comp函数的定义:
int Comp(const void *p1, const void *p2) {
    ...
}
qsort通过形参fcmp调用Comp函数对数组中的元素比较排序。

函数指针还可以参数化代码体内的控制。另外,利用函数指针数组,也可以创建基于表格驱动的应用程序。

2.3.3 指针的应用

(1) 构造链式数据结构,见结构体的应用(3)

(2) 引用动态分配的数据结构,如线性链表的构造:
void CreateList_L(LinkList *L, int n) {
    LinkList p;
    int i;
    *L = (LinkList)malloc(sizeof(LNode));
    (*L)->next = NULL;
    for(i=n; i>0; --i) {
        p = (LinkList)malloc(sizeof(LNode));
        scanf("%d", &p->data);
        p->next = (*L)->next;
        (*L)->next = p;
    }
}

(3) 实现引用调用(call by reference),见结构体的应用(2)

(4) 访问和迭代数据元素,如:
Status GetElem_L(LinkList L, int i, int *e) {
    int j = 1;
    LinkList p = L->next;
    while(p!=NULL && j<i) {
        p = p->next; ++j;
    }
    if(p==NULL || j>i) return ERROR;
    *e = p->data;
    return OK;
}

(5) 传递数组参数,如:
void MakeHuffTable(DWORD code[], BYTE size[], const BYTE bit[], const BYTE val[]){
    ...
}
code、size、bit和val虽然均以数组形式定义,用以接收数组实参,但其实是指针变量。

(6) 引用函数,例如二叉树遍历中,访问函数的调用:
Status PreOrderTraverse(BiTree T, Status (* Visit)(char)) {
    if(T != NULL) {
      if(Visit(T->data))
        if(PreOrderTraverse(T->lchild, Visit))
          if(PreOrderTraverse(T->rchild, Visit)) return OK;
      return ERROR;
    }
    else return OK;
}

(7) 作为其它值的别名

(8) 代表字符串,如:
LPCTSTR lpszMenuName = "TestBMPMenu";

(9) 直接访问系统内存,以下是DOS环境中,访问显示存储器的代码片段:
#define MK_FP(seg, ofs) ((void far *)(((unsigned long)(seg)<<16)|(unsigned)(ofs)))
...
BYTE *p = MK_FP(0xa000, offset);
...
if (T_bpp == 1)
    *p = g;
else
    memcpy(p, &T_color[g], T_bpp);
DOS存储空间的布局为:
段地址  长度      用途
0000H   640K   基本内存
A000H   128K   映射显示存储器
C000H   256K   BIOS ROM区
宏MK_FP用于按给定的段地址(16位)和段内偏移(16位)构造远端指针(32位,段地址<<16 + 段内偏移)。而实际存储位置的物理地址值为20位(段地址<<4 + 段内偏移)。8086/8088只有20根地址线,因此,最大寻址空间为1M。
此时,远指针p指向的内存区域没有被定义为变量,它直接访问了系统内存。

2.4 函数的数据类型
函数的数据类型也就是函数返回值的数据类型,原则上可以是除了数组类型以外的任何类型。当然,函数也可以没有返回值,这时函数的数据类型为void。

阅读(4292) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册