目录
(5)有一个实参数组,想在函数中改变该数组中的元素的值,实参与形参对应关系有4种
程序是需要载入内存中运行,内存是有范围的,对于32位系统,内存地址范围是0x0000_0000~0xFFFF_FFFF,也就是内存大小为4GB,内存地址指的是内存中单元的编号,编号是固定的。,所以内存地址(存储单元的编号)本质就是一个整数,对于32位系统而言,地址所对应的编号是4字节的正整数。
用户想要访问内存地址下面的数据是比较麻烦的,因为地址比较难记忆,内存地址都是由内核管理,所以C语言规定用户有权利对内存地址进行命名,比如变量名字、数组名字.........,所以标识符就可以和操作系统提供的内存单元建立映射关系。
思考1
既然用户可以定义变量来存储数据,那能否把内存地址当成数据存储在一个变量中?
回答:是可以的,因为存储单元的地址本质就是一个整数
如果是在32bit系统下,则只需要4个字节的存储单元就可以完成存储。
如果是在64bit系统下,则只需要8个字节的存储单元就可以完成存储。
思考2
既然内存地址可以当做数据存储在一个变量中,那内核如何区分变量中的数据是作为普通数据还是作为内存地址呢?
回答:操作系统不需要区分,但是作为用户而言,需要区分该变量下存储的是地址还是普通整数
所以C语言表中规定:用户如果打算定义一个变量来存储一个内存地址,则需要定义变量的时候指明该变量中存储的是一个地址。
1、指针变量
(1)数据类型所占字节
16位系统 | 32位系统 | 64位系统 | 取值范围 | |
char | 1 | 1 | 1 | -128~127 |
unsigned char | 1 | 1 | 1 | 0~255 |
short | 2 | 2 | 2 | -32768~32767 |
unsigned short | 2 | 2 | 2 | 0~65535 |
int | 2 | 4 | 4 | -2147483648 ~2157483647 |
unsigned int | 2 | 4 | 4 | 0~4294967295 |
long | 4 | 4 | 8 | -2147483648 ~2147483647 |
unsigned long | 4 | 4 | 8 | 0~42294967259 |
long long | 8 | 8 | 8 | -9223372036854775808 ~9223372036854775807 |
float | 4 | 4 | 4 | |
double | 8 | 8 | 8 | |
long double | 10/12 | 10/16 | 有效位10字节 32位为了对齐实际分配12字节 64位分配16字节 | |
指针 | 2 | 4 | 8 |
(2)指针变量
C语言中把用于存储地址的变量称为指针变量,指针变量的值是地址(指针)因为通过变量中的地址可以指向某个存储单元!指针指向的是地址,所以可以把指针理解为地址,也可以把地址当做指针使用,注意:如果,打算获取某个地址下的值,必须使用 * 间接运算符 对变量的访问是通过地址的,
*地址 == 地址下的值
(3)指针变量格式
指针变量定义格式:
数据类型 *变量名;
比如
int *p;
char *p;
int * buf[5];
在声明的时候,*代表修饰的意思
在表达式中,*表示解引用的意思
定义指针变量时,左侧必须指定“基类型”,否则就不是定义指针变量。
指针变量的基类型用来指定此指针变量可以指向的变量的类型
(4)变量的访问
- 直接访问:直接按变量名访问
- 间接访问:将变量i的地址放入另一个变量中,然后通过该比那辆来找到变量i的地址,从而访问i变量
要存储变量i的值,既可以采用直接访问方式,也可以采用间接访问方式
(5)指针初始化
可以在定义变量时对指针进行初始化
int *point=&a; //等价于 int *point; point=&a;
*表示该变量是指针变量
指针变量中只能存放地址,不可以将一个整数赋值给一个指针变量
不同类型的数据在内存中所占的字节和存放方式不同
&a--->包含变量a的位置(存储单元编号表示的纯地址)和存储单元的数据类型信息
(6)指针偏移
- 如果指针指向整型变量,指针值+1 表示地址值+4个字节
- 如果指针指向整型变量,指针值+1 表示地址值+1个字节
(7)指针引用
引用指针的3种方式
- (1)给指针变量赋值 int a; p=&a; //把a的地址赋值给指针变量p
- (2)引用指针变量指向的变量 int a; int *p=&a; printf("%d",*p); //*p=1 等价于 a=1
- (3)引用指针变量的值 int a; int *p=&a; printf("%o",p); //以八进制输出指针变量p的值,输出a的地址,即&a
两种有关运算符
- (1)& 取地址符运算符 &a是变量a的地址
- (2)* 指针运算符(间接运算符) *p代表指针变量p指向的对象
(8)指针变量作为函数参数
形参在函数调用时,才分配内存。函数调用时,实参变量的值传送给形参变量,采取值传递的方式。单向传递,故形参值得改变不能使实参得值随之改变。函数调用结束后,形参占用的内存被系统自动释放,如何实现调用函数使变量的值发生变化?
指针变量作为函数参数,表示将变量的地址传送到另外一个函数中。
不能企图通过改变指针形参的值而使指针实参的值改变,但可以改变实参指针变量所指向变量的值注意:函数调用可以(而且只可以)得到一个返回值,而使用指针变量作为参数,可以得到多个变化了的值
思考3
既然指针变量可以存储一个内存地址,那请问内核是否会为指针变量分配内存空间?
回答:当然会分配,因为定义变量的目的就是为了申请内存单元
32bit系统下需要4个存储单元才能记录一个地址,而记录的地址和变量本身的地址是不一样的。变量的存储单元就相当于是一个容器,记录的地址就相当于数据而已。
思考4
用户定义了一个指针变量,但是此时并没有打算让该指针变量指向某个内存地址,请问内核分配给指针变量的内存空间中是否会存储一些未知的数据?如果存在,应该如何解决?
回答:是会的,所以为了提高程序的可靠性,为了避免异常出现。所以就算不存储有效地址,也应该对定义的指针变量进行初始化
注意:对指针变量进行初始化,则应该把指针变量对应的内存初始化为0,但是0只是一个整数,并不是地址,而指针变量就是应该存储地址。用户应该把普通整数0进行强制转换,转换为一个地址
0x00000000 --> (void *)0x00000000
int *p = (void *)0x00000000; //可读性较差,所以C语言中提供了一个宏定义 NULL 空指针
int *p = NULL;
//对指针变量进行初始化,目的是防止野指针出现,为了避免段错误的!!!
2、保留区
可以看到,linux系统的内存中有一部分内存是属于保留区,保留区地址范围就是0x0000_0000 ~ 0x0804_8000,属于用户没有权限访问的内存空间,一旦用户访问这块区域,就会导致段错误。
思考5
已经知道内存中有一块保留的空间,程序是没有权限访问的,但是用户能否定义一个指针变量指向这块空间?
回答:是可以的,但是只能用指针变量记录该地址,但是不能通过指针变量间接访问该地址,如果间接访问,则会导致内存异常,发生段错误。
3、指针变量中地址的运算
思考6
既然指针变量中存储的是一个内存地址,内存地址的本质就是一个整数,所以能否对整数进行算术运算呢?
回答:是可以的,只不过普通整数的算术运算和地址的算术运算的理解是不同的,一般对于普通整数可以进行算术运算,则结果也是一个整数。
但是对于指针变量中存储的地址进行算术运算,一般只能进行加法运算和减法运算,,对地址进行加法运算和减法运算,其实就是对地址进行偏移而已,偏移的单位一般是应该以字节为单位。
!!!注意
对于指针变量的偏移,要考虑到变量中存储的地址的数据类型,所以
地址 + 1 不表示存储单元向后偏移1个字节
应该是向后偏移 (1 * 数据类型)个字节。
4、数组指针
思考7
既然可以用指针变量指向另一个变量的地址,请问能否用指针变量指向数组的地址?
回答:当然可以,就相当利用指针变量来对数组的地址进行备份,提高了访问数组的安全性,而利用指针变量来指向数组的地址,被称为数组指针!!!!
int buf[5];
int *p = buf;
(1)引用数组中的元素
如果此时用户需要访问数组中的元素,可以通过数组下标 、数组名 and 指针访问,两者的区别如下:
(1)下标法:a[1]
int a[10];
for(i=0;i<10;i++) scanf("%d\n",&a[i]);
for(i=0;i<10;i++) printf("%d\n",a[i]);
(2)数组名计算元素地址,找出元素的值:*(a+i)
int a[10];
for(i=0;i<10;i++) scanf("%d\n",&a[i]);
for(i=0;i<10;i++) printf("%d\n",*(a+i));
(3)指针法:*p 指针指向数组元素
int a[10];
for(i=0;i<10;i++) scanf("%d\n",&a[i]);
for(p=a;p<(a+10);p++) printf("%d\n",*p);
等价于:
int a[10];
for(p=a;p<(a+10);p++) scanf("%d\n",p);
for(p=a;p<(a+10);p++) printf("%d\n",*p);
- 指针是变量,可以进行++、--、+、-运算
- 地址是整型常量,只能进行+、-运算、不能进行++、--运算
- 可以通过改变指针变量的值指向不同的元素
- 要注意指针变量的当前值,参与下次运算,指针指向的位置可能该改变
- 指针指向数组以后的存储单元,编译器不认为是违法的,只是运行结果不是预期的,可能发生数组溢出。或者访问了无权访问的存储单元,发生段错误
- ++和*同优先级,采用右结合
(1)分析 p++; *p; p++使p指向下一个元素,然后间接访问p下面的元素
(2)*p++ ++与*同优先级,遵循右结合性,等价于*(p++)
(3)*(p++)与*(++p) *(p++)是先取*p,再使p+1 *(++p)是先使p+1,再取*p
(4)++(*p) 表示p所指向的元素的值+1,如果p=a;则++(*p)==++a[0]
(5)若p指向数组a中第i个元素a[i],则有: *(p++)== a[i++] 先对p进行“*”运算,再p)== a[++i]对p+1 *(p--)== a[i--] 先对p进行“*”运算,再对p-1 *(++ 先对p+1,再对p进行“*”运算 *(--p)== a[--i] 先对p-1,再对p进行“*”运算
(2)引用数组时指针的运算
p+1不是将p的值+1,而是+一个数组元素所占的字节数 数组名代表数组首元素的地址,a+1也是地址 *(p+i)是p+i所指向的数组元素,即p[i]==*(p+i) E1[E2] == *(E1+E2) 如果指针变量p1与指针变量p2都指向同一数组中的元素,执行p2-p1,的值是(两个地址之差/数组元素的长度) 两个地址不能相加
(3)数组名作为函数参数
当用数组名作为函数参数时,如果形参数组中各元素的值发生变化,实参数组元素的值随之变化,为什么?
实参数组名代表该数组的首元素的地址,形参是用来接受实参传递过来的数组首元素的地址的。因此形参应该是一个指针变量(只有指针变量才能存放地址),编译器都是将形参数组名作为指针变量来处理的 以下两个函数等价 fun(int arr[],int n) fun(int *arr,int n) *(arr+i)==arr[i]
(4)以变量名和数组名作为函数参数的比较
实参类型 | 形参类型 | 传递的信息 | 函数调用是否改变参数的值 |
变量名 | 变量名 | 变量的值(值传递-单向传递) | 不能改变实参变量的值 |
数组名 | 数组名/指针变量 | 实参数组首元素的地址(双向传递) | 能改变实参数组的值 |
(5)有一个实参数组,想在函数中改变该数组中的元素的值,实参与形参对应关系有4种
(1)形参与实参都用数组名
int main()
{
int a[10];
/*code*/
f(a,10);
}
int f(int x[],int n)
{
/*code*/
}
形参数组x接受了实参数组首元素a[0]的地址 &a[0]
理解为函数调用期间,形参数组与实参数组共用一段内存单元
(2)实参用数组名,形参用指针变量
int main()
{
int a[10];
/*code*/
f(a,10);
}
int f(int *x,int n)
{
/*code*/
}
实参a位数组名,形参x为int *型指针变量
函数调用期间,形参x指向a[0],x=&a[0]
通过x值得改变,可以指向a数组得任一元素
(3)实参形参都用指针变量
int main()
{
int a[10];
int *p=a;
/*code*/
f(p,10);
}
int f(int *x,int n)
{
/*code*/
}
实参p和形参x都是int *型指针变量
先使实参指针变量p指向数组a[0],p的值是&a[0]
然后将p的值传递给形参指针变量x,x的初始值也是&a[0]
通过x值的改变,可以使x指向数组a的任一元素
(4)实参为指针变量,形参为数组名
int main()
{
int a[10];
int *p=a;
/*code*/
f(p,10);
}
int f(int x[],int n)
{
/*code*/
}
实参p为指针变量,指向a[0]
形参为数组名x,编译系统把x作为指针变量处理,将a[0]的地址传给形参x,使x也指向a[0]
理解为形参数组x与a数组共用一段内存单元
函数执行过程中,可以使x[i]的值发生变化,而x[i]就是a[i]
5、指针变量来访问二维数组中的元素
思考8
用户打算定义一个二维数组,并且用一个指针变量来存储数组的地址,现在想用指针变量来访问二维数组中的元素,请问如何访问?
思考9
既然数组可以存储同一类型的数据,请问能否在一个数组中存储指针变量的地址???
思考10
既然可以使用一个指针变量来存储另一个变量的地址,能否定义一个指针变量,然后来存储另一个指针变量的地址?如果可以,那如何可以访问到最终内存地址下的数据?
指针小结
1、指针及地址
- &a是变量a的地址,也是a的指针
- 指针变量是存放地址(指针)的变量
- 指针变量的值是一个地址(指针)
- &是取地址运算符,&a是a的地址
- &是指针运算符,&a是变量a的指针
- 数组名是一个地址(指针),是数组首元素的地址(指针)
- 函数名是一个指针(地址),指向函数代码区的首字节(指向代码区首字节的地址)
- 函数实参若是数组名,传递给形参的是一个地址(指针)
2、地址类型(指针类型)
数据都是有类型的数据(带类型的数据),地址也是有数据类型的,叫地址类型(指针类型),占用4个字节的内存
地址型数据包括以下信息:
- 表示内存编号的纯地址
- 本身类型:指针类型
- 所标识的存储单元中存放的基类型
3、指针与指针变量的区别
- 指针就是地址
- 指针变量是用来存放地址的变量
- 指针变量的值是一个地址
指向:
- 地址就意味着指向,通过地址才能找到该地址的对象;
- 对于指针变量来说,把谁的地址存放在指针变量中,称此指针指向谁
- 与指针变量的基类型相同的数据的地址才能存放在相应的指针变量中
4、指针变量的类型和含义
变量定义 | 类型表示 | 含义 |
int i; | int | 定义整型变量i |
int *p; | int * | 定义p为指向整型数据的指针变量 |
int a[5]; | int [5] | 定义整型数组a 有5个元素 |
int *p[5]; | int *[4] | 定义指针数组p 有5个指向整型数据的指针元素组成 |
int (*p)[4]; | int (*)[4] | p为指向包含4个元素的一维数组的指针变量 |
int f(); | int () | f为返回整型函数值的函数 |
int *p(); | int *() | p为返回一个指针的函数,该指针指向整型数据 |
int (*p)(); | int (*)() | p为返回指向函数的指针,该函数返回一个整型值 |
int **p; | int ** | p为一个指针变量,指向一个整型数据的指针变量 |
void *p; | void * | p是一个指针变量,基类型为void,不指向具体的对象 |
5、指针运算
(1)形参与实参都用数组名
int main()
{
int a[10];
/*code*/
f(a,10);
}
int f(int x[],int n)
{
/*code*/
}
形参数组x接受了实参数组首元素a[0]的地址 &a[0]
理解为函数调用期间,形参数组与实参数组共用一段内存单元
(2)实参用数组名,形参用指针变量
int main()
{
int a[10];
/*code*/
f(a,10);
}
int f(int *x,int n)
{
/*code*/
}
实参a位数组名,形参x为int *型指针变量
函数调用期间,形参x指向a[0],x=&a[0]
通过x值得改变,可以指向a数组得任一元素
(3)实参形参都用指针变量
int main()
{
int a[10];
int *p=a;
/*code*/
f(p,10);
}
int f(int *x,int n)
{
/*code*/
}
实参p和形参x都是int *型指针变量
先使实参指针变量p指向数组a[0],p的值是&a[0]
然后将p的值传递给形参指针变量x,x的初始值也是&a[0]
通过x值的改变,可以使x指向数组a的任一元素
(4)实参为指针变量,形参为数组名
int main()
{
int a[10];
int *p=a;
/*code*/
f(p,10);
}
int f(int x[],int n)
{
/*code*/
}
实参p为指针变量,指向a[0]
形参为数组名x,编译系统把x作为指针变量处理,将a[0]的地址传给形参x,使x也指向a[0]
理解为形参数组x与a数组共用一段内存单元
函数执行过程中,可以使x[i]的值发生变化,而x[i]就是a[i]
笔试题1
请分析以下程序,根据自己对程序的理解回答出程序的运行效果,不许讨论和抄袭!
笔试题2
请分析以下程序,根据自己对程序的理解回答出程序的运行效果,不许讨论和抄袭!