《C语言指针:从入门到精通》第一部分:引言

C语言指针:从入门到精通

第一部分:引言


 系列内容已经在B站上传为一系列视频,其中第一个视频地址为

C语言指针和内存原理 -- 1、序言和内存基本概念_哔哩哔哩_bilibiliC语言指针和内存原理 -- 1、序言和内存基本概念https://www.bilibili.com/video/BV14r4y1D7YH/

C语言自诞生以来就受到了极大的关注,应用及其广泛,介绍其历史发展的书籍以及各种教材数不胜数。作为过去几十年最为流行的编程语言之一,其重要性是不言而喻的,目前国内外绝大部分大学的计算机相关专业都开设有C语言的课程,C语言也被广泛视为数据结构等后继课程的基础知识,因此掌握C语言对于计算机相关专业的学生来说是非常基础性的要求。

在学习和使用C语言的过程中,无论是初学者还是经验丰富的程序员,几乎所有人都将“指针”视为C语言最重要也最困难的一个要点。此外,指针还跟很多其他C语言的基本概念紧密相关,例如数组就跟指针有着千丝万缕的关系。理解C语言中的指针,不仅对于C语言本身的程序设计至关重要,对于其他高级程序语言的掌握和应用也有着重要的借鉴作用。无论是C++还是Java或其他语言,掌握C语言的指针概念对于理解这些语言也有非常大的帮助,例如Java语言关于子类里面的super和this的区别等。因此,指针的学习,并不仅仅是进行简单的语法学习,而是要彻底搞清楚指针及其应用背后的原理和机制,这样才能进行更好地进行高质量的程序设计。

指针这个概念之所以难,主要原因是因为指针是C语言进行内存访问操作的核心机制之一,了解面向内存的各种操作对于掌握指针有着重要的指导意义。因此,本文以内存管理作为核心概念,将重点放在以内存为视角的C语言指针及其相关的概念的介绍和理解上,逐步展开内容,通过层层递进的方式让读者更好地了解指针相关的概念。本文的预期读者是对C语言有一定的了解,选修或自学过一些C语言的基础知识,掌握C语言的一般语法并具有一定的程序开发经验,同时又希望能进一步掌握指针的运行机制,提高自身的程序设计能力。为了让大家对本文的内容有一个直观的了解,我们这里给出了一些指针相关基本概念的示例,也请读者试着做一做并思考其背后的原理,并带着问题来阅读本文的后续章节。

###假设使用的是32位编译器###

例题1:不同的进制

C语言中数字是可以用不同的进制来表示,这是C语言相关的一些基础概念。这里有一个很简单的例题1,如图1所示。请问a,b,c三个变量打印出来的值分别是多少?这道例题是非常简单的,我们把它放在这里的原因只是作为一个热身,因为本文的后续内容会大量使用到十进制和十六进制的整数,能够进行快速的转换是非常重要的。

int a = 10;
int b = 010;
int c = 0x10;
printf("a=%d\n", a);
printf("b=%d\n", b);
printf("c=%d\n", c);

图1 整数的不同进制表示

例题2:整数变量与地址的关系

在很多C语言的资料和教材中,int类型的变量以及指向int类型变量的指针是用来讲解指针概念时使用几乎最多的例子,这里我们也同样的给出一个这样的小例题,如图2所示。对于一个整数a,我们将它初始化为10,现在我们来分别打印a的值和&a的值,结果是否一样?其次,在这个例题里面,打印a的值的时候,我们用的格式参数是%d,相信这一点大家应该没有任何疑问,但是打印&a的值这里用的也是%d,选择%d只是为了让大家试一下的时候可以清楚地看出这两个值是否一样。此外,变量a打印出来的值是一个int类型,那么&a的值对应的类型应该是一个什么呢?这个例题相信绝大部分读者都能给出正确的答案,a的值的类型是int,而&a得到的值的类型是一个指向int类型的指针,换言之,如果要定义一个变量p去等于&a,则声明形式应该是“int *p = &a;”。这道例题本身没有什么难度,我们将它放在这里的原因是为了引出后面的例题。

int a = 10;
printf("a=%d\n", a);
printf("&a=%d\n", &a);

2 整数变量的值及地址的关系

例题3:一维数组变量与地址的关系

下面我们接着上面那个例题来做一点修改和扩展,这时a不再是一个整数,我们将a重新定义成一个由3个int组成的一个一维数组,如图3所示。对于这个例题,首先第一个问题是,a的值打印出来应该是什么?其次,&a的值打印出来应该是什么?第三,a[0]的值打印出来应该是什么?另外,这三个打印出来的值会是一样的吗?如果有一样的,是什么原因?不一样的,又是什么原因呢?此外,a打印出来的值应该对应什么类型?&a打印出来的值应该对应什么类型?a[0]打印出来的值应该对应什么类型?这三个值对应的变量类型一样吗?如果不一样,它们之间有什么关系呢?如果要定义一个变量n,让“n = a[0]”,n该如何声明和定义?如果要定义一个变量p,让“p = a”,p该如何声明和定义?如果要定义一个变量q,让“q = &a”,q该如何声明和定义?我们相信对于这个例题,可能有一些读者就开始产生困惑了,大家可以自己试一试这段代码,看看结果跟自己想的是否一样。

int a[3] = {10};
printf("a=%d\n", a);
printf("&a=%d\n", &a);
printf("a[0]=%d\n", a[0]);

3 一维整数数组及地址的关系

例题4:二维数组变量与地址的关系

我们进一步来修改这个变量a的类型,我们现在把a重新定义为一个二维数组,如图4所示。同样的,a的值打印出来应该是什么?&a的值打印出来是什么?a[0]的值打印出来是什么?a[0][0]的值打印出来是什么?这四个打印值还会是一样的吗?此外,a打印出来的值应该对应什么类型?&a打印出来的值应该对应什么类型?a[0]打印出来的值应该对应什么类型?a[0][0]打印出来的值应该对应什么类型?这四个值对应的变量类型一样吗?如果不一样,它们之间有什么关系呢?如果要定义一个变量p,让“p = a”,p该如何声明和定义?如果要定义一个变量q,让“q = &a”,q该如何声明和定义?类似的,如果要定义一个变量r,让“r = a[0]”,r该如何声明和定义?如果要定义一个变量s,让“s = a[0][0]”,s该如何声明和定义?对于这个例题,由于数组维度的提升,可能就带来了更多困惑,大家依然可以自己试一试这段代码,看看结果跟自己想的是否一样。

int a[3][4] = {10};
printf("a=%d\n", a);
printf("&a=%d\n", &a);
printf("a[0]=%d\n", a[0]);
printf("a[0][0]=%d\n", a[0][0]);

4 二维整数数组及地址的关系

例题5:更高维数组变量与地址的关系

大部分教材或平时应用中大家都很少会用到更高维的数组,但是高维数组的理解依然是非常必要的,其背后的逻辑应该是和一维、二维数组是一样的。那么这时候如果我们进一步来修改这个变量a的类型,我们现在把a定义为一个四维数组,如图5所示。如果将a、&a、a[0]、a[0][0]、a[0][0][0]和a[0][0][0][0]都打印出来,这些打印出来的值还会是一样的吗?有哪些会不一样呢?此外,a打印出来的值应该对应什么数据类型?&a打印出来的值应该对应什么数据类型?a[0]、a[0][0]、a[0][0][0]以及a[0][0][0][0]打印出来的值应该分别对应什么类型?这些值对应的变量类型之间有什么关系呢?此外,如果要定义一个变量p,让“p = a”,p该如何声明和定义?如果要定义一个变量q,让“q = &a”,q该如何声明和定义?类似的,如果要定义一个变量r,让“r = a[0]”,r该如何声明和定义?如果要定义一个变量s,让“s = a[0][0]”,s该如何声明和定义?如果要定义一个变量t,让“t = a[0][0][0]”,t该如何声明和定义?如果要定义一个变量u,让“u = a[0][0][0][0]”,u该如何声明和定义?大家依然可以自己试一试这段代码,看看结果跟自己想的是否一样。三维以上的数组是不好想象的,数组维度的提升,可能会带来更多困惑,那么有什么好的办法来理解这些更高维的数组吗?这些数组和指针的关系到底是怎样的?本文将围绕着以上几个例题中可能出现的困惑,在后续章节中提供一个一般化的理解思路。

int a[3][4][5][6] = {10};
printf("a=%d\n", a);
printf("&a=%d\n", &a);
printf("a[0]=%d\n", a[0]);
printf("a[0][0]=%d\n", a[0][0]);
printf("a[0][0][0]=%d\n", a[0][0][0]);
printf("a[0][0][0][0]=%d\n", a[0][0][0][0]);

5 高维整数数组及地址的关系

例题6:理解sizeof

sizeof是一个C语言中重要的关键字,能够获取一个变量类型或者一个表达式运算结果的大小,这个关键字对于指针的理解和应用也是有着非常重要的作用,我们这里通过两个小例子来说明。第一个示例如图6所示,我们首先声明一个int类型的变量a,然后计算sizeof(a)以及sizeof(a+1-1)的值并打印出来,这两个结果是否一样?如果一样,是为什么?这两个sizeof表达式其背后隐含的逻辑和物理意义是一样的吗?

int a = 10;
printf("sizeof(a)=%d\n", sizeof(a));
printf("sizeof(a+1-1)=%d\n", sizeof(a+1-1));

6 整数与sizeof的关系

接下来我们修改一下这个示例,我们将a修改为一个由3个int组成的一个数组变量,如图7所示。我们继续计算sizeof(a)以及sizeof(a+1-1)的值并打印出来,这两个结果是否一样?大家可以自己动手试一试这段代码,看看结果跟自己想的是否一样。

int a[3] = {10};
printf("sizeof(a)=%d\n", sizeof(a));
printf("sizeof(a+1-1)=%d\n", sizeof(a+1-1));

7 整数数组与sizeof的关系

例题7:数组变量作为参数传递

数组和指针的关系在数组变量作为参数传递过程中表现是最直接的,这里我们看一看下面这个例题,如图8所示。我们在main函数中定义了两个数组变量a和b,分别对应一个3个int的整数数组和一个3×4大小的二维整数数组,当我们将a和b作为实参传递给func1和func2函数的时候,为什么形参pa和pb看上去像数组,但是第一维大小是不用指定的?这个参数传递过程中的现象背后的机制是怎样的呢?

void func1(int pa[]) 
{
    // do something
}

void func2(int pb[][4]) 
{
    // do something
}

int main() 
{
    int a[3] = {10};
    int b[3][4] = {10};

    func1(a);
    func2(b);

    return 0;
}

8 整数数组作为参数传递

例题8const的使用

const是C语言的一个关键字,用来限定一个变量的值不可以修改,下面是一个const使用的示例,如图9所示,分别定义了三个变量p、q和r。大家可以自己试一试,看看对*p、*q和*r以及p、q和r的赋值在编译的时候哪些可以,哪些不可以?是否跟自己想的一样。

int a = 10;

const int* p = &a;
int const* q = &a;
int* const r = &a;

*p = 20;
p = NULL;

*q = 20;
q = NULL;

*r = 20;
r = NULL;

9 const的使用示例

例题9:能不能为数组变量赋值

下面的例题是对于一个数组变量进行赋值,如图10所示。我们定义了一个3个int组成的一维数组a,接下来试图将a赋值为NULL,这样的操作是否可行?大部分读者可能都能指出这样的操作会导致编译错误,但是编译错误的原因是什么?变量a不能修改是因为a是只读变量吗?假设我们定义一个“const int b”,当我们对b赋值的时候,例如“b = 10”,编译的错误信息和“a = NULL”的编译错误信息一样吗?如果不一样?那么数组变量a不能赋值修改其背后的原因到底是什么呢?

int a[3] = {10};

a = NULL;

10 数组变量赋值错误

以上这些例题其实都跟指针的概念是有关联的,指针概念的理解和应用,并不是简单的理解指针的语法,其背后蕴含的对内存的理解以及操作才是最为关键的。因此本文的后续章节将从内存的基本概念开始展开,并逐渐为大家梳理指针相关的各种运行机制。

###看看答案跟你想的一样吗?###

例题1:a,b,c的值打印出来分别是10,8,16(10进制,8进制,16进制)

例题2:对于int a = 10,a的值打印出来是10,&a的值打印出来换成16进制看看?是变量a对应的内存的首地址

例题3:对于int a[3] = {10},a的值和&a的值打印出来是一样的(同样的值其背后蕴含的物理意义是不一样的),a[0]的值是10。int *p = a; int (*q)[3] = &a; int r = a[0];

例题4:对于int a[3][4] = {10},a的值,&a的值以及a[0]的值打印出来是一样的(同样的值其背后蕴含的物理意义是不一样的),a[0][0]的值打印出来是10。int (*p)[4] = a; int (*q)[3][4] = &a; int *r = a[0]; int s = a[0][0];

例题5:对于int a[3][4][5][6] = {10},a、&a、a[0]、a[0][0]、a[0][0][0]打印出来的值都是一样的(同样的值其背后蕴含的物理意义是不一样的),a[0][0][0][0]打印出来的值是10。int (*p)[4][5][6] = a; int (*q)[3][4][5][6] = &a; int (*r)[5][6] = a[0]; int (*s)[6] = a[0][0]; int *t = a[0][0][0]; int u = a[0][0][0][0];

例题6:对于int a,sizeof(a) = sizeof(a+1-1) = 4(这个值虽然一样,但是计算过程的机制是不一样的),对于int a[3],sizeof(a) = 12, sizeof(a+1-1) = 4(a+1-1了之后为什么丢失了数组大小信息?)

例题7:数组作为参数第一维大小丢失的原因是因为"pass by value"的机制(到底什么是"pass by value","value"是什么?)。

例题8:对于const int* p和int const* q,这两个变量性质一样,*p=20和*q=20会导致编译错误,p=NULL或q=NULL则不会,int* const r不一样,*r=20会执行正确,而r=NULL会导致编译错误,其原因是因为const修饰数据类型不一致造成的(const的位置到底应该怎么放?)。

例题9:对于数组变量int a[3] = {10},a=NULL编译出错,不能为数组变量赋值(需要注意的是,a是一个变量,不能赋值是因为取值的机制造成的,而不是因为变量a为只读变量)。

这些例题背后的原理和机制将在后续部门进行详细说明 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值