指针(续)

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函数是系统通过函数指针的回调调用。

演示

代码:

#include <stdio.h>

int main(int argc, char **argv)  // {"abc","aaa"}   对行地址解引用,得到首列地址
{
	// 访问参数个数 argc
	printf("argc=%d\n", argc);
	
	// 遍历参数(每一个参数都是一个字符串常量)
	for(int i=0;i< argc; i++){
		printf("%s,%s\n", argv[i], *(argv+i));
	}
	printf("\n");
}

运行结果:

在这里插入图片描述

在这里插入图片描述

常量指针与指针常量

常量类型

① 字面量:直接使用固定值(如:12,hello,orange, 杨家辉三角),符号常量和枚举在编译器转换为了字面量

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

const int a = 10;  // 只读常量
a = 21;  // 编译报错

常量指针

  • 本质:指向常量数据的指针

  • 语法:

    const 数据类型 *变量名;
    const 数据类型* 变量名;
    
  • 举例:

    const int *p;   // p是常量指针
    
  • 特性:

    • 指向对象的数据不可改变(int a = 10; const int *p = &a; *p = 20;,非法)
    • 指针本身的指向可以改变(int a = 10, b = 20; const int *p = &a; p = &b;,合法)
  • 案例:

    #include <stdio.h>
    
    int main()
    {
    	int a = 10;        // 变量
    	const int *p = &a; // 常量指针
    	
    	// *p = 100;       // 错误,指针指向的数据不可改变
    	printf("%d\n", *p);// 10
    	
    	int b = 20;        // 变量
    	p = &b;            // 正确,指针指向可以改变
    	printf("%d\n", *p);// 20
    }
    

指针常量

  • 本质:指针本身是常量,指向固定地址

  • 语法:

    数据类型* const 变量名;
    数据类型 *const 变量名;
    
  • 特性:

    • 指向对象的数据可以改变(int a = 10; int* const p = &a; *p = 20;,合法)
    • 指针本身的指向不可改变(int a = 10, b = 20; int* const p = &a; p = &b;,非法)
  • 注意:

    定义时必须初始化:

    int a = 10;
    int* const p = &a;  // 正确
    
  • 案例:

    #include <stdio.h>
    
    int main()
    {
    	int a = 10;        // 变量
    	int* const p = &a; // 指针常量
    	
    	*p = 100;          // 正确,指针指向的数据可以改变
    	printf("%d\n", *p);// 100
    	
    	int b = 20;        // 变量
    	// p = &b;            // 错误,指针指向不可改变
    	printf("%d\n", *p);// 100
    }
    

常量指针常量

  • 本质:指针指向和指向对象的数据都不可改变

  • 语法:

    const 数据类型* const 变量名;
    const 数据类型 *const 变量名;
    
  • 举例:

    const int* const p;  // p是常量指针常量
    
  • 特性:

    • 指向对象的数据不可改变(int a = 10; int* const p = &a; *p = 20;,非法)
    • 指针本身的指向不可改变(int a = 10, b = 20; int* const p = &a; p = &b;,非法)
  • 注意:

    定义时需要初始化:

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

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

总结对比

类型 语法 指向可变 数据可变
常量指针 const int *p ✔️
指针常量 int *const p ✔️
常量指针常量 const int *const p

关键点

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

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

野指针

定义

指向无效内存区域(比如未初始化、已释放或者越界访问)的指针称之为野指针。野指针会导致未定义(UB)行为。

危害:

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

产生场景:

  1. 指针变量未初始化

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

    int *p = malloc(sizeof(int)); // 在堆区申请1个int大小的内存空间,将该空间地址赋值给指针变量p
    free(p); // 释放指针p指向的空间内存
    printf("%d\n", *p); // 危险操作:p就是野指针
    
  3. 返回局部变量的地址

    int* fun(int a, int b)
    {
        int sum = a + b; // sum就是一个局部变量
        return &sum;  // 将局部变量的地址返回给主调函数
    }
    
    int main()
    {
        int *p = fun(2,3);
        printf("%d\n", *p); // 危险操作:p就是野指针
    }
    

如何避免野指针:

  1. 初始化指针为NULL

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

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

  4. 使用前检查指针有效性(非空校验,边界检查)。

    int fun(int *pt)
    {
        int *p = pt;
        // 校验指针
        if(p == NULL) // 结果为假   等价于 if(!p)  其实底层: if(p == 0)
        {
            printf("错误!");
            return -1;
        }
        printf("%d\n", *p);
        return 0;
    }
    

空指针

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

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

示例:

int *p = NULL;  // 初始化为空指针

free(p);   // 释放后置空
p = NULL;

空悬指针

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

示例:

char *p = malloc(100); // 在堆区分配100个char的空间给p
free(p); // 释放指针p指向的内存空间
printf("%p,%d\n", p, *p); // p可以正常输出,*p此时属于危险操作 
// p指向的内存空间被回收,但是p指向空间的地址依然保留,此时这个指针被称作空悬指针

void与void*的区别

定义

  • void: 表示“无类型/空类型”,用于函数返回类型或者参数。

    void func(void);    // 没有返回值也没有参数,一般简写:void func();
    
  • *void:**通用指针类型(万能指针),可指向任意类型数据,但需要强制类型转换后才能解引用。

    void* ptr = malloc(4);  // ptr指向4个字节大小的堆内存空间  
    // 存放int类型数据
    int *p = (int*)ptr;
    *p = 10;
    
    // 存放float类型数据
    float* p1 = (float*)ptr;
    *p = 12.5f;
    
    // 存放char类型数组
    char* p2 = (char*)ptr;
    
    // 以下写法完全错误
    float* ptr = malloc(4);
    int *p = (int*)ptr;  // 此时编译报错,类型不兼容 float* int*
    

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

注意事项

  • void不能直接解引用(*ptr 会报错
  • 函数返回void*需要外部接收的时候明确类型(不明确类型,就无法解引用)

示例

#include <stdio.h>

/**
 * 定义一个返回类型为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:%lf\n", *result_double);
	
	// void* p_void = proces_data(p_double);
	// printf("Void value:%lf\n", *p_void);
	// *p_void = 20;
	
	// 注意:void* 修饰的指针是可以进行赋值操作的,但是不能对其解引用
	
	return 0;
}

运行结果:

在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐