C语言集训--车蒙蒙--第五次作业

170 阅读6分钟

指针


1.指针的重要性:

表示一些复杂的数据结构;快速的传递数据,减少了内存的耗用;使函数返回一个以上的值;能直接访问硬件,能够方便地处理字符串;是理解面向对象语言中引用的基础。

总结:指针是C语言的灵魂

2.指针的定义:

地址: 内存单元的编号;从零开始的非负整数 指针:

指针就是地址,地址就是指针;

指针变量就是存放内存单元编号的变量,或者说指针变量就是存放地址的变量;

指针和指针变量是两个不同的概念;

但要注意的是:通常我们叙述时会把指针变量简称为指针,实际它们含义并不一样;指针的本质就是一个操作受限的非负整数。


3.指针的分类:

基本类型指针:

int * p:p是变量的名字,int * 表示p变量存放的是int类变量的地址

    //int * p;不表示定义了一个名叫*p的变量;
    
    //int* p;应该这样理解:p是变量名,p变量的数据类型是int*类型
    
    //所谓int* 类型,实际就是存放int变量地址的类型
    
    int i = 3;
    int j;
    p = &i;
    
  • p保存了i的地址,因此p指向i;

  • p不是i,i也不是p,更准确的说:修改p的值不影响i的值。修改i的值也不会影响p的值;

  • 如果一个指针变量指向了某个普通变量,则* 指针变量 就完全等于 普通变量;

  • 如果p十个指针变量,并且p存放了普通变量i的地址,则p指向了普通变量i ;* p 就完全等同于i;或者说在所有出现*p的地方都可以替换成i,在所有出现i的地方都可以替换成 * p

附注:

* 的含义:

1.乘法;

2.定义指针变量;

3.指针运算符:该运算符放在已经定义好的指针变量的前面,如果p是一个已经定义好的指针变量,则* p表示以p的内容为地址的变量。

如何通过被调函数修改主调函数普通变量的值:

1.实参必须为该普通变量的地址;

2.形参必须为指针变量;

3.在被调函数中通过

  • 形参名 =......

的方式就可以修改主调函数相关变量的值


4.指针和数组:
指针和一维数组:

一维数组名是个指针常量,它存放的是一维数组第一个元素的地址

下标和指针的关系

如果p是个指针常量,则p[i]永远的等价于* (p+i)

确定一个一维数组需要几个参数【如果一个函数要处理一个一维数组,需要确定两个参数:数组第一个元素的地址数组的长度

指针变量的运算:

指针变量不能相加,不能相乘,也不能相除;如果两个指针变量指向的是同一块连续空间中的不同存储单元,则这两个指针变量才可以相减。

一个指针变量到底占几个字节:

sizeof(数据类型)

功能:返回值就是该数据类型所占的字节数

  • 例子:sizeof(int) = 4 ;sizeof(char) = 1;
  • sizeof(double) = 8

sizeof(变量名)

功能:返回值是该变量所占的字节数

  • 假设p指向char类型变量(一个字节)

  • 假设q指向int类型变量(4个字节)

  • 假设r指向double类型变量(8个字节)

总结:一个指针变量,无论它指向的变量占几个字节,该指针变量本身只占四个字节;一个变量的地址是用该变量首字节的地址来表示。


利用指针完成两个数字互换:
#include<stdio.h>

void swap_3(int* p, int* q)
{
    int t;
    t = *p ; *p = *q ; *q = t;
}

int main(void)
{
    int a = 3;
    int b = 5;

    swap_3(&a,&b);
    printf("a = %d, b = %d\n",a , b);

    return 0;
}

image.png

检测实参和形参是否是用一个变量:
#include<stdio.h>

void f(int * i)
{
     *i = 99;
}
int main(void)
{
    int i =6 ;
    printf("i = %d\n",i);
    f(&i);
    printf("i = %d\n",i);

    return 0;
}

image.png

#include<stdio.h>

void f(int  i)
{
     i = 99;
}
int main(void)
{
    int i =6 ;
    printf("i = %d\n",i);
    f(i);
    printf("i = %d\n",i);

    return 0;
}

image.png

指针使函数返回一个以上的值:
#include<stdio.h>

void g(int * p, int * q)
{
    *p = 1;
    *q = 2;
}
int main(void)
{
    int a = 3,b = 5;
    g(&a, &b);
    printf("%d %d\n",a,b);

    return 0;
}

image.png

确定一个一维数组需要两个参数:
#include<stdio.h>
void f(int * parr, int len)
{
    int i;

    for (i = 0;i <len; ++i)
        printf("%d\n",*(parr + i));
        printf("\n");
}
int main()
{
    int a[5] = {1,2,3,4,5};
    int b [6] ={-1,-2,-3,4,5,-6};
    int c [100] = {1,99,22,33};

    f(a ,5);
    f(b, 6);
    f(c, 100);

    return 0;
}

image.png

指针变量的运算:
#include<stdio.h>

int main()
{
    int i = 5;
    int j =10;
    int * p =&i;
    int *q = &j;
    int a[5];
    p = &a[1];
    q = &a[4];
    printf("p和q所指向的单元相隔%d个单元\n",q-p);

    return 0;
}

image.png


指针和二维数组:

多级指针:

int i = 10;

int * p=&i; //p只能存放int类型变量的地址

int ** q = &p;//q是int ** 类型,所谓int** 类型就是指q只能存放int* 类型变量的地址;

int *** r = &q;

printf(“i = %d\n”,*** r);


为什么需要动态分配内存:

动态数组很好的解决了传统数组的四个缺陷(传统数组也叫静态数组)

动态内存分配举例,动态数组的构造:

int* p=(int*)malloc(int len);

  • malloc函数能且只能返回第一个字节的地址,所以需要把这个无任何实际意义的第一个字节的地址(俗称干地址)转化为一个有实际意义的地址,因此,malloc前面必须加(数据类型*),表示把这个无实际意义的第一个字节的地址转化为相应类型的地址。

如: int* p=(int*)malloc(50);

  • 表示将系统分配好的50个字节的第一个字节的地址转化为int* 型的地址,更准确的说是把第一个字节的地址转化为四个字节的地址,这样p就指向了第一个的四个字节,p+1就指向了第二个字节的四个字节,p+i就指向了第i+1个的四个字节。p【0】就是第一个元素,p【i】就是第i+1个元素。

double* p=(double *) malloc(80);

  • 表示将系统分配好的第一个字节的地址转化为8个字节的地址,这样p就指向了第一个的8个字节,p+1就指向了第2个的8个字节,p+i就指向了第i+1的8个字节。

free (p)

  • 表示把p所指向的内存给释放掉,p本身的内存是静态的;不能由程序员手动释放,p本身的内存只能在p变量所在的函数运行终止时由系统自动释放;

image.png


习题作业

打印结果如下:

image.png

2.数组ref有4个元素;

3.数组名ref指向该数组的首元素(整数8); 表达式ref+1:指向该数组的第二个元素(整数4); ++ref不是有效的表达式。

a.* ptr = 12* (ptr+2) = 16 b.* ptr= 12* (ptr+2)= 14

12和16

12和14