C语言基础(五)- 指针

KinglyJn      2013-02-26

什么事指针?

指针就是内存地址(注意:通常我们叙述时,会把指针变量简称为指针,实际上他们含义并不一样),指针的本质是一个变量房间的“门牌号”,是一个操作受限的非负整数!(可以进行相减运算(表示两个地址的间隔),但是不能进行相加、相乘和相除类的运算!)

指针的优点

指针变量的重要性:(使用指针的优点!)

  1. 通过指针我们可以表示一些复杂的数据结构(例如链表、树、图等等);

  2. 快速地传递数据、减少内存耗用、大大提高执行速度和效率(很强大!);

  3. 能够直接访问硬!(很强大!);

  4. 能够方便地访问硬件;(很强大!)

  5. 能够方便地处理字符串!

  6. 使函数返回一个以上的值,并且可以做到用被调函数修改主调函数普通变量的值!;

  7. 是理解面向对象语言中引用的基础;

总之:指针是C的灵魂,C之所以是C,90%的原因就是因为C中有指针这么一个要素!

下面是指针的简单示例:


# include <stdio.h>

int main() {
    int* p;
    int i = 3;
    
    /*
        p是变量的名字,(int *)是p的数据类型,表示p变量存放的是int类型变量的地址
        又如:double * p;表示p变量存放的是double类型变量的地址!//注意p变量存放的只能是地址!
        int * p; 应该这样理解:p是变量名,p变量的数据类型是int* ,所谓int*类型,实际就是存放int变量地址的类型;
       (注意:int* p; 不表示定义了一个名字叫做*p的变量!)
    */
    p = &i;
    
    /*
        1. p保存了i的地址,因此p指向i,p相当于i房间的门牌号;
        2. p不是i,i也不是p,更准确地说:修改p的值不影响i的值,修改i的值也不会影响p
        3. (指针)变量的本质仍是变量,只不过指针变量存放的是其他变量的一个地址,指针变量就是地址变量; 
            p本质上是一个变量,只不过它存放的是地址变量,计算机会为p分配内存空间!
        4. 如果一个指针变量指向了某个普通变量,则 *指针变量 就完全等同于普通变量;
            例如:如果p是个指针变量,并且p存放了普通变量i的地址,则p指向了普通变量i,
            *p就完全等同于i,或者说在所有在所有出现 *p的地方都可以替换成i,在所有出现i的地方都可以替换成*p;
            *p的本质就是p指向的变量!
    */
    
    //p= i;  //error! 因为类型不一致,p只能存放int类型变量的地址
    
    printf("%#X\n", p); //0X5FBFF79C,结果输出的是i的地址,每次输出的可能会不一样,因为变量i的内存地址每次是随机分配的;
    printf("%f\n", p);  //0.000000,结果输出的是变量i的值3,语法上没有错误,但是输出的结果永远是0.000000,因为p存放的是地址变量!只能以整型形式输出
    printf("%d\n", *p); //3,结果输出的是变量i的值3;
    printf("%f\n", *p); //0.000000,但是输出的结果永远是0.000000,因为*p就等价于i
    
    return 0;
}

内存泄漏的问题

# include <stdio.h>

int main() {
    int i = 5;
    int* p;
    int* q;
    
    p = &i; //ok  p存放了i的地址,p指向i,*p是以p的内容为地址的变量,*p等价于i,修改p或修改i不会改变对方的值
    p = q; //OK! q是垃圾值,q赋值给p之后,p也变成了垃圾值;
    
    printf("%#X\n",p); //OK,输出结果为0,野指针
    //printf("%d", *p); //error, q是属于本程序的,所以本程序可以读写q的内容,但是如果q内部是垃圾值(q是野指针),则本程序不恩能够读写*q的内容,因为*q所代表的内存单元的控制权限没有分配给本程序!
    
    return 0;
}

总结:就debug版中的堆栈中的局部变量(包括指针)在明确初始化之前都用0xcc…进行初始化,因此,未初始化时候的指针是指向地址0xcccccccc的,而这段地址一来是处于内核地址空间,一般的应用程序是无权访问的,上面的报错就是这样产生的。因此,一旦遇到上述报错,基本可以认定程序中出现了野指针。

指针的一些经典示例

/**
* 2013年12月23日15:56:47
* 经典指针程序_互换两个数字
* 总结:由这个程序,我们可以知道,通过指针变量,我们可以达到通过被调函数修改主调函数普通变量的值,
*      并且使被调函数返回一个以上的值的目的。
*/

# include <stdio.h>

int main() {
    int a=3, b=5;
    void huhuan_1(int, int);
    void huhuan_2(int*, int*);
    void huhuan_3(int*, int*);
    
    huhuan_1(a, b);
    printf("a= %d, b = %d\n", a, b);
    
    huhuan_2(&a, &b);
    printf("a= %d, b = %d\n", a, b);
    
    huhuan_3(&a, &b);
    printf("a= %d, b = %d\n", a, b);
}

void huhuan_1(int a, int b) { //传统的函数不能达到两数互换的目的
    int t;
    
    t = a;
    a = b;
    b = t;
    
    return;
}

void huhuan_2(int* p, int* q) { //交换形参指针变量不能达到两数互换的目的
    int* t;
    
    t = p;
    p = q;
    q = t;
    
    return;
}

void huhuan_3(int* p, int* q) { //交换指针变量指向的变量可以达到两数互换的目的
    int t;
    
    t = *p;
    *p = *q;
    *q = t;
    
    return;
}

注:* 的含义“”的三种含义:
1、乘法
2、定义指针变量:int * p;表示定义了一个名字叫做p的变量,p只能存放int类型变量的地址;
3、指针运算符:该运算符放在已经定义好的指针变量(如 p)的前面,则
p表示指针变量p指向的变量;

指针和数组的关系

/**
* 2013年12月23日19:30:05
* 指针和数组以及如何定义一个输出任何数组的函数
*/

# include <stdio.h>

int main() {
    int a[5] = {1,2,3,4,5};
    void print_int_array(int*, int);
    
    printf("array a size is: %ld\n", sizeof(a)); //20
    print_int_array(a, 5); //这里的a指的是该二维数组的首地址,该局将二维数组的首地址赋值给指针parr
    
    return 0;
}

void print_int_array(int* parr, int len) {
    printf("parr size is: %ld\n", sizeof(parr)); //8 //不同的计算机拥有不同的寻址能力,32位的机器为4
    for (int i=0; i<len; i++) {
        printf("%d\n", *(parr+i)); //parr[i]等价于*(parr+i),通过被调函数和指针就可以对主函数中的任何变量进行任何操作
    }
}


[注]:
再强调一遍,“arr 本身就是一个指针”这种表述并不准确,严格来说应该是“arr 被转换成了一个指针”。这里请大家先忽略这个细节,我们将在《数组和指针绝不等价,数组是另外一种类型》和《数组在什么时候会转换为指针》中深入讨论。 可以验证:szieof(arr) != sizeof(parr);


动态二维数组的构造

/**
* 动态二维数组的构造
*/

# include <stdio.h>
# include <mm_malloc.h> //xcode,其他可能为malloc.h

int main(){
    int** parr;
    int row, col;
    void input_array(int**, int, int);
    void print_array(int**, int, int);
    void transport_array(int**, int, int);
    
    printf("请输入方阵的行数:");
    scanf("%d",&row);
    printf("请输入方阵的列数:");
    scanf("%d",&col);
    printf("\n");
    
    parr = (int**) malloc(sizeof(int*)*row); //分配指针数组空间
    for (int i=0; i<row; i++) {
        parr[i] = (int*) malloc(sizeof(int)*col); //分配每个二维指针所指向数组的空间
    }
    
    input_array(parr, row, col); //方阵的输入
    print_array(parr, row, col); //方阵的转置
    transport_array(parr, row, col); //方阵的输出
    print_array(parr, row, col);
    
    return 0;
}

void input_array(int** parr, int row, int col) {
    printf("请输入方阵的元素;\n");
    for (int i=0; i<row; i++) {
        for (int j=0; j<col; j++) {
            printf("a[%d][%d] = ", i, j);
            scanf(" %d", &parr[i][j]);
        }
    }
}

void print_array(int** parr, int row, int col) {
    printf("输出的方阵为:\n");
    for (int i=0;i<row; i++) {
        for (int j=0; j<col; j++) {
            printf("a[%d][%d] = %d\n", i, j, parr[i][j]);
        }
    }
}

void transport_array(int **parr, int row, int col) {
    int t;
    for (int i=0; i<row; i++) {
        for(int j=0; j<i; j++) {
            t = parr[i][j];
            parr[i][j] = parr[j][i];
            parr[j][i] = t;
        }
    }
}


指向函数的指针

定义:

int (*p) (int, int); //表示定义了一个指针变量,该指针变量指向函数返回值为整型且有两个整型参数的函数

//由于()的优先级高于*,所以在定义指向函数的指针变量时,必须要加(),
//如果写成了语句int * (p (int, int))就表示声明了一个p函数了,该函数的返回值是(int *)类型!
//指向函数的指针的赋值:在给指向函数的指针赋值时,只需要将函名赋值给该指针变量即可,而不必给出函数的参数;
//用指向函数的指针变量调用它指向的函数时,只需要用(*p)代替函数名即可!
//指向函数指针的一个重要用途就是把函数的地址作为参数传递到其他函数!



/**
* 测试函数指针
*/

# include <stdio.h>

int main() {
    int a=1, b=2;
    
    int max(int, int);
    int min(int, int);
    int add(int, int);
    void func(int, int, int (*p)(int,int)); //该函数的第三个参数是一个函数指针
    
    func(a, b, max); //2
    func(a, b, min); //1
    func(a, b, add); //3
    
    return 0;
}

void func(int x, int y, int (*p)(int,int)) {
    printf("%d\n", (*p)(x, y));
}

int max(int x, int y) {
    return x>y ? x : y;
}

int min(int x, int y) {
    return x < y ? x : y;
}

int add (int x, int y) {
    return x+y;
}


常见有关指针的数据类型



Tags:


Share: