想自己做网站需要会什么,科技网站欣赏,沙洋网页定制,wordpress哪个版本最好用文章目录 指针(下)野指针、空指针野指针空指针 二级指针**main**函数的原型说明 常量指针与指针常量常量指针指针常量常量指针常量 动态内存分配常用函数**malloc****calloc****realloc****free** **void**与**void***的区别扩展#xff1a;形式参数和实际参数的对应关系 指针… 文章目录 指针(下)野指针、空指针野指针空指针 二级指针**main**函数的原型说明 常量指针与指针常量常量指针指针常量常量指针常量 动态内存分配常用函数**malloc****calloc****realloc****free** **void**与**void***的区别扩展形式参数和实际参数的对应关系 指针(下)
野指针、空指针
野指针 定义访问了一个已经销毁或者访问受限的内存区域外的指针这个指针就被称为野指针。 野指针产生的场景 变量未初始化通过指针访问该变量 int a;
int *p a; // p就是野指针
ptf(*p); // 访问野指针但是数据不安全指针变量未初始化 int *p NULL; // 此时的p也是野指针
ptf(*p);指针指向的内存空间被free函数回收了—后面讲 指针函数中直接返回了局部变量的地址。 指针指向数组以外的地址下标越界 何避免野指针 写代码要养成两个习惯通过编码规范避免
指针变量幻及时初始化如果暂时没有对应的值建议赋值为NULL;数组操作遍历,指针运算时注意数组的长度避免越界指针指向的内存空间被回收建议给这个指针变量赋值为NULL;
int *p (int*)malloc(10);// 动态内存分配
free(p);// 动态内存释放
p NULL;// p不指向任何空间指针变量使用之前要检查它的有效性以后开发中要做非空校验 int *p NULL;
if(!p)
{
return -1;
}说明NULL是空常量它的值是0这个NULL一般存放在内存中的0x00000000位置这个地址只能存放NULL不能被其他程序修改。
空指针
空指针又被称作悬空指针当一个指针的值是NULL这个指针被称为空指针对空指针访问会运行报错段错误
二级指针
定义二级指针又被称作多重指针引用一级指针的地址此时这个指针变量就得定义成二级指针。
int a 10;
int *p a;
int **w p;定义格式 数据类型 **变量名 指针数组的数组名或者一级指针的地址 // 指针数组
int array {1,2,3};
int *arr {array[0],array[1],array[2]};// 指针数组
// 一级指针
int a 10;
int *p a; // 一级指针举例
// 字符型指针数组
char *arr[3] {abc,aaa034,12a12};// 等效于char arr[3][6] {abc,aaa034,12a12}
// 定义二级指针并赋值(指针需要跟依赖的源同类型)
char **p arr;// 正确
int array[2][3] {{1,2,3},{11,22,33}};
int **k array;// 编译报错数据类型不相符二维数组不等于指针数组int a 90;
int *p a;// 一级指针
int **k p;// 正确二级指针结论
二级指针和指针数组是等效和二维数组不等效二维数组和数组指针是等效和二级指针不等效
二级指针的用法
如果是字符的二级指针可以像遍历字符串数组一样遍历它如果是其他的二级指针就需要解引用两次访问它所指向的数据
案例
/**
* 二级指针案例使用指向指针数据的指针变量。
*/
#include stdio.h
void fun1(){char *name[]{Follow me,BASIC,Great Wall,FORTRAN,Computer design};// 定义一个二级指针char **p;// 定义循环变量int i 0;// 遍历指针数组do{p name i;printf(%s\n,*p);i;}while(i 5);printf(\n);
}
void fun2()
{int arr1[5] {11,12,13,14,15};// 创建一个指针数组int *arr[] {arr1[0],arr1[1],arr1[2],arr1[3],arr1[4]};int **p arr,i 0;// 遍历for(;i 5; i){// printf(%5d,**(pi));printf(%5d,**p);p;}printf(\n);
}
int main()
{fun1();fun2();
}main函数的原型
定义main函数有多种定义格式main函数也是函数函数相关的结论对main函数也有效也可以定义main函数的函数指针。
main函数的完整写法
int main(int argc,char *argv[]){}
int main(int argc,char **argv){}扩展写法
int main(){}
int main(void){}
void main(){}
main(){} ---- int main(){}
void main(void){}
int main(int a){}
int main(int a,int b,int c){}
...说明 argcargv是形参的名称它们俩可以修改 main函数的扩展写法有些编译器不支持编译报警告 argc和argv的常规写法 argc存储了参数的个数 argv存储了所有参数的字符串形式 main函数是系统通过函数指针的回调形式调用的
注意如果一个函数没有写返回值类型这个函数的默认返回类型是int。
案例
/**
* main函数
*/
#include stdio.h
int main(int argc,char **argv)
{int k;for (k1;k argc;k)printf(%s\n,argv[k]);
}常量指针与指针常量
常量分为字面量和只读常量字面量就是我们平时直接操作的如
printf(12)|printf(“hello”)只读常量使用关键字 const 修饰凡是被这个关键字修饰的变量一旦赋值值就不能改变。
语法
// 字面量举例字面量是一种匿名的常量
printf(12);
printf(请输入一个数\n);// 只读常量
const int a 10;
a 21;// 编译错误因为此时这个变量是只读常量所以不可更改其值常量指针
定义常量的指针本质是一个指针指针指向的数据不能改变。 定义格式 const 数据类型 *变量名; 举例 const int *p; // p就是常量指针结论 常量指针指向的数据不能被改变不能解引用间接修改数据 常量指针的地址可以改变。 应用场景作为形式参数实际参数需要给一个常量。 案例
#include stdio.h
/* 常量指针 */
void test1()
{// 定义变量int a 10;// 定义常量指针const int *p a;// *p 100; // 编译报错常量的值不能修改常量指针指向地址空间的数值不能修改printf(%d\n,*p);// 10int b 90;p b; // 编译通过常量的地址可以修改常量指针指向的地址空间可以发生改变printf(%d\n,*p);// 90
}
int main()
{test1();
}指针常量 定义指针的常量指针的指向不能改变 定义格式 数据类型* const 变量名
举例
int* const p; // 指针常量 1结论 指针常量的指向不能被改变不能给指针变量重新赋地址值 指针常量指向的数据可以改变。 注意指针常量在定义时就要赋值不能先定义后赋值否则编译报错。 案例 /**
* 常量指针与指针常量
*/
#include stdio.h
/* 指针常量 */
void test2()
{// 定义变量int a 10;// 定义指针变量int* const p a;// 错误写法先定义后赋值编译报错// int* const p;// p a;// 间接取数据pirntf(%d\n,*p);// int b 200;// p b;// 编译报错地址不能改变*p 200;printf(%d\n,*p);// 200
}
int main()
{test2();
}常量指针常量 定义语法 const 数据类型* const 变量名; 举例
const int* const p; // 常量指针常量作用p的指向不能被改动地址p指向的数据不能被改地址对应内存中存放的数据
动态内存分配
我们要想实现动态内存分配就需要学习标准C提供的函数库
函数所属的库文件函数的原型-函数的声明 函数名形参返回值类型 函数的功能
注意内存分配函数在申请内存时建议用多少申请多少可以有少量的预留量但不能越界访问虽然编译和运行不报错但数据不安全
常用函数
malloc 头文件 #include stdlib.h 函数功能C 库函数 void *malloc(size_t size) 分配所需的内存空间并返回一个指向它的指针。 函数原型 函数名malloc 形式参数size_t size内存块的大小以字节为单位。本质上就是一个 unsigned int 返回值类型void* 该函数返回一个指针指向已分配大小的内存如果请求失败返回NULL。 举例 int *p (int*)malloc(4);说明 malloc函数分配的内存没有默认值是不确定数大概率是0malloc函数申请的内存空间连续。
calloc
头文件 #include stdlib.h函数功能C库函数 void * calloc(size_t nitems,size_t size) 分配所需的内存空间并返回一个指向它的指针。
malloc和calloc之间不同点事malloc不会设置内存为零而calloc会设置内存为零。 函数原型 void *calloc(size_t nitems,size_t size) 函数名calloc 形式参数 size_t nitems申请多少个size_t size一个占几个内存单元一个内存单元 等于 一个字节 返回值类型void*该函数返回一个指针指向已分配大小的内存。如果请求失败返回NULL。 举例 int *p (int*)calloc(3,4); // p指向的空间的大小是12个字节
if(!p) printf(内存申请失败\n);说明 calloc函数分配的内存有默认值每个内存单元都是0calloc函数申请的内存空间连续calloc大多时候为数组中的元素申请内存 转存栈中数组中的数据 int arr[3] {10,20,30}; // 在栈区
int *p (int*)calloc(3,4); // 申请内存在堆区
if(!p) puts(内存申请失败);
// 转存
for(int i 0;i 3; i)p[i] arr[i];// 遍历
for(int i 0;i 3; i)printf(%d,,p[i]);
printf(\n);// p使用完记得释放内存
free(p);
p NULL; // 内存回收后建议置空realloc 头文件 #include stdlib.h 函数功能尝试重新调整之前调用malloc或calloc所分配的ptr所指向的内存块的大小。 函数原型 void *realloc(void *ptr,size_t size) 函数名realloc 形式参数 void *ptr是malloc或者calloc的返回值size_t size重新分配后的内存大小 返回值void*该函数返回一个指针指向已分配大小的内存。如果请求失败返回NULL。 案例 int *p (int*)malloc(4);
int *w (int*)realloc(p,20);
// int *q (int*)realloc(p,0); // 等效于free(p)说明 realloc以原来malloc返回的内存地址开始分配总共20个字节的内存空间 如果原来的内存空间后有20个连续空间就扩容20-4 16个内存单元返回原来旧的内存首地址。 如果原来的内存空间后不够20个连续内存空间就重新找一个内存地址开始申请20个内存单元。并将原来的数据拷贝到新的内存中回收旧的内存单元并返回新的内存首地址。
free 头文件 #include stdlib.h 函数功能释放之前调用 malloc、calloc、realloc所分配的内存空间是访问完记得使用NULL置空。 函数原型 void free(void *ptr) 函数名free 形式参数 void *ptrcalloc,malloc.realloc的返回值 返回值类型void没有返回值 注意 堆内存中的指针才需要回收栈中系统会自动回收 堆内存不能重复回收运行会报错
说明
堆的内存空间相比较栈要大很多内存分配函数返回的指针变量可以参与运算只读但不能被修改p或者pi 是错误的
void与void*的区别 定义 void是空类型是数据类型的一种void*是指针类型是指针类型的一种可以匹配任意类型的指针类似于通配符 void 说明void作为返回值类型使用表示没有返回值作为形参表示形参列表为空在调用函数是不能给实参 举例 // 函数声明
void fun(void); // 等效于 void fun();
// 函数调用
fun();void* 说明 void*是一个指针类型但该指针的数据类型不明确无法通过解引用获取 内存中的数据因为 void* 不知道访问几个内存单元。 void*是一种数据类型可以作为函数返回值类型也可以作为形参类型 void*类型的变量在使用之前必须强制类型转换明确它能够访问几个字节的内存空间 int *p (int*)malloc(4);
double *p2 (double*)malloc(8);举例 #include stdio.h
#include stdlib.h
// 函数定义
void* fun(void* p) // 指针函数返回值类型是指针的函数此时返回的是不明确类型需要外部强转
{int *p;// double *p;// long *p;// char *p;return p;
}
// 函数调用
void main()
{int *p;void* a fun(p);// 这种接收方式实际上没有意义printf(%p\n,a);// 可以正常打印打印出一个地址*a 10;// 编译报错void*变量不能解引用访问数据int *w (int*)a;*w 10;// 编译和运行正常void*变量a在使用前已经强制类型转换了数据类型明确了访问的内存单元明确了。
}说明 void作为返回值类型这个函数可以返回任意类型 char,int*,double*等 的指针。void作为形参类型这个函数在调用时可以给任意类型 char,int*,double*等 的指针。 总结 void* 类似于通配符不能对void*类型的变量解引用因为不明确内存单元的大小。void*在间接访问解引用前要强制类型转换但不能太随意否则存和去的数据类型不一致。
扩展形式参数和实际参数的对应关系
//形式参数和实际参数的对应关系
#include stdio.h
//void test(int arr[]) //可以
//void test(int arr[2]) //可以
//void test(int * arr) //可以
//void test(int *arr[]) //不可以编译报错
//void test(int ** arr) //不可以
//void test(int (*arr)[]) //不可以
//---------------------------------
//void test2(int * arr[]) //可以
//void test2(int ** arr) //可以
void test2(int (*arr)[]) //不可以
{printf(test执行了\n);
}
int main()
{
/*int arr[3]{0};test(arr);*/int *arr[3] {0};test2(arr);return 0;
}//形式参数和实际参数的对应关系
#include stdio.h
//void test(int arr[2][3]) //可以
//void test(int arr[][3]) //可以
//void test(int arr[2][]) //不可以
//void test(int arr[][]) //不可以
//void test(int (*arr)[]) //可以
//void test(int(*)arr[]) //语法错误
//void test(int(*arr)[3])
//void test(int *arr[]) //不可以
//----------------------------------------
//void test2(int *arr[]) //可以
//void test2(int arr[][1]) //不可以
//void test2(int **arr) //可以
void test2(int(*arr)[]) //不可以
{printf(test\n);
}
int main()
{/*int arr[2][3] {0};test(arr);*//*int a 20;int *arr[] {a};test2(arr);*/int** p;test3(p);return 0;
}