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

it2025-10-04  2

学习足迹

前言一、指针和数组1.代表数组的指针2.指针表示多维数组 二、指针和函数总结


前言

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方法:

方式一:直接使用数组下标索引,即ages[i],names[j]。方式二:从这里开始,我们使用指针帮助索引。先初始化两个指针: 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个单位。 这样一听,就会觉得一个数组的名称也是一个指向这个数组首元素的指针啊。我感觉从实际效果来看是这样的。 因此我们可以有方式三来索引这个数组,这时就会发现,对指针和数组名称的索引操作是一样的。

方式三:这种索引方式就完全把指针看作了数组的名称,通过下标来索引。 其实应该说,数组下标的索引方式,是使用了地址移动取值的原理。 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类型的函数了。


总结

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

最新回复(0)