C语言——指针

指针的优点:

  1. 使程序更简洁、紧凑、高效
  2. 有效的表达更复杂的数据结构
  3. 动态分配内存
  4. 得到多于一个数的函数返回值

指针的基本用法

指针的概念

地址:内存中每个字节单位的编号(一般用十六进制表示)。

指针:指针就是地址。

指针变量:用于存放地址的变量就叫指针变量。

普通变量:

指针变量:

定义格式

存储类型 数据类型 *指针变量名;

例如:

int *p;//定义了一个整数型指针变量p

例如:

int a=5;
int *p=&a;
char b='v';
char *q=&b;
printf("%p %p\n",p,&a);
printf("%p %p\n",q,&b);
printf("%d %d\n",*p,a);
printf("%d %d\n",*q,b);

访问指针所指的空间里的内容时,需要用取内容运算符*来获取

此时,变量p存放的就是a的地址,变量q存放的就是b的地址;符号*可以访问地址指向的空间里面的内容。

指针与变量之间的关系如下:

int i=3;

int i_pointer=&i;

指针操作符

取地址运算符(&):取变量地址

取内容运算符(* ):取地址指向的空间里的内容

*&a=a;//*和&是相互逆运算

&*a; //错误,运算符优先级

初始化和赋值

指针变量在使用前不仅要定义还得初始化,未初始化指针变量不能随便使用,不然会产生野指针。

将普通变量的地址赋值给指针变量

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

int main(int argc,char const *argv[])
{
    int a=10;
    int *p=&a;//定义指针变量的同时赋值
    int *p1=NULL;//可以给指针赋值为空指针
    int *q;
    q=p;//先定义后赋值
    printf("%d %d %d\n",a,*p,*q);//打印a的值
    printf("%p %p %p\n",&a,p,q);//打印a的地址

    *q=20;//通过*q改变指针指向内存里的内容
    printf("%d %d %d\n",a,*p,*q);//打印a的值:20 20 20
    PRINTF("%p %p %p\n",&a,p,q);//打印a的地址
    return 0;
}

将数组的首地址赋值给指针变量

char s[10]="hello";
char *p=s;
int arr[5]={1,2,3,4,5};
int *q=arr;
printf("%c\n",*p);//h
printf("%d\n",*q);//1

将指针变量中保存的地址赋值给另一个指针变量 

float a=1.5;
float *p=&a;
float *q=p;
printf("%f %f %f\n",a,*p,*q);

指针运算

算数运算 + -

对指针的加减操作实际上是让指针向前或者向后移动。

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

int main(int argc,char const *argv[])
{
    char s[32]="hello";
    char *p=s;
    int arr[5]={1,2,3,4,5};
    int *q=arr;
    printf("%c\n",*p);//h
    p=p+2;
    printf("%c\n",*p);//l
    p--;
    printf("%c\n",*p);//e
    
    printf("%d\n",*q);//1
    printf("%p\n",q);
    q++;
    printf("%d\n",*q);//2
    printf("%p\n",q);//指针移动到下一个单位,数值上多了字节
    return 0;
}

p+n:访问高地址方向的第n个数据的地址,指针指向不变。

p-n: 访问低地址方向的第n个数据的地址,指针指向不变。

关系运算 > >= < <= == !=

指针之间的关系运算比较的是地址的高低,高地址的指针大于低地址的指针。

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

int man(int argc,char const *argv[])
{
    char s[32]="hello";
    char *p1=&s[1];
    char *p2=&s[4];
    if(p2>p1)
        printf("p2>p1\n");
    else
        printf("p1>p2\n");
    return 0;
}

注意:

指向不同类型的数组指针关系运算没有意义, 指向不同区域的指针关系运算也没有意义;在同一个数组内进行比较。

练习:

y打印出来的是什么

char s[32]="hello";
char *p=s;
char y=(*--p)++;
printf("%c\n",y);//未知字符(随机字符)

以下程序都会打印什么结果

int x[]={10,20,30};
int *px=x;
printf("%d,",++*px);      //11
printf("%d\n",*px);       //11
px=x;
printf("%d,",(*px)++);    //11
printf("%d\n",*px);       //12
px=x+1;
printf("%d,",*px++);      //20
printf("%d\n",*px);       //30
px=x+1;
printf("%d,",*++px);      //30
printf("%d\n",*px);       //30

下面这段程序的运行结果是(C

char a[]="language",*p;
p=a;
while(*p!='u')
{
    printf("%c",*p-32);
    p++;
}

A. LANGUAGE        B. language        C. LANG        D. langUAGE

利用指针判断输入的字符串是否为回文

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

int main(int argc,char const *argv[])
{
    char s[32];
    char *p1,*p2;
    int flag=1;
    gets(s);
    p1=s;
    p2=s+strlen(s)-1;
    //p2=&s[strlen(s)-1];
    while(p2>p1)
    {
        if(*p1!=*p2)
            flag=0;
        p1++;
        p2--;
    }
    if(flag==0)
        printf("不是回文字符串\n");
    else
        printf("是回文字符串\n");
    return 0;
}

打印杨辉三角形前十行

解析:杨辉三角如下

1

1  1

1  2  1

1  3  3  1

1  4  6  4  1

……

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

int main(int argc,char const *argv[])
{
    int a[10][10]={},i,j;
    /*负责对角线和每一行的第一个元素*/
    for(i=0;i<10;i++)
    {
        a[i][0]=1;
        a[i][i]=1;
    }
    for(i=2;i<10;i++)
    {
        for(j=1;j<i;j++)
        {
            a[i][j]=a[i-1][j]+a[i-1][j-1];
        }
    }
    for(i=0;i<10;i++)
    {
        for(j=0;j<=i;j++)
            printf("%4d ",a[i][j]);
        printf("\n");
    }
    return 0;
}

程序实现的功能:将字符串“Computer Science”赋值给一个字符数组,然后从第一个字母开始间隔的输出该字符串,用指针完成。

解析:输出结果 为Cmue cec

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

int main(int argc,char const *argv[])
{
    char s[32]="Computer Science";
    char *p=s;
    while(*p!='\0')
    {
        printf("%c",*p);
        p+=2;
    }
    printf("\n");
    return 0;
}

指针的大小和段错误

指针的大小

计算指针大小:sizeof(指针变量名)

int a=5;
int *p=&a;
char c='a';
char *q=&c;
double b=1.5;
double *p2=&b;
printf("%d\n",sizeof(p));     //4
printf("%d\n",sizeof(q));     //4
printf("%d\n",sizeof(p2));    //4

总结:

  1. 32位操作系统中, 指针的大小都是4字节;在64位操作系统中,指针的大小都是8字节。
  2. 内存地址是固定的,但是变量地址不是固定的(栈区随机分配的)。
  3. 指针类型根据指针指向的空间数据类型而决定的。

使用指针时容易报段错误

指针虽然灵活好用,但是使用时应该注意,因为指针使用不当容易报段错误。

段错误:Segmentation fault(core dumped)

野指针(没有规定指向的指针,会在内存中乱指)

产生野指针的原因主要有两种:

1. 指针变量没有初始化就使用。

int *p;
printf("%d",*p);

2. 指针被free之后没有设置为NULL,会让人以为是合法指针从而开始使用。

内存泄漏(对非法空间进行赋值)

练习:

将字符串转换成整型数字输出,用指针实现。

要求:字符串为0-9组成,输出数据为一个整数

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

int main(int argc,char const *argv[])
{
    char a[32]="124";
    char *p=a;
    int num=0;
    while(*p!='\0')
    {
        num=num*10+(*p-48);
        p++;
    }
    printf("num=%d\n",num);
    return 0;
}

指针修饰

const常量化

修饰普通变量

const int a

int const a

此时的a是只读状态,不可以修改。

const int a=10;
//int const a;
a=20;    //错误,变量的值不可以修改
printf("%d\n",a);

 报错: error: assignment of read-only variable ‘a’  a=20;

 但是可以通过指针修改。

const int a=10;
int *p=&a;
*p=20;
printf("%d\n",a);

修饰指针所指内容

修饰*p,指针指向内容不能更改,但是可以更改指针的指向。

const int *p

int const *p

int a=10;
const int *p=&a;
//int const *p=&a;
*p=20;//错误,指针所指内容不可修改
printf("%d\n",a);

错误:

error: assignment of read-only location ‘*p’

     *p=20;

可以改变指针的指向:

int a=10,b=20;
const int *p=&a;
printf("%d\n",*p);
p=&b;
printf("%d\n",*p);

修饰指针指向

int *const p

此时,修饰的是p,也就是修饰指针的指向,指针指向不能修改,但是所指内容可以修改。

int a=10,b=20;
int *const p=&a;
printf("%d\n",*p);
p=&b;//错误,指针指向不可以修改
printf("%d\n",*p);

 错误:error: assignment of read-only variable ‘p’

     p=&b;

所指内容可以改变:

int a=10,b=20;
int * const p=&a;
printf("%d\n",*p);
*p=b;
printf("%d\n",*p);

void

  1. 不允许修饰普通变量:void a;//错误
  2. 可以修饰指针变量:void *p;

            用void修饰指针变量,则这个指针变量是任意类型的指针。

            注意:

                通过void类型指针进行取内容时,需要对指针进行强制转换。

                转换格式:强转(int *)p为右值。

例如:

int a=100;
void *p=&a;
int *q=(int *)p;
printf("%d %d\n",*q,*(int *)p);

强制转换要随用随转。

大小端

在计算机进行超过一字节的数据的存储,会出现存储顺序不同的两种情况,也就是大端存储和小端存储。

大端存储:数据的低位存储在高地址处,数据的高位存储在低地址处,大端字节序列称为MSB。

小端存储:数据的低位存储在低地址处,数据的高位存储在高地址处,小端字节序列称为LSB。

例如:

存储数据0x12345678在0x4000处

地址        0x40000        0x4001        0x4002        0x4003

小端        0x78              0x56            0x34            0x12

大端        0x12              0x34            0x56            0x78

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

int main(int argc,char const * argv[])
{
    int a=0x12345678;
    char *p=(char *)&a;
    if(*p==0x78)
        printf("LSB\n");
    else if(*p==0x12)
        printf("MSB\n");
    else
        printf("error\n");
    return 0;
}

二级指针

一级指针:存放变量的地址。

二级指针:存放一级指针的地址。

格式

存储类型 数据类型 **指针变量名;

p指向了a,q指向了p。

a的值a的地址p的地址
a&a&p
*ppq
**q*q
int a=10;
int *p=&a,**q=&p;
printf("%d %d %d\n",a,*p,**q);
printf("%p %p %p\n",&a,p,*q);
printf("%p %p\n",&p,q);

指针和数组

两种访问形式

直接访问:按变量的地址存取变量的值。(通过数组名访问)

间接访问:通过存放变量的地址的变量去访问。(通过指针访问)

指针和一维数组

用法

int a[5]={1,2,3,4,5};//a是数组名也是首地址

int *p=a;

直接访问:

通过指针间接访问:

访问数组元素a[i]的地址:

直接访问:&a[i]        a+i

间接访问:&p[i]        p+i

访问数组元素a[i]的值:

直接访问:a[i]        *(a+i)

间接访问:p[i]        *(p+i)

代码验证:

int a[3]={1,2,3};
int *p=a;
printf("%p %p %p %p\n",a,&a[0],p,&p[0]);
printf("%p %p %p %p\n",a+1,&a[1],p+1,&p[1]);
printf("%d %d %d %d\n",*a,a[0],*p,p[0]);
printf("%d %d %d %d\n",*(a+1),a[1],*(p+1),p[1]);

打印结果:

0xbfd100a0 0xbfd100a0 0xbfd100a0 0xbfd100a0

0xbfd100a4 0xbfd100a4 0xbfd100a4 0xbfd100a4

1 1 1 1

2 2 2 2

注意:a和p的数值一样,但是本质不同。

  1. a是地址常量,p是指针变量。
  2. a不能执行++操作,但是p可以。

运算方法

++和*都是单目运算符。

单目运算符都是从右向左运算的。

++在前先移动再取值,++在后先取值后移动 。

int a[5]={1,2,3,4,5};
int *p=a;
//printf("%d\n",*(p++));//1
//printf("%d\n",++*p);//2
//printf("%d\n",++(*p));//2
//printf("%d\n",*++p);//2
printf("%d\n",*(++p));2

指针和二维数组

二维数组数组名表示

例如:

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

a是数组名,表示第一行的首地址,a+1表示第二行的首地址。

在a前面加*表示将行地址降级为列地址。

*a表示第一行第一列的地址,即&a[0][0]

*a+1表示第一行第二列的地址,即&a[0][1]

*(a+1)表示第二行第一列的地址,即&a[1][0]

*(a+1)+1表示第二行第二列的地址,即&a[1][1]

访问二维数组 元素的地址(a[i][j]的地址):

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

访问二维数组元素的内容(a[i][j]的内容):

a[i][j]          *(*(a+i)+j)        *(a[i]+j)

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

int main(int argc,char const argv[])
{
    int a[2][3]={1,2,3,4,5,6};
    for(int i=0;i<2;i++)
    {
        for(int j=0;j<3;j++)
            printf("%d %d\n",*(*(a+i)+j),*(a[i]+j));
        printf("\n");
    }
    return 0;
}

打印结果:

1 1

2 2

3 3

4 4

5 5

6 6

数组指针

定义

本质还是指针,指向的是数组。(又称为行指针)

格式

存储类型 数据类型 (*指针变量名)[列数];

例如:

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

        int (*p)[3]=a;

此时p可以代替a进行元素的访问,但是本质不同,a是地址常量,p是指针变量。

访问a[i][j]的地址:

p[i]+j           *(p[i]+j)

访问a[i][j]的内容:

*(p[i]+j)        *(*(p+i)+j)

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

int main(int argc,char const *argv[])
{
    int a[2][3]={1,2,3,4,5,6};
    int (*p)[3]=a;
    printf("%d %d\n",*p[0],**p);
    printf("%d %d\n",*(p[0]+1),*(*p+1));
    printf("%d %d\n",*(p[1]+1),*(*(p+1)+1));
    return 0;
}

打印结果:

1 1

2 2

5 5

练习:

有一个班,3个学生,各学4门课,计算总平均分以及输出第n个学生的成绩。

int a[3][4] = {65,55,23,57,52,67,64,80,90,42,75,92};

思路:用数组指针的方式把每个学生分别当成一个数组,int (*p)[4]表示,这样每列的元素就代表每门课的成绩。然后循环嵌套算出总成绩,总成绩除以12算出平均成绩。然后通过循环,找到第几个学生的成绩输出。

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

int main(int argc,char const *argv[])
{
    int a[3][4]={66,55,23,57,52,67,64,80,90,42,75,92};
    int (*p)[4]=a,sum=0,ave,n;
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<4;j++)
            sum+=*(p[i]+j);
    }
    ave=sum/12;
    scanf("%d",&n);
    if(n<=3)
    {
        for(int k=0;k<4;k++)
            printf("%d ",*(p[n-1]+k));
        printf("\n");
    }
    return 0;
}

大小

sizeof(p)=4;

因为本质还是指针,所以大小都是4字节。

例如:

int a[3][4]={1,2,3,4,5,6};用数组指针遍历二维数组

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

int main(int argc,char const *argv[])
{
    int a[2][3]={1,2,3,4,5,6};
    int(*p)[3] = a;
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 3; j++)
            //printf("%d ", *(p[i] + j));
            printf("%d ", *(*(p + i) + j));
        printf("\n");
    }
    return 0;
}

已知字符数组a[10]和b[10]中元素的值递增有序,用指针实现将两个数组中元素按照递增顺序输出。如:char a[10]=”acdgjmno” ; char b[10]=”befhil”;->”abcdefghijlmno”

解析:利用指针比较两字符数组中的元素,若a中的小,打印a中字符,然后a的指针移动到下一个元素继续比较,反之打印b然后移动b指针到下一个元素继续比较。最后判断字符串是否结束,打印没有结束的字符串剩下部分。

#include <stdio.h>
#inclide <stdlib.h>
int main()
{
    char a[10] = "acegijkm";
    char b[10] = "bdfh";
    char *p = a, *q = b;
    while (*p != '\0' && *q != '\0')
    {
        if (*p < *q)
        {
            printf("%c", *p);
            p++;
        }
        else
        {
            printf("%c", *q);
            q++;
        }
    }
    if (*p == '\0')
        printf("%s", q);
    else if (*q == '\0')
        printf("%s", p);
    return 0;
}

 给定一串字符"I love china",实现以单词为单位的逆序,如:"china love i"

解析:可以先全部倒过来:anihc evol i, 设置两个指针,分别指向头和尾,然后比较两指针进行移动,倒置字符串。然后在设置一个指针,遍历字符串用来寻找空格和末尾并记录,只要找到就把每个单词倒过来。

方法一:

#include <stdio.h>
#include <string.h>
int main()
{
    char a[32] = "i love china";
    char *p, *q, *k, t;
    p = a;
    q = p + strlen(a) - 1;
    while (q > p)
    {
        t = *p;
        *p = *q;
        *q = t;
        p++;
        q--;
    }
    p = q = a;
    while (*p != '\0')
    {
        while (*p == ' ') //让p一直指向开头
        {
            p++;
        }
        q = p;                          //让q先指向开头
        while (*q != ' ' && *q != '\0') //为了让q找空格
        {
            q++;
        }
        k = q; //把空格的位置用k去记录
        q--;   //让q指向结尾
        while (p < q)
        {
            t = *p;
            *p = *q;
            *q = t;
            p++;
            q--;
        }              //每个小字符串倒置
        p = k;         //让p指向空格
    }
    printf("%s\n", a);
    return 0;

方法二:

#include <stdio.h>
#include <string.h>
#define MAX 50
int main()
{
    char a[32] = "i love china";
    char str[MAX][100] = {};
    int i = MAX - 1, j;
    int t = 0;
    while (a[t] != '\0')
    {
        j = 0;
        while (a[t] != ' ' && a[t] != '\0')
        {
            str[i][j] = a[t];                //二维数组倒每一行记录一个小字符串
            t++;
            j++;
        }
        if (a[t] == ' ')                //找上一行,为了把新的字符串放到上一行
        {
            i--;
            t++;
        }
    }
    for (; i < MAX; i++)                    //依次打印每个字符串
    {
        printf("%s ", str[i]);
    }
    return 0;
}

方法三:

#include <stdio.h>
#include <string.h>
#define MAX 50
int main()
{
    char a[32] = "i love china";
    char str[MAX][100] = {};
    int i = MAX - 1, j;
    int t = 0;
    while (a[t] != '\0')
    {
        j = 0;
        while (a[t] != ' ' && a[t] != '\0')
        {
            str[i][j] = a[t];                //二维数组倒每一行记录一个小字符串
            t++;
            j++;
        }
        if (a[t] == ' ')                //找上一行,为了把新的字符串放到上一行
        {
            i--;
            t++;
        }
    }
    for (; i < MAX; i++)                    //依次打印每个字符串
    {
        printf("%s ", str[i]);
    }
    return 0;
}

指针数组

定义

所谓指针数组就是由若干个具有相同类型的指针变量组成的集合。本质是数组,数组里面存放的元素是指针。

格式

存储类型 数据类型 *数组名[元素个数];

指针数组名就表示该指针数组的首地址。

例如:

        int *arr[3];

应用

用于存放普通变量的地址

例如:

int a=10,b=20,c=30;
int *p[3]={&a,&b,&c};
printf("%d %d %d\n",**p,**(p+1),**(p+2));
printf("%d %d %d\n",*p[0],*p[1],*p[2]);

printf("%p %p %p\n",*p,*(p+1),*(p+2));
printf("%p %p %p\n",p[0],p[1],p[2]);
printf("%p %p %p\n",&a,&b,&c);

用于存放二维数组每一行第一个元素的地址(列地址)

int a[2][3]={1,2,3,4,5,6};
int *p[2]={a[0],a[1]};
printf("%d\n",*(p[1]+2));
printf("%d\n",*(*(p+1)+2));

 sizeof(p)=8;

访问地址:p[i]+j                *(p+i)+j

访问元素:*(*(p+i)+j)        *(p[i]+j)

用于存放字符串

char *p[3]={"hello","new","world"};
printf("%s\n",p[1]);//new
printf("%c\n",*(p[2]+1));//o
printf("%c\n",*(*(p+2)+1));//o

命令行参数

例如:

执行./a.out hello world

int main(int argc, char const *argv[])
{
    printf("%d\n", argc);
    printf("%s\n", argv[0]);//./a.out
    printf("%s\n", argv[1]);//hello
    printf("%s\n", argv[2]);//world
    printf("%c\n", *(argv[1] + 1));//e
    return 0;
}

argv:就是一个指针数组,里面存放的是命令行传递的字符串

argc:表示argv指针数组里面存储数据的个数,即命令行传递字符串的个数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值