• 1. 指针的专题总结小洪
  • 2. 内存空间存储地址空间是指对存储器编码(编码地址)的范围。所谓编码就是对每一个物理存储单元(一个字节)分配一个号码,通常叫作“编址”。分配一个号码给一个存储单元的目的是为了便于找到它,完成数据的读写,这就是所谓的“寻址”(所以,有人也把地址空间称为寻址空间)。
  • 3. 一、指针的概念
  • 4. 指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。要搞清一个指针需要搞清指针的四方面的内容:1、指针的类型,2、指针所指向的类型,3、指针的值或者叫指针所指向的内存区,4、还有指针本身所占据的内存区。 (1)int *ptr; (2)char *ptr; (3)int **ptr;特别注意:是表示指向指针的指针变量ptr (4)int (*ptr)[3]; (5)int *(*ptr)[4]; 小朋友首先看一下看得懂吗?自己先试着分析一下
  • 5. int p; //这是一个普通的整型变量 int *p; //首先从P 处开始,先与*结合,所以说明P 是一 //个指针,然后再与int 结合,说明指针所指向 //的内容的类型为int 型.所以P 是一个返回整 //型数据的指针 int p[3]; //首先从P 处开始,先与[]结合,说明P 是一个数 //组,然后与int 结合,说明数组里的元素是整 //型的,所以P 是一个由整型数据组成的数组 int *p[3]; //首先从P 处开始,先与[]结合,因为其优先级 //比*高,所以P 是一个数组,然后再与*结合,说明 //数组里的元素是指针类型,然后再与int 结合, //说明指针所指向的内容的类型是整型的,所以 //P 是一个由返回整型数据的指针所组成的数组
  • 6. int (*p)[3]; //首先从P 处开始,先与*结合,说明P 是一个指针 //然后再与[]结合(与"()"这步可以忽略,只是为 //了改变优先级),说明指针所指向的内容是一个 //数组,然后再与int 结合,说明数组里的元素是 //整型的.所以P 是一个指向由整型数据组成的数 //组的指针 int **p; //首先从P 开始,先与*结合,说是P 是一个指针,然 //后再与*结合,说明指针所指向的元素是指针,然 //后再与int 结合,说明该指针所指向的元素是整 //型数据.由于二级指针以及更高级的指针极少用 //在复杂的类型中,所以后面更复杂的类型我们就 //不考虑多级指针了,最多只考虑一级指针. int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入 //()里分析,说明该函数有一个整型变量的参数 //然后再与外面的int 结合,说明函数的返回值是 //一个整型数据
  • 7. Int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与 //()结合,说明指针指向的是一个函数,然后再与()里的 //int 结合,说明函数有一个int 型的参数,再与最外层的 //int 结合,说明函数的返回类型是整型,所以P 是一个指 //向有一个整型参数且返回类型为整型的函数的指针 int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂 //从P 开始,先与()结合,说明P 是一个函数,然后进 //入()里面,与int 结合,说明函数有一个整型变量 //参数,然后再与外面的*结合,说明函数返回的是 //一个指针,,然后到最外面一层,先与[]结合,说明 //返回的指针指向的是一个数组,然后再与*结合,说 //明数组里的元素是指针,然后再与int 结合,说明指 //针指向的内容是整型数据.所以P 是一个参数为一个 //整数据且返回一个指向由整型指针变量组成的数组 //的指针变量的函数.
  • 8. 1、 指针的类型从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型: (1)int *ptr; //指针的类型是int * (2)char *ptr; //指针的类型是char * (3)int **ptr; //指针的类型是 int ** (4)int (*ptr)[3]; //指针的类型是 int(*)[3] (5)int *(*ptr)[4]; //指针的类型是 int *(*)[4] 小朋友怎么样?找出指针的类型的方法是不是很简单?
  • 9. 2、指针所指向的类型。 当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。 从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如: (1)int *ptr; //指针所指向的类型是int (2)char *ptr; //指针所指向的的类型是char (3)int **ptr; //指针所指向的的类型是 int * (4)int (*ptr)[3]; //指针所指向的的类型是 int()[3] (5)int *(*ptr)[4]; //指针所指向的的类型是 int *()[4] 在指针的算术运算中,指针所指向的类型有很大的作用。 注意:指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C++越来越熟悉时,你会发现,把与指针搅和在一起的“类型”这个概念分成“指针的类型”和“指针所指向的类型”两个概念,是精通指针的关键点之一。
  • 10. 3、指针的值,或者叫指针所指向的内存区或地址 ①、指针的值是指针本身存储的数值,一个指针只能保存一个地址,可以被赋值。这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。 ②、指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。 ③、指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。 小朋友注意:以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指向的类型是什么?该指针指向了哪里?
  • 11. 4、指针本身所占据的内存区。⑴、指针本身的内存地址可以通过&来运行得出,小朋友先自己思考一下?? #include using namespace std; int main() { int *a; int *a1; double *b; char *c; cout<<"指针p本身的内存地址是:"<<&a<
  • 12. ⑵、指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据了4个字节的长度。 指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。 #include using namespace std; int main() { int *a; double *b; char *c; cout<<"指针p本身的内存大小是:"<
  • 13. 二、指针的运算符
  • 14. 1.指针只支持4 种算术运算符:++,――,+,-.指针只能与整数加减.指针运算的原则是:每当指针的值增加时,它将指向其基本类型的下一个元素的存储单元.减少时则指向上一个元素的存储单元. 2.++,――运算符,假设int 型x 的地址为200,且int 型占4 个字节,定义int *p;p=&x;则p++的地址将是204,而 不是201,因为当指针p 的值增加时,它都将指向下一个int 型数据.减少时也是这样,如p――则,p 的地址将是196. 3.+,-,运算符,注意两个指针不能相加.例int *p;p=&x;假设x 的地址为200,则p+9 将的指针地址将是200+4*9=236, 即p 指向了从当前正指向的元素向下的第9 个元素. 4.两指针相减,同类型的一个指针减去另一个指针的值将是两个指针分开的基本类型的元素的个数. 指针的算术运算
  • 15. 指针运算符*和&地址运算符1.&地址运算符是一元运算符,能反回它的操作数的内存地址.如y=&x;把变量x 的地址输入到y 中,它与x 的值无关,比如x 的值为1000,而x 的地址为55 则,y 将接收到地址55。 2.*指针运算符是一元运算符,它是&运算符的相反形式,*运算符能反回位于其操作数所指定的地址的变量的值. 例如 y = &x;z = *y;假设x 的值为1000,地址为55,则第二条语句说明z 的值为1000,*y 把由y 所指向的内存的地址的变量x 的值赋给z。*运算符可理解为“在地址中”,则z=*y 可描术为“z 接收了在址址y 中的值。”。 3.其实可以把*y 当成一个变量来使用,即可以为*y 赋值等,例如*y=100;(*y)++;等,但要注意的是对*y 的操作相当 于是对此指针指向的地址中的变量的操作,即对*y=100 的赋值语句,相当于是x=100,而(*y)++则相当于x++。
  • 16. 三、指针和数组
  • 17. 1.在C++语言中使用没有下标的数组名会产生一个指向数组中第一个元素的指针.如char x[20];char *p;p=x;此语句说明将x 数组的第一个元素的地址赋给指针p. 2.*(p+4)和x[4]两句都可以访问数组中第5 个元素,这里假设int x[33];int *p;p=x;因为p 是指向数组x 的第一个元素地址的指针,而p+4 就是指向第五个元素的指针,而*(p+4)就是第五的个元素了. 3.p[i]语句相当于*(p+i)或x[i]即数组中第i+1 个元素的值,假设char x[20];char *p;p=x; 4、int x[5]; int **p=&x; //小朋友考虑一下为什么错?
  • 18. 解析:因为数组名本身就是表示数组第一个元素的地址,所以不用加上&,直接 int **p=x; 或者 int **p=&x[0];就行 例1: int array[10]={0,1,2,3,4,5,6,7,8,9},value; ... ... value=array[0];//也可写成:value=*array; value=array[3];//也可写成:value=*(array+3); value=array[4];//也可写成:value=*(array+4); 上例中,一般而言数组名array代表数组本身,类型是int [10],但如果把array看做指针的话,它指向数组的第0个单元,类型是int *,所指向的类型是数组单元的类型即int。因此*array等于0就一点也不奇怪了。同理,array+3是一个指向数组第3个单元的指针,所以*(array+3)等于3。其它依此类推。
  • 19. 例2: char *str[3]={ “Hello,this is a sample!”, “Hi,good morning.”, “Hello world” }; char s[80]; strcpy(s,str[0]);//也可写成strcpy(s,*str); strcpy(s,str[1]);//也可写成strcpy(s,*(str+1)); strcpy(s,str[2]);//也可写成strcpy(s,*(str+2)); ▲上例中,str是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str当作一个指针的话,它指向数组的第0号单元,它的类型是char**,它指向的类型是char *。 ▲*str也是一个指针,它的类型是char*,它所指向的类型是char,它指向的地址是字符串“Hello,this is a sample!”的第一个字符的地址,即‘H’的地址。 str+1也是一个指针,它指向数组的第1号单元,它的类型是char**,它指向的类型是char *。 ▲*(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向“Hi,good morning.”的第一个字符‘H’,等等。
  • 20. 小朋友考虑如何通过指针去访问数组的元素???#include using namespace std; int main() { int a[5]={1,2,3,4,5}; int *p=a; cout<<"通过指针去访问得出数组的元素:"<
  • 21. 下面总结一下数组的数组名的问题。1、声明了一个数组TYPE array[n],则数组名称array就有了两重含义:第一,它代表整个数组,它的类型是TYPE [n];第二,它是一个指针,该指针的类型是TYPE*,该指针指向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0号单元,该指针自己占有单独的内存区,注意它和数组第0号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的。 2、在不同的表达式中数组名array可以扮演不同的角色。 在表达式sizeof(array)中,数组名array代表数组本身,故这时sizeof函数测出的是整个数组的大小。 在表达式*array中,array扮演的是指针,因此这个表达式的结果就是数组第0号单元的值。sizeof(*array)测出的是数组单元的大小。 表达式array+n(其中n=0,1,2,....。)中,array扮演的是指针,故array+n的结果是一个指针,它的类型是TYPE*,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。
  • 22. 指针数组声明形式int *p[10];该语句声明了10个指针数组,每个数组中存储一个整数值地址.p[2]=&x;语句为指针变量的第三个元素赋予x变量的地址,现在要访问x变量的值需要编写*p[2].即访问指针数组第三个元素的地址指向的变量的值. int *p[10]和int (*p)[10]区别: int *p[10]表示定义十个数组指针。 int (*p)[10]表示定义了一个指向多维数组的指针。 小朋友注意:变量p先与[]结合生成很多数组元素,然后这些数组元素再与*形成很多的数组指针变量。加上()就改变了运算顺序。
  • 23. 四、指针与二维数组(对指针的透彻理解)(1)
  • 24. 1、两条基本准则:a、首先要明白,指针运算符的作用,我用一言以概之,你在哪里使用都不会错。指针运算符*的作用是求出*后面所指地址里的值。因此只要*后面的变量表示的是一个地址就可以使用*运算符,来求出这个地址中的值,你不用管这个地址的表示形式是怎样的,只要是地址就可以使用*来求出地址中的值。 b、[ ]这个运算符的的运算法则是,把左侧的地址加上[ ]内的偏移量然后再求指针运算,注意有[ ]运算符的地方就有个隐 含的指针,比如x[2]表示的就是将指针x偏移2个单位量后再求指针运算。也就说x[2]与*(x+2)是相等的。
  • 25. 2、对二维数组的讲解:a、对一维数组地址的详细讲解: 比如一维数组a[5],众所周知,一维数组的数组名a表示的是第一个元素a[0]的地址,也就是说数组名与&a[0]是等价的,因此数组名a的地址,并不是这个一维数组的数组的地址。那么&a,表示的又是什么呢?因为,a是一个一维数组,所以&a表示的就是这个一维数组的地址,也就是说&a中的地址是一个包含有4个元素的一维数组的地址。就好比int i中的&i,才表示的是这个变量i的地址一样。 b、对二维数组地址的讲解: 比如二维数组b[3][4],我们首先从一维开始分析,众所周知b[0],b[1]分别表示的是二维数组中第一行第一个元素和第二行第一个元素的地址,也就是说b[0]是与&b[0][0]等价的,b[1]是与&b[1][0]等价的。因此数组名就与&b[0]等价的,即b= &b[0].也就是说数组名表示的是二维数组中第一行所包含的一维数组的地址,说简单一点,就是说二维数组名是二维数组中第一行的行地址。因此二维数组名b所包含的地址中包含有二维数组中第二维中元素的个数的一维数组,也就是b的地址中包含一个含有4个元素的一维数组的地址(也就是所谓的数组的数组了)。
  • 26. c、对二维数组中地址的相加运算的讲解: 同样以b[3][4]为例来讲解,在上面讲到b[0]表示的是&b[0][0],因此对b[0]进行相加运算,比如b[0]+1,那么就将使地址偏移一个单位,也就是地址被偏移到了&b[0][1]处,也就是b[0]+1表示的是b[0][1]的地址。上面也讲到数组名b,表示的是一个一维数组的地址,因此对数组名进行偏移,比如b+1,则将使指针偏移一个一维数组的长度,也就是b+1,将是&b[1]的地址,因此b+1 的地址,表示的是二维数组中第二行所包含的一维数组的地址,简单点就是第二行的行地址。
  • 27. d、对二维数组的指针运算: *b 在上面讲解过,因为b表示的是二维数组中第一行所包含的一维数组的地址,因此*b=*(b+0)=*(&b[0])=b[0],所以*b表示的是二维数组中第一行第一个元素的地址,即*b=&b[0][0],用语言来描术*(b+0)就是,把数组名的地址偏移0个单位,然后再求这个地址所包含的值。在二维数组中,这个值就是指的b[0],因此这个值是与b[0][0]的地址相等的,结果就是*(b+0)与b是相等的,这在多维数组中是个怪现象,至少本人无法理解这是为什么。因为对同一个地址,进 行指针运算得到了不同的结果,比如*b 和*b[0],因为b 和b[0]都是相同的地址,但对他们进行指针运算却得到了不同的结果,*b得到了&b[0][0]的地址,而*b[0]得到了b[0][0]的值,这是对同一个地址进行指针运算却得到了不同的值,对于这个问题,无法理解。 *(b+1)和*(b+0) 对于*(b+1)也和*(b+0)是同样的道理,*(b+1)=b[1]。我们再来看*b[1],因为*b[1]=*(b[1]+0)=*(&b[1][0])=b[1][0],可以看出,这就是二维数组中第二行第一个元素的地址。 *(*(b+1)+1) 因为*(*(b+1)+1)=*(*(&b[1])+1)=*(b[1]+1)=*(&b[1][0]+1)=*(&b[1][1])=b[1][1],语言描术就是,b+1使地址偏移到了二维数组中第二行所包含的一维数组的地址(或第二行的行地址),然后再对这个行地址求指针(或求值)运算,因此就得 到第二行第一个元素的地址,然后再对这个地址偏移一个单位,就得到第二行第二个元素的地址,再对这个地址进行指针运算,就得到了这个元素的值,即b[1][1],其他的内容可以以止类推。 e、对二维数组的指针和[ ]的混合运算 在下面的指针和[ ]的混合计算中,要记住两点关键法则,记住了这两点在哪里计算都不会出错
  • 28. a、对于像b[1]这样的地址,最好应表示为&b[1][0]再进行偏移计算,比如对于b[1]+1,这不是直接在对b[1]加1,也就是b[1]+1 不等于b[2],因为b[1]表示的是第二行行1 个元素的地址,对其加1,应该表示的是第二行第二个元素的地址,也就是&b[1][1],而b[2]则表示的是第二行第一个元素的地址,因此错误,所以在计算时应把b[1]转换为&b[1][0]之后,才能直接进行地址的偏移,也就是说b[1]+1=&b[1][0]+1=&b[1][1],这样才能得到正确的结果,并且不会出错。 b、对于有小括号的地方,一定不要省略小括号。比如(&b[1])[1]与&b[1][1]将表示的是不同的结果,第二个是显然的,对于第一个(&b[1])[1]=*((&b[1])+1)=*(&b[1]+1)=*(&b[2])=b[2],可以看到,表示的是第3 行第1 个元素的地址,因此这两个的结果是显然不一样的。因此对于(b+1)[1] 这样的运算, 不能省略小括号,即 (b+1)[1]=(&b[1])[1]=*((&b[1])+1)=*(&b[1]+1)=*(&b[2])=b[2],如果省略了小括号,则是(b+1)[1]=&b[1][1],这将是不易发现的错误。因此这是两个完完全全不同的符案。 c、总结,指针和[ ]混合运算2点关键, 第1:应把是地址的[ ]运算,转换为地址的形式,比如b[1]应转换为&b[1][0]。因为只有这样才能进行直接的地址相加运算,即&b[1][0]+1=&b[1][1],而b[1]+1不等于b[2]。 第2:有小括号的地方不能省略小括号,如(b+1)[1]=(&b[1])[1]=*((&b[1])+1)=*(&b[1]+1)=*(&b[2])=b[2],也&b[1][1] 是完全不同的。
  • 29. (*(b+1))[1] ,(*b)[1] 最简单的理解方法为*(b+1)和语句b[1]等价,即(*(b+1))[1]和语句b[1][1]是相同的,也就是二组数组第2 行第2 个元素的值b[1][1],理解方法2逐条解释,如上面的解释*(b+1)表示的是二维数组b的第二行第一个元素的地址,也就是b[1],然后再与后面的[1]进行运算,就得到b[1][1]。或者(*(b+1))[1]=*((*(b+1))+1)=*(*(b+1)+1)这个语句上面解释过。 同理(*b)[1]=b[0][1] *(b+1)[1]: 计算方法1把[ ]分解为指针来计算:因为[ ]运算符高于指针,因此应先计算[ ]再计算指针,因为[1]表示的是把左侧的地址偏移1 个单位,再求指针,因此(b+1)[1]=*((b+1)+1) ,最后再计算一次指针运算,也就是 *(b+1)[1]=**((b+1)+1)=**(b+2)=*(*(b+2))=*b[2]=b[2][0],可以看到,最后表示的是b中第3行第一个元素的值。 计算方法2把指针化为[ ]来计算:*(b+1)[1]=*(&b[1])[1]=*(*(&b[1]+1))=**(&b[2])=*b[2]=b[2][0],注意*((&b[1])[1])表达式中应把(&b[1])括起来,若不括起来,则[ ]运算符的优先级高于&运算符,因此(&b[1])[1]与&b[1][1]是不一样的,后一个表示的是第二行第二个元素的地址,而头一个(&b[1])[1]则表示的是,对b 的第二行的行地址偏移1 个单位后再求指针的结果,也就是*(&b[1]+1)=*(&b[2])=b[2],所以性质是不一样的。
  • 30. (*b+1)[2] 计算方法1化简[ ]运算符:(*b+1)[2]=*((*b+1)+2)=*(*b+3)=*(&b[0][0]+3)=*(&b[0][3])=b[0][3],这里要注意*b表示的是b[0]=&b[0][0],在计算时最好不要代入b[0]来计算,而应把b[0]转换为&b[0][0]后再计算,因为b[0]+3,很容易被错误的计算为b[3],而实际上b[0]指向的是第一行第一个元素的地址,对其偏移3个单位应该是指向第一行第4个元素的地址,即&b[0][3],而b[3],则指向了第3行第1个元素的地址,这是不同的。 计算方法2化简*运算符:(*b+1)[2]=(b[0]+1)[2]=(&b[0][0]+1)[2]=(&b[0][1])[2]=*(&b[0][1]+2)=*(&b[0][3])=b[0][3],注意,在计算过程中小括号最好不要省略,省略了容易出错,因为[ ]运算符的优先给更高,如果省略了,某些地方将无法计算。比如(&b[0][0]+1)[2]=(&b[0][1])[2],如果省略掉括号,则成为&b[0][1][2],这对于二维数组来讲,是无法计算的。 (*(b+1))[5] 计算方法:(*(b+1))[5]=(*(&b[1]))[5]=(b[1])[5]=*(b[1]+5)=*(&b[1][0]+5)=*(&b[1][5])=b[1][5],结果等于第二行第6 个元素的值。
  • 31. f、注意,在二维或者多维数组中有个怪现象,比如对于多维数组a[n][m][i][j],那么这些地址是相同的,即数组名a, a[0], a[0][0], a[0][0][0], &a[0][0][0][0],都是相同的地址。而且对数组名依次求指针运算将得到,比如*a=a[0],*a[0]=a[0][0], *a[0][0]=a[0][0][0], *a[0][0][0]=a[0][0][0][0],可以看到,只有对最后这个地址求指针运算才真正得到了数组中的值, 因此对数组名求指针运算,要得到第一个元素的值,应该****a,也就是对4 维数组需要求4 次指针运算。同样可以 看到,对数组名进行的前三次指针运算的值都是相同的,即*a, **a, ***a和a的值都是&a[0][0][0][0]的值,这就是这个怪问题,按理说对地址求指针应该得到一个值,但对多维数组求指针,却得到的是同一个地址,只是这些地址所包含的内容不一样。
  • 32. 四、数组指针与二维数组讲解(2)
  • 33. 下面我们将以y[4]={1,2,3,4}这个一维数组为例来层层讲解,指针和数组的关系。 1、数组指针: 定义形式为:int (*p)[4];表示定义了一个指向多维数组的指针,即指针p指向的是一个数组,这个数组有4个元素, 对这个指针p 的赋值必须是有4 个int 元素的数组的地址,即只要包含有四个元素的数组的地址都能赋给指针p,不 管这个数组的行数是多少,但列数必须为4。即int y[4],x[22][4];都可以赋给指针p。赋值方式为p=&y或p=x,对于 &y和二维数组数组名前面已讲过,&y中的地址是一个包含有4个元素的一维数组的地址,二维数组名x也是一个含 有4 个元素的一维数组的地址,因此可以这样赋值。而这样赋值将是错误的p=y,或者p=x[0]; 因为y和x[0]的地址 只包含一个元素,他们不包含一个数组,因此出错。 2.注意()必须有,如果没有的话则,int *p[4];则是定义了一个指针数组,表示每个数组元素都是一个指针,即p[2]=&i; 指向一个int型的地址,而*p[2]则表示p[2]所指向的元素的值。
  • 34. 3.初始化数组指针p: a、当把int y[4]赋给指针p时p=y将是错误的,正确的方式为p=&y因为这时编译器会检查赋给指针p的元素是否是含有四个元素的数组,如果是就能正确的赋值,但语句p=y中的y代表的是数组y[4]第一行第一列的元素的地址,也就是&y[0]的地址,因此y 指向的地址只有一个元素,而指针p 要求的是有4 个元素的数组的地址,因此语句p=y将出错。而&y也表示的是一个地址,因为数组名y表示的是&y[0]的地址,因此&y=&(&y[0])。可以把&y理解为是数组y[4]的第一行的行地址,即&y包含了数组y[4]的第一行的所有元素,在这里&y包含有4个元素,因则p=&y才是正确的赋值方法,在这里要注意,数组的某行的行地址是第本行的第一个元素的地址是相同的。 b、把x[22][4]赋给指针p有几种方法,方法一:p=x;我们这样来理解该条语句,首先x[0]表示的是二维数组x[22][4]的第1行第一列元素的地址,这个地址包含一个元素,这是显而易见的,而数组名x也表示一个地址,但这个地址包含的是一个数组(即数组的数组),这个数组是包含4个元素的数组,这4个元素就是数组x第一行的4个元素,也就是说x表示的是数组x[22][4]的第1行的行地址,即数组名x就包含了数组x[22][4]第1行的4个元素,因此这种赋值方式是正确的。这时指针p就相当于是数组名一样,比如p[2][1]访问的就是数组x的第3行的第2个元素。
  • 35. c、方法二:p=x+1或者p=&x[1];注意必须要有地址运算符&,同理语句&x[1]表示的是数组x[22][4]第2行的行地址,因为x[1]表示的是数组x[22][4]第的第2行第1列的元素的地址,因此&x[1]表示的就是数组x的第2行的行地址,因为&x[1]这个地址包含了一个数组,这个数组的起始地址是从x[1]这个地址开始的,这,即数组x[22][4]中x[1]这一行的4 个元素。在这一行中包含了4 个元素。而x+1 本身就是指的x[1]的地址。这时指针p 的起始地址是&x[1],所以p[0][1]不再是访问的x的第一行第二个元素,而是访问的x的第二行第二个元素。 d、小朋友注意,再次提示,数组的某行的行地址是与本行的第一个元素的地址是相同的。
  • 36. 4.访问成员: a. 可以把数组指针p 当成数组名来理解,即可以像使用数组一样使用指针,比如p[1][2]访问数组x 中的第二行第 三个元素7。这种方法容易理解。 b. 访问数组元素的方法:记住二条法则*(p+1)与语句p[1]或者x[1]等价。 法则二:[ ]这个运算符的的运算法则是,把左侧的地址加上[ ]内的偏移量然后再求指针运算,注意有[ ]运算符 的地方就有个隐含的指针,比如x[2]表示的就是将指针x偏移2个单位量后再求指针运算。 5.int (*p)[4]与语句int x[][4]等价。 6.int (&p)[4]引用和指针有一些差别因为对引用赋值必须是变量的名字,所以int y[4];int (&p)[4]=y;是正确的而用&y将得到一个错误的结果,同样int x[22][4]; int (&p)[4]=x[1];才是正确的,不能用x+1或者&x[1]来初始化引用。int (&p)[4]=x也将得到一个错误的结果,只能是int (&p)[4]=x[0];对于引用来说语句p[0][1]将是错误的,只能是p[0],p[1],依次访问p 所引用的对象的第一个元素的值和第二个元素的值,int (&p)[4]=x[1];p[1]将访问的是p 所引用的第二个元素 的值也就是数组x第二行第2个元素的值。
  • 37. 例:指针与二维数组int (*p)[4]. int main() { int x[3][4]={1,2,3,4,5,6,7,8,9,10,11,12}; int y[4]={13,14,15,16}; int (*p)[4]=&y; //必须要有&运算符,因为这时的y只是一个有四个元素的名字和int i中的i是一样的只是个名字,所以必须要有&运算符 //p=y; //错误y现在不是地址,而是一个名字。 cout<
  • 38. cout<
  • 39. 五、空指针(零指针)和空类型指针
  • 40. 空指针(零指针)定义:指针变量值为零 #define NULL 0 p=NULL与未对p赋值不同 p指向地址为0的单元,系统保证该单元不作它用,表示指针变量值没有意义! 用途: 避免指针变量的非法引用 在程序中常作为状态比较
  • 41. 空类型指针(万能指针)void *类型指针 表示: void *p; 使用时要进行强制类型转换 表示不指定p是指向哪一种具体类型数据的指针变量
  • 42. 危险的指针用法使用NULL指针的内容 使用没有初始化的指针变量 使用已经被释放的指针内容 函数返回局部变量的地址 由函数申请的堆空间,而由调用者来释放
  • 43. #include int main() { int arr[3][4] = {{1,2,3,4},{5,6,7,8},{2,4,6,8}}; int *p1 = arr[0]; int (*pArr)[4] = arr; printf("sizeof(p1) = %u\n", sizeof(p1)); printf("sizeof(arr) = %u\n", sizeof(arr)); printf("sizeof(pArr) = %u\n", sizeof(pArr)); printf("sizeof(*arr) = %u\n", sizeof(*arr));//16 printf("sizeof(*(pArr + 1)) = %u\n", sizeof(*(pArr + 1))); printf("sizeof(*(arr + 1)) = %u\n", sizeof(*(arr + 1))); printf("sizeof(arr + 0) = %d\n", sizeof(arr + 0)); printf("%d\n", *(*(arr + 1))); printf("(*arr)[0] = %d\n", (*arr)[0]); printf("*(arr + 1)[0] = %u\n", *(arr + 1)[0]); printf("%d\n", sizeof(pArr)); printf("%d\n", sizeof(pArr[1])); return 0; }
  • 44. int main() { int a[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; int *p[4], i; for(i = 0; i < 4; i++) { p[i] = &a[i*3]; } printf("%d\n", *(*p+3)); printf("%d\n", *p[3]); printf("%d\n", **(p + 3)); printf("%d\n", p[3][3]); printf("%d\n", p[3][-3]); printf("%d\n", (p[3] - 3)[3]); return 0; }
  • 45. 指针类型分析int p; int *p; int p[3]; int *p[3]; int (*p)[3]; int **p; int **p[5][6]; int p(int); int (*p)(int); int *(*p(int))[3]; void (*signal (int signo, void (*func)(int))) (int);
  • 46. 解读复杂指针声明int (*func)(int *p); int (*func)(int *p, int (*f)(int*)); int (*func[5])(int *p); int (*(*func)[5])(int *p); int (*(*func)(int *p))[5]; int func(void) [5]; //ERROR int func[5](void); //ERROR
  • 47. 常量指针、指针常量、指向常量 的常量指针六、const指针
  • 48. 1.int x=1; const int *p=&x; 声明了一个指向int型常量的指针,也就是说不能用p来修改x的值,也就是说*p的值不能 被修改。即语句*p=2将是错误的。虽然p不能修改x的值,但可以通过变量x来修改x的值。p的声明并不代表p指 向的值实际上是一个常量,而只是对p而言这个值是常量。可以让p指向另一个地址,如int y=2;p=&y;这时p指向的 值为2,但指针p同样不能修改他所指向的变量y的值。 2.const int x=1; const int *p=&x;表明既不能用变量x来改变x的值,也不能用指针p来改变变量x的值。 const int x=1; int *p=&x;这样做将发生错误,因为如果把x的地址给了p,而指针p又修改了x的值时,这时x就违反 了是常量的规定,所以不允许这样做。 3.int x=1; int * const p=&x;这种方式使得指针p只能指向x的地址,即指针p的地址不能改变,但可以通过指针p来修 改p所指向的变量x的值。 4.int x=1; const int * const p=&x; 这种方式使得指针p既不能修改变量x的值,也不能改变p所指向的地址。 5.当const指针用作函数形参时int hyong(const int *p, int x)意味着,函数不能修改传递给指针p的变量的值。
  • 49. 常量指针常量指针是指--指向常量的指针,顾名思义,就是指针指向的是常量,即,它不能指向变量,它指向的内容不能被改变,不能通过指针来修改它指向的内容,但是指针自身不是常量,它自身的值可以改变,从而指向另一个常量。 int a=45; const int *p=&a; //或者 int const *p=&a; *p=50;//是错误的,因为是指向常量的指针。 int b=5; p=&b;//这是正确的,因为指针本身保存的地址是可以被改变的
  • 50. 指针常量指针常量是指--指针本身是常量。它指向的地址是不可改变的,但地址里的内容可以通过指针改变。它指向的地址将伴其一生,直到生命周期结束。有一点需要注意的是,指针常量在定义时必须同时赋初值。 注:也有人将这两个名称的定义与含义反过来认为:“指针常量:顾名思义它的中心词是“常量” 这是重点,指针就是一个修饰的作用。所以这里的指针还是一个变量,它的内容存放的是常量的地址。常量指针:关键字是指针,它是不能被改变的,因为指针总是指向地址的,所以它的意思是它指向的地址是不能被改变的”。但我个人认为后者不合理,所以使用前者。 int a=20; int * const p=&a; 注意:指针常量在定义时必须同时赋初值。 *p=25;正确的,可以 int b=25; p=&b;这样是错误的,因为是指针常量,所以不允许改变指针的值
  • 51. 小朋友分别运行两个代码,看错误都分别出现在哪一行,分析为什么错,通过你的理解自己修改代码。#include using namespace std; int main() { int a=25; int const *p=&a; int b=45; p=&b; *p=545; cout<<*p< using namespace std; int main() { int a=25; int * const p=&a; int b=45; p=&b; *p=545; cout<<*p<
  • 52. 指向常量的指针常量指向常量的指针常量就是结合前面两种指针类型的,它不仅是指向的常量,也就是所指向的内容是不可改变的。而且指针本身也是不可以改变,也就是指针本身所保存的地址是不可改变的。 int a=45; const int *const p=&a; *p=56;//错误,小朋友考虑为什么错误?? int b=23; p=&b;//错误,小朋友考虑为什么错误?? 总结一句:对于指向常量的指针常量一旦指向一个内存区域就不可改指向,同时也不能通过该指针去修改内存地址所保存的内容。
  • 53. 小结1.常量指针(常指针) int const*p 特点是指针指向的数值可以改变,然而指针所保存的地址却不可以改变。 2.指针常量 const int*p 特点是指针所保存的地址可以改变,然而指针所指向的值却不可以改变 3.指向常量的常指针 const int const*p 特点是指针所保存的地址不可变,指针所指向的数值也不可变 4.空指针 顾名思义,就是保存地址为空的指针 int *p=0;   //此时的指针p就是一个空指针 。
  • 54. 5.内存泄露 int *p=new int; p=new int; delete p;        //用delete来释放刚刚申请的那块内存区域,注意此时指针p还是可能依旧指向的这块区域(注意可能二字)。 这样就会有内存丢失,也就是所谓的内存泄漏。 因为第一行我们在堆中申请了一块int型的区域,并且用指针p指向该空间。但是紧接着我们又在堆中申请了一块int型的区域,用p指向了该空间。 那么先前申请的那块区域就无法访问了,但同时这块无法访问的区域没有被删除。所以就造成了内存泄漏。 6.迷途指针 int *p=new int;   //在堆中申请一块区域,用p指向这块区域 所以此时指针p的指向地址具有不确定性,故而称作迷途指针,避免的方法是将指针初始化为空指针
  • 55. 字符串常量在C++中字符串常量会被存储到程序的串表中,所以语句char *p;p=”hyong”;是合法的,该语句将字符串常量存储在串表中的地址赋给指针变量p.
  • 56. 指向指针的指针首先看这个语句: int **x,*y,z; z=25; y=&z;//z的地址赋给指针y //注意y没有加星号* x=&y;//指针y的地址赋给 //指向指针的指针x cout<<**x ;//输出z的值25 cout<<*x ;//输出y的值或z的地址50 cout<
  • 57. 指针的指针看上去有些令人费解。它们的声明有两个星号。例如: char ** cp; 如果有三个星号,那就是指针的指针的指针,四个星号就是指针的指针的指针的指针,依次类推。当你熟悉了简单的例子以后,就可以应付复杂的情况了。当然,实际程序中,一般也只用到 二级指针,三个星号不常见,更别说四个星号了。 指针的指针需要用到指针的地址。 char c='A'; char *p=&c; char **cp=&p; 通过指针的指针,不仅可以访问它指向的指针,还可以访问它指向的指针所指向的数据。下面就是几个这样的例子: char *p1=*cp; char c1=**cp; 你可能想知道这样的结构有什么用。利用指针的指针可以允许被调用函数修改局部指针变量和处理指针数组。 void FindCredit(int **); main() { int vals[]={7,6,5,-4,3,2,1,0}; int *fp=vals; FindCredit(&fp); printf(%d\n,*fp); }
  • 58. void FindCredit(int ** fpp) { while(**fpp!=0) if(**fpp<0) break; else (*fpp)++; } 首先用一个数组的地址初始化指针fp,然后把该指针的地址作为实参传递给函数FindCredit()。FindCredit()函数通过表达式**fpp间接地得到数组中的数据。为遍历数组以找到一个负值,FindCredit()函数进行自增运算的对象是调用者的指向数组的指针,而不是它自己的指向调用者指针的指针。语句(*fpp)++就是对形参指针指向的指针进行自增运算的。但是因为*运算符高于++运算符,所以圆括号在这里是必须的,如果没有圆括号,那么++运算符将作用于二重指针fpp上。
  • 59. (本页无文本内容)
  • 60. 七、指针和结构类型的关系
  • 61. 1、可以声明一个指向结构类型对象的指针。 例一: struct MyStruct { int a; int b; int c; } MyStruct ss={20,30,40};//声明了结构对象ss,并把ss的三个成员初始化为20,30和40。 MyStruct *ptr=&ss;//声明了一个指向结构对象ss的指针。它的类型 MyStruct*,它指向的类型是MyStruct。 int *pstr=(int*)&ss;//声明了一个指向结构对象ss的指针。但是它的类型和它指向的类型和ptr是不同的。 请问怎样通过指针ptr来访问ss的三个成员变量? 答案: ptr->a; ptr->b; ptr->c; 又请问怎样通过指针pstr来访问ss的三个成员变量? 答案: *pstr;//访问了ss的成员a。 *(pstr+1);//访问了ss的成员b。 *(pstr+2)//访问了ss的成员c。 呵呵,虽然我在我的MSVC++6.0上调式过上述代码,但是要知道,这样使用pstr来访问结构成员是不正规的,为了说明为什么不正规,让我们看看怎样通过指针来访问数组的各个单元:
  • 62. 例二: int array[3]={35,56,37}; int *pa=array; 通过指针pa访问数组array的三个单元的方法是: *pa;//访问了第0号单元 *(pa+1);//访问了第1号单元 *(pa+2);//访问了第2号单元 从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。 所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干个“填充字节”,这就导致各个成员之间可能会有若干个字节的空隙。 所以,在例二中,即使*pstr访问到了结构对象ss的第一个成员变量a,也不能保证*(pstr+1)就一定能访问到结构成员b。因为成员a和成员b之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字节,这倒是个不错的方法。 通过指针访问结构成员的正确方法应该是象例十二中使用指针ptr的方法。
  • 63. 八、指针和函数 (指针函数和函数指针)
  • 64. 1、指针函数是指带指针的函数,即本质是一个函数。函数返回类型是某一类型的指针 类型标识符 *函数名(参数表) int *f(x,y); 首先它是一个函数,只不过这个函数的返回值是一个地址值。函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有函数返回值,而且,在主调函数中,函数返回值必须赋给同类型的指针变量。 表示: float *fun(); float *p; p = fun(a); 注意指针函数与函数指针表示方法的不同,千万不要混淆。最简单的辨别方式就是看函数名前面的指针*号有没有被括号()包含,如果被包含就是函数指针,反之则是指针函数。 来讲详细一些吧!请看下面 指针函数: 当一个函数声明其返回值为一个指针时,实际上就是返回一个地址给调用函数,以用于需要指针或地址的表达式中。 格式: 类型说明符 * 函数名(参数) 当然了,由于返回的是一个地址,所以类型说明符一般都是int。 例如:int *GetDate(); int * aaa(int,int); 函数返回的是一个地址值,经常使用在返回数组的某一元素地址上。
  • 65. int * GetDate(int wk,int dy); main() { int wk,dy; do { printf(Enter week(1-5)day(1-7)\n); scanf(%d%d,&wk,&dy); } while(wk<1||wk>5||dy<1||dy>7); printf(%d\n,*GetDate(wk,dy)); } int * GetDate(int wk,int dy) { static int calendar[5][7]= { {1,2,3,4,5,6,7}, {8,9,10,11,12,13,14}, {15,16,17,18,19,20,21}, {22,23,24,25,26,27,28}, {29,30,31,-1} }; return &calendar[wk-1][dy-1]; } 程序应该是很好理解的,子函数返回的是数组某元素的地址。输出的是这个地址里的值。
  • 66. 2、函数指针是指向函数的指针变量,即本质是一个指针变量。  int (*f) (int x); /* 声明一个函数指针 */  f=func; /* 将func函数的首地址赋给指针f */ 指向函数的指针包含了函数的地址,可以通过它来调用函数。声明格式如下: 类型说明符 (*函数名)(参数) 其实这里不能称为函数名,应该叫做指针的变量名。这个特殊的指针指向一个返回整型值的函数。指针的声明笔削和它指向函数的声明保持一致。 指针名和指针运算符外面的括号改变了默认的运算符优先级。如果没有圆括号,就变成了一个返回整型指针的函数的原型声明。 例如: void (*fptr)(); 把函数的地址赋值给函数指针,可以采用下面两种形式: fptr=&Function; fptr=Function; 取地址运算符&不是必需的,因为单单一个函数标识符就标号表示了它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。 可以采用如下两种方式来通过指针调用函数: x=(*fptr)(); x=fptr(); 第二种格式看上去和函数调用无异。但是有些程序员倾向于使用第一种格式,因为它明确指出是通过指针而非函数名来调用函数的。下面举一个例子:
  • 67. void (*funcp)(); void FileFunc(),EditFunc(); main() { funcp=FileFunc; (*funcp)(); funcp=EditFunc; (*funcp)(); } void FileFunc() { printf(FileFunc\n); } void EditFunc() { printf(EditFunc\n); } 程序输出为: FileFunc EditFunc 主要的区别是一个是指针变量,一个是函数。在使用是必要要搞清楚才能正确使用
  • 68. 八、指针类型转换
  • 69. 当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式。在我们前面所举的例子中,绝大多数情况下,指针的类型和指针表达式的类型是一样的,指针所指向的类型和指针表达式所指向的类型是一样的。 1。 float f=12.3; 2。 float *fptr=&f; 3。 int *p; 在上面的例子中,假如我们想让指针p指向实数f,应该怎么搞?是用下面的语句吗? p=&f; 不对。因为指针p的类型是int*,它指向的类型是int。表达式&f的结果是一个指针,指针的类型是float*,它指向的类型是float。两者不一致,直接赋值的方法是不行的。至少在我的MSVC++6.0上,对指针的赋值语句要求赋值号两边的类型一致,所指向的类型也一致,其它的编译器上我没试过,大家可以试试。为了实现我们的目的,需要进行“强制类型转换”: p=(int*)&f; 如果有一个指针p,我们需要把它的类型和所指向的类型改为TYEP*和TYPE,那么语法格式是: (TYPE*)p; 这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE*,它指向的类型是TYPE,它指向的地址就是原指针指向的地址。而原来的指针p的一切属性都没有被修改。
  • 70. 一个函数如果使用了指针作为形参,那么在函数调用语句的实参和形参的结合过程中,也会发生指针类型的转换。 例1: void fun(char*); int a=125,b; fun((char*)&a); ... ... void fun(char*s) { char c; c=*(s+3);*(s+3)=*(s+0);*(s+0)=c; c=*(s+2);*(s+2)=*(s+1);*(s+1)=c; } 注意这是一个32位程序,故int类型占了四个字节,char类型占一个字节。函数fun的作用是把一个整数的四个字节的顺序来个颠倒。注意到了吗?在函数调用语句中,实?amp;a的结果是一个指针,它的类型是int *,它指向的类型是int。形参这个指针的类型是char*,它指向的类型是char。这样,在实参和形参的结合过程中,我们必须进行一次从int*类型到char*类型的转换。结合这个例子,我们可以这样来想象编译器进行转换的过程:编译器先构造一个临时指针 char*temp,然后执行temp=(char*)&a,最后再把temp的值传递给s。所以最后的结果是:s的类型是char*,它指向的类型是char,它指向的地址就是a的首地址。
  • 71. 我们已经知道,指针的值就是指针指向的地址,在32位程序中,指针的值其实是一个32位整数。那可不可以把一个整数当作指针的值直接赋给指针呢?就象下面的语句: unsigned int a; TYPE *ptr;//TYPE是int,char或结构类型等等类型。 ... ... a=20345686; ptr=20345686;//我们的目的是要使指针ptr指向地址20345686(十进制) ptr=a;//我们的目的是要使指针ptr指向地址20345686(十进制) 编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗?不,还有办法: unsigned int a; TYPE *ptr;//TYPE是int,char或结构类型等等类型。 ... ... a=某个数,这个数必须代表一个合法的地址; ptr=(TYPE*)a;//呵呵,这就可以了。
  • 72. 严格说来这里的(TYPE*)和指针类型转换中的(TYPE*)还不一样。这里的(TYPE*)的意思是把无符号整数a的值当作一个地址来看待。 上面强调了a的值必须代表一个合法的地址,否则的话,在你使用ptr的时候,就会出现非法操作错误。 想想能不能反过来,把指针指向的地址即指针的值当作一个整数取出来。完全可以。下面的例子演示了把一个指针的值当作一个整数取出来,然后再把这个整数当作一个地址赋给一个指针: 例2: int a=123,b; int *ptr=&a; char *str; b=(int)ptr;//把指针ptr的值当作一个整数取出来。 str=(char*)b;//把这个整数的值当作一个地址赋给指针str。 好了,现在我们已经知道了,可以把指针的值当作一个整数取出来,也可以把一个整数值当作地址赋给一个指针。
  • 73. 以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?(重点注意)
  • 74. 九、this指针
  • 75. 静态成员函数和非静态成员函数静态成员函数就是必须是在类中定义一个有static的成员函数,如static int func(....)不是出现在类中,则它不是一个静态成员函数,只是一个普通的全局函数,只不过由于 static 的限制,它只能在文件所在的编译单位内使用,不能在其它编译单位内使用。静态成员函数的声明除了在类体的函数声明前加上关键字static,以及不能声明为const或者volatile之外,与非静态成员函数相同。对于静态成员函数在类内声明,在类外定义的时候不要制定关键字static,只需要在类内声明的时候加static就行。 Static关键字   在类中,用static声明的成员变量为静态成员变量,它为该类的公用变量,在第一次使用时就被初始化,以后都不重新初始化。对于该类的所有对象来说,static成员变量只有一份。 用static声明的方法是静态方法,在调用该方法时,不会将对象的引用传递给它,所以在static方法中不可访问非static的成员
  • 76. 1、静态变量: static string str1 = "1234"; 2、静态对象 static SqlConnection cn = new SqlConnection(); 3、静态函数 class aa {     public static string test(string str)     {         return str & "111";     } } 调用 aa.test("123"); 4、非静态函数(就是需要实例化的) class aa {     public string test(string str)     {         return str & "111";     } } 调用 aa a1 = new aa(); a1.test("123");
  • 77. 其中静态变量和静态对象是比较好理解,也没有什么争论。争论最大的是静态函数这一块。 1、静态变量。在内存里是应该只有一份,不管是不是多线程,是不是多用户同时访问,静态变量只占用一份内存。 2、静态对象和静态变量也差不多,只有一份。 个人认为 SqlConnection 是不应该只用静态的,除非你的网站没有(或很少)并发访问的情况。 否则就很容易出现千军万马过独木桥的现象。挤不过去了就会瘫痪的。而且连接池也就无用武之地了。 3、非静态函数,就是在调用的时候必须先实例化,然后才能访问到。 实例化到底做了什么呢?是不是把整个类都“复制”了一份供调用者使用呢? 类的属性、函数的参数、返回值,这些应该是在实例化的时候产生一份,供调用者单独使用,其他的调用者是绝对访问不了的。 4、静态函数,直接调用不需要实例化,也没有“属性” 没有实例化,函数是一份的,多少人调用,都是这一份。那么函数用的参数和返回值呢?也是只有一份吗?这个好像就不是了,应该有多份,有一个调用的就产生一份,return 后自动销毁。我想应该是这样的,要不然多线程的时候不就乱了吗?当然函数内定义的变量、对象也应该是独立的(多份),有一个调用的就产生一份。   小结 静态函数和非静态函数最大的区别是,静态的不能访问所在类的属性和内的私有变量。 再有就是在调用的时候会不会“复制”一遍算法(函数本身)?(我感觉是都不会复制的)。 两者需要的参数,返回值应该是独立的,一个调用者一份,一个调用者不会访问到其它调用者的参数和返回值。
  • 78. this指针只能在一个类的成员函数中调用,它表示当前对象的地址。下面是一个例子: void Date::setMonth( int mn ) { month = mn; // 这三句是等价的 this->month = mn; (*this).month = mn; } 1. this只能在成员函数中使用。 全局函数,静态函数都不能使用this。 实际上,成员函数默认第一个参数为T* const register this。 如: class A{public: int func(int p){}}; 其中,func的原型在编译器看来应该是: int func(A* const register this, int p); 2. 由此可见,this在成员函数的开始前构造的,在成员的结束后清除。 这个生命周期同任一个函数的参数是一样的,没有任何区别。 当调用一个类的成员函数时,编译器将类的指针作为函数的this参数传递进去。如: A a; a.func(10); 此处,编译器将会编译成: A::func(&a, 10); 看起来和静态函数没差别,对吗?不过,区别还是有的。编译器通常会对this指针做一些优化的,因此,this指针的传递效率比较高--如vc通常是通过ecx寄存器来传递this参数。
  • 79. 3. 回答 #1:this指针是什么时候创建的? this在成员函数的开始执行前构造的,在成员的执行结束后清除。 #2:this指针存放在何处? 堆,栈,全局变量,还是其他? this指针会因编译器不同,而放置的位置不同。可能是栈,也可能是寄存器,甚至全局变量。 #3:this指针如何传递给类中函数的?绑定?还是在函数参数的首参数就是this指针.那么this指针又是如何找到类实例后函数的? this是通过函数参数的首参数来传递的。this指针是在调用之前生成的。类实例后的函数,没有这个说法。类在实例化时,只分配类中的变量空间,并没有为函数分配空间。自从类的函数定义完成后,它就在那儿,不会跑的。 #4:this指针如何访问类中变量的/? 如果不是类,而是结构的话,那么,如何通过结构指针来访问结构中的变量呢?如果你明白这一点的话,那就很好理解这个问题了。 在C++中,类和结构是只有一个区别的:类的成员默认是private,而结构是public。 this是类的指针,如果换成结构,那this就是结构的指针了。 #5:我们只有获得一个对象后,才能通过对象使用this指针,如果我们知道一个对象this指针的位置可以直接使用吗? this指针只有在成员函数中才有定义。因此,你获得一个对象后,也不能通过对象使用this指针。所以,我们也无法知道一个对象的this指针的位置(只有在成员函数里才有this指针的位置)。当然,在成员函数里,你是可以知道this指针的位置的(可以&this获得),也可以直接使用的。
  • 80. #6:每个类编译后,是否创建一个类中函数表保存函数指针,以便用来调用函数? 普通的类函数(不论是成员函数,还是静态函数),都不会创建一个函数表来保存函数指针的。只有虚函数才会被放到函数表中。但是,既使是虚函数,如果编译器能明确知道调用的是哪个函数,编译器就不会通过函数表中的指针来间接调用,而是会直接调用该函数。 7:这些编译器如何做到的?8:能否模拟实现? 知道原理后,这两个问题就很容易理解了。 其实,模拟实现this的调用,在很多场合下,很多人都做过。 例如,系统回调函数。系统回调函数有很多,如定时,线程啊什么的。 举一个线程的例子: #include #include using namespace std; class A{ int n; public: static void run(void* pThis){ A* this_ = (A*)pThis; this_->process(); } void process(){} }; int main(){ A a; _beginthread(A::run,0,&a); return 0; }这里就是定义一个静态函数来模拟成员函数。 如果有这样的错误出现 error C2065: '_beginthread' : undeclared identifier 解决办法: Project -> Settings... 中 选C/C++ 的页,然后在Category分类中选Code Generation 然在它下面的Use run-time libaray 中选Multithreaded(或 Multithreader DLL 或 Debug Multithreader DLL 或 Debug Multithreader ) 保存settings,再 build 一次就ok了
  • 81. 也有许多C语言写的程序,模拟了类的实现。如freetype库等等。 其实,有用过C语言的人,大多都模拟过。只是当时没有明确的概念罢了。 如: typedef struct student{ int age; int no; int scores; }Student; void initStudent(Student* pstudent); void addScore(Student* pstudent, int score); ... 如果你把 pstudent改成this,那就一样了。 它相当于: class Student{ public: int age; int no; int scores; void initStudent(); void addScore(int score); } const常量可以有物理存放的空间,因此是可以取地址的 ///this指针是在创建对象前创建. this指针放在栈上,在编译时刻已经确定. 并且当一个对象创建后,并且运行整个程序运行期间只有一个this指针. 问题的提出:编写程序实现对象资源的拷贝(要求使用this指针)。
  • 82. #include #include using namespace std; class student{ private: char *name; int id; public: student(char *pName="no name",int ssId=0) { id=ssId; name=new char[strlen(pName)+1]; strcpy(name,pName); cout<<"construct new student "<
  • 83. void disp() { cout<<"Name:"<