内容提要
- 指针
- 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; // 正确
简单理解,不管是常量指针、指针常量,还是常量指针常量,本质上都是一个赋值受到限制的指针变量
总结对比
关键点
const
在*
左侧:修饰数据(常量指针)const
在*
右侧:修饰指针(指针常量)- 函数参数优先使用常量指针,提高代码安全性
- 指针常量必须初始化,且不可重新指向
野指针、空指针、空悬指针
野指针
**定义:**指向无效内存区域(如未初始化、已释放或越界访问)的指针称之为野指针。
危害:
- 访问野指针能引发段错误(Segmentation Fault)。
- 可能破坏关键内存数据,导致程序崩溃。
产生场景:
-
指针变量未初始化
int *p; // p未初始化,是野指针 printf("%d\n", *p); // 危险操作
-
指针指向已释放内存
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称为野指针
-
返回局部变量的地址
int* fun(int a, int b) { int p=a+ b; return &p; } int main() { int *p = fun(5,3); printf("%d\n",*p);// 危险操作 }
如何避免野指针
-
初始化指针为NULL。
-
释放内存后立即置指针为NULL。
-
避免返回局部变量地址。
-
使用前检查指针有效性。
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 = π
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;
}
- 注意事项:
- 分配内存后需要手动初始化内存,推荐
memset
或calloc
- 内存空间是连续的,不可越界访问
- 分配内存后需要手动初始化内存,推荐
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