C/C++心得-理解指针
上一篇笔者用自己那不是怎么好理解的逻辑介绍了内存和C中的基本数据类型,现在笔者再根据自己重新所学来说说C语言中的指针。
理解指针才能真正的算C语言入门。也许是我大学期间太关注前端UE,也许是当初开始学C语言的时候没怎么认真;直到毕业后的某一天我才“懂”指针,才算理解C语言的独特。如果有初学C语言的同行对指针有困惑,希望我这浅薄的认识能帮助你。
1、简介
指针在原英文中为pointer,个人觉得翻译过来后针的含义不如指的含义好理解,pointer还可翻译为指示器,如果是初学者的话,笔者建议在学习过程中多琢磨指的含义。
指针可以说是一种特殊的数据类型,前面的内存篇介绍过,程序运行时的数据基本上都存储在内存中,内存中用数字标识不同数据,在各种计算机语言中,把这种数字标识成为地址。计算机系统并不认识C语言,程序编译就是把C语言翻译为操作系统可识别的语言。比如“我”翻译英文为I,翻译过后已经看不到“我”这个字了。我们在程序中定义了整型变量i,赋值为5,那么经过编译运行,操作系统同样看不到i,对操作系统而言,i被翻译为某个内存地址上的整型值。
2、基本使用
要使用指针首先要认识两个符号:一个是'&',可以在程序中取得变量的内存地址;一个是'*','*'在定义变量的时候标明该变量为指针,在已定义的变量前面使用的时候,表示获取(设置)该指针变量所在内存地址中存储的值。
#include <stdio.h> int main(int arg, char * args[])
{
int i = ; // 定义整型变量i并赋值为5
int * srcI = &i; // &为取地址符号,取得i的内存地址,并赋值给int *的整型指针类型变量srcI
printf("srcI:%d,%X\n", srcI, srcI); // 内存地址可以转化为数据打印出来,一般使用16进制查看
printf("*srcI:%d\n", *srcI); // 在定义变量的时候,符号*表示定义指针,
// 在对已定义的变量的前面加上*的时候,符号*表示取得该变量所标地址中变量的值
*srcI = ;
printf("i:%d\n", i); // 可以通过地址设置变量的值
getchar(); // 起暂停作用
return ;
}
上面代码执行结果如下(每次程序运行时变量的内存地址可能会不同):
srcI:,74F7B8
*srcI:
i:
笔者认为理解指针重点就在理解'*'这个符号,这里强调下,在定义变量时,'*'表示后面定义的变量是指针类型,在已定义的指针前'*'表示获取(设置)后面变量存储的内存地址中实际存储的值,专业点说就是获取(设置)后面变量所指向的值。下面列举一个未理解'*'含义时会犯的错误:
#include <stdio.h> int main(int arg, char * args[])
{
int i = , j = ;
int * src = &i;
*src = &j; // 本意是把src的地址由i的地址改为j的地址,但这里弄错了*的含义
printf("*src:%d\n", *src);
printf("i:%d\n", i); // 实际结果改变了变量i的值
getchar();
return ;
}
上述代码编译运行都没有报错,本意是想把src存储的地址改为变量j的内存地址,而实际却变成设置变量i的值,这就是对'*'错误的理解后果。这段代码还说明了一件事情:第7行地址可以赋值给整型变量,这也证实了前面的话:内存中用数字标识不同数据,该数据标识就是内存地址。
下面再通过代码说明const指针
#include <stdio.h>
#include <stdlib.h> int main(int arg, char *args)
{
int num = ;
const int * p1 = # // *前面 const int 和 int const 等价,这样定义的指针表示不可修改其地址指向的值
int * const p2 = # // 表示指针地址不可修改,但是可以修改其地址指向的值
const int * const p3 = # // 指针地址和地址指向的值都不可修改
p1 = NULL;
//* p1 = 0; // 报错
//p2 = NULL;// 报错
*p2 = ;
//p3 = NULL;// 报错
//*p3 = 0; // 报错
return ;
}
3、指针高级
之前只使用了整型作为示例,而实际上'&'可以用来取得所有数据类型的地址,如char,float,double等等;用'*'定义指针的时候,同样可以定义出 char *,float *,double *这些指针类型,不同的指针类型在读取数据的时候的不同点就在于其解释地址的方式不同。如int *类型,定义后使用*取值,会从int *所代表的地址开始,取sizeof(int)位地址的数据,然后以int的方式解析出数据,使用其他数据类型以此类推。
指针类型可以存储所有数据类型的地址,那么指针是否可以存储指针的地址呢?确实可以,有关多级指针的问题,这里举个可能不是很恰当的例子:在班级课堂上你需要一支笔,于是你向你同桌要,同桌说ta后面的人有笔,然后你再向同桌后面的人要,同桌后面的人说ta旁边的人有,于是你再向同桌后面旁边的人要......如果需要的话,这个场景可以循环下去。多级指针大概就是这么个意思,你读取这个地址发现里面还是个地址,于是再读里面的地址......当然现实中遇到这种借笔借半天的问题肯定很坑,但是对于计算机指针来说,只是多一个符号的问题。下面做个简单示例:
#include <stdio.h> int main(int arg, char * args[])
{
int i = ;
int * src = &i;
int ** srcsrc = &src; // 多级指针无非就是多个*,多次&
int *** srcsrcsrc = &srcsrc; printf("*src:%d\n", *src);
printf("**srcsrc:%d\n", **srcsrc);
printf("***srcsrcsrc:%d\n", ***srcsrcsrc); // 同样可以设置值
*src = ;
printf("i:%d\n", i);
**srcsrc = ;
printf("i:%d\n", i);
***srcsrcsrc = ;
printf("i:%d\n", i);
getchar();
return ;
}
执行结果如下:
*src:
**srcsrc:
***srcsrcsrc:
i:
i:
i:
4、指针运用
说了这么多,指针在实际使用中有何作用?首先,你发现指针读取的都是操作系统内存地址,所有程序的数据都存在系统内存中,如果能读取设置他们所有的值,那么也就可以通过内存地址修改系统其他程序的数据,这就是修改器、外挂的部分原理,实际想这么做还要考虑如何找地址,如何通过系统的内存保护(注入)。
首先介绍下指针与数组。定义一个容量为6的整型数组int array[6],其实此时array就是一个指针,其地址就是该数组中第一个元素的地址。关于数组和指针可以看下面代码及其注释:
#include <stdio.h> int changeArr(int * arr)
{
arr[] = ; // 正常方式赋值数组元素
*(arr + ) = ; // 指针方式赋值数组元素
} int main(int arg, char *args)
{
int i = ;
int array[] = { , , , , , };
printf("*array:%d\n", *array); // 可以直接通过*取得首元素值 // 正常方式遍历数组
printf("array:");
for (i = ; i < sizeof(array) / sizeof(int); i++)
{
printf("%3d ", array[i]);
}
printf("\n");
// 指针方式遍历数组
printf("array:");
for (i = ; i < sizeof(array) / sizeof(int); i++)
{
printf("%3d ", *(array + i));
}
printf("\n"); // 通过带指针参数的函数对数组值进行修改
changeArr(array);
// 指针方式遍历数组
printf("after change\narray:");
for (i = ; i < sizeof(array) / sizeof(int); i++)
{
printf("%3d ", *(array + i));
}
printf("\n");
getchar();
return ;
}
执行结果如下:
*array:
array:
array:
after change
array:
上面所用示例基本都是栈上的内存空间,在直接定义变量的时候会申请栈上的内存空间,实际程序所能申请使用的栈空间很小,所以在处理大一些的数据的时候,应手动申请堆上的内存。在C语言中,手动申请需要了解几个C语言函数,这里先列举它们的函数名:malloc,calloc,realloc,free,_alloca。
这里先以malloc和free两个函数做个示例(对数据类型内存空间有疑问的可以看看之前的内存的随笔):
#include <stdio.h>
#include <stdlib.h> int main(int arg, char *args)
{
// malloc函数原型为 void *malloc(size_t size); 其中size_t指无符号整数,也就是非负数,返回值数据类型为无类型指针地址(void *)
// 分配失败的时候该地址值为0,C语言中NULL就是0的别称。
int * a = malloc(sizeof(int)); // sizeof函数可以获取数据类型已经变量所占用的内存空间,这里需要一个整型的空间
if (a == NULL)
{
// 分配内存失败,基本不会执行到这里,一般出现该问题基本都是系统内存不足,或者系统出现大问题
printf("malloc failed\n");
return -;
}
*a = ;
printf("%d,%X\n", *a, a); // 分别打印出指针a地址指向的值及a地址 if (a != NULL)
{
free(a); // 用free可释放已申请的内存空间
// 有些内存已经释放,但是如果还有指针指向这篇被释放的内存很比较危险,这种情况叫野指针
// 一般都会在释放后将其置为NULL
a = NULL;
} getchar(); // 暂停作用
return ;
}
执行结果为:
,
其他内存分配相关函数用的并不是很多,这里只做简介记录
realloc:对已分配内存的指针进行重新分配,同样需要free手动释放内存,以应用举例:原来分配的内存可能过小,这时可用realloc,申请一片新的内存空间,然后将之前的内存空间复制过去,如果新空间比原空间小会导致数据丢失。可用realloc做C语言中的动态数组(可变容量)
原型:void *realloc(void *mem_address, unsigned int newsize);
calloc:申请两个数乘积大小的空间,同样需要free手动释放内存。比如我想申请一个含6个元素的整型数组空间:calloc(6,sizeof(int));
原型:void *calloc(size_t n, size_t size);
_alloca:在栈上申请空间,用完自动释放
原型:void * __cdecl _alloca(size_t);
二级指针应用和三级指针应用一般在开发动态库的时候才会用得到,有兴趣的可以自行查查相关资料,本文中暂不赘述。
5、函数指针引子
一般来说,一个人能运用函数指针说明其水平正从入门走向熟练,笔者自己用的基本都是在一些系统调用下才会用到,一般用于回调,这里就做个引子,简单说说其定义及使用。请看下图中代码及注释:
#include <stdio.h> void func()
{
printf("hello func pointer\n");
} float addFloat(float a, float b)
{
return a + b;
} int main(int arg, char *args[])
{
void (*f)() = func; // 定义函数指针并初始化
float(*addf)(float a, float b) = addFloat; // 带参数的函数指针
f(); // 可通过该函数指针直接执行
printf("%.1f+%.1f=%.1f\n", 3.0f, 4.0f, addf(3.0f, 4.0f));
getchar();
return ;
}
执行结果如下:
hello func pointer
3.0+4.0=7.0
我对于指针的部分理解及运行暂时就先到这里,如果有问题还请大家指出,谢谢!
C/C++心得-理解指针的更多相关文章
- 你好,C++(40)7.1 一切指针都是纸老虎:彻底理解指针
第7章 C++世界的奇人异事 在武侠小说中,初入武林的毛头小子总是要遇到几位奇人,发生几件异事,经过高人的指点,经历一番磨炼,方能武功精进,从新手成长为高手.在C++世界,同样有诸多的奇人异事.在C+ ...
- C语言指针专题——如何理解指针
本文为原创,欢迎转发! 最近在研读C primer plus 5版中文版,老外写的,还是很经典的,推荐给读者们,有需要的朋友可以在这里购买:C primer plus 5版中文版 指针,传说中是C语言 ...
- 深入理解C语言-深入理解指针
关于指针,其是C语言的重点,C语言学的好坏,其实就是指针学的好坏.其实指针并不复杂,学习指针,要正确的理解指针. 指针是一种数据类型 指针也是一种变量,占有内存空间,用来保存内存地址 指针就是告诉编译 ...
- C语言重点——指针篇(一文让你完全搞懂指针)| 从内存理解指针 | 指针完全解析
有干货.更有故事,微信搜索[编程指北]关注这个不一样的程序员,等你来撩~ 注:这篇文章好好看完一定会让你掌握好指针的本质 C语言最核心的知识就是指针,所以,这一篇的文章主题是「指针与内存模型」 说到指 ...
- 大一C语言学习笔记(9)---指针篇--从”内存的使用“和“流程控制”的角度来理解“指针变量的使用‘
#深入理解指针变量 举个错误栗子: //以下代码的目的是输出100和1000,但输出结果只有一个100 #include<stdio.h> #include<malloc.h> ...
- c++父类指针强制转为子类指针后的测试(帮助理解指针访问成员的本质)(反多态)
看下面例子: #include "stdafx.h" #include <iostream> class A { //父类 public: void f() / ...
- 用php理解指针--写给刚刚学习编程的人
在刚刚学习编程时,可能for循环什么的还是可以理解,但是当学习到指针的时候,课上估计很多人就睡觉去了. 现在用两端php程序说明指针 先写一个简单的,大家都理解下 <?php class tex ...
- 深入理解指针—>结构体里的成员数组和指针
单看这文章的标题,你可能会觉得好像没什么意思.你先别下这个结论,相信这篇文章会对你理解C语言有帮助.这篇文章产生的背景是在微博上,看到@Laruence同学出了一个关于C语言的题,微博链接.微博截图如 ...
- 深入理解指针—>指针函数与函数指针的区别
一. 在学习过程中发现这"指针函数"与"函数指针"容易搞错,所以今天,我自己想一次把它搞清楚,找了一些资料,首先它们之间的定义: 1.指针函数是指带指针的函数, ...
随机推荐
- 判断当前IE浏览器是否支持JS
1.server 2008 r2 64位中自带的IE默认不支持js,这样一些有JS的页面就是失效,所以如果要考虑这方面的系统,需要判断浏览器是否支持JS <div class="js- ...
- Query performance optimization of Vertica
Don't fetch any data that you don't need,or don't fetch any columns that you don't need. Because ret ...
- Python基础学习总结(五)
7.用户输入输出和while循环 1.使用函数 input() 输入,print() 打印,字符串可以用逗号隔开.end=' ' 关键字参数,打印时可以不换行,sep=‘ 你想要的分隔符 ’,关键字参 ...
- 7、srpingboot改变JDK版本
在pom.xml中加上 <plugin> <artifactId>maven-compiler-plugin</artifactId> <configurat ...
- csharp: DataTable export to excel,word,csv etc
http://code.msdn.microsoft.com/office/Export-GridView-to-07c9f836 https://exporter.codeplex.com/ htt ...
- Luogu1261: 服务器储存信息问题
题面 传送门 Sol 我们可以考虑每种\(rank\)的点\(u\)会被哪些点\(v\)感兴趣 如果\(dis[u][v]<\)所有满足\(rank\)大于\(rank[u]\)的点到\(v\) ...
- Python入门-深浅拷贝
首先我们在这里先补充一下基础数据类型的一些知识: 一.循环删除 1.前面我们学了列表,字典和集合的一些操作方法:增删改查,现在我们来看一下这个问题: 有这样一个列表: lst = ['周杰伦','周润 ...
- Oracle中的索引详解(转载)
一. ROWID的概念 存储了row在数据文件中的具体位置:64位 编码的数据,A-Z, a-z, 0-9, +, 和 /, row在数据块中的存储方式 SELECT ROWID, last_name ...
- springboot 使用webflux响应式开发教程(二)
本篇是对springboot 使用webflux响应式开发教程(一)的进一步学习. 分三个部分: 数据库操作webservicewebsocket 创建项目,artifactId = trading- ...
- content provider其中操作文件的函数
此类函数还是有杀伤力的 1.openAssetFile(Uri uri, String mode)This is like openFile(Uri, String), but can be impl ...