目录
一、指针变量
数据对象是指存储在内存中的一个指定数据类型的数值或字符串,它们都有一个自己的地址,而指针便是保存这个地址的变量。也就是说:指针是一种保存变量地址的变量
定义指针变量的格式: 类型名 *指针变量名;
我们定义一个整型变量 int var_runoob = 10;
再定义一个跟他数据类型相同的指针 int *p;
然后把 整形变量的地址赋给指针变量 p = &var_runoob;(他们的而关系如下图所示)
如果想访问这个单元,可以通过 访问变量名直接访问,还可以通过 访问该变量的地址间接访问。
二、指针的运算
比如说定义一个整形的指针变量
int *p; //假如指针变量此时指向 地址1000
p++; //当他加一后,他会指向下一个整数,int整型占4个字节,也就是会指向1004
如果是一个字符型的变量
char *p; //假如指向 地址1000;
p++; //那么现在,因为char占1个字节,所以指针指向1001
//运算优先级
1、*p++
由于++和*同优先级,结合方向为自右向左,它等价于 *(p++),先引用p的值,再进行自增运算
如, for(i=0,i<10,i++,p++)
printf("%d",*p);
可以改写为
for(i=0,i<10,i++)
printf("%d",*p);
三、指针和地址的区别
指针和地址最大的区别就是指针是有类型的,地址是没有类型的
不能对地址进行算术操作,在涉及诸如数组等操作时就不能通过地址的自增和自减来访问数组的各个变量。通过对指针的引用,就可以通过对指针进行加减操作(数组不越界的情况下)方便地访问数组的各个元素。
从上分析可以得出,指针是由地址和类型两部分构成的,指向数据的指针不仅记录该数据的在内存中的存放的地址,还记录该数据的类型,即在内存中占用几个字节,这是地址所不具有的。指向函数的指针不仅记录函数的入口地址,也记录该函数的类型,即函数的返回值类型和该函数的参数类型。
指针的使用一定要特别小心,千万不能越界,否则会出现意想不到的结果。而且一定不要使用未初始化过的指针,这样将会访问到位置的内存,有可能会给操作系统带来毁灭性的灾难。
四、C 中的 NULL 指针
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。NULL 指针是一个定义在标准库中的值为零的常量
#include <stdio.h>
int main ()
{
int *ptr = NULL;
printf("ptr 的地址是 %p\n", ptr );
return 0;
} //编译结果就是 ptr 的地址是 0x0
五、指针变量做函数参数
1、交换变量的值
swap函数是用来交换a,b两个变量的值。先给a,b分别赋值 1,5。然后把a,b的地址分别赋给指针变量pointer_1,pointer_2。然后执行swap函数,函数调用时依然是用值传递的方式把 pointer_1,pointer_2的值分别赋给p1,p2。 因此现在 p1的值是&a,p2的值是&b,接着 执行 *p1和*p2的值互换,也就是使 a 和 b 的值互换。之后在main 函数中输出结果就是 a=5,b=1。程序如下。
int main()
{
void swap(int *p1, int *p2);
int a=1,b=5;
int *pointer_1,*pointer_2;
p1 = &a;
p2 = &b;
swap(pointer_1 , pointer_2);
printf("a = %d \n b = %d",a,b);
}
void swap(int *p1,int *p2) //用来交换a,b变量的值
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
} //程序输出结果 a = 5 b = 1
2、需要注意的地方
不能企图通过改变指针形参而改变指针实参的值
举个例子,我们改变上面代码中的交换函数,这种不能交换变量的值,代码如下
void swap(int *p1,int *p2)
{
int *p;
p = p1;
p1 = p2;
p2 = p;
} //企图交换形参的值来交换实参pointer_1,pointer_2的值
//这只是改变了形参的值,对实参没有影响
还有一种错误的写法,代码如下
void swap(int *p1,int *p2)
{
int *temp;
*temp = *p1;
*p1 = *p2;
*p2 = *temp;
} //这里在函数内定义了指针变量 temp; 把*p1也就是变量a的值赋给 temp所指向的单元
//但由于没给temp赋初值,temp指向的单元也就是不可见的,对*temp赋值就就是向一个位置单元赋值
//而这个位置单元可能储存着一个有用的数据,这样就可能破坏系统的正常工作
六、指针引用数组
1、数组元素的指针
数组名 代表 数组首元素的地址
int a[3],*p;
p = a; //这就是把数组a的第一个元素地址赋给了指针变量p
等价于: p = &a[0];
2、通过指针引用数组
int a[n];
int p = a;
*(a+1) 或者 *(p+1) 或者 a[i]
//这三个是等价的,a是数组名,p是指向数组元素的指针变量
指针引用二维数组把这个 图看明白就可以了
七、指针引用字符串
1、字符串指针的使用
用指针变量指向一个字符串常量,通过字符指针变量引用字符串常量
# include <stdio.h>
int main()
{
char *string = "Hellow World";
printf("%s\n",string);
} //注意不是把 整个字符串存放到string 中而是把 "Hellow World"
// 的首字母地址赋给了指针变量string
// %s是字符串输出用的格式,在输出项中给出字符指针变量名string,系统会给出string所指向字符串的第一个字符,然后自动使string加1输出下一个字符,知道遇到'\0',在内存中字符串最后自动被加了'\0'
2、字符串指针变量和字符数组的比较
1.字符数组每个元素中放一个字符,字符指针变量中存放的是第一个字符的地址
2.可以对字符指针变量赋值,但不能对数组名赋值
如下是正确的, char *p; p = "Hellow World"; //√
如下是错误的, char a[15]; a = "Hellow World"; //×
对于初始化可以 char *p="abcde"; 或 char a[15]="abcde";
3.如果定义了字符指针变量应及时给他赋一个地址值,它未指向具体的一个对象,如果对该指针指向的对象输入数据,可能会出现严重的后果。
八、函数指针
1、函数指针的调用
函数指针是指向函数的指针变量,也可以调用函数,传递参数
(函数名就是函数的指针,即函数的起始地址,用指针变量来存放这一地址)
灵活性: 用函数名调用函数,只能调用所指定的一个函数,而通过指针变量调用函数比较灵活,可以根据情况先后调用不同函数
//格式
int (*p)(int , int );
//举个例子用指针变量调用它指向的函数
#include <stdio.h>
int max(int a,int b) //定义max 函数
{
int z;
if(a >= b) z=a;
else z=b;
return z;
}
int main()
{
int x,y,z;
int (*p)(int,int); //定义函数指针变量
p = max; //使指针指向max函数
scanf("%d %d",&x,&y);
z = p(x,y); //用指针调用函数
printf("max: %d\n",z);
return 0;
}
2、函数指针做参数使用(回调函数)
可以用函数指针把函数的入口地址作为参数传递到其他函数
void fun (int(*x1)(int) , int(*x2)(int , int)) //定义fun函数,形参是函数指针
{
int a,b,i=1,j=2;
a = (*x1)(i);
b = (*x2)(i,j);
}
如只用到两个函数没必要这样,直接吧这两个函数当做形参就可以了,但是当每次要调用的函数不是固定的,而且有多个函数,用这种方法就更方便了。举个例子,如下
#include <stdio.h>
int main()
{
int fun(int x,int y,int(*p)(int , int ));
int max(int ,int );
int min(int ,int );
int add(int ,int );
int a=1,b=2,n;
printf("输入要用的函数(1,2,3):");
scanf("%d",&n);
if(n==1) printf("%d",max(a,b));
else if(n==2) printf("%d",min(a,b));
else if(n==3) printf("%d",add(a,b));
return 0;
}
int max(int a,int b)
{
return (a >= b ? a: b); //返回较大的数
}
int min(int a,int b)
{
return (a >= b ? b: a); //返回较小数
}
int add(int a,int b)
{
return (a + b); //返回两数之和
}
九、返回指针值的函数
函数可以返回整型、字符型、实型值等。也可以返回指针型数据,即地址。概念与之前函数类似,只是返回值变成了指针(地址)。
//格式
类型名 *函数名(参数);
int *p (int);
举个例子,用返回指针值的函数来输出一个数组
#include <stdio.h>
int main()
{
int *p ( int *p1,int n); //声明返回指针的函数
int a[5]={1,2,3,4,5}; //定义一个数组
int n;
printf("输入你想输出的数组中的哪一个:"); //1,2,3,4,5
scanf("%d",&n);
printf("a[%d] = %d \n",n,*(p(a,n-1))); //因为数组中的数从0开始,所以n-1
return 0;
}
int *p(int *p1,int n)
{
int *a;
a = p1 + n; //a的值就是&a[n]
return a;
}
十、指针数组
一个元素均为指针类型数据的数组为指针数组
//格式
类型名 *数组名[数组长度]; int *p[5];
//注意不要写成 int (*p)[5] 这是指向一位数组的指针变量
使用:指针数组一般用来处理指向若干个字符串,是字符串的处理更加灵活
用谭浩强书上的一个例子来说:图书馆有若干本书,想把书名放在一个数组中,然后要对这些书目进行排序和查询。按一般方法,字符串本身就是一个字符数组。因此要设计一个二维的字符数组才能存放多个字符串。但在定义二维数组时,需要指定列数,也就是说二维数组中每一行中包含的元素个数(即列数)相等。而实际上各字符串(书名)长度一般是不相等的。如按最长的字符串来定义列数,则会浪费许多内存单元,如果用指针来存放的话,就会根据字符串的长度自动分配所需的内存单元。 下面举个例子
//我们用指针数组在存放五个字符串,并输出他们
#include <stdio.h>
int main()
{
int i;
//把字符串的地址赋给指针数组
char *p[4]={"Follow me","Apple","Great Wall","Computer","Wow"};
for(i=0;i<5;i++)
printf("%s\n",p[i]);
return 0;
}
//char *p[4]={"Follow me","Apple","Great Wall","Computer","Wow"};
//这句话也可以改成
char *p[4];
p[0] = "Follow me";
p[1] = "Apple";
p[2] = "Great Wall";
......
十一、多重指针
格式: char **p; // 指向指针数据的指针
他的用法很简单,举个例子
#include <stdio.h>
int main()
{
int a[3]={1,2,3};
int *b[3] = {&a[0],&a[1],&a[2]};
int **p,i; //定义了指向指向指针数据的指针变量
p = b;
for(i = 0;i < 3;i++)
{
printf("%d ",**p++); // *P 即 b[0] 即&a[0]
} // **p 即 *b 即 a[0]
return 0; // 输出结果就是 1 2 3
}