内存与地址
在计算机中,数据是存放在内存单元中的,一般把内存中的一个字节称为一个内存单元。为了更方便地访问这些内存单元,可预先给内存中的所有内存单元进行地址编号,根据地址编号,可准确找到其对应的内存单元。由于每一个地址编号均对应一个内存单元,因此可以形象地说一个地址编号就指向一个内存单元。C 语言中把地址形象地称作指针。
C语言指针
C是对底层操作非常方便的语言,而底层操作中用到最多的就是指针,这成就了优秀的C程序的效率几乎和汇编语言程序一样高的功绩。
指针就是地址,口语中说的指针通常指的是指针变量。
指针的存在使程序的运行更加快速灵活。
指针的定义和类型
C语言是强类型语言,所有变量都是有类型的,那么以此类推指针变量也是有类型的。
指针的类型是:类型 + *
,如:
char *c_p=NULL;
int *n_p=NULL;
char *
类型的指针是为了存放字符型变量的地址;
int *
类型的指针是为了存放整型变量的地址。
指针要初始化,否则会产生野指针。
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
指针指向地址,而地址长度相同,那么指针为什么要有类型呢?这是为了数组中指针的移动。比如:
char *
类型的指针以为这块内存存储的是字符型的数据,那么访问下一个数据就应该是走一个字节的距离;
int *
类型的指针以为这块内存存储的是整型的数据,那么访问下一个数据就应该是走四个字节的距离。
总结:指针的类型决定了指针在数组中向前或者向后走一步有多大(距离)。
指针的使用
指针变量的引用
int *p,a=3;
p=&a;
如果知道内存空间的名字,可通过名字访问该空间,称为直接访问。也就是a
等于3。
&
是取地址运算符,在例子里取了a
的地址给了指针p
。
*
是解引用,可以取出p
指向的地址的数据。如:*p
等于3。这就是间接访问。
指针与数组(数组指针)
数组是一系列相同类型变量的集合,不管是一维数组还是多维数组其存储结构都是顺序存储形式,即数组中的元素是按一定顺序依次存放在内存中的一块连续的内存空间中(地址连续)。
数组名表示的是数组首元素的地址。因此指针可以这样用:
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *p = arr;
二级指针
所谓的二级指针就是指向指针的指针。指针变量也是变量,那么就也会有地址,而存放指针变量的地址的变量就叫做二级指针变量。
对于二级指针的运算有:
int a=10;
int *pa=&a;
int **ppa=&pa;
-
*ppa
通过对ppa中的地址进行解引用,这样找到的是pa
,*ppa
其实访问的就是pa
。 -
**ppa
,先通过*ppa
找到pa
,然后再对pa
进行解引用操作:*pa
,那找到的就是a
。
指针与函数(函数指针)
函数指针,其本质是一个指针变量,该指针指向这个函数。总结来说,函数指针就是指向函数的指针。
int (*fun)(int x,int y);
类型 (*函数名) (参数)
函数指针是需要把一个函数的地址赋值给它,有两种写法:
fun = &Function;
fun = Function;
取地址运算符&
不是必需的,因为函数名就表示了它的地址。
因此调用函数指针的方式也有两种:
x = (*fun)();
x = fun();
指针与结构体(结构体指针)
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合,所以指针也可以指向结构体。
结构体指针的定义方法:
-
struct 结构体名 *指针
struct student *p1;
-
直接在定义结构体的时候添加结构体指针的声明
struct student { int num; char name[20]; }*p2;
结构体指针的访问变量方法:
-
p->结构体成员
struct student *p; p=&st1; printf("%d,%s\n",p->num,p->name);
-
(*p).结构体成员
struct student *p; p=&st1; printf("%d,%s\n", (*p).num, (*p).name);
链表
当我们在结构体中定义结构体指针时,就可以将多个结构体相连,组成链表。
struct student
{
int num;
char name[20];
struct student *next;
};