一、变量的地址

内存变量简称变量,在C语言中,每定义一个变量,系统就会给变量分配一块内存,而内存是有地址的。如果把计算机的内存区域比喻成一个大宾馆,每块内存的地址就像宾馆房间的编号。

C语言采用运算符&来获取变量的地址。请看下面的示例。

示例(book50.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* 程序名:book50.c,此程序用于演示获取变量的地址
* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>

int main()
{
int ii=10;
char cc='A';
double dd=100.56;

printf("变量ii的地址是:%p\n",&ii);
printf("变量cc的地址是:%p\n",&cc);
printf("变量dd的地址是:%p\n",&dd);

return 0;
}

运行效果

在这里插入图片描述

注意:

1)在printf函数中,输出内存地址的格式控制符是%p,地址采用十六进制的数字显示。

2)book50程序运行了两次,每次输出的结果不一样,原因很简单,程序每次运行的时候,向系统申请内存,系统随机分配内存,就像您去宾馆开房,如果您不提前预约指定房号,每次得到的房间编号大概率不会相同。

二、指针

指针是一种特别变量,全称是指针变量,专用于存放其它变量在内存中的地址编号,指针在使用之前要先声明,语法是:

1
datatype *varname;

datatype 是指针的基类型,它必须是一个有效的C数据类型(int、char、double或其它自定义的数据类型),varname 是指针的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个场景中,星号是用来表示这个变量是指针。以下是有效的指针声明:

1
2
3
int     *ip;    // 一个整型的指针
char *cp; // 一个字符型的指针
double *dp; // 一个 double 型的指针

三、对指针赋值

不管是整型、浮点型、字符型,还是其他的数据类型的内存变量,它的地址都是一个十六进制数,可以理解为内存单元的编号。我们用整数型指针存放整数型变量的地址;用字符型指针存放字符型变量的地址;用双精度型指针存放双精度型变量的地址,用自定义数据类型指针存放自定义数据类型变量的地址。

把指针指向具体的内存变量的地址,就是对指针赋值。

示例book51.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
* 程序名:book51.c,此程序用于演示指针变量
* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>

int main()
{
int ii=10;
char cc='A';
double dd=100.56;

int *pii=0; // 定义整数型指针并初始化
char *pcc=0; // 定义字符型指针并初始化
double *pdd=0; // 定义双精度型指针并初始化

pii=&ii; // 数型指针并指向变量ii
pcc=&cc; // 字符型指针并指向变量cc
pdd=&dd; // 双精度型指针并指向变量dd

// 输出指针变量的值
printf("pii的值是:%p\n",pii);
printf("pcc的值是:%p\n",pcc);
printf("pdd的值是:%p\n",pdd);
}

运行效果

在这里插入图片描述

四、通过指针操作内存变量

定义了指针变量,并指向了内存变量的地址,就可以通过指针来操作内存变量(在指针前加星号*),效果与使用变量名相同。

示例(book52.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* 程序名:book52.c,此程序演示指针的使用。
* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>

int main()
{
int ii=10;

int *pii=0; // 定义整数型指针并初始化

pii=&ii; // 数型指针指向变量ii

// 通过指针操作内存变量,改变内存变量的值
*pii=20; // 同ii=20;

printf("pii的值是:%p\n",pii);
printf("*pii的值是:%d\n",*pii);
printf("ii的值是:%d\n",ii);
}

运行效果

在这里插入图片描述

五、再来讨论函数的参数传递

在我们之前讲的函数的参数章节中,book49.c演示了函数的参数传递,主程序调用funcld函数的时候,传递的是变量的值,现在把它修改一下。

示例(book55.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* 程序名:book55.c,此程序演示函数参数的传递和指针
* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>

// 声明funcld函数,p是一个指针变量
void funcld(int *p);

int main()
{
int a=10;

printf("位置一:a是一个变量,变量的地址是%p,a的值是 %d\n",&a,a);
funcld(&a); // 调用函数,传递变量a的地址的值
printf("位置二:a是一个变量,变量的地址是%p,a的值是 %d\n",&a,a);
}

void funcld(int *p)
{
printf("位置三:p是一个指针 %p, 指向的内存的地址是 %d\n",p,*p);
*p=20;
printf("位置四:p是一个指针 %p, 指向的内存的地址是 %d\n",p,*p);
}

运行效果

在这里插入图片描述

book55.c演示了函数参数和指针的使用,主程序把变量a的地址传递给函数funcld,funcld函数的参数p是一个指针,接存放变量a的地址。在函数funcld中,根据指针中的地址直接操作内存,从而修改了主程序中变量a的值。

我们已经使用scanf函数很多次了,调用scanf函数的时候,需要在变量前面加符号&,其实就是把变量的地址传给scanf函数,scanf函数根据传进去的地址直接操作内存,改变内存中的值,完成了对变量的赋值。

六、空指针

空指针就是说指针没有指向任何内存变量,指针的值是空,所以不能操作内存,否则可能会引起程序的崩溃。

示例(book56.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
* 程序名:book56.c,此程序演示操作空指针引起程序的崩溃
* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>

int main()
{
int *pi=0; // 定义一个指针

printf("pi的值是 %p\n",pi);

*pi=10; // 试图对空指针进行赋值操作,必将引起程序的崩溃

return 0;
}

运行效果

在这里插入图片描述

段错误(Core Dump),就是程序崩溃掉了。

七、数组的地址

在C语言中,数组占用的内存空间是连续的,数组名是数组元素的首地址,也是数组的地址。

示例(book57.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* 程序名:book57.c,此程序数组的地址
* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>

int main()
{
char name[51];
strcpy(name,"C语言技术网(www.freecplus.net)");

printf("%p\n",name);
printf("%p\n",&name);
printf("%p\n",&name[0]);

printf("%s\n",name);
printf("%s\n",&name);
printf("%s\n",&name[0]);
}

运行效果

在这里插入图片描述

从以上的示例可以看出,数组名、对数组取地址和数组元素的首地址是同一回事。在应用开发中,程序员一般用数组名,书写最简单。

八、地址的运算

地址可以用加(+)和减(-)来运算,加1表示下一个存储单元的地址,减1表示上一个存储单元的地址,一般情况下,地址的运算适用于数组,对单个变量的地址运算没有意义。

示例(book58.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* 程序名:book58.c,此程序演示地址的运算。
* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>

int main()
{
char cc[4]; // 字符数组
int ii[4]; // 整数数组
double dd[4]; // 浮点数组

// 用地址相加的方式显示数组全部元素的的址
printf("%p %p %p %p\n",cc,cc+1,cc+2,cc+3);
printf("%p %p %p %p\n",ii,ii+1,ii+2,ii+3);
printf("%p %p %p %p\n",dd,dd+1,dd+2,dd+3);
}

运行效果

在这里插入图片描述

大家请注意,第一行输出的每个地址的增量是1,第二行的每个地址的增量是4,第三行的每个地址的增量是8,为什么会这样?因为数组cc是char型,一个存储单元是1个字节,数组ii是int型,一个存储单元是4个字节,数组ll是long型,一个存储单元是8个字节,地址加1指的是下一个存储单元,不是数学意义中的1。

在应用开发中,地址的运算很重要,主要用于字符串操作,在以后的字符串章节中我将详细介绍。

九、指针占用内存情况

指针也是一种内存变量,是内存变量就要占用内存空间,在C语言中,任何类型的指针占用8字节的内存(32位操作系统4字节)。

1
2
3
printf("sizeof(int *) is %d.\n",sizeof(int *));        // 输出:sizeof(int *) is 8
printf("sizeof(char *) is %d.\n",sizeof(char *)); // 输出:sizeof(char *) is 8
printf("sizeof(double *) is %d.\n",sizeof(double *)); // 输出:sizeof(double *) is 8

输出的结果都是8。

十、指针的其它知识

本章节介绍的知识已经包括了指针99%的用法,还有一些的知识点如指针的指针、函数指针等,这些概念难以理解,应用场景极少。学习的方法应该是循序渐进,等功力增长之后,那些复杂的概念其实也很容易。如果在这里就把人搞晕了,就没办法继续学习下去。

十一、小结

操作变量可以用变量名,也可以用变量的地址。

指针用一句话可以概括,就是用来存放变量的地址,是一种中间状态的变量。

变量的地址是变量的地址,指针是指针,地址和指针之间的关系像水与水桶的关系,表达的时候要严谨一些,不要把地址说成指针,也不要把指针说成地址。

指针就这么简单,您自己不要把自己晕了就行。

十二、课后作业

1、编写示例程序,把本章节的知识全部演示一遍,必须充分理解每一个细节,指针对C/C++程序员极其重要,没有指针,程序没法写。

2、系统会为变量分配内存,也会为常量分配内存,有内存就有地址,试试以下代码,如果不能理解就跳过。

1
2
3
4
char *pstr="西施";
printf("pstr=%p\n",pstr);
printf("pstr=%s\n",pstr); // 不会出现段错误(Core dump)
strcpy(pstr,"杨玉环"); // 会出现段错误(Core dump)

十三、版权声明

C语言技术网原创文章,转载请说明文章的来源、作者和原文的链接。
来源:C语言技术网(www.freecplus.net
作者:码农有道