深入理解C语言中的数组与指针的关系问题
发表于:2022-10-20 | 分类: C语言佛系教程

“偷懒”的C语言

之所以取这个标题,是因为C语言真的很“懒”。

  • “懒”表现在两个方面:

1.C语言是没有Bool类型和String类型的。
在C语言的世界里,Bool类型是用int代替的,String类型是用char数组来代替的。


比如,它要求你用0来表示false,用1来表示true。

while(i){
    print "Hello World!";
}
//这句话只会在变量i为非0的时候执行

再比如,它要求你用字符数组或者指针来表示字符串。

char array[]="Hello,World!";
//字符数组表示
char *array="Hello,World!";
//字符指针表示

2.C语言的世界里,数组只保存数组头。
C语言是不会将数组里的所有东西都记住的,它只会记住每个数组的头号元素。说人话就是:C语言是依靠数组第一个元素的标识来识别这个数组的。


比如下面这段代码:

int array[3] = {1,2,3};
//C语言只记住了这个数组中1元素的地址

灵活的黑魔法——“指针”

提到指针,很多人会觉得:“这是个令人很头疼的问题”。而诚然,“指针”却是C语言的精髓所在。


  • 指针的定义很简单:指向地址的变量。相信这个定义大家都能理解了。也就是指针本质上是个变量,只不过这个变量存着另一个变量的地址。按照指针里面的的地址进行,我们就能够到达对应变量的“区域”。

  • 那么这样子,我们访问一个变量就有两种方式了:

1.我本来就熟悉a变量的家在哪儿(a变量的名字)。

int a=1;
printf("a在这儿%d",a);

2.我不知道a的家在哪儿,但是指针知道,我们找它问路。

int a = 1;
int *pt = a; //告诉这个刚出生的指针,你的使命是存储a的地址
printf("a在这儿%d",*pt);

  • 向指针“问路”的过程,我们称为“解引用”。就是把指针里面的地址取出来,并且到达那个变量。

  • 有的时候,指针会很皮,如果我们一开始不跟它说它应该负责谁的地址,那么这个指针就会“乱指”。就是它会随便找个地方,指向它。那么我们可以想一想,如果有个变量b,它本来在好好工作,突然莫名其妙被指针误伤(free掉),那么b变量就会很生气:“我明明没有打扰到谁啊,为啥要指我?”,如果b变量负责整个程序的关键部位(例如system),那么b变量的罢工就会导致整个操作系统崩溃掉。所以为了避免这个问题,我们就出现了NULL指针,就是强行将指针指向系统不使用的区域。

int *ptr = NULL;
//当我么没想好指针应该让它指向谁的时候,我们可以使用NULL指针。

偷懒带来的“好处”

我们都知道偷懒是肯定没用好处的,但是C语言的偷懒却带来了极大的灵活性。比如下文将从两个方面去说明这个好处。


1.指针很灵活,对于C语言而言,一切皆为指针。

  • 还记得上面我们提到的关于字符数组的问题吗?C语言的确只会记住字符数组的第一个字符的地址,而且对于C语言而言,这个地址是作为不同字符串的区分。

请看下面这个例子:

char a[]="Hello";
char *ptr=a;
printf("%p\n",a);
printf("%p\n",&a[0]);
printf("%p\n",ptr);
//这里的%p指的是以unsigned int的长度打印出值。

输出的结果是这样子的:

0x7fff0997e102

0x7fff0997e102

0x7fff0997e102

是不是很惊讶,这里的三个结果是一样的!
那这能不能说明这三个等价呢?也就是 a = &a[0] = ptr

即:“数组名(a)本质上也是一个指针(ptr),他指向数组元素的第一个元素的地址(&a[0])”

所以,数组名就是个指向内存块的指针!

我们都知道,字符数组和字符串、数字数组是不一样的,那么这个结论能否推广到数字数组呢?我们试试,看接下来的例子:

int a[]={1,2,3,4,5,6,7,8};
int *pt = a;
printf("%p\n",a);
printf("%p\n",&a[0]);
printf("%p\n",pt);

输出结果是这样子的:

0x7ffdbe898240

0x7ffdbe898240

0x7ffdbe898240

那么由此我们可以发现,无论是否是字符串,数组名永远作为指针,指向数组一个个元素的地址。

所以我们就可以做出总结了:

数组名本质是一个指针,它指向这个数组的第一个元素的地址。

2.指针允许用户对内存进行分配。

通过以上的例子我们可以知道,数组和指针有着十分相似的关系。那么它们能否混合使用呢,答案是“一定条件下可以”。条件就是我们需要给指针分配内存块地址。而分配内存这一个操作,当我们定义数组的时候就已经被编译器自动执行了。这就是数组名和单独的指针的不同点。

C语言中内置了malloc函数,可以直接进行内存地址的分配。函数成功执行后会返回分配的内存块的首个地址,可以用指针进行接受,用完后请释放内存。

看这个例子:

int *ptr = (int*)malloc(sizeof(int));
//分配一块int类型的内存给ptr这个指针
*ptr = 8;
//对指针的地址进行赋值操作
printf("%d\n",*ptr);
//打印输出结果

输出结果如下:

8

本例子中使用malloc函数分配了内存给一个int类型的指针ptr,并且对其进行赋值,最后对这个指针解引用并且输出。

再看这个例子:

int *ptr = (int *)malloc(8*sizeof(int));
//分配八个int类型的内存块大小
for (int i=0;i<8;i++){
    *(ptr+i) = i;//利用循环进行赋值,每次指针向后移动一位
}
for(int j=0;j<8;j++){
    printf("%d",*(ptr+j));
}

输出的结果为:

01234567

本例子中使用malloc函数分配了内存给一个int类型的指针ptr,一共分配了8个int类型的内存块大小,并且对其一一进行赋值,最后输出。

如果把原始代码中的*(ptr+j)更改为ptr[j],那么结果依旧是一样的。

int *ptr = (int *)malloc(8*sizeof(int));
//分配八个int类型的内存块大小
for (int i=0;i<8;i++){
    *(ptr+i) = i;//利用循环进行赋值,每次指针向后移动一位
}
for(int j=0;j<8;j++){
    printf("%d",ptr[j]);//这一行进行了更改
}

输出结果为:

01234567

这样子再一次证明了数组名就是指针的原理。

总结

  • C语言中,数组名就是一个指针,它指向数组所在内存块的首地址,如果对其解引用,便会得到数组第一个元素的值。

  • 在C语言中,指针就是灵魂一般的存在,如果没有指针的存在,那么我们的C语言将会变得毫无活力,变得很不自由。也正是因为C语言有这种特性,才铸就了它的不可替代性,在今日仍是我们作为程序员必须掌握的技能之一。

  • 不管语言如何发展,C语言永远有着不可或缺的地位。C语言是其他任何语言的基础,甚至是操作系统的基础。在它的基础之上,越来越多的高级语言发展起来,有的甚至“青出于蓝而胜于蓝”。但是我想作为对编程热爱的我们而言,我们必须始终记得C语言这位“长者”。

上一篇:
将Linux的ssh改为密钥登录
下一篇:
独立部署twikoo评论系统