C 语言学习笔记(指针5)

内容提要

  • 指针
    • main函数原型
    • 常量指针与指针常量
    • 野指针、空指针、空悬指针
    • void与void*的区别
  • 动态内存分配

指针

main函数原型

定义

main函数有多种定义格式,main函数也是函数,函数相关的结论对main函数也有效。

main函数的完整写法;

int main(int argc, char *argv[]){..}
int main(int argc, char **argv){..}

扩展写法:

main(){}  等价 int main(){}  //c11之后不再支持 缺省 返回类型
int main(void){}  等价  int main(){}
void main(void){}  等价  void main(){}
int main(int a){}
int main(int a, int b, int c){}
...
说明

①argc,argv是形参,他们俩可以修改

②main函数的扩展写法有写编译器不支持,编译报警告

③argc和argv的常规写法

  • argc:存储了参数的个数,默认是1个,也就是运行程序的名字。
  • argv:存储了所有参数的字符串形式

④main函数是系统通过函数指针的回调调用。

演示

代码:

int main(int argc, char *argv[])
{
    //访问参数的个数
    printf("argc=%d\n", argc);
    
    //遍历参数(每一个参数都是字符串常量)
    for (int i = 0; i<argc; i++)
    {
        printf("%s,%s\n", argv[i], *(argv+i));
    }
    printf("\n");
    
    return 0;
}

运行结果

在这里插入图片描述

常量指针与指针常量

常量类型

①字面量:直接使用的的固定值(如12,hello,orange,李双真帅)符号常量在编译器转换为了字面量。

②只读常量:用const修饰的变量,初始化后不可修改。

const int a = 10;  //只读常量
a = 21; //编译报错
常量指针
  • 本质:指向常量的指针
  • 语法
const 数据类型* 变量名;
const 数据类型 *变量名;
  • 举例
const int *p; // p是常量指针
  • 特性

    1.指向的数据不可通过指针修改,指向对象的值不可变*p = x非法);

    2.指针本身可指向其他地址,指向可以变p = &b合法)

  • 应用场景

  • 函数参数保护数据不被修改,使用率高

// 遍历数组
viod print_array(const int *arr, int len);
  • 案例演示
#include <stdio.h>

int main(int argc,char *argv[])
{
    int a = 10;// 变量
    const int *p= &a; // 常量指针

    //*p=100;  //错误:指针指向的数据不可改变
    printf("%d\n",*p);  // 10

    int b = 20;    //变量
    p = &b;          //正确:指针指向可改变
    printf("%d\n",*p); // 20

    return 0;
}
指针常量
  • 本质:指针本身是常量,指向规定地址
  • 语法
数据类型* const 变量名;
数据类型 *const 变量名;
  • 举例
int *const p; // p是指针常量
  • 特性

    1.指向的数据可通过指针修改,指向对象的值可变*p = x合法);

    2.指针本身不可指向其他地址,指向不可以变p = &b非法)

  • 注意

    定义时必须初始化

int a = 10;
int *const p = &a;
  • 案例演示
#include <stdio.h>
int main(int argc,char *argv[])
{
    int a = 10;        // 常量
    int *const p= &a;  // 指针常量
    
    *p = 100// 正确,指向对象的值可以改变
    printf("%d\n",*p); // 100

    int b = 20;// 变量
    p = &b;      // 错误,指向不可改变
    printf("%d\n",*p);
    
    return 0;
}
常量指针常量
  • **本质:**指针的指向和指向的数据均不可改变
  • 语法
const 数据类型* const 变量名;
const 数据类型 *const 变量名;    
  • 举例:
const int* const p; // p是常量指针常量
  • 特性

    1.指针的指向不可变(p = &b非法)

    2.指针指向的数据不可变(*p = x非法)

  • 注意

    定义时必须初始化

int a = 10;
const int *const p = &a; // 正确

简单理解,不管是常量指针、指针常量,还是常量指针常量,本质上都是一个赋值受到限制的指针变量

总结对比

在这里插入图片描述

关键点
  1. const *左侧:修饰数据(常量指针)
  2. const *右侧:修饰指针(指针常量)
  3. 函数参数优先使用常量指针,提高代码安全性
  4. 指针常量必须初始化,且不可重新指向

野指针、空指针、空悬指针

野指针

**定义:**指向无效内存区域(如未初始化、已释放或越界访问)的指针称之为野指针。

危害:

  1. 访问野指针能引发段错误(Segmentation Fault)。
  2. 可能破坏关键内存数据,导致程序崩溃。

产生场景:

  1. 指针变量未初始化

    int *p; // p未初始化,是野指针
    printf("%d\n", *p); // 危险操作
    
  2. 指针指向已释放内存

    int* fun(int a, int b)
    {
        int p = a + b;
        return &p;
    }
    
    int main()
    {
        int *p = fun(5,3);
        printf("%d\n", *p); //危险操作
    }
    
    ---------------------------------------------------
    int *p = malloc(sizeof(int));
    free(p);
    printf("%d\n",*p);// p称为野指针
    
  3. 返回局部变量的地址

    int* fun(int a, int b)
    {
        int p=a+ b;
        return &p;
    }
    int main()
    {
        int *p = fun(5,3);
        printf("%d\n"*p);// 危险操作
    }
    

如何避免野指针

  1. 初始化指针为NULL。

  2. 释放内存后立即置指针为NULL。

  3. 避免返回局部变量地址。

  4. 使用前检查指针有效性。

    int fun(int *pt)
    {
        int *p = pt;
        
        // 校验指针
        if (p == NULL) // 等价于 if(!p)  |     if(p != NULL) 等价于 if(p)
        {
            printf("错误!");
            return -1;
        }
        printf("%d\n", *p);
    }
    
空指针

定义:值为NULL的指针,指向地址0x0000 0000(系统保留,不可访问)

在这里插入图片描述

**作用:**明确表示指针当前不指向有效内存。一般用作指针的初始化。

空悬指针

定义:指针指向的内存已被释放,但未重新赋值。空悬指针是野指针的一种特例。

实例:

int *p = NULL; //初始化为空指针
free(p); // 释放后置空
// free(p) 一定要置空,否则会产生空悬指针现象,p = NULL
printf("%p\n", 0); // p仍保留原地址,称为空悬指针

viod与void*的区别

定义
  • void:表示“无类型”,用于函数返回类型或参数。
void fun(void); // 没有返回值也没有参数,一般简写: void func()
  • void*:通用类型指针(万能指针),可指向任意类型数据,但需要强制类型转换后才能解引用。
void* ptr = malloc(4); // ptr指向4个字节大小的内存空间
// 存放int
//int* p = (int*)ptr;
// *p = 10;

// 存放float
float* p = (float*)ptr;
*p = 21.5f;

注意:只能是具体的类型(int*,double*,float*, char*...)和void*之间转换

注意事项
  • void*不能直接解引用(如: 函数返回 *ptr 会报错)
  • 函数返回void* 需要外部接收的时候明确类型(不明确类型,就无法解引用)
/**
*定义一个返回值为void*类型的指针函数大
*/
void* proces_data(void* p)
{
    return p;
}
int main(int argc,char *argv[])
{
    // int类型
    int m = 10;
    int* p_int = &m;

    int* result_int=(int*)proces_data(p_int);
    printf("Integer value:%d\n",*result_int);
    
    // double类型
    double pi = 3.1415926;
    double* p_double = &pi;
    
    double* result double =(double*)proces_data(p_double);
    printf("Double value:%.6f\n", *result_double);
    
    //void* generic_ptr = process_data(p_int);
    // *generic_ptr = 30; // 错误:void*不能直接解引用(必须明确类型,才能解引用)

    return 0;
}

动态内存分配

要实现动态内存分配,需要使用C库提供的函数,我们所说的动态内存分配,其实就是在堆区申请内存(此时的内存在回收需要程序员自身来维护)

常用函数

malloc
  • 头文件:include <stdio.h>
  • 函数原型:
void* malloc(size_t size);
  • 功能:分配指定字节数的内存到堆区,返回指向内存块首地址的指针。内存内容未初始化(随机值)。
  • 参数:
    • size:要分配的内存大小(字节),这里的size_t是数据类型unsigned long int的别名。
  • 返回值
    • 成功:返回指针类型(申请到的内存的首地址)
    • 失败:返回NULL
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main()
{
    // 创建一个指针变量,用来接收内存分配后返回的内存地址
    int *p = malloc(sizeof(int)); // 4字节
    
    // 对于指针的使用,一定要进行校验,反制野指针
    if (p == NULL)
    {
        printf("内存申请失败!\n");
        return -1;
    }
    
    // malloc申请的内存空间默认填充的是随机值,需要我们对其清零
    memset(p,0,sizeof(int)); // 适合于批量赋值0
    
    // 向这块空间赋值
    *p = 100;
    
    // 访问这块空间中的数据
    printf("%d\n", *p);
    
    // 内存使用完毕,释放内存
    free(p);
    // 置空,防止产生悬空指针
    p = NULL;
    
    return 0}
  • 注意事项:
    • 分配内存后需要手动初始化内存,推荐memsetcalloc
    • 内存空间是连续的,不可越界访问
calloc
  • 头文件:include <stdio.h>
  • 函数原型:
void* calloc(size_t nitems, size_t size);
  • 功能:动态分配内存,并初始化为0
  • 参数:
    • nitems:元素个数
    • size:每个元素的大小
  • 返回值
    • 成功:返回指针类型(申请到的内存的首地址)
    • 失败:返回NULL
  • 示例:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define LEN 5

int main()
{
    // 创建一个指针变量,用来接收内存分配后返回的内存地址
    int *arr = calloc(LEN,sizeof(int)); // 分配可以存储5个int数据的内存空间
    
    // 对于指针的使用,一定要进行校验,反制野指针
    if (arr == NULL)
    {
        printf("内存申请失败!\n");
        return -1;
    }
    
    // 使用for循环快速赋值
    for (int i = 0; i < LEN; i++)
    {
        if (i % 2 == 0)
        {
            continue;
        }
        arr[i] = (i+1)*10;  // 0 10 0 ..
    }
    
    //遍历数组
    int *p = arr;
    for (; p < arr + LEN; p++)
    {
        printf("%d\n", *p);
    }
        
    // 内存使用完毕,释放内存
    free(arr);
    // 置空,防止产生悬空指针
    p = NULL;
    
    return 0}
  • 使用场景:为数组分配内存是更安全更高效
realloc
  • 头文件:include <stdio.h>
  • 函数原型:
void* realloc(void* ptr, size_t size);
  • 功能:调整已分配内存块的大小,可能迁移数据到新地址。扩容后,空余位置是随机值,需要手动清零。
  • 参数:
    • ptr:原内存指针(需要扩容的内存空间的指针,这个ptr的来源(malloc/calloc/realloc))
    • size:指定重新分配内存的大小(字节)
  • 返回值
    • 成功:返回指针类型(申请到的内存的首地址)
    • 失败:返回NULL
  • 示例:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define LEN 5
#define NEW_LEN 8

int main()
{
    // 创建一个指针变量,用来接收内存分配后返回的内存地址
    int *arr = calloc(LEN,sizeof(int)); // 分配可以存储5个int数据的内存空间
    
    // 对于指针的使用,一定要进行校验,反制野指针
    if (arr == NULL)
    {
        printf("内存申请失败!\n");
        return -1;
    }
    
    // 使用for循环快速赋值
    for (int i = 0; i < LEN; i++)
    {
        if (i % 2 == 0)
        {
            continue;
        }
        arr[i] = (i+1)*10;  // 0 10 0 ..
    }
    
    //遍历数组
    int *p = arr;
    for (; p < arr + LEN; p++)
    {
        printf("%d\n", *p);
    }
     
    // 扩容至8个int的大小
    int *temp = (int*)realloc(arr, NEW_LEN * sizeof(int));
    // 扩容成功,更新指针
    arr = temp;
    // 对扩容部分清零
    memset(arr+5,0, 3 * sizeof(int));
    
    arr[7] = 666;
    
    // 遍历数组
    for (int i = 0; i < NEW_LEN; i++)
    {
        printf("%d\n", *(arr+i));
    }
    
    // 内存使用完毕,释放内存
    free(arr);
    // 置空,防止产生悬空指针
    p = NULL;
    
    return 0}
  • 注意事项:
    • 分配后需要手动初始化内存,推荐memset
    • 必须用临时变量接收返回值,避免直接覆盖原指针导致内存泄漏。
    • size 为0,等效于 free(ptr)
free
  • 头文件:#include <stdlib.h>
  • 函数原型:void free(void* ptr);
  • 功能:释放动态分配的内存。
  • 注意事项:
    • 只能释放一次,重复释放会导致程序崩溃。
    • 释放后应将指针置为 NULL,避免野指针。
    • 栈内存由系统自动释放,无需手动free
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值