C++中关于指针,CONST修饰符的一些说明
关于指针
如果说C/C++语言中什么最难掌握,那么应该是指针的使用了要想真正理解指针,不通过一段时间的学习实践体会,是很难做到的要想掌握指针,最好的学习语言是C,因为C++对指针的使用做了很多限制,比如指针自动类型转换,不安全指针使用等等.
下面说说我学习C/C++几年来,对指针的一些理解.与大家分享
首先,到底什么是指针呢?
其实,指针就是一个无符号整形数值(当然,指针也可以是负数,不过没有任何意义)
其实就是一个指向内存中某个位置的地址号,就象你家的门牌号一样.
在DOS和16位操作系统中,指针都是16位的,在WIN32系统中,有两种指针,16位短指针和32位的长指针.
而在WINDOWS程序中,指针同样有真实地址指针和进程虚拟指针两种.
为什么要使用指针?
我刚学C语言的时候,也为这个问题而迷惑,到底为什么要用指针呢?指针的好处在哪?
下面我举几个实际编程中遇到的情况来说明:
参数的传递.当我们想要在子函数中修改传递进去的参数的值(而不是他的拷贝值),那么我们必须传递指针.在C++中有了引用,但其实引用的本质就是指针.
动态的内存分配.有时候,我们不可能可以预计我们到底需要多少个变量,这个时候我们就需要动态的来创建变量.就需要用到指针.
数据结构.数据结构是离不开指针的,无论是链表,树,还是图,都是离不开指针的.如果你想编写一个出色的存储查找程序
你就必须使用指针,象我们平时用到的几乎所有数据库(ACCESS,SQL,ORACLE...)的内部存储查找结构就是如此.
WINDOWS消息机制.WINDOWS消息机制函数只传递两个参数,WPara和LPara,但却能满足实现WINDOWS成百上千种不同的消息.
为什么呢,因为使用了指针,WPara是一个16位短整型,LPara是一个32位长整形,这两个参数就是指针(一个是16位指针,一个是
32位指针).WINDOWS使用指针来传递消息函数所需要的任何参数.这也只有指针能做到.(想想为什么)
WINDOWS线程函数.和WINDOWS消息函数很相似,WINDOWS线成函数只提供一个参数PVOID(VOID型指针)作为你所能想到的任何线程函数
的参数.没有指针,你能做到吗?
还有很多必须使用指针的情况,无法在这一一列举.
关于指针的类型
指针也有类型的,指针的类型同样支持编译器内部定义类型(如INT,CHAR等)和用户自定义类型(如STRUCT等).
在C中,指针的类型到底有什么用呢?其中最重要的一点就是指针的加减运算.
如果一个指针i的值是10000,那么如果执行i++,或者i+n,结果将由指针的类型来决定.值等于 10000+sizeof(指针类型) 或者 10000+sizeof(指针类型)*n.
换句话说,对指针的加减n操作,就是相当于将指针向后或向前移动n个该类型单位.
数组的实现正是基于该机制的.一般编译器遇到数组后,比如a[5],实际上的操作是取指针a+5所指向的值.
另外,直接使用指针加减法要比使用数组下标速度更快.任何数组都是指针.
指针的加减法以外的其他运算操作,一般情况下是没有任何意义的.
而在C++中,由于考虑到程序稳定性,安全性的问题,对指针类型的自动转换有严格的限制,只允许将非VOID类型的指针
自动转换为(或者赋值)VOID类型的指针,而不允许将VOID类型的指针或其他类型的指针自动转换(或者赋值)
给其他非VOID型指针.
关于指针的初始化
如果定义了一个指针,我们没有初始化的话,那么该指针可能是任意值(甚至指向你操作系统的某个核心块)
所以,没有初始化的指针是极为不安全的,绝大多数情况下也是无法正常使用的.
所以,象下面这些程序都会导致程序运行出错,却能编译成功(我们称为运行期错误).
char* p;
char* m;
realloc(p,5);
scanf("%s",m);
数组也是指针,那么为什么我们能把数组象常量那样定义,而不用初始化呢
比如 char s[6];
scanf("%s",s);
这是因为,编译器对数组的处理是先分配内存,然后把内存的地址给数组指针,这实际上就是已经初始化了.象上面
那段程序,编译器相当于做了下面的工作:
char *s=malloc(6); //malloc(6)函数分配一个6Byte的内存,返回给s
scanf("%s",s);
但如果你在输入S的时候,输入的字符大于5,同样会导致运行期错误.(想想为什么?)
这里还要注意一个问题,如果我们写成
char s[6];
s="abcde";
那么其实是一个完全不同的概念.
任何字符串都是指针! "123","abcde",当程序中出现这样的字符串的时候,你千万不要认为它是一串值,其实它是一个指针.
编译器遇到 "abcde" 等字符串后,就会分配一个字符串长度加1的内存空间(这个1是'\0'),然后把指针值返回给s.
所以,假设一开始char s[6]的s指针值是10000的话,执行s="abcde",s就是一个完全不同的值了,指向了一个完全不同的内存块.
所以, char* s;
s="abcde";
是完全正确的.
但是,
char* s;
char* s1;
s="abcde";
s1="12345";
memcpy(s,s1,6);
却也会导致一个运行期错误,这是为什么呢?
这里我们又必须再次讨论编译器对字符串的处理了.编译器遇到字符串后给他们分配的内存是一个只读内存,也就是说不能往里面写东西,只能读取,
所以当我们将s1内存块的值COPY到s的时候,就会导致错误.
关于堆栈指针
什么是堆,什么是栈?
任何程序运行的时候都离不开内存的使用,那么在程序的运行中,内存是分两块的,一种是堆内存,一种是栈内存.
堆内存是可以自由使用,自由释放的内存,而且一般是几个程序共享的内存,而栈内存是一块固定的内存,程序在这块内存中按后进先去的方法
使用内存.程序从主函数main的入口开始使用栈,任何在该函数体中定义的变量指针,都在栈中分配内存,然后再函数结束的地方释放这些内存.
动态创建内存的时候,往往就使用堆内存,一般是通过函数 malloc,relloc,alloc,calloc来创建分配内存的.通过free类释放.在C++中,可以通过
NEW来创建.堆内存的使用是不安全的,因为只有你自己调用释放函数,它才会释放该块内存,否则,就会导致资源泄露.
有一点是需要注意的,就是返回子函数中的栈内存地址是无效且是危险的.
例如: int* f()
{ int a[6];
return a;
};
是错误的,因为a[6]是在子函数f的栈上创建的,在退出函数体后,子函数栈上创建的所有内存都被释放,那么,指针a原先指向的位置也早已无效了
关于CONST指针
const指针一般是在指针前面加上const符号
例如:const char* const s;
这里需要区分一下两个const的不同作用.
const char* s; //char* s是常量, *s='a' 是非法的,但是 s="a"却是合法的
char* const s;//s是常量, *s='a' 是合法的,但是 s="a"却是非法的
const char* const s;//*s='a'和s="a"都是非法的
定义 const int *i 的用处实际上是为了增强安全性,比如你想引用某个变量的指针,又不想因为疏忽而改变了这个变量的值,这个时候你就可以使用const int * i了,比如在程序中
int m = 10;
const int* i=&m;
.....
在后面的程序中如果你试图修改*i,比如
(*i)++;
编译的时候就会出错的
const int* i一般在函数参数中使用的最多,比如
void function(const int * i)
{
......
};
其作用和上面说的一样
关于C++中指针
C++中对指针做了一些补充和限制(更多的是限制)
C++中有了引用,引用的本质是指针.
例如: char s='a';
char& s1=s;
那么, &s1和&s是相同的,也就是说,变量s和s1实际上是同一块内存的内容.
如果我们改变s1的值,如 s1='b';那么s的值也就同样改变了.
引用比较多的发生在函数的参数传递中代替指针,优点是语法比指针简明.
C++中对指针作的限制上面已经提到过,就是不允许非VOID的自动类型转换.
例如 : int* i=malloc(sizeof(int)); 是非法的
而必须是: int* i=(int*)malloc(sizeof(int));
C++中还多了类的内部成员指针.一般很难通过外部直接得到类的内部指针.通常都是通过类自己的函数来返回内部成员指针.
在C++类的虚继承和多态中,被继承的结构通常不在继承对象中,而是通过指针引用.
对于指针,自己也有许多仍然不是很清楚的地方,说的比较乱,不对的地方还请大家多多指教
非常感谢刀剑如梦的补充
我这里把他的内容整理一下写在下面
#i nclude <iostream.h>
char *p="abcd";
cout<<p<<endl;
cout<<*p<<endl;
这两个输出完全两个概念,第一个是字符串,第二个其实是*(P+0),可是应用到int就不同
前几天看到论坛上有关COUT的疑问,这里再就着这个问题解释一下(虽然可能有点超出指针的范围)。
在VC++原代码中可以看到cout是一个外部引用的IOSTREAM基类的继承类实例对象。
也就是extern ..... cout (中间那个具体的继承类名字记不起来了)
具体的代码我就不写出来了,可以自己在VC++头文件中去找。
<<实际上是一个IOSTREAM继承类中定义的运算符重载函数,而且该继承类对该运算符进行了多次重载。
重载的参数包括了所有的编译器原始数据类型,指针(一般的指针被看做长整形,除字符串指针外)
在上面这个例子中,前面的内容中我已经说到过 "abcd" 其实就是一个指针,并把它赋值给字符串指针p。
cout<<p<<endl,传递给函数<<的参数是字符串指针类型,所以,就会调用相对应的重载函数,该函数会输出字符串。
cout<<*p<<endl,传递给函数<<的参数是字符,所以,就会调用另外一个重载函数,该函数输出单个字符。
而 int *i = 100; cout<<i<<*i<<endl; 由于i不是字符串指针,所以会把i当作整形处理,实际上,输出了两个整形数值。
另外:数据类型转换指针的转换格式 *(int*)p以及空类型指针的转换 *(int*)this->P 中this是指针,P是空类型类成员指针;
包括指针和引用的基本使用错误手法,为什么错要讲的更清楚,比如:
int *P,R=5;
*P=10;
P=&R;
错在那里,应该说清楚
*(int*)p实际上是把不是int型指针的p强制转化为int型指针,并求它的值。这实际上是一种特殊的操作,一般情况下不是经常用到
我举个实际点的例子来说明他的作用吧。
比如在编写通讯程序的时候,一般接收到的信息都是以BYTE 也就是 UCHAR 的形式发送过来的。
那么指向接收缓存的往往是一个UCHAR*型指针。
这个时候,如果你知道这些信息中,有你想要的INT型数据,并且你知道它的具体字节位置。
你就可以使用 *(int*)p 来获取这个INT型数据。
例如: UCHAR* pBuffer = GetBuffer();
p = pBuffer + 10;
INT iNum = *(int*)p;
至于*(int*)this->P ,一般都是使用在类中。
this是指向类自身的指针,this指针一般都是使用在需要将自身指针做为参数传递给某个函数的时候用到。
例如创建对话框时将this指针传递给Create函数,当做对话框的父窗口指针,
又或者调用拷贝构造函数时用到的
class ClassObject A,B;
在A中某个函数中将A赋值给B B=(*this);
相反,*(int*)this->P这种用法不是很常见,因为可以直接写成*(int*)P。省略掉this。
最后一段代码的错误之处在于P没有初始化就随便给他所指向的地址内容赋值了。
http://www.jysls.com/thread-171516-1-1.html