共包含八个部分:地址和指针的概念、变量的指针和指向变量的指针变量、通过指针引用数组、通过指针引用字符串、指向函数的指针、返回指针值得函数、指针数组和多重指针、动态分配内存与指向它的指针变量
一、地址和指针的概念
一个变量的地址称为该变量的“指针”,专门用来存放指针的变量就是“指针变量”。
图1
如图1所示,i即为变量,存放int数值3,在内存中的地址为2000;2000是变量i的地址,亦成为指针;i_p即为指针变量,存放i的地址2000,即指向变量i的指针变量。
二、变量的指针和指向变量的指针变量
1、指针变量的定义
包括:基类型 * 指针变量名 ,三个部分缺一不可,其中:基类型是指针变量指向的变量的类型,定义是必须指定;*表示该变量是指针变量。
注意:指针变量只能存放地址(指针);赋给指针变量的变量地址必须与指针变量的基类型具有相同类型的变量的地址。
2、指针变量的引用
仍如图1中所示,i=3;*i_p=3;i_p=&i=2000。
3、指针变量作为函数参数
基本类型变量作为函数实参,函数中变量值(形参)的改变,不会影实参的改变;
指针变量作为函数实参,由于直接访问实参在内存中的地址,所以,函数中变量值(形参)的改变,会导致地址中存放的数值改变,也即实参的改变。
三、通过指针引用数组
1、数组元素的指针
int a[3]={1,2,3};
int *p;
p=&a[0]与p=a等价,因为C语言中数组名代表数组首元素的地址。
2、通过指针引用数组元素
(1)下标法
int i;
for(i=0;i<3;i++){
printf("%d ",a[i]);
}
(2)数组名计算数组元素地址
int i;
for(i=0;i<3;i++){
printf("%d ",*(a+i));
}
在C语言中(1)中,编译系统会把a[i]转换成*(a+i)来处理,所以a[i]与*(a+i)是无条件等价的。
(3)指针变量指向数组元素
int i;
int *p;
p=a;
for(i=0;i<3;i++){
printf("%d ",*(p+i));
}
或者
for(;p<(a+3);p++){
printf("%d ",*p);
}
注意:数组名a代表数组首元素的地址,是一个指针常量,它的值在程序运行期间保持不变,所以不能(a++),只能定义一个指针变量p,使用(p++)。
3、数组名做函数参数
共有四种对应关系。分别如下:
(1)形参和实参都用数组名
int main() int show(int a[], int n)
{ {
int a[3]={1,2,3}; .
int n=3; .
show(a,n); .
} }
(2)实参用数组名,形参用指针变量
int main() int show(int *p, int n)
{ {
int a[3]={1,2,3}; .
int n=3; .
show(a,n); .
} }
(3)实参用指针变量,形参用指针变量
int main() int show(int *p, int n)
{ {
int a[3]={1,2,3};
int *p; .
int n=3; .
p=a; .
show(p,n); .
} }
(4)实参用指针变量,形参用数组名
int main() int show(int a[], int n)
{ {
int a[3]={1,2,3};
int *p; .
int n=3; .
p=a; .
show(p,n); .
} }
4、指针引用多维数组
一维数组的数组名指向首元素的地址,二维数组的数组名是指向行的首地址
int sd[3][3] = { {11,12,13},{21,22,23},{31,32,33} };
有以下说明:
sd:二维数组名,指向一维数组sd[0],即0行首地址;
sd[0],*(sd+0),*sd:0行0列元素的地址;
sd+1,&sd[1]:指向1行首地址;
sd[1],*(sd+1):指向1行0列元素的地址,即a[1][0]的地址;
sd[1]+2,*(sd+1)+2,&sd[1][2]:指向1行2列元素的地址,即a[1][2]的地址;
*(sd[1]+2),*(*(sd+1)+2),sd[1][2]:指向1行2列的元素,即a[1][2]的值。
四、通过指针引用字符串
1、字符指针
int main_pointer_char()
{
char * cp;
cp = "hello zhangkang";
printf("%c \n",*cp);
printf("%s \n", cp);
char cs[] = "hello zhangkang";
printf("% s\n",cs);
return 0;
}
其中,如红色字体所示,字符指针变量cp指向字符串“hello zhangkang”首字符的地址;如蓝色字体所示,使用“%s”可以输出该字符串,以为C语言在内存中将字符串保存为一个字符数组,该内存是一段连续的内存空间,故可以根据首字符的地址输出整个字符串;字符串存储为字符数组如黑色加粗部分所示。
2、使用字符指针变量和字符数组的讨论
(1)、字符数组由若干个元素组成,每个元素中存一个字符,而字符指针变量中存放的是字符串首字符的地址。
(2)赋值方式
字符数组只能使用粗体所示的方式,不能使用斜体所示的方式 字符指针两种方式均可
char cpb[]= "zhangkang"; char *a;
char cpc[]; a = "hello";
cpc= "zhangkang"; char *a= "hello";
(3)定义一个字符数组,在编译时即为它分配了内存单元,就有确定的地址;而定义一个字符指针变量时,给指针变量分配内存单元,在其中可以存放一个字符变量的地址,该指针指向一个字符型数据,但如果赋予一个地址值,则它并未指向一个确定的字符数据,这种做法是相当危险的。定义任何变量,必须赋初值。
(4)指针变量的值是可以改变的,例如char *p="hello";*p='h';*(++p)='e';然而,数组名虽然也代表地址,但它是常量,它的值是不能改变的,char s[]="hello";s=s+1;粗体部分是不可以的。
(5)指针变量指向数组,则可以使用指针变量带下标的形式引用数组,同理,字符指针变量指向字符串,也可以使用指针变量带下标的形式引用所指字符串中的字符。
int da[] = {1,2,3}; char *t;
int *d; t = "hello";
d = da; printf("%c \n",t[2]);
printf("%d \n",d[2]);
(6)字符数组中的各元素的值是可以改变的(可以对它们再赋值),但字符指针变量指向的字符串中的内容是不可以被取代的(不能对它们再赋值)。如:
char *ta= "hello";
char tb[] = "hello";
ta[1] = 'a'; 是不可以的
tb[1] = 'a';可以
printf("% s \n", ta);
printf("% s \n", tb);
五、指向函数的指针
1、定义形式:
数据类型 (* 指针变量名) (函数参数列表)
如 int (*fp) (int a,int b);
数据类型为返回值的类型;(* 指针变量名)加括号的指针变量一定是指向函数的;(函数参数列表)函数的相关参数
2、“int (*fp) (int a,int b)”表示定义一个指向函数的指针变量fp,它不是固定的指向某一个函数,而只是定义了一个这种类型的变量,专门用来存放函数的入口地址。在程序中,把哪个函数(函数返回值和参数列表与指针变量的相同)的额入口地址赋给该指针变量,它就指向哪一个函数。一个程序中,函数指针变量可以先后指向不同的函数。
3、如示例程序中粗体所示,将函数的入口地址(函数名即表示函数的入口地址)赋给指向函数的指针变量;只用函数名即可,不需要函数的参数列表,这在定义指向函数的指针变量时已经指定了。
4、如示例程序中斜粗体所示,调用指向函数的指针变量时,只需要用指针变量取代函数名即可。
5、指向函数的指针变量不能使用例如fp++、fp--、fp+n等操作,因为fp只能指向函数的入口处,不可能指向函数中的某一条指令。
6、使用函数名调用函数,只能调用一个函数;而使用指针变量可以比较灵活的调用不同的函数,即指针变量作为函数参数,根据不同情况,使指针指向不同的函数,从而调用不同的函数。
以下为示例程序:
#include <stdio.h>
#include "multi.h"
int copy(char *,char *);
int main_pointer_char()
{
int (* fp) (char *, char *);
fp = copy;
char *temp;
char cpa[] = "hello";
char cpb[]= "zhangkang";
char *a, *b;
a = cpa;
b = cpb;
printf("% s \n", a);
printf("% s \n", b);
fp(a,b);
printf("% s \n",a);
printf("% s \n", b);
return 0;
}
int copy(char *a, char *b)
{
for (;*a != '\0';a++,b++) {
*b = *a;
}
*b = '\0';
return 0;
}
六、返回指针的函数
定义如下形式:
类型名 * 函数名 (参数列表)
与第五节中形成对比,该函数的返回值是一个指针,这类函数称为指针型函数(返回值是指针)。
注:因为括号的优先级高于*。所以函数名先与括号结合,这是函数类型。
七、指针数组和多重指针
1、指针数组
类型名 * 数组名[数组长度]
使用场景:指针数组比较适合用来指向若干个字符串,字符串的长短不一,使得字符串的处理更加灵活方便。
例如下面示例程序中的字符指针数组p所示,p[0]指向“hello”,p[1]指向“ zhang”,p[2]指向“ kang”,从而不必使用行和列相同的二维数组保存长短不一的字符串集合,导致浪费内存。
#include <stdio.h>
#include "multi.h"
int main_pointer_multi()
{
char * p[3] = {"hello"," zhang"," kang"};
printf("%s \n",p[1]);
int a[2][3] = { {1,2,3},{4,5,6} };
int (*dp)[3];
dp = a;
printf("%d \n", a[1][1]);
printf("%d \n",*(*(dp+1)+1));
return 0;
}
示例程序中的蓝色所示即为指针数组,即数组中的所有元素都是指针,也是地址,相当于一个指针变量。
例如:
int * p[3],数值型的一维指针数组,由于[]的优先级高于*,所有p先与[3]结合,构成数组,然后再与*结合,表明数组中存放的是指针(该数组就是指针类型的数组),int表示每个元素(指针变量)指向一个int型的变量。
int (* p)[3]与int * p[3]有着本质的区别,由于*先与p结合,从而表明这是一个每行包含3列的二维数组,(* p)指向行,如示例程序中所示,*(dp+1)等价于* a[1]。
2、指向指针数据的指针
char * p[3] = {"hello"," zhang"," kang"};;
printf("%o \n", p[1]);
printf("%s \n",p[1]);
char **cp;
cp = p+1;
printf("%o \n", *cp);
printf("%s \n", *cp);
char *dp;
dp = p[1];
printf("%o \n", dp);
printf("%s \n", dp);
如粗体部分所示,定义指向指针的指针cp,让cp指向指针数组p中的第二个元素(也是一个指针,存储的是字符串“ zhang”首字符的地址),从而*cp指向“ zhang”首字符的地址,使用%s 即可得到该字符串。
八、动态分配内存与指向它的指针变量
malloc,calloc,free,realloc
总结:
指针的定义形式:
int i; 定义整型变量
int *p; 定义指向整型数据的指针变量p
int a[n]; 定义数组长度为n的整型数组a
int *p[n]; 定义长度为n的指针数组p,数组元素为指针,指向整型数据
int (*p)[n]; p指向二维数组中的各个一维数组,一维数组的元素个数为n,等价于int a[][n],二维数组名指向行,即指向各个一维数组
int f(); 定义返回值为整型的函数
int * p(); 定义返回值为指针的函数,指针指向整型数据
int (*p) (); 指针函数,定义指针p指向函数的入口地址
int **p; 定义指向指针变量的指针变量p,p指向的指针变量,指向整型数据
void *p; 定义一个不指向具体数据的指针变量,基类型为空