一.指针基础
在说指针之前,我们先来说说地址。平时生活化的地址比如邮箱地址,ip地址,家庭住址,这些都是地址。
在计算机中,所有的数据都是存放在存储器中的。一般把存储器中的一个字节称为一个内存单元。 为了正确地访问这些内存单元,必须为每个内存单元编上号。内存单元的编号也叫做地址。
既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。也就是说指针就是地址。
内存单元的指针和内存单元的内容是两个不同的概念。比如:教室中有学生,教室是指针,教师的内容是学生;0x100 是指针,这个地址所指向的空间的内容’A’。指针是常量。地址是个常量。
#include <stdio.h>
int main(int argc, const char * argv[]) {
int a = 10;
int *p = &a;
printf("a的地址 = %p, a的内容 = %d\n", &a, a); //a的地址 = 0x7ffeefbff4bc, a的内容 = 10
printf("p = %p, *p = %d\n", p, *p); // p = 0x7ffeefbff4bc, *p = 10
return 0;
}
二.指针变量
1、指针变量的概念:
变量的指针就是变量的地址,存放变量地址的变量是指针变量。
2、定义一个指针变量:
一般形式:数据类型说明符 *指针变量名;(int *p;)
int main(void)
{
//数据类型说明符 * 变量名;
int *p;
//p ----> int 声明了一个指针变量p,p用来指向Int类型的变量。
float *p1;
//p1--------->float 声明了一个指针变量p1, p1用来指向float 类型的变量。
double *p2;
//p2--------->double 声明了一个指针变量p2, p2用来指向double 类型的变量。
char *p3;
//p3--------->char 声明了一个指针变量p3, p3用来指向char 类型的变量。
long *p4;
//p4--------->long 声明了一个指针变量p4, p4用来指向long 类型的变量。
return 0;
}
[注]:
1、"*"是一个说明符,用来说明这个变量是个指针变量,是不能省略的,但它不属于变量名的一部分
2、前面的类型标识符表示指针变量所指向的变量的类型,而且只能指向这种类型的变量
3、指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值。未经赋值的指针变量不能使用,否则将造成系统混乱,甚至死机。指针变量的赋值只能赋予地址,决不能赋予任何其它数据,否则将引起错误。
3、指针变量的初始化
1.先定义后初始化
// 先定义int类型的变量a
2 int a = 10;
3
4 // 定义一个指针变量p
5 int *p;
6
7 // 将变量a的地址赋值给指针变量p,所以指针变量p指向变量a
8 p = &a;
2.在定义的同时初始化
// 先定义int类型的变量a
int a = 10;
// 定义一个指针变量p
// 并将变量a的地址赋值给指针变量p,所以指针变量p指向变量a
int *p = &a;
复制代码
[注意]: 指针变量使用前,一定要先赋值,未被赋值的指针,是野指针。野指针可能指向随机的位置。
4、如何避免野指针的出现?
int *p = NULL;
int a;
p = &a;
if (p != NULL)
{
*p = 100;
printf("*p = %d\n", *p);//*p = 100
}
/**
【NULL指针】:空指针,大多数编译器NULL == 0。
NULL指针所指向的区域,既不能写也不能读。
NULL指针的使用场合:
1)可以赋值给任意指针,比如指针初始化,赋值给释放内存后的指针。
2)可以和任意类型的指针进行比较,判断是否是有效的指针。
if (指针 != NULL), if (指针 == NULL)
3)可以作为返回指针函数的返回值。
**/
三、指针运算符
一般形式:* 运算符 一般形式(*p)
作用:访问指针变量所指向的存储区域。另外,指针变量和一般变量一样,存放在它们之中的值是可以改变的,也就是说可以改变它们的指向。简单来说就是取值和修改值。
int main(int argc, const char * argv[]) {
char a = 10;
// 指针变量p指向变量a
char *p = &a;
//访问指针变量所指向的存储区域
printf("*p = %d\n", *p);//*p = 10
// 通过指针变量p间接修改变量a的值
*p = 9;
printf("修改后,a的值:%d\n", a);//修改后,a的值:9
return 0;
}
四.指针变量作为函数参数
先来看一段代码
void swap(int x, int y);
void swap1(int *x, int *y);
#include <stdio.h>
int main(int argc, const char * argv[]) {
int a = 10;
int b = 18;
swap(a, b);
printf("a = %d, b = %d\n", a, b);//a = 10, b = 18
swap1(&a, &b);//&a--->x, &b--->y
printf("a = %d, b = %d\n", a, b);//a = 18, b = 10
return 0;
}
void swap(int x, int y)
{
printf("1,x = %d, y = %d\n", x, y);//x = 10, y = 18
int temp = x;
x = y;
y = temp;
printf("2.x = %d, y = %d\n", x, y);//x = 18, y = 10
}
void swap1(int *x, int *y)
{
printf("1.*x = %d, *y = %d\n", *x, *y);//*x = 10, *y = 18
int temp = *x;
*x = *y;
*y = temp;
printf("2.*x = %d, *y = %d\n", *x, *y);//*x = 18, *y = 10
}
从输出结果可以看到,虽然x和y的值被交换了,但是变量a和b的值根本就没有换过来。因为基本数据类型作为函数实参时,只是纯粹地将值传递给形参,形参的改变并不影响实参。如果需要形参改变实参,那么需要进行指针传递。
五、关于指针的疑问
刚学完指针,都可能有一大堆的疑惑,这里我列出几个常见的疑惑吧。
1.一个指针变量占用多少个字节的内存空间?占用的空间是否会跟随所指向变量的类型而改变?
在同一环境下,一个指针变量所占用的内存空间是固定的。比如,在16位环境下,任何一个指针变量都只占用2个字节,并不会随所指向变量的类型而改变。
2.既然每个指针变量所占用的内存空间是一样的,而且存储的都是地址,为何指针变量还要分类型?而且只能指向一种类型的变量?比如指向int类型的指针、指向char类型的指针。
其实,我觉得这个问题跟"数组为什么要分类型"是一样的。
- 看下面的代码,利用指针p读取变量c的值
1 int i = 2;
2 char c = 1;
3
4 // 定义一个指向char类型的指针
5 char *p = &c;
6
7 // 取出
8 printf("%d", *p);
这个输出结果应该难不倒大家:,是可以成功读取的。
- 如果我改一下第5行的代码,用一个本应该指向int类型变量的指针p,指向char类型的变量c
int *p = &c;
我们再来看一下输出:,c的原值是1,现在取出来却是513,怎么回事呢?这个要根据内存来分析
根据变量的定义顺序,这些变量在内存中大致如下图排布:
其中,指针变量p和int类型变量i各占2个字节,char类型的c占一个字节,p指向c,因此p值就是c的地址
1> 最初的时候,我们用char p指向变量c。当利用p来获取变量c的值时,由于指针p知道变量c是char类型的,所以会从ffc3这个地址开始读取1个字节的数据:0000 0001,转为10进制就是1
2> 后来,我们用int p指向变量c。当利用p获取变量c的值时,由于指针p认为变量c是int类型的,所以会从ffc3这个地址开始读取2个字节的数据:0000 0010 0000 0001,转为10进制就是513
可见,给指针分类是多么重要的一件事,而且一种指针最好只指向一种类型的变量,那是最安全的。