C语言学习(6)—— 指针

一、指针

(1)指针是内存的地址;指针变量是保存了内存地址的变量。

(2)在声明指针变量时,如果没有确切的地址赋值,则声明为空指针:int *ptr = NULL

(2)获取变量的地址用 &,比如: int num = 10,获取num的地址:&num

(3)指针变量存的是一个地址,这个地址指向的空间存的才是值,比如: int *ptr = # ptr 就是指向int类型的指针变量,即 ptr 是 int* 类型,ptr存的是num的地址,该地址存的是num的值。

(4)获取指针变量所指向的值使用 *,比如: int *ptr = &num,使用*ptr获取ptr指向的值,即num的值。

(5)指针是一个变量,其值为另一个变量的地址。就像其他变量或常量一样,在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为: ① int *ip:一个整型的指针;(2) double *dp:一个 double 型的指针;③ float *fp:一个浮点型的指针;(4)char *ch:一个字符型的指针。

#include   

int main(){
    int num = 2;

    // 获取变量的值
    printf("num的地址为: %p \n", &num);                            // num的地址为: 000000000061FE1C

    // int* 表示类型为 整数指针类型
    // ptr是一个 int* 类型,指向了int类型变量 num 的地址
    int *ptr = #

    // 获取指针变量的地址(&ptr):指针变量本身也有地址
    printf("ptr的地址为: %p \n", &ptr);                             // ptr的地址为: 000000000061FE10

    // 获取指针变量存储的内容(ptr):指针变量存储的是 num 的地址
    printf("ptr存储的内容为: %p \n", ptr);                          // ptr存储的内容为: 000000000061FE1C

    // 获取指针变量指向的值(*ptr):指针变量指向的值是 num 的值
    printf("ptr指向的值为: %d \n", *ptr);                           // ptr指向的值为: 2

    return 0;
}

二、指针的算数运算

1. 自增和自减

#include   

int main(){
    int arr[] = {10, 12, 15};
    int *ptr = arr;              // ptr是一个 int* 类型,指向了数组的首地址,即arr[0]的地址

    // 通过指针来获取数组中每一个元素的值
    for(int i = 0; i < 3; i++){
        printf("arr[%d]的地址: %p \n", i, ptr);       // ptr 指向数组元素的地址
        printf("arr[%d] = %d \n", i, *ptr);     // *ptr 是 ptr 指向地址对应的值 
        ptr++;              // ptr = ptr + 1(1个 int 长度的字节数),ptr指向了数组的下一个地址
    }

    return 0;
}
#include   

int main(){
    int arr[] = {10, 12, 15};
    int *ptr = &arr[2];              // ptr是一个 int* 类型,指向了数组 arr[2] 的地址

    for(int i = 2; i >= 0; i--){
        printf("arr[%d]的地址: %p \n", i, ptr);
        printf("arr[%d] = %d \n", i, *ptr);     
        ptr--;              // ptr = ptr - 1(1个 int 长度的字节数),ptr指向了数组的前一个地址
    }
    return 0;
}

2. 加法和减法

#include   

int main(){
    int arr[] = {10, 12, 15};
    int *ptr = arr;              // ptr是一个 int* 类型,指向了数组的首地址,即arr[0]的地址

    // 通过指针来获取数组元素的值
    ptr += 2;       // ptr = ptr + 2(2个int长度的字节数),ptr指向了当前地址 +2 后的地址
    printf("%d \n", *ptr);     // 15

    ptr -= 1;
    printf("%d \n", *ptr);     // 12

    return 0;
}

三、指针函数

当函数的形参类型是指针类型时,需要传递指针(变量的地址 或 数组)。

1. 函数形参是指针

#include  
 
void add(int *num){   //    num 等于 变量 的地址
    (*num)++;         //    *num 指向 变量 的值
}
 
int main(){
    int n = 3;
    int *ptr = &n;

    add(&n);
    printf("n = %d \n", n);   // n = 4

    add(ptr);
    printf("n = %d \n", n);   // n = 5
    
    return 0;
}
// 数组

#include  

// 通过下标的方式:该方式不会使 arr 的值发生变化
int getSum1(int *arr, int size){      //  arr 指向 数组 的首地址
    int sum = 0;
    for(int i = 0; i < size; i++){
        sum += arr[i];
    }
    return sum;
}

// 通过自增修改地址的方式:该方式会使 arr 的值发生变化
int getSum2(int *arr, int size){      //  arr 指向 数组 的首地址
    int sum = 0;
    for(int i = 0; i < size; i++){
        sum += *arr;                  //  *arr 为当前地址的值
        arr++;                        //  arr++ 指向 数组的下一个地址
    }
    return sum;
}

// 通过移动地址的方式:该方式不会使 arr 的值发生变化
int getSum3(int *arr, int size){      //  arr 指向 数组 的首地址
    int sum = 0;
    for(int i = 0; i < size; i++){
        sum += *(arr + i);                  //  *(arr + i) 为 arr[i] 地址的值
    }
    return sum;
}

int main(){
    int nums[] = {1, 2, 3, 4};
    int sum;

    sum = getSum1(nums, 4);
    printf("sum = %d \n", sum);   // sum = 10

    sum = getSum2(nums, 4);
    printf("sum = %d \n", sum);   // sum = 10
    
    sum = getSum3(nums, 4);
    printf("sum = %d \n", sum);   // sum = 10

    return 0;
}

2. 函数返回值是指针

指针函数:函数的返回值是一个指针(地址)

#include  

int *add(int *arr1, int len, int *arr){ 
    for(int i = 0; i < len; i++){
        arr[i] = arr1[i] + 2;
    }
    return arr;
}

int main(){
    int nums1[] = {1, 2, 3};
    int *nums;
    int *result;

    result = add(nums1, 3, nums);
    for(int i = 0; i < 3; i++){
        printf("%d  ", result[i]);   // 3  4  5
    }

    return 0;
}
#include 
#include 

char *getLong(char *string1, char *string2){  
    if(strlen(string1) > strlen(string2)){
        return string1;
    }else{
        return string2;
    }
}

int main(){
    char str1[] = "ABCD";
    char str2[] = "EF";
    char *str;

    str = getLong(str1, str2);
    printf("%s", str);    // ABCD
    return 0;
}

 (1)用指针作为函数返回值时,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形参,函数返回的指针不能指向这些数据;(2)函数运行结束后会销毁所有的局部数据,这里所谓的销毁并不是将局部数据所占用的内存全部清零,而是程序放弃对它的使用权限,后面的代码可以使用这块内存;

// 错误示例

#include  

int *getSum(int *arr, int size){    
    int sum = 0;        // 局部变量,在getSum返回时就会销毁
    for(int i = 0; i < size; i++){
        sum += arr[i];
    }
    return ∑        // 错误:不能返回局部变量的地址,因为该地址在调用完函数时会销毁
}

int main(){
    int nums[] = {1, 2, 3, 4};
    int *ptr;
    int sum;

    ptr = getSum(nums, 4);
    sum = *ptr;
    printf("sum = %d \n", sum); 

    return 0;
}

(3)C语言不支持在调用函数时返回局部变量的地址,如果确实有这样的需求,需要定义局部变量为 static变量。

#include  

int *getSum(int *arr, int size){    
    static int sum = 0;        // 静态变量存储在静态存储区,该区域的不会由于函数执行情况被回收
    for(int i = 0; i < size; i++){
        sum += arr[i];
    }
    return ∑        
}

int main(){
    int nums[] = {1, 2, 3, 4};
    int *ptr;
    int sum;

    ptr = getSum(nums, 4);
    sum = *ptr;
    printf("sum = %d \n", sum); 

    return 0;
}
#include  

int *add(int *arr1){
    static int arr[3];
    for(int i = 0; i < 3; i++){
        arr[i] = arr1[i] + 2;
    }
    return arr;
}

int main(){
    int nums1[] = {1, 2, 3};
    int *result;

    result = add(nums1);
    for(int i = 0; i < 3; i++){
        printf("%d  ", result[i]);   // 3  4  5
    }

    return 0;
}

四、函数指针

一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。

函数指针定义:函数返回值类型  (*指针名称) (函数参数列表);

注意:(1)参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称;(2)第一个括号不能省略,如果写作函数返回值类型  *指针名称 (函数参数列表)就成了函数原型。

#include  

int getMax(int x, int y){
    return x > y ? x : y;
}

int getMin(int x, int y){
    return x > y ? y : x;
}

int main(){
    int a = 2;
    int b = 3;
    int maxValue;
    int minValue;

    // ptrFun是函数指针的名称,int表示函数返回值为int,(int, int)表示该函数指针指向的函数形参是两个int
    int (*ptrFun)(int num1, int num2);

    ptrFun = getMax;                 // ptrFun指向函数getMax的地址
    maxValue = ptrFun(a, b);      // 调用函数getMax
    printf("%d", maxValue);          // 3

    ptrFun = getMin;                // ptrFun指向函数getMin的地址    
    minValue = ptrFun(a, b);     // 调用getMin
    printf("%d", minValue);         // 2

    return 0;
}

五、回调函数(函数挂钩)

函数指针变量可以作为某个函数的形参,回调函数就是一个通过函数指针调用的函数。 

将自己编写的钩子函数挂在已经声明的函数指针上

#include  

int getMax(int x, int y){
    return x > y ? x : y;
}

int getMin(int x, int y){
    return x > y ? y : x;
}

// ptrFun是回调函数,进行函数挂钩
int function(int (*ptrFun)(int num1, int num2), int x, int y){
    int result = ptrFun(x, y);
    return result;
}

int main(){
    int a = 2;
    int b = 3;
    int maxValue;
    int minValue;

    maxValue = function(getMax, a, b);      // 挂getMax的钩子函数,调用getMax
    printf("%d \n", maxValue);              // 3
 
    minValue = function(getMin, a, b);     // 挂getMin的钩子函数,调用getMin
    printf("%d \n", minValue);             // 2

    return 0;
}

六、多重指针

多重指针:定义一个指向指针的指针,第一个指针包含了第二个指针的地址,第二个指针指向实际值的地址。

数据类型  **指针名

#include   

int main(){
    int num = 2;
    int *ptr = #        // 一级指针,指向 num值 的地址
    int **pptr = &ptr;      // 二级指针,指向 ptr 的地址

    printf("num = %d,  num的地址: %p \n", num, &num);
    printf("num = %d,  num的地址: %p,  ptr的地址: %p \n", *ptr, ptr, &ptr);
    printf("num = %d,  num的地址: %p,  ptr的地址: %p,  pptr的地址: %p \n", **pptr, *pptr, pptr, &pptr);
}

七、指针数组

指针数组:数组中的元素 是 基本数据类型 的地址(指针)

数据类型  *指针数组名[大小]

int *ptr[3]:ptr是一个指针数组,包含3个整数指针,其中每个元素都是一个指向int类型值 的 指针

#include   

int main(){
    int arr[] = {10, 12, 15};
    int *ptr[3];              // ptr是一个指针数组

    // 给指针数组赋值,指针数组的每个元素都是一个指向值的指针
    for(int i = 0; i < 3; i++){
        ptr[i] = &arr[i];       // ptr 的每一个元素都是 数组arr每一个元素的地址,该地址(指针)指向了arr每个元素的值
    }

    // 通过指针数组来获取 数组元素的值
    for(int i = 0; i < 3; i++){
        printf("%d \n", *ptr[i]);   // *ptr[i] 是 ptr[i] 指针 对应的值    
    }

    return 0;
}
// 字符串指针数组

#include   

int main(){
    char *ptr[] = {"ABC", "DEF", "GHI"};  

    // 通过指针数组来获取 数组元素的值
    for(int i = 0; i < 3; i++){
        printf("%s \n", ptr[i]);     
    }

    return 0;
}

八、动态内存分配

不同类型的数据在内存中的分配情况:① 静态变量和全局变量:静态存储区;② 非静态的局部变量:动态存储区(栈);③ 临时使用的数据:动态存储区(堆),需要时随时开辟,不需要时及时释放。

动态内存分配:根据需要向系统申请所需大小的空间,由于未在声明部分定义其为变量或者数组,不能通过变量名或者数组名来引用这些数据,只能通过指针来引用。使用完堆上分配的内存后要及时释放,否则会导致内存泄露。

内存动态分配的相关函数(在 中):

(1) void * malloc (usigned int size):在内存的动态存储区中分配一个长度为size的连续空间,形参size的类型为无符号整型,函数返回值是所分配区域的第一个字节的地址,即此函数是一个指针型函数,返回的指针指向该分配域的开头位置。例如:malloc(100); //开辟100字节的临时空间,返回值为其第一个字节的地址。

(2)void *calloc (unsigned n, unsigned size):在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,函数返回指向所分配域的起始位置的指针,分配不成功,返回NULL。用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。例如:p = calloc(50,4); //开辟 50*4 个字节临时空间,把起始地址分配给指针变量 p。

(3)void free (void *p):释放变量p所指向的动态空间,使这部分空间能重新被其他变量使用,p是最近一次调用calloc或malloc函数时的函数返回值,free函数无返回值。例如:free(p);  // 释放p 所指向的已分配的动态空间。

(4)void *realloc (void *p,unsigned int size):重新分配malloc或calloc函数获得的动态空间大小,将p指向的动态空间大小改变为size,p的值不变,分配失败返回NULL。例如:realloc(p,50);  // 将p所指向的已分配的动态空间 改为50字节。

malloc,calloc,realloc 函数的基类型定为 void 类型,这种指针称为无类型指针,即不指向哪一种具体的类型数据,只表示用来指向一个抽象的类型的数据,即仅提供一个纯地址。

#include  
#include

int main(){
    int *arr;
                                // arr指向开辟的动态存储区的首地址
    arr = malloc(20);           // arr是int *,malloc返回值是void *,这里相当于自动进行了类型转换 arr = (int *)malloc(20)
    for(int i = 0; i < 5; i++){
        arr[i] = i;
    }

    for(int i = 0; i < 5; i++){
        printf("%d ", arr[i]);    // 0 1 2 3 4
    }

    free(arr);       // 销毁堆区 arr 指向的整个空间
    
    return 0;
}

九、总结

变量定义 含义
int i 定义整型变量 i
int *p 定义 p为指向整型数据的指针变量
int a[5] 定义整型数组 a,包含5个元素
int *p[5] 定义指针数组 p,其中每个元素都是一个指向int类型值 的 指针
int (*p)[5] 定义 p 为指向 包含5个元素的一维数组 的指针变量
int f() 定义 f 为返回整型值的函数
int *p() 定义 p 为返回一个指针的函数,该指针指向整型数据
int (*p) () 定义 p 为函数指针,指向某函数(该函数返回一个整型值)的地址
int **p 定义 p 是一个指针变量,它指向一个指向整型数据的指针变量
void *p 定义 p 是一个指针变量,基类型为 void(空类型),不指向具体的对象 

你可能感兴趣的:(C语言,C语言)