1.1指针是什么
关于地址:
在程序中定义一个变量系统就会分配内存单元,根据变量类型去分配一定空间的长度。每一个字节都有一个编号,这就是“地址”。
通过地址能找到变量单元,所以我们说该地址指向该变量单元,因此形象地将地址称为指针。
3.4用数组名作函数参数
首先要知道,数组在程序中我们定义一个数组例如:int a【100】;数组名a的含义是&a【0】,
所以本质上是指针变量(因为只有指针变量才能储存地址)
所以下面的两种声明是等价的:
void hanshu(int a[],int n);
void hanshu(int* a;int n);
第一个函数的作用都是接收一个数组的首元素的地址。
下面是用指针方法让数组a中的函数倒序输出:
一开始我还写错了
#include<stdio.h>
main()
{
int a[10]={1,2,3,4,5,6,7,8,9,10};
int* p,* s;
int temp;
p=a;
s=a+9;
while(p<s)
{
temp=p;
p=s;
s=temp;
s--;
p++;
}
p=a;
for(;p<a+10;p++)
printf("%d",*p);
}
上述程序的结果是次序没变,为什么呢,我不是交换地址了吗?
那确实一直在改变指针变量p和s的指向,a【i】的值根本没有改变。
#include<stdio.h>
main()
{
int a[10]={1,2,3,4,5,6,7,8,9,10};
int* p,* s;
int temp;
p=a;
s=a+9;
while(p<s)
{
temp=*p;
*p=*s;
*s=temp;
s--;
p++;
}
p=a;//或者这里让s=(n-1)/2也可以;
for(;p<a+10;p++)
printf("%d",*p);
}
3.5指向多维数组的指针
关于多维函数的地址
在一维数组中,例如a[4],a[1]是一个数组元素,a是一个数组名代表&a[0](即为首个元素的地址)
but神奇的事情来了,在多维数组中比如a[4][3]中a[1]是一个地址,因为此时的a[1]不是数组元素而是代表由3个整型变量所组成的一维数组,a[1]代表第一行的第一个元素的地址即&a[1][0]。而数组名a代表的是首行的起始地址&a[0],a+1代表序号为序号为1的行的起始地址。
a[0],a[1],a[2]是一维数组名,代表首元素的地址。
那么要具体到列的元素的地址怎么表示呢?
&a[0][1]等价于a[0]+1,之前我们说过地址加1是加上该类型地址的字节数。
用指向数组的指针作函数参数
若想定义一个指向一维数组的指针,可以这样定义
int (*p)【4】;
它表示指针变量p指向由四个整形元素组成的一维数组。
下面我们看一段程序:
#include<stdio.h>
void research(int (*p)[4],int n)
{
int i=0;
for(i=0;i<=3;i++)
printf(" %d", *(*(p+n-1)+i));
}
void average(int *p)
{
int i=1;
int sum=0;
for(i=1;i<=12;i++)
sum+=*(p++);
printf("%f",sum/12.0);
}
main()
{
int grade[3][4]={1,2,3,4,5,6,7,8,2,2,3,4};
int (*p)[4]=grade;
research(p,1);
average(*p);
}
12个人的成绩,我编写了两个函数分别去输出平均成绩和某个人的全部成绩,一开始定义的指针p是指向一维数组的,这与research函数的形参要求相符(接收行的地址),然而average函数要求一个指向列的地址,于是用的实参是*p以满足要求。
4.1 通过指针引用字符串
首先记住一句话,在c语言中,字符串都是字符数组。它就是按字符数组去处理的。
另外在定义字符串常量的时候字符数组的实际长度比字符元素多1,因为会在末尾架上‘\0'
直接看两段程序:
#include<stdio.h>
main()
{
char string[]="I Love You";
printf("%s,%c",string,string[5]);
char *p=string;
printf("%s",*p);
}
1结果:第一个printf输出了i love you和第六个字符 e,第二个输出了了i love you。
2关于指针指向:定义了一个字符类型的指针变量,要理解为该指针变量p指向了该字符串的第一个数组元素i。
3关于%s:对于%s在输出时会输出指针变量p指向的字符然后使得p加1(指向下一个字符)直到遇到’\0'为止
4注意:另外要记住,c语言中没有字符串变量,只有字符变量。
有关定义:
char *p="i love you";
等价于
char *p;
p ="i love you";
下面看一个将字符串b复制到字符串a的例子:
#include<stdio.h>
main()
{
char a[]="i love you,my baby";
char b[30];
char *p1=a,*p2=b;
int i=0;
for(;*p1!='\0';p1++,p2++)
*p2=*p1;
*p2='\0';
p2=b;
printf("%s",p2);
}
两个字符型的指针分别赋值然后加加而已啦。
#include<stdio.h>
main()
{
char a[]="i love you,my baby";
char b[30];b[18]='C';
char *p1=a,*p2=b;
int i=0;
for(;*p1!='\0';p1++,p2++)
*p2=*p1;
*p2='\0';
printf("%s",b);
}
其实有没有*p2='\0'都行因为好像剩下没有赋值的自动为0了(已测试)这里是没问题,但是在后边有吧一个段的字符串复制到长的上去,那个时候不加就会出错哦。
4.2字符指针作函数参数
下面是是一个copy函数。
5.1 指向函数的指针
当你定义了一个函数的时候,在编译时会把函数的源代码转换为可执行代码并分配一定的储存空间,其实这段空间有一个起始地址也称为函数的入口地址。函数名就代表这个起始地址,调用函数时可以从函数名获取它的起始地址并执行函数代码。
可以定义一个指针变量指向某种类型的函数例如:
int (*p)(int,int);
定义p是一个指向函数的指针变量,它可以指向函数类型为整形且有两个整形参数的函数,指针变量p的类型用int(*)(int,int)表示。
定义和使用指向函数的指针变量
直接看程序:
#include<stdio.h>
int max(int x,int y)
{return (x>y?x:y);
}
int min(int x,int y)
{return (x>y?y:x);
}
main()
{
int (*p)(int ,int );
int n,a,b;
scanf("%d %d",&a,&b);
scanf("%d",&n);
if(n==1)
{
p=max;
printf("%d",(*p)(a,b));}
if(n==2)
{
p=min;
printf("%d",(*p)(a,b));}
}
定义了一个指向函数的指针p,注意只能指向和指针变量相同基类型的函数。
这里注意要让指针指向函数时不要忘记*p两边的括号(*p)一开始我就忘了,哎。
5.4 用指向函数的指针做函数参数
指向函数的指针变量的一个重要用途是把函数的入口地址作为参数传递到其他函数。
这个就不多说了,在定义函数的时候这样
void fun(int(*p1)(int),int(*p2)(int,int))
{
int a,b,i=1,j=2;
a=(*p1)(i);
b=(*p2)(i,j):
}
在调用的时候直接将实参(函数名)传入fun接受函数地址的地方即可。
6.1 返回指针值的函数
看这个 int* a(int x,int y) 这难道是指向函数的指针吗?!不.......不对,在*a两边没有加括号,这说明定义了一个函数a而且有两个形参,该函数的类型是int* 型,也就是说返回值是一个指针!!
下面看一个查找学生成绩的程序:
#include<stdio.h>
main()
{
int n,i;
int* research(int (*p)[4],int n);
int grade[][4]={{22,44,65,22},{88,66,43,11},{6,78,99,44}};
printf("please enter the number you want to research");
scanf("%d",&n);
int *k=research(grade,n);
for(i=0;i<4;i++)
printf("% d",*(k+i));
}
int* research(int (*p)[4],int n)
{
int *pt;
pt= *(p+n-1)+0;
return pt;
}
二维数组grade存放三个学生每个人四门成绩,research返回第n个人grade【n】【0】的地址之后输出即可。
最后看一个找出有一门不合格的成绩的学生并输出起全部成绩。
7.1指针数组和多重指针
一个数组,如果其元素全为指针类型数据,这就是指针数组。
int *p[4];什么,这难道不是指向 由四个整形元素组成的一维数组的 指针吗?!显然由于[ ]的优先级比较高,因此先运算p[4],这显然是一个数组,而这个数组又是一个int *类型的,所以是指针数组。
应用场景
使指针数组指向多个字符串进行排序:
#include<stdio.h>
#include<string.h>
main()
{
void sort(char *name[],int n);
void print(char* name[],int n);
char* name[]={"Follow me","Basic","i love you","my honey","fuck me"};
//定义了一个指针数组使得它指向这5个字符串
sort(name,5);
print(name,5);
}
void sort(char* name[],int n)
{
int i,j,k;
char* temp;
for(i=0;i<n-1;i++)
{
for(k=i+1;k<n;k++)
if(strcmp(name[i],name[k])>0)
{
temp=name[i];
name[i]=name[k];
name[k]=temp;
}
}
}
void print(char* name[],int n)
{
int i=0;
for(;i<n;i++)
printf("%s\n",name[i]);
}
我们定义了一个字符型的指针数组,它分别指向5个字符串,之后将该指针数组的首地址传入函数中进行处理。strcmp是程序提供的字符串比较函数,name[k]和name[i]是第i个和第k个字符串首字符的地址。它的值为若name[i]指向的字符串大于name[k]的,则返回正数,若相等则返回0,若小于则返回负数。
7.2指向指针的指针变量
上面我们已经讲了指针数组,即数组元素由指针组成。而我们知道,数组名表示该数组第一个元素的地址,刚上面的name则是&name[0],name+i就是&name[i]。
我们可以定义一个指向指针数组元素的指针,p就是一个指向指针型数据的指针变量,look!
char **p;
这个要分开来看,(*p)代表它是一个指针变量,char* 代表它的基类型(即它可以指向的数据的类型)显然是字符型的指针变量。思考下面的程序会输出什么:
printf("%d %s",*p,*p);
答案是输出了name[2]的值(是一个地址)和name[2]代表的字符串“Basic”。
关于为什么由%s输出了“Basic”其实这个谭浩强的书没有细讲,在“C primer plus”上讲的很清楚,一个字符串加上双引号就是字符串面常量,加上双引号就是地址了指向第一个字符的地址,而当指针指向它的话算是引用(回去我在看看,没学精这部分)
8.1 动态内存分配与指向它的指针变量
之前我们讲过全局变量和局部变量的概念,全局变量是分配在内存中的静态存储区的,非静态存储区的局部变量(包括形参)是分配在内存中的动态存储区的,这个存储区称为 栈 。
除此之外,c语言还允许建立内存动态分配区域,以存放一些临时用的数据,随时用随时开辟,不需要用了就释放,称为 堆 区。
建立内存的动态分配(4个函数)
1、用 malloc 函数开辟动态存储区
函数原型为:
void *malloc(unsigned int size);
作用为在内存的动态区域分配一个长度为size的连续空间,形参size的类型定为无符号整形(非负),该函数的返回值为分配区域第一个字节的地址(因为函数类型是指针嘛)
malloc(100);开辟100个字节的临时分配域,返回值为第一个字节的地址。
注意:函数类型为void 只返回一个纯地址而不指向任何类型的数据,如果函数未能成功的执行则返回空指针(NULL)。
2、用 calloc函数 开辟动态存储区
函数的原型为
void *calloc(unsigned n,unsigned size);
它的作用是开辟n个长度为size的连续空间,空间一般较大,足以储存一个数组。
用calloc函数可以为一维数组开辟内存储存空间,n为元素的个数,每个元素长度为size,这就是动态数组。函数的返回值指向第一个自节的地址,如果不成功则返回NULL。
如 p=calloc(50,4);
3、用 realloc函数 重新分配动态存储区
函数原型为
void *realloc(void *p,unsigned int size);
如果已经通过malloc函数或者calloc函数分配了动态存储区域,若想改变大小,可以通过realloc函数重新分配。
用realloc函数将p指向的动态空间的大小改变为size。p的值不变。如果重分配不成功,返回NULL,如 realloc(p,50);//将p所指向的已分配的动态空间改为50字节
4、用 free函数 释放动态存储区
函数原型为:
void free(void *p);
释放p所指向的存储空间,p为最近一次调用malloc函数或者calloc函数时得到的返回值。
free(p);free函数无返回值。
以上四个函数的声明在stdlib.h头文件中,要用#include<stdlib.h>包含到程序文件中。
void指针类型