C语言学习记录(四)——指针!不要怕它!


前言

C语言中的指针一直是我最怵的地方,它有很多种用法,总是让人搞不清楚。其实只要我们用正常的方式使用指针,它就没那么难以理解。我认为理清指针含义的好方式是将其和数组进行比较,理解它和数组在使用时的关系。

参考及引用:笨办法学C


一、指针和数组

指针的基本概念:
指针是指向了计算机中的某个地址,这个地址里存放着一个特定类型的数据。指针本身的值就是一个地址值,它指向的这个地址中的内容才是数据。

指针的通常用法:
type *ptr
定义一个type类型的指针,名为ptr。这个type类型其实是指针指向的数据的类型,而指针本身是地址值。

*ptr
取值操作,表示ptr所指向地址中存放的值。

*(ptr + i)
表示(ptr所指向位置加上i)的值。
注意:以字节为单位的话,应该是ptr所指向的位置再加上sizeof(type) * i。所以初始化的时候type很关键,它决定着指针向下移一位时,究竟是移动了几个字节的地址。

&thing
取址操作,表示thing存放的地址。

type *ptr = &thing
名为ptr,type类型的指针,值设置为thing的地址。

ptr++
自增ptr指向的位置。


1.代表数组的指针

我们先来看一段程序,它的用途就是输出人名及对应的年龄。但在打印的时候,我们采用三种不同的方式,并且这些方式的输出都是相同的。

#include <stdio.h>
int main(int argc, char *argv[])
{
    // create two arrays we care about
   
    int ages[] = {23, 43, 12, 89, 2};
    char *names[] = {
        "Alan", "Frank",
        "Mary", "John", "Lisa"
    };
    // safely get the size of ages
    int count = sizeof(ages) / sizeof(int);
    int i = 0;
    // first way using indexing
    for(i = 0; i < count; i++) {
        printf("%s has %d years alive.\n",
                names[i], ages[i]);
    }
    printf("---\n");
    // setup the pointers to the start of the arrays
    int *cur_age = ages;
    char **cur_name = names;
    // second way using pointers
    for(i = 0; i < count; i++) {
        printf("%s is %d years old.\n",
                *(cur_name+i), *(cur_age+i));
    }
    printf("---\n");
    // third way, pointers are just arrays
    for(i = 0; i < count; i++) {
        printf("%s is %d years old again.\n",
                cur_name[i], cur_age[i]);
    }
    printf("---\n");
    return 0;
}

首先定义了两个数组,ages:由int型元素构成的数组;names:由char * 型元素构成的数组。其中char *类型就是指字符串,这个我们下面再说。

程序中元素顺序打印时使用了四种index方法:

  1. 方式一:直接使用数组下标索引,即ages[i],names[j]。
  2. 方式二:从这里开始,我们使用指针帮助索引。先初始化两个指针:
    int *cur_age = ages;
    char **cur_name = names;

第一句代码的含义:int * 指“指向整数类型的指针”类型,也是一种数据类型。我们构建一个这样数据类型的变量 cur_age,并给它赋值为ages,ages是一个整数数组的名称。

第二句代码同第一句,初始化了一个“指向char * 类型的指针”,命名为cur_name,把names赋值给它。names是一个字符串数组的名称。

在索引数组时,使用指针加一(即地址下移一位)的方式获取数组的下一个值。注意这里使用了 * 来取地址中的存储值。

    for(i = 0; i < count; i++) {
        printf("%s is %d years old.\n",
                *(cur_name+i), *(cur_age+i));
    }

看到这种取值方式,我们就可以理解了。指针cur_name一开始的地址是指向数组names的第一个元素的,不停地加一,就会使地址不停向下移动,可以遍历整个数组。
这时,我们可能有个疑问,我们明明是把一个数组赋给了cur_name,为什么它却获得的是一个该数组首元素的地址呢?
这就要理解数组的名称names代表什么了。其实在C语言中数组的名称就会被推导成这个数组的首元素地址,之后才能用下标索引来索引数组中的元素。它的下标i就代表了从首元素向后移动i个单位。
这样一听,就会觉得一个数组的名称也是一个指向这个数组首元素的指针啊。我感觉从实际效果来看是这样的。
因此我们可以有方式三来索引这个数组,这时就会发现,对指针和数组名称的索引操作是一样的。

  1. 方式三:这种索引方式就完全把指针看作了数组的名称,通过下标来索引。
    其实应该说,数组下标的索引方式,是使用了地址移动取值的原理。
    for(i = 0; i < count; i++) {
        printf("%s is %d years old again.\n",
                cur_name[i], cur_age[i]);

2.指针表示多维数组

学会了上面三种数组索引方式,我们应该对指针有个清楚一些的认识,在常规使用中,指针通常和数组挂钩,比如:

int * 表面上表示指向整数的指针,实际上通常从用它来代表一个元素都是整数的数组,帮助程序进行数组操作。

char * 就表示一个元素都是char型的数组,这样的数组不就是字符串嘛!这就解释了前面为什么说char *表示字符串类型。

那么int **,char **表示什么呢?

其实我们可以不想那么复杂,可以简单地把一个 * 就当作是代表数组,再加一个*,那就是代表数组的数组。
比如 int **,就表示二维整数数组(如a[3][4]),即整数数组的数组;
char** 表示字符数组的数组,即字符串数组。
具体关于二维整数数组的操作可以看这里的栗子,里面讲的比较清楚了。

那么int ***,char ***呢?

再加一个* ,就表示又多了一层数组,那它们表示的就是三维数组。

“ * ” 一多就容易犯晕,这里有个小问题,int *p这个指针,对它取值即*p,得到的值是p地址中存放的那个整数。那对int ** q取值,*q得到的是什么呢?

要回答这个问题,我们需要理清楚int ** 数据类型实际是啥内容,不能笼统地想成二维数组了。首先它是指针,指向的地址中存放的数据是int*类型的。而int *类型也是一个指针,它指向的地址中存放的才是整数数据。也就是说,对q取值,获得的也是一个地址(指针),这个地址代表了一个整数数组。
如果要把*q的含义和二维数组对应,就可以理解成*q就是二维数组中第一行的数组首元素地址。
(*q)[i] 表示这个二维数组第一行第i个元素值;(*(q+1))[i]表示第二行第i个元素值…

二、指针和函数

指针和函数的部分就好理解了,这里会有一个新概念,叫函数指针,它就是一个指向某类函数的指针。
下面看它的定义和用法。

int (*tester)(int a, int b) = sorted_order;  //test是函数指针的名字
printf("TEST: %d is same as %d\n", tester(2, 3), sorted_order(2, 3));

上面的tester可以赋值为任何一个符合 int (int a, int b)输入输出格式的函数。函数指针常被用于向其他函数传递回调函数。为了简化表示一个函数指针,使它能简洁地作为某个函数的参数,可以使用typedef将其固定成一种类型。方法如下:

//化为可用类型,类型名是compare_cb
typedef int (*compare_cb)(int a, int b); 
//作为排序函数test_sorting的输入参数
void test_sorting(int *numbers, int count, compare_cb cmp)
{
    int i = 0;
    int *sorted = bubble_sort(numbers, count, cmp);
    if(!sorted) die("Failed to sort as requested.");
    for(i = 0; i < count; i++) {
        printf("%d ", sorted[i]);
    }
    printf("\n");
    free(sorted);
}

这样,我们在调用程序中的test_sorting时,就需要给它传入符合compare_cb类型的函数了。


总结

把指针和数组相对应,就会发现它没那么复杂,一般也确实是这样使用的。* 的数量越多,指针或数组的嵌套就越多,我们应尽量避免这样的复杂用法。
当然,指针还可以指向结构体,这个用法就会直观很多。这次就记录到这里,有新的感悟再写吧~如有错误,请大家一定指正!

评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值