赞
踩
/* 指针的概念: 1.为了方便访问内存中的内容,给每一个内存单元,进行编号, 那么我们称这个编号为地址,也就是指针。 2.指针也是一种数据类型,指针变量有自己的内存, 里面存储的是地址,也就是那些编号。 四要素 1.指针本身的类型 例如:float* int* ... 2.指针指向的类型 例如:float int ... 3.指针本身的内存 4.指针指向的内存 运算符 *: 1.定义指针时,通过 * 符号,来表示定义的是一个指针,并且是指针自身的类型的组成部分 2.其他时候,表示解析引用(取内容:通过内存编号,读取内存中的内容) &:取(首)地址符,作用:取出(内存的)首地址 */ # include <stdio.h> int main() { // 定义指针(指针:pointer) float* p_name; // 指针本身的类型:float* 指针指向的类型:float int * p1; int *p2; // * 符号偏不偏移不影响其功能 return 0; }
/* 知识储备: // 初识化:定义的同时,给值 int a = 0; // 赋值:先定义,再给值 int b; b = 0; */ # include <stdio.h> int main() { // 初始化 int num = 6; int val = 8; // 初始化 int* p1 = # // 对于指针变量p1,如果进行给值,必须给地址(内存编号) // 赋值 int* p2; p2 = &val; // 自行体会 int* p3 = p1; // 直接存入地址(不推荐使用,因为你不知道自己随便写的地址里面是什么!!) int* p4 = (int*)123456; // 将 整型123456 强转为 int*类型 的“地址” int* p5 = (int*)0XAB25; // 计算机中的内存地址通常用16进制数表示 // 直接使用地址:置空(即:"使用0地址,NULL:0X0") int* p6 = NULL; // 等价于 int* p6 = (int*)0X0; // 目的:为了给暂无指向的指针,提供指向,保证安全,将内存中的0地址特殊化 // 数组名就是数组的首地址 int arr[3] = { 1, 2, 3 }; // 数组类型:int [3] // 元素类型:int // arr 类型:int* int* p7 = arr; return 0; }
/* 1.变量在内存中所占的字节数 所有的指针变量,不论类型,在内存中所占的字节数都是一样的,都是4个字节(或者8个字节) (8个字节是因为时代的发展,部分好的计算机性能得到提升,一般都是4个字节) 2.指针本身的内存,以及指针指向的内存 指针本身的内存:4个字节(指针变量只需要存储,所指向的变量的首地址) 指针指向的内存:看你所指向的类型,视情况而定 */ # include <stdio.h> int main() { double num = 12.0; double* p1 = # printf("%f \n", num); printf("%f \n", *p1); // 利用指针,取得 num 的值 return 0; }
/* 内存区域的划分 四个:常量区,栈区,堆区,静态全局区 五个:常量区,栈区,堆区,静态全局区,代码区 1.代码区:存代码 2.常量区:存常量 3.静态全局区:静态(static)变量,全局变量 4.栈区:普通局部变量 5.堆区:由程序员手动申请,手动释放 */ # include <stdio.h> int a; // 普通“全局”变量(初识值默认为零) // 作用域:当前项目 // 生命周期:程序开始到结束 static int b; // 静态“全局”变量(初识值默认为零) // 作用域:当前文件 // 生命周期:程序开始到结束 int main() { int c; // 普通局部变量(无初始值) // 作用域:当前语块 // 生命周期:当前语块 static int d; // “静态”局部变量(初识值默认为零) // 作用域:当前语块 // 生命周期:程序开始到结束 return 0; }
# include <stdio.h> void func() { static int num; // 只会定义一次 printf("%d \n", num); num++; printf("%d \n", num); } int main() { func(); func(); func(); return 0; } /* 运行结果: 0 1 1 2 2 3 请按任意键继续. . . */
/* void* 指针 1.不能自增自减 2.不能偏移 3.不能读取内容 但是!可以接收任何类型的指针而不需要强转类型 可以利用这个特点,将 void* 指针当作通用的存放地址的“容器” e.g. int a = 6,b = 8.8; int* p1 = &a; double* p2 = &b; void* p0 = NULL; // 当作存放“内存地址”的容器使用 p0 = p1; p0 = p2; ... */ # include <stdio.h> int main() { void* p0 = NULL; return 0; }
/* 简单开辟内存 1.申请 ---有两个函数能够实现申请内存的功能: A. malloc(参数:需要字节的总数); B. calloc(参数:每个需要的字节数,个数); 返回值都是 void* 类型的指针 2.使用 3.释放 free(参数:首地址) 如果不释放的话,会导致“内存泄露” 4.置空 如果不置空的话,会出现“野指针” */ # include <stdio.h> int main() { /* malloc */ double* p = (double*)malloc(sizeof(double)); // 申请一个double类型大小的内存(8字节) *p = 3.14; // 使用 printf("%lf \n", *p); free(p); // 通过 p 里面存储的首地址,找到相对应的内存,从这里开始释放,一直释放到,申请内存的时候,做了标记的地方 p = NULL; // 通过置空,让指针不再指向已经被释放掉的内存 /* calloc */ float* p1 = (float*)calloc(sizeof(float),1); *p1 = 3.14f; printf("%f \n", *p1); free(p1); p1 = NULL; printf("进阶运用 \n"); // 进阶运用 p = (double*)malloc(sizeof(double)*10); // 申请10个 double 类型大小的连续的内存(补充:因为上面将p定为 double* 而且置空过了,所以可再度利用) for (int i = 0; i < 10; i++ ) { *(p + i) = 10 + i; // 给值 printf("%lf \n", *(p + i)); // 展示值 } free(p); p = NULL; /* 对于上面 for 循环部分的补充: p:里面存的是:申请的内存的首地址 在一次申请中,申请的内存是连续的 *(p + i) <===> p[i] // 注意!它不是数组! */ return 0; }
# include <stdio.h> int main() { // 指针布局 int row = 3; int** pp = (int**)calloc(sizeof(int*), row); int len = 4; for (size_t i = 0; i < row; i++) // size_t是什么?点我跳转学习 { pp[i] = (int*)calloc(sizeof(int), len); } // 内容展示 for (size_t i = 0; i < row; i++) { for (size_t j = 0; j < len; j++) { pp[i][j] = i * 10 + j; // 给值 printf("%-5d", pp[i][j]); // 展示值,注意!这里不是二维数组!(看不懂请回顾上页内容) } printf("\n"); } // 释放内存 for (size_t i = 0; i < row; i++) { free(pp[i]); pp[i] = NULL; } free(pp); pp = NULL; return 0; }
# include <stdio.h> int main() { int len = 5; // 默认长度 int* p = (int*)calloc(sizeof(int), len); int num = 1; for (size_t i = 0; num != 0; i++) // 用户不输入0结束,就一直获取数据并复制到开辟的内存中 { scanf("%d", &num); p[i] = num; // 数据复制到开辟的内存中 } for (size_t i = 0; p[i] != 0; i++) { printf("%-5d", p[i]); // 展示数据 } free(p); p = NULL; return 0; }
/* 扩容的本质是: 将小内存中的所有内容拷贝到大内存中,然后,再继续对大内存进行别的操作 */ # include <stdio.h> int main() { // 长度 int len = 5; // 首次申请内存 int* p = (int*)calloc(sizeof(int), len); int* temp = p; // 成为p的分身,以防万一 // 重复输入数据(并复制到内存中) int num = 1; int i = 0; while (scanf("%d", &num), num != 0) { if (i < len) // 没满的情况下 { temp[i++] = num; // 存完一次,记录一下 } else // 满了的情况下 { len += 5; p = (int*)calloc(sizeof(int), len); // 重新申请更大的内存 for (int j = 0; j < i; j++) { p[j] = temp[j]; } free(temp); temp = NULL; temp = p; // 继续成为当前p的分身 temp[i++] = num; } } // 输出数据 printf("--------------------\n"); for (int j = 0; j != i; j++) { printf("%d \n", temp[j]); } free(p); p = NULL; temp = NULL; return 0; }
/* 内存区域的划分 1.代码区 存储代码 2.常量区 存储常量 3.全局区(静态全局区) 存储: 1.静态变量 2.全局变量 # include <stdio.h> int c; // 普通全局变量 static int d; // 静态全局变量 int main() { int a; // 普通局部变量 static int b; // 静态局部变量 int c; // 注意这个c不是上面的c,它们只是名字看起来一样而已 a = 10; // 普通局部变量没有默认初始值,所以需要自己赋值 printf("a = %d \n", a); printf("b = %d \n", b); printf("c = %d \n", c); printf("d = %d \n", d); // 通过以上 printf,可以总结规律:静态全局区,默认的初始值为0 // 作用域和生命周期 作用域 生命周期 普通全局变量 当前项目 程序开始到程序结束 静态全局变量 当前文件 程序开始到程序结束 普通局部变量 当前语块 当前语块 静态局部变量 当前语块 程序开始到程序结束 return 0; } 4.栈区 存储:普通局部变量 从定义时系统自动分配内存,离开当前语块系统就会自动回收内存 5.堆区 由程序员手动申请和释放 */
/*
1.指针函数
返回值类型是指针的函数
2.函数指针
指向函数的指针
*/
// 1.指针函数 // 下面的代码是一个错误的例子,你能发现它的错误吗? # include <stdio.h> int* test(); // 声明 int main() { int* temp = test(); printf("%d \n", *temp); return 0; } int* test() { int num = 10; int* p = # return p; // 返回了栈区变量的首地址(非常严重的问题!详情见下) } /* 下面的内容是在栈区,当运行完毕,系统会回收其内存资源: int* test() { int num = 10; int* p = # return p; } 当函数返回栈区变量 num 的内存地址之后, 函数运行完毕,系统回收 num 内存,供以后"某某东西"使用 所以,返回的地址不但没有作用,还会导致以后非法访问内存的问题出现 */
// 2.函数指针 /* 函数指针的定义 返回类型说明符 (*函数指针变量名)(参数列表); */ # include <stdio.h> int func(); // 声明 int main() { // 定义函数指针,并进行初始化 int(*p)() = func; // 即:定义了指针p,而且 p 等于 func(func里面存的是函数的首地址) func(); p(); return 0; } int func() { printf("成功执行了 func 函数!\n"); return 6; }
// 函数指针,知识扩展1 # include <stdio.h> int func(); // 声明 typedef int funcType(); // 将 int...() 取别名为 funcType int main() { funcType* p = func; p(); return 0; } int func() { printf("成功执行了 func 函数!\n"); return 6; }
// 函数指针,知识扩展2 # include <stdio.h> int func(int a, int b); // 声明 typedef int(*pfunc)(int a, int b); int main() { pfunc p = func; // 这样也能定义函数指针 int res = p(1,2); printf("%d \n", res); return 0; } int func(int a, int b) { printf("成功执行了 func 函数!\n"); return a + b; }
/*
1.指针数组
......
2.数组指针
......
*/
// 1.指针数组 /* # include <stdio.h> int main() { // 定义并初始化"数组" int arr1[3] = { 1, 2, 3 }; // 数组 arr1[3] -> 里面存的都是 int 类型 // 定义并初始化"指针数组" int* arr2[3] = { 地址1,地址2,地址三 }; // 数组 arr2[3] -> 里面存的都是 int* 类型 arr2[2] = 新地址; return 0; } */
// 2.数组指针 /* 定义"数组"指针: 所指向的数组里面存的数据类型 (*数组指针名称)[所指向的数组的长度]; */ # include <stdio.h> int main() { int arr[3] = { 1, 2, 3 }; // 建立一个数组。 // arr 里面存的是数组内存的首地址,而 [] 表示内存里面存的那一堆东西是数组,3 表示数组长度,int 表示数组里面存的数据是 int 类型 int(*p)[3]; // 长度为 3 的数组指针 p = arr; printf("%d \n", p[1]); return 0; }
// 数组指针,知识扩展 # include <stdio.h> typedef int(*pType)[3]; // 定义类型:int(*pType)[3],取别名为:pType int main() { int arr[3] = { 1, 2, 3 }; pType p; // 变量 p 的类型为 pType,而属于这种类型的变量 p 必然满足 int(*pType)[3] 模板格式 p = arr; // arr里面储存的“数组的内存首地址”复制给变量 p printf("%d \n", (*p)[0]); // 注意!不要写成 p[0],虽然 p 获得了 arr 里面存的首地址,但是 *p 才是代表数组整体 return 0; } /* 同理: # include <stdio.h> typeof int pArr[3]; int main() { int arr[3] = { 1, 2, 3 }; pArr p; p = arr; } */
/* const:常量,被它修饰的变量会具有常量的属性,使用 const 修饰指针包含三种情况 1.常量指针(指向常量的指针) 指向常量的指针 type const *p; 或者 const type *p; 可以改变指向,但是不能用 *p 修改指向变量的值 2.指针常量 它是常量,本身不能改变,也就是不能改变指向 因为指向不能改,所以必须初始化 但是可以通过取内容修改指向的内存中的内容 3.常量指针常量("常量指针"常量即:指针常量) 指针本身是一个常量,指向的也是常量 const int * const p = &a; 不能改变指向,也不能改变指向的内存的内容 */ # include <stdio.h> int main() { const int num = 0; // 变量 num 使用 const 修饰了就不能被修改了 // 1.常量指针 int a = 0, b = 9; const int * p = &a; // const -> int p = &b; // 可以改变指向 // 2.指针常量 int c = 6; int* const p1 = &c; // const -> p1 *p1 = 10; // 可以修改内容 // 3.常量指针常量 int d = 8; const int* const p = &d; // const -> int 和 p return 0; }
/*
指针与结构体:
指针与结构体结合起来使用包含两种情况:
一.指针成员
结构体变量的成员中存在指针
二.结构体指针
指向结构体变量的指针
*/
// 指针成员 # include <stdio.h> typedef struct { int n; int m; int* p; // 定义指针 }MyStruct; int main() { MyStruct mystr; mystr.n = 0; mystr.m = 0; mystr.p = NULL; // 地址置空 return 0; }
// 结构体指针 # include <stdio.h> typedef struct { int n; int m; }MyStruct; int main() { MyStruct mystr; mystr.n = 0; mystr.m = 0; MyStruct* p = NULL; // 定义一个指针 p ,它的类型是 MyStruct*,即该指针指向的是有 MyStruct 类型的变量(首地址) p = &mystr; // mystr 便符合条件,可以将首地址 &mystr 赋值给 p // 注意!通过指针访问“结构体中的元素”的时候,用 -> 符号,而不是用 . 符号 p->n = 9; p->m = 8; return 0; }
/*
注意事项
1.避免野指针
推荐:每次定义指针都进行初始化(有指向就给指向,没指向就置空 )
2.注意类型匹配
3.防止内存泄漏
只有堆区是自己申请,自己释放,其他地方都是系统分配,系统回收
总结:指针能够直接操作内存,必须在自己明确用途的情况下使用,否则很可能会造成严重后果!
*/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。