内存单元的编号叫做地址,也叫做指针。指针可以直接访问计算机内存,开发底层的程序,是的C这种高级语言也能够完成低级语言的工作。灵活运用指针,可以编写出简介、高校、紧凑的程序,可以提高程序的运行速度,降低程序的存储空间,也可以有效的表示和实现复杂的数据结构。
指针变量
变量地址和变量的值:
计算机为存储空间的每一个字节(8个二进制单位)分配一个编号,通常叫做“编址”,这个编号就是我们常说的内存地址(简称地址)。编址时保证内存中的每一个字节都有独一无二的地址号。
指针变量的定义和访问:
定义:
指针变量用于保存地址值的变量。
语法:基类型标识符 *指针变量名1【,*指针变量名2,......*指针变量n】;
int *q,*p
- 基类型标识符代表该指针的变量可以指向的变量的类型,即该指针变量中存储的地址值所对应的空间中的类型,对应空间中只能存在这种类型的数据;
- “*”是定义指针变量时的说明符,“基类型标识符”标识指针类型。q、p都是int型,说明都用于保存int类型变量的地址值的指针变量。
指针变量的初始化和赋值:
指针变量定义之后应该给予一个合法值,避免变成随机值的指针变量。
直接访问和间接访问:
直接访问:
如count++,把count对应的内存单元的值+1(直接引用、直接寻址)。
间接访问:
非法指针:
指针变量的运算:
“&”和“*”两个运算符都是单目运算符,优先级相同,结合方向都是自左而右。
“&”----取地址运算符。其操作数是变量,包括普通变量和指针变量,得到的是变量的地址。
“*”-----间接引用运算符(指针运算符),其操作数必须是地址,大部分情况是指指针变量,得到该地址对应空间中存放的内容。
“*”运算符的三种应用场合:
doubel area;
int x=10;
int *p=&x; //定义指针变量p时的一个说明符,而不是一个运算符
(*p)++; //间接引用运算符,单目运算符
area=3。14*x*x; //作为乘法运算符,双目运算符
算数运算:
指针可以参与算数运算,但具有一定的特殊性。
- 指针变量适用于存储地址的变量,两个指针变量即使基类型相同,也不能进行相加的操作,因为两个地址相加的结果没有任何意义。
- 指针变量能进行自加、自减或加减一个整数的运算,但不同于普通变量的增减,指针变量的增减是以指针变量的基类型所占字节大小为单位的,即减1地址变化是一个基类型所占存储空间的字节数。
关系运算符:
指针变量参与的运算可用于比较地址变量的大小,如果指针p存储的地址值小于指针q的地址值,那么关系时表达p<q的值为1.
input *p;
.........
if(p=NULL) //判断指针是否没有指向
.........
NULL作为特殊的值来表示指针是没有指向的,在某些特定的应用中,比如单向链表会利用这个值表示链表的结束,访问时就需要判断指针的值是否是NULL。
指针与数组
指针与一维数组
//对已知数组输出数组中个元素的地址,并求出所有元素的平均值 #include<stdio.h> int main() { double score[5]={90.5,91.0,92.0,93.5,94.0}; int i; double sum=0.0; printf("The address of the array:%10p\n",score); printf("The address and value of each element:\n"); for(i=0;i<5;i++) printf("score[%d]:\t%p\t%5.2f\n",i,&score[i],score[i]); for(i=0;i<5;i++) sum+=*(score+i); printf("the average of score is:%5.2f\n",sum/5); return 0; }
指针与二维数组
//用一级指针变量访问二维数组元素 #include<stdio.h> int main() { int a[3][2]={1,2,3,4,5,6}; //数组元素初始化 int i; int *p; //定义指针变量 p=&a[0][0]; //用变量地址给指针赋值 for(i=0;i<6;i++) { printf("%p\t%d\n",p+i,*(p+i)); } return 0; }
//用行指针变量访问二维数组元素 #include<stdio.h> int main() { int a[3][2]={1,2,3,4,5,6}; int i,j; int (*p)[2]; p=a; for(i=0;i<3;i++) { for(j=0;j<2;j++) printf("%p\t%d\n",p[i]+j,p[i][j]); //通过行指针访问 } return 0; }
//针与数组 #include<stdio.h> int main() { int a[3][2]={1,2,3,4,5,6}; //数组元素初始化 int i,j; int *p[3]; //定义长度为3的指针数组 for(i=0;i<3;i++) { p[i]=a[i]; for(j=0;j<2;j++) printf("%p\t%d\n",p[i]+j,*(p[i]+j)); } return 0; }
指针与函数
传值与传地址
函数调用过程中,数据从实参传递到形参,是把实参的值单向复制到形参中。
地址调用:实参给形参传递的时地址值
否则为值调用
指针作为形参返回多个值 #include<stdio.h> void caculate(int x,int y,int *sum,int *diff) { *sum=x+y; *diff=x-y; } int main() { int a=3,b=4; int sum,diff; caculate(a,b,&sum,&diff); printf("%d与%d的和是:%d\n",a,b,sum); printf("%d与%d的差是:%d\n",a,b,diff); return 0; }
返回指针的函数
在C语言中允许一个函数的返回值是一个指针(即地址值),这种返回指针值的函数称为指针(型)函数。
类型名 * 函数名 (参数表);
int * smaller(int *x,int *y);
#include<stdio.h> int *smaller(int *x,int *y); int main() { int a,b,*s; printf("Enterc two integer values:\n"); scanf("%d%d",&a,&b); s=smaller(&a,&b); printf("The smaller value is %d.\n",*s); return 0; } int *smaller(int *x,int *y) { if(*y<*x) return y; return x; }
//批量数据的筛查 #include<stdio.h> #include<math.h> #define NUM 15 #define T 5 void Find(double *p,int n) { double average,sum=0; int i,count=0; for(i=0;i<n;i++) sum+=*(p+i); /*(p+i)可以表示为p[i] average=sum/n; for(i=0;i<n;i++) { if(fabs(*(p+i)-average)>T) { printf("%5.2f\t",*(p+i)); count++; } } printf("\n The number of abnormal data is;%d\n",count); } int main() { double array[NUM]={17.5,20.1,23.1,15,17,26,30,12,18.2,19.6,10,16.7,17.7,16.5,20}; Find(array,NUM); //将数组的地址传递给形参指针p return 0; }
//进制转换使用数组访问 #include<stdio.h> int main() { int r[16]; int i, j; int m; do { do { printf("Input an integer which belong to 0~65535:"); scanf("%d", &m); } while (m < 0 || m > 65535); s = 0; // 初始化s为0 while (m != 0) { r[i] = m % 2; m = m / 2; i++; // 每次循环后,s自增1 } for (j = i - 1; j >= 0; j--) { // 从最高位开始输出 printf("%d", r[j]); } printf("\n"); } while (m == 0); return 0; }
//进制转换只用指针 #include<stdio.h> int main() { int r[16]; int *p=r; int m; do{ do { printf("Input an integer which belong to 0~65535:"); scanf("%d",&m); } while (m<0||m>65535); while (m!=0) { *p=m%2; m=m/2; p++; } printf("The binary is:"); p--; for( ;p>r;p--) printf("%d",*p); printf("\n"); }while(m==0); return 0; }
//选择法排序 #include<stdio.h> void Input(int *pa,int n) //接受实参传递值 { int i; printf("Please input %d elements:\n",n); for(i=0;i<n;i++) scanf("%d",pa+i); //pa+i等同于pa[i],将读入元素依照for循环存入数组中 } void sort(int *pa,int n) //接受参数 { int index,i,k,temp; for(k=0;k<n-1;k++) //k控制排序趟数,以0~2表示所有趟 { index=k; //本趟最小位置存与index,开始时为k for(i=k+1;i<n;i++) //通过内循环找出本躺最小元素的下标给index if(pa[i]<pa[index]) //将本趟最小元素的下标赋给index index=1; if(index!=k) //如果本趟最小元素没有到位 { temp=pa[index]; //则通过交换方式使本趟最小元素到k下标处 pa[index]=pa[k]; pa[k]=temp; } } } void Output(const int *pa,int n) { int i; for(i=0;i<n;i++) printf("%5d",*(pa+i)); printf("\n"); } int main() { int a[10],n; //定义数组长度为10 do{ printf("Please input n(1<=n<=10):"); scanf("%d",&n); }while(n<1||n>10); //输入数组范围 Input(a,n); //调用输Input函数,读入数组元素,将实参a和n传递给函数 printf("The oaiginal array is :\n"); Output(a,n); //调用Output函数,将实参传递给函数 sort(a,n); printf("The sorted array is:\n"); Output(a,n); return 0; }
//矩阵的计算 #include<stdio.h> #define ROW 3 #define COL 3 void Output(int (*pa)[COL],int row,int col) //接受a,ROW,COL参数值 { int i,j; for(i=0;i<row;i++) { for(j=0;j<col;j++) printf("%d\t",pa[i][j]); printf("\n"); } } int Sum(int (*pa)[COL],int row) { int i; int sum=0; for(i=0;i<row;i++) sum+=pa[i][i]; return sum; } int main() { int a[ROW][COL]={{5,6,7},{10,11,12},{8,9,10}}; //初始化矩阵数据 Output(a,ROW,COL); //调用Output函数进行矩阵输出,用指针a初始化行指针变量pa printf("\nTHe sum of diagonal is:%d\n",Sum(a,ROW)); //调用sum函数进行对角计算 return 0; }
针针进阶
const与指针结合
常指针:表示指针指在经过初始化后不允许修改的指针
基类型名 *const 指针名=地址值;
int a=10,b=20;
int *const p=&a;
*p=20; //合法,等同于a=20
p=&b; //非法,试图改变p的值,指向另外一个变量
//定义了常指针p,说明p只能用于读取而不能用于修改,因此定义时就必须初始化使其具有确切的地址值,此后就只能修改*p而不能修改p。
指向常量的指针:所指向的内容不允许通过该指针修改。
基类型名 const *指针名;或者const 基类型名*指针名
int a=10,b=20;
int const * p=&a;
*p=20; //非法,不能通过*p的方式改变a的值
p=&b; //合法,改变p的值,指向另外一个变量b
//const修饰指针名,表示指针指向的内容不允许通过指针修改,但是指针本身是变量,可以改变。在这种定义下,只是限定了不能通过指针修改它所指向空间中的内容,所以*p=20是非法的,但是可以通过直接引用方式对a的值作出修改,比如a=20,则是正确的。
指向常量的常指针:其指针本身以及指向的内容都允许被修改
const 基类型名 * const 指针名=地址值;
int a=10,b=20;
const int * const p=&a;
*p=20; //非法,不能用*p的方式改变a的值
p=&b; //非法,不能改变p的值,指向另外一个变量
//两个const分别表示指针以及指针指向的内容都是常量,定义时就必须初始化使指针具有确定的地址值。程序运行过程中,指针及指针所指向的内容都只能适用于读取,而不能被修改。
二级指针
指针作为一种变量,也有自己的值和地址。
int x=10;
int *p=&x; //p值代表&x的地址值
指针变量p的地址值可以定义一个二级指针进行存放。
类型标识符 **二级指针变量名;
int **c;
#include<stdio.h> int main() { int a=10; int *b=&a; int **c=&b; printf("a and b:\n"); printf("&a:%p\tb:%p\n",&a,b); printf("a=%d\t*b=%d\n",a,*b); printf("\nb and c:\n"); printf("&b:%p\tc:%p\n",&b,c); printf("b=%p\t*c=%p\n",b,*c); printf("\na and c:\n"); printf("a:%d\t**c:%d\n",a,**c); return 0; }
指针与动态空间
实际就是根据需要使用指针实现动态空间管理,避免资源空间的浪费。
- 动态空间管理函数
-
C语言的动态内存和分配和释放由函数实现。ANSIC标准定义了4个相关函数:malloc(),calloc(),free(),realloc()。在程序中根据需要调用这些函数完成动态空间的分配和释放,使用时需要包含头部文件<stdlib.h>。
- 申请动态内存空间
-
void *malloc(unsigned size);
或者
void *calloc(unsigned numElements,unsigned sizeOfElements);
函数malloc只有一个参数:size表示所需空间大小;
函数malloc有两个参数:元素个数和每个元素所占大小。
分配成功则返回都是起始地址,不成功则返回NULL(指针值为0),并终止程序。
语句:int *p=(int*) malloc(n*sizeof(int));
如果分配成功,指针p返回动态空间的首地址,p指向的数组空间中的n个元素值均为随机值,即p[0]到p[n-1]都是随机值。
语句:int *p=(int *) calloc(n,sizeof(int));
如果分配成功,指针p返回动态空间的首地址,p指向的数组空间的n个元素值均自动初始化为0.
- 释放动态空间
-
void free(void *p);
函数free的功能时释放p指向的动态空间,执行后,系统可以把这部分空间重新分配给其他变量或者进行使用。
- 改变动态空间大小
-
void *realloc(void *p,unsigned int newsize);
函数realloc功能是改变指针p指向空间的大小,变成newsize字节,返回的是重新分配期间的首地址,和原来分配的首地址不一定相同。!!!!!新的空间大小一定要大于原来的,否则可能导致数据丢失。
和动态空间分配相关的函数里都涉及了void类型指针,这种指针被称为通用指针(泛指针)。
他们可以指向任意类型的数据,亦即可用任意数据类型的指针对void指针赋值。
int a;
int *pi;
void *pvoid;
pvoid =pi;
如果要将pvoid赋给其他类型指针,则需要强制类型转换,如:pi=(int*)pvoid,而不能直接赋值方式,比如pi=pvoid就是错误的。
- 动态一维数组的应用
-
动态空间申请后,系统会分配若干连续空间,这段连续空间就可以作为一维数组来存放数据。
/*用筛选法球n以内的所有指数
定义一个长度为n+1的数组,将1至n与数组的下标对应,作为筛选对象。下标”指向“的组元素如果为0
表示该下标是质数,如果是1,则不是质数。
1、定义一恶搞长度为n+1的数组s,数组s相当于”筛子“,所有元素初始值为0;
2、令s[0]和s[1]的值为1,将0和1排除在质数之外
3、循环,下标i从2开始至n依次递增1进行筛选,只要某一个s[i]的值为0,则i一定是质数,这个i称为
“筛眼”。将i的所有倍数下标对应的数组元素值改为1(因为这下数能被i整除,肯定不是质数),一次类推至
循环结束。
*/
#include<stdio.h> #include<stdlib.h> int main() { int i,j,n; int *s; //定义指针s用来申请动态数组空间 do { printf("Please input n:"); scanf("%d",&n); } while (n<0); //保证读入一个正整数 s=(int*) calloc(n+1,sizeof(int)); //用s申请长度为n+1的动态一维数组 if(s==NULL) //判断是否申请失败 { printf("allocation failure"); exit(1); //终止程序,控制权交给操作系统 } s[0]=s[1]=1; //0和1不是质数,元素值修改为1 for(i=2;i<=n;i++) //从2到n筛选 if(s[i]==0) //i是质数,则s[i]的倍数都不是质数 for(j=2*i;j<n+1;j=j+i) //元素s[i]为筛眼的倍数,修改值为1表示不是质数 s[j]=1; for(i=0;i<=n;i++) if(!s[i]) printf("%5d",i); printf("\n"); free(s); //释放动态数组空间 return 0; }
/*在上面代码中,数组里面的元素在申请动态一维数组的时候会将所有的返回值初始化为0,
0和1排出在外之后,从2开始到n,当遇到2的倍数则将数组元素设置为1以此来进行质数筛选。
*/
- 动态二维数组的应用
-
要得到动态二维数组需要使用二级指针来实现。首先使用二级指针申请一维指针数组空间(第一维空间),指针数组长度就是动态二维数组的行数;一维指针数组的每一个元素都是一个一级指针变量,因此,接着使用这些一级指针变量分别再次申请一维数组空间(第二维空间)。其元素个数就是动态二维数组的列数。
申请ROW行COL列的动态二维数组的通用程序段如下:
int i;
type **p; //这是二级指针,语句格式为: 类型标识符 **二级指针变量名
p=(type **)malloc(ROW*sizeof(type *));
//动态分配指针数组的空间(第一维空间),长度为二维数组的行数
for(i=0;i<ROW;i++)
p[i]=(type *) malloc(COL*sizeof(type));
//动态分配一维数组空间(第二维空间),长度为二维数组的列数
/*其中type表示二维数组的元素类型,这里p是一个二级指针,指向一个包含ROW个元素的指针数组
并且每个元素指向一个有COL个元素的一维数组,这样就构建了一个ROW行COL列的动态二维数组*/
//以矩阵形式输出动态二维数组 #include<stdio.h> //用于标准输入输出工作 #include<stdlib.h> //包含一些函数,一级随机生成等 #include<time.h> //处理一些时间操作 int main() { int i,j,row,col; int **array; //定义二级指针变量,即指针的指针 printf("Input row and col\n"); scanf("%d%d",&row,&col); //读入变量 行与列 array=(int **)malloc(row*sizeof(int *)); //分配一维空间,长度为行数 for(i=0;i<row;i++) //利用每一个一直指针元素再申请动态 array[i]=(int *)malloc(col*sizeof(int)); //分配二维空间,长度为列数 srand(time(0)); //生成随机种子 for(i=0;i<row;i++) for(j=0;j<col;j++) array[i][j]=rand()%100; //调用随机函数为二维数组的元素赋值 printf("Maxtrix is:\n"); for(i=0;i<row;i++) //以矩阵的形式输出二维数组 { for(j=0;j<col;j++) printf("%6d",array[i][j]); printf("\n"); } for(i=0;i<row;i++) //先通过一维指针数组的每个针对元素 free(array[i]); //释放动态二维数组的空间 free(array); //再通过二级指针变量释放动态二维数组空间 return 0; }
指向函数的指针
C语言中规定,一个函数的代码总是占用一段连续的内存去,而函数名就是该函数代码所占内存区的首地址(入口地址)。可以将函数的首地址赋值给一个指针变量,使的该指针变量指向这个函数,而后通过指针变量就剋一找到并调用这个函数。这种指向函数的指针变量称为函数指针。
语法:
类型说明符 (*指针变量名)(形参表);
int (*pf1)( ); //表示pf1是一个指向函数的指针变量,该函数的返回值是整型
int (*pf2)(int x); //表示pf2是一个指向函数的指针变量,该函数的返回值是整型,且只有一个整型形参
!!注意:
1.类型说明符表示被指向函数的返回值类型
2.(*指针变量名)表示“*”后面的变量是函数指针变量,()表示这个指针是一个指向函数的指针
3.最后括号里面是形参表,形参表可以和普通函数一样有若干参数,也可以没有参数,仅表示一个括号。
#include<stdio.h> int larger(int x,int y); int smaller(int x,int y); int main() { int a,b; int (*pf) (int ,int ); //定义函数指针 printf("Enter two intefer values:\n"); scanf("%d %d",&a,&b); pf=larger; //pf指向函数larger printf("The larger value is %d.\n",(*pf) (a,b)); //相当于调用larger(a,b) pf=smaller; //pf指向函数smaller printf("The smaller value is %d.\n",(*pf) (a,b)); //相当于调用smaller(a,b) return 0; } int larger(int x,int y) //返回较大数 { if(y>x) return y; return x; } int smaller(int x,int y) //返回较小数 { if(y<x) return y; return x; }