8.4.2 字符指针做函数参数
1)用字符数组名作为函数的参数;
/*用函数调用实现字符串的复制*/ #include <stdio.h> int main() {void copy_string(char from[],char to[]); char a[]="i am a teacher ."; char b[]="you are a student."; printf("string a=%s\nstring b=%s\n",a,b); printf("copy string a to string b:\n"); copy_string(a,b); printf("\nstring a=%s\nstring b=%s\n",a,b); return 0; } void copy_string (char from[],char to[]) {int i=0; while(from[i]!='\0') {to[i]=from[i];i++;} to[i]='\0'; } / 用字符数组名做函数参数; 首先咱们看看这串代码,我们定义了一个函数,这个函数的功能是将字符串1和字符串2相互 之间互换,而咱们是怎么调用这个函数的。你看; void copy_string(char from[],char to[]);这是函数的形参,定义了一个字符型的 数组,而电脑在编译他的时候,把from[]转化成了*form,意思就是指向某一个地址; copy_string(a,b);这个函数定义的实参是a,b;此时实参向形参传递了一个地址; 此时形参里面的地址就是a提供的;而a作为实参是a数组数组名,他向形参传递的是 第一个元素的地址; 再看; {int i=0; while(from[i]!='\0') {to[i]=from[i];i++;} to[i]='\0'; } 这个定义的函数的函数体,在这个函数体里面,我使用到了from[i]这个i的基准就是 第一个元素提供给的地址;这样就链接起来了;
2)用字符型指针变量作实参
/*用函数调用实现字符串的复制*/ #include <stdio.h> int main() {void copy_string(char from[],char to[]); char a[]="i am a teacher ."; char b[]="you are a student."; char *from=a,*to=b; printf("string a=%s\nstring b=%s\n",a,b); printf("copy string a to string b:\n"); copy_string(from,to); printf("\nstring a=%s\nstring b=%s\n",a,b); return 0; } void copy_string (char from[],char to[]) {int i=0; while(from[i]!='\0') {to[i]=from[i];i++;} to[i]='\0'; } / 在这个函数里面我与上面的不同唯有定义了, char *from=a,*to=b; 我定义了两个指针变量;然后就是我不同数组的第一个元素的地址赋值 给了指针变量,辞职from 也代表数组a的第一个元素的地址;
3)用字符指针变量作形参和实参
/*用函数调用实现字符串的复制*/ #include <stdio.h> int main() {void copy_string(char from[],char to[]); char *a="i am a teacher ."; char b[]="you are a student."; char *p=b; printf("string a=%s\nstring b=%s\n",a,b); printf("copy string a to string b:\n"); copy_string(a,p); printf("\nstring a=%s\nstring b=%s\n",a,b); return 0; } void copy_string (char *from,char *to) { for(;*from!='\0';from++,to++) {*to=*from;} *to='\0'; } 与之前一样;*from=form[],一个意思;
所以总结下来就有一下几种类型;多余的不阐述;
8.4.3 使用字符指针变量和字符数组的比较
1)字符数组每一个储存空间存放的都是字符元素;而字符指针变量只能储存一个元素,而且只能储存地址;
char a[]; a[]="i am a student!";这个意思是把每一个元素都赋值再给数组; char *a; *a="i am a student!";这个意思是电脑先定义一个数组,然后把数组中的第一个元素的 地址给这个变量;
2)可以对指针变量名去赋值,但是不能对数组名去赋值;
char *a; a="i am a student!";这个意思是电脑先定义一个数组,然后把数组中的第一个元素的 地址给这个变量; char a[]; a="i am a student!";这是不合法的,数组名是不适合赋值的;
3)初始化的含义不同;
char a[]="i am a student!";这句话的意思是把元素里面的元素一个个赋值; char *a="i am a student!";这句话的意思是把该数组的第一个元素的 地址赋值给这个指针变量
4)储存单元分配不同
对于指针的储存单元分配一个基类型字节; 但是对于数组而言他是分配若干基类型字节,看数组元素的个数; 对于指针变量,我电脑会随机分配一个储存单元,然后如果你没有对指针变量进行初始化 那么他不代表里面就没有地址,这个地址是随机的,所以说当你使用这个随机地址的时候,会 出现警告,造成非常严重的错误;
5)指针变量的值是可以改变的,而数组名代表的是一个固定的地址数值(数组首元素的地址);
6)引用数组元素
对于数组本身而言,引用数组元素的方法一般就是a[1] 而a[1]电脑是无法识别的,它会被编译成*(a+1) 所以说a[1]=*(a+1)均可以; 同理,如果定义了字符指针变量P,并且使他指向数组a的首元素,则可以用指针 变量带下标的形式引用数组元素p[5],同样,可以用地址法引用数组元素*(p+5) 即p[5]=*(p+5)
7)字符数组中各个元素的数值是可以改变的,但字符指针变量指向的字符串常量中的内容是不可以被取代的(不能对他们再一次赋值);
char a[]={"i love cina!"}; char *b="house"; a[2]='r'; b[2]='r';//非法,字符串常量不能被改变; 首先得解释一下什么是b[2],b[2]相当于 *(b+2); 为啥'r'不能赋值给b[2];
8)用指针变量指向一个格式字符串,可以用它代替printf函数的格式字符串
format="a=%d,b=%f\n"; printf(format,a,b); 或者用字符数组也可以 cahr format[]="a=%d,b=%f\n"; printf(format,a,b); 看你怎么解读printf ;printf是一个函数 那么这个就是直接调用;不同逗号都是函数实参; 但是format是地址,是地址在这儿,那么说明原本"........."也只是提供一个地址; 电脑编译时王往函数体内送的只是一个地址。
8.5指向函数的指针
8.5.1 什么是函数指针
在程序中定义了一个函数,电脑编译的时候,电脑会给函数定义一个储存空间,而这个储存空间的起始地址,就是这个函数的指针。
可以定义一个指针变量,用来存放某一个函数的起始地址,这意味着此时指针变量通过该地址指向该函数。
8.5.2 用函数指针变量调用函数
举个例子。
通过函数名调用
/*用函数求整数a和b之间的大者*/ /*通过函数名调用*/ #include <stdio.h> int main() {int max(int ,int ); int a,b,c; printf("please enter a and b\n"); scanf("%d,%d",&a,&b); c=max(a,b); printf("a=%d\nb=%d\nmax=%d\n",a,b,c); return 0; } int max(int x,int y) {int z; if(x>y) z=x; else z=y; return(z); }
通过指针变量引用函数
#include <stdio.h> int main() {int max(int ,int ); int (*p)(int,int); int a,b,c; p=max; printf("please enter a and b\n"); scanf("%d,%d",&a,&b); c=(*p)(a,b); printf("a=%d\nb=%d\nmax=%d\n",a,b,c); return 0; } int max(int x,int y) {int z; if(x>y) z=x; else z=y; return(z); }
8.5.3 怎样定义和使用指向函数的指针变量
通过上面的例子,咱们学会如何定义一个指针变量指向函数。
类型名(指针变量名)(函数形式参数系列)
比如
int(*p)(int,int);
系列之间用逗号隔开;类型名是指函数返回值的类型;括号内是形参的类型;一旦定义了这类型,说明该指针变量只能被该类型的函数赋值;这里解释一下为什么要加括号,首先就是括号有优先级*p必须要有括号,否则就是
int *p(int,int);
定义了一个指针形式函数;p是指针变量名;也是指针变量;可以用来储存地址;只能够储存一个地址;储存空间有限
p=max;
max的不仅仅是函数名,还代表该函数的入口地址;还有就是怎么调用函数的时候怎么使用指针变量,
c=(*p)(b,c);
首先括号是要加的,不加更与优先级把指针函数的返回值赋值给c;这句话的意思是指向p内地址的函数,把实参b,c带入函数体;然后把返回值赋值给c;
通过以上咱们可以总结出用指针变量调用函数的时候,指针变量可以调用不同函数,与前面的数组指针变量相比,函数指针指针变量没有运算这一回事;没有意义;
/*输入两个整数a,b,然后让用户选则1和2,选1时候调用max函数,输 出两者之间较大的数值, 选择2得时候调用min函数,输出两者之间较小的数值*/ #include <stdio.h> int main() {int max(int x,int y); int min(int x,int y); int (*p)(int,int); int a,b,c,n; p=max; printf("please enter a and b\n"); scanf("%d,%d",&a,&b); printf("please choose 1 or 2:"); scanf("%d",&n); if(n==1) { p=max; c=(*p)(a,b); } else {p=min; c=(*p)(a,b); } printf("a=%d\nb=%d\nmax=%d\n",a,b,c); return 0; } int max(int x,int y) {int z; if(x>y) z=x; else z=y; return(z); } int min(int x,int y) {int z; if(x<y) z=x; else z=y; return(z); }
8.5.4 用指向函数的指针作函数变量
指向函数的指针变量的一个重要用途是把函数的地址作为函数参数传递到其他参数。也就是说,实参是地址,而形参是指向函数的指针变量。实参把地址通过1值传递传递给形参,这样两个指针变量就临时储存了两个函数的入口地址,这样就等价于两个指针变量指向了两个函数。比如
c=fun(f1,f2);//f1,f2也是两个函数的地址。 void fun (int(*x1)(int),int(*x2)(int,int)); {int a,b,i=3;j=5; a=(*x1)(i); b=(*x2)(i,j); }
看个例题
/*有俩个整数a,b,由用户输入1,2或者3.如输入1, 程序就会给出a,b中间较大者,输入2, 就会给出a,b之间较小者,输入3,则会给出两者之和*/ #include <stdio.h> int main() {int fun(int x,int y,int(*p)(int,int)); int max(int,int); int min(int,int); int add(int,int); int a=34,b=-21,n; printf("please choose 1,2,3:"); scanf("%d",&n); if(n==1) fun(a,b,max); else if(n==2) fun(a,b,min); else if(n==3) fun(a,b,add); return 0; } int fun(int x,int y,int(*p)(int,int)) {int result; result=(*p)(x,y); printf("%d\n",result); } int max(int x,int y) {int z; if(x>y) z=x; else z=y; printf("max="); return z; } int min(int x,int y) {int z; if(x<y) z=x; else z=y; printf("min="); return z; } int add(int x,int y) {int z; z=x+y; printf("sum="); return z; }
8.6 返回指针值的函数
咱们学了函数与指针,那么咱们有没有一种可能就是把函数与指针联系起来就是,有一个返回指针值的函数。所谓指针值就是地址。返回一个地址。所以能返回指针值得函数称之为指针函数
那怎么定义指针函数:
类型名 *函数名(参数表列);
注意在这里面函数名就是函数的名字,他的左右是没有括号的;但在参数表列里面有一个括号,括号有优先级别所以说,电脑就直接能判断名字所代表的是函数名了;那么在前面再加一个*意思就是指针函数。
int (*p)(int x,int y);//意思是定义了一个指向函数的指针变量; int *p(int x,int y);//定义了一个能返回指针数值的指针变量;
/*有a个学生,每个学生有b门课的成绩。要求在用户输入学生序号以后,能输出 该学生的全部成绩*/ #include <stdio.h> int main () {float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}}; float *search(float(*pointer)[4],int n);//这段比较难理解,形参1的意思是定义了一个 //含有四个元素的一维数组。而这个pointer指向这个一维数组整个整体的地址。 float *p;//定义的是一个指向某变量的指针。 int i,k; printf("enter the number of student:"); scanf("%d",&k); printf("the scores of NO.%d are :\n",k); p=search(score,k); for(i=0;i<4;i++) printf("%5.2f\t",*(p+i)); printf("\n"); return 0; } float *search(float (*pointer)[4],int n) { float *pt; pt=*(pointer+n);//pointer+n的意思是同样第二行....第n行的整体开头的地址。 return(pt); }
区分这三者之间的区别;
float *p;定义了一个指针变量;指向任意float型变量; float (*pointer)[4];定义了一个指针变量,指向含有四个元素的一维数组; float *search(float(*pointer)[4],int n); 定义了一个指针函数 ,能返回指针值; float (*score)(int x,int y); 定义了一个指针变量,指向了函数;
这段话的意思是我定义了一个二维数组,我现在把二维数组的第一行的地址给函数,函数把行地址转化为列地址然后返回出来,然后此时地址是具体某一个数得地址在前面再加*就是输出该数值;
我们把这个例子改一改;
/*有a个学生,每个学生有b门课的成绩。要求在用户输入学生序号以后 ,找出其中不合格的课程的学生及其学生号码*/ #include <stdio.h> int main () { float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}}; float *search(float(*pointer)[4]); float *p; int i,j; for(i=0;i<3;i++) {p=search(score+i); if(p==*(score+i)) {printf("No.%d score:",i); for(j=0;j<4;j++) printf("%5.2f",*(p+j)); printf("\n"); } } return 0; } float *search(float (*pointer)[4]) { int i=0; float *pt; pt=NULL; for(;i<4;i++) if(*(*pointer+i)<60)pt=*pointer; return (pt); }
8.7指针数组和多维数组
8.7.1什么是指针数组
什么是指针数组,就是说一个数组有若干个元素,若干个储存空间,这些个元素都是一个个储存地址,所以说每一个元素都是指针变量。具有指向的功能。那么指针变量有什么用?加入有若干个字符串。很多,你想给这些字符串进行排序,那么你可以定义一个指针数组,只要改变指针数组的地址,这样输出的时候就能改变字符串的顺序。如果说按照传统方法,你使用是会定义一个二维数组,初始化。首先定义二维数组你肯定得选最长的列数作为二维数组的列数。这样会浪费很多的储存单元,再者说如果你想调换它们之间的顺序的话。你得重新初始化一下,这样会很麻烦,如果使用指针变量,让每个字符串对应一个指针变量,然后这些指针变量组成一个指针数组,如果能达到同样的效果,如果说你想调换它们之间的顺序,你可以调换指针数组之间的地址就可以了。就会很方便。
说来这么多,我们来看一看怎么定义一个数组指针。
类型名 *数组名[数组长度];
比如
int *p[4];这句话的意思是定义了一个指针数组,包含四个指针变量; int (*p)[4];定义了一个指针变量,指向一个包含四个元素的一维数组;
*p有无括号很重要,如果没加括号,像第一个,电脑会首先识别括号[]内的内容,他的优先级大。这样电脑就会知道p是一个数组,又有指针型符号,所以是指针型数组。括号的顺序是自左向右;对于第二个,所以说先识别(*p),电脑知道了,他是一个指针变量。然后是什么指针变量呢?后面告诉你了,是一个一维数组,包含四个元素的指针变量。
咱们看一个例子;
/*将若干字符串按照字母顺序输出(由小到大)输出*/ #include <stdio.h> #include <string.h> int main() { void sort(char *name[],int n); void print(char *name[],int n); char *name[]={"Follow me","BASIC","great wall","FORTRAN","Computer design"}; int n=5; sort(name,n); print(name,n); return 0; } void sort(char *name[],int n) { char *temp; int i,j,k; for(i=0;i<n-1;i++) { k=i; for(j=i+1;j<n;j++) if(strcmp(name[k],name[j])>0) k=j; //对于strcmp函数字符串比较函数,如果大于,函数值为正值,如果是小于,则函数值为负数; // 如果是等于,则函数值是0;具体看数组字符串函数那儿; if(k!=i) { temp=name[i];name[i]=name[k];name[k]=temp; // 指向互换 } } } void print(char *name[],int n) { int i=0; char *p; p=name[0]; while(i<n) { p=*(name+i++); //这句话的意思是先执行*(name+i), // 所以说就是先name+i的意思是第二个元素的地址,*(name+i)的意思是对应的某个字符串的开头地址。然后赋值给p,再i++; printf("%s\n",p); } } //换一种写法print void print (char *name[],int n) { int i; for(i=0;i<n;i++) printf("%s\n",name[i]); }
8.7.2指向指针数据的指针
前面咱们学了,指针变量的指向,可以有普通变量,数组,函数。这儿咱们说指针还可以指向指针自己。上面咱们说对于一个指针数组而言,里面各个元素都储存的是地址,每一个单独的单元都是一个单独的指针。
name[0];-->a1 name[1];-->a2 name[2];-->a3 ...
所以说这三个都是指针,那么我能不能再定义一个指针p,就是指向他们。
int *a; int a1; a=&a1;那么说a指向a1里面储存的数值 3or4or5; 指向某一整数型数据的指针变量; char *(*p); char *name[4]; p=name;那么说p指向name储存的数值,&1or&2or&3;称为指向指针的指针变量; char *p char name[4]; p=name;那么说指针变量指向的是name中储存的数值,'q' or 'w' or 'e'; 称为指向数组的指针变量; int *sum(int x,int y); 定义一个函数,能返回函数值是指针值; int (*sum)(int x,int y);定义一个指针变量指向函数,比如(*sum)(1,2);储存返回指针值的变量;
所以说所有指针类型都是储存地址,但指向不同,它指向是指针。就是说指向的储存单元里面的储存内容还是地址;这就是指向指针的指针变量;
那么我们怎么定义一个指针变量指向指针:
类型名 *(*指针名)
比如,
char *(*p);
先识别括号内(*p),是一个指针变量,这个指针变量指向指针变量,然后就是给与了char *,就是说p指向的是char型数据。
char *p; char *(*p); p;指向的是char型数据的指针变量; *p;指向的是p这个指针变量;
注意
char **p;与char *(*p);等价;
看个例子:
/*使用指向指针数据的指针变量*/ #include <stdio.h> int main() { char *name[]={"FOLLOW ME","BASIC","GREAT WALL","FORTRAN","COMPUTER"}; char **p; int i; for(i=0;i<5;i++) { p=name+i; printf("%s\n",*p); } return 0; }
/*有一个指针数组,其元素分别指向一个整型数组的元素,用指向指针数据的指针变量,输出整形数组的个元素的数值。*/ #include <stdio.h> int main() { int a[5]={1,3,5,7,9}; int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]}; int **p,i; p=num; for(i=0;i<5;i++) { printf("%d\n",**p); p++; } printf("\n"); return 0; }
指针是属于间接访问;那么指针指向指针属于二级间接访问,那么同理说有二级就有三级,四级等多级。不过越多越麻烦好理解,也容易出错。如下图。
8.7.3指针数组作main函数的形参
一般main函数括号内都是空的,里面没有形参,也就是说操作系统不需要给主函数提供实参。
通常main函数与其他函数构成一个文件模块,具有一个文件名字。在编译和连接之后,得到一个exe的可执行文件。然后用户执行这个exe,操作系统就会调用main函数,然后main函数就会调用其他函数,从而得到相应的结果。
int main(); int main(void);
但是有的时候,main函数也会具有形式参数,比如。
int main(int argc,char *argv[]);
前者argv的意思是参数个数,后者是指针数组。里面每一个指针元素指向一个字符串。
main函数是操作系统调用的,所以说main函数是操作系统提供实参的。
具体看看UNIX系统怎么提供参数的。
首先UNIX系统在exe中如何输入命令行:
命令名 参数1 参数2 参数3(中间按空格隔开)
命令文件名必须要有,而且占据指针数组第一个储存空间,但是它不属于传送到主函数的参数。所以说参数有三个,其中传递到函数中的参数只有两个。但是参数又三个,指针数组中的数值也有三个。 如下图。
int main(int argc,char *argv[]) { while(argc>1)//argc=3 { ++argv;//argv指向的是数组第二元素的地址 printf("%s\n",argv); --argc; } return 0; } 这是一个在UNIX系统里面执行的程序。 exe文件框中输入 ===================================== file1 China Beijing ===================================== 最后输出: ============ China Beijing ============ 程序改写: int main(int argc,char *argv[]) { while (argv-->1) { printf("%s\n",*++argv); } }
echo 命令:即将echo后面的各个参数在同一行输出。
#include <stdio.h> int main(int argc,char *argv[]) { while(--argc>0) printf("%s%c",*++argv,(argc>1)?'':'\n'); return 0; } exe 执行文件显示框输入: ================== echo Computer and c language ================== 显示屏输出 ============ Computer and language ============ 程序可以改写: #include <stdio.h> int main(int argc,char *argv[]) { int i; for(i=1;i<argc;i++) printf("%s%c",argv[i],(i<argc-1)?'':'\n'); return 0; }
8.8 动态内存分配与指针变量
8.8.1什么是内存的动态分配
前面咱们学过了全局变量还有局部变量,电脑有动态储存区,也有静态储存区域;
全局变量放置在静态储存区域,局部变量储存在动态储存区;比如函数,在定义形参的时候,建立一个储存区域,然后函数执行结束的时候,区域自行解散。
这里我们强调一个动态分配区域这是一个非常自由的区域,想用的时候就用,想解散的时候就自行解散。可以说在静态储存区的是要事先声明的,但是再动态分配区域是不需要事先声明的,直接用就完了。
所以说它不能通过变量名字或者数组名字去引用这些数据,只能通过指针来使用
这里推出两个概念:
C/c++程序经过编译连接后形成的二进制映像文件,这文件包含:
栈,堆,数据段(只读数据段,已经初始化读写数据段,未初始化数据段即BBS)和代码段组成.
1.栈区(stack):
由编译器自动分配释放,存放函数的参数值,局部变量等值。其操作方式类似于数据结构中的栈。
2.堆区(heap):
堆允许程序在运行时动态地申请某个大小的内存。
一般由程序员分配释放,若程序员不释放,则可能会引起内存泄漏。
注堆和数据结构中的堆栈不一样,其类是与链表。
3.程序代码区:存放函数体的二进制代码。
所有的语句编译后会生成CPU指令存储在代码区.
4.数据段:由三部分组成。
栈:动态储存区域
堆:动态分配区域
8.8.2怎样建立内存的动态分配
提供这些的动态储存空间的方法,一般是malloc,calloc,free,realloc;调用这些函数要使用头文件+#include ;
1.malloc
函数原型:void *malloc(unsigned int size); 这个定义的是一个指针函数,void 的意思是返回的地址所指向的void. 就是说无任何类型。 返回的是所分配区域的第一个字节的地址,是一个抽象的概念。 其中形参是一个无符号整数。size就是你需要的常数 调用:malloc(100); 如果不能成功的执行,比如储存空间不足,那么返回空指针NULL example : L.data =(ElemType*)malloc(sizeof(ElemType)*InitSize);//动态扩展内存。
2.calloc
函数原型: void *calloc(unsigned n,unsigned size); 这个定义是一个指针函数,void的意思是返回一个无任何类型的地址。 返回的地址是所分配区域的第一个字节。是一个抽象的概念。 意思是分配n个长度为size的连续空间。这个空间一般比较大,可以为一个数组。 调用:p=calloc(50,4); 分配不成功,返回NULL;
3.free
函数原型: void free(void *p); 释放指针变量p所指向的动态空间。p是最近一次调用calloc,malloc的返回值。 free函数无返回值。 调用:free(p);
4.realloc
void *realloc(void *p,unsigned int size); 如果你已经使用了colloc malloc 函数获得了动态空间,想改变其(size)。 可以使用realloc函数重新分配。 调用:realloc(p,50); //将p所指向动态储存空间的长度改为50个字节。 同样返回一个地址,所分配字节区域第一个地址。如果分配不成功,返回NULL
C99以前是没有void 型的,其mailloc ,colloc 一般都是char 型 ,如果想建议一个动态的整型需要强制类型转换;
char *malloc(unsigned int size); pt=(int *)malloc(100);
8.8.3void的指针类型
定义一个void 类型的指针
void *p1;
这个指针意思是不指向任何值的。是一个空的类型;
p1=(void *)&a; 也可以写 p1=&a;编译系统会自动转换。
p1具有a的地址,但是不具备指向a中的数值的能力。也就是说不具备指向功能。
/*建立一个动态数组,输入五个学生的成绩,另外用一个函数,检查其中有没有低于60,输出不及格的成绩*/ #include <stdio.h> #include <stdlib.h> int main() { void check(int *); int *p1,i; p1=(int *)malloc(5*sizeof(int)); for (i=0;i<5;i++); scanf("%d",p1+i); check (p1); return 0; } void check(int *p) { int i; printf("they are fail:"); for(i=0;i<5;i++) if(p[i]<60)printf("%d",p[i]); printf("\n"); }