C语言-9.指针

48 阅读10分钟

@TOC

9.1指针

9.1-1取地址运算:&运算符取得变量的地址

运算符&

  • scanf("%d",&i);里的&
  • 获取变量的地址,它们操作数必须是变量
  • int i;printf("%x",&i);
  • 地址的大小是否与int相同取决于编译器
  • int i;printf("%p",&i); &不能取的地址
  • 不能对没有地址的东西取地址
  • &(a+b) &(a++) &(++a)这些都不能取地址 试试这些&
  • 变量的地址
#include <stdio.h>

int main()
{
    int a=1;
    printf("0x%p",&a);
    return 0;
}

在这里插入图片描述

  • 相邻变量的地址
#include <stdio.h>

int main()
{
    int a=1;
    int b=2;
    printf("0x%p\n",&a);
    printf("0x%p\n",&b);
    return 0;
}

在这里插入图片描述

  • &的结果的sizeof
#include <stdio.h>

int main()
{
    int a=1;
    int b=2;
    printf("0x%p\n",&a);
    printf("0x%p\n",&b);
    printf("%lu\n",sizeof((&a)));
    return 0;
}

在这里插入图片描述

  • 数组的地址
  • 数组单元的地址
  • 相邻的数组单元的地址
#include <stdio.h>

int main()
{
    int a[10];
    printf("%p\n",a);
    printf("%p\n",&a);
    printf("%p\n",&a[0]);
    printf("%p\n",&a[1]);
}

在这里插入图片描述 &数组名,数组名,数组名[0],都表示数组的首地址。

9.1.2指针:指针变量就是记录地址的变量

scanf

  • 如果能够将取得的变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量
  • scanf("%d",&i);
  • scanf()的原型应该是怎样的?我们需要一个参数能保存别的变量的地址,如何表达能够保存地址的变量? 指针 就是保存地址的变量 int i; int* p = &i; int* p,q; int p,q; 这里边的 号只是表示p是存储地址的变量,不是代表int这种类型,也没有int这种类型。 int *p=&a,应为int *p中p是要储存一个地址,所以给int *p赋值时必须是一个变量的地址 int *p,q这里边只有p是存储地址的变量,而q还是一个int型的变量。 指针变量
  • 变量的值是内存的地址
  • 普通变量的值是实际的值
  • 指针变量的值是具有实际意义的变量的地址 作为参数的变量
  • void f(int *p);
  • 在被调用的时候得到了某个变量的地址;
  • int i = 0;f(&i);
  • 在函数里面可以通过这个指针访问外面的这个i
# include <stdio.h>
int f(int *p);
int main()
{
    int a=1;
    int *p=&a;
    printf("&a=%p\n",&a);
} 

int f(int *p)
{
    printf("p=%p\n",p);
}

在这里插入图片描述 访问那个地址上的变量*

  • *是一个单目运算符,用来访问指针的值所表示的地址上的变量
  • 可以做右值也可以做左值
  • int k = *p;
  • *p = k+1;
# include <stdio.h>
int f(int *p);
int main()
{
    int a=1;
    int *p=&a;
    printf("&a=%p\n",&a);
    f(&a);
} 

int f(int *p)
{
    int i= *p;
    printf("%d\n",i);
}

** 左值之所以叫左值*

  • 是因为出现在赋值号左边的不是变量,而是值,是表达式计算的结果
  • a[0] = 2;
  • *p =3;
  • 是特殊的值,所以叫做左值 **指针的运算符& * **
  • 互相反作用
  • *&yptr -> *(&yptr) -> *(yptr的地址)-> 得到那个地址上的变量 -> yptr
  • &*yptr -> &(*yptr) -> &(y) -> 得到y的地址,也就是yptr -> yptr
# include <stdio.h>
int f(int *p);
int k(int a);
int main()
{
    int a=1;
    int *p=&a;
    printf("&a=%p\n",&a);
    f(&a);
    k(a);
} 

int f(int *p)
{
    int i= *p;
    *p=3;
}

int k(int a)
{
    printf("%d",a);
}

这个代码中将a的地址传给指针p通过f函数将a的值修改,在k函数中将a的值输出,发现a的值被改变,说明指针可以将地址中的值修改。

9.1-3指针的使用:指针有什么用呢?

指针应用场景一 交换两个变量的值

#include <stdio.h>

int main()
{
    int a=5;
    int b=2;
    jiaohuan(&a,&b);
    printf("a=%d,b=%d",a,b);
}

void jiaohuan(int *a,int *b)
{
    int t=*a;
    *a=*b;
    *b=t;
}

取数组中的最大值

#include <stdio.h>

void maxmin(int a[5],int len,int *max,int *min);
int main()
{
    int a[5]={34,12,56,23,78};
    int len=(sizeof(a)/sizeof(a[0]));
    int max;
    int min;
    maxmin(a,len,&max,&min);
    printf("max=%d,min=%d",max,min);
}

void maxmin(int a[5],int len,int *max,int *min)
{
    *max=*min=a[0];
    for(int i=1;i<len;i++)
    {
        if(*max<a[i])
        {
            *max=a[i];
        }
        if(*min>a[i])
        {
            *min=a[i];
        }
    }
}

指针应用场景二

  • 函数返回多个值,某些值就只能通过指针返回
  • 传入的参数实际上是需要保存带回的结果的变量
  • -1或0(在文件操作会看到大量的例子)
  • 但是当任何数值都是有效的可能结果时,就得分开返回了
  • 后续的余元(C++,Java)采用了异常机制来解决这个问题
#include <stdio.h>

int jisuan(int a,int b,int *c);
int main()
{
    int a;
    int b;
    int c;
    printf("请输入被除数\n");
    scanf("%d",&a);
    printf("请输入除数\n");
    scanf("%d",&b);
    int count=jisuan(a,b,&c);
    if(count==1)
    {
        printf("%d/%d=%d\n",a,b,c);
    }
    else
    {
        printf("除数不能为零");
    }
}

int jisuan(int a,int b,int *c)
{
    int count=1;
    if(b==0)
    {
        count=0;
    }
    else
    {
        *c=a/b;
    }
    
    return count;
}

指针常见的错误 定义了指针变量,还没有指向任何变量的地址,就开始使用指针

9.1-4指针与数组

传入函数的数组组成了什么?

  • 函数参数表中的数组实际上是指针
  • sizeof(a) == sizeof(int*)
  • 但是可以用数组的运算符[]进行运算 数组参数
  • 以下四种函数原型是等价的:
  • int sum(int *ar,int n);
  • int sum(int *,int);
  • int sum(int ar[].int n);
  • int sum(int [],int); 数组变量是特殊的指针
  • 数组本身表达地址,所以
  • int a[10];int*p=a;//无需用&取地址
  • 但是数组的单元表达式是变量,需要用&取地址
  • a == &a[0]
  • []运算符可以对指针做,也可以对数组做;
  • p[0]<==>a[0]
  • *运算符可以对指针做,也可以对数组做
  • *a = 25;
  • 数组变量是coust的指针,所以不能被赋值
  • int a[]<==>int *count a=....

9.1-5指针与const:指针本身和所指的变量都可能const

指针与counst 指针 -- 可以是const 值 -- 可以是const 指针是counst

  • 表示一旦得到了某一个变量的地址,不能再指向其他变量
  • int * const q =&i;//q是 const
  • *q =26;//OK
  • q++;//ERROR 所指是counst
  • 表示不能通过这个指针去修改那个变量(并不能使得那个变量成为 const)
  • const int *p =&i;
  • *p =26;//ERROR!(*p)是const
  • i = 26//OK
  • p = &j;//OK 这些是啥意思? int i; const int *p1 = &i; int const *p2 = &i; int *const p3 = &i; 判断哪个被const了的标志是const在 *的前面还是后面 转换
  • 总是可以把一个非const的值转换成const的 void f(const int* x);//这个函数的意思是保证在函数f中不会修改x所指的地址 int a = 15; f(&a);//ok const int b = a;

f(&b);//ok b = a+1;//Error! 当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递给参数,又能避免函数对外面的变量的修改 const数组

  • const int a[] = {1,2,3,4,5,6};
  • 数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int
  • 所以必须通过初始化进行赋值 ** 保护数组值**
  • 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值
  • 为了保护数组不被函数破坏,可以设置参数为const
  • int sum(const int a[],int length);

9.2 指针运算

9.2-1指针运算

1=1=2?

  • 给一个指针加1表示要让指针指向下一个变量 int a[10] int *p = a; *(p+1)==>a[1]
  • 如果指针不是指向一片连续分配的空间,如果数组(是连续的空间),则这种运算没有意义 ** 指针运算**
  • 这些算数运算可以对指针做
  • 给指针加,减一个整数(+,+=,-,-=)
  • 递增递减(++/--)
  • 两个指针相减 ** p++*
  • 取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
  • *的优先级虽然高,但是没有++高
  • 常用于数组类的连续空间操作
  • 在某些cpu上,这可以直接被翻译成一条汇编指令 指针比较
  • <,<=,==,>,>=,!=都可以对指针做
  • 比较它们在内存中的地址
  • 数组中的单元的地址肯定是线性递增的 0地址
  • 当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
  • 所以你的指针不应该具有0值
  • 因此可以用0地址来表示特殊的事情;
  • 返回的指针是无效的
  • 指针没有被真正初始化(先初始化为0)
  • NULL是一个预定定义的符号,表示0地址
  • 有点编译器不愿意你用0来表示0地址 指针的类型
  • 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
  • 但是指向不同类型的指针是不能直接互相赋值的
  • 这是为了避免用错指针 指针的类型转换
  • void* 表示不知道指向什么东西的指针
  • 计算时与char*相同(但不想通)
  • 指针也可以是转换类型
  • int p = &i;voidq = (void*)p;
  • 这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量
  • 我不再当你是int啦,我认为你就是个void! 用指针来做什么
  • 需要传入较大的数据时用作参数
  • 传入数组后对数组做操作
  • 函数返回不止一个结果
  • 需要用函数来修改不止一个变量
  • 动态申请的内存......

9.2-2动态内存分配

输入数据

  • 如果输入数据时,先告诉你个数,然后再输入,要记住录入每个数据
  • C99可以用变量做数组定义的大小,C99之前呢?
  • int * a = (int*)malloc(nsizeof(int)) malloc #include<stdlib.h> void malloc(size_t size);
  • 向malloc申请的空间的大小是以自己为单位的
  • 返回的结果是viod*,需要类型转换为自己需要的类型
  • (int*)malloc(n*sizeof(int))
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *a;
    int n;
    scanf("%d",&n);
    a=(int *)malloc(n*(sizeof(int)));
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
    }
    for(int i=n-1;i>=0;i--)
    {
        printf("%d",a[i]);
    }

    free(a);
}

没空间了?

  • 如果申请失败则返回0,或者叫做NULL
  • 你的系统能给你多大的空间
#include <stdio.h>
#include <stdlib.h>

int main()
{
    void *p;
    int a=0;
    while((p=malloc(100*1024*1024)))
    {
        a++;
        
    }
    printf("分配了%d00MB内存",a);
    return 0;
}

这个代码可以告诉你,电脑分配了多少MB内存 其中10010241024表示100MB。 free()

  • 把申请得来的空间还给“系统”
  • 申请过的空间,最终都应该要还
  • 混出来的,迟早要还
  • 只能还申请来的空间的首地址 常见问题
  • 申请了内free-->长时间运行内存逐渐下降
  • 新手:忘了
  • 老手:找不到合适的free的时机
  • free过了再free
  • 地址变过了,直接去free 例如:
#include <stdio.h>
#include <stdlib.h>

void main()
{
    int *p;
    p=(int *)malloc(100*1024*1024);
    p++;
    free(p);
}

这里的p的地址变了所以无法直接free