一个变量的(内存)地址称为该变量的“指针”,通过指针能找到以它为地址的内存单元。指针概念是构成C/C++的重要元素之一,是变量的一种类型,存放的是指定类型数据的地址,而同类型变量存放的是具体的数据。指针变量是用来存放另一个变量的地址的变量。指针是概念,指针变量是具体实现。
每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)
运算符访问的地址,它表示了在内存中的一个地址。
- 地址运算符
&
用于获取一个变量的地址。 - 间接运算符
*
用于从指针中获取指针地址存储的值。
要认识指针需要搞清楚指针的四方面类容
- 指针的类型:从语法的角度看,只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。
int *ptr;//指针的类型是int*
char *ptr;//指针的类型是char*
int **ptr;//指针的类型是int**
int (*ptr)[3];//指针的类型是int(*)[3]
int *(*ptr)[4];//指针的类型是int*(*)[4]
- 指针所指向的类型:当通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。从语法上看,只须把指针声明语句中的指针名字和名字左边的指针声明符
*
去掉,剩下的就是指针所指向的类型。
int *ptr; //指针所指向的类型是int
char *ptr; //指针所指向的的类型是char
int **ptr; //指针所指向的的类型是int*
int (*ptr)[3]; //指针所指向的的类型是int()[3]
int *(*ptr)[4]; //指针所指向的的类型是int*()[4]
- 指针的值:指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。
- 指针本身所占据的内存区:指针本身占了多大的内存,32平台指针的长度是4个字节,64位平台指针的长度是8个字节。
注意:
- 指针的类型与指针所指向的类型不是同一个概念。很多情况下却容易忽视它们的区别。指针的类型是指针自身的类型(指针类型可以被强制转换),而指针所指向的类型是指针指向的数据(内存)的类型。
- 指针所指向的内存区与指针所指向的类型是两个完全不同的概念。不要混淆
认识指针示例:
两个运算符:“*”(间接引用)、“[]”(下标),“[]”的优先级别大于“*”的优先级别。
int (*a)[4]
它是一个数组指针,只是这个指针类型不是int,而是int[4]类型的数组
解析:它首先是个指针,即*q,剩下的“int [4]”作为补充说明,即说明指针q指向一个长度为4的数组。
int *a[4]
它是指针数组,表示一个一维指针数组,数组里面的元素都是指针类型。
解析:int *p[4],“[]”的优先级别高,所以它首先是个大小为4的数组,即p[4];剩下的“int *”作为补充说明,即说明该数组的每一个元素为指向一个整型类型的指针。
遇到指针从哪些方面入手
- 这个指针的类型是什么?
- 指针指的类型是什么?
- 该指针指向了哪里?
一个表达式的结果如果是一个指针,那么这个表达式就叫指针表式。
指针可以加上或者减去一个整数,指针的这种运算的意义和通常的数值的加减运算的意义是不一样的,指针的运算单元与它指向的类型有关。
一个指针加(减)一个整数n 后,结果是一个新的指针,运算后指针的类型相同,新的指针所指向的类型和之前的指针所指向的类型也相同。新的指针的值将比旧的值增加(减少)了 n 乘 sizeof
(旧指针所指向的类型)个字节。
指向常量的指针,常量指针有如下特点:
- 常量指针指向的对象不能通过这个指针来修改。
- 指针还可以指向别处,因为指针本身只是个变量,可以指向任意地址,所以叫常量指针,是限制了通过这个指针修改变量的值。
声明常量指针
int const* p;
const int* p;
指针常量是一个指针,还有常量的特性。指针常量的值是指针,这个值因为是常量,所以不能被赋值。 指针本身是常量,指向的地址不可以变化,但是指向的地址所对应的内容可以变化;
声明指针常量
int* const p;
指向常量的指针常量就是一个常量,且它指向的对象也是一个常量。
声明指向常量的常指针
const int* const p;
示例:
int a = 4;
const int b = 5;
//常量指针:指向常量的指针,不能通过*cpb1给b再次赋值
int const *cpb1 = &b;
const int *cpb2 = &b;
//指针常量,pc不能再指向其他值
int* const pc = &a;
//指向常量的常指针:
const int* const cpca = &a;
const int* const cpcab = &b;
数组名不代表整个数组,只代表数组元素的首地址,数组的数组名可以看作一个指针。
- 指针数组
- 数组指针
- 指针数组的加减运算,注意:如果数组是常量,不允许对该数组指针进行加减运算
- 通过指针引用多维数组
//示例1
int array[10]={0,1,2,3,4,5,6,7,8,9},value;
value=array[0]; //也可写成:value=*array;
value=array[3]; //也可写成:value=*(array+3);
value=array[4]; //也可写成:value=*(array+4);
//示例2
char *arr[20];//arr指向的是一个字符指针
char **parr = arr;
//示例3
int array[10];
int (*ptr)[10];//ptr是一个指针,指向的是一个长度为10的整型数组
ptr=&array;:
函数指针:如果在程序中定义一个函数,在编译时编译系统为函数代码分配一段存储空间,这段存空间的起始地址(入口地址)称为函数的指针。
定义函数指针:类型名(* 指针变量名)(函数参数列表)
,例如:int (*p)(int,int);
指针函数相关内容:
- 指向函数的指针
- 函数指针作为函数参数
- 返回指针的函数
主要在于如何认识指针类型。
可以声明一个指向结构类型对象的指针。对于结构体指针,可以通过->
符号直接引用结构体的成员变量
struct MyStruct{
int a;
int b;
int c;
};
struct MyStruct ss={20,30,40};
struct MyStruct *ptr=&ss;
int value = ptr->c;
指针可以进行强制类型转换
强制类型转换后,错误赋值:
char s='a';
int *ptr;
ptr=(int *)&s;
*ptr=1298;//本来我们只有s一个字节的内存,现在把s后面的三个字节也修改了
永远不要这样做,可能会造成意想不到的结果:
//定义一个指针而没有赋值,这时这个指针会指向一个未知的地址
int *tem;
//而如果此时给 *tem赋值,就会改变那个未知地址的值
*tem = *a;
这里会导致程序异常。因为地址10不存在:
int * a = 10;
printf("%d", *a);
分配空间:下面代码是非常危险的,因为name是未知的内存地址,然后用户输入后导致未知 的值被覆盖,正确的做法是需要为name声明长度来分配空间。
char *name;
scanf("%s",name);