指针知识体系搭建
1. 指针是一种数据类型
1.1 指针也是一种变量
占有内存空间,用来保存内存地址
测试指针变量占有内存空间大小
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char p = 100;
char * p1 = 100;
char **** p2 = 100;
printf("p:%d,p1:%d,p2:%d\n", sizeof(p),sizeof(p1), sizeof(p2));
system("PAUSE");
return 1;
}
1.2 (*p)操作内存
- 在指针声明时,*号表示所声明的变量为指针
- 在指针使用时,*号表示 操作 指针所指向的内存空间中的值
- *p相当于通过地址(p变量的值)找到一块内存;然后操作内存
- *p放在等号的左边赋值(给内存赋值)
- *p放在等号的右边取值(从内存获取值)
1.3 指针变量和它指向的内存块是两个不同的概念
- 给p赋值
p=0x1111;
只会改变指针变量值,不会改变所指的内容;p = p +1; //包括p++
- 给*p赋值
*p='a';
不会改变指针变量的值,只会改变所指的内存块的值 - =左边*p 表示 给内存赋值, =右边*p 表示取值 含义不同
- =左边
char *p
表示初始化一个指针变量
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char a = 10;
int *p3 = NULL;
p3 = &a;
*p3 = 20; //间接的修改a的值
//*就像一把钥匙 通过一个地址(&a),去修改a变量的标示的内存空间
{
int c = 0;
c = *p3; //c=20
//*p放在=号左边 写内存
//*p放=号的右边 读内存
printf("c:%d \n", c);
}
{
char *p4 = NULL;
p4 = (char *)malloc(100);
p4 = (char *)malloc(200); //修改的只是指针指向的内存块
}
system("PAUSE");
return 1;
}
- 保证指针所指向的内存空间 可以被修改,有时候指针指向常量区(隶属于全局静态区),是不可修改那部分内存空间的
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *getStr81()
{
char *tmp = NULL;
tmp = "abcdefgf";
return tmp;
}
void main()
{
char *p = getStr81();
printf("p:%s \n", p);
*(p+2) = 'r'; //经常出现的错误 保证指针所指向的内存空间 可以被修改
system("pause");
return ;
}
改程序运行时会抛出异常!!!!
1.4 指针是一种数据类型,是指它指向的内存空间的数据类型
- 指针步长(p++),根据所指内存空间的数据类型来确定
p++=(unsigned char *)p+sizeof(a);
结论:指针的步长,根据所指内存空间类型来定。
注意: 建立指针指向谁,就把把谁的地址赋值给指针。图和代码和二为一。不断的给指针变量赋值,就是不断的改变指针变量(和所指向内存空间没有任何关系)。
- 指针做函数参数 形参有多级指针的时候,
- 站在编译器的角度 ,只需要分配4个字节的内存(32bit平台)。所以在分析参数的时候只需要站在编译器的角度考虑即可
- 当我们使用内存的时候,我们才关心指针所指向的内存 是一维的还是二维的
1.5 野指针的实例
- 产生野指针的原因:
没有明白指针变量和指针所指的内存空间(表示的变量)是两个概念,代表的是不同的内存块 - 避免野指针的方法主要有两个步骤:
- 在声明指针变量的时候随即初始化为NULL;
- 在释放掉堆空间的内存以后将相应的指针变量赋值为NULL。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char * p1 = NULL;
p1 = (char*)malloc(100);
if (NULL == p1)
return -1;
strcpy(p1, "112233");
if (p1 != NULL)
{
free(p1);
//p1 = NULL;//如果加了这个语句就不会出现野指针。
}
/*something else*/
if (p1 != NULL)
{
free(p1);
}
system("PAUSE");
return 1;
}
2. 间接赋值(*p)是指针存在的最大意义
2.1 零级指针到一级指针的推演
- 指针指向哪个变量,就是把相应的变量的地址赋值给他
- 不断改变指针变量的值,只是不断修改它的指向,但是原内存空间的内容不变
- 值传递和地址传递的区别本质上是通过*这个钥匙是否操作了同一块内存(指针的精华:通过
*
和指针变量可以操作同一块内存)
示例代码:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int getFileLen(int * p)
{
*p = 100;
return *p;
}
int getFileLen2(int b)
{
b = 200;
return b;
}
int main(void)
{
int a = 10; //条件1 定义了两个变量(实参 另外一个变量是形参p)
int *p = NULL;
//修改a的值
a = 20; //直接修改
p = &a; //条件2 建立关联
*p = 30; //p的值是a的地址 *就像一把钥匙 通过地址 找到一块内存空间 求间接的修改了a的值
printf("a: %d \n", a);
{
*p = 40; // p的值是a的地址 *a的地址间接修改a的值 //条件3 *p
printf("a: %d \n", a);
}
getFileLen(&a);
printf("getFileLen以后a: %d \n", a);
getFileLen2(&a);
printf("getFileLen2以后a: %d \n", a);
system("PAUSE");
return 1;
}
2.2 一级指针到二级指针的推演
- 实际上所谓的值传递和地址传递都是值传递,实参传递给行参的时候都是进行一份数据的拷贝;
- 如果同级指针之间进行传递,比如实参和形参都是零级指针,这时候就是最容易让人理解的值传递,大家都知道这样在被调函数里对形参的修改不会影响实参;同理如果实参和形参都是一级指针,在被调函数里又没有通过*进行间接修改的话,被调函数的修改也不会影响主调函数的实参;
- 对于被调函数能否修改主调函数实参的内容,关键是看在被调函数里是否存在使用*进行间接访问,或者存在数组的下标访问,这两种访问都是一种间接访问的方式。
- 如果想修改一级指针的值,那么形参要用二级指针;
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void getMem(char ** p)
{
*p = 400;
}
/*这种情况实际上就是常说的值传递*/
void getMem2(char * p)
{
p = 800;//被调函数里没有使用*或者数组下标进行间接访问,所以不会影响实参
}
/*注意:这个函数很重要,这是一个很容易出错的地方*/
void getMem3(char * p)
{
*p = 1;//由于p是char *类型,所以*p修改的只是一个char类型的内存块,也就是只修改了1个字节8个bit的内容
//虽然从主调函数传递实参给被调函数的时候,使用&p1,从理论上说,是没有错误的,因为指针就是一个地址值,不管是多少级的指针,它的值都是一个地址,对应内存中的某一块;
//p1是一个char *类型,取地址以后是一个4字节的地址,所以传递给p的尽管是一个合法的地址,且p确实也指向了p1所处的内存位置
//但是在进行间接访问操作的时候,编译器把*p解释为修改一个char类型的值,
//所以他只修改了p1地址值(也就是p1表示的那块内存,注意不是p1指向的内存,而是p1本身对应的内存)的低8位,也就是最低字节
//故在前面p1的值已经被修改为400以后,对应的16进制是0x190,此处再次修改该内存的内容只是修改0x90为0x01,
//前面高位的2个字节没有动,所以修改以后的内存内容为0x101,对应的十进制便是257,从而说明数据类型匹配的重要性。
//实际上,一级指针取地址以后的数据类型便是二级指针,推广到n级指针取地址以后的数据类型是n+1级指针,
//当你用二级指针传递给形参为一级指针的时候,数据类型已经不对了,后面的间接访问操作是会存在很大隐患的。
//尽管此时你任然认为将一个地址值赋值给一个指针变量天经地义(这句话没有错,错在间接访问的时候需要依赖指针的数据类型决定间接访问的内存块的大小)
}
int main(void)
{
char * p1 = NULL;
char ** p2 = NULL;
p1 = 0x11;
p2 = 0x22;
printf("sizeof(char):%d\n",sizeof(char));
printf("p1:%d\n",p1);
p2 = &p1;
*p2 = 100;
printf("p1:%d\n", p1);
{
*p2 = 200;
printf("p1:%d\n", p1);
}
getMem(&p1);
printf("getMem以后p1:%d\n", p1);
getMem2(p1);
printf("getMem2以后p1:%d\n", p1);
getMem3(&p1);
printf("getMem3以后p1:%d\n", p1);
system("PAUSE");
return 1;
}
注意:
/*注意:这个函数很重要,这是一个很容易出错的地方*/
void getMem3(char * p)
{
*p = 1;//由于p是char *类型,所以*p修改的只是一个char类型的内存块,也就是只修改了1个字节8个bit的内容
//虽然从主调函数传递实参给被调函数的时候,使用&p1,从理论上说,是没有错误的,因为指针就是一个地址值,不管是多少级的指针,它的值都是一个地址,对应内存中的某一块;
//p1是一个char *类型,取地址以后是一个4字节的地址,所以传递给p的尽管是一个合法的地址,且p确实也指向了p1所处的内存位置
//但是在进行间接访问操作的时候,编译器把*p解释为修改一个char类型的值,
//所以他只修改了p1地址值(也就是p1表示的那块内存,注意不是p1指向的内存,而是p1本身对应的内存)的低8位,也就是最低字节
//故在前面p1的值已经被修改为400以后,对应的16进制是0x190,此处再次修改该内存的内容只是修改0x90为0x01,
//前面高位的2个字节没有动,所以修改以后的内存内容为0x101,对应的十进制便是257,从而说明数据类型匹配的重要性。
//实际上,一级指针取地址以后的数据类型便是二级指针,推广到n级指针取地址以后的数据类型是n+1级指针,
//当你用二级指针传递给形参为一级指针的时候,数据类型已经不对了,后面的间接访问操作是会存在很大隐患的。
//尽管此时你任然认为将一个地址值赋值给一个指针变量天经地义(这句话没有错,错在间接访问的时候需要依赖指针的数据类型决定间接访问的内存块的大小)
}
2.3 间接赋值(指针作函数参数)存在的意义
- 通过间接赋值可以操作特定的内存
- 指针作为函数参数使得不同函数之间可以操作同一块内存
- 指针作为函数参数使得主调函数和被调函数可以同时操作某个变量或者某块内存,使得在被调函数里进行运算的结果可以传递到主调函数,这里的结果强调的是多个变量或者多块内存,从而实现软件的分层模型,进一步推演出接口的封装和设计
- 函数调用时,用实参取地址,传给形参,在被调用函数里面用*p,来改变实参,把运算结果传出来
示例代码:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int getMemMAX(char ** my_p1, int *my_len1, char ** my_p2, int *my_len2)
{
int ret = 0;
char * tmp1, *tmp2;
tmp1 = (char *)malloc(100);
if (tmp1 == NULL)//对有可能出现NULL的指针进行判断
return ret = -1;
strcpy(tmp1, "1234567890");
*my_p1 = tmp1;//二级指针间接赋值
*my_len1 = strlen(tmp1);//一级指针间接赋值
tmp2 = (char *)malloc(200);
if (tmp2 == NULL)
return ret = -1;
strcpy(tmp2, "zxcvbnmasdffjhj");
*my_p2 = tmp2;
*my_len2 = strlen(tmp2);
return ret;
}
int main(void)
{
char *p1, *p2;
p1 = NULL;
p2 = NULL;
int len1 = 0, len2 = 0;
int ret = 0;
ret = getMemMAX(&p1, &len1, &p2, &len2);
if (ret != 0)
return ret;
printf("p1:%s\tlen1:%d\n", p1, len1);
printf("p2:%s\tlen2:%d\n", p2, len2);
if (p1 != NULL){//避免出现野指针
free(p1);
p1 = NULL;
}
if (p2 != NULL){
free(p2);
p2 = NULL;
}
system("PAUSE");
return 1;
}
2.4 间接赋值成立的条件
- 条件一:对每一个要进行间接赋值的变量或者内存空间声明两个成对的变量
- 条件二:让上述两个变量建立关联(低级指针变量取地址赋值给另一个高一级的指针变量或者函数调用传递参数)
- 条件三: 使用间接访问操作符
2.5 间接赋值的应用场景
- 三个条件同时出现在一个函数内(最简单的方式)
- 第一二个条件出现在一个函数,第三个条件单独出现在被调函数(指针作函数参数)
- 第一个条件单独出现在一个函数。第二第三个条件一起出现在别的地方(C++知识点)
2.6 间接赋值推论
函数调用时,用n级指针(形参)改变n-1级指针(实参)的值。
3. 理解指针必须和内存四区概念相结合
- 主调函数 被调函数
- 主调函数可把堆区、栈区、全局数据内存地址传给被调用函数(输入特性)
- 被调用函数只能返回堆区、全局数据(输出特性)
- 内存分配方式
指针做函数参数,是有输入和输出特性的。实际上就是关注指针所指内存是从主调函数传递给被调函数使用还是从被调函数分配给主调函数使用。
4. 应用指针必须和函数调用相结合(指针做函数参数)
指针做函数参数,问题的实质不是指针,而是看内存块,内存块是1维、2维。
5. 指针经典语录
- 指针也是一种数据类型,指针的数据类型是指它所指向内存空间的数据类型
- 间接赋值*p是指针存在的最大意义
- 理解指针必须和内存四区概念相结合
- 应用指针必须和函数调用相结合(指针做函数参数)
指针是子弹,函数是枪管;子弹只有沿着枪管发射才能显示它的威力;指针的学习重点不言而喻了吧。接口的封装和设计、模块的划分、解决实际应用问题;它是你的工具。 - 指针指向谁就把谁的地址赋给指针,用它对付链表轻松加愉快
- 链表入门的关键是分清楚链表操作和辅助指针变量之间的逻辑关系
- C/C++语言有它自己的学习特点;若java语言的学习特点是学习、应用、上项目;那么C/C++语言的学习特点是:学习、理解、应用、上项目。多了一个步骤
- 学好指针才学会了C语言的半壁江山,另外半壁江山是函数指针亦即回调函数
- 理解指针关键在内存,没有内存哪来的内存首地址,没有内存首地址,哪来的指针啊。