指针
一、指针变量
1.概念
存储地址,占4字节的空间
类型:存放的地址指向的数据类型
vs
普通变量根据不同类型占内存空间不同:char(1字节)、short(2字节)、int(4字节)、long(4字节)、long long (8字节)、float(4字节)、double(8字节)
2.初始化和操作
int f = 123;
int *p = &f;
int f2 = 456;
p = &f2; 修改指针指向
*p = 789; 修改指针指向的数据
3.指向常量的指针和常量指针
3.1指向常量的指针
(1)指针可以修改为指向不同的常量
(2) 指针可以修改为指向不同的变量
(3) 可以通过解引用来读取指针指向的数据
(4)不可以通过解引用修改指针指向的数据;
int num = 520;
const int cnum = 880;
const int *pc = &cnum;
*pc = 1024; //error:不可以通过解引用修改指针指向的数据
pc = # //可以修改指向
3.2指向非常量的常量指针
(1)指针自身不可以被修改:p = &b;(对int *const p = &a;)
(2)指针指向的值可以被修改。
int num = 520;
const int cnum = 880;
int *const p = #
*p = 1024; ///指针指向的值可以被修改
p = &cnum; ///指针自身不可以被修改指向
3.3指向常量的常量指针
(1)指针自身不可以被修改。
(2)指针指向的值可以被修改。
int num = 520;
const int cnum = 880;
int *const p = &cnum;
指针指向的数据不能通过对指针解引用来修改,指针自身也不能被修改指向
二、数组指针和指针数组
int *p1[5]; //指针数组
int (*p2)[5]; //数组指针
首先,需要明确一个[优先级]顺序:()>[]>*,所以:
1.定义
数组指针(*p)[n]:根据优先级,先看括号内,则p是一个指针,这个指针指向一个[一维数组],数组长度为n,这是“数组的指针”,即数组指针;
数组指针:是指针,指向一个数组。数组在这里并没有名字,是个匿名数组。
指针数组*p[n]:根据优先级,先看[],则p是一个数组,再结合,这个数组的元素是指针类型,共n个元素,这是“指针的数组”,即指针数组。
指针数组:是一个数组,每个数组元素存放一个指针变量。
2.区别
对指针数组来说,首先它是一个数组,数组的元素都是指针,也就是说该数组存储的是指针,数组占多少个字节由数组本身决定;而对数组指针来说,首先它是一个指针,它指向一个数组,也就是说它是指向数组的指针,在 32 位系统下永远占 4 字节,至于它指向的数组占多少字节,这个不能够确定,要看具体情况。
3.数组指针(*p)[n]
#include "stdafx.h"
int main()
{
//一维数组
int a[5] = { 1, 2, 3, 4, 5 };
//步长为5的数组指针,即数组里有5个元素
int (*p)[5];
//把数组a的地址赋给p,则p为数组a的地址,则*p表示数组a本身
p = &a;
//%p输出地址, %d输出十进制
//\n回车
//在C中,在几乎所有使用数组的表达式中,数组名的值是个指针常量,也就是数组第一个元素的地址,它的类型取决于数组元素的类型。
printf("%p\n", a); //输出数组名,一般用数组的首元素地址来标识一个数组,则输出数组首元素地址
printf("%p\n", p); //根据上面,p为数组a的地址,输出数组a的地址
printf("%p\n", *p); //*p表示数组a本身,一般用数组的首元素地址来标识一个数组
printf("%p\n", &a[0]); //a[0]的地址
printf("%p\n", &a[1]); //a[1]的地址
printf("%p\n", p[0]); //数组首元素的地址
printf("%d\n", **p); //*p为数组a本身,即为数组a首元素地址,则*(*p)为值,当*p为数组首元素地址时,**p表示首元素的值1
printf("%d\n", *p[0]); //根据优先级,p[0] 表示首元素地址,则*p[0]表示首元素本身,即首元素的值1
printf("%d\n", *p[1]); //为一个绝对值很大的负数,不表示a[1]...表示什么我还不知道
//将二维数组赋给指针
int b[3][4];
int(*pp)[4]; //定义一个数组指针,指向含4个元素的一维数组
pp = b; //将该二维数组的首地址赋给pp,也就是b[0]或&b[0],二维数组中pp=b和pp=&b[0]是等价的
pp++; //pp=pp+1,该语句执行过后pp的指向从行b[0][]变为了行b[1][],pp=&b[1]
int k;
scanf_s("%d", &k);
return 0;
}
4.指针数组*p[n]
#include "stdafx.h"
int main()
{
int a = 1;
int b = 2;
int *p[2];
p[0] = &a;
p[1] = &b;
printf("%p\n", p[0]); //a的地址
printf("%p\n", &a); //a的地址
printf("%p\n", p[1]); //b的地址
printf("%p\n", &b); //b的地址
printf("%d\n", *p[0]); //p[0]表示a的地址,则*p[0]表示a的值
printf("%d\n", *p[1]); //p[1]表示b的地址,则*p[1]表示b的值
//将二维数组赋给指针数组
int *pp[3]; //一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2],所以要分别赋值
int c[3][4];
for (int i = 0; i<3; i++)
pp[i] = c[i];
int k;
scanf_s("%d", &k);
return 0;
}
5.初始化
(1)数组指针
数组名的值是个指针变量,也就是数组第一个元素的地址
error//
int (*p2)[5] = {1,2,3,4,5};
/*
p2是一个指针,所以要给它的是一个地址
*/
//==>
int temp[5] = {1,2,3,4,5};
int (*p2)[5] = temp;
/*
指针类型初始化不一致
在 C 语言中,赋值符号“=”号两边的数据类型必须是相同的,如果不同,则需要显示或隐式类型转换。
“=”号两边的数据类型就不一致了(左边的类型是指向整个数组的指针,而右边的数据类型是指向单个字符的指针),因此会提示错误信息。
*/
//==>
/right//
int *p = temp;
/*
此时这个指针(只是一个指向整型变量的指针,而不是指向数组的指针)指向数组的第一个元素,而不是数组。因为数组的所有元素都是一个挨着存放,所以知道第一个元素的地址,就可以遍历后面的所有元素。
*/
int (*p2)[5] = &temp;
/*
把数组temp的地址赋给p2,则p2为数组temp的地址,则*p2表示数组temp本身
*/
//===>真正的数组指针
(2)指针数组
int *p1[5] = {&a,&b,&c,&d,&e};
三、指针和二维数组
对二维数组:int array[4] [5]
array:整个二维数组的首地址。指向包含5个元素的数组的指针(地址)。
*(array+1):相当于array[1],而array[1]相当于array[1] [0]的数组名,即 *(array+1)是指向第二行子数组第一个元素的地址。
((array+1)+3):指向第二行子数组第四个元素的指针,在对进行取值
int (*p)[4];
// *(*(p+i)+j):指向第i+1行子数组第j+1个元素的指针的值
int array[3][4] = {
{0,1,2,3},
{4,5,6,7},
{8,9,10,11}
};
int i, j;
p = array;
for (i = 0;i < 3;i++){
for (j = 0;j < 4; j++){
printf("%2d ",*(*(p+i)+j));
}
printf("\n");
}
//通过数组指针的方式来访问二维数组
四、void指针
无类型指针,不用来定义一个变量
通用指针,可以指向任何类型的数据
不要对void指针直接进行解引用,因为编译器不知道它指向的什么数据类型,需要强制类型转换:
int num = 1024;
int *pi = #
char *ps = "Andyli";
void *pv;
pv = pi;
printf("pi:%p,pv:%p\n",pi,pv);
printf("*pv:%d\n",*pv); ///error
===>
printf("*pv:%p\n",*(int *)pv); 1024
五、NULL指针
#define NULL ((void *)0)
一个良好的编程习惯:当不清楚要将指针初始化为什么地址时,请将它初始化为NULL;在对指针进行解引用时,先检查该指针是否为NULL。这种习惯可以为以后编写大型程序节省大量的调试时间。
六、指针数组和指向指针的指针
char **p[4]; //p定义的是一个数组,里面存放的是指向字符指针的指针变量
//对于一个指针数组p2
char *p2[] = {
.....
};
/*对&p2[5]得到的是字符串的首地址,即在这里是指向字符指针的指针
则对 char **p3 可以将&p2[5]赋值给p3
即 p3 = &p2[5]
*/
// 此时*p3(对p3解引用),得到p2[5]的值
// 使用指向指针的指针来指向数组指针,避免重复分配内存