四、指针(小结)
A.定义:
一个地址即为一个指针,是一个常量,所谓指针变量是指一个来存储指针(地址)的变量;
ep1:int a=10;
int *b=&a;
这里,&a就是一个指针,是一个常量,而b就是一个变量,用来存储a的地址的。
B.指针变量的定义和引用:
a定义:
格式: [存储类型] 数据类型符 *变量名;
注意:1.*和变量名之间可以有若干空格;
2.int *p定义的是指针变量p,而不是*p;
b赋值(三种方法):
1.指针变量初始化 int a;int *p=&a;
2.赋值语句法 int a;int *p;p=&a;
3.转移 int a=20;int *p,*q;p=&a;q=p;
注意:在第三种方法中,一般而言只能同类型之间相互赋值。如果=两边不同类型,则需要强制转换,但是强制转换后他们引用内存单元对应的值是不一样的;
ep2:int a;
int pi;
char pc;
pi=&a;
pc=(char *)pi;
如上,引用pi就相当于引用a的整个内存单元的值,而引用pc就相当于引用a的第一个字节所对应的值(在vc6.0中,a对应4个字节,从低到高,引用pi就是将a4个字节存储的内容全部引用,引用pc就是将a的地址最低的一个字节存储的内容引用);
C.零指针与空类型指针
零指针:指针变量的值为零的值的指针
int *p=0;
int *p=NULL;
这两种都可以表示零指针;
空类型指针(void):使用时需进行强制转化
D:指针变量的引用
注意:1.*在指针定义是表示类型说明符,在表达式中表示运算符,意为取内容;
2.*是单目运算符,优先级同++、--一样。
3.在vc6.0中指针变量需要4个字节的存储单元;
E:指针和地址运算
指针可以进行简单的加减运算,但不能进行乘除法运算;
加减运算规则:
ptype a=值;
ptype *p;
p=&a;(假设&a的值为ADDR)
则p+(-)n=ADDR+(-)n*sizeof(ptype)
另外,指针变量之间可以进行==、>、<的判断;
F.指针与数组
1.数组的指针:数组的指针实际上就是数组在内存中的起始地址;
用两段代码来解释吧。
代码1:
int a[10];
int k;
for(k=0;k<10;K++)
a[k]=k;
代码二:
int a[10];
int k;
for(k=0;k<10;K++)
*(a+k)=k;
代码一和代码二运行的结果是完全相同的;变量名a就是数组的起始地址,*(a+k)就是对a[k]的引用
地址 数组元素 指针
| 2000 | a[0] | ←a | |||
| 2004 | a[1] | ←a+1 | |||
| 2008 | a[2] | ←a+2 | |||
| 2012 | a[3] | ←a+3 | 注意: | ||
| 2016 | a[4] | ←a+4 | a+k | ↔ | &a[k] |
| 2020 | a[5] | ←a+5 | *(a+k) | ↔ | a[k] |
| 2024 | a[6] | ←a+6 | |||
| 2028 | a[7] | ←a+7 | |||
| 2032 | a[8] | ←a+8 | |||
| 2036 | a[9] | ←a+9 |
当然,你也可以单独设置一个指针变量指向数组的起始地址
int a[10];
int *p=&a;(int *p=&a[0];)
对应的关系就如下表:
| 2000 | ←指针p所占据的内存单元 | |||||
| 地址 | 元素 | |||||
| 2000 | a[0] | ←a | p | *a | *p | p[0] |
| 2004 | a[1] | ←a+1 | p+1 | *(a+1) | *(p+1) | p[1] |
| 2008 | a[2] | ←a+2 | p+2 | *(a+2) | *(p+2) | p[2] |
| 2012 | a[3] | ←a+3 | p+3 | *(a+3) | *(p+3) | p[3] |
| 2016 | a[4] | ←a+4 | p+4 | *(a+4) | *(p+4) | p[4] |
| 2020 | a[5] | ←a+5 | p+5 | *(a+5) | *(p+5) | p[5] |
| 2024 | a[6] | ←a+6 | p+6 | *(a+6) | *(p+6) | p[6] |
| 2028 | a[7] | ←a+7 | p+7 | *(a+7) | *(p+7) | p[7] |
| 2032 | a[8] | ←a+8 | p+8 | *(a+8) | *(p+8) | p[8] |
| 2036 | a[9] | ←a+9 | p+9 | *(a+9) | *(p+9) | p[9] |
这蛮简单的,通过这两组表就非常容易理解了;
2.二维数组的指针
在这个里面个人以为,要非常清晰的理解行指针与列指针的含义。
用一个表达式来概括:
*(*(a+i)+j)
a:第0行的地址;
a+i:第i行的地址;
*(a+i)第i行第0列的地址;
*(a+i)+j第i行第j列的地址;
*(*(a+i)+j)第i行第j列的元素;
这样就对指针表示二维数组的理解非常清楚了,再定义一个指针变量 int *p;,就可以对同一个数组元素有各种不同的表示方法了;
例如:
a[i][j]
*(a+i)[j]
*(*(a+j)+j)
*(a[i]+j)
G:指针数组与数组指针
其实懂了行指针与列指针,这也是蛮好理解的
指针数组(int *p[20]):列指针(就是列地址,比如a[0],a[1]等),定义一组数组元素全为指针变量的数组;
数组指针(int (*p)[20]):行指针(就是行地址),定义一个指向大小为20的int型数组的指针;
区别:指针数组是定义一组指针,而数组指针是定义一个指针。
F:指针与字符串
1.字符串给指针赋值有以下两种方式:
a:格式 char *字符指针变量名=字符串常量;
char *pstr="I Love xiao qian!" ;
b: 格式 char *字符指针变量名;
字符指针变量名=字符串常量;
char *pstr;
pstr="I Love xiao qian!" ;
2.字符串的引用也有以下两种方式:
a:逐个单字符引用:
1 #include <stdio.h> 2 3 void main () 4 { 5 char *pstr="I Love XiaoQian!"; 6 for (;*pstr++!='\0';) 7 printf("%c",*(pstr-1)); 8 }
b:整体引用:
1 #include <stdio.h> 2 3 void main () 4 { 5 char *pstr="I Love XiaoQian!"; 6 printf("%s",pstr/*地址*/); 7 }
注意:如果一个指针没有指向一个有效内存就被引用,则其被称为“野指针”操作或空指针赋值;
一个比较好的例题:
1 #include <stdio.h> 2 #include <string.h> 3 4 void main () 5 { 6 int i,j,k; 7 char *pcity[]={"wuhan","beijing","shanghai","tianjin","guangzhou",""}; 8 char *ptemp; 9 10 for (i=0;strcmp(pcity[i],"")!=0;i++) 11 { 12 k=i; 13 for (j=i+1;strcmp(pcity[j],"")!=0;j++) 14 if (strcmp(pcity[k],pcity[j])>0) 15 k=j; 16 if (k!=i) 17 { 18 ptemp=pcity[i]; 19 pcity[i]=pcity[k]; 20 pcity[k]=ptemp; 21 } 22 } 23 for (i=0;strcmp(pcity[i],"")!=0;i++) 24 printf("%s ",pcity[i]); 25 printf("\n"); 26 }
H:指针与动态内存分配
void *malloc(unsigned int size); 说明:用于分配若干字节的内存空间,返回一个指向该区域地址的指针;若系统不能提供足够的内存单元,函数将返回一个空指针;注:n*sizeof(int)
void *calloc(unsigned int num,unsigned int size); 说明:用于给若干同一类型的数据项分配连续的存储空间,其中每个数据的长度单位为字节,且系统自动存储单元的值置为0;
void *realloc(void *p,unsigned int size); 说明:将指针*p所指向的存储空间大小改为size个字节
void free(void *block); 说明:动态内存分配之后必须用free()函数来释放;block是连续空间的首地址
动态内存分配遵循的规则:分配、判断、释放,三位一体;
I:多级指针
a:定义:一般来说,n级指针变量其内容是存放一个n-1级指针变量的地址(以下我们以二级指针为例)。
定义格式:[存储类型] 数据类型符 **变量名;/*数据类型符是二级指针指向最终目的变量的存储类型*/
**:在定义是表示说明符,**在表达式中表示运算符,这点同指针的定义时*的作用是一样的;
1 main() 2 { 3 static int a[5]={1,3,5,7,9}; 4 int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]}; 5 6 int **p,i; p=num; 7 for(i=0;i<5;i++) 8 { printf("%d\t",**p); 9 p++; 10 } 11 }
J:指针作为函数参数
要实现函数的传址调用,在定义形参是就必须使用指针型函数。
1 #include <stdio.h> 2 #include <string.h> 3 void alltrim(char *psstr,char *pdstr) 4 { 5 char *pstart,*pend; 6 7 //将pstart指向左边非空格的第一个位置 8 pstart=psstr; 9 while(*pstart==' ') 10 pstart++; 11 //将pend指向右边非空格的第一个位置 12 pend=pstart+strlen(pstart)-1; 13 while(pend>pstart&&*pend==' ') 14 pend--; 15 //将pstart所指向的字符到pend所指向的字符复制到pdstr中 16 while(pstart<=pend) 17 *pdstr++=*pstart++; 18 *pdstr='\0'; 19 } 20 int main( ) 21 { 22 char *pstr,str[20]; 23 pstr= "Good Bye! "; 24 printf("%s\n",pstr); 25 alltrim(pstr,str); 26 printf("%s",pstr); 27 28 }
还有个比较经典的例子是书上P291的例9-13,我给出最经典的部分:
1 p=(int **)malloc(row*sizeof(int *)); 2 for(i=0;i<row;i++) 3 p[i]=(int *)malloc(col*sizeof(int));
这是动态为二维数组分配内存,先分配row个行,然后在每个行的基础上为每个列分配动态内存空间

通过图像可知,行与行之间的地址可能是不连续的。
释放:
1 for(i=0;i<row;i++) 2 free(p[i]); 3 free(p);
先释放行,再释放列;
K:关于const
const作用:为了避免程序员修改形参的值,可以将参数设定为常态形参。
对于指针型参数来说,常态有一下两种含义:
1.指形参本身是常态
1 void func(int * const p) 2 { 3 int a[10]; 4 *p=5;//正确 5 p=a;//将引起编译错误 6 }
不能对指针变量本身做修改
2.指针所指的内存单元是常态
1 void func(const int *p) 2 { 3 int a[10]; 4 *p=5;//将引起编译错误 5 p=a;//正确 6 }
另外还有一点特别值得注意的是:如果程序想通过函数为指针变量分配内存,则形参必须是一个指针的指针。
/*因为二级指针在函数中引用后是一级指针的地址被赋值,而一级指针在函数中引用后是目标变量内存的地址,是一个常量,不能被赋值*/
L:指向函数的指针——函数指针
定义:一个函数在编译时,被分配了一个入口地址,用函数名来表示,这个地址就称为该函数的指针。
定义格式: 函数类型 (*指针变量)([形参类型1],[形参类型2],[形参类型3],[形参类型4])
说明:1.函数类型为函数指针所指向函数的返回值类型;
2.指针变量是专门存放函数入口地址,可指向返回值的类型相同的不同函数
3.“*指针变量”外的括号不能缺,否则就成了返回指针值得函数了(指针函数)
赋值: 函数指针=[&]函数名;
1 int max (int a,int b) 2 { 3 return(a>b?a:b); 4 } 5 int (*p)(int,int); 6 p=max;
调用格式:函数指针变量([实参类型1],[实参类型2],[实参类型3],[实参类型4]);或(*函数指针变量)([实参类型1],[实参类型2],[实参类型3],[实参类型4]);
ep:
p(2,3)或(*p)(2,3);//等价于max(2,3)
M:指针作为函数的返回值——指针函数
定义格式:函数类型 *函数名([形参类型1],[形参类型2],[形参类型3],[形参类型4])
注意:1.其他函数调用指针函数获取指针后,要在适当的时候,调用free函数释放这个指针;
2.如果一个函数返回一个指针,不能返回auto型局部变量的地址,但可以返回static型变量的地址(返回的指针对应的内存空间不能因该指针的返回二被释放掉)。
一般而言有以下三种:a、函数中动态内存分配的首地址;
b、函数中静态变量或全局变量对应的的存储单元的首地址;
c、通过指针形参所获得的实参的有效地址;
N:带参数的main函数
main (int argc,char *argv[])
命令行格式:命令名 参数1 参数2 参数3....(DOS提示符下命令行的一般形式为: C:\>可执行文件名 参数 参数 ……;)
注意:1.参数之间以一个或多个空格隔开
2.argc参数表示了命令行中参数的个数(注意:文件名本身也算一个参数),argc的值是在输入命令行时由系统按实际参数的个数自动赋予的。