zoukankan      html  css  js  c++  java
  • C语言--->指针

    指针的运算

    • 当两个指针p1, p2相减时,p2-p1就是从p1p2,不包含p2的元素个数,结果的类型是ptrdiff_t
    #include <stdio.h>
    int main()
    {
        int a[10] = {1,2,3,4,5,6,7,8,9,0};
        int sub;
        int *p1 = &a[2];
        int *p2 = &a[8];
    
        sub=p2-p1;                                                                            
        printf("%d
    ",sub);    // 输出结果为 6
    
        return 0;
    }
    

    指针与数组(数组指针)

    先来定义如下的二维数组:

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

    a的意义

    首先,对于一个数组而言,数组名就是该数组的首地址。

    首地址:一段存储空间中的第一个存储单元的地址

    所以对于这个二维数组 a[3][4],数组名a指向的就是第一个数组,用如下代码可以进行验证:

        printf("a=%p
    ", a);
    	printf("a+1=%p
    ", a + 1);
    	printf("a+2=%p
    ", a + 2);
    

    输出如下:

    a=000000C63835F628
    a+1=000000C63835F638
    a+2=000000C63835F648
    

    可以看到,每+1地址递增16
    a是数组名,是该数组的首地址,
    指向该数组的第一个存储单元(一个一维数组),a类型为 int(*)[4]
    所以a+1 会跳到第二个数组,地址加上16B

    *a的意义

    	printf("*a=%p
    ", *a);
    	printf("*a+1=%p
    ", *a + 1);
    	printf("*(a+1)=%p
    ", *(a + 1));
    

    输出如下:

    *a=000000C63835F628
    *a+1=000000C63835F62C
    *(a+1)=000000C63835F638
    

    可以看到,每+1地址递增4
    *a指向以一个一维数组的首地址即

    *a==a[0]==&a[0][0]

    所以*a+1,地址会偏移4B,即指向下一个数据,

    *a类型为int*

    *(a+1),地址会偏移16B,即指向下一个一维数组的首地址。

    &a的意义

    	printf("&a=%p
    ", &a);
    	printf("&a+1=%p
    ", &a + 1);
    	printf("&(a+1)=ERORR
    ");
    

    输出如下:

    &a=000000C63835F628
    &a+1=000000C63835F658
    &(a+1)=ERORR
    

    &a指向整个二维数组,是取这个二维数组的地址。

    &a类型为 int(*)[3][4]

    &a+1 地址偏移了48B,跳过了整个二维数组

    &a[0]的意义

    	printf("&a[0]=%p
    ", &a[0]);
    	printf("&a[0]+1=%p
    ", &a[0] + 1);
    	printf("&a[0]+1=%p
    ", &a[0]);
    	printf("&(a[0]+1)=ERORR
    ");
    

    输出如下:

    &a[0]=000000C63835F628
    &a[0]+1=000000C63835F638
    &a[0]+1=000000C63835F628
    &(a[0]+1)=ERORR
    

    &a[0]指向第一个数组,是取第一个数组的地址

    &a[0]类型为 int(*)[4]

    &a[0]+1 地址偏移了16B,跳过了第一个一维数组

    a[0]的意义

    	printf("a[0]=%p
    ", a[0]);
    	printf("a[0]+1=%p
    ", a[0] + 1);
    	printf("&a[0][0]%p
    ", &a[0][0]);
    

    a[0]是第一个数组的数组名,是第一个数组的首地址,即a[0]指向指向第一个存储单元a[0][0]

    a[0]类型为 int*

    a[0]+1,指向了第二个存储单元,地址偏移了4B
    &a[0][0],是指向a[0][0]的指针,

    &a[0][0]类型为 int*

    指针数组

    int *p[10]
    

    [] 的优先级比 * 高,故 p 先与 [] 结合,成为一个数组 p[];再由 int * 指明这是一个 int的指针。数组的第 i 个元素是 *p[i],而 p[i] 是一个指针。

    数组指针

    int (*p)[10]
    

    由于 () 的优先级最高,所以 p 是一个指针,指向一个 int 类型的一维数组,这个一维数组的长度是 10,这也是指针 p 的步长。也就是说,执行 p+1 时,p 要跨过10int 型数据的长度。数组指针与二维数组联系密切,可以用数组指针来指向一个二维数组,如下:

    #include <stdio.h>
     
     int main()
     	{
         	int arr[2][3] = 
    		    {
    			 {1,2,3},
    			 {4,5,6}
    			};             // 定义一个二维数组并初始化
    
         	int (*p)[3];       // 指针指向一个含有3个元素的一维数组
     
        	p = arr;           // p 指向 arr[0]==&arr[0][0]
         	printf("%d
    ",(*p)[0]);  // 输出结果为 1
        	p++; 
         	printf("%d
    ",(*p)[1]);  // 输出结果为5
    		return 0;	
     	}
    

    访问数组中的元素

    1. 下标法 printf("a[i][j] ");

    	printf("a[i][j]
    ");
    	for (int i = 0; i < 3; i++)
    	{
    		for (int j = 0; j < 4; j++)
    			printf("%5d", a[i][j]);
    
    		printf("
    ");
    	}
    

    2. 指针法

    1. printf("1:*(a[i]+j) ");
    	for (int i = 0; i < 3; i++)
    	{
    		for (int j = 0; j < 4; j++)
    			printf("%5d", *(a[i] + j));
    		printf("
    ");
    	}
    
    1. printf("2:*(*(a+i)+j) ");
    	for (int i = 0; i < 3; i++)
    	{
    		for (int j = 0; j < 4; j++)
    			printf("%5d", *(*(a + i) + j));
    		printf("
    ");
    	}
    

    亦即a[i][j]==*&a[i][j]==*(a[i]+j)==*(*(a+i)+j)

    综合上面的分析,对于二维数组a[3][4]有如下结论:

    表一

    表达式 数据类型 指向
    a==&a[0] int(*)[4] 均指向第一个一维数组
    a[0]==&a[0][0] int* 均指向第一个一维数组的第一个单元
    &a int(*)[3][4] 指向整个二维数组
    *(a+i)=a[i]=&a[i][0] int* 指向数组i的第一个存储单

    表二

    运算 意义
    a+1 a 指向第一个一维数组,所以a+1地址偏移4x4=16B
    *a+1 *a指向第一个数组的第一个单元,所以*a+1地址偏移4B
    *(a+1) a+1 指向下一个数组,*a==a[0]---->*(a+1)==a[1]所以a+1地址偏4x4=16B
    &a+1 &a 指向整个二维数组,所以&a+1地址偏移4x4x3=48B
    &(a+1) 数组名a是指针常量,不能更改了,此种写法错误
    &a[0]+1 &a[0] 指向第一个数组,&a[0]+1---->&a[1],指向下一个数组,所以地址偏移4x4=16B
    &(a[0]+1) 此种写法错误
    a[0]+1 a[0] 指向第一个数组的首地址a[0][0],所以a[0]+1指向下一个数据a[0][1],地址移4B

    数组指针

    类型 (*指针名)[N]; //N元素个数
    数组指针是指向含 N 个元素的一维数组的指针。由于二维数组每一行均是一维数组,故通常使用指向一维数组的指针指向二维数组的每一行。

    • 注意:[]运算的优先级高于*int *p[N]为指针数组,每个元素类型为 int*
    #include<stdio.h>
    int main()
    {
    	int a[3][4];
    	int(*p)[4]=a;//a是首地址,指向一维数组,类型为int(*)[4]与p吻合
    	//第i行首地址 p+i==a+i,其余操作与上文一直
    }
    

    指针数组

    指针数组最主要的用途是处理字符串。在 C 语言中,一个字符串常量代表返回该字符串首字符的地址,即指向该字符串首字符的指针常量,而指针数组的每个元素均是指针变量,故可以把若干字符串常量作为字符指针数组的每个元素。通过操作指针数组的元素间接访问各个元素对应的字符串。

    #include<stdio.h>
    #include<stdlib.h>
    //#define NULL ( (void*) 0)
    int main()
    {
        char *c[]={"if","else","for","while",NULL};
        for(int i=0;c[i]!=NULL;i++)
            puts(c[i]);
        system("pause");
        return 0;
    }
    

    注意:

    1. 首地址:一段存储空间中的第一个存储单元的地址
    2. 分析指针:关键不在指针的值,而实指针的类型及其指向
    3. *a==a[0]==&a[0][0]
    4. 访问数组元素:
    • 下标法
    • 指针法
      *(a[i]+j)==*(*(a+i)+j)

    结构指针

    结构指针是指向结构的指针,使用 -> 操作符来访问结构指针的成员。

    #include<stdio.h>
    typedef struct{
    	char name[10];
    	int age;
    	int score;
    }message;
    int main()
    {
    
    	message mess={"elio",18,92};
    	message *p=&mess;
    	printf("%s
    ",p->name);//输出elio
    	printf("%d
    ",p->score);//输出92
    
    	return 0;
    }
    

    指针与函数

    C语言的所有参数均是以“传值调用”的方式进行传递的,这意味着函数将获得参数值的一份拷贝。这样,函数可以放心修改这个拷贝值,而不必担心会修改调用程序实际传递给它的参数。

    指针作为函数的参数

    • 传值调用:实参为要处理的数据,函数调用时,把要处理数据(实参)的一个副本复制到对应形参变量中,函数中对形参的所有操作均是对原实参数据副本的操作,无法影响原实参数据。且当要处理的数据量较大时,复制和传输实参的副本可能浪费较多的空间和时间。

    • 传址调用:顾名思义,实参为要处理数据的地址,形参为能够接受地址值的“地址箱”即指针变量。函数调用时,仅是把该地址传递给对应的形参变量,在函数体内,可通过该地址(形参变量的值)间接地访问要处理的数据,由于并没有复制要处理数据的副本,故此种方式可以大大节省程序执行的时间和空间。

    • 传值调用的好处是是被调函数不会改变调用函数传过来的值,可以放心修改。但是有时候需要被调函数回传一个值给调用函数,这样的话,传值调用就无法做到。为了解决这个问题,可以使用传指针调用。指针参数使得被调函数能够访问和修改主调函数中对象的值。

    #include <stdio.h>
    void swap1(int a,int b) // 参数为普通的 int 变量
    {
    int temp;
    temp = a;
    a = b;
    b = temp;
    }
    void swap2(int *a,int *b) // 参数为指针,接受调用函数传递过来的变量地址作为参数,对所指地址处的内容进行操作
    {
    int temp; // 地址本身并没有改变,地址所对应的内存段中的内容发生了变化
    temp = *a;
    *a = *b;
    *b = temp;
    }
    int main()
    {
    int x = 1,y = 2;
    swap1(x,y); // 将 x,y 的值本身作为参数传递给了被调函数
    printf("%d %5d
    ",x,y); // 输出结果为:1 2
    swap(&x,&y); // 将 x,y 的地址作为参数传递给了被调函数,传递过去的也是一个值,与传值调用不冲突
    printf("%d %5d
    ",x,y); // 输出结果为:2 1
    return 0;
    }
    

    指向函数的指针

    指针做函数返回值 类型*函数名(形参)

    有时函数调用结束后,需要函数返回给调用者某个地址即指针类型,以便于后续操作,这种函数返回类型为指针类型的函数,通常称为指针函数。在处理字符串中常见。

    #include<stdio.h>
    #include<stdlib.h>
    char *link(char*str1,char*str2);
    int main()
    {
        char s1[20]="Chinese";
        char s2[10]="Dream";
        char *p=link(s1,s2);
        puts(p);
        system("pause");
        return 0;
    }
    char *link(char*str1,char*str2)
    {
        char*p1=str1;
        char*p2=str2;
        while(*p1!='')
            p1++;//结束时p1指向字符串str1的结尾
        *p1=' ';
        p1++;
        while(*p2!='')
        {
            *p1=*p2;
            p2++;
            p1++;//*p1++=*p2++
        }
        return str1;
    }
    

    指向函数的指针————函数指针

    函数像其他变量一样,在内存中也占用一块连续的空 间,把该空间的起始地址称为函数指针。而函数名就是该空间的首地址,故函数名是常量指针。可把函数指针保存到函数指针变量中。

    返回类型(*指针变量名)(函数参数表);
    定义中,括号不能省略。

    int *p1(int,int)//声明了函数原型,函数名为p1,含有俩int参数,返回值int*
    int (*p2)(int,int)//定义了一个函数指针变量p2,p2指向任意含有俩int参数,返回值为整型的函数
    

    定义如下函数

    int f1(int a,int b)
    {
    	//...
    }
    p2=f1//p2=&f1;
    

    在给函数指针变量赋值时,函数名前面的取地址操作符 & 可以省略。因为在编译时,C 语言编译器会隐含完成把函数名转换成对应指针形式的操作,故加 & 只是为了显式说明编译器隐含执行该转换操作。

    当函数指针变量p2被初始化,指向f1之后,调用f1(),有以下几种方式

    int res;
    res=f1(a,b);
    res=p1(a,b);
    res=(*p1)(a,b)
    

    下面的程序,是一个应用函数指针的例子。

    #include<stdio.h>
    #include<stdlib.h>
    void cal(void(*ptr)(int,int),int op1,int op2);
    void add(int a,int b);
    /* sub mult div */
    int main()
    {
        int num;
        int a,b;
        printf("Operation menu:
    ");
    //    printf("1 for add       2 for sub
    ");
    //    printf("3 for mult      4 for div
    ");
        printf("Enter the operator:");
        scanf("%d",&num);
        printf("Input 2 numbers:
    ");
        scanf("%d %d",&a,&b);
        switch (num){
        case 1:
            cal(add,a,b);
            break;
        /*codes*/
        default:
            printf("Input error!");
        }
        system("pause");
        return 0;
    }
    void cal(void(*ptr)(int a,int b),int op1,int op2){
        ptr(op1,op2);
    }
    
  • 相关阅读:
    数据库——事务
    数据库——连接池
    JDBC——使用jdbc操作时,如何提取数据的性能
    2019-06-26 The Eclipse executable launcher was unable to locate its companion launcher
    2019-06-24在windows下如何查看80端口占用情况?是被哪个进程占用
    2019-06-19_解决ActiveMQ访问出现503的错误
    2019-06-17 tomcat启动;zookeeper启动批处理
    2019-06-13记忆 长命令
    2019-06-13linux的vi编辑器中如何查找关键字
    2019-06-13-vim配色步骤
  • 原文地址:https://www.cnblogs.com/oasisyang/p/13218506.html
Copyright © 2011-2022 走看看