4 指针
约 5531 字大约 18 分钟
2025-04-05
系统给虚拟内存的每个存储单元分配了一个编号,从0x00 00 00 00 ~0xff ff ff ff这个编号咱们称之为地址 指针就是地址
指针变量:是个变量,是个指针变量,即这个变量用来存放一个地址编号在 32 位平台下,地址总线是 32 位的,所以地址是 32 位编号,所以指针变量是 32 位的即 4 个字节。
注意:1: 无论什么类型的地址,都是存储单元的编号,在 32 位平台下都是 4 个字节,即任何类型的指针变量都是 4 个字节大小 对应类型的指针变量,只能存放对应类型的变量的地址举例:整型的指针变量,只能存放整型变量的地址
扩展: 字符变量 char ch='b'; ch 占1个字节,它有一个地址编号,这个地址编号就是 ch 的地址整型变量 inta=0x12 34 56 78; a 占4 个字节,它占有4 个字节的存储单元,有4个地址编号。
定义
1.简单的指针变量
数据类型 * 指针变量名;
int * p;//定义了一个指针变量p
在定义指针变量的时候 * 是用来修饰变量的,说明变量 p 是个指针变量,变量名是 p
2.关于指针的运算符
& 取地址 ∗取值
int a=0x1234abcd;
int *p;//在定义指针变量的时候*代表修饰的意思,修饰p是个指针变量
p=&a;//把 a 的地址给 p 赋值,&是取地址符
p 保存了 a的地址,也可以说p 指向了 a
p和a 的关系分析: a 的值是 0x1234abcd,假如 a的地址是: 0xbfe8 98 68
分析
1、在调用的时候 ∗代表取值的意思,∗p 就相当于p 指向的变量,即a, 2、故num-*p和num=a 的效果是一样的。 3、所以说num 的值为0x1234abcd。
扩展
如果在一行中定义多个指针变量,每个指针变量前面都需要加∗来修饰
int*p,*q;//定义了两个整型的指针变量p和q
int*p,q;//定义了一个整型指针变量p和整型的变量 q
int main()
{
int a= 100, b = 200;
int*p_1,*p_2 = &b; //表示该变量的类型是一个指针变量,指针变量名是 p_1而不是*p_1
//p_1 在定义的时候没有赋初值,P_2 赋了初值
p_1= &a; //p_1先定义后赋值
printf("%d\n", a);
printf("%d\n", *p_1);
return 0;
}
在定义p_1的时候,因为是个局部变量,局部变量没有赋初值,它的值是随机的,P_1指向哪里不一定所以p_1 就是个野指针。
分类
按照指针指向的数据的类型来分
- 字符指针
字符型数据的地址
char *p;//定义了一个字符指针变量,只能存放字符型数据的地址编号
char ch;
p=&ch;
- 短整型指针
short int *p;//定义了一个短整型指针p,只能存放短整型变量的地址
short int a;
p=&a;
- 整形指针
- 长整型指针
- float型指针
- double型指针
- 函数指针
- 结构体指针
- 指针的指针
- 数组的指针
- 通用指针
void *p;
无论什么类型的指针变量,在 32 位系统下,都是 4 个字节 指针只能存放对应类型的变量的地址编号。
指针与类型
指针与变量
指针可以存放变量的地址编号
int a=100;
int *p;
p=&a;
在程序中,引用变量的方法
1、直接通过变量的名称
int a;
a=100;
2、可以通过指针变量来引用变量
imt *p://在定义的时候,*不是取值的意思,而是修饰的意思,修饰 p 是个指针变量
p-&a;//取 a的地址给p 赋值,p 保存了 a 的地址,也可以说 p 指向了 a
*p= 100;//在调用的时候*是取值的意思,*指针变量 等价于指针指向的变量
指针变量在定义的时候可以初始化 对应类型的指针,只能保存对应类型数据的地址,如果想让不同类型的指针相互赋值的时候,需要强制类型转换
指针与数组
变量存放在内存中,有地址编号,定义的数组,是多个相同类型的变量的集合,每个变量都占内存空间,都有地址编号指针变量当然可以存放数组元素的地址。
int a[5];
//int *p =&a[0];
int *p;
p=&a[0];//指针变量p保存了数组a中第 0个元素的地址,即 ar0的地址
数组元素的引用方法2
方法1: 数组名[下标]
int a[5]:
a[2]=100;
方法2:指针名加下标
int a[5];
int *p;
p=a;
p[2]=100://相当于 a[2]=100;
补充: c 语言规定: 数组的名字就是数组的首地址,即第 0 个元素的地址,就是&a[0],是个常量。
方法3: 通过指针变量运算加取值的方法来引用数组的元素
int a[5];
int *p;
p=a;
*(p+2)=100://也是可以的,相当于 a[2]=100
解释:p是第 0个元素的地址,p+2 是 a[2]这个元素的地址 对第二个元素的地址取值,即 a[2]
方法 4:通过数组名+取值的方法引用数组的元素
int a [5]:
*(a+2)=100://也是可以的,相当于 a[2]=100;
注意: a+2 是 a[2]的地址。这个地方并没有给 a 赋值
指针的运算
1、指针可以加一个整数,往下指几个它指向的变量,结果还是个地址前提: 指针指向数组的时候,加一个整数才有意义
int a[10];
int *p;
p=a;
p+2;//p是 a[0]的地址,p+2 是&a[2]
假如p 保存的地址编号是 2000 的话,p+2 代表的地址编号是 2008
char buf[5];
char *q;
q=buf;
g+2//相当于&buf[2]
假如:q 中存放的地址编号是 2000 的话,g+2代表的地址编号是 2002
2、两个相同类型指针可以比较大小
前提: 只有两个相同类型的指针指向同一个数组的元素的时候,比较大小才有意义指向前面元素的指针小于指向后面元素的指针
#include <stdio.h>
int main(int argc, char *argv[])
{
int a[10];
int *p,*q,n;//如果在一行上定义多个指针变量的,每个变量名前面加//上边一行定义了两个指针 p和g,定义了一个整型的变量n
p=&a[1J;
q=&a[6];
if(p<q){
printf("p<q\n");
}
return 0;
}
3、两个相同类型的指针可以做减法
前提: 必须是两个相同类型的指针指向同一个数组的元素的时候,做减法才有意义做减法的结果是,两个指针指向的中间有多少个元素
#include <stdio.h>
int main(int argc, char *argv[])
{
int a[10];
int *p,*q;
p=&a[0];
9=&a[3];
printf("%d\n",q-p);
return 0;
}
//结果3
4、两个相同类型的指针可以相互赋值
注意:只有相同类型的指针才可以相互赋值 (void *类型的除外)
int *p;
int *q;
int a;
p=&a://p 保存a的地址,p 指向了变量 a
q=p;//用p给q 赋值,q 也保存了a的地址,指向a
注意: 如果类型不相同的指针要想相互赋值,必须进行强制类型转换 注意:c 语言规定数组的名字,就是数组的首地址,就是数组第 0 个元素的地址
int *p;
int a[10];
p=a; p=&a[0];这两种赋值方法是等价的
const修饰指针
const char * str
str 指向的内存的内容不能修改 但是 str 的指向是可以改变的
char * const str
str 是只读的变量,str 不能指向别的地方 但是 str 指向的内存的内容,是有可能可以修改的
const char * const str
str不能指向其他地方,指向的内存内容也不能通过str修改
指针和数组
指针数组
1、指针和数组的关系
指针可以保存数组元素的地址 可以定义一个数组,数组中有若干个相同类型指针变量,这个数组被称为指针数组
指针数组本身是个数组,是个指针数组,是若干个相同类型的指针变量构成的集合
2、指针数组的定义方法:
类型说明符 * 数组名 [元素个数];
imt * p[5];//定义了一个整型的指针数组p,有5个元素 p[o]~p[5].每个元素都是 int*类型的变量
int a;
p[0]=&a;
int b[10];
p[1]=&b[5];
p[2]、* (p+2)是等价的,都是指针数组中的第2个元素。
数组指针
定义方法:
指向的数组的类型 (*指针变量名)[指向的数组的元素个数]
int(*p)[5];//定义了一个数组指针变量 p,p指向的是整型的有 5 个元素的数组p+1 往下指 5 个整型,跳过一个有 5 个整型元素的数组
#include<stdio.h>
int main(){
int a[3][5];//定义了一个 3 行 5 列的一个二维数组
int(*p)[5];//定义一个数组指针变量 p,p+1跳一个有 5 个元素的整型数组printf("a=%p\n",a);//第 0 行的行地址
printf("a+1=%p\n"a+1);//第 1行的行地址,a和a +1差 20 个字节
p=a;
printf("p=%p\n",p);
printf("p+1=%p\n",p+1);//p+1跳一个有 5 个整型元素的一维数组
return 0;
}
各种数组指针的定义:
1、一维数组指针,加 1后指向下个一维数组
int(*p)[5]; // int *p[5];
配合每行有 5个 int 型元素的二维数组来用
2、二维数组指针,加 1后指向下个二维数组
int(*p)[4][5];
配合三维数组来用,三维数组中由若干个 4 行 5 列二维数组构成
int a[3][4][5];
3、三维数组指针,加 1后指向下个三维数组
int(*p)[4][5][6];// p+1 跳一个三维数组:
由4个5行6列的二维数组构成的三维数组配合使用
int a[7][4][5][6];
四维数组指针,加1后指向下个四维数组,以此类推。。。。
注意
指针数组
是个数组,有若干个相同类型的指针构成的集合
int * p[10]; 数组 p有 10 个 int * 类型的指针变量构成,分别是 p[0] ~p[9]
数组指针
本身是个指针,指向一个数组加1跳一个数组 int (* p)[10]; P 是个指针,p 是个数组指针,p 加 1指向下个数组,跳 10 个整形。
指针的指针:
int **p;//p 是指针的指针
int *q
p=&q;
a 是个 int* 类型的指针,是 a[0]的地址。 &a 变成了数组指针,加 1跳一个 10 个元素的整型一维数组
在运行程序时,a和&a 所代表的地址编号是一样的,即他们指向同一个存储单元,但是 a和&a 的指针类型不同。
int a[4][5];
a+1跳5个整型 (&a)+1跳4行5列( 80 个字节)
c 语言规定,数组名字取地址,变成了数组指针。加 1跳一个数组
数组名字和指针变量的区别
int a[5];
int *p;
p=a;
相同点:
a 是数组的名字,是 a[0]的地址,p=a 即p 保存了 a [0]的地址,即a 和p 都指向 a[0],所以在引用数组元素的时候,a和p等价
引用数组元素回顾: a[2]、(a+2)、p[2]、(p+2) 都是对数组a 中 a[2]元素的引用。
不同点:
1、a 是常量、p 是变量可以用等号’=给p 赋值,但是不能用等号给 a 赋值
2、对 a 取地址,和对 p 取地址结果不同因为 a 是数组的名字,所以对 a 取地址结果为数组指针。 p 是个指针变量,所以对 p 取地址 (&p) 结果为指针的指针
int a[5]={0,1,2,3,4};
int *p=a;
假如 a[0]的地址为 0x00002000.p 的地址为 0x00003000 1、&p 是指针的指针,为 int ** 类型,结果为 0x00003000,&p +1,往后指向一个 int* 类型的指针,地址编号差 4 2、&a 结果是数组指针,为 int(* )[5]类型,结果还是 0x00002000,&a +1,往后指一个数组(有5个整型元素的一维数组),地址编号差 20
指针的指针
指针的指针,即指针的地址, 定义一个指针变量本身指针变量占 4 个字节,指针变量也有地址编号
int a=0x12345678;
//假如: a的地址是 0x00002000
int*p;
p =&a;
//则 p中存放的是 a的地址编号即 0x00002000
因为p也占4 个自己内存,也有它自己的地址编号,及指针变量的地址,即指针的指针假如:指针变量 p 的地址编号是 0x00003000,这个地址编号就是指针的地址我们定义一个变量存放 p 的地址编号,这个变量就是指针的指针
int**q;
q=&p;//q 保存了p的地址,也可以说 q 指向了p则 q 里存放的就是 0x00003000
指针和字符串
字符串的地址,是第一个字符的地址。如: 字符串“helloworld”的地址,其实是字符串中字符”h”的地址。
字符串的存储形式: 数组、文字常量区、堆
1、字符串存放在数组中
其实就是在内存(栈、静态全局区) 中开辟了一段空间存放字符串
char string[100]= “Ilove C!”
定义了一个字符数组 string.用来存放多个字符,并且用”I love C!”给 string 数组初始化字符串“Ilove C!”存放在 string 中。
普通全局数组,内存分配在静态全局区普通局部数组,内存分配在栈区。 静态数组(静态全局数组、静态局部数组),内存分配在静态全局区
2、字符串存放在文字常量区
在文字常量区开辟了一段空间存放字符串,将字符串的首地址付给指针变量。
char* str = “I love C!”
定义了一个指针变量 str,只能存放字符地址编号Ilove C! 这个字符串中的字符不是存放在 s 指针变量中。sT 只是存放了字符I的地址编号,“I love C!”存放在文字常量区
3、字符串存放在堆区
使用 malloc 等函数在堆区申请空间,将字符串拷贝到堆区
char * st =(char*)malloc(10*sizeoD;//动态申请了 10 个字节的存储空间,首地址给 str 赋值。
strcpy(st,"I love C"); //将字符串“ Ilove C!”拷贝到 s 指向的内存里
字符串的可修改性
字符串内容是否可以修改,取决于字符串存放在哪里
1、存放在数组中的字符串的内容是可修改的
注:数组没有用 const 修饰
char str[100]="I love C!";
str[0]=“y’://正确可以修改的
2、文字常量区里的内容是不可修改的
char *str="I love C!"
*st ='y’://错误,I存放在文字常量区,不可修改
1、str 指向文字常量区的时候,它指向的内存的内容不可被修改。 2、st 是指针变量可以指向别的地方,即可以给 str 重新赋值,让它指向别的地方。
3、堆区的内容是可以修改的
char *str =(char*)malloc(10);
strcpy(str,"I love c")
*st='y';//正确,可以,因为堆区内容是可修改的
1、str 指向堆区的时候,st 指向的内存内容是可以被修改的。 2、str 是指针变量,也可以指向别的地方。即可以给 str 重新赋值,让它指向别的地方
str 指针指向的内存能不能被修改,要看 str 指向哪里 st 指向文字常量区的时候,内存里的内容不可修改 str 指向数组 (非 const ,修饰)、堆区的时候,它指向内存的内容是可以修改
初始化:
1.字符数组初始化
char buf aver[20]="hello world";
2.指针指向文字常量区,初始化
char *buf point="hello world";
3、指针指向堆区,堆区存放字符串。
不能初始化,只能先给指针赋值,让指针指向堆区,再使用 strcpy、scanf 等方法把字符拷贝到堆区
char *buf heap;
buf heap=(char *)malloc(15);
strcpy(buf heap,"hello world");
scanf(s’,buf heap);
使用时赋值
1、字符数组:使用 scanf或者 strcpy
char buf[20]="hello world"
buf="hello kitty";//错误,因为字符数组的名字是个常量.不能用等号给常量赋值。
strcpy(buf,"hello kitty");//正确,数组中的内容是可以修改的
scanf("%s",buf);//正确,数组中的内容是可以修改的
2、指针指向文字常量区
char *buf point =“hello world”;
1) buf point="hello kitty";//正确buf point 指向另一个字符串
2) strcpy(buf point,"hello kitty");//错误,这种情况,buf point 指向的是文字常量区,内容只读当指针指向文字常量区的时候,不能通过指针修改文字常量区的内容。
3.指针指向堆区,堆区存放字符串
char *buf heap;
buf_heap=(char *)malloc(15);
strcpy(buf_heap,"hello world");
scanf("%s",buf heap);
指针和函数
指针作为函数的参数
给被调函数传数值,只能改变被调函数形参的值,不能改变主调函数实参的值
要想在改变实参的值要传地址进去
调用函数的时候传变量的地址,在被调函数中通过*+地址来改变主调函数中的变量的值
给函数传数组
给函数传数组的时候,没法一下将数组的内容作为整体传进去只能传数组名进去 数组名就是数组的首地址,即只能把数组的地址传进去 也就是说,只能传一个4 个字节大小的地址编号进去
函数返回指针
返回地址的时候,地址指向的内存的内容不能释放如果返回的指针指向的内容已经被释放了,返回这个地址,也没有意义了. 可以在内容前加一个static
指针指向文字常量区的地址也可以
返回堆内存的地址(堆内存一直存在,直到程序结束)
函数指针:
定义的函数,在运行程序的时候,会将函数的指令加载到内存的代码段。所以函数也有起始地址。 函数的名字就是函数的首地址,即函数的入口地址就可以定义一个指针变量,来存放函数的地址。这个指针变量就是函数指针变量。
应用
函数指针用来保存函数的入口地址。 在项目开发中,经常需要编写或者调用带函数指针参数的函数。 比如Linux 系统中创建多线程的函数,它有个参数就是函数指针,接收线程函数的入口地址,即线程创建线程成功后,新的任务执行线程函数。
函数指针变量的定义
返回值类型(* 函数指针变量名)(形参列表);
int(*p)(int,int)://定义了一个函数指针变量pp 指向的函数必须有一个整型的返回值,有两个整型参数。
int max(int x,int y){
};
int min(int x,int y){
};
//可以用这个p 存放这类函数的地址
p=max;
p-min;
int main(){
int num=max(3,5);//通过函数的名字去调函数(最常用的)
//可以通过函数指针变量去调用
int num1=(*p)(10,20);//等价于 num = min(10,20)
};
函数指针数组
概念
由若干个相同类型的函数指针变量构成的集合,在内存中连续的顺序存储。函数指针数组是个数组,它的每个元素都是一个函数指针变量。
定义方法
指针类型名(* 数组名[元素个数])(形参列表)
int(* p[5])(int,int);
定义了一个函数指针数组,有 5 个元素 p[o]~p[4],每个元素都是函数指针变量每个函数指针变量指向的函数,必须有整型的返回值,两个整型参数。
易混淆的指针
int * f(void);
∗f没有用括号括起来它是个函数的声明,声明的这个函数返回值为 int* 类型的。
int (* f)(void)
∗f用括号括起来了,* 修饰 f说明,f是个指针变量。f是个函数指针变量,存放函数的地址,它指向的函数,必须有一个 int 型的返回值,没有参数。
特殊指针
void* 通用指针
任何类型的地址都可以给 void* 类型的指针变量赋值。
NULL
空指针: char* p=NULL 可以认为 p 哪里都不指向,也可以认为 p 指向内存编号为 0的存储单位在p的四个字节中,存放的是 0x00 00 00 00一般NULL 用在给指针初始化。
函数指针
函数名就是函数的地址
贡献者
版权所有
版权归属:PinkDopeyBug