7.1 地址指针简单认识
-
地址(指针):为了正确地访问内存单元,必须为每个内存单元编上号。根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。通常也把这个地址称为指针
-
内存单元:在计算机中,所有的数据都是存放在存储器中的。一般把存储器中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不等,如整型量占 2 个单元,字符量占 1 个单元等
-
内存单元的指针和内存单元的内容:
- 对于一个内存单元来说,单元的地址即为指针,其中存放的数据才是该单元的内容
-
指针变量:在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。某个内存单元的地址或称为某内存单元的指针就是一个指针变量的值
-
示例:
表示有字符变量 C,其内容为 “K”(ASCII 码为十进制数 75),C 占用了 011A 号单元(地址用十六进数表示)
表示有指针变量 P,内容为 011A
这种情况我们称为 P 指向变量 C,或说 P 是指向变量 C 的指针
-
-
约定:“指针”是指地址,是常量,“指针变量”指取值为地址的变量
- 一个指针是一个地址,是一个常量。而一个指针变量却可以被赋予不同的指针值,是变量。 常把指针变量简称为指针
-
定义指针的目的:为了通过指针去访问内存单元
-
在一个指针变量中存放一个数组或一个函数的首地址的意义
- 因为数组或函数都是连续存放的。通过访问指针变量取得了数组或函数的首地址,也就找到了该数组或函数。这样做,将会使程序的概念十分清楚,程序本身也精练,高效
-
在C语言中,一种数据类型或数据结构往往都占有一组连续的内存单元。 用“地址”这个概念并不能很好地描述一种数据类型或数据结构,而“指针”虽然实际上也是一个地址, 但它却是一个数据结构的首地址,它是“指向”一个数据结构的,因而概念更为清楚,表示更为明确。这也是引入“指针”概念的一个重要原因
7.2 指针变量
-
变量指针:就是变量地址;指针变量:存放变量地址的变量
-
在程序中使用"*"符号表示"指向"
-
示例:(仅供暂时理解,不代表是正确的指针变量定义和赋值)
i=3; //变量 i 的值为 3 *i_pointer = 3; //指针变量 i_pointer所指向的变量为 3
-
-
定义一个指针变量
-
一般形式:
类型说明符 *变量名;
*:表示这是一个指针变量
变量名:即为定义的指针变量名
类型说明符:表示本指针变量所指向的变量的数据类型
-
示例:
int *p1;
表示 p1 是一个指针变量,它的值是某个整型变量的地址
至于 p1 究竟指 向哪一个整型变量,应由向 p1 赋予的地址来决定(类似于变量)
- 注意:一个指针变量只能指向同类型的变量,如 P1 只能指向整型,不能时而指向一个整型,时而又指向一个字符变量
-
-
指针变量的引用
-
在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址,所以提供了取地址运算符"&",来表示地址
-
一般语法格式:
&变量名;
变量本身必须预先说明
-
-
指针变量的赋值:
-
例如:有指向整型变量的指针变量 p,如要把整型变量 a 的地址赋予 p
-
采用指针变量初始化
int a; int *p=&a;
-
采用赋值语句
int a; int *p; p=&a;
-
-
错误的赋值操作:
-
把一个数赋予指针变量
int *p; p=100;
-
被赋值的指针变量前加"*"说明符
int a,*p; *p=&a;
-
-
变量与指针变量之间的简单操作:先定义两个整型变量 i、x,还定义了一个指向整型数的指针变量 ip
int i=100,x; int *ip;
-
把 i 的地址赋给 ip:
*ip=&i;
- 如果给变量 i 假设一个地址 300,这个赋值可形象理解为下图所示的联系
- 如果给变量 i 假设一个地址 300,这个赋值可形象理解为下图所示的联系
-
通过指针变量 ip 间接访问变量 i:
x=*ip;
运算符 * 访问以 ip 为地址的存贮区域,而 ip 中存放的是变量 i 的地址,因此,*ip 访问的是地址为 300 的存贮区域(因为是整数,实际上是从 300 开始的两个字节)
- 此时的赋值等价于
x=i;
- 此时的赋值等价于
-
改变指针变量的指向:
int i,j,*p1,*p2; i='a'; j='b'; p1=&i; p2=&j;
- 指针变量指向示意图:
p2=p1;
- 指针指向示意图:
*p2=*p1;
- 指针指向示意图:
- 指针变量指向示意图:
-
加减算术运算
- 对于指向数组的指针变量,可以加上或减去一个整数 n
- 设 pa 是指向数组 a 的指针变量,则 pa+n,pa-n,pa++,++pa,pa--,--pa 运算都是合法的
- 意义:把指针指向的当前位置(指向某数组元素)向前或向后移动 n 个位置
- 指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减运算是毫无意义的
- 对于指向数组的指针变量,可以加上或减去一个整数 n
-
-
通过指针访问它所指向的一个变量是以间接访问的形式进行的,所以比直接访问一个变量要费时间,而且不直观,所以不常用于访问
-
由于指针是变量,我们可以通过改变它们的指向,以间接访问不同的变量,使程序代码编写得更为简洁和有效
int x,y,*px=&x; y=*px+5; //表示把 x 的内容加 5 并赋给 y y=++*px; //表示 px 的内容加上 1 之后赋给 y,相当于++(*px) y=*px++; //相当于 y=*px; px++
-
示例:输入 a 和 b 两个整数,按先大后小的顺序输出 a 和 b
main() { int *p1,*p2,*p,a,b; scanf("%d,%d",&a,&b); p1=&a;p2=&b; if(a<b) { p=p1;p1=p2;p=p2; } printf("\na=%d,b=%d\n",a,b); printf("max=%d,min=%d\n",*p1, *p2); }
-
-
指针变量作为函数参数
-
函数的参数不仅可以是整型、实型、字符型等数据,还可以是指针类型
-
作用是将一个变量的地址传送到另一个函数中
-
示例:
swap(int *p1,int *p2) {int temp; temp=*p1; *p1=*p2; *p2=temp; }
-
说明:
- 指针变量只能进行赋值运算和部分算术运算及关系运算
- 指针运算符:
- 取地址运算符&:取地址运算符 & 是单目运算符,其结合性为自右至左,其功能是取变量的地址
- 取内容运算符*:取内容运算符 * 是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在 * 运算符之后跟的变量必须是指针变量
-
7.3 数组指针和指针数组
-
数组指针
-
指数组的指针,是一个指针,存放(指向)该数组的这个指针
-
定义方式与前面
-
引用:
- C 语言规定:如果指针变量 p 已指向数组中的一个元素,则 p+1 指向同一数组中的下一个元素
- 如果 p 的初值为&a[0],则:
- p+i 和 a+i 就是 a[i] 的地址,或者说它们指向 a 数组的第 i 个元素
- *(p+i) 或 *(a+i) 就是 p+i 或 a+i 所指向的数组元素,即 a[i]
- 指向数组的指针变量也可以带下标,如 p[i] 与 *(p+i) 等价
- 引入指针变量后,就可以用两种方法来访问数组元素了:
- 下标法,即用 a[i] 形式访问数组元素。在前面介绍数组时都是采用这种方法
- 指针法,即采用 *(a+i)或 *(p+i) 形式,用间接访问的方法来访问数组元素,其中 a 是数组名,p 是指向数组的指针变量,其处值 p=a
-
指向多维数组的指针变量
-
一般说明形式:
类型说明符 (*指针变量名)[长度]
“长度”表示 二维数组分解为多个一维数组时,一维数组的长度,也就是二维数组的列数
注意:“(*指针变量名)”两边的括号不可少
-
示例:
int (*p)[4]
表示 p 是一个指针变量,它指向包含 4 个元素的一维数组
-
-
-
指针数组
-
指指针的数组,是一个数组,是一组有序的指针的集合。 指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量
-
一般定义形式:
类型说明符 *数组名[数组长度]
-
示例:
int *a[3];
表示 a 是一个指针数组,有三个数组元素,每个元素值都是一个指针,指向整型变量
-
指针数组和二维数组指针变量的区别
- 二维数组指针:是单个的变量,其一般形式中"(*指针变量名)"两边的括号不可少
- 指针数组:表示的是多个指针(一组有序指针)在一般形式中"*指针数组名"两边不能有括号
-
指针数组也可以用作函数参数
-
7.4 字符串指针和指向字符串的指针变量
-
字符串的表示形式:
-
用字符数组存放一个字符串,然后输出该字符串
main() { char string[]="I LOVE China!"; printf("%s\n",string); }
-
用字符串指针指向一个字符串
main() { char *string=”I love China!”; printf("%s\n",string); }
-
-
字符串指针变量与指向字符变量的指针变量的定义和说明是相同的。是通过指针变量的赋值不同来区别
char c,*p=&c; //表示 p 是一个指向字符变量 c 的指针变量 char *s="C Language"; //则表示 s 是一个指向字符串的指针变量。把字符串的首地址赋予 s
-
注意:
- 字符串指针变量本身是一个变量,用于存放字符串的首地址。而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以 ‘\0’ 作为串的结束。字符数组是由于若干个数组元素组成的,它可用来存放整个字符串
- 当一个指针变量在未取得确定地址前使用是危险的,容易引起错误。但是对指针变量直接赋值是可以的。因为 C 系统对指针变量赋值时要给以确定的地址
7.5 函数指针变量和指针类型函数
-
函数指针变量
-
在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址
-
指向函数的指针变量 称为“函数指针变量”
-
一般定义形式:
类型说明符 (*指针变量名)();
(* 指针变量名)”表示“*”后面的变量是定义的指针变量
最后的空括号表示指针变量所指的是一个函数
-
示例:
int (*pf)();
表示 pf 是一个指向函数入口的指针变量,该函数的返回值(函数值)是整型
-
-
调用:
(*指针变量名) (实参表);
"(*指针变量名)"的两边的括号不可少,其中的 * 不应该理解为求值运算,在此处它只是一种表示符号
-
示例:
int max(int a,int b) { if(a>b) return a; else return b; } main() { int max(int a,int b); int (*pmax)(); int x,y,z; pmax=max; printf("input two numbers:\n"); scanf("%d%d",&x,&y); z=(*pmax)(x,y); printf("maxmum=%d",z); }
-
-
说明:
- 函数指针变量不能进行算术运算,这是与数组指针变量不同的
- 数组指针变量加减一个整数可使指针移动指向后面或前面的数组元素,而函数指针的移动是毫无意义的
-
-
指针类型函数
-
在C语言中允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针型函数
-
一般定义形式:
类型说明符 *函数名(形参表) { 函数体 }
函数名之前加了“*”号表明这是一个指针型函数,即返回值是一个指针
类型说明符表示了返回的指针值所指向的数据类型
-
示例:是通过指针函数,输入一个 1~7 之间的整数,输出对应的星期名
char *day_name(int n) { static char *name[] = { "Illegal day", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}; return((n<1||n>7)?name[0]:name[n]); }
-
-
注意:
- 函数指针变量和指针型函数这两者在写法和意义上的区别。如 int(*p)() 和 int *p() 是两个完全不同的量
- int (*p)() 是一个变量说明,说明 p 是一个指向函数入口的指针变量,该函数的返回值是整型量,(*p)的两边的括号不能少
- int *p() 是函数说明,说明 p 是一个指针型函数,其返回值是一个指向整型量的指针,*p 两边没有括号。作为函数说明,在括号内最好写入形式参数,这样便于与变量说明区别
- 对于指针型函数定义,int *p() 只是函数头部分,一般还应该有函数体部分
- 函数指针变量和指针型函数这两者在写法和意义上的区别。如 int(*p)() 和 int *p() 是两个完全不同的量
7.6 指向结构变量的指针
-
说明:
-
一般形式:
struct 结构名 *结构指针变量名
-
示例:说明一个指向 stu 的指针变量 pstu
struct stu *pstu
- 也可在定义 stu 结构时同时说明 pstu。与前面讨论的各类指针变量相同
-
-
赋值:
- 结构指针变量也必须要先赋值后才能使用
- 赋值是把结构变量的首地址赋予该指针变量,不能把结构名赋予该指针变量
- 如果 boy 是被说明为 stu 类型的结构变量,则:
pstu=&boy
是正确的,而:pstu=&stu
是错误的 - 结构名和结构变量是两个不同的概念,不能混淆
- 结构名只能表示一个结构形式,编译系统并不对它分配内存空间
- 只有当某变量被说明为这种类型的结构时,才对该变量分配存储空间
- 如果 boy 是被说明为 stu 类型的结构变量,则:
-
访问:
-
访问一般形式:
(*结构指针变量).成员名; //或者 结构指针变量->成员名;
如果采用括号形式要注意两侧的括号不可少,因为成员符“.”的优先级高于“*”。如去掉括号写作*pstu.num 则等效于*(pstu.num)
-
示例:
(*pstu).num; //或者 *pstu->num;
-
-
示例:
#include"stdio.h" struct str { int num; char *name; char sex; float score; } boy1={100,"Zhang san",'M',78.5},*pstu; int main() { pstu=&boy1; printf("Number=%d\nName=%s\n",boy1.num,boy1.name); printf("Sex=%c\nScore=%f\n\n",boy1.sex,boy1.score); printf("Number=%d\nName=%s\n",(*pstu).num,(*pstu).name); printf("Sex=%c\nScore=%f\n\n",(*pstu).sex,(*pstu).score); printf("Number=%d\nName=%s\n",pstu->num,pstu->name); printf("Sex=%c\nScore=%f\n\n",pstu->sex,pstu->score); }
Number=100 Name=Zhang san Sex=M Score=78.500000 Number=100 Name=Zhang san Sex=M Score=78.500000 Number=100 Name=Zhang san Sex=M Score=78.500000
可以看出这三种用于表示结构成员的形式是完全等效的
7.7 指向结构变量数组的指针
-
指针变量可以指向一个结构数组,这时结构指针变量的值是整个结构数组的首地址
- 结构指针变量也可指向结构数组的一个元素,这时结构指针变量的值是该结构数组元素的首地址
-
示例:
#include"stdio.h" struct str { int num; char *name; char sex; float score; } boy[5]={ {101,"Zhou ping",'M',45}, {102,"Zhang ping",'M',62.5}, {103,"Liou fang",'F',92.5}, {104,"Cheng ling",'F',87}, {105,"Wang ming",'M',58}, }; int main() { struct str *ps; printf("No\tName\t\t\tSex\tScore\t\n"); for(ps=boy;ps<boy+5;ps++) printf("%d\t%s\t\t%c\t%f\t\n",ps->num,ps->name,ps->sex,ps->score); }
-
将指向结构变量数组的指针当做函数参数使用
-
在 ANSI C 标准中允许用结构变量作函数参数进行整体传送。但是这种传送要将全部成员逐个传送,特别是成员为数组时将会使传送的时间和空间开销很大,严重地降低了程序的效率
-
因此最好的办法就是使用指针,即用指针变量作函数参数进行传送。这时由实参传向形参的只是地址,从而减少了时间和空间的开销
-
示例:假设已经定义了结构数组指针 stu
void ave(struct stu *ps) { ... } main() { ... void ave(struct stu *ps); ... }
-
7.8 指向指针的指针
-
通过指针访问变量称为间接访问。由于指针变量直接指向变量,所以称为“单级间址”。而如果通过指向指针的指针变量来访问变量则构成“二级间址”
-
定义形式:
类型说明符 **变量名;
-
示例:
main() { char *name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer desighn"}; char **p; int i; for(i=0,i<5,i++) { p=name+i; printf("%s\n",*p); } }
No Name Sex Score 101 Zhou ping M 45.000000 102 Zhang ping M 62.500000 103 Liou fang F 92.500000 104 Cheng ling F 87.000000 105 Wang ming M 58.000000
在循环语句 for 的表达式 1 中,ps 被赋予 boy 的首地址,然 后循环 5 次,输出 boy 数组中各成员值
-
注意:一个结构指针变量虽然可以用来访问结构变量或结构数组元素的成员, 但是,不能使它指向一个成员
- 错误使用方式:
ps=&boy[1].sex;
- 错误使用方式:
7.9 小结
-
有关指针的数据类型小结
定义 含义 int i; 定义整型变量 i int *p; 定义指向整型数据的指针变量 p int a[n]; 定义有 n 个元素的整型数组 a int *p[n]; 定义有 n 个指向整型数据的指针元素的指针数组 p int *(p)[n]; 定义有 n 个元素的一维指针变量 p int f(); 定义返回值为整型数据的函数 f int *p(); 定义返回值为整型指针的函数 p int (*p)(); 定义一个返回值为整型数据的函数的指针 p int **p; 定义一个指向整型变量的指针的指针 - void 类型指针:ANSI 新标准增加了一种“void”指针类型,即可以定义一个指针变量,但不指定它是指向哪一种类型数据
-
指针运算小结
-
指针变量加(减)一个整数:
- p++、p--、p+i、p-i、p+=i、p-=i
- 一个指针变量加(减)一个整数是将该指针变量的原值(是一个地址)和它指向的变量所占用的内存单元字节数加(减)
-
指针变量赋值:将一个变量的地址赋给一个指针变量
运算 说明 p=&a; 将 变量 a 的地址赋予 p p=array; 将数组 array 的首地址赋予 p p=&array[i]; 将数组 array 的第 i 个元素的地址赋予 p p = max; (max 已经定义)将 max 的值赋予 p p1=p2; (p1、p2 都是指针)将 p2 的地址赋予 p1 - 错误赋值:
p=1000;
- 错误赋值:
-
指针变量可以有空值:
p=NULL;
-
两个指针变量可以相减:如果两个指针变量指向同一个数组的元素,则两个指针变量值之差是两个指针之间的元素个数
-
两个指针变量比较:如果两个指针变量指向同一个数组的元素,则两个指针变量可以进行比较。指向前面的元素的指针变量“小于” 指向后面的元素的指针变量
-