c之精髓——指针(pointer)——用来存储地址的变量。一般来讲,指针是一个其数值为地址的变量(或更一般地说是一个数据对象)。

   一元运算符&可以取得变量的存储地址,一个变量的地址可以被看作是该变量在内存中的位置。

   地址运算符&:后跟一个变量名时,&给出该变量的地址。

   间接运算符*:当后跟一个指针名或地址时,*给出存储在被指向地址中的数值。

   指针声明,举例如下:

int * pi;
char * pc; // pc所指向的值(*pc)是char类型的,而pc本身又是什么类型?我们把它描述为“指向char的指针”类型。指针是一种新的数据类型
float * pf, * pg; 

   类型标识符表明了被指向变量的类型,而星号(*)表示该变量为一指针。声明int * pi;的意思是pi是一个指针,而且*pi是int类型的。

  

   数组名同时也是该数组首元素的地址。也就是说,如果flizhy是一个数组,那么flizhy == &flizhy[0]; flizhy和&flizhy[0]都代表首元素的内存地址。

  1. 指针的数值就是它所指向的对象的地址。地址的内部表示方式是硬件来决定的。很多计算机(包括PC机和Macintosh机)都是以字节编址的,这意味着对每个内存字节顺序进行编号,对于包含多个字节的数据类型,比如double类型的变量,对象的地址通常指的是其首字节的地址
  2. 在指针前运用运算符*就可以得到该指针所指向的对象的数值
  3. 对指针加1,等价于对指针的值加上它所指向的对象的字节大小

   数组和指针之间的密切关系:可以用指针标识数组的每个元素,并得到每个元素的数值。从本质上来说,对同一个对象有两种不同的符号表示方法。c语言标准在描述数组时,确实借助了指针的概念。例如:定义ar[n]时,意思是*(ar + n),即“寻址到内存中的ar,然后移动n个单位,再取出数值”。

   

   在函数原型或函数定义头的场合中(并且也只有在这两种场合中),可以用int * ar代替int ar[]

int sum(int * ar, int n); 
int sum(int ar[], int n);

 

    c提供了8种基本的指针操作:(了解就好)

  1. 赋值——可以把一个地址赋给指针。通常使用数组名或地址运算符&来进行地址赋值。(注意:地址应该和指针类型兼容。也就是说,不能把一个double类型的地址赋给一个指向int的指针。c99允许使用类型指派这样做,但是我们不推荐使用这种方法。)
  2. 求值或取值——运算符*可取出指针指向地址中存储的数值。
  3. 取指针地址——指针变量同其他变量一样具有地址和数值,使用运算符&可以得到存储指针本身的地址。
  4. 将一个整数加给指针——可以使用+运算符把一个整数加给一个指针,或者把一个指针加给一个整数。两种情况下,这个整数都会和指针所指类型的字节数相乘,然后所得到的结果会加到初始地址上。如果相加的结果超出了初始指针所指向的数组的范围,那么这个结果是不确定的,除非超出数组最后一个元素的地址能够确保是有效的。
  5. 增加指针的值——可以通过一般的加法或增量运算符来增加一个指针的值。对指向某数组元素的指针做增量运算,可以让指针指向该数组的下一个元素。
  6. 从指针中减去一个整数——可以使用-运算符来从一个指针中减去一个整数。指针必须是第一个操作数,或者是一个指向整数的指针。这个整数都会和指针所指类型的字节数相乘,然后所得的结果会从初始地址中减掉。(同4)
  7. 减小指针的值——指针当然也可以做减量运算。
  8. 求差值——可以求出两个指针间的差值。通常对分别指向同一个数组中两个元素的指针求差值,以求出元素之间的距离。差值的单位是相应类型的大小。有效指针差值运算的前提是参加运算的两个指针是指向同一个数组(或是其中之一指向数组后面的第一个地址)。指向两个不同数组的指针之间的差值运算可能会得到一个数值结果,但也可能会导致一个运行时错误。
  9. 比较——可以运用关系运算符来比较两个指针的值,前提是两个指针具有相同的类型。

   ¤使用指针,有一个规则需要特别注意:不能对未初始化的指针取值!!!

   例:

int * pt;  // 未初始化的指针
* pt = 5; // 一个可怕的错误!!! 表示把数值5存储在pt所指向的地址中。但是由于pt没有被初始化,因此它的值是随机的,不知道5会被存储在什么位置。这个位置也许对系统危害不大,但也许会覆盖程序数据或代码,导致程序的崩溃。

   切记:当创建一个指针时,系统只分配了用来存储指针本身的内存空间,并不分配用来存储数据的内存空间。因此在使用指针之前,必须给它赋予一个已分配的内存地址。再如:

char * name;
scanf("%s", name);

   这可能会通过编译器,但是在读入name的时候,name会覆盖程序中的数据和代码,并可能导致程序异常终止。这是因为scanf()把信息复制到由参数给定的地址中,而在这种情况下,参数是个未初始化的指针,name可能指向任何地方。再如:

char * str;
strcpy(str, "The C of Tranquility");

   函数将把字符串“The C of Tranquility”复制到str指定的地址中,但是str没有初始化,因此这个字符串可能被复制到任何地方!

   结构中使用字符数组还是字符指针?

   例如:

#define LEN 20
struct names {
    char first[LEN];
    char last[LEN];
};

   可以改写成下面这样吗?

struct pnames {
    char * first;
    char * last;
}

   对于struct names结构类型的变量来说,字符串存储在结构内部,这个结构总共分配了40字节来存放两个字符串。然而对于struct pnames结构类型的变量来说,字符串存储在编译器存储字符串常量的任何地方。这个结构存放的只是两个地址而已。struct pnames结构不为字符串分配任何存储空间。它只适用于在其他的地方已经为字符串分配了空间(例如字符串常量或数组中的字符串)。简单地说,pnames结构中的指针应该只用来管理那些已创建的而且在程序其他地方已经分配过空间的字符串

   所以,对于以下程序:

struct pnames attorney;
puts("Enter the last name of your attorney: ");
scanf("%s", attorney.last);

   scanf()把字符串放到由attorney.last给出的地址中。因为这是个未经初始化的变量,所以该地址可能是任何值,程序就可以把名字放在任何地方。

   因此,如果需要一个结构来存储字符串,请使用字符数组成员。存储字符指针有它的用处,但也有被严重误用的可能。

   如果非得使用指针处理字符串,也是可以的,可借助malloc()函数分配内存,并使用指针来存放地址。这个方法的优点是可以请求malloc()分配刚好满足字符串需求数量的空间。

   例如:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct namect {
    char * fname;
    char * lname;
    int letters;
};
void getinfo(struct namect *);
void makeinfo(struct namect *);
void showinfo(const struct namect *);
void cleanup(struct namect *);

int main(void)
{
    struct namect person;

    getinfo(&person);
    makeinfo(&person);
    showinfo(&person);
    cleanup(&person);
    return 0;
}
void getinfo(struct namect * pst)
{
    char temp[81];
    printf("Please enter your first name.\n");
    gets(temp);
    pst->fname = (char *)malloc(strlen(temp) + 1);
    strcpy(pst->fname, temp);
    printf("Please enter your last name.\n");
    gets(temp);
    pst->lname = (char *)malloc(strlen(temp) + 1);
    strcpy(pst->lname, temp);
}
void makeinfo(struct namect * pst)
{
    pst->letters = strlen(pst->fname) + strlen(pst->lname);
}
void showinfo(const struct namect * pst)
{
    printf("%s %s, your name contains %d letters.\n", pst->fname, pst->lname, pst->letters);
}
void cleanup(struct namect * pst)
{
    free(pst->fname);
    free(pst->lname);
}
View Code

相关文章: