引子:数据在内存中是如何存储的,又是如何读取的?内存编号就是内存的地址(内存中每个字节都有一个编号,即地址)
1.概念:
地址:内部存储器的编号,称为地址。如变量int a的位置编号,变量char b的位置都是指针。
指针变量:专门存放地址的变量称为指针变量。
地址、指针、指针变量都称为指针。
一、变量的地址(指针)和指向变量的地址变量(指针)
概念:
变量的指针: 就是变量的地址。
指针变量: 是用来存放地址的变量,普通变量是用来存放数据的,指针变量是存放地址的。
2.定义地址变量:
1) 格式:[ 存储类型 ] 数据类型 *指针变量名;
int i, j;
int * pointer1, *pointer2;
存储类型是这个地址变量的存储位置
数据类型指的是这个地址变量指向的目标变量类型,不代表本身的类型大小。
2) 指针变量的赋值:
方法一:
int a = 5; int *p = &a; //定义并初始化。
方法二:
int a = 5; int *p; p = &a;// 先定义后赋值。
PS: 定义时,int *p中*是为了说明该p是地址变量,用来存放地址; 定义地址变量时必须指定数据类型,不同类型指针不可相互赋值; 指针变量的数据类型不表示变量的类型,是表示该变量指向的目标的数据类型,访问内存时读取的内存空间大小。 |
3.指针变量引用
*和&符号
* 定义指针变量/取地址对应的变量的内容(间接访问);//i = 3直接,*p=3间接
& 取变量的地址。int a; &a, //&(&a);&a是地址,不可以对地址使用&*和&互为逆运算。自右向左
3) 引用
? 对指针变量赋值
p = &a;
? 引用地址变量指向的内容
printf( “a=%d\n”, *p );
? 引用变量本身的内容(即存储的地址)
printf(“%x\n”, p);
eg: int i = 188; int *p = &i; p 指针变量,内部存放的是目标的地址; *p 目标,目标内存数据; &p 指针变量的内存地址; p = &i = &(*p) i = *p = *(&i) |
4.指针运算
指针运算就是地址运算,即指针变量中的地址作为运算量。
地址只能做算术、关系、赋值运算。
算术运算
px + n 代表指针向地址大的方向移动 n 个 数据。
移动后的地址量是: (px) + sizeof(px的类型) * n
px++ 指针变量向地址大的方向移动一个数据。
px - py 表示两个相同类型指针间相差数据的个数,而不是一个地址量。
px - py的结果是 (px - py) /sizeof(数据类型)
px + py 的结果? 没有任何意义
指针加发运算加的数值是增加地址本身类型的N倍大小(这在数组中访问经常用到)
4) 关系运算
指针关系表示两个指针在内存位置的高低关系。
不同数据区域间的指针,关系运算没有意义。
指针和除0外的整数比较没有意义,和0比较可以判定指针是否为空。(标准写法为if (NULL == p) ).
5) 赋值运算
向指针变量传递一个地址值。这个值是地址常量或指针变量(同类型),不能是普通整数(0可以表示空值)。
6) const/void指针
const 表示的使变量常量化,即不可修改。
int const a = 9;
a = 10; //报错,a为const修饰不可改变。
const 在遇到指针时会发生一些变化
const int a 与 int const a, const可以在int的左右位置。
int a = 9; int b = 12; const int *p = &a; // const 修饰的是*p , pa 指向变量a, int const *p = &a; //和上面效果相同, 都表示地址变量pa指向a,且*pa不可变 *p = 10 ; // 通过p改变a的值,但*p是const类型,不可改变。 p = &b; //可以改变p的值,(即指向)。 *p = 11;// 同样不可以 int *const q = &a; //const 修饰的是 q, 所以q是不能改变的,即不能改变q的指向 *q = 111; q = &b; // 将q指向b,报错。 |
void 型指针
指针变量指向不确定数据类型的变量的时候,可以定义为void型指针,
因为void类型指针可以赋值给其他任意类型的指针,而其他类型不能相互赋值.
如:malloc函数
void * malloc(size_t size);
malloc 函数因为不知道分配空间的具体用途,所以返回void型地址。
7) 多级指针
指向地址变量的地址变量,称为多级指针(画图表示);
定义一个二级指针
int *p = &a;
int **q = &p;
8) 小结:指针自增与自减
ü p++(或 p+=1): 使p指向下一个元素
ü *p++: ++与* 具有相同优先级且结合方向自右向左, 等价于*(p++), 先取*p的值,然后p再自加,指向下一个元素。
ü *(p++) 与 *(++p) 作用不同。 前者是先取*p的值,再使p自加。后者先使p自加,再取自加后指向的内容。
ü ++(*p): 表示将p指向的元素的值加1.
二、指针与数组
指针与一维数组
数组的指针是指数组在内存中的起始地址,即第一个数组元素的地址.
一维数组的数组名代表一维数组的指针(起始地址)
[ ] 又叫做变址运算符
a[i] <=> *(a+i) 在计算机内部实现的时候,数组下标都会转化为地址。
若地址变量px的地址值等于数组指针x(指针变量px指向数组的首地址),则:
x[i]、*(px+i)、 *(x+i) 和px[i]具有相同功能的功能:访问数组第i+1个数组元素。
数组元素访问过程中,数组地址与指针变量具有相同的访问效果
不同: 地址变量是变量。
数组地址(数组名)是常量,不能自加或自减
地址变量与数组的赋值
1. int *p = &a[0];
2. int *p;
p = &a[0];
3. int *p = a;
小结:
1. p+i和a + i就是a[i]的地址, 指向a数组的第i个元素。
2. *(p+i) 或*(a+i) 是取a[i]元素的值。
3. 指向数组的地址变量也可以带下标 p[i]和*(p+i)和*(a+i) 等效。
9) 指针与数组常见操作
数组 | 指针表示 | 含义 |
array | &array[0] | 数组名是第一个元素的地址 |
*array | array[0] | 数组的第一个元素 |
array + i | &array[i] | 数组第i个元素的地址 |
*(array + i ) | *(&array[i]) == array[i] | 数组第i个元素 |
*array + m | array[0] + m | 数组第一个元素加m |
*array++ | error | error |
经典例子:
一维字符 指针数组ps[5] 里面存放着字符串首地址
char *ps[5] = {“beijing city”, “New York”, “London”, “Paris city”, “Moscow city”};
定义一个指针变量,并指向数组首地址;
char **pps = ps; 那么ps指向 指针数组的首地址
5.指针与二维数组
定义一个二维数组a,有3行4列
int a[3][4] = { {1, 3, 5, 7}, {9, 11, 13, 15}, {17, 19, 21, 23} };
a 是数组名, a数组包含3行,即3个元素,分别是a[0]、a[1]、a[2]。每个元素同样是一个一维数组,包含4个元素,a[0][0], a[0][1], a[0][2], a[0][3].
a[0] 是一维数组名,代表的是第一行的第一个元素的首地址,a[0] = &a[0][0].
a[1] 是一维数组名, 代表的是第二行的第一个元素的首地址, a[1] = &a[1][0].
a[0][0] 是一个元素 &a[0][0] ==> a[0] 取一维数组的首地址,即一维数组名a[0], (a[0] 为第一行首地址) &a[0] ==> a 取a[0], a[1], a[2[ 三个元素中的首地址,即数组名a. &a == &a 取数组a的地址,即取数组的位置。 a 指向? (指向第0行首地址) *(a+0) == a[0]; a+1 指向? (指向第一行的首地址) *(a+1) == a[1]; *(a+0) + 1 是a[0][1]的地址, *(*(a+0) +1) == a[0][1] *(a+1) + 1 是a[1][1]的地址, *(*(a+1) + 1) == a[1][1] == *(a[1] + 1) |
PS: 对数组名取值就得到数组元素; 对数组元素取址就得到当前数组地址; a[0][0] =(&a[0][0]) => a[0], 对第0行首元素取地址得到地址; a[0] =(&a[0]) => a, 对3个元素的第0个元素取地址得到数组名。 a =(&a) => &a, 对数组名取地址,得到数组地址。 思考: &a +1 指向? (指向下一个数组,增加一整个数组); a+1 指向? (指向下一行首地址, *(a+1) == a[1] ); *(a+1) + 1 指向? (指向a[1][1], *(*(a+1) + 1) == a[1][1] ); |
注意点:
a + 1 表示的是第一行首地址, a表示是行数组的地址变化。
a[1] 表示的是第一行0列的地址 &a[1][0], a[1] = * (a + 1); a表示的是当前行的首地址变化
二维数组的行地址、列地址
行地址:二维数组名是个特殊的地址,参与运算时以行为单位移动,因此被称为是行地址。如int a[2][3], a代表的是第一行的首地址,a + 1代表第二行的首地址。
列数组:int *p = a;
printf(“%d”, *(p+i) );p+i 相当于移动了i列, 因此指针p为列指针。
综上:
a+1与a[1]表示的地址值是相同的, 但含义是不同的, a+1 是序号为第一行的首地址, a[1] 或 *(a+1), &a[1][0] 指向1行0列元素。
二维数组名是指向行的,在行指针前面加上*, 就可以转换为指向列的指针。
eg:
a和a+1是指向行的地址;
*a和*(a+1)是指向列的指针。
反之,在列指针前面加上&,就可以转换为行指针。
eg:
a[0] 指向0行0列的列元素指针, &a[0] 与*(a + 0)
int main() { int a []={5,8,7,6,2,7,3}; int y,*p=&a[1]; y=(*--p)++; printf(“%d ”,y); // 5 printf(“%d”,a[0]); //6 } |
10) 指向元素的指针变量和指向数组的指针变量(数组指针)比较
ü 指向数组元素的指针变量定义和普通指针变量相同