指针(续)
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"} 对行地址解引用,得到首列地址
{// 访问参数个数 argcprintf("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);// 10int 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);// 100int 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 | ❌ | ❌ |
关键点
const
在*
左侧:修饰数据(常量指针)const
在*
右侧:修饰指针(指针常量)- 函数参数优先使用常量指针,提高代码安全性
- 指针常量必须初始化,且不可重新指向
野指针、空指针、空悬指针
野指针
定义:
指向无效内存区域(比如未初始化、已释放或者越界访问)的指针称之为野指针。野指针会导致未定义(UB)行为。
危害:
- 访问野指针可能引发段错误(Segmentation Fault)
- 可能破坏关键内存数据,导致程序崩溃。
产生场景:
-
指针变量未初始化
int *p; // p未初始化,是野指针 printf("%d\n", *p); // 危险操作:p就是野指针
-
指针指向已释放的内存
int *p = malloc(sizeof(int)); // 在堆区申请1个int大小的内存空间,将该空间地址赋值给指针变量p free(p); // 释放指针p指向的空间内存 printf("%d\n", *p); // 危险操作:p就是野指针
-
返回局部变量的地址
int* fun(int a, int b) {int sum = a + b; // sum就是一个局部变量return ∑ // 将局部变量的地址返回给主调函数 }int main() {int *p = fun(2,3);printf("%d\n", *p); // 危险操作:p就是野指针 }
如何避免野指针:
-
初始化指针为NULL
-
释放内存后立即置指针为NULL
-
避免返回局部变量的地址
-
使用前检查指针有效性(非空校验,边界检查)。
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 = π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;
}
运行结果: