《C和指针》的第6章主要介绍了指针的定义以及指针的使用。指针对于很多C语言的初学者来说,简直就是一场"噩梦",谈指针色变。其实,当你真正理解了指针后,真的非常简单,只不过在使用的时候要更加细心一点罢了。本文主要是介绍自己对于指针的理解。
地址和值
首先,要有地址和值的概念。
打个比方,平常我们去就酒店开房,工作人员就会给你一张指定房间的房卡,一般房卡上就会写着房号,你就要根据这个房号的指引,找到这个房间,然后…
那么,这个"房号"就代表这个房间的地址,我们根据"房号"的指引就能找到这个房间。
假设,刚好全市对所有酒店进行专项治理活动,需要对酒店内的每个房间的客人进行身份核验。就需要去到每个房间进行访问,然后核验身份信息。
那么,每个房间"客人"就代表这个房间的值,警察叔叔对每个房间进行访问,就能获取到客人的身份信息。
类似,在计算机的世界里,内存可以存储大量的数据,我们这里可以简单的将内存类比为酒店。我们知道,内存的最小存储单位是bit,每个bit可以容纳两个值0和1。显然一个bit的信息容量太小,就需要更多的bit来组成一个更大一点的单位,那么多少bit合适呢?就是一个字节,一个字节由8个bit组成,一个字节可以表示256( 2 8 2^8 28)个数。我们假设,每个字节就类似于一个房间,那么每个字节也会有一个地址,每个字节8bit的0和1的组合就是它的值。比如,地址为0x100的字节的值为8。那么,简单来说,指针就表示这个地址。
变量
一个数据对象的三个要素:存储地址,存储大小,存储内容。变量名,变量类型,变量值与三个要素一一对应。因此,变量名的本质就是一段内存空间地址的别名。
对于程序员来说,通常通过变量来引用内存值。显然变量名与内存的某个地址是有关联的,而这个联系是由编译器决定的(编译器在编译时将变量名看成是一个符号,符号值即为变量的地址,各种不同的符号保存在符号表中),并不需要程序员来处理,而程序员只关心变量里存储的值。例如,int a = 10;
,使用变量名来关联变量a的内存,使用变量a来获取和修改这块内存的值。但是有时我们也需要知道内存的地址,以便更好地进行数据的操作和存储。
从表述方式上,“指针”表达地址的含义(指针就是地址);而int *p = 1;
指针p表示“指针变量”;
指针变量 也是一种变量,同时也会有变量的声明、定义、运算等。但是和普通变量相比,它更特殊一些。
特殊的地方在于 指针变量的值是一个地址值,而不是普通变量的数值。虽然变量的值都是由0和1构成的数字,但是不同数字背后的含义不同。
综上所述,从指针的定义上的理解就是以上内容。接下来,就是实操层面,指针的灵活使用。
指向类型的指针
通常在定义指针时,需要显示的指定指针指向的数据类型,例如,
void* p1 = NULL;
int* p2 = NULL;
char* p3 = NULL;
typedef struct _fruit_s
{
float weight;
float price;
}fruit_t;
fruit_t* p4 = NULL;
void* p5 = NULL;
知道了怎么定义指针,上面的简单示例在初始化时都是将指针初始化为NULL。怎样将指针指向实际的内存地址呢?例如,
// 简单数据类型
int a = 10;
int* p_a = &a;
// 复合数据类型
fruit_t apple;
apple.weight = 0.55;
apple.price = 5.98;
fruit_t* p_apple = &apple;
// 动态分配水果的内存
fruit_t* p_arr = (fruit_t*)malloc(10*sizeof(fruit_t));
for (int i = 0; i < 10; ++i)
{
p_arr[i].weight = 0.55; // 成员访问方式1
(p_arr+i)->price = 5.98; // 成员访问方式2
}
指针的解引用(dereference)
指针的解引用就是通过一个指针来访问它所指向的内存的内容。指针的解引用操作符为星号,例如,
int a = 100;
int* p = &a;
printf("a is %d\n", *p);
指针常量、常量指针、指向常量的指针常量
当年在学校刚开始学习C语言时,总是搞不清楚这三者之间的关系,现在回头来看,觉得是很容易理解。下面结合实际的例子,来解读三者的含义
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv)
{
int a = 10;
int b = 10;
#if 1
// 常量指针,指向常量的指针,指针所指向的地址的值不能修改,但指针变量的值可以修改
const int* p1 = &a; //认为a的值不能修改
int const *p2 = &a;
// 上面展示了两种常量指针的定义形式,推荐使用第一种
//*p1 = 11; // error: assignment of read-only location ‘*p1’
//不能通过指针变量p1解引用来修改变量a的值
//*p2 = 12;
p1 = &b; // 可以修改p1的值
#endif
#if 1
// 指针常量,指针的值不能修改,指针所指向的地址的值可以修改
// 也就是指针常量初始化以后,其指向的地址就不能被更改
int* const p3 = &a;
//p3 = &b; // error: assignment of read-only variable ‘p3’
// 不能修改p3的值
*p3 = 11; // 可以修改p3所指向的地址的值
#endif
//常量指针 是 指针所指向的地址的值不能修改
//指针常量 是 指针的值不能修改
//那么,有没有指针的值不能修改而且指针所指向的地址的值也不能修改呢?
//指向常量的指针常量
int c = 1;
int d = 2;
const int* const p = &c;
*p = d; // error: assignment of read-only location ‘*p’
p = &d; // error: assignment of read-only variable ‘p’
return 0;
}
总之,不管是指针常量还是常量指针,只要理解常量的值是不能修改的就能万变不离其宗,只是这个值是数值还是地址值。我们知道通过const关键词修饰的变量即变成了常量,那么变量名就变成了常量名,普通常量的值定义后不能再被修改;同样的道理,指针变量,如果对指针变量本身进行const修饰,那么指针变量的值(也就是内存的地址)不能被修改,该指针变量总是指向一个不变的内存地址。由于指针有两层含义,如果指针变量所指向的地址的值(普通变量)修饰为const,那么通过解引用访问该内存地址时,只具有读权限,不能写;
二级指针
工程中,我们除了常用一级指针外,在数据结构中还会使用到二级指针。因为二级指针可以简化程序的实现逻辑。虽然语法上还支持多级指针,但三级及以上的指针,使用起来太复杂,使用的较少。
所谓的二级指针就是指向指针的指针。其通过两个星号来定义或声明二级指针,例如,
int a = 1;
int* p_a = &a;
int** pp_a = &p_a; // 二级指针的值即为一级指针变量的地址
本文不展开介绍二级指针的使用示例,在后续讲解数据结构时再展开,这里先简单了解这个概念即可。
好了,第6章的总结就写到这里,最近因为家里有事,所以更新的慢了一点。
关注我
我的公众号二维码,欢迎关注
QQ讨论群:679603305