第五章 指针【C语言的灵魂】
指针【C语言的灵魂】
实质:
指针的实质就是地址,地址就是指针
地址就是内存单元的编号
指针变量是存放地址的变量
指针和指针变量是两个不同的概念
但是要注意:通常我们叙述时会把指针变量简称为指针,实际它们的含义并不一样。
学习指针后,应将变量空间看作两部分内容:变量的内容和变量的地址。
实质:不在同一函数的两个指针,都可以指向同一变量。
注意:&取地址符不能取常量和表达式的地址。
指针的重要性:
1.表示一些复杂的数据结构
2.快速的传递数据,减少内存耗用
使用指针指向主函数中的变量,从而直接修改主函数中的变量,且只发送4个字节的地址,速 度快,效率高。
3.使函数返回一个以上的值
4.能直接访问硬件
5.能够方便地处理字符串
6.是理解面向对象语言中引用的基础
指针的分类:
1.基本类型指针
详见经典指针程序_互换两个数.cpp
2.指针和数组
3.指针和函数
4.指针和结构体
5.多级指针
指针与函数
函数的形参与实参永远不可能是同一个变量,通过指针指向实参可以使 *形参与实参为同一变量,在
函数内部将实参的值改变。
【指针:改写主函数中变量的值】
要想改变主函数中变量的值,实参必须发送地址。
通过传值,形参永远不可能改变实参的值,只能返回一个值,但可以通过指针,发送(复制)地址给形参,从而改变实参的值(即返回多个值)。
指针与数组(地址的运算)
一维数组名
是个指针常量,存放的是数组中第一个元素的地址,固定指向数组中第一个元素,不可被赋值 改变。
inta[5]; //a是int *类型
*a= 9; //一维数组名即表示数组中第一个元素的地址,此处元素a[0]被赋值9。
*(a+1)= 10; //依次类推,a+1、a+2...a+i分别表示第2个、第3个...第i+1个元素。
地址+1即为数组中下一连续元素的地址。
下标与指针
如果p是个指针变量(或数组名),则 p[i] 永远等价于 *(p+i)。
指针变量加下标即表示数组元素,正如a[i]表示数组元素一样,a本身就是指针变量。
函数对数组进行处理,需要接收两个参数:函数第一个元素的地址(即数组名)和数组元素的个数。
即确定一个数组需要两个参数(首地址与长度),只要知道函数第一个元素的地址(即数组名)和数组 元素的个数,那么数组中所有元素的信息都可以知道。
指针的运算
指针变量不能相加,不能相乘,也不能相除。
如果两个指针变量指向的是同一块连续空间中的不同存储单元,
则这两个指针变量才可以相减。
正如,在同一小区的门牌号才可以相减,不在同一小区的门牌号相减无意义。
指针的相减运算就是十六进制数的相减运算。
inti = 3;
intj = 5;
inta[50];
int* p = &i;
int* q = &j;
q- p; //不在同一存储空间的两个变量i、j的地址相减无意义。
p= &a[1];
q= &a[44];
printf("%d\n",q-p); //即为十六进制数的相减,然后以十进制输出。
动态内存分配
内存分配头文件:<malloc.h>
malloc.h文件包括malloc()函数、free()函数、realloc()函数等操作内存函数。
在使用时应注意声明<malloc.h>头件。
malloc是memory(内存)allocate(分配)的缩写。
inti = 3; //静态分配内存,给变量i分配了4个字节空间。
int* p = (int *)malloc(20);
/*
1.动态分配内存,要使用malloc函数,必须添加malloc.h头文件 (函数声明)。malloc是 memory(内存) allocate(分配)的缩写。
2.malloc函数只有一个形参,并且形参是整型。
3.20表示请求系统为本程序分配20个字节内存空间。
4.malloc函数的返回值为第一个字节的内存地址,赋值给指针变
量p。
malloc函数的返回值为void * 类型,void *类型也叫干地址
,即无实际意义的地址。
并且需要强制类型转换(int *),将所分配的内存转换为指针变
量p所定义的int * 类型。
共分配了20个字节,每个int类型变量占4个字节,所以共生成 了5个变量(动态数组)。
第一个变量为*p,第二个为*(p+1),依次类推,第i个为*(p+i) 5. 注意:指针变量p所占的内存是4个字节,为静态分配的,p所指 向的内存为动态分配的。
6.如果内存分配失败,则malloc函数会返回地址NULL,所以经常在调用 malloc函数后写上if (NULL == p);语句来判断是否分配成功。
*/
*p= 3; //为第一个变量赋值3
*(p+1)= 5; //为第二个变量赋值5
printf("%d,%d\n", *p, *(p+1));
free(p);//free()函数,将p指向的内存空间释放掉。注意:p本身的内存空间是静态的,不 会被释放,只能在程序结束后被系统释放。
动态构造一维数组:
inta[5]; //静态构造了5个元素的一维数组。
int* pArr;
intlen;
inti;
printf("请输入需要存放的元素的个数:");
scanf("%d",&len);
pArr= (int *)malloc(4*len);
/*
动态构造了一维数组,长度为len,数组类型为
int类型,数组名即为pArr。类似于 int a[5]; 数组名PArr与a相同。
动态数组的操作与静态数组相同,数组名pArr存
放了数组第一个元素的地址。
*/
for (i=0;i<len; ++i)
{
printf("请输入第%d个元素的值:", i);
scanf("%d",&pArr[i]); //与静态数组操作相同。
}
printf("一维数组中的内容为:\n");
for(i=0; i<len; ++i)
printf("a[%d]= %d\n", i, pArr[i]);
realloc(pArr,100); /*
realloc()重新分配内存函数,用于改变数组的长度,后跟两个参数, 第一个是数组名,第二个是重新分配的数组字节数,
如此函数即将pArr数组所占内存改为100字节。
当内存改变时,增长:如50字节变为100字节,前50字 节内容保留不变,再增加50字节内存。
缩小:如150字节变为100字节,前100字节内容保留,
后50字节内容丢失。
*/
free(pArr); //free()函数释放指针变量所指向的变量内存,当释放数组名时,将全 //部释放整个数组所占的内存,而不是只释放数组名所指向的第一个元 //素变量内存。
printf("一维数组中的内容为:\n");
for(i=0; i<len; ++i)
printf("a[%d]= %d\n", i, pArr[i]);
动态内存与静态内存比较:
静态内存是由系统自动分配,由系统自动释放。
静态内存是在栈中分配的。
动态内存是由程序员手动分配,手动释放。
动态内存是在堆中分配的。
内存分为五大区,其中在栈中分配的内存调用时压栈,完毕后出栈,内存被释放,不能够跨函 数使用,而在堆中分配的内存不存在这种情况,可以跨函数使用。
多级指针
多级指针,即为指针的指针,取指针变量的地址。
inti = 10;
int* p = &i;
int** q = &p; //p为int *类型,&p即为int ** 类型。
int*** r = &q;
//p= &r; //错误,r为int *** 类型,&r即为int **** 类型,而p为int * 类型,类型不一 //致,无法赋值。
printf("i= %d\n", i);
printf("i= %d\n", *p);
printf("i= %d\n", **q);
printf("i= %d\n", ***r); //*r是q,**r是p,等价于*q,***r是i,等价于*p。
int** p如何理解:p是int ** 类型,*p就是int * 类型,**p就是int类型。(星号的移动)
注意:要在另一函数中修改谁,就应该发送谁的地址,以其指向(包含多次指向)来修改,而 不可能修改发送的变量内容。
如: int * p;
change(p); //错误,要修改p的指向,应该发送p的地址,而不能发送p本身。
即: int * p;
change(&p); //以*q == p,*q指向来实现修改p的指向。
跨函数使用内存
上已介绍,静态内存是在栈中分配的,函数运行结束后内存被释放,不能跨函数使用。
动态内存是在堆中分配的,函数运行结束内存空间仍然存在,所以可以跨函数使用。
学习指针的重要用途就是为了跨函数使用动态内存变量,在跨函数使用中,传递的都是地址。
指针变量所占字节
sizeof()函数:
sizeof(数据类型或变量名)
sizeof()函数返回值为该数据类型或该变量所占字节数。
如:sizeof(int) = 4; sizeof(double) =8; sizeof(char) = 1;
sizeof(int * ) = 4; sizeof(double * ) = 4;sizeof(char * ) = 4;
结论:一个指针变量无论是什么类型指针变量,指针变量都占4个字节。
原因:
在内存中,8位二进制数为一个字节,一个字节为一个基本单元,有一个编号。
如:double类型数据,占8个字节,所有有8个编号,但C语言中规定,一个变量地址 的编号以其第一个编号作为其编号,所以无论是什么类型数据,其编号都只有一个, 一个十六进制地址编号占4个字节,所以指针变量也就占4个字节。
拓展:
32位CPU有32根地址总线,2^32=4G,所以32位CPU最大支持4G内存。
每个地址编号都是由32位二进制数组成,所以占4个字节,转换为十六进制表示。
地址总线进行寻址是由32根地址总线确定的。
//本函数功能是输出任意一维数组。
voidstring(int * p, int l) //函数对数组进行处理,需要接收两个参数:函数第一个元
{ 素的地址(即数组名)和数组元素的个数。
inti;
for (i=0;i<l; ++i)
printf("a[%d]= %d\n",i, *(p+i));
}
【空指针NULL】
NULL代表的是内存单元的地址编号00000...0
计算机中规定了以0为编号的内存存储单元的内容不可读,不可写。
操作系统中很重要的数据会放在以0为编号的内存存储单元中。
int* p; p = NULL; //就不可再对*p中的内容进行操作。
经常这样写:free(p); p = NULL; //p所指向的变量空间已被释放,不能再读写,为防止程序出错崩溃,写
上p = NULL;防止再对*p进行操作。
注意:空指针NULL是四个大写字母,不能写成小写。
指针常见问题
1.若普通变量未被初始化,则会存放垃圾值;
但若指针未被初始化,则会出错,因为指针变量存放了未知的地址,指向了未知的变量,
指针变量可使用,即地址可使用,但 *指针变量的值不属性本程序,无权读取或访问。
2.普通变量不同类型之间可以相互赋值,虽然可能会有精度丢失;
但指针变量不同类型之间即使不会丢失精度也不能够相互赋值,如:long * p;即定义p只能存放 long类型变量地址,不能存放double类型变量地址,也不能存放int类型变量地址,因为指针变量 只保存了变量第一个字节的地址,即只指向第一个字节,但int * p规定了p指向的变量占4个字节, 以此来确定变量。
3. &:取地址符,即取变量的地址,不仅可以取普通变量的地址,也可以取指针变量的地址,即指针 变量也是变量,只要分配了变量空间,就可以取其地址。
星号* 的三种含义:
1.乘号
2.定义指针变量
3.指针运算符(为取地址符 &的逆运算)
查看变量的地址
把变量的地址以十六进制输出(地址编号都是以十六进制表示的)
%x(X):输出为十六进制整型(字母大小写) %#x(X):输出为Ox(X)十六进制整型
printf("%#X",&i); //以十六进制整型输出变量i的地址。
或用%p以32位地址十六进制控制输出。
例1.
注意:指针变量p为int * 类型,*p为int类型,即p存放了整形变量的地址,*p代表整形变量。(*与&为互逆运算)
int* p; //定义整形指针变量,p是指针变量的名字,int * 表示p变量存放的是int类型变量的地址。
//p变量为int * 类型,即存放int变量地址的类型。
inti = 3;
p= &i;
/*
1. p存放了i的地址,因此p指向i,通过p可以找到i。
2. p不是i,i也不是p,p与i是两个不同的变量。更准备地说:修改p的值不影响i的值,修改i的值也不 影响p的值。
3. 如果一个指针变量指向了某个普通变量,则
*指针变量 就完全等同于 普通变量
例:如果p是个指针变量,并且p存放了普通变量i的地址
则p指向普通变量i
*p 就完全等同于 i, *p == i (*是取地址&的逆运算,可认为是取出指针所指向的普通变 量。)
或者说:在所有出现*p的地方都可以替换成i(除定义声明外)
在所有出现i的地方都可以替换成*p
*p表示以p的内容为地址的变量
所以,*p就是 i,修改 *p 的值就是修改i的值。
*/
printf("i= %d\n", i);
printf("i= %d\n", *p);
*p= 99; //即修改了i的值。
printf("i= %d\n", i);
printf("i= %d\n", *p);
例2.
inta[5] = {1,2,3,4,5};
inti;
for(i=0;i<5; ++i)
printf("a[%d]= %d\n", i, *(a+i)); //此处写*(a+i)和a[i]都是完全等价的。
例3.
voidarr(int * p, int len)
{
inti;
for(i=0; i<len; ++i)
printf("a[%d]= %d\n", i, p[i]); //此处写*(p+i)和p[i]都是完全等价的。
//*(p+i)、p[i]、a[i]和*(a+i)都是同一个变量。
return; //【p[i] == *p(p+i) ==a[i] == *(a+i)】
}
intmain(void)
{
inta[10] = {1,2,3,4,5};
arr(a,10);
return0;
}