【指针】(适合考研、专升本)

指针

  • &与*是两个作用相反的运算符。

  • 二级指针只能保存一级指针变量的地址和指向指针数组,其余情况不考虑。

        int *p[2];int a=12;int b=15;*p=&a;*(p+1)=&b;printf("%d\n%d\n",**p,**(p+1));int **r=p;printf("%d\n",**r);

  • 普遍变量名是一个值,数组名等属于一个指针常量(即前者为一个值,后者是一个地址)

  • 对于二维字符数组,话不多说看代码

        char p[][5]={"1951","hell","than"};printf("%s\n",*p);//1951
    ​char p2[][5]={"19512","hell0","thank"};printf("%s\n",*p2);//19512hell0thank
    ​char p2[][5]={{'1','9'},"hell0","thank"};printf("%s\n",*p2);//19
  • 使用%s输出字符串时,printf第二个参数必须是地址

  • sizeof(指针)的值是4(个别操作系统是8),因为指针的值实际上是无符号整数(unsigned int),注意指针常量除外

        int arr[10];printf("%d\n",sizeof(arr));//40int *p=arr;printf("%d\n",sizeof(p));//4
  • 指针数组就是保存指针的数组(每一个数组元素都是一个地址)

    int b=10,c=23,*a[2]={&b,&c};
    printf("%d\n",**a);//10
  • 指针在做函数参数带回返回值时,在函数内对该指针指向的地址进行更改可以影响到外面的指针,但是对该指针的值进行改变不会影响外面的指针

    void fn(int *p,int A){p=&A;//首先对p进行赋值,不会影响到外面的值,其次形参A在函数调用后地址会销毁。
    }
    int main(){int *p=NULL;int A=12;fn(p,A);printf("%d\n",*p);//报错!
    }
  • 函数使用指针带回返回值时,对于一级指针来说有以下两种用法(切记不能初始化NULL再代入)

        int a,*p=&a;fn(&a);//或者pprintf("%d\n",*p);//12
  • 初始化为NULL的指针不允许访问值和改变。

        int *p=NULL;printf("%d\n",*p);//报错

初识指针与指针变量

指针:指针就是地址。(因地址指向变量的存储单元,故形象的将地址称为指针)

指针变量:用于保存地址的变量。指针变量的值就是地址,用来指向一个变量、函数、数组等。(即指针)

  • c语言对变量的访问本质上都是通过地址访问的,分为直接访问间接访问(每定义一个变量,系统自动为该变量分配内存空间,变量名指向的是这片内存空间的首地址,故直接访问直接引用变量名,间接访问只是使用指针变量保存这个地址再通过引用指针访问)

  • 内存以字节为单位,一个字节就是一个内存单元。例如一个int基类型变量占用4个存储单元。

  • c语言的内存地址是一个无符号整数。

  • 普通变量前面不要加*就可以按地址访问是因为普通变量不是指针变量(数组名是一个指针常量)

  • c语言对变量的访问实质上是访问变量所占用的内存单元。

指针变量的定义与初始化

定义:

格式:数据类型 *标识符例如 int *p;
指针变量的初始化可以在定义时初始化,也可以定义之后初始化(两者无条件等价)例如 int *p=&a;      int *p;p=&a;(注意这里不能使用*p)  
指针变量初始化指向的对象一般是变量、数组、函数等
  • 在定义时*是区别指针变量与其他变量的字符。调用时表示指针运算,即取指向对象的值或该对象。算数表达式中表示乘

  • 指针变量的最左边的数据类型称为指针变量的基类型,它指定该指针变量可以指向的变量类型,不能省略(即指针变量是基本数据类型派生出来的类型,不能离开基类型而单独存在)

  • 指针变量只能指向同类型的变量,所以初始化时应保证=两边的数据类型一致。(指针变量保存的是变量在内存中的首地址,地址都是无符号整型。例如int类型指针可以保存一个float类型变量的地址,因地址都是无符号整型,但是这个int指针却无法指向这个float类型变量,因数据类型不同)

  • 在说明一个指针时要说明其是指向什么类型的指针

  • 指针变量只能保存地址,即不能赋常量或自己给一个地址。但是空指针允许赋值为0或者NULL。例int *p=0(NULL)。

    此外int类型指针初始化一个常量值编译可以通过,但是概念上是错误的。例如 int *p=100。

  • 未初始化的指针变量指向的是一个随机地址。

  • 切记不要使用未初始化的指针

    int main(){void fn(int *);int *s;fn(s);//报错,因为传入的指针s未初始化,指向一个随意的空间
    }
    ​
    void fn(int *s){*s=12;
    }
  • 常见的指针初始化

    指针=&变量名
    指针=数组名
    指针=指针
    指针=NULL(0)

    NULL是标准头文件<stdio.h>中定义的符号常量

  • 所有类型的指针变量的内存占用都是4。(unsigned int)

  • 使用指针常见的错误:

    1.使用未初始化的指针
    2.指针赋常量值
    3.间接访问void类型指针
    4.指针与初始化对象类型不一致。
  • 未初始化的指针不能使用,但是未初始化的指针的地址可以使用。

    void fn(int **p,int *b){*p=b;**p=123;
    }
    ​
    int main(){int a=12,*p;fn(&p,&a);printf("%d\n",*p);
    }
  • 指针做函数参数时,指针本身的值不能改变,但是可以改变指针下面变量的值或指针的指向。

fun(int *p){*p=12;
}
int main(){int a;fun(&a);//传入a无法改变a的值,但是传入a的地址可以。即指针做函数参数时,指针本身的值不能改变,但是可以改变指针下面变量的值或指针的指向。一集指针:如果函数参数是指针变量,那么不能改变指针变量的指向,此时是因为指针本身是按值传递的,但可以改变指针指向的变量的值。如果是指针常量(即上面那样,一个变量的地址),那么也不能改变指针本身的值,但是此时的原因是因为是常量,依然可以改变其指向的变量的值二级指针:如果函数参数是指针,那么不能改变二级指针的指向,但是可以改变二级指针保存的一集指针的地址的指向,跟甚至改变这个一集指针指向的变量的值。如果是指针常量(一个一集指针的地址),那么此时也不能改变指针本身的值,它也属于一个常量,是一集指针在内存中的地址,这个是无法改变的由系统分配的,但依然可以改变其指向的指针的值,跟甚至改变这个一集指针指向的变量的值。
}

指针变量的引用

引用:

对指针变量的引用分为三种情况:
1.给指针变量单独初始化例如 int *p;p=&a;(注意这里不能加*,否则表示的是该指针指向的对象)
2.引用指针变量指向对象的值:例如  int *p=&a;*p=100;printf("%d\n",*p);
3.引用指针变量的值(一般没意义)例如  int *p=&a;printf("%d\n",p);(等价于&a)
  • 访问指针指向对象的值可以使用*也可以使用[]。(其中[]里面的值可以是负数)

    注意:再引用时*右边与[]左边的操作数必须是地址(指针),且已指向明确的对象。

    int a=10086,*pa=&a;
    printf("a=%d\t*pa=%d\tpa[0]=%d\n",a,*pa,pa[0]);//a=10086      *pa=10086       pa[0]=10086
    //即*pa指向的a,pa[0]也指向a(常见于数组的指针)
    int arr[4]={1,2,3,4};
    int *p=arr,*p2=&arr[2];
    printf("*p=%d\t*p2=%d\n",p[0],p2[0]);//*p=1    *p2=3
    printf("%d\n",p2[-1]);//2
  • 以 int *p为例,p表示地址或该指针变量,*p则表示该地址保存的值或指向的对象。

    一般情况下:

    当p出现在=左边时表示对指针变量赋值或重新赋值,即改变指向的对象。相反在右边时则表示引用其保存的地址(即给另一个指针)

    int a=123,*pa=&a,b=456,*pb=&b;
    printf("%d\t%d\n",*pa,*pb);//123  456
    pb=pa;//改变pb指向a(因为pa指向a)
    printf("%d\t%d\n",*pa,*pb);//123  123

    当*p出现在=左边时表示引用其所指向的对象,即改变该对象的值。相反在右边则表示引用该对象的值。

    int a=123,*pa=&a,b=456,*pb=&b;
    printf("%d\t%d\n",*pa,*pb);//123 456
    *pb=*pa;//改变变量b的值为a的值
    printf("%d\t%d\n",a,b);//123  123

    其余情况大多引用指针所指向的值(即*p),因为讨论指针的值(地址)一般没有意义。一种特殊情况是scanf函数的地址可以直接

    放指针变量,因为指针本就是地址。(即p等价&a,*p等价a与p[0])

    1.常见的printf("%d\n",*p)、int a=*pa+*pb等等
    ​
    2.int a,*pa=&a;scanf("%d",pa);(该语句等价于scanf("%d",&a)语句与scanf("%d",&*pa)语句)//注意这里&*pa(*pa等价a,变量赋值需加&)printf("a=%d\n",a);
  • 未初始化的指针指向一个不确定的内存空间,所以c不允许使用未初始化的指针

    特别注意如果函数返回的值是用指针传出的话,函数内不能创建一个指针,让这个需要带返回值的指针指向这个函数内的指针或他俩指向同一片空间,因为函数结束后这里面创建的指针会销毁,导致返回值指针指向一个销毁的空间(数据结构链表与现线性表)

  • void类型指针可以指向任何类型,但必须强制转换后才能使用

    其他类型也可以保存void类型指针,使用时候不需要强制转换。

       int a=12;void *m=&a;int *x=m;printf("%d\n",*x);//void类型地址给int会自动类型转换printf("%d\n",*(int *)m);//int类型地址给void不会自动转换

指针变量作函数参数

  • 若函数的参数为指针,则声明时候可省略形参标识符,但是要加*(数组要加[])。例如 int fn(int *)。

  • 函数参数为指针时,可以通过形参指针改变实参指针指向的对象的值,因为两者指向同一地址。无法通过形参指针改变实参指针的值(地址),因为两者在参数传递上仍然属于值传递。

  • 指针作为函数参数,可以得到多个变化的值。(函数只能返回一个值)

通过指针引用数组

  • []其实是变址运算符,即将 a[i] 按 a+i 计算地址,再找出此地址保存的值(数组下标表示法arr[i]其中arr不是数组名,而是指针常量)

    访问数组元素:arr[4](arr表示数组首地址,arr+4后移4位即第5个元素)
    指针变量也可以用[]访问:int *p=&a,printf("%d",p[0])。p代表a的地址,a+0仍表示a(注意p[0]表示是指向的对象,则&p[0]表示该对象的地址,但是不能引用这个地址进行赋值,因为=左边只能是一个变量)

指向数组元素的指针

  • c语言中,数组名代表数组中的首元素地址,不代表整个数组。所以 int *pa=&arr[0] 等价于 int *p=arr。注意一般数组名属于一个指针常量,即它表示一个数组的首地址,这个值是不允许改变的,即不允许进行++、--、+=、-=运算。但是作为函数形参数组名相当于一个指针变量,这个值可以改变

    int main(){int arr[]={1,5,3};arr=&arr[1];//首先数组不允许这样重新赋值,其次数组名是一个指针常量,常量不允许重新赋值,所以这里会报错printf("%d\n",*(arr++));//因为arr不允许进行++、--,所以这里也报错void fn(int []);fn(arr);
    }
    void fn(int arr[]){printf("第一次指向:%d\n",*arr);//1arr=&arr[1];printf("第二次指向:%d\n",*arr);//5printf("还允许进行++运算:++arr=%d\n",*(++arr));//3
    }
  • 从二维的角度看,二维数组名代表的是首行首地址,即指向的是整个数组。每行是一个一维数组,这些一维数组的数组名代表该行首列地址。

    int arr[2][3]={{1,2,3},{4,5,6}};
    printf("%d\n",arr);//arr指向的是第一个一维数组arr[0],是一个地址
    printf("%d\n%d\n",*arr[0],*arr[1]);//1	4
  • 二维数组名的基类型是一个一维数组(数组名都属于指针常量),面向的是第一行整个一维数组,而第一个一维数组基类型是一个数组元素,面向的是一个普通变量,所以虽然两者都指向二维数组的首地址(行指针列指针都指向首行首地址,但面向对象不同),但是前者属于行地址,前面加*就变成了列地址,列地址前面加&又变成了行地址,列地址前面再加一个*才表示列地址保存的对象

    int arr[2][3]={{1,2,3},{4,5,6}};
    printf("%d\n",arr);//arr指向arr[0][0],但是值是一个行地址,即面向的是整个一维数组arr[0]
    printf("%d\n",*arr);//*arr指向arr[0][0],但是值是一个列地址。即面向的一维数组arr[0]的首地址
    printf("%d\n",**arr);//**arr指向arr[0][0],值是一个数组元素。输出数组元素arr[0][0]
    printf("%d\n",&(*arr));//*arr指向arr[0][0],但是值是一个列地址,前面加上&表示行地址,输出一个地址
    结论:arr是行地址,*arr是列地址,&(*arr)又表示行地址,**arr表示列地址指向的对象

指针的运算

说明:指针就是地址,所以对地址的运算是没有意义的,但是当指针指向一片连续的存储单元时(例如数组),对指针的+、-运算是有意义的。

(1)当指针指向数组时下列运算有意义:以 int *p=&arr[3] 为例
  • 加一个整数(+、+=)。例如 p+1表示指针后移1位,指向下一个数组元素

  • 减一个整数(-、-=)。 例如 p-1表示指针前移1位,指向上一个数组元素

  • 自加运算。例如 p++,++p

  • 自减运算。例如 p--,--p

  • 两个指针相减。例如 p1-p2 表示两个地址间隔之差(p1、p2必须指向同一个数组,c不允许两地址相加,例 p1+p2)

  • 两个指针的关系运算,例如 p1>p2:值为1是表示p2在p1前面。值为0时表示p1在p2前面。

p1==p2:值为1表示p1与p2是同一个数组元素。值为0时表示p1与p2不是同一个元素。

p1 !=P2:值为1表示p1与p2不是同一个数组元素。值为0时表示p1与p2是同一个元素。

(2)引用指向数组的指针注意事项:
  • 数组名是一个指针常量不允许进行++、--、+=、-=等重新赋值的操作,但是允许进行+、-运算。(包括一维数组与二维数组,因为都属于指针常量)

    int arr[]={1,5,3,10,78};
    int *p=arr+1;
    printf("%d\n",*p);//5
  • 以下为例说明(++*p计算得是指针指向的对象进行自增,而🌟p++是指针地址指向的位移)

    int arr[4]={1,5,89,45};
    int *p=arr;
    printf("%d\n",p+1);//1834234068(地址)
    printf("%d\n",*p+1);//2
    printf("%d\n",*(p+1));//5
    printf("%d\n",*p++);//1(++与*优先级相同,则右结合,此时先打印p当前指向的值,执行完后p指向arr[1])
    printf("%d\n",*(p++));//5(先打印当前值,执行完后p指向arr[2])
    printf("%d\tarr[0]=%d\n",++(*p),arr[0]);//90   arr[0]=2
    printf("%d\tarr[0]=%d\n",++*p,arr[0]);//91     arr[0]=3结论:*p++与*(p++)加不加括号一样,因为优先级相同,但是计算的是指针位移后的值(注意与++*p的区别)++*p与++(*p)加不加括号都一样,因为优先级相同,但是计算的是指针指向的元素++(注意与*p++的区别)*p+1与*(p+1)运算过程不一样,前者计算p当前数组元素的值+1,后者是p下一个数组元素的值	 
  • ++、--、+=、-=运算会改变指针的指向,+、-运算则不会改变指针的指向

  • 引用数组元素可以使用arr[i]、*(arr+i)、*(p+i)、p[i]。

  • 对于指向二维数组元素的指针,数组名的移动是行的移动,一维数组名名的移动是列的移动

    int arr[2][3]={{1,2,3},{4,5,6}};
    printf("%d\n",**(arr+1));//4
    printf("%d\n",*(arr[0]+1));//2
  • 指针常量不能进行++、--、+=、-=等操作,但是其指向的值可以进行这些操作。

    int arr[4]={1,5,89,45};
    *arr=12;
    printf("%d\n",*arr);//12
    (*arr)++;
    printf("%d\n",*arr);//1
  • 注意:

        int a=1,b=4;int *p1=&a,*p2=&b;a=(*p1)++;b=(*p2)++;printf("%d\n%d\n",a,b);//1 4printf("%d\n%d\n",*p1,*p2);//1 4(在这种自己=自己++,后面的++不会改变自身的值)int a=1,b=4;int *p1=&a,*p2=&b;a=(*p2)++;b=(*p1)++;printf("%d\n%d\n",a,b);//5 4printf("%d\n%d\n",*p1,*p2);//5 4(这种自己=别人++,自身的值=别人的值,然后别人的值++)

指向整个数组的指针

定义:

数据类型 (*标识符)[数组长度]例如 int (*p)[4]=&a(a是一个一维数组)
  • 指向整个数组的指针基类型是一个数组,不能赋一个数组元素。例如上面不能写成 int (*p)[4]=a

  • 指向整个数组的指针也类似一个行指针,所以(a是一个一维数组名,属于列指针)前面加&转变为行地址。

  • 如果是一个二维数组,那么指向整个数组的指针可以赋值二维数组名,因为二维数组名就是指向第一个一维数组。

       int arr[2]={3,9},arr2[2][2]={2,4,6,8};int (*p)[2]=arr2;printf("%d\n",**(p+1));//6
  • 注意指向整个一维数组的指针[]中间的长度不能省略。

  • 以前的错误:

通过指针引用字符串

  • 指向字符串的指针初始化有两种方法

    第一种方法:(pa可以+、-、++、--、+=、-=。*pa可以+、-,不允许进行++、--、+=、-=操作,因为属于改变常量)char *pa="A23";printf("%c\n",*(pa+1));//2printf("%c\n",*(pa+=1));//2printf("%c\n",*(++pa));//3printf("%c\n",*pa+1);//4printf("%c\n",*pa++);//3printf("%c\n",*pa+=1);//常量不允许改变第二种方法:(对pb的改变实际上对字符数组的位移(++、--、+、-、+=、-=),对*pb的改变实际上是对*pb指向的值的改变(++、--、+、-、+=、-=)char str[]="1235";char *pb=str;printf("%c\n",*(pb++));//1printf("%c\n",*(pb+=1));  // 3printf("%c\n",*(pb+1));//5printf("%c\n",*pb+1);//4printf("%c\n",*pb++);//3printf("%c\n",*pb+=1);//6结论:对于字符指针于指针数组,可以对地址进行++、--、=、-、+=、-=等操作,但是不能对地址的值进行==、--、+=、-=。以下是两种方法使用*p改变值的对比:
    char *pa="1639";
    pa++;//能位移
    printf("%c",*(pa+1));
    *pa='4';//但是不能重新赋值
    pa="456";//此时pa指向该数组首地址4char str[]="hello";
    char *p=str;
    *p='H';//能重新赋值
    printf("%c",*p);//H
    p++;//还能位移
    printf("%c",*p);//e
  • 使用指针变量连续输出字符串时,如果指针变量指向改变,则输出起始位置也会改变(只有字符型才允许这样连续输出)

    char *pa="123";//实际上将1这个地址赋值给*pa,与下面等价
    printf("%s\n",pa);//123
    pa++;
    printf("%s\n",pa);//23
  • 使用%s输出字符串时注意printf函数第二个参数只能是字符数组名或者指向这个数组的指针名(即只能是地址)

    #include <stdio.h>
    int main(){char *p;char str[]="i love you";p=str;printf("%c\n",*p);printf("%s\n",p);//如果写成*p就是错误的,只能是地址str或者p,他们都是地址
    }
  • 在c语言中,字符串在表现形式上分为:字符数组字符指针两种情况。

  • 使用字符数组与字符指针处理字符串的区别:

    1.赋值方式不同
    2.系统为其分配内存单元的方法不同:字符指针是一个存放指针值的单元(4),字符数组是一段连续单元
    3.修改值(地址)的方式不同:字符指针允许修改,字符数组不允许修改。

指向函数的指针

定义:

数据类型 (*指针变量名)(形参列表)例如 int (*p)(int,int)
  • 定义时指针变量括号不能省略,否则就成了定义一个返回指针值的函数

  • 定义时如果需要指向的函数有参数,则指针函数一般也要参数(也可以不带,因为函数名是唯一的),但是初始化只需要给出函数名。

    void fn(int *p){printf("%d\n",*p);*p=14;printf("%d\n",*p);
    }
    int main(){//以下两种方法都正确void (*p)()=fn;void (*p1)(int *)=fn;
    }
  • 函数名就是函数的入口地址。

  • 指向函数的指针初始化时只能给一个函数名(函数的入口地址)。(如果给出函数的参数则成了调用函数,将函数的返回值给指针)

  • 对于指向函数的指针,进行算数运算没有意义。

  • 函数名代表函数的起始地址,与数组名相同,它属于一个指针常量

  • 在调用指向函数的指针时,指针调用与函数名调用等价,但是需要注意不管是调用还是初始化,指针变量的括号都不能省略

    int main(){int add();int (*p)()=add;//*p左右的括号不能省略printf("%d\n",(*p)());//(*p)()等价于add(),注意调用时*p左右的括号也不能省略
    }
    int add(){return 10078;
    }
  • 指向函数的指针只能指向同类型的函数,例如 int (*p)(int,int) 只能指向一个int类型函数,其后面的参数int可以省略,即

    与 int (*p)()等价(因为函数名是唯一的

  • 指向函数的指针与指向变量的指针和指向数组的指针有一点不同在于有些函数类型是void类型,那么指向函数的指针也应该是void类型。(注意变量没有void类型吗,所以void类型指针指向其他类型变量使用需强制转换)

    void fn(char *p1,char *p2){int n=strlen(p1);for(int i=0;i<n;i++){*p2=*p1;p1++;p2++;}
    }
    int main(){char a[]="mother",b[]="father";// fn(a,b);// void (*p)(char *,char *)=fn;与下面等价void (*p)(char *,char *);p=fn;(*p)(a,b);printf("%s\n",b);//mother
    }

使用:

  • 单独指向一个函数,使用指针变量调用该函数

    int main(){int add();int (*p)()=add;//*p左右的括号不能省略printf("%d\n",(*p)());/*(*p)()等价于add(),注意调用时*p左右的括号也不能省略(p保存add函数的首地址,那么*p就是这个地址里面保存的指令,所以(*p)()就是执行这段指令*/
    }
    int add(){return 10078;
    }
  • 指向一个函数,使用该指针作为另一个函数的形参,即将函数的起始地址作为形参,这样就能在主调函数中不用声明调用另一个函数

    int main(){void fn(int (*p)());int fn2();int (*p)()=fn2;fn(p);//fn2=10086(p保存fn2的地址,所以这里等价于 fn(fn2))
    }void fn(int (*p)()){printf("fn2=%d\n",(*p)());
    }int fn2(){return 10086;
    }以上程序等价以下程序:c不允许函数嵌套定义,但是允许函数嵌套调用int main(){void fn(int ());int fn2();fn(fn2);
    }void fn(int fn2()){printf("fn2=%d\n",fn2());
    }int fn2(){return 10086;
    }结论:以上两个程序如果不适应指针(传函数名属于指针常量,传指针变量就传地址),需要在fn函数内声明fn2才能使用
  • 指向一个函数,但是使用该指针指向的值作为另一个函数参数,即将指向函数的返回值作为函数的参数

    int main(){void fn(int);int fn2();int (*p)()=fn2;fn((*p)());//fn2=10086(这里的(*p)()等价fn2())
    }void fn(int n){printf("fn2=%d\n",n);
    }int fn2(){return 10086;
    }

返回指针值的函数

格式:

数据类型 *函数名(形参列表){}例如 int *max(){}
  • 返回指针值的函数即返回一个地址,而不是该地址保存的值。

  • 返回指针的函数在调用时与普通函数一样不加*,但是声明时要加**。

    int *max(int []);//函数声明
    max(arr);//函数调用
  • 指针函数常用来处理动态数据结构。(一般函数内定义一个静态存储类型数组)

指针数组

  • 说起指针数组就要想到行指针就是指针常量,列指针就是字符型指针

  • 指针数组类似一个二维数组。

定义:

数据类型 *数组名[数组长度]例如 char *p[3]={"hello","PS","GDP"};
  • 指针数组就是保存指针的数组,每个数组元素都是一个地址

  • 注意指针数组与指向整个数组的指针的区别。

  • 指针数组本质上还是一个数组,其数组名类似一个行指针,这个行指针是指针常量,不能进行改变(即不能进行++、--、+=、-=等操作,只能进行+、-等操作。

    第一种方法:char *p[]={"hello","pdf","word"};printf("%c\n",**p);//hprintf("%c\n",**(p+1));//pprintf("%c\n",**(p+2));//w(此三种表示指针常量p可以进行+、-操作)// p++;//报错!表示指针常量p不能进行++、--、+=、-=等操作(不能更改)第二种方法:char *p2[2];*p2="one";p2[1]="three";printf("%c\n",*p2[0]);//o(等价:printf("%c\n",**p2);)printf("%c\n",**(p2+1));//t(等价:printf("%c\n",*p2[1]))// p2++;//报错
    总结:两种方法指针数组名和普通数组名一样相当于一个指针常量,不能进行修改等操作,但是能进行+、-。

    指针数组名类似一个行指针,但是指针数组的元素是一个个指针变量,所以其元素可以进行改变(注意只能进行++、--、+、-操作),对整个列指针能进行+=、-=、=操作,对列指针指向的值不能进行+=、-=、=等操作,因为这些字符型指针指向的是字符串常量的其中一个地址,改变是对常量的改变。

        char *p[]={"hello","pdf","word"};(*p)++;printf("%c\n",**p);//eprintf("%c\n",*((*p)+1));//l// (**p)='E';//报错char *p2[2];*p2="one";p2[1]="three";(*p2)++;printf("%c\n",**p2);//nprintf("%c\n",*((*p2)+1));//e // (**p)='E';//报错// *p2[0]='N';//报错

    总结:对于指针数组,数组名是一个指针常量,类似一个行指针,只能进行+、-运算。指针数组的元素就是一个个字符指针,类似列指针,可以进行++、--、+=、-=、+、-运算,但是列指针是一个字符指针(字符型指针的第一种定义方法可以直接对字符指针重新赋值),允许改变整个列指针的值,但是不允许改变其所指向的指针常量单个字符。

    char str[]="123";//str是一个字符数组,保存的是一个个字符。
    char *p="123";//p是一个字符指针,指向的是一个“字符串常量”的首地址。(所以不能改变第一个char *p[2]={"chinese","elgnish"};printf("%c\n",**p);//cprintf("%c\n",**(p+1));//eprintf("%c\n",*((*p)+1));//h(行指针只能进行+、-)// p++;//报错(行指针(指针常量)不能进行改变++、--、+=、—=)// p={"hello","word"};//首先指针数组本质上是数组,不允许单独赋值。其次指针常量不允许重新赋值(*p)++;//列指针可以进行++、--printf("%c\n",**p);//hprintf("%c\n",*(*p)+1);//i(列指针可以进行+、-)*p="chinese1";//可以直接对列指针重新赋值,因为列指针相当于一个字符指针。printf("%s\n",*p);// **p='C';//不允许改变其中的单个值,因为是字符串常量
  • 指针数组本质上是数组,所以不能像指针一样,在定义后单独赋值。

        char *p[2];p={"hello","word"};//报错
  • 指针数组与二维数组的区别:二维数组列指针是指针常量,但是指针数组的列指针是指着变量

     int a[2][2]={1,6,3,9};
    //printf("%d\n",*((*a)++));错误,二维数组的列指针也不允许改变,即不允许++、--、+=、-=
    char *b[]={"hel","happy","today"};
    printf("%c\n",*((*b)++));//但是指针数组的列指针允许改变
  • 指针数组里面的元素不能通过scanf输入,因为里面是一个个字符指针变量,这些字符指针都没有初始化,且他们只能赋值一个地址,而输入的字符串是常量,此时字符指针数组里面的字符指针都没有初始化,属于空指针访问,所以不能使用

        char *s="789";//哪怕这样都是不行的,看似初始化了一个地址,scanf("%s",s);printf("%s\n",s);

二级指针

定义:

数据类型 **变量名例如 char **p
  • 二级指针就是指向指针变量的指针(类似一个行指针)

  • 二级指针与保存指针值的指针的区别。

        int a=12;int *p=&a,*p2=p;//p2保存的就是p的值,所以两者都指向aprintf("%d\n%d\n",*p,*p2);//12 12int a=12;int *p=&a,**p2=&p;//p2保存的是指针p的地址,指向指针变量pprintf("%d\n%d\n",*p,**p2);//12 12
  • 注意二级指针不存在以下情况

        int *p=&a,*p2=&p;//p2是一个一级指针,无法保存二级地址
  • 二级指针不能指向二维数组,虽然都类似行指针,可以使用指向数组的指针指向二维素组的首行

       int arr[2]={3,9},arr2[2][2]={2,4,6,8};
    //    int **p=arr2;//报错!int (*p)[]=arr2;//可以使用指向数组的指针指向一维数组,一样可以遍历访问printf("%d\n",**p);//2

    如果非要二级指针指向二维数组,实质上和一级指针用法相同,但是又和二级一样,注意别这样用

       int arr[2]={3,9},arr2[2][2]={2,4,6,8};int **p=arr2;//报错!printf("%d\n",*p);//2printf("%d\n",*(p+1));//6
  • 二级指针的使用只能保存指针变量的地址,所以要改变二级指针的指向,需要定义一个中间指针,保存该二级指针的下一片空间,再让该指针指向中间指针,详细见第八节最后一题。

  • 二级指针作用:1.指向一个指针。2.指向指针数组首地址。

指针数组做main函数参数

  • 指针数组一个重要的作用就是做main函数的参数

  • 通常情况下main函数没有参数,为空或者void。

    int main(int argc,char *argv);//argc是字符串个数,此参数不需要用户输入。argv是一个char类型指针数组,用户输入的字符串将他们的首地址保存在指针数组中,argc则自动记录指针数组的元素个数。
  • main函数的形参的值是不可能在程序中得到的,因为main函数由系统调用,实参只能由操作系统给出。

    给main函数传参数:
    命令名(可执行文件路径) 参数1 参数2 ... 参数n例如 file1 china beijing注意:1.命令名就是可执行文件路径2.各参数之间使用空格隔开(命令名也属于参数)3.用户输入的所有字符串都属于参数,包括命令名,所以argv第一个元素保存的是命令名的首地址。

动态内存分配于指向他的指针变量

动态内存分配:建立堆(栈是系统的动态存储区)。

  • 使用以下四个函数建立堆需要引入头文件"stdlib.h"。

  • malloc、calloc这两个函数返回一个指向开辟空间的首地址指针,一般需要使用指着变量保存以使用这片存储空间,使用指针变量来访问、存储这片空间。

建立堆的函数:
  • malloc(原型:void * malloc(unsigned int size))

    功能:建立一块动态存储区(堆),堆的大小由调用时传入的参数size决定,单位为字节(size不允许为负数)。函数返回值为该区域的		 首地址。如果函数执行失败,返回NULL。例如 malloc(100)//开辟100字节的内存空间用于临时分配,返回值为该区域首地址。
  • calloc(原型:void * calloc(unsigned n,unsigned size))

    功能:在内存的动态存储区建立一片连续的空间(即动态数组),参数n表示数组的个数、size表示每个元素的长度。函数执行成功返回该区域第一个字节的地址,失败返回NULL。
  • realloc:(原形:void * realloc(void *p,unsigned int size))

    功能:对已经分配好的存储空间重新分配大小。p是这片需要改变大小空间的首地址,size对这片内存空间重新分配大小。如果分配不成功返回NULL
  • free(原形:void free(void *p))

    功能:释放堆,参数是需要释放的首地址

注意:以上四个函数都是void类型,前三个返回值都是void类型指针,void类型指针是不能指向任何类型的,即返回的是一个纯地址,不能使用int、float、char等类型指针变量保存,所以在使用指针变量保存这些函数返回值时候需要将函数的类型强制转换为相应的类型。但是c编译系统有自动类型转换,所以实际上这不操作可以省略。

int *p=(int *)malloc(100);//函数返回值是void *,使用int *保存需要强制转换
等价于:
int *p=malloc(100);//自动类型转换

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/86659.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电路图识图基础知识-行程开关自动往返运行控制电路详解(二十三)

行程开关自动往返运行控制电路详解 在机床设备运行中&#xff0c;部分工作台需在特定距离内自动往复循环&#xff0c;行程开关自动往返运行控制电路可实现该功能&#xff0c;通过行程开关自动控制电动机正反转&#xff0c;保障工作台有序运动&#xff0c;以下展开详细解析。 …

SpringBoot学习day1-SpringBoot的简介与搭建

springboot回顾springspringbootspringboot搭建&#xff08;新闻为例&#xff09;springboot中的配置文件spring集成jdbc,mybatis,阿里巴巴数据源**SpringBoot 集成日志功能**(了解)常用日志组件日志级别 springboot统一异常处理 springboot 回顾spring spring是一个轻量级的…

【牛客小白月赛117】E题——种类数小结

1 初步想法 1.1 前置知识&#xff1a;vector数组的去重操作 unique()将不重复的元素放在数组前面&#xff0c;重复元素移到后面&#xff0c;qs获取不重复元素的后一个位置&#xff0c;之后用erase()函数去除重复元素。 qsunique(a.begin()1,a.begin()k1); a.erase(qs,a.end(…

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源&#xff08;HTML/CSS/图片等&#xff09;&#xff0c;响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址&#xff0c;提高安全性 3.负载均衡服务器 支持多种策略分发流量…

MatAnyone本地部署,视频分割处理,绿幕抠像(WIN/MAC)

大家好&#xff0c;今天要和大家分享的项目是MatAnyone&#xff0c;与上一篇分享的SAM2LONG类似&#xff0c;不过上次的分享没有提到如何在 MAC 上部署&#xff0c;后来有小伙伴私信说希望能出一个 MAC 版本的。那正好看到MatAnyone这个项目顺手就写下来。该项目基于SAM2同样可…

记录下blog的成长过程

2025-06-11 新人榜83 2025-06-09 新人榜87 北京市原力月榜 80

C语言学习20250611

指针 指针类型 int p;》普通的整形变量int *p;》p先与*结合&#xff0c;表示p为指针&#xff0c;该指针指向的内容的数据类型为整型int p[3];》p为一个由整型数据组成的数组int *p[3];》因为[]比*优先级高&#xff0c;p先与方括号结合&#xff0c;所以p为一个数组&#xff0c…

【AI智能体】Dify 从部署到使用操作详解

目录 一、前言 二、Dify 介绍 2.1 Dify 是什么 2.2 Dify 核心特性 2.2.1 多模型支持 2.2.2 可视化编排工作流 2.2.3 低代码/无代码开发 2.3 Dify 适用场景 2.4 Dify 与Coze的对比 2.4.1 定位与目标用户 2.4.2 核心功能对比 2.4.3 开发体验与成本 2.4.4 适用场景对比…

Java爬虫库的选择与实战代码

如果你的项目正在Java中考虑引入爬虫能力&#xff0c;无论是做数据分析、信息聚合&#xff0c;还是竞品监测&#xff0c;选对库确实能大幅提升开发效率和运行效果。结合当前主流库的特点与适用场景&#xff0c;我整理了一份更贴近实战的对比分析&#xff0c;并附上可直接运行的…

详细解释aruco::markdetection _detectInitialCandidates函数

_detectInitialCandidates 是 OpenCV 的 ArUco 模块中一个非常关键的函数&#xff0c;它负责检测图像中的候选 ArUco 标记。该函数的主要目标是&#xff1a; 使用多个尺度&#xff08;scale&#xff09;对输入图像进行自适应阈值处理&#xff1b;在每个尺度下提取轮廓并筛选出…

Android 开发中配置 USB 配件模式(Accessory Mode) 配件过滤器的配置

在 Android 开发中配置 USB 配件模式&#xff08;Accessory Mode&#xff09; 的配件过滤器&#xff08;accessory_filter.xml&#xff09;&#xff0c;需要以下步骤&#xff1a; 1. 创建配件过滤器文件 在项目的 res/xml/ 目录下创建 accessory_filter.xml 文件&#xff08;若…

FreeRTOS互斥量

目录 1.使用场合2.函数2.1 创建2.1.1 动态创建2.1.2 静态创建 2.2 删除2.3 释放&#xff08;Give&#xff09;2.4 获取&#xff08;Take&#xff09;2.5 ISR 版本注意事项 3.常规使用流程4.和二进制信号量的对比5.递归锁5.1 死锁5.2 概念5.2.1 问题5.2.2 解决方案&#xff1a;递…

ThinkPad 交换 Ctrl 键和 Fn 键

概述 不知道那个大聪明设计的将fn设置在最左边&#xff0c;xxx&#xff0c;我服了&#xff0c;你这个老六真恶心。 方法 一&#xff1a;BIOS/UEFI 设置&#xff08;推荐&#xff09; 重启 你的 ThinkPad。 在启动时按下 F1&#xff08;或 Enter&#xff0c;再按 F1&#xff0…

`dispatch_source_t` 计时器 vs `NSTimer`:核心差异一览

维度GCD 计时器 (dispatch_source_t)NSTimer依赖机制直接挂在 GCD 队列;底层走 Mach 内核定时源挂在 RunLoop,必须指定 RunLoop & mode线程上下文哪个队列就在哪条线程回调(例中用 dispatch_get_main_queue())总在定时器所在的 RunLoop 线程(默认主线程 & NSDefau…

ubuntu22.04系统安装部署docker和docker compose全过程!

更新系统包 首先&#xff0c;确保系统包是最新的&#xff1a; sudo apt updatesudo apt upgrade -y安装依赖 安装 Docker 所需的依赖包&#xff1a; sudo apt install -y apt-transport-https ca-certificates curl software-properties-common添加 Docker 官方 GPG 密钥 添加…

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…

VS2017----打开ui文件几秒后闪退

问题描述 在vs2017中双击ui文件能够打开,但是几秒后就闪退了,提示报错 问题解决 QT VS tools ----Options,把这个设置为True保存即可

深入解析Docker网桥模式:从docker0到容器网络的完整通信链路

1. 简介docker 网桥模式 Docker 启动时默认创建 docker0 虚拟网桥&#xff08;Linux bridge&#xff09;&#xff0c;并分配私有 IP 地址范围&#xff08;如 172.17.42.1/16&#xff09;&#xff0c;它的作用相当于一个虚拟交换机&#xff0c;让宿主机和多个容器之间可以通信。…

Proof of Talk专访CertiK联创顾荣辉:全周期安全方案护航Web3生态

6月10日&#xff0c;CertiK联合创始人兼CEO顾荣辉在Proof of Talk 2025举办期间&#xff0c;接受大会官方专访&#xff0c;分享了他对Web3安全现状的观察以及CertiK的安全战略布局。 顾荣辉指出&#xff0c;虽然安全的重要性被广泛认可&#xff0c;但许多创业者和开发者仍存在…

再说一说LangChain Runnable接口

之前我们介绍过LangChain通过Runnable和LCEL来实现各个组件的快捷拼装&#xff0c;整个过程就像拼积木一样。 今天我们深入剖析Runnable接口的底层实现逻辑。 往期文章推荐: 16.Docker实战&#xff1a;5分钟搞定MySQL容器化部署与最佳实践15.Ollama模板全解析&#xff1a;从基…