指针

基本概念

指针的概念

引例1:答疑教室

  • 期末考试前,主讲教师在最后一市课通知学生下周会有的助教答疑,但是答疑教室还没有确定。主 讲教师皆诉学生:答疑那天,在教研室的疑教室的房间号。假设学生已经知道教研室的房间号102,请问如何找到助教?

  • 答疑教室的房间号相当于变量rs0m的地址。由于变量room的存储地址存放在变量中,则可以通过变量p取出变量room的存储地址,从而实现对变量class的间接访问。

    image-20220417154415102

  • 设置变量room表示答疑教室,p代表教研室,算法如下

    step1:安排答疑助教, 为变量room赋值
    step2:将变量room的存储地址存放在变量p中
    step3:通过变量p获得变量room的值
    
#include "stdio.h"

int main(){

    int room = 1582;
    int *p = NULL;
    p = &room;
    printf("教室房间号是:%X\n", p);
    printf("助教的教师编号是:%d\n", *p);


    return 0;
}
//教师编号表示答疑助教。用取地址运算符&获得变量ro0m的地址并赋给指针变量p,再通过指针p访问变量room

为了正确理解指针,必须在机器层面理解变量在内存中的存储万式,深刻理解变量地址的含义。

image-20220417155356787

指针变量的定义

  • 指针变量的定义的一般形式如下

    基类型 *指针变量名;
    int *a;
    
  • 其中,基类型是该指针变量指向内存单元的数据类型,可以是任意合法的数据类型;“*” 称为指针定义符,用来说明指针变量以区别于普通变量;指针变量名是合法的标识符。

    “*”称为间接引用运算符;

    指针变量必须指向某个确定的存储单元

    含义:访问指针变量所指存储单元

指针变量需要注意的问题

    1. 定义指针变量时需要明确该指针所指向的数据类型,即该指针所指向的内存单元可以存放什么类型的数据,对 指针变量的运算与它所指向的数据类型密切相关。

      int x, *p;//指针变量p只能指向int类型的变量
      double y, *q;//指针变量q只能指向double类型的变量。
      

      image-20220417160218427 image-20220417160403815

    2. 定义指针变量时,“”是指针定义符,用来说明该变量是指针变量;对指针变量进行间接访问时,“”是间接引用运算符,用来访问指针所指存储单元。

      int num;
      int *p = #
      num = 100;
      *p = 100;
      

      image-20220417160727760

    3. 指针变量存放该指针指向变量的存储地址,内存地址通常是一个无符号整数,因此所有指针变量占有相同大小的存储空间,具有占用的存储空间单元数与计算机系统和编译器有关。

      image-20220417160832271

变量的访问方式

  • 直接访问:通过变量名对变量所占存储单元进行访问(即存取操作),这种访问方式称为直接访问 image-20220417161006722

    int x = 10; //对变量x进行存操作,x体现左值特征——变量的地址
    

    image-20220417161112461

    int y = x; //对变量x进行取操作,x体现右值特征——变量的值
    
  • 间接访问:通过指针对指针所指向变量的存储单元进行访问,这种访问方式称为变量的间接访问

    int x = 10, y;
    int *p = &x;
    *p  = 10; //间接访问——存操作
    y = *p;//间接访问——取操作
    

    image-20220417161305489

指针变量的初始化

int *p;
int *p = NULL; //NULL为空指针
  • 系统为变量p分配存储空间,由于尚未给指针变量p赋值,则指针阿斌了p是“值无定义的”,可能指向内存中任意位置,这种指针称为野指针

  • 野指针在程序中是很危险的,可能会引发系统崩溃 image-20220417161517094

  • 良好的编程习惯是初始化空指针为NULLimage-20220417161634137

  • 初始化指针变量可以將变量的存储地址或是另一个已经定义的指针赋给该指针变量。

    int x = 10;
    int *p = &x;
    int *q = p;
    

    image-20220417162406245

  • 在指针变量初始化的时,赋值运算符左侧内存地址的数据类型应该与基类型一致

    int x = 10;
    double *q = &x;//指针q的基类型是double,不能指向int类型变量
    
    int x = 10, *p=&x;
    double *q = p;//指针q和指针p的基类型不一致
    

指针变量的赋值

int x = 10;
int *p = NULL, *q = NULL; // 指针p和q均初始化为空
p = &x;// 指针p指向变量x
q = p;// 指针q指向变量p所指内存单元

image-20220417163101916

  • 通用指针是可以指向任意类型数据的指针

  • 定义通用指针的一般形式如下:

    void *指针变量名;
    
  • 其中,"*"是指针定义符;void *表示通用指针;指针变量名是一个合法的标识符。

  • 含义:定义通用指针,编译器为该指针变量分配存储空间

    #include "stdio.h"
    
    int main(){
    
        void *void_ptr = NULL;//定义void_ptr为通用指针并初始化为空
        int *int_ptr, num = 10;
        double *double_ptr, radius = 2.5;
        void_ptr = #//将void_ptr可以指向int型变量
        int_ptr = (int *)void_ptr;//将void_ptr强制类型转换
        void_ptr = &radius;//void_ptr可以指向double型变量
        double_ptr = (double *)void_ptr;//将指针void_ptr强制类型转换
    
    
        return 0;
    }
    

    image-20220417163617946

int *p = NULL; //p为指向int型数据的指针变量
p  = 100;//编译器会给出警告

image-20220417164009292

指针与函数传参

引例2:数据交换问题

  • 阅读以下两段程序,理解并分析为什么会有不同的运行结果?
#include "stdio.h"

void swap(int a, int b){
    int temp = a;
    a = b;
    b = temp;
}

int main(){
    int x = 5, y = 9;
    printf("swap函数调用前: x = %d y = %d\n", x, y);
    swap(x, y);
    printf("swap函数调用后: x = %d y = %d\n", x, y);



    return 0;
}

执行流程

image-20220417164853838

image-20220417164915377

#include "stdio.h"

void swap2(int * a, int * b){
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main(){
    int x = 5, y = 9;
    printf("swap函数调用前: x = %d y = %d\n", x, y);
    swap2(&x, &y);
    printf("swap函数调用后: x = %d y = %d\n", x, y);

    return 0;
}

image-20220417165204872

image-20220417165437391

传值方式

  • 函数定义时,将形参定义为普通类型(即菲指针类型),实参可以是类型与形参相容的常量、变量或表达式;
  • 函数调用时,系统为形参分配存储空间,然后将实参的值传递到形参中;
  • 调用结東后,系统自动释放形参的存储空间。
  • 值传递方式的特点:被调用函数的执行不影响函数的实参,即在被调用函数中不能对函数的实参进行修改,因此,通常以值传递方式实现函数的输入。

传地址方式

  • 函数定义时,将形参声明为指针类型,实参以是基类型与形参相容的指针大变量地址;
  • 函数调用时,系统为形参分配存储空间,并将实参的间(即地址)传递到形参中;
  • 调用结束后,系统自动释放形参的存储空间。
  • 指针传递方式的特点:在被调用函数中可以对实参地址所对应的存储单元进行访问,即可以读取或修改该内存单元的值。因此,可以通过指针传递方式实现函数的输出,即将被调用函数修改的值传递(返回)给调用者。

指针与一维数组

指针与一维数组

引例3:顺序查找

  • 要求用指针实现:在整数集合r中顺序查找与给定值key相等的元素

  • 用一维整型数组a[n]存储整数集合,顺序查找从数组的第一个元素开始,依次比较每一个元素,直至找到key为止,如果数组的全部元素都比较完了,说明查找失败。 image-20220417201214071

    #include "stdio.h"
    
    #define N 6
    
    int main(){
    
        int a[N] = {2, 4, 8, 6, 5, 3}, i, key, index = 0;
        key =  8;
        for (int i = 0; i < N; i++)
        {
            if (a[i] == key){
                index = i;
                break;
            }
        }
        if (i == N) index = -1;
        if (index == -1)
            printf("查找失败!\n");
        else
            printf("查找功能,元素%d在集合中的序号是%d\n", key, index);
        
    
    
    
        return 0;
    }
    
    #include "stdio.h"
    
    #define N 6
    
    int main(){
    
        int a[N] = {2, 4, 8, 6, 5, 3}, i, key, index = 0;
        key =  8;
        for (int i = 0; i < N; i++)
        {
            if (a[i] == key){
                index = i;
                break;
            }
        }
        if (i == N) index = -1;
        if (index == -1)
            printf("查找失败!\n");
        else
            printf("查找成功,元素%d在集合中的序号是%d\n", key, index);
        
    
    
    
        return 0;
    }
    
  • 对一维数组元素的访问共有三种方式:

    1. 通过下标访问数组元素:

      int a[10];
      for(int i=0;i<10;i++)
          a[i] = i;
      
    2. 通过地址访问数组元素

      int a[10];
      for(int i=0;i<10;i++)
          *(a+i) = i;
      
    3. 通过指针访问数组元素:

      int a[10];
      int *p = a;
      for(int i = 0;i<10;i++)
          *(p+i) = i;
      

      image-20220418175548880

p++自增运算要比p+i算术运算快得多,因此其执行效率比地址方式高。

int a[10];
int *p =  a;
for(int i = 0;p<(a+10);i++,p++)//表达式三是逗号表达式
    *p = i;

image-20220418175813965

指针变量的算术运算

  • 指针变量的加减运算。对指针变量加上或减去一个整数,表示将该指针后移或前移确定的存储单元

  • 两个同类型指针的减法运算。对两个相同类型的指针变量进行减运算,表示这两个指针间有多少存储单元。

  • 对指针变量加上或减去一个浮点数、进行乘、除等算术运算,对西个指针变量进行加、乘、除等算术运算通常没有实际意义

  • 因为数组占用一段连续的内存空间,因此,指针的算术运算通常用于数组

    int num, a[5] = {1, 2, 3, 4 ,5}
    int *p  = a, *q;
    q = p + 4;
    num = q - p;//4
    num = (int)q-(int)p;//16
    

指针变量的关系运算

  • 通常对指向同一数组中的指针变量之间进行比较大小具有实际意义。
  • 如果表达式p>q成立,则指针p所指数组元素在指针q所指数组元素的后面;
  • 假设数组a有10个元素,如果表达式p<a+10成立,则指针p指向数组中某元素。

一维作为函数的参数

image-20220418180730821

#include "stdio.h"


void swap(int * a, int * b);
void input(int * nums, int n);
void output(int * nums, int n);
void process(int * nums, int n);
void process2(int * nums, int n);

int main(){

    int a[10];

    input(a, 5);
    process(a, 5);
    output(a, 5);

    return 0;
}


void swap(int * a, int * b){
    int temp = *a;
    *a = *b;
    *b = temp;
}

void input(int nums[], int n){
    printf("请输入%d个数字:", n);;
    for (int i = 0; i < n; i++)
    {
        scanf("%d", &nums[i]);
    }
}

void output(int nums[], int n){
    printf("调整后:");
    for (int i = 0; i < n; i++)
    {
        if (i == 0){
            printf("%d", nums[i]);
        }else{
            printf(" %d", nums[i]);
        }
    }
    printf("\n");
}

void process(int nums[], int n){
    int min, max;
    min = max = 0;
    for (int i = 0; i < n; i++)
    {
        if (nums[i] < nums[min]) min = i;
        if (nums[i] > nums[max]) max = i;
    }
    if (min != 0){
        swap(&nums[0], &nums[min]);
    }
    if (max != n - 1){
        if (max != 0){
            swap(&nums[n - 1], &nums[max]);
        }else{
            swap(&nums[n - 1], &nums[min]);
        }
    }
}

void process2(int * nums, int n){
    int *p;
    int *min, *max;
    p = max =  min = nums;

    for(;p < nums + n;p++){
        if (*p < *min) min = p;
        if (*p > *max) max = p;
    }

    if (min != nums){
        swap(nums, min);
    }
    if (max != nums + n - 1){
        if (max != min){
            swap(min, nums + n - 1);
        }else{
            swap(max, nums + n - 1);
        }
    }

    output(nums, n);

        
}

image-20220418203032845

指针与二维数组

指针与二维数组

  • 如何理解二维数组?我们以二维的方式理解二维数组,它在存储却是一维的 image-20220418203406204
  • 把二维数组的每一行看成是一个一维数组,其中每个数组元素是一个一维数组。 image-20220418203658571
  • 通过指针访问二维数组的不同形式 image-20220418203850313

用指针访问二维数组

image-20220418204523309

image-20220418204807592

image-20220418204854857

指向字符串的二维数组

char *str[3] = {"Red", "Green", "Blue"};

image-20220418210802703

image-20220418210854882

#include "stdio.h"

void average(int * score);
void search(int (*score)[4], int n);


int main(){

    int score[][4] = {{65, 70, 70, 60}, {80, 87, 90, 81}, {90, 99, 100, 98}};

    average(&score[0][0]);
    search(score, 2);
    return 0;
}
void average(int * score){
    
    int sum = 0;
    int * p_end = score + 12, *p = score;
    while(p != p_end){
        sum += *p++;
    }
    printf("avg = %.2f\n", sum/12.0);
}

void search(int (*score)[4], int n){
    int i;
    printf("第%d个学生的成绩为:", n);
    for (int i = 0; i < 4; i++)
    {
        printf("%d ", *(*(score+n)+i));
    }
    printf("\n");
    
}

二维数组作为函数的参数

image-20220418212822493

image-20220418212917900

image-20220418213041793

#include "stdio.h"
#include "math.h"

void swap(int *a, int *b);
void inverse1(int *p, int n);
void inverse2(int (*p)[4]);

int main(){
    int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};

    // inverse1(&a[0][0], 12);
    inverse2(a);

    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            printf("%2d ", a[i][j]);
        }
        printf("\n");
    }
    


    return 0;
}

void inverse1(int *p, int n){
    int *q = p, *q_end = p + n - 1;

    while(q < q_end){
        swap(q, q_end);
        q_end--;
        q++;
    }
}

void inverse2(int (*p)[4]){
    int (*q)[4];

    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            if (i == 1 && j == 2){
                break;
            }
            swap(&p[i][j], &p[2-i][3-j]);
        }
        
    }
    
}

void swap(int *a, int *b){
    int temp = *a;
    *a = *b;
    *b = temp;
}

指针函数与函数指针

指针函数的定义(返回指针的函数)

  • 指针函数的定义 函数声明:返回值* 函数名(参数类型序列); 函数定义:返回类型* 函数名(形式参数序列){

    函数体
    
    return 返回指针类型变量;
    

    }

  • 指针函数调用 char *q; q = 函数名(实际参数列表) q与被调用函数的返回类型一致

#include "stdio.h"

const char * getWord(char c){
    switch(c){
        case 'A':return "Apple";break;
        case 'B':return "Banana";break;
        case 'C':return "Cat";break;
        case 'D': return "Dog";break;
        default: return "None";
    }
}

int main(){

    char input;
    printf("Please input a character:");
    input = getchar();
    printf("%c\n", input);

    printf("%s\n", getWord(input));
    

    return 0;
}

指针函数,就是函数的返回值为指针类型。在这个例子中,输入字母后,返回字母对应的字符串。getWord函数的返回值为字符串的首地址。

#include "stdio.h"

const char * getWord(char c){
    char str1[] = "Apple";
    char str2[] = "Banana";
    char str3[] = "Cat";
    char str4[] = "Dog";
    char str5[] = "None";


    switch(c){
        case 'A':return str1;break;
        case 'B':return str2;break;
        case 'C':return str3;break;
        case 'D': return str4;break;
        default: return str5;
    }
}

int main(){

    char input;
    printf("Please input a character:");
    input = getchar();
    printf("%c\n", input);

    printf("%s\n", getWord(input));
    

    return 0;
}

注意:不要返回局部变量的指针(数组》。在这个例子中,str1数组是局部变量,这个字符数组在子程序结束后,它所对应的存储空间会被释放。

(函数指针)指向函数的指针

函数指针的定义和调用

  • 函数指针顾名思义就是指向函数起始地址的指针
  • 函数指针定义 函数返回类型名(*指针变量名)(函数指针类型列表) int (*fptr)(int, int);
  • 函数指针的使用
    1. 指针变量名=被指向函数名; fptr = maxx;
    2. 函数调用:指针变量名(函数调用时的实际参数) int x = ptr(5, 8);
#include "stdio.h"

int square(int num){
    return num*num;
}

int main(){

    int num;
    int (*fp)(int);
    printf("please input a number:");
    scanf("%d", &num);
    fp = square;

    printf("fp=0x%x, %d\n", fp, (*fp)(num));
    // printf("fp=0x%x, %d\n", fp, fp(num));


    return 0;
}

函数名等于函数的地地址。

通过sp=square;语句使得fp指向函数的入口,因此通过fp指针就能找到函数square的代码存放在哪里,从而调用并执行这段代码。

函数指针的说明

  • 函数指针描一类函数的共同特征,这些函数有相同的形式参数类型序列,相同的返回类型。

  • 函数指针是指针变量,可以指向函数的入口地址,上面函数square的入口地址:0x401340(每次运行 都不一定相同)

  • 函数指针与指针函数不冋,函数指针是一个指针变量,它可以指向符合定义过程描叙的任何函数;指针函数是一个函数,函数返回值的类型是一个指针。

    函数指针作为参数进行传递

#include "stdio.h"

int add(int num1, int num2){
    return num1 + num2;
}

int sub(int num1, int num2){
    return num1 - num2;
}

int calculate(int (*fp)(int, int), int num1, int num2){
    return fp(num1, num2);
}

int main(){

    printf("3 + 5 = %d\n", calculate(add, 3, 5));
    printf("3 - 5 = %d\n", calculate(sub, 3, 5));


    return 0;
}

定义一个函数指针数组

#include "stdio.h"

void function1(int);
void function2(int);
void function3(int);


int main(){
    void (*f[3])(int) = {function1, function2, function3};
    int choice;
    printf("输入一个数字:范围[0~2]:");

    scanf("%d", &choice);
    while(choice >= 0 && choice < 3){
        (*f[choice])(choice);//(*f[choice])(choice)
        printf("输入一个数字:范围[0~2]:");
        scanf("%d", &choice);
    }
    printf("程序运行结束。");


    return 0;
}

void function1(int a){
    printf("我是1 : %d\n", a);
}
void function2(int a){
    printf("我是2 : %d\n", a);

}
void function3(int a){
    printf("我是3 : %d\n", a);

}

指针与结构体

结构体变量的传参

image-20220418231252284

image-20220418231442791

//返回结构体变量
#include "stdio.h"

typedef struct Point{
    int x;
    int y;
}Point;

void Display(Point point){
    printf("x is %d\n", point.x);
    printf("y is %d\n", point.y);
}

Point SetPoint(int x, int y){
    Point point;
    point.x = x;
    point.y = y;
    return point;
}

int main(){
    Point point;
    point=SetPoint(2, 3);
    Display(point);

    return 0;
}

x is 2 y is 3

//传值方式传参
#include "stdio.h"

typedef struct Point{
    int x;
    int y;
}Point;

void Display(Point point){
    printf("x is %d\n", point.x);
    printf("y is %d\n", point.y);
}

void SetPoint(Point point){
    point.x = 2;
    point.y = 3;
}

int main(){
    Point point;
    SetPoint(point);
    Display(point);

    return 0;
}

x is 228401189 y is 1

结构体数组的传参

image-20220418235946238

image-20220419000318279

#include "stdio.h"

const int N = 2;
struct StudentType{
    char no[10];
    double foreign;
    double spec;
    double total;
};
void InputMarks(struct StudentType studnet[], int n);
void AddMarks(struct StudentType studnet[], int n);
void OutputMarks(struct StudentType studnet[], int n);


int main(){
    struct StudentType student[10];
    int n;
    printf("请输入学生人数:");
    scanf("%d", &n);
    InputMarks(student, n);
    AddMarks(student, n);
    printf("每个学生的总成绩为:\n");
    OutputMarks(student, n);


    return 0;
}

void InputMarks(struct StudentType studnet[], int n){
    for (int i = 0; i < n; i++)
    {
        printf("请输入%d考生考号: ", i+1);
        scanf("%s", studnet[i].no);
        printf("请输入%d考生外语成绩: ", i+1);
        scanf("%lf", &studnet[i].foreign);
        printf("请输入%d考生专业课成绩: ", i+1);
        scanf("%lf", &studnet[i].spec);
    }
    
}
void AddMarks(struct StudentType studnet[], int n){
    for (int i = 0; i < n; i++)
    {
        studnet[i].total = studnet[i].foreign + studnet[i].spec;
    }
    
}
void OutputMarks(struct StudentType studnet[], int n){
    for (int i = 0; i < n; i++)
    {
        printf("%s的总分是%5.1lf\n", studnet[i].no, studnet[i].total);
    }
    
}

结构体与指针

image-20220419001654956

回顾前面的例子

//传值方式传参
#include "stdio.h"

typedef struct Point{
    int x;
    int y;
}Point;

void Display(Point point){
    printf("x is %d\n", point.x);
    printf("y is %d\n", point.y);
}

void SetPoint(Point point){
    point.x = 2;
    point.y = 3;
}

int main(){
    Point point;
    SetPoint(point);
    Display(point);

    return 0;
}

使用结构体指针

#include "stdio.h"

typedef struct Point{
    int x;
    int y;
}Point, *pPoint;

void Display(Point point){
    printf("x is %d\n", point.x);
    printf("y is %d\n", point.y);
}

void SetPoint(pPoint point){
    point->x = 2;
    point->y = 3;
}

int main(){
    Point point;
    SetPoint(&point);
    Display(point);

    return 0;
}

image-20220419002855193

指向结构体数组的指针

image-20220419003212625

image-20220419003313669

动态内存分配

系统内存分区

image-20220419115325835

image-20220419194541129

image-20220419194848439

image-20220419195104270

image-20220419195230881

image-20220419195540078

image-20220419195619216

image-20220419195745933