c的详细学习(8)指针学习(二)

(1)指针与二维数组

一个数组的名字代表该数组的的首地址,是地址常量(作为形式参数的数组名除外),这一规定对二维数组或更高维数组同样适用。

在c语言中定义的任何一个二维数组实际上都可以看做是一个一维数组,该一维数组中的每一个成员又是一个一维数组。

若有定义int d[3][4],i,j;且0<=i<=2,0<=j<=3;

1.数组元素d[i][j]的表示方法为:

d[i][j]

*(d[i]+j)

*(*(d+i)+j)

(*(d+i))[j]

*(&d[0][0]+4*i*j)

*(d[0]+4*i+j)

2.数组元素d[i][j]地址的表示方法为:

&d[i][j]

d[i]+j

*(d+i)+j

&((*(d+i))[j])

&d[0][0]+4*i*j

d[0]+4*i*j

例如:设数组d的首地址是无符号十进制整数40000,则各表示形式、含义与地址的关系如下所示。其中地址值增减变化随程序运行环境的不同而不同

二维数组d各表示形式、含义与地址的关系

d                        二维数组名,数组首地址,第0行首地址        40000
d[0],*(d+0),*d           第0行第0列元素d[0][0]的地址              40000
d+1                      第1行首地址                             40008
d[1],*(d+1)              第1行第0列元素d[1][0]的地址              40008
d[1]+2,*d[+1]+2,&d[1][2] 第1行第2列元素d[1][2]地址                40012 

二维数组名d是指向行的,例如d+2表示二维数组d第2行的首地址;一维数组名d[0],d[1],d[2]是指向各自对应的列元素的,例如d[0]+2表示元素d[0]+2表示元素d[0][2]的地址。*d与*(d+1)都不代表任何数组元素。

例子:将a矩阵与b矩阵相加,所得之和存入c矩阵中。a,b,c矩阵都是3*4的矩阵

 #include<stdio.h>
 #include<stdlib.h>
 #include<time.h>

 int i,j;
 void maxtrix(int *x,int *y,int *z){
     for(i=0;i<3;i++){
         for(j=0;j<4;j++){
             *(z+i*4+j)=*(x+i*4+j)+*(y+i*4+j);
         }
    }
 }
 int main(){
     srand((int)time(NULL));
     int (*p)[3][4],a[3][4],b[3][4],c[3][4];
     printf("The value of a:\n");
     for(i=0;i<3;i++){
         for(j=0;j<4;j++){
           *(a[i]+j)=(int)rand%1000;
         }
     }
     printf("the value of b:\n");
     for(i=0;i<3;i++){
         for(j=0;j<4;j++){
           *(*(b+i)+j)=(int)rand%1000;
         }
     maxtrix(*a,b[0],&c[0][0]);
     }
     printf("The value of c:");
     printf("%d",a[2][1]);
     return 0;
 }

这个程序运行时会出现错误,以后再讨论。

3.指针数组

如果一个数组的元素都是指针变量,则称这个数组是指针数组。

指针数组定义的一般形式:

类型说明符 *数组名[]......

4.指针与字符串数组

字符数组中每个元素都是一个字符,而字符串数组中指的是数组中的每个成员都是存放字符串的数组。

1)直接给字符串数组赋初值

char b[4][8]={"Turbo C","FORTRAN","BASIC","Foxpro"};

char   b[][8]={"Turbo C","FORTRAN","BASIC","Foxpro"};

2)用给字符型指针数组赋初值的方式构成字符串数组

char *f[4]={"Turbo C","FORTRAN","BASIC","Foxpro"};

char *f[]={"Turbo C","FORTRAN","BASIC","Foxpro"};

实例:利用数字月份查找其英文月份名:

 1 #include<stdio.h>
 2
 3 char *month_name(int n){
 4     char *name[]={
 5     "Illegal month",
 6     "January","February","March",
 7     "April","May","June","July",
 8     "August","September","October",
 9     "November","December"};
10     return (n<1|n>12)?name[0]:name[n];
11 }
12
13 int main(){
14     int n;
15     scanf("%d",&n);
16     printf("%d month name is %s\n",n,month_name(n));
17     return 0;
18 }

5.指向数组的指针变量

指向数组的指针变量定义的一般形式:

类型说明符 (*变量名)[正整型常量表达式];

功能:定义一个名为"变量名"的指针变量,该指针变量所指向的是一个具有"正整型常量表达式"个元素的一维数组,该数组的每个元素都是"类型说明符"类型的。

例如:int (*p)[10],定义p是一个指针变量,它所指向的对象是一个具有10个元素的数组int型数组。

这种形式定义的指针一般用于指向二维的数组。

例如:int d[3][2],(*p)[2];p=d;

p=d;//p指向第0行,即指向整个数组d[0][0],d[0][1],d[0][2]......;

p++;//p指向第1行,即指向整个数组d[1][0],d[1][1],d[1][2]......;

(2) 二级指针

如果一个变量的值是其他变量的地址,而这些其他变量的值不再是内存地址,则这个变量是一级指针变量;

如果一个变量的值是一级指针的地址,则称这个变量是二级指针变量。

定义方法:

类型说明符   **标识符

int b,*m,**p;

m=&b;

p=&m;

实例1:二级指针输出整型数据:

 1 #include<stdio.h>
 2
 3 void swap(int **m,int **n){
 4     int *i;
 5     i=*m;
 6     *m=*n;
 7     *n=i;
 8 }
 9 int main(){
10     int a,b,*pa,*pb;
11     pa=&a;
12     pb=&b;
13     scanf("a=%d,b=%d",pa,pb);
14     swap(&pa,&pb);
15     printf("pa=%d,pb=%d\n",*pa,*pb);
16     printf("a=%d,b=%d",a,b);
17     return 0;
18 }

这里输入时要注意一个问题,就是输入时,要满足scanf语句中的格式,必须进行格式的完全匹配。

实例2:二级指针输出字符串型数据

 1 #include<stdio.h>
 2
 3 int main(){
 4     char **p,*address[]={
 5         "China","Japan","English",""
 6     };
 7     p=address;
 8     for(;**p!=‘\0‘;){
 9         printf("%s\n",*p++);
10     }
11     return 0;
12 } 

实例3:指针数组(区别于指向数组的指针)

 1 #include<stdio.h>
 2
 3 int main(){
 4   int x[5]={
 5    2,4,6,8,10
 6   };
 7   int *y[5];
 8   int **a,b,i;
 9   for(i=0;i<5;i++){
10       y[i]=&x[i];
11   }
12   a=y;
13   for(b=4;b>=0;b--){
14       printf("%3d",**a);
15     a++;
16   }
17   printf("\n");
18     return 0;
19 } 

本程序还可以写成下面的形式:

 1 #include<stdio.h>
 2   void s(int **a){
 3       int  b;
 4     for(b=0;b<5;b++){
 5       printf("%3d",**a);
 6       a++;
 7   }
 8   printf("\n");
 9 }
10 int main(){
11     int x[5]={
12         2,4,6,8,10
13     };
14     int *y[5],i;
15     for(i=0;i<5;i++){
16         y[i]=&x[i];
17     }
18     s(y);
19     return 0;
20 }

本例用二级指针变量输出了数组各元素的值。指针数组名作为函数实参时,形参得到的是实参指针数组的地址,这是一个二级指针。因此,形参可以写成int **a形式,也可以写成int *a[]的形式,两种方法等价。

(3)内存空间的动态分配

1.指向void类型的指针

void 类型是一种抽象的数据类型,如果用指针的void类型说明符定义一个指针变量,则该指针变量并没有被确定存储具体哪一种数据类型变量的地址。

void类型的指针和其他类型的指针可以相互赋值,且不必强制类型转换。

指向任何类型的指针都可以转换为指向void类型,如果将结果再转换为初始指针类型,则可以恢复初始指针类型并不会丢失信息。

 1 #include<stdio.h>
 2
 3 int main(){
 4     float a[4]={
 5       1,2,3,5
 6     },*p1,*p3;
 7     //测试void型指针,进行连续的类型转换
 8     void *p2=a;
 9     p1=(float *)p2;
10     p2=(void *)p1;
11
12     p3=&a[2];
13     p2=p3;
14     p3=(float *)p2;
15     printf("a=%p,p1=%p,p2=%p,p3=%p,*p3=%f",a,p1,p2,p3,*p3);
16     printf("\n");
17     return 0;
18 }

将void型指针赋值给其他类型时,仍然需要强制类型转换。如上述14行所示。

从运行的结果可以看出,指向void的类型的指针起到了通用指针的作用。

2.常用内存管理函数

在此之前编写的程序,在程序运行之前所需要的变量数或者数组的大小就已经确定了。

C语言提供了一些内存管理函数,这些内存管理函数可以在程序运行期间分配内存空间,即可动态的分配内存空间,也可以将已经分配的内存空间释放。常用的内存管理函数有calloc,malloc,free函数。

1)calloc函数的功能是向系统申请分配连续的内存空间,如果申请获得成功,则把所分配内存区域的首地址作为函数返回值返回,该函数返回的是void类型的指针;如果申请获得成功,则函数返回空指针。原型:void *calloc(unsigned n,unsigned size);

int *p;

p=calloc(20,sizeof(int));

该程序段的功能是申请一块连续的能保存20个int型数据的内存区域,并使p指向该块内存区域。

2)malloc的功能与calloc函数类似,malloc函数用于向系统申请分配一块连续的size个字节的内存区域。

函数的原型:void *malloc(unsigned size);

3)free函数的原型是void free(void *p);其功能是释放p所指向的由calloc函数或者malloc函数已经申请成功的内存区域,free没有返回值。

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3
 4 int main(){
 5     int i,n,sum=0,*p;
 6     printf("Please input a interger number imply that how many numbers you need to input next:\n");
 7     //scanf输入数据以空格结尾
 8     scanf("%d",&n);
 9     p=(int *)malloc(n*sizeof(int));//也可以改成p=malloc(n*size0f(int));//或者p=(int *)calloc(n,sizeof(int));
10     printf("please input %d number:\n",n);
11     for(i=0;i<n;i++){
12         scanf("%d",&p[i]);
13         sum+=p[i];
14     }
15     free(p);
16     printf("sum=%d\n",sum);
17     return 0;
18 } 

malloc和calloc函数用于创建动态数组,还与后续的结构体有关。

(4)main函数的参数

在此前写的main函数总是main(),实际上main()函数也可以有参数。

1.命令行参数

在操作系统提示符状态下为了执行某个操作系统命令或某个可执行文件,而键入的一行字符称为命令行。

命令行的一般形式为:命令名[参数1][参数2]......[参数n]

命令名与参数之间以及各参数之间用空格或者Tab键隔开,若参数本身含有空格,则给参数要用""包含起来。

例子:C>copy f1.c  f2.c

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

在操作系统下运行一个c程序,实际上就是操作系统调用该函数的主函数,主函数再调用其他函数。

主函数main的形参一般有两个参数,例如:

main(int argc,char *argv[]){};

第一个参数是int型,习惯上记作argc,表示命令行中参数的个数(包括命令名在内),在运行c程序时由系统自动计算出参数的个数,第二个参数是指向字符型的指针数组,习惯上记作argv,用来存放命令行中的各个参数。(作为形参,char *argv[]等于char **argv);名字是可以自定的。

实例:假设以下程序存放在xx.c文件中,编译之后已经生成一个xx.c文件:

 1 #include<stdio.h>
 2
 3
 4 int main(int argc,char *argv[]){
 5     int i=1;
 6     printf("argc=%d",argc);
 7     printf("argv= ");
 8     while(i<argc){
 9         printf("%s  ",argv[i]);
10         i++;
11     }
12     return 0;
13 } 

命令行:(当输入为)xx shenyang liaoning China

如何能让这个程序运行,并执行呢?后面再解决这个问题。

本例命令行共有四项,其中xx是执行程序的命令,其与三项是命令行参数,argc中存放的是命令行中包括命令在内的字符串的个数。argv中存放的是命令行各项的首地址。ANSI要求argv[argc]的值必须是一个空指针。

(5)指针的编程练习

1.将直角坐标系的一个坐标点(x,y),转换为极坐标系中的坐标值(p,角度):

 1 #include<stdio.h>
 2 #include<math.h>
 3
 4 //用变量的地址值当形式参数,改变对应的地址所在单元的值。
 5 double change(double x,double y,double *p){
 6     double m;
 7     m=sqrt(x*x+y*y);
 8     *p=atan(y/x);
 9     return m;
10 }
11  int main(){
12     double a,b,c,q;
13     printf("please input x,y:\n");
14     scanf("%lf%lf",&a,&b);
15     c=change(a,b,&q);
16     printf("c=%f,1=%f",c,q);
17     return 0;
18 }

2.将10个国家的名字按照字母由小到大的顺序排列:

 1 #include<stdio.h>
 2 #include<string.h>
 3 //用字符数组指针指向字符数组,作为形参
 4 void sort(char *name[],int n){
 5     char *temp;
 6     int i,j,k;
 7     for(i=0;i<n-1;i++){
 8         k=i;
 9         for(j=i+1;j<n;j++){
10             if(strcmp(name[k],name[j])>0)
11                 k=j;
12         }
13         if(k!=i){
14             temp=name[i];
15             name[i]=name[k];
16             name[k]=temp;
17         }
18     }
19 }
20 int main(){
21     int n=10,i;
22     char *name[]={
23       "China","America","France","Britain","Canada",
24       "Australia","Switzerland","Japan","Italy","Germany"
25     };
26     sort(name,n);
27     for(i=0;i<n;i++){
28         printf("%s\n",name[i]);
29     }
30     return 0;
31 }

3.编写程序,删除字符串的所有尾部空格:

 1 #include<stdio.h>
 2 #include<string.h>
 3
 4 int main(){
 5       char *p;
 6     char str[20];
 7     for(int k=0;k<20;k++){//将字符数组初始化为‘\0‘不是必须的。
 8       str[k]=‘\0‘;
 9     }
10       p=str;
11       printf("Please input a string:\n");
12       gets(p);
13       printf("打印一边字符串:");
14       puts(p);
15       //此语句用于将指针移到字符串末尾的‘\0‘处。
16       while((*p)!=‘\0‘){p++;}
17     //上述语句等价于while(p++!=‘\0‘);p--;
18     printf("字符串的长度为%d\n",p-str);
19     do
20       p--;
21     while(*p==‘ ‘);//此语句用于将指针位置移动到‘\0‘前面第一个不为空格的字符上。
22     *++p=‘\0‘;
23     printf("%s",str);//puts(str)等同于printf("%s\n",str);
24     return 0;
25 }

在做这个程序时,犯得错误有“指针前面忘了加*符,导致内存访问错误”,“printf和puts函数的功能的区别有点模糊”。

4.编写函数output和input,其功能分别于gets和puts相同,函数中分别用getchar()和putchar()读入和输出字符:

 1 #include<stdio.h>
 2 #include<string.h>
 3 void input1(char *s)
 4 {
 5    int i=0;
 6    char c;
 7    while((c=getchar())!=‘\n‘)
 8       s[i++]=c;
 9    s[i]=0;
10 }
11 void input2(char *a){
12     char c;
13     int i=0;
14     c=getchar();
15     while(c!=‘\n‘){
16         a[i++]=c;
17         c=getchar();
18     }
19 }
20 void output(char a[]){
21     while(*a!=‘\0‘){
22         putchar(*a++);
23     }
24 }
25 int main(){
26     char *p;
27     char str[20];
28     output("please input a string:\n");
29     input1(p);//同于input1;
30     output(p);
31 }

在本例中我出错的地方是getchar()函数用出错了,char c=getchar();我用成char c;getchar(c);

5.统计一个字符串中所有单词的个数:

 1 #include<stdio.h>
 2 #include<string.h>
 3
 4 int count(char *q){
 5     char *p=q;
 6     int flag=0;//用于返回的单词个数。
 7     //将指针指到字符数组的最后的‘\0‘的位置
 8     while(*p++!=‘\0‘);
 9     p--;
10
11     for(p--;q<=p;p--){
12       if(*p!=‘ ‘&&*(p-1)!=‘ ‘|*p==‘ ‘&&*(p-1)==‘ ‘){
13               continue;
14         }else if(*p!=‘ ‘&&*(p-1)==‘ ‘){
15             flag++;
16         }else if(*p!=‘ ‘&&p==q){
17             flag++;
18         }
19     }
20     return flag;
21 }
22
23 int main(){
24   char str[30],*p;
25   puts("Please input a string containing many space to divide them: ");
26   p=str;
27   gets(p);
28   printf("单词的个数为:%d",count(p));
29 } 

不知道应该写什么,自己怎么搞定的好像都不太清晰。醉了!

6.编写一个小学生做加减乘除的程序。例如在操作系统下,键入cal 15 * 15<回车>则在屏幕上显示15*15=225:

 1 #include<stdio.h>
 2 #include<string.h>
 3
 4 int main(int argv,char (*argc)[]){
 5     double a,b;
 6     char c;
 7     double result;
 8     a=(double)argc[1];
 9     c=argc[3];
10     if(strlen(argc[2])==1){
11         if(argc[2]=="+"){
12             result=a+b;
13         }else if(argc[2]=="-"){
14             result=a-b;
15         }
16         else if(argc[2]=="*"){
17             result=a*b;
18         }
19         else if(argc[2]=="/"){
20             result=a/b;
21         }
22         printf("%lf %s %lf=%lf",a,argc[2],b,result);
23     }else
24     {
25         printf("输入错误!\n");
26     }
27     printf("输入错误!\n");
28 } 

这种程序的设计方法还不太清楚,目前先写到这。

时间: 2024-07-30 08:50:05

c的详细学习(8)指针学习(二)的相关文章

【C语言学习】指针再学习(二)之数组的指针

★一维数组 一个整型数据为4个字节.4个字节就是32位,即可以表示2^32个数字 在程序中定义一个数组a[5] = {1,2,3,4,5}; 那么a[0]的地址就是0x00000000,数组名a是数组首元素的地址,a的地址也是0x00000000.a+1则表示的地址是0x00000004,而不是0x00000001.因为1这个值,根据前面的指针a所指向的类型的长度来调整自己的长度.也就是说如果a是指向整型的指针,那么后面加的1也表示4个字节,如果a是指向字符型的指针,那么后面加的1表示1个字节.

Golang 才是学习指针的安全之地,关于指针的二三事

感谢平台分享-http://bjbsair.com/2020-04-10/tech-info/53305.html 本章学习目标 学会声明和使用指针 理解指针和随机访问存储器(RAM)之间的关系 了解指针的使用时机 当我们在街上散步的时候,常常会看到一些用于指引方位的地址和街道标识.你可能曾经遇到过这样一种情况,一家大门紧闭的商店在它的橱窗上贴出了道歉标语"抱歉,本店已乔迁新址!",并在标语的下方给出新的地址.指针就有点儿像这个给出新地址的标语,它会把你指引至不同的地址. 指针是指向另

一个女大学生的代码学习之路(二)

首先说一下,写这种文章是由于我在四月四日晚上,在手动搭建自己的第一个ssh项目的时候,遇到了一个配置的问题,怎么解决也弄不好,当时是四号晚上九点,我看了一眼表,我就想两个小时之内,我要是能搞定就算行了,但是其实,我搞到三点才OK(凌晨),那时候已经是五号了,转天是一家子去扫墓的时候,结果我居然以这种一个理由没有去,理由是我太累了么?我只是就是搭了一个架子,就是由于我的包太混乱了,导致不兼容,所以tomcat总也不启动,你可能认为好笑,这么简单一个问题怎么就费这多多时间呢,但是作为一个刚接触三框架

kafka学习之路(二)——提高

kafka学习之路(二)--提高 消息发送流程 因为Kafka内在就是分布式的,一个Kafka集群通常包括多个代理.为了均衡负载,将话题分成多个分区,每个代理存储一或多个分区.多个生产者和消费者能够同时生产和获取消息.     过程: 1.Producer根据指定的partition方法(round-robin.hash等),将消息发布到指定topic的partition里面 2.kafka集群接收到Producer发过来的消息后,将其持久化到硬盘,并保留消息指定时长(可配置),而不关注消息是否

c 函数及指针学习 6

不完整声明 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 /* 方法一   */ struct tag_a{     struct tag_b *bp;  /* 这里struct tag_b 还没有定义,但编译器可以接受 */     int value; }; struct tag_b{     struct tag_a *ap;     int value; }; typedef struct tag

黑马程序员——c语言学习心得——函数传递二维数组

黑马程序员——c语言学习心得——函数传递二维数组 -------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 一.定义指针的时候一定要初始化.   变量定义的时候给变量初始化,这是保证不出错的一个很好的习惯.尤其是在指针的使用上,如果我们没有给指针初始化,就会出现野指针,该指针的指向并不是我们所希望的,一旦错误的释放了这个指针,就会发生内存的访问.那么如何初始化指针变量呢,一般有以下几种方法:   1.初始化空指针   int* pInteger=N

【Unity Shaders】学习笔记——SurfaceShader(二)两个结构体和CG类型

[Unity Shaders]学习笔记——SurfaceShader(二)两个结构体和CG类型 转载请注明出处:http://www.cnblogs.com/-867259206/p/5596698.html 写作本系列文章时使用的是Unity5.3. 写代码之前: 当然啦,如果Unity都没安装的话肯定不会来学Unity Shaders吧? 阅读本系列文章之前你需要有一些编程的概念. 在VS里面,Unity Shaders是没有语法高亮显示和智能提示的,VS党可以参考一下这篇文章使代码高亮显示

【Linux】鸟哥的Linux私房菜基础学习篇整理(二)

1. dumpe2fs [-bh] devicename:查询superblock信息.参数:-b:列出保留为坏道的部分:-h:列出superblock的数据,不会列出其他的区段内容. 2. df [-ahikHTm] 目录或文件名:列出文件系统的整理磁盘使用量.参数:-a:列出所有的文件系统,包括系统特有的/proc等文件系统:-k:以KB的容量显示各文件系统:-m:以MB的容量显示各文件系统:-h:以人们易阅读的GB.MB.KB等格式自行显示:-H:以M=1000K替代M=1024K的进位方

c指针学习小结(参考别人总结的经验)

指针学习与总结一.1.int *p :p与*先结合,说明p是一个指针,然后与int结合说明指向的是一个int型的.2.int p[3] :p与[]结合说明p是一个数组,然后与int结合,说明数组里的元素是int型的.3.int *p[3]: p与[]结合说明p是一个数组,后与*结合,说明数组的指针,后与int型结合,说明指针指向的是int型的.4.int (*p)[3] :p与*结合,说明p是一个指针,后与[]结合说明指向的是一个数组,与int结合说明数组的元素是int型.5.int **p :