一.地址
1.什么是地址
再计算机运行时,数据会存放在内存中,内存会以字节为单位划分为多个存储空间,并且为每个字节默认设置一个对应的编号,这个编号就是地址
地址只是计算机规定的一个值,所以不会占用内存的存储空间,地址显示的长度会根据系统及编译器的位数确定。64位编译器显示的地址为16个16进制数,32位编译器显示的地址为8个16进制数。
如果存放的数据只占用了一个字节,那么该数据占用的字节地址就是该数据的地址。
10000000 | 10000001 | 10000002 | 1000003 |
---|---|---|---|
a |
a的地址为10000000000
如果存放数据占用多个字节,那么该数据的地址就是第一个字节的地址
10000000 | 10000001 | 10000002 | 10000004 |
---|---|---|---|
a | b | c | d |
字符串abcd的地址为10000000000
在计算机运行时,内存会动态分配存放数据的位置,所以同样的数据在每次运算时存放的地址可能会产生变化。
2.获取地址值
只有通过获取数据的地址才能对内存中的数据进行操作。在C语言中,使用取地址符&获取内存中数据的地址。最典型的取地址的应用就是使用库函数scanf()。
可见同样的数据在每次运算时存放的地址可能会产生变化。
- %p占位符指定输出的数据为地址,不可以用%x占位符代替。
二.指针
指针可以说是C语言的灵魂,指针指代数据存放在内存中的地址。每个地址代表一个数据。
1.声明指针
指针用于指代数据在内存中的地址,用户可以通过指针访问对应的数据。在内存中数据是按照数据类型存放的。
int a = 8;
…… | 10000000 | 10000001 | 10000002 | 10000003 | …… |
---|---|---|---|---|---|
0 | 0 | 0 | 8 |
一个数据的地址是指存放该数据的存储单元的首地址,这里就是10000000000,但通过首地址是无法访问到数据8的。
a.声明指针变量
在声明指针变量时,不但要声明指针变量的名称,还要声明指针变量的长度,用于划分指代数据的范围。声明指针变量的语法包含:基类型、星号(*)、变量名 三部分。
基类型 *变量名
- 星号(*)是一个说明符,告诉计算机,这是个指针变量。
- 变量名是指针变量的名称,要符合标识符的命名规则。
- 基类型指定了指针指代的数据在内存中所占存储单元的大小,即该数据在内存中占几个字节,通过指定这个长度,可以确定指针访问数据时的终点位置。
在声明指针变量的语法中,星号(*)默认靠近变量名。这样该语法可读性高。
但以下语法都是正确的
int* p;
int *p;
int * p;
如果星号靠近基类型,虽然正确但是可读性低。如果int一个指针变量和多个变量,就会产生误解
int* a,b,c;
容易误解为三个指针变量。
b.数据类型与基类型的比较
数据类型与基类型有不同的定义。作用也是不一样的。
(1)数据类型用于声明存放数据的变量。
int a;
改语法表示申请了4个字节大小的存储单元以存放某数据,该存储单元用变量a指代。数据类型变量是用于存放数据的。如图
…… | 10000000 | 10000001 | 10000002 | 1000003 | …… |
---|---|---|---|---|---|
0 | 0 | 0 | 0 |
(2)基类型用于声明指针变量
int *a;
改语法用于声明一个变量a,指代某段数据的地址(变量a的大小为32位,即占4个字节,具体大小与编译环境有关)。并且规定从首地址开始往后移动四个字节长度的位置是指针访问某数据的终止位置。
所以基类型实际上可以被理解为指针变量的长度单位,最小的为1个字节长度,int为4个字节长度。如图
…… | 10000000 | 10000001 | 10000002 | 10000003 | …… |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | ||
第一个数据地址 | 指针访问该数据的终止位置 |
基类型规定了指针访问数据时,在内存中的终止位置,即数据存储单元长度。
基类型和数据类型是完全不同的
2.给指针变量赋值
在声明指针变量后,就要将指针变量初始化,即指定指针可以访问的内存地址。给指针变量赋值为以下三种
a.&变量名赋值
&变量名赋值是指通过取地址符(&)获取数据存储的内存地址,然后将该内存地址赋给指针变量。
-
&变量名复制的语法如下:
基类型 *变量名 = &变量名
-
通过&变量名给指针变量赋值的语句如下:
int a = 9; int *p = &a;
-
或者将声明指针变量语句与给指针变量赋值语句分开
int a = 9; int *p; p = &a;
这段语句把变量a所指代的数据地址赋给了指针变量p,所以指针变量p指代的是地址而不是数据“9”。
b.指针变量赋值
指针变量赋值是将指针变量的值赋给一个空指针变量。在此,要使用赋值运算符“=”、有值的指针变量及空指针变量。
-
指针变量赋值的语法如下
空指针变量名=有值的指针变量名
-
指针变量赋值的语句如下:
int i = 8; int *p = &i; int *q; q = p;
-
或将声明指针变量的语句与指针变量赋值语句合并起来。
int i = 8; int *p = &i; int *q = p;
这段语句把指针变量p指代的地址赋给了指针变量q,给指针变量q赋给的是一个地址,而不是数据8。
其中,变量i的存储空间用于存放数据8 。指针变量p与q存放的都是数据8的地址。在将指针变量p赋给指针变量q的过程中,是把地址赋给了指针变量q。这种复制要求赋值运算符“=”两边的值都必须为指针变量。
c.赋空值
只要申请一个存放地址的备用存储空间,就可以给指针变量赋一个空值,
-
赋空值的语法如下
指针变量名 = NULL
-
赋空值的语句如下
#include<stdio.h> int *p = NULL;
-
开发人员也可以将声明指针变量语句与给指针变量赋值的语句分开
#include<stdio.h> int *p; p = NULL;
这段语句给指针变量p赋了一个空值。
当给指针赋一个空值,指针地址将变为00000000。
上面三种给指针变量赋值的方式都会用到赋值运算符“=”,在进行指针变量赋值运算时,赋值运算符的两则数据的基类型必须是相同的,否则会报错
3.动态分配存储空间
普通变量一旦被声明后,无论他是否参与实际运算,计算机都会给她分配存储空间,这样就造成了一些空间的浪费,为了避免这种,C语言提供了动态分配存储空间的解决办法,即只有数据参与运算,才会在从内存中给他分配对应的存储空间
在C语言中,函数malloc()和calloc()用于动态分配存储空间。使用这两个函数时,都要引用头文件#include<stdlib.h>或#include<malloc.h>。
a.函数 malloc()
函数malloc()只有一个参数。该函数可以动态申请一个连续的存储空间,并返回该存储空间的首地址,调用malloc()的语法:
(基类型*)malloc(size);
-
(基类型*)是指强制将返回值转换为指定基类型,这样才能将存储空间的值赋给相同基类型的指针变量。其中,小括号不可以省略。
-
size为指定动态分配的存储空间的大小,单位为字节。
还有一种更加完善的调用malloc()的语法,而这种语法才是标准语法,而且能兼容多个平台(基类型*)malloc(sizeof(数据类型)*number); 第二个星号为乘法运算符
-
(基类型*)是指强制将返回值转换为指定基类型。其中小括号不能省略
-
sizeof(数据类型)可以获取当前平台数据所占字节的多少,由于不同平台的相同类型数据所占字节的多少不同,所以使用sizeof(数据类型)可以更加准确的分配存储空间大小。
-
number是指申请的存储单元个数
-
sizeof(数据类型)number指定了动态分配的最终存储空间大小。
函数malloc()只能动态分配存储空间,不会对存储空间中的数据进行初始化。由于存储空间是不断重复使用的,所以申请的存储空间可能会存在遗留数据。
p = (int)malloc(sizeof(int) * 1);这行代码表示申请4个字节的int数据类型的存储空间,并把返回值强制转换为int基类型后赋值给指针变量p。
b.函数 calloc()
函数calloc()有两个参数:第一个参数规定申请几个单位存储空间;第二个参数规定申请单位存储空间大小。
调用calloc()的语法如下:
(基类型*)calloc(number,sizeof(数据类型));
- (基类型*)是指强制将返回值转换为指定基类型。其中小括号不可以省略。
- number是第一个参数,用于指定申请几个单位空间。
- sizeof(数据类型)是第二个参数,可以获取当前平台数据所占字节的多少,这个字节的多少便是每个单位存储空间大小。
p = (int*)calloc(1,sizeof(int));这行代码表示申请4个字节的int数据类型的存储空间,并把返回值强制转换为int基类型后赋值给指针变量p。
c.函数free()
当对应的数据使用完后,就要使用函数free()回收动态申请的存储空间,把之前动态申请的存储空间返还给系统。函数free()要与函数malloc或calloc()配对使用。
调用free()的语法如下:
free(指针变量名)
其中,指针变量名会指向数据所占的存储空间。在使用函数free()回收指针变量指向的存储空间后,要将该指针变量赋值给NULL,否则该指针变量存放的地址会指向一个未知的数据,此时将这种指针变量成为野指针。
三.指针运算
指针运算是指对地址进行运算。通过指针运算可以对内存中的数据进行相关操作。
1.使用存储单元值
如果使用存储单元值,就要使用间接访问运算符(*)。间接访问运算符又称间址运算符,为单目右结合运算符。
间接访问运算符的语法如下:
*操作数
再通过函数malloc()动态分配存储空间时,不会对存储空间的数据进行初始化。再通过calloc()函数动态分配存储空间时,会将存储空间的数据初始化为0。
2.移动指针
移动指针就是对指针进行加减运算,基类型是对指针进行加法和减法运算的基础单元,当指针加1时,代表指针移动了一个基类型长度。
由于C语言没有提供字符串数据类型,所以在将字符串赋值给指针变量时,传递的是字符串的首地址,而不是具体字符。
在C语言中,只有当指针指向连续的数据存储单元时,移动指针才有意义。如果不是连续的数据存储单元,当指针移动后,无法知道指针指向了什么数据,也就没有意义了。移动指针可以对指针进行加法和减法运算外,还可以对指针进行自加和自减运算。对指针的自加和自减运算均为单目右结合
++指针变量名、指针变量名++
--指针变量名、指针变量名--
除了对指针进行自加和自减运算外,还可以对指针的存储单元值(*指针变量名)进行自加和自减运算,
++*指针变量名、*指针变量名++
--*指针变量名、*指针变量名--
间接访问运算符“*”与自加和自减运算符的优先级是一样,都是右结合。
对指针的存储单元值进行自加运算的过程如下:
- ++*p表示先取数据,然后对数据进行自加(++)运算,整个自加过程处理的都是具体数据。
- p++表示根据结合性,先对p++进行结合但搁置自加(++)运算,然后进行p运算。其中,被搁置的自加(++)运算会在跳出表达式*p++后执行。
3.指针比较
指针比较就是对地址的比较。指针比较一般用于判断两个存储单元在连续内容中的先后关系。指针比较会用到(>) (<)两种比较运算符。
四.二级指针和多级指针
在C语言中,除上述的基础指针(又称一级指针)外,还有二级指针与多级指针。无论是哪一种指针,它们存放的都是地址。
1.二级指针
二级指针就是指向地址的指针。他指代的是其他指针的地址。指针变量在内存中也会占用空间,所以指针变量也会有自己的地址。如果将指针变量a的地址存放到指针变量b中,此时指针b就是一个二级指针变量。
a.定义二级指针
声明二级指针变量的语法
数据类型 **变量名
- 数据类型是指二级指针指向的数据的数据类型。
- 双星号(**)是说明符,用于告诉计算机,该变量是二级指针变量。
- 变量名就是二级指针变量的名称,并要符合标识符的命名规则
给二级指针变量赋值与给一级指针变量赋值的方式是一样的
二级指针变量的基类型由数据类型与双星号( ** )组成。其中双星号 ( ** )表示二级指针变量会通过地址间接跳转两次,然后根据数据类型的长度确定指针访问的数据最终位置。
b.使用二级指针
二级指针与两个间接访问运算符(**)结合使用就可以用于获取二级指针指向的数据。
故二级指针q保存的是一级指针p所在存储空间的地址,而不是数据10的地址。但是,通过指针变量q保存的地址也可以访问到数据10。
如果使用二级指针变量直接存放数据的地址,就会出现无法将一级指针变量转换为二级指针变量的错误提示。
2.多级指针
三级指针,四级指针都可以被称为多级指针,多级指针就如同把藏宝图b的位置又藏在了藏宝图c中,然后把藏宝图c又到了d中,依此类推,从而形成多级指针,声明多级指针变量与声明一级指针变量或二级指针变量唯一的区别就是星号(*)多少的不同,即有几个星号就是几级指针
多级指针变量只能存放其上一级的指针变量地址。不能越级,不然会出错
五.指针应用
指针可以直接处理内存中的数据。在重复性操作的情况下,使用指针来读取数据,可以明显提高程序的执行效率。在C语言中,指针的用处十分广泛,包括处理字符串、函数传值等。
1.处理字符串
通过移动指针,可以将字符串中的每个字符依次进行处理,如打印。
2.作为函数形参
函数可以通过return语句返回一个值,但不能返回多个值,如果将指针作为函数参数使用,就可以解决这个问题。
3.作为函数返回值
指针还可以作为函数返回值被使用。在声明函数是,函数类型实际是return语句返回值的类型。因此,如果函数返回值为指针类型,那么函数类型也为指针类型,而这种函数又称指针函数。
4.函数指针
函数指针是指向函数的指针。通过函数指针变量可以访问函数的地址。
声明函数指针变量的语法如下:
基类型(*函数名)(数据类型 参数)
其中小括号不可以省略;如果函数没有形参,则第2个括号中的数据类型与参数可以省略给函数指针变量赋值的方式与普通指针的一样。
函数指针变量名=&函数名;
取地址&可以省略
在给函数指针变量赋值时,所赋值的函数要与函数指针变量在声明时的基类型、参数的基类型,参数个数保持一致。不然会出错。