目录

第八章 指针与二维数组

第1课:复习--指针定义与使用

第2课:指针数组与多级指针

第3课 多级指针及变量间的关系

第4课:数组指针--指向多维数组的指针

第5课 以指针变量传参

第6课:指向二维数组的指针计算二维数组行列的平均值

第7课:函数指针与memset函数

第8课:指针总结

第9课   字符指针与字符串

第10课:通过main函数的参数实现计算器


        本文深入C语言灵魂——指针,系统剖析其与二维数组的复杂交互(行列指针、数组指针/指针数组辨析),详解多级指针函数指针及指针作为函数参数的完整应用。最后,通过内存操作函数(memset/memcpy) 与命令行参数处理实战,带你从原理到应用彻底掌握指针。

第八章 指针与二维数组

第1课:复习--指针定义与使用

int a=10;

int *p=&a;  等同于:

       int *p;

       p=&a;

C语言中,NULL定义为0

int *p;   //P为野指针,避免使用野指针,更不能读写野指针指向的内存

int *p1;

p1=NULL   //p1为空指针

野指针不是空指针

float32位系统下是4个字节

float a=3.14;

       char *p=&a;

       printf("%x,%x,%x,%x\n",*p,*(p+1),*(p+2),*(p+3));

       return 0;

}

结果为:

巧妙修改 指向常量的指针处的值

int main(){

       int a=100;

       const int *p=&a;

       //*p=200;      //不能将const int *作为左值使用(不能修改指向的值),会报错

       int *p1=p;    //把指向常量的指针赋值给别的指针后,其指向的值就可以被修改了,这个是C语言不够严谨的地方

       *p1=200;

       printf("a=%d\n",a);   

       return 0;

}

结果为:a=200

巧妙修改常量的值

int main(){

       const int ABC=100;

       //ABC=300;        //不能修改常量的值,会报错

       int *p=&ABC;

       *p=300;                     //可以用指针来修改常量的值,这也是C语言不严谨之处。

       printf("%d\n",ABC);

       return 0;

}

指针常量

       指针常量的意思是一旦定义之后,就固定指向一个地址,不能再改变为其它地址。

数组名也是常量

int main(){

       int buf[10]={1,2,3,4,5,6,7,8,9,10};

      int *p=buf+5;

       //p++;

       //buf++; //buf=buf+1 //报错,buf是数组名,是一个常量,常量不能做左值。

       printf("%d\n",*p);

       return 0;

}

结果为:6

思考一个问题:

       int buf[10]={1,2,3,4,5,6,7,8,9,10};

       int *p=buf;

       printf("%d,%d\n",sizeof(p),sizeof(buf));

结果输出什么?

答:结果为4,40

指针也可以使用数组下标

int main(){

       int buf[10]={1,2,3,4,5,6,7,8,9,10};

       int *p=buf;

       int i;

       for(i=0;i<10;i++){

             printf("%d,",p[i]); //C语言允许指针通过数组下标的方法访问数组成员。p[i] 等同于 *(p+i)

       }

                     return 0;

}

结果为:

用指针修改数组成员值的几种方法

int main(){

       int buf[10]={0,1,2,3,4,5,6,7,8,9};

       int *p=buf;

       int i;

       //方法1

       *(p+2)=20;      

       //方法2

       p[6]=60;

       //方法3

       p+=8;

       *p=80;

       p=buf;          //p的值恢复      

       for(i=0;i<10;i++){

              printf("%d,",p[i]);

       }

       return 0;

}

结果为:

方法1和方法2都没有修改p的值,但方法三修改了p的值

思考一个问题:

在上面的代码中,执行p+=8; 之后,再执行 p[1]=90; 那么它将修改数组中的哪个成员值?

答:将会修改buf[9]的值。

解释:p[1]等同于*(p+1),执行p+=8;后,p指向的是buf[8],因此…………

第2课:指针数组与多级指针

定义一个指针数组

int main(){

       int *a[10];     //定义一个指针数组,共10个成员,每个成员都是int *类型

       short *b[10];

       char *c[10];

       printf("%d,%d\n",sizeof(a),sizeof(a[0]));

       printf("%d,%d\n",sizeof(b),sizeof(b[0]));

       printf("%d,%d\n",sizeof(c),sizeof(c[0]));

       return 0;

}

结果为:(结果都一样

指向指针的指针(二级指针)

       指针就是一个变量,既然是变量,也就存在内存地址,所以可以定义一个指向指针的指针。

定义一个二级指针(两个*代表二级指针)

int main(){

       int a=10;

       int *p=&a;

       int **pp=&p;     //定义了一个二级指针,指向了一个一级指针的地址

       **pp=100;          //通过二级指针,修改内存的地址。

       //*pp=10       //相当于将p指向了编号为10的内存。极可能报错

printf("%d\n",a);

       return 0;

}

结果为:100

二级指针与一级指针的关系图:

int a=10;

int *p=&a;

int **pp=&p;

三级指针:

       通过***ppp访问变量a的值

       通过**ppp访问指针p的值

       通过*ppp访问pp的值

二级指针:

       通过**pp访问变量a的值

       通过*pp访问指针p的值

一级指针:

       通过*p访问变量a的值

上面所谓的值,是指内存单元中保存的数据

定义一个三级指针

int a=10;

int *p=&a;

int **pp=&p;

int ***ppp=&pp;

***ppp=100;

printf("a=%d\n",a);

结果为:a=100

案例:

int main(){

       int a=10;

       int *p=&a;

       int **pp=&p;

       int ***ppp=&pp;

       a=ppp;         //等同于a=&pp;

       printf("%x,%x,%x,%x,%x\n",***ppp,&p,&pp,&ppp,*ppp);

       return 0;

}

结果为:

以此类推,可以定义3级甚至更多级指针,C语言允许定义多级指针,但是指针级数过多会增加代码的复杂性,实际编程时最多用到3级,且3级指针也不常用。

一级和二级指针是大量使用的。

第3课 多级指针及变量间的关系

案例:三级指针、二级指针、一级指针、所指向变量间的关系

int main(){

       int a=20;

       int *p=&a;

       int **pp=&p;

       int ***ppp=&pp;

       printf("a=%x  (16进制)\n",a);

       printf("&a=%x\n",&a);

       printf("------------\n");

       printf("p=%x\n",p);

       printf("*p=%x\n",*p);

       printf("&p=%x\n",&p);

       printf("------------\n");

       printf("pp=%x\n",pp);

       printf("*pp=%x\n",*pp);

       printf("**pp=%x\n",**pp);

       printf("&pp=%x\n",&pp);

       printf("------------\n");

       printf("ppp=%x\n",ppp);

       printf("*ppp=%x\n",*ppp);

       printf("**ppp=%x\n",**ppp);

       printf("***ppp=%x\n",***ppp);

       printf("&ppp=%x\n",&ppp);

       return 0;

}

结果为:

第4课:数组指针--指向多维数组的指针

指向二维数组的指针

案例:

#include <stdio.h>

int main(){

       int buf[2][3]={{1,2,3},{4,5,6}};

       //int *p[10]   //定义指针数组

       int (*p)[3];   //定义一个指针,指向二维数组的指针,指向int [3]类型的数据,

       p=buf;

       //p++      //指向第二行

       int i=0;

       int j=0;

       printf("%d\n",sizeof(p));

       printf("%d,%d,%d\n",p,p+1,p+2); 

       for(i=0;i<2;i++){

              for(j=0;j<3;j++){

                     printf("%d\n",p[i][j]);

              }

       }

       return 0;

}

结果为:

注:p+1位移大小为:1sizeof(int [3])

       p+2位移大小为:2sizeof(int [3])

sizeof(int [3]也就是二维数组中一行的长度。一行3int类型的元素,共12个字节。因此:

       p+1位移大小为:12个字节

       p+2位移大小为:24个字节

p[i][j] 等同于 *(*(p+i)+j)

第5课 以指针变量传参

指针变量做为函数的参数

       函数的参数可以是指针类型,它的作用是将一个变量的地址传送给另一个函数。

       通过函数的指针参数可以间接的实现形参修改实参的值。

案例如下:

void test(int *n){

       (*n)++;

}

int main(){

       int i=100;

       test(&i);

       printf("i=%d\n",i);

       return 0;

}

结果为i=101

一维数组名作为函数参数

       当数组名作为函数参数时,C语言将数组名解释为指针。

案例如下:

#include <stdio.h>

void test(int buf[]){   / / buf[] 等同于 *buf,数组名代表数组首地址

       buf[0]=100;

       buf[1]=200;

       printf("buf=%d\n",sizeof(buf));

}

int main(){

       int buf[5]={10,20,30,40,50};

       int i=0;

       test(buf);      // 这里 buf &buf&buf[0] 等同

       printf("buf=%d\n",sizeof(buf));

       for(i=0;i<5;i++){

              printf("%d,",buf[i]);

       }

       return 0;

}

结果为:

案例:智能打印数组成员(无需写具体成员个数)

void print_buf(int *buf,int n){

       int i;

       for(i=0;i<n;i++){

              printf("%d,",buf[i]);

       }

}

int main(){

       int buf[]={10,20,30,40,50,60,92};

       print_buf(buf,sizeof(buf)/sizeof(int));

       return 0;

}

二维数组名作为函数参数

       二维数组作为函数参数时可以不指定第一个下标。

       将二维数组作为函数的参数的情况不是特别常用。

案例:

void print_buf(int (*p)[3],int a,int b){

       int i,j;

       for(i=0;i<a;i++){

              for(j=0;j<b;j++){

                     printf("p[%d][%d]=%d\n",i,j,p[i][j]);

              }

       }      

}

int main(){

       int buf[2][3]={{1,2,3},{4,5,6}};

       print_buf(buf,sizeof(buf)/sizeof(buf[0]),sizeof(buf[0])/sizeof(int));      

       return 0;

}

结果为:

void print_buf(int (*p)[3],int a,int b) 等同于:

void print_buf(int p[][3],int a,int b)

const关键字保护数组内容

       如果将一个数组做为函数的形参传递,那么数组内容可以被调用函数修改,有时不希望这样的事情发生,所以要对形参采用const参数。

案例如下:

void mystrcat(char *s1,const char *s2){      //第一个参数要被修改,第二个参数不被修改

       int len=0;

       int i;

       while(s2[len]){

              len++;

       }      

       while(*s1){

              s1++;

       }      

       for(i=0;i<len;i++){

              *s1=*s2;

              s1++;

              s2++;

       }

}

int main(){

       char s1[10]="abc";

       char s2[10]="efg";

       mystrcat(s1,s2);

       printf("s1=%s\n",s1);

       return 0;

}

结果为:

指针作为函数的返回值

案例如下:

char *mystr(char *s,char c){

       while(*s){

              if(*s==c)

                     return s;

              s++;

       }

       return NULL;

}

int main(){

       char str[100]="hello world";

       char *s=mystr(str,'o');      

       printf("%s\n",s);

       return 0;

}

结果为:

为什么结果是:o world ?

因为mystr(str,'o')返回的是一个指向字符 'o'的地址,而printf("%s\n",s);是把这个地址中的内容以字符串的形式打印出来。因此它会把 'o'和后面的字符全打印出来,直到碰到结尾符0

第6课:指向二维数组的指针计算二维数组行列的平均值

方法一、先用数组下标方式实现:

代码如下:

#include <stdio.h>

int main(){

       int buf[3][5]={{2,5,8,9,4},{3,6,7,2,9},{5,8,7,2,1}};

       int i,j;

       for(i=0;i<3;i++){

              int sum=0;

              for(j=0;j<5;j++){

                     sum+=buf[i][j];

              }

              printf("buf[%d][?]/5=%d/5=%d\n",i,sum,sum/5);

       }

       printf("------------\n");

       for(i=0;i<5;i++){

              int sum=0;

              for(j=0;j<3;j++){

                     sum+=buf[j][i];

              }

              printf("buf[?][%d]/5=%d/5=%d\n",i,sum,sum/5);

       }

       return 0;

}

结果如下:

方法二、用指向二维数组的指针实现,只需要用*(*(a+1)+2)替换buf[i][j]即可

代码如下:

#include <stdio.h>

int main(){

       int buf[3][5]={{2,5,8,9,4},{3,6,7,2,9},{5,8,7,2,1}};

       int i,j;

       for(i=0;i<3;i++){

              int sum=0;

              for(j=0;j<5;j++){

                    sum+=*(*(buf+i)+j);

                     //sum+=buf[i][j];

              }

              printf("buf[%d][?]/5=%d/5=%d\n",i,sum,sum/5);

       }

       printf("------------\n");

       for(i=0;i<5;i++){

              int sum=0;

              for(j=0;j<3;j++){

                     sum+=*(*(buf+j)+i);

                     //sum+=buf[j][i];

              }

              printf("buf[?][%d]/5=%d/5=%d\n",i,sum,sum/5);

       }

       return 0;

}

第7课:函数指针与memset函数

       指针可以指向变量,数组,也可以指向一个函数。

       一个函数在编译时会分配一个入口地址,这个入口地址就是函数指针,函数名就代表函数的入口地址。

       函数定义指针变量的形式为:

             函数返回类型(*指针变量名称)(参数列表)

         函数可以通过指针调用

指向函数的指针

int add(int a,int b){

       return a+b;

}

int main(){

       int i=0;

       int (*p)(int,int);        //定义了一个指向函数的指针,可以

       p=add;          //直接写函数的名字,代表函数的地址,将add这个函数的地址赋值给指针变量p

       i = p(5,8);        //通过指针变量间接调用指针指向的函数

       printf("%d\n",i);

       return 0;

}

结果为:13

int (*p)(int,int); 这样定义指向函数的指针时,此指针能指向的函数必须具备以下条件:有两个int类型参数,有int类型的返回值。

有这么一个函数:

       void function(int *s,int n){

           ....................                                                             

       }

请问如何,定义一个指向它的指针?

可以这样:void (*p)(int *s,int n);

也可以这样:void (*p)(int *,int);

void *p(int,char *); 表示什么意思?

答:声明了一个函数,函数名叫p,返回值类型为void *,函数的参数类型是intchar *

void (*p)(int,char*) //定义一个指向参数为intchar*,返回值为void的函数的指针。

int *(*p)(int *); //定义一个参数为int *,返回值为int *的指向函数的指针

void (*p)();  //定义一个指向没有参数,没有返回值的函数的指针。

在回调函数和运行期动态绑定的时候大量的用到了指向函数的指针。

案例:

void man(){

       printf("吃喝嫖赌\n");

}

void woman(){

       printf("坑蒙拐骗\n");

}

int main(){

       int i=0;

       void (*p)();

       scanf("%d",&i);

       if(i==1)

              p=man;

       else if(i==2)

              p=woman;      

       p();

       return 0;

}

把指向函数的指针做为函数的参数

       将函数指针做为另一个函数的参数称为回调函数

int fun(int(*p)(int,int),int a,int b){       //第一个参数是指向函数的指针

       return p(a,b);     //通过指向函数的指针调用一个函数

}

int add(int a,int b){

       return a+b;

}

int main(){

       int i=0;

       i=fun(add,7,8);   //add函数在这里就叫回调函数

       printf("i=%d\n",i);

       return 0;

}

结果为:i=15

memset,memcpy,memmove函数

这三个函数分别实现内存设置,内存拷贝和内存移动

使用memcpy的时候,一定要确保内存没有重叠区域。

它们需要一个头文件:  #include <string.h>

案例:将数组初始化

#include <stdio.h>

#include <string.h>

int main(){

       int i;

       int buf[10]={0}//只能用于定义数组的时候同时初始化内容,定义完成后,再用 buf[10]={0}是不行的,这是错误语法

       buf[0]=10;

       buf[6]=60;

       buf[8]=20;

       //想将这个buf再一次初始化为0,可以用下面方式,但效率不高

       //for(i=0;i<10;i++){

       //     buf[i]=0;

       //}

      memset(buf,0,sizeof(buf));  //将一块内存初始化为0,最常用的方法      

       for(i=0;i<10;i++){

              printf("buf[%d]=%d\n",i,buf[i]);

       }      

       return 0;

}

memset(buf,0,sizeof(buf)); //第一个参数是要设置的内存地址,第二个参数是要设置的值,第三个参数是内存大小,单位:字节

案例:将数组1拷贝到数组2中:

#include <stdio.h>

#include <string.h>

int main(){

       int i;

       int buf1[10]={1,2,3,4,5,6,7,8,9,10};

       int buf2[10]={0};

       memcpy(buf2,buf1,sizeof(buf1));      

       for(i=0;i<10;i++){

              printf("buf2[%d]=%d\n",i,buf2[i]);

       }      

       return 0;

}

注:memcpy(buf2,buf1,sizeof(buf1));buf1的内存内容全部拷贝到buf2中,拷贝大小为第三个参数的大小,单位为:字节

案例:将buf1中的数组成员移动到buf2中,代码跟拷贝一样:

#include <stdio.h>

#include <string.h>

int main(){

       int i;

       int buf1[10]={1,2,3,4,5,6,7,8,9,10};

       int buf2[10]={0};

       memmove(buf2,buf1,sizeof(buf1));      

       for(i=0;i<10;i++){

              printf("buf2[%d]=%d\n",i,buf2[i]);

       }      

       return 0;

}

使用内存移动函数memmove后,源内存中的数据仍然存在,没有变化。效果跟memcpy是一样的,那么这两个函数有什么区别呢?请看第8课

第8课:指针总结

memmovememcpy的区别在于:

使用memcpy的时候,一定要确保内存没有重叠区域。案例如下:

#include <stdio.h>

#include <string.h>

int main(){

       int i;

       int buf[10]={1,2,3,4,5,6,7,8,9,10};

       int *start=buf+3;

       int *end=buf+5;

       memmove(start,end,16);        //这里就产生了内存重叠区域      

       for(i=0;i<10;i++){

              printf("buf[%d]=%d\n",i,buf[i]);

       }      

       return 0;

}

指针总结:

int main(){

       int *p[3];      //定义一个指针数组,有3个int *成员,

       int a,b,c;

       p[0]=&a

       p[1]=&a;

       p[2]=&a;      

       *p[0]=10; //通过指针数组成员访问指针指向内存的值。

int (*p)[3];   //定义一个数组指针,是一个名字叫p的指针变量,他指向 int [3] 这种数据类型,数组指针大多数情况下用在指向一个二维数组。

       int buf1[5][3];

       int buf2[7][3], //只要二维数组的第一维是int [3]这种类型,那么都可以用int (*p)[3] 来指向他

       return 0;

}

案例:对数组指针的深入了解

int main(){

       int buf[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};

       int (*p)[4];   //p++移动4*sizeof(int)个字节,(*p)++移动sizeof(int)个字节

       p=buf;

       printf("%p,%p,%p,%p,%p,\n",buf,buf[0],&buf[0][0],p,*p);

       p++;

       printf("%p,%p,%p,%p,%p,\n",buf,buf[1],&buf[1][0],p,*p);

       printf("%p,%p\n",(p+1),(*p+1));

       return 0;

}

结果为:

如何理解指向二维数组的指针呢?

可以把二维数组buf[3][4]看作是一行有4个成员,共3行的表格。

int (*p)[4];可以指向一行有4个成员,共N行的表格。

p 表示逐行递进,因此p++ 移动4*4=16个字节。

*p 表示逐个成员递进,因此 (*p)++ 移动4个字节。

       p+3 移动3*16=48个字节。

       *p+3 移动 3*4=12个字节。

buf[3][4]可以看成是如下表格:

p[0], *(p+0),  p,  *p  表示0行,0列元素地址

p+1  表示第1行首地址

p[1], *(p+1) 表示第1行,0列元素地址

p[1]+2 , *(p+1)+2 , &p[1][2] 表示第1行,2列元素地址

*(p[1]+2), *(*(p+1)+2), p[1][2] 表示第1行,2列元素的值。

第9课   字符指针与字符串

字符指针与字符串

案例:用指针来打印int数组与 打印字符串数组

void print_array(int *p,int n){     //如果参数是int数组,那么就必须传递第二个参数来标识数组的长度

       int i;

       for(i=0;i<n;i++){

              printf("p[%d]=%d,",i,p[i]);

       }

}

void print_str(char *s)//如果参数是字符串,就不需要第二个参数,因为字符串是以0结尾的,以此可判断是否到了字符尾。

       int i=0;

       while(s[i]){

              printf("%c",s[i++]);

       }

}

int main(){

       int array[5]={1,2,3,4,5};

       char str[100]="hello world";

        print_array(array,5);

        print_str(str);

       return 0;

}

指针数组作为main函数的形参

int main(int argc,char *args[]){

       int i;

printf("%d\n",argc);

//argc代表程序执行时有几个参数,程序名称本身就是一个参数,所以argc最小值为1

//第二个参数是一个指针数组,其中每个成员的类型是 char *

       printf("args[0]=%s\n",args[0]);

//args是一个指针数组,那么他的成员数量是多少呢?argc这个参数就是告诉main函数args这个数组有多少个成员的。

       for(i=0;i<argc;i++){

              printf("args[%d]=%s\n",i,args[i]);

       }

return 0;

}

运行结果为:

然后在cmd中手动运行,并随意输入一些参数,结果如下:

第10课:通过main函数的参数实现计算器

如何打开通过VS创建的windows控制台程序?

假如程序名称为main-a,源文件名为main.c,则要在VS中再次打开、编辑、调试,就应该打开main-a.sln文件:

通过main函数的参数实现计算器

代码如下:

#include <stdio.h>

int main(int argc,char *args[]){

       int a=atoi(args[1]);

       int b=atoi(args[3]);

       if(argc<4){

              printf("参数不足,程序返回");

              return 0;

       }

       switch(args[2][0]){

              case'+':

                     printf("%d+%d=%d\n",a,b,a+b);

                     break;

              case'-':

                     printf("%d+%d=%d\n",a,b,a-b);

                     break;

              case'*':

                     printf("%d+%d=%d\n",a,b,a*b);

                     break;

              case'/':

                     if(b){

                            printf("%d+%d=%d\n",a,b,a/b);

                            break;

                     }else{

                            printf("除数不能为0");

                     }

       }

       return 0;

}

通过CMD运行main-a.exe 并输入参数,效果如下:


计算机科学与技术 & 计算机网络技术:双专业课程体系完全导航指南

 本系列目录

1、回看经典!第一章 从“Hello World”到理解编译本质(2015年C语言培训班笔记重读)

2、回看经典!第二章 数据类型与运算符详解(2015年C语言培训班笔记重读)

3、回看经典!第三章 流程控制全解 逻辑/分支/循环/图形实战(2015年C语言培训班笔记重读 )

4、回看经典!第四章 · C语言数组与字符串核心实战 从定义、遍历到排序与算法详解(2015年C语言培训班笔记重读)

5、回看经典!第五章 C语言数组高级应用实战:字符串处理、安全防范与多文件编程(2015年C语言培训班笔记重读)

6、回看经典!第六章 C语言综合能力提升:多文件编程与递归函数核心解析及实战(2015年C语言培训班笔记重读)

7、回看经典!第七章 全面解析C语言指针:从核心概念到高效应用(2015年C语言培训班笔记重读)

8、回看经典!第八章 C语言指针完全指南:深度解析二维数组、多级指针与内存操作实战(2015年C语言培训班笔记重读)

9、回看经典!第九章 C语言内存管理完全指南:四区模型、堆栈操作与malloc/free核心实践(2015年C语言培训班笔记重读)

10、回看经典!第十章 C语言结构体完全指南:内存对齐、动态管理与指针应用实战(2015年C语言培训班笔记重读)

11、回看经典!第十一章 C语言文件操作与数据处理实战:从结构体到文本加密、排序全解析(2015年C语言培训班笔记重读)

12、回看经典!第十二章 C语言二进制文件操作实战:fread/fwrite读写、fseek随机访问与加密排序案例(2015年C语言培训班笔记重读)

13、回看经典!第十三章 C语言数据结构与算法基础:文件操作、排序查找实现及链表简介(2015年C语言培训班笔记重读)

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐