【文章标题】: 关于指针的一点理解
【文章作者】: BianChengN
【文成日期】: 2011-10-19
【作者邮箱】:273282378@qq.com
【作者声明】: 本文说的只是本人在学习及应用过程中遇到的指针问题的一些总结(本文所说的情况都是指在32位系统下的情况),希望对初学者有一些帮助,也算是我自己的一个总结。如果哪有说的不对的地方或者需要补充的地方请指教。 如果本文有说错的地方,恳请高手指出,及时纠正我错误的理解,感激不尽,同时也希望这篇幼稚的文章对刚接触编程的朋友能有所帮助。(本人文笔很差,大家将就着看吧^.^)
ps:由于我比较笨,
没找到插入图片的方法,故此文省略了图片,如果想看全版,请下载附件,附件中还有几本经典的书,一起发给大家。
先插个小插曲,我大四毕业的时候去上海一公司面试,人家问了我一个最基本的问题,关于指针的,题目如下 :
首先请定义一个整形指针,(简单吧)我不暇思索的写道 int *pInt;
然后的问题是,请给该指针赋值。 我又写道, pInt = Null;
然后他又问我如下的问题
int *pInt = &2; 可以这样么? 我当时的答案是不可以,因为不可以对常量取地址,(书上这么说的)虽然我说的没错,但当时我不明白原因。请大家带着这个问题继续阅读本文。
什么是指针呢?我们该如何定义一个指针呢?我们该如何给一个指针赋值呢?带着这些疑问请大家和我向下看。
什么是指针?
指针,本质上是一个无符号整数(unsigned int),32位系统下占4字节,(猜测在64位下应该占8字节,感谢我的同事yyk 很有想法的一哥们 ^.^)如果用更简单的话来描述指针的话,我想应该是存放同类型变量地址的变量。
该如何定义一个指针?
定义一个指针和定义一个变量是一样一样的,因为指针本身也是变量。^.^
我们可以这样定义一个整形指针, int* pInt; 其中int 代表整形, *是定义指针的说明符,pInt 是指针变量名,(感觉表达的不够精确,望指教)上面一句话的意思是 我们定义了一个指向整形变量的指针。 如果我们想定义其他类型的指针也很简单,只需要 把int 换成我们想要定义的类型就可以了。 如 double* pDbl;
该如何给一个指针赋值?
给指针赋值也很简单,定义完一个指针后我们就可以使用了,慢着,正如在函数内部定义一个变量编译器是不会给该变量赋初值 一样,我们定义完一个指针,编译器也不会给该指针赋初值,此时该指针的值是随机值,正如上面所说 指针是指向某地址的变量,指针的值即是它所指向的变量的地址,如果此时我们正好用了该指针,则后果不堪设想(所以建议大家定义指针的时候一定要赋初值)有点扯远了,继续接着说赋初值的事。我们可以先定义一个同类型的变量,然后对指针赋值, 比如 int nNum; pInt = &nNum; (pInt = nNum 也是‘对’的,但是却完全违背了我们的初衷,一会再说) 此时我们完成了对指针的赋值操作。怎么样简单吧?
nNum的内存地址是0x0012ff60
变量pInt 的值 是0x0012ff60,也就是pInt保存了变量nNum的内存地址,也就是指向了nNum。
指针pInt的内存地址是0x0012ff54
也就是编译器给变量分配的内存地址是0x0012ff54。
以上得到的数据是通过VS2005的内存窗口得到的值。
我再画蛇添足的说几句。
当我们声明或者定义一个变量时,编译器会为该变量分配空间,比如我们定义一个整型变量int nNum; 编译器会分配一个4字节大小的空间出来,如果我们想引用该变量我们可以用该变量名 即 可以这么用Tmp = nNum;
我们还可以通过定义一个相同类型的指针指向刚刚分配的空间的首地址,即
int *pInt = & nNum;
此时我们遇到了另外一个新符号“&” 叫 取地址符。顾名思义,用它可以取一个变量的地址。和它对应的是 “*”,应该叫 解引用操作符,我就是这么称呼它的^.^。我们可以用解引用操作符来定义一个指针,也可以取得指针变量(指针也是变量)所指向的内容。比如 上面的例子int *pInt = &nNum;
我们可以这样用 int nResult = *pInt; 此时 nResult和 nNum的值一样。也就是说我们通过解引用操作符取得了指针所指的地址的内容。
以下说下指针的几个重要的,容易错的地方,有什么疏漏还请多指点。
1)指针的sizeof
*对任何类型的指针sizeof的结果是4
例子 int *pInt = NULL;
sizeof(pInt) = 4
double *pDbl = NULL;
sizeof(pDbl) = 4
char *pChar = NULL;
sizeof(pChar) = 4
char cArray[100];
sizeof(cArray) = 100 // (100*1) 对数组进行sizeof 将得到数组的大小
//如果想获得数组的元素个数 则可以用很经典的方法,如下
int nLen = sizeof(cArray) / sizeof(cArray[0])
pChar = cArray;
sizeof(pChar) = 4
需要注意的是把数组作为参数传给函数时,在函数内部进行sizeof操作得到的永远是4,因为在函数内部把我们传过来的数组当做同类型的指针看待(数组退化成指针了),具体参考指针作为函数的参数。
2)指针作为函数的参数
在说之前请明确一点:函数调用是按值传递,而不是按地址传递的,不论传递的是指针还是普通的变量。这样理解就会使很多问题清晰化。
2-1)函数内部对数组进行sizeof
void funcTest(int nArray[100]){
int nSize = Sizeof(nArray); // nSize = 4 而不是 400 (100 * 4)
}
void sameFunc(int *pArray){
int nSize = Sizeof(pArray); // nSize = 4
}
这两个函数是一样的,第一个函数中的 100 是被忽略掉的,写不写都OK。所以我们定义函数的时候可以把传数组的地方直接用同类型的指针替代就可以了。(反正编译器是这么理解的)
也许有人就要问了,我如果想在函数内部获得数组的大小怎么办啊?这个我们可以通过增加一个额外的参数来确定我们所传递的数组的大小。如下例
void funcTest(int nArray[], int nLen){
int nSize = Sizeof(nArray); // nSize = 4 而不是 400 (100 * 4)
for( int idx=0; idx < nLen; idx++){
int nTmp = nArray[idx];
// Do Something Else Here …
}
}
利用该种方法的典型的API函数有memcpy, memmov,(关于这两个函数的区别请大家查一下,同样是内存拷贝函数,为什么有两个呢?)原型如下,具体请参考MSDN
void *memcpy(
void *dest,
const void *src,
size_t count
);
void *memmove(
void *dest,
const void *src,
size_t count
);
2-2)通过传递指针改变变量的值。
举一个比较经典的例子,交换两个数。
// Ok
void swap1(int *p1, int *p2){
int nTmp = *p1;
*p1 = *p2;
*p2 = nTmp ;
}
// No 新增对比 内部处理有问题,与上面的做对比 来说明传值
void swap1(int *p1, int *p2){
int* nTmp = p1;
p1 = p2;
p2 = nTmp;
}
// No
void swap2(int p1, int p2){
int nTmp = p1;
p1 = p2;
p2 = nTmp ;
}
int x = 10, y = 20;
swap2(x,y); // x = 10, y = 20
为什么这样呢?因为函数调用采用的方式是传值,swap2接受到的是x,y的副本,改变的也是副本,根本不会对x,y产生任何影响。
swap1(&x,&y); // x = 20, y = 10
这个为什么就可以呢?因为传递的是x,y在内存中的地址的副本(按值传递的理论),虽然是地址的副本,但地址唯一标识了变量在内存中的位置,也就是说 接触到“变量的真身”了,自然对其所做的任何改变都是生效的。
不知道像上面这么说能不能理解,反正我是这么理解的。还有一个例子可以更好的说明按值传递的理论。如下
Void funcTest(int nArray[], int nSize){
For(int idx =0; idx < nSize; idx++){
nArray[idx] = idx;
}
}
Int arrayTest[10] = {0};
funcTest(arrayTest, 10); // 函数调用结束后arrayTest的内容改变了,分别是 0,1,2,3,4,5,6,7,8,9
但是arrayTest本身的值没有变,这一点可以通过watch窗口得以观察到。(说到这就涉及到调试的内容了,真心希望初学者能学会自己调试,这样才能发现问题的本质,反汇编的时候也一样)
上面的例子很简单,说明的道理是,如果我们想改变函数外部的变量的值,我们应该传地址。(下面介绍的引用除外,具体参见 指针与引用)
也许有人又要问了,如果我想改变指针的值怎么办呢?那就传递指针的地址给函数不就行了。
例如《高质量C++/C编程指南》中的一例。 个人感觉很经典。
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(sizeof(char) * num);
}
void Test2(void)
{
char *str = NULL;
GetMemory2(&str, 100); // 注意参数是 &str,而不是str
strcpy(str, "hello");
cout<< str << endl;
free(str);
}
示例7-4-2用指向指针的指针申请动态内存
3)指针与引用
首先说什么是引用,通俗点说引用就是别名(Alias)
举个例子
int m;
int &n = m;
n 相当于m 的别名(绰号),对n 的任何操作就是对m 的操作。n 既不 是m 的拷贝,也不是指向m 的指针,其实n 就是m 它自己。(对n的任何改变都会影响到m,因为它们两个就是一回事)
又如有人名叫王小毛,他的绰号是“三毛”。说“三毛”怎么怎么的,其实就是对王小毛说三道四。(网上很经典的解释^.^)
指针和引用的区别与联系
(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
(2)不能有NULL 引用,引用必须与合法的存储单元关联(指针则可以是NULL)。
(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。
引用用的最多的是函数的参数,引用做函数的参数时,能起到和指针一样的作用,但在调用形式上不用取变量的地址,直接传递变量名就相当于传递变量的地址了。
如上面的交换两个数的例子,用引用实现如下:
void swap(int &p1, int &p2){
int nTmp = p1;
p1 = p2;
p2 = p1;
}
swap(x, y); // x = 20,y = 10, 和传指针效果一样,但更方便,更简洁
引用还有很多其他的用途,如类的复制构造函数等等,希望大家能看看我在最后给大家推荐的一些书,个人感觉很经典。
4)指针和数组
其实指针和数组没有必然联系,指针就是指针,数组就是数组。只不过我们在访问数组元素的时候既可以通过下标访问,也可以通过指针访问,所以大家可能有所误解。
还有很多可以写,没时间写了,以后有时间一定补上,或者大家可以跟帖补充。
5)指针的运算
和整数的加法,减法,自身的增量、减量
指针增量后指向下一个与指针基类同型的元素,增减单位是所指类型的长度。
例如 int *pInt; //假设pInt = 0x0000ff00
pInt + 1 = ?
// 0x0000ff01 (No) 0x0000ff04(OK) <=============> 0x0000ff00 + 1*4
6)指针的指针(二级指针)
其实二级指针也是指针,只不过它存储的是某个指针的地址值。大家可以按照理解指针的方法理解二级指针。(先不介绍了,打了三天了,有机会再补上吧)
最后向大家推荐几本入门的书籍。个人看完了感觉不错,希望对初学者有所帮助。(我有几本电子版,有需要的同学请联系我,或者教我怎么上传附件,太大了,传不上来,只能传小附件了)
C++:
《c++程序设计》第二版 吴乃陵 况迎辉 高等教育出版社
《c++程序设计》谭浩强 清华大学出版社
上面这两本都是大学课本,大家一定要看的,多看几遍才能感觉出人家写的确实不错。
《C++ Primer》算是公认的入门级的好书了,看了感觉还行,要多看几遍才能发现好!
《Thinking In C++》 中文名叫 《C++编程思想》
强力推荐,学C++的你请不要错过,否则你会后悔的。^.^ (建议至少看5遍)
《高质量C++/C编程指南》 林锐博士
《语言深度剖析》 陈正冲 石虎 个人感觉挺好的,适合有一定基础的人看。尤其是指针和内存管理那两章,值得一看,其他章节也不错。
汇编:
《汇编语言》郑晓薇
《80X86汇编语言程序设计》
数据库:
《数据库系统概念》 机械工业出版社 该书只看前三章即可,讲得是数据库的一些基本知识,如果想深入可以继续看后面的章节。老外的书就是不错。
Ps:如果下面的话不允许的话,还请版主帮忙删除,或者通知我,我自己删,没别的意思,只是希望大家能多交流经验。
如果大家有什么好想法,或者想交流技术,可以加我qq或者进群,真心寻找志同道合的朋友一同进步,最后祝愿看雪论坛越办越好,也祝大家工作顺利,学业有成。
以上
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: