指针_1
一、指针的基本概念
计算机内存以字节为最小单位进行编址的。
1、地址就是内存单元的编号。
2、指针就是地址,地址就是指针。
3、指针变量是存放地址(也就是指针)的变量。指针和指针变量是两个不同的概念。
4、但是要注意,通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样。
int i;
int *p; //p是变量的名字,p变量的数据类型是int*。int * 表示p变量存放的是int型变量的地址。
p = &i; //ok
/*
1、p保存了i的首地址,因此p指向i。
2、p不是i,i也不是p。修改i的值不影响p的值,修改p的值也不影响i的值。
3、如果一个指针变量指向一个普通变量(即指针变量存放普通变量的地址),则
*指针变量 就完全等同于 普通变量
4、*p就是以p的内容为地址,空间存储的变量
*/
p = i; //error,因为类型不一致,p只能存放int类型变量的地址,不能存放int类型的值。
二、指针的重要性
1、表示一些复杂的数据结构
2、快速传递数据
3、使函数返回一个以上的数据
void f(int *pa, int *pb)
{
*pa = 1;
*pb = 2; //被调函数修改主调函数的值----指针可以做到,其他做不到
}
int main(void)
{
int a = 6, b= 8;
f(&a, &b);
}
4、能够直接访问硬件
5、能够方便的处理字符串
6、是理解面向对象语言种引用的基础
三、指针的定义
地址:
内存单元的编号
从零开始的非负正数
范围:32位的最大访问空间是4G.
指针:
1、指针的本质就是一个操作受限的非负整数(指针可以相减,不能进行相加,相乘,相除运算)
2、指针存放的是地址,地址是一个4字节大小(32位机器)的非负整数。不管数据类型有多大,指针只保留这个数据起始第一个字节的地址(数据类型决定每个数据在内存中所占字节的大小),所以任何类型的指针都是4字节大小。
四、指针的分类
1、基本类型指针
指针的常见错误
1.1、野指针
int main(void)
{
int *p; //局部变量不初始化,存放的是垃圾值
int i = 5;
*p = i; //把i的值赋值给一个以垃圾值为地址的内存里。在这个程序里系统只分配了2个数的操作空 间(p,i),但在程序运行过程中操作了分配空间以外的空间。
printf("%d\n", *p);
return 0;
}
int main(void)
{
int i = 5;
int *p, *q;
p = &i;
*q = p; //q为int*类型,则*q为int类型,p为int*类型,类型不一致不能进行赋值,出现语法错误
return 0;
}
/*
q的空间是属于本程序的,所以本程序可以读写q的内容,但如果q内部是垃圾值,则本程序不能读写*q的内容(也就是以q内容为地址的空间里的内容),因为此时*q代表的内存单元的控制权限并没有分配给本程序。
*/
int main(void)
{
int i = 5;
int *p, *q;
p = &i;
p = q;
printf("%d\n", *p); //error:因为q本身系统分配空间(但p的值是q局部变量里的垃圾值), 但*p是一个垃圾值地址内的内容(超出系统分配的空间)。
printf("%d\n", *q); //error:操作q本身没有问题,但操作*p就越界了。
return 0;
}
1.2、基本类型指针的使用
互换2个数
/*
函数之间,实参和形参之间的传递都是数据的拷贝,指针传递的是地址的拷贝,被调函数通过地址直接修改主调函数的值,被调函数释放后对主调函数的影响依然在。
*/
#include <stdio.h>
void Huhuan(int *pa, int *pb)
{
int c;
c = *pa;
*pa = *pb;
*pb = c;
}
int main(void)
{
int a = 3;
int b = 4;
printf("*pa = %d, *pb = %d\n", a, b);
printf("///////////////////////\n");
Huhuan(&a, &b);
printf("*pa = %d, *pb = %d\n", a, b);
return 0;
}
1.3、*号的含义
1、乘法
2、定义指针变量
int *p; 定义了一个名字是p的变量,int* 表示p只能存放int类型变量的地址。
3、指针运算符
该运算符放在已经定义好的指针变量的前面;
如果p是一个已经定义好的指针变量,则*p就是以p内容为地址空间内存放的变量。
4、除定义指针变量外,变量名前边加*。
在变量使用过程中,只有指针变量前边可以加*号,普通变量前边坚决不可以加*号。
1.4、如何通过被调函数修改主调函数中普通变量的值
1、实参必须是该普通变量的地址
2、形参必须为该类型指针变量
3、在被调函数中通过
*形参名 = ......
的方式就可以修改主调函数相关变量的值。
2、指针和数组
2.1、指针和一维数组
一维数组名:
一维数组名是一个指针常量,它存放的是一维数组第一个元素的首地址。
//错误
int a[5]; b[3];
a = b; //error,一维数组名是一个指针常量
#include <stdio.h>
int main(void)
{
int a[3];
printf("%#X\n", &a[0]);
printf("%#X\n", a);
}
//运行结果
fuchunjie@FCJ:/mnt/hgfs/VMshare/mySpace/指针互换2数$ ./app
0XD5E2C42C
0XD5E2C42C
//证明:一维数组名是一个指针常量,它存放的是一维数组第一个元素的首地址。
下标和指针的关系
如果p是一个指针变量,则p[i] 永远等价于 *(p+i);
如何确定一个一维数组(确定一个一维数组需要几个参数)
数组名:
数组长度:
指针变量运算
指针只能相减,不能进行加,乘,除。
如果两个指针变量指向的是同一个连续空间中的不同存储单元,则这两个指针变量才可以相减。不同存储单元指针变量相减没有任何意义。
2.2、指针和二维数组
3、指针和函数
4、指针和结构体
5、多级指针
五、动态内存的分配
5.1、传统数组的缺点
1、数组的长度必须事先指定,并且只能是常整数,不能是变量。
2、传统形式定义的数组,该数组的内存无法手动释放。除非程序终止,由系统释放。
3、数组的长度不能在函数运行过程中动态的扩充和缩小。数组长度一旦定义在运行过程中不能改变。
4、A函数定义的数组,在A函数运行期间可以被其他函数使用,但在A函数运行完毕之后,A函数中的数组将无法被其它函数使用。(静态内存在函数终止之后不能再被使用)
5.2、为什么需要动态分配内存
动态分配内存很好的解决了传统数组(静态内存)的4点缺陷。
5.3、动态内存分配举例,动态数组的构成
1、使用malloc函数分配动态内存空间。并把从系统申请的动态内存的第一个字节地址返回,为了确定所指空间每个数据变量大小(确定变量类型),malloc函数前边必须加强制数据类型。
int *p = (int *)malloc(4);
/*
1、malloc函数只用一个参数,并且参数值整型
2、4表示请求系统为本程序分配4个字节空间
3、malloc函数只能返回第一个字节地址(如何确定这些空间每个变量占几个字节?)由malloc前边的强制类型转换确定。
4、本行程序分配了8个字节,p本身占4个字节,malloc函数分配4个字节。
5、p本身是数据类型+变量名形式分配(静态分配内存),p所指向的内存是动态分配的。
*/
5.4、静态内存和动态内存的比较
/*
1、int a[5]; //如果int占4个字节,则该数组总共包含20个字节,每四个字节被当作了一个int变量来使用
2、 int *pArr;
pArr = (int *)malloc(sizeof(int) * 5);
pArr变量保存的是malloc返回动态分配空间的首地址,因为是int*类型指针,所以pArr指向了以首地址开始连续4字节空间。pArr+1则指向接下来的4个字节空间,以此类推。
*/
静态内存:
1、静态内存由系统分配,由系统释放。
2、静态内存是在栈中分配的。
动态内存:
1、动态内存是程序员手动分配的,手动释放。
2、动态内存是在堆里分配。
5.5、快函数使用内存的问题
思考:指针最主要的目的是为了跨函数使用指针。
指针运算符*使用分2种情况
1、定义时
类型名 * 变量名;定义了某类型的指针变量。
int *p; //定义int类型指针变量
int **p; //定义int*类型指针变量
int ***p; //定义int**类型指针变量
2、使用时
int ***p;
/*
*p 是int**类型变量
**p 是int*类型变量
***p 是int类型变量
*/