指针
本文摘自孙广中老师的PPT
基本概念
指针类型
指针变量的值是所指变量的起始地址,是带有类型属性的特殊整数
1. 编译器依据指针变量声明的类型,来读写、理解指针所指向地址的 内容
2. 指针变量本身也占据内存空间,在32/64位系统上是4/8字节。即必 须能把程序中所用的最大地址表示出来,通常是内存地址的
宽度, 也往往等于一个机器字长
3. 应注意,不同类型变量不仅占用内存的字节数不同,而且对内存单 元的起始地址也有不同要求(与操作系统有关),如int
float double要求偶数(或4的倍数)地址
指向复杂数据类型的指针
() 的优先级比 * 的优先级高
空类型指针
空类型指针是用 void 声明的一种通用指针变量(有人称其为万能指针)
声明形式: void *变量名 ;
其它类型指针可以直接赋值给 void 指针
void 指针赋值给其它类型指针时,必须进行强制类型转换
void 指针不能直接取值或运算,一般应将 void 指针强制转换后再进行
指针运算
指针加减
指针变量的加减并非整数的加减,而是依据指针声明时的数据类 型来确定指针单位增量的长度,进行地址的前后移动
int x=10;
int *p;//定义指针
p=&x;//取地址
char (*ptr)[5]; //ptr是指向由5个字符元素构成的数组的指针变量
int **ip; //ip是一个指向整型量的指针变量的指针变量
int *fip(); //fip是返回整型指针的函数(函数声明)
int (*pti)(); //pti是指向一个返回整数的函数的指针变量
int *(*pfpi)(); //pfpi是一个指向函数的指针变量,该函数返回指向整数的指针
float x[]={1.0, 2.0, 3.0};
float *fp=&x[1];//每次增减1都在内存中向后或向前移动4个字节
float (*fp)[3]=&x;//p每次增减1都在内存中向后或向前移动4*3个字节
指针相减
两个指针可以相减,或进行关系比较,但必须是相容指针(所指对象是同一类型,且指向同一数据集合)
相减的结果是两个指针间相差的该类型数据的个数,两个指针不可以相加(无意义)
自增自减
*p++ 等价于 *(p++) ,表示先取值,然后 p 再指向后继元素
*++p 等价于 *(++p) ,表示 p 先指向后继元素,然后再取值
指针与数组
指向数组的指针
定义形式: 类型名 (*变量名)[数组大小]
二维数组名的类型是一个二维数组,其值的类型是一个指向一维 数组的指针
指针数组
声明形式 类型说明符 *数组名[数组长度]
指针数组中的元素都是指向同一类型的指针
多维数组
多维数组虽然也是连续存储在内存中的元素集合,但语法上(编 程使用时)是元素为数组的数组
数组取值时,每处理一个[ ] *,相当于一次指针取值运算
int b, a[10]={1,2,3,4,5,6};
&(a[10])-&(a[5]);//值为5
&(a[10])>&(a[5]);//的值为TRUE
int (*pa)[4];//指向4个整型量构成的数组的指针变量
//若有int a[2][4],则可以pa=a;
char (*next)[16];//指向16个字符构成的数组的指针变量
//若有char n[5][16],则可以next=n;
int *p[3]; char *name[]=... ; //注意int (*p)[3]int *p[3]的区别
int a[10][10];
//a[i]为指向10个元素的一维数组的指针
int b[3][4]={{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
数组取地址时,&表示指向该类型数据初始地址的指针
多维数组的指针进行赋值时,应特别注意类型的匹配
pa 将以自己的类型(指向包含两个整型数的数组的指针)去理解 b c 数组中的数据
指针与字符串
符串可以视为一个字符序列
字符数组存放在程序活动的栈中,可以修改
字符串常数存放在常量区,不可以修改。
可以定义一个字符指针变量,让其指向一个字符串常量
实际上就是把字符串常量在内存中的首地址赋给该指针变量
其地位等价于指向一维字符数组第一个元素的指针
注意这个指针指向常量区,指针值可以改,指针指向的内容不可以改
可以用字符串常量对字符指针变量和字符数组初始化
可以用字符串常量对字符指针变量赋值
不可以用字符串常量对数组名赋值(试图修改数组名常量)
只能逐个元素地对数组赋值(for循环等方法)
当字符指针变量指向一个字符串(不需要是首字符)后,可以用 数组下标的形式来引用该字符串中的字符
int a[3][4];
aa+i//指向4个整型数构成的数组的指针
a[i],a[i]+j //指向整型数的指针,即 *(a+i)*(a+i)+j
a[i][j]//整型数,即 *(*(a+i)+j) *(*a+i*4+j)
int a[3][4];
&a[i][j]//指向整型数的指针,值为 *(a+i)+j a[i]+j
&a[i]//指向4个整型数构成的数组的指针,值为 a+i
&a//指向二维数组的指针,值为 a
int a[2][2]={{1,2},{3,4}};
int b[3][3]={2,3,4,5,6,7,8,9,10};
int c[4]={12,13,14,15} ;
int (*pa)[2];
pa=a; //通过编译
pa=b; //警告但通过编译
pa=c; //警告但通过编译
char *p="abcde";
char *pstr1= "Hello", str1[40]= "Okay";
char *pstr2, str2[40];
pstr2= "world";
str2= “world”; //错误!
函数指针
函数代码存储在一片连续的内存单元中(代码区),其首地址就是函数的入口地址,函数名则代表该地址
主函数在调用子函数时,让程序转移到函数的入口地址去执行
程序也可以自定义指针,指向函数的入口地址,即指向函数,以此实现函数调用
定义形式: 类型 (*指针变量名)(参数表);
其中类型即函数返回值类型,参数表即函数形参
和普通指针一样,指向函数的指针也必须先初始化(指向某个函数),然后才能使用
注意,指向函数的返回值和形参必须与指针变量中的定义相一致
C语言规定,函数定义是不能嵌套的,整个函数也不能作为参数在函数间进行传送,如果需要进行传递,就必须使用指向函数的指针
变量作为参数
动态分配空间
单个元素
多个元素
分配空间后,可以用 realloc 调整大小
char *pstr= "Hello, world!";
char str[40];
int i;
for(i=0; *(pstr+i)!='\0'; i++) str[i]=pstr[i];
str[i]= '\0'; //此句不能少
//注意,*(pstr+i)等价于pstr[i],即访问字符串中第i个字符
float (*func)(int x, char y, float z);//注意与 float *func(int x, char y, float z);的区别
double square(double x); //函数声明
double (*p)(double x); //函数型指针声明
p=square; //指针初始化
s=p(1.6); s=(*p)(1.6) //函数调用
#include <stdlib.h>
void *malloc(unsigned int size);
//分配成功返回指向空类型void的指针,失败返回NULL
int *p; p=(int *) malloc(n*sizeof(int));
//常用调用方法(类型转换、大小计算)
#include <stdlib.h>
void *calloc(size_t nmemb, size_t size);
//nmemb个元素分配内存空间,其中每个元素的长度都是size个字节
//分配成功返回指向空类型void的指针,并用0初始化空间;失败返回NULL
释放空间
结构体中的可变长数组
动态数组放在结构体末尾
建立一维数组
建立二维数组
#include <stdlib.h>
void *realloc(void *ptr, size_t size);
//ptr指向的动态分配内存重新分配为大小为size个字节的空间。新尺寸可能会大于或小于原有尺寸
//分配成功返回指向空类型void的指针,失败返回NULL
//ptr必须是通过malloccalloc获得指针
#include <stdlib.h>
void free(void *pb);
free(p);//free只能释放malloc分配的内存
typedef struct
{
int a;
char buf[0]; // 或者char buf[];
}Node;
......
Node *p = (Node *)malloc(sizeof(Node) + 16);
......
...... p->buf[5] ......
......
free(p);
int *pb;
if ((pb=(int *)malloc(n*sizeof(int)))==NULL)
{
printf(内存不足\n”);
exit(1);
}
int (*pb)[10] = (int (*)[10]) malloc(5*10*sizeof(int));