欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C语言入门-指针

程序员文章站 2022-06-21 08:58:45
终于到了精髓的地方了,这确实有点懵,总感觉这太麻烦了,而且写着也不爽,还是怀念py或者java,但也没办法,还是要继续学下去。 一、运算符& 1. scanf("%d" , &i); 里的& 2. 获取变量的地址,它的操作数必须是变量 3. 地址的大小是否与int相同取决于编译器 &不能取的地址 & ......

终于到了精髓的地方了,这确实有点懵,总感觉这太麻烦了,而且写着也不爽,还是怀念py或者java,但也没办法,还是要继续学下去。

一、运算符&

  1. scanf("%d" , &i); 里的&
  2. 获取变量的地址,它的操作数必须是变量
  3. 地址的大小是否与int相同取决于编译器
#include <stdio.h>
int main(void)
{
    int i = 0;
    printf("0x%x\n", &i);
    // 0x62fe4c
    return 0;
}

&不能取的地址

&不能对没有地址的东西取地址

  1. &(a+b) ?
  2. &(a++) ?

二、指针

  1. 如果能够将获得变量的地址传递个一个函数,能否通过这个地址在那个函数内访问这个变量?
  2. scanf("%d" , &i);
  3. scanf()的原型应该是怎样的?我们需要一个参数保存别的变量的地址,如何表达能够保存地址的变量

指针

就是保存地址的变量 , *p

// p是一个指针,现在把i的地址交给了p
int i;
int* p = &i;

// 下面两种形式一样,p是一个指针,而q是一个普通的int变量
int* p , q;
int *p , q;

指针变量

  1. 变量的值是内存的地址
  2. 普通变量的值是实际的值
  3. 指针变量的值是具有实际值的变量的地址

作为参数的指针

  1. void f(int *p);
  2. 在被调用的时候得到了某个变量的地址
  3. int i = 0;f(&i);
  4. 在函数里面可以通过这个指针访问到外面的的这个i
#include <stdio.h>

void f(int *p);

int main(void)
{
    int i = 6;
    printf("&i=%p\n", &i);
    f(&i);

    return 0;
}

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

// 可以看到这里获取的地址是相同的
// &i=000000000062fe4c
//  p=000000000062fe4c

访问那个地址上的变量*

  1. *是一个单目运算符,用来访问指针的值所表示的地址上的变量
  2. 可以是右值也可以是左值
  3. int k = *p;
  4. *p = k + 1;

*左值之所以叫左值

  1. 是因为出现在赋值号左边的不是变量,而是值,是表达式计算的结果
  2. a[0] = 2;
  3. *p = 3;
  4. 是特殊的值,所以叫左值
#include <stdio.h>

// 声明两个函数
void f(int *p);
void g(int k);


int main(void)
{
    int i = 6;
    printf("&i=%p\n", &i);
    f(&i);
    
    // 此时i的值已经发生了变化
    g(i);

    return 0;
}


// 传入的是地址
void f(int *p)
{
    printf(" p=%p\n", p);
    printf("*p=%d\n", *p);
    *p = 66;
}

// 传入的普通int
void g(int k){
    printf("k=%d\n", k);
}


// &i=000000000062fe4c
//  p=000000000062fe4c
// *p=6
// k=66

三、指针的使用

指针应用场景一

交换两个变量的值

#include <stdio.h>

void swap(int *pa , int *pb);

int main()
{
    int a = 5;
    int b = 10;
    swap(&a , &b);
    printf("a=%d , b=%d\n", a , b);
    return 0;
}

void swap(int *pa , int *pb){
    int t = *pa;
    *pa = *pb;
    *pb = t;
}

指针应用场景二

  1. 函数返回多个值,某些值就只能通过指针返回
  2. 传入的参数实际上是需要保存带回的结果的变量
#include <stdio.h>

void minmax(int a[] , int len , int *min , int *max);

int main(void)
{
    int a[] = {1,2,3,4,5,6,7,8,9,12,13,14,15,34,35,66,};
    int min , max;
    minmax(a , sizeof(a)/sizeof(a[0]) , &min , &max);
    printf("min = %d , max = %d \n", min , max);

    return 0;
}


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

指针应用场景二b

  1. 函数返回运算的状态,结果通过指针返回
  2. 常用的套路是让函数返回特殊的不属于有效范围内的值表示出错
    • -1 或 0
  3. 但是当任何数值都是有效的可能结果时,就得分开返回了
#include <stdio.h>

// 如果成功就返回1,否则就是0
int divide(int a , int b , int *result);

int main(void)
{
    int a = 5;
    int b = 2;
    int c;
    if (divide(a,b,&c))
    {
        printf("%d/%d = %d\n", a, b, c);
    }

    return 0;
}

int divide(int a , int b , int *result)
{
    int ret = 1;
    if ( b== 0)
    {
        ret = 0;
    }else{
        *result = a/b;
    }

    return ret;
}

指针常见的错误

定义了指针变量,还没有指向任何变量,就开始使用了

四、指针与数组

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

  1. 函数参数表中的数组实际上是指针
  2. sizeof(a) == sizeof(int*)
  3. 但是可以用数组的运算符[]进行运算

下面的四种函数原型是等价的

int sum(int *ar , int n);
int sum(int* , int);
int sum(int ar[] , int n);
int sum(int[] , int);

数组变量是特殊的指针

数组变量本身表达地址,所以

  1. int a[10]; int*p = a; // 无需用&取地址
  2. 但是数组的单元表达的是变量,需要用&取地址
  3. a == &a[0]

[]运算符可以对数组做,也可以对指针做

  1. p[0] <===> a[0]

*运算符可以对指针做,也可以对数组做

  1. *a = 25

数组变量是const的指针,所以不能被赋值

五、指针与const

  1. 表示一旦得到了某个变量的地址,不能再指向其他变量
    // q内写的地址不能被改变
    int *const q = &i;
    *q = 26; // ok
    q++;   // error
  1. 表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)
    const int *p = &i;
    *p = 26; // error  (*p是不能变的)
    i = 26; // ok
    p = &j; // ok
  1. 看懂下面的意思?
    const int * p1 = &i;
    int const * p2 = &i;

    int *const  p3 = &i;

判断那个被const的标志是const在的前面还是后面
前面两个
p不能被修改
,就像第二种情况

const数组

const int a[] = {1,2,3,4,5,6,};
  1. 数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int
  2. 所以必须通过初始化进行赋值

保护数组值

  1. 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值
  2. 为了保护数组不被函数破坏,可以设置参数为const
int sum(const int a[] , int length);

六、指针运算

1+1 = 2 ?那么指针加1等于什么呢

#include <stdio.h>

int main(void)
{
    char ac[] = {0,1,2,3,4,5,6,7,8,9,};
    char *p = ac;

    printf("p =%p\n", p);
    printf("p + 1 =%p\n\n", p+1);
    // p =000000000062fe30
    // p + 1 =000000000062fe31
    // 相差了1
    
    int ai[] = {0,1,2,3,4,5,6,7,8,9,};
    int *q = ai;

    printf("q =%p\n", q);
    printf("q + 1 =%p\n\n", q+1);
    //q =000000000062fe00
    //q + 1 =000000000062fe04
    // 相差了4

    return 0;
}

我们不难发现,在char中,相差了1,在int中相差了4,这是怎么回事?

sizeof(char) = 1 , sizeof(int) = 4

对指针做一个加1的动作,意味着要把它移动到下一个单元去

int a[10];
int *p = a;
*(p+1) --> a[1]

如果指针不是指向一片连续分配的空间,如数组,那么这运算没有意义

可以对指针的算术运算

  1. 给指针加、减、一个整数(+、+=、-、-=)
  2. 递增递减(++/--)
  3. 两个指针相减
#include <stdio.h>

int main(void)
{
    char ac[] = {0,1,2,3,4,20,6,7,8,9,};
    char *p1 = &ac[5];
    char *p2 = &ac[4];

    printf("p1 - p2 = %d\n" , p1 - p2);
    printf("*p1 - *p2 = %d\n" , *p1 - *p2);
    // p1 - p2 = 1   也就是 5 -4
    // *p1 - *p2 = 16  也就是 20 - 4

    
    int ai[] = {0,1,2,3,4,5,6,7,8,18,};
    int *q1 = &ai[9];
    int *q2 = &ai[2];

    printf("q2 - q1 = %d\n", q2 - q1);
    printf("*q2 - *q1 = %d\n", *q2 - *q1);
    // q2 - q1 = -7      也就是 2 - 9
    // *q2 - *q1 = -16   也就是 2 - 18

    return 0;
}

*p++

  1. 取出p所指的那个数据来,完事之后顺便把p移动到下一个位置
  2. *的优先级虽然高,但没有++高
  3. 常用于数组类的连续空间操作
  4. 在某些cpu上,这可以直接被翻译成一条汇编指令
    char ac[] = {0,1,2,3,4,20,6,7,8,9,-1};
    char *p = &ac[0];

    // 普通方法遍历数组
    // int i ;
    // for (i = 0; i < sizeof(ac)/sizeof(ac[0]); i++)
    // {
    //  printf("%d\n", ac[i]);
    // }
    
    // 普通指针遍历数组
    // for (p = ac; *p != -1; p++)
    // {
    //  printf("%d\n", *p);
    // }

    // printf("---------------\n");
    
    // 使用*p++,快
    while(*p != -1){
        printf("%d\n", *p++);
    }

0地址

  1. 当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
  2. 所以你的指针不应该具有0值
  3. 因此可以用0地址来表示特殊的事情
    • 返回的指针是无效的
    • 指针没有被真正的初始化(先初始化为0)
  4. null是一个预定定义的符号,表示0地址

指针的类型

  1. 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
  2. 但是指向不同类型的指针是不能直接互相赋值的
  3. 只是为了避免用错指针

用指针来做什么

  1. 需要传入较大的数据时用作参数
  2. 传入数组后对数组做操作
  3. 函数返回不止一个结果
  4. 需要用函数修改不止一个变量
  5. 动态申请内存时

七、动态内存分配

输入数据

  1. 如果输入数据时,先告诉你个数,然后再输入,要记录每个数据
  2. c99可以用变量做数组定义的大小,c99之前呢?
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int number;
    int *a;
    int i;
    printf("请输入数量:\n");
    scanf("%d" , &number);

    // 动态内存分配用malloc,此时a就相当于数组
    a = (int*)malloc(number*sizeof(int));

    for (i = 0; i < number; i++)
    {
        scanf("%d" , &a[i]);
    }

    for (i = number - 1; i >=0; i--)
    {
        printf("%d\n", a[i]);
    }

    // 最后释放空间
    free(a);

    return 0;
}

malloc函数

#include <stdlib.h>
void* malloc(size_t size);
  1. 向malloc申请的空间的大小是以字节为单位的,需要什么类型就在sizeof里面写什么类型
  2. 返回的结果是void* , 需要类型转换为自己需要的类型
  3. 最后释放空间
(int*)malloc(n*sizeof(int));

没空间了?

  1. 如果申请失败则返回0 , 或者叫做null
  2. 你的系统可以给你多大的空间?
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    void *p;
    int cnt = 0;

    while((p = malloc(100 * 1024 *1024))){
        cnt ++ ;
    }

    printf("分配了%d00mb的空间\n", cnt);

    return 0;
}
// 分配了27200mb的空间

free()

  1. 把申请得来的空间还给系统
  2. 只能还申请的空间的首地址
  3. 一个malloc对应一个free